fit_transform () akzeptiert 2 Positionsargumente, aber 3 wurden mit LabelBinarizer angegeben


77

Ich bin völlig neu im maschinellen Lernen und habe mit unbeaufsichtigten Lerntechniken gearbeitet.

Das Bild zeigt meine Beispieldaten (nach allen Reinigungen). Screenshot: Beispieldaten

Ich habe diese zwei Pipline gebaut, um die Daten zu bereinigen:

num_attribs = list(housing_num)
cat_attribs = ["ocean_proximity"]

print(type(num_attribs))

num_pipeline = Pipeline([
    ('selector', DataFrameSelector(num_attribs)),
    ('imputer', Imputer(strategy="median")),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scaler', StandardScaler()),
])

cat_pipeline = Pipeline([
    ('selector', DataFrameSelector(cat_attribs)),
    ('label_binarizer', LabelBinarizer())
])

Dann habe ich die Vereinigung dieser beiden Pipelines durchgeführt und der Code für dieselbe ist unten gezeigt:

from sklearn.pipeline import FeatureUnion

full_pipeline = FeatureUnion(transformer_list=[
        ("num_pipeline", num_pipeline),
        ("cat_pipeline", cat_pipeline),
    ])

Jetzt versuche ich, fit_transform für die Daten durchzuführen, aber es zeigt mir den Fehler.

Code für die Transformation:

housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared

Fehlermeldung:

fit_transform () akzeptiert 2 Positionsargumente, aber 3 wurden angegeben


4
LabelBinarizer sollte nicht mit X (Features) verwendet werden, sondern ist nur für Labels vorgesehen. Daher werden die Methoden fit und fit_transform so geändert, dass sie nur ein einzelnes Objekt y enthalten. Die Pipeline (die mit Funktionen arbeitet) versucht jedoch, sowohl X als auch Y an sie zu senden. Daher der Fehler.
Vivek Kumar

4
Sie sollten LabelBinarizer außerhalb der Pipeline verwenden, um die kategorialen Funktionen in One-Hot-Codierung zu konvertieren oder möglicherweise zu verwenden pandas.get_dummies().
Vivek Kumar

Antworten:


71

Das Problem:

Die Pipeline geht davon aus, dass die fit_transformMethode von LabelBinarizer für drei Positionsargumente definiert ist:

def fit_transform(self, x, y)
    ...rest of the code

während es definiert ist, nur zwei zu nehmen:

def fit_transform(self, x):
    ...rest of the code

Mögliche Lösung:

Dies kann gelöst werden, indem ein benutzerdefinierter Transformator erstellt wird, der drei Positionsargumente verarbeiten kann:

  1. Importiere und erstelle eine neue Klasse:

    from sklearn.base import TransformerMixin #gives fit_transform method for free
    class MyLabelBinarizer(TransformerMixin):
        def __init__(self, *args, **kwargs):
            self.encoder = LabelBinarizer(*args, **kwargs)
        def fit(self, x, y=0):
            self.encoder.fit(x)
            return self
        def transform(self, x, y=0):
            return self.encoder.transform(x)
    
  2. Behalten Sie Ihren Code nur bei, anstatt LabelBinarizer () zu verwenden. Verwenden Sie die von uns erstellte Klasse: MyLabelBinarizer ().


Hinweis: Wenn Sie auf LabelBinarizer-Attribute (z. B. classes_) zugreifen möchten, fügen Sie der fitMethode die folgende Zeile hinzu :

    self.classes_, self.y_type_, self.sparse_input_ = self.encoder.classes_, self.encoder.y_type_, self.encoder.sparse_input_

Ich schlage diese Alternative für den Klassenkörper vor. Was denkst du (Entschuldigung für die Formatierung)? def fit (self, X, y = None): \ n return self \ n def transform (self, X, y = None): \ n return LabelBinarizer (). fit_transform (X)
otonglet

2
Ich erhalte eine Fehlermeldung - '<' wird zwischen Instanzen von 'str' und 'int' nicht unterstützt. Was könnte der Grund dafür sein. In kategorialen Spalten fehlen keine Werte.
Chandra

@Chandra Ich muss Ihren Code sehen, um Ihnen zu helfen, aber dieser Fehler kann generiert werden, wenn Sie eine Zeichenfolge an einen der Parameter pos_labels und neg_labels übergeben (z. B. LabelBinarizer (pos_labels = "good"))
Zaid E.

@otonglet Ich denke, das funktioniert, aber wenn (fit_transform) drin ist, bedeutet dies, dass jedes Mal, wenn Sie die neue Klasse aufrufen (transformieren), die Anpassung erneut durchgeführt wird. Dies kann zu unerwartetem Verhalten führen, wenn Sie es in einem Testsatz mit wenigen Beispielen und vielen Beschriftungskategorien verwenden. Außerdem wird der Beitrag aktualisiert, um einfacheren Code zu erhalten.
Zaid E.

62

Ich glaube, Ihr Beispiel stammt aus dem Buch Hands-On Machine Learning mit Scikit-Learn & TensorFlow . Leider bin ich auch auf dieses Problem gestoßen. Eine kürzlich vorgenommene Änderung in scikit-learn( 0.19.0) hat LabelBinarizerdie fit_transformMethode geändert . Leider LabelBinarizersollte nie funktionieren, wie dieses Beispiel es verwendet. Informationen zur Änderung finden Sie hier und hier .

Bis sie eine Lösung dafür finden, können Sie die vorherige Version ( 0.18.0) wie folgt installieren :

$ pip install scikit-learn==0.18.0

Danach sollte Ihr Code ohne Probleme ausgeführt werden.

In Zukunft sieht es so aus, als ob die richtige Lösung darin bestehen könnte, eine CategoricalEncoderKlasse oder ähnliches zu verwenden. Sie haben offenbar jahrelang versucht, dieses Problem zu lösen. Sie können die neue Klasse hier sehen und das Problem hier weiter diskutieren .


1
Dies ist an sich kein Fehler. LabelBinarizer soll nicht mit Features ( X) verwendet werden, sondern nur für Labels ( y). Daher haben sie aufgehört, sowohl X als auch y an die Methode zu senden.
Vivek Kumar

Sie arbeiten an OneHotEncoder, der Zeichenfolgenfunktionen unterstützt. github.com/scikit-learn/scikit-learn/issues/4920
Vivek Kumar

6
Vielen Dank für Ihre Antwort. Und ja, ich bin im Lernmodus mit "Praktisches maschinelles Lernen mit Scikit-Learn & TensorFlow". Also ja, anstatt die vorherige Version zu verwenden, habe ich einen benutzerdefinierten Binarizer bekommen, der für mich funktioniert hat. Link für den Code ist: github.com/scikit-learn/scikit-learn/pull/7375/…
Viral Parmar

Ich habe die Frage bearbeitet, um das Problem weiter zu erläutern und zu verdeutlichen, dass es sich nicht um einen Fehler handelt.
Steven Oxley

Vielen Dank. Ich bin mit dem gleichen Problem festgefahren und das hat funktioniert.
Gdanton

11

Ich denke, Sie gehen die Beispiele aus dem Buch durch: Praktisches maschinelles Lernen mit Scikit Learn und Tensorflow . Beim Durchlaufen des Beispiels in Kapitel 2 bin ich auf dasselbe Problem gestoßen.

Wie von anderen erwähnt, liegt das Problem im LabelBinarizer von sklearn. Die Methode fit_transform benötigt weniger Argumente als andere Transformatoren in der Pipeline. (Nur y, wenn andere Transformatoren normalerweise sowohl X als auch y verwenden, siehe hier für Details). Deshalb haben wir beim Ausführen von Pipeline.fit_transform mehr Argumente in diesen Transformator eingespeist als erforderlich.

Eine einfache Lösung, die ich verwendet habe, besteht darin, nur OneHotEncoder zu verwenden und "sparse" auf "False" zu setzen, um sicherzustellen, dass die Ausgabe ein numpy-Array ist, das mit der Ausgabe von num_pipeline identisch ist. (Auf diese Weise müssen Sie Ihren eigenen benutzerdefinierten Encoder nicht codieren.)

Ihre ursprüngliche cat_pipeline:

cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('label_binarizer', LabelBinarizer())
])

Sie können diesen Teil einfach ändern in:

cat_pipeline = Pipeline([
('selector', DataFrameSelector(cat_attribs)),
('one_hot_encoder', OneHotEncoder(sparse=False))
])

Sie können von hier aus gehen und alles sollte funktionieren.


5
Einige Seiten früher verwendet der Autor ein 'reshape ()' im OneHotEncoder. Wie kommt es, dass wir diese Umformung () der kategorialen Daten nicht verwenden müssen, wenn wir jetzt den LabelBinarizer durch den OneHotEncoder ersetzen?
tobias.henn

@ tobias.henn wahrscheinlich, weil der DataFrameSelector ein numpy-Array anstelle eines pandas-Datenrahmens zurückgibt. Ich gehe davon aus, dass dieses numpy-Array die richtigen Abmessungen hat und nicht umgeformt werden muss.
EternusVia

10

Da LabelBinarizer nicht mehr als 2 Positionsargumente zulässt, sollten Sie Ihren benutzerdefinierten Binarizer wie folgt erstellen

class CustomLabelBinarizer(BaseEstimator, TransformerMixin):
    def __init__(self, sparse_output=False):
        self.sparse_output = sparse_output
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        enc = LabelBinarizer(sparse_output=self.sparse_output)
        return enc.fit_transform(X)

num_attribs = list(housing_num)
cat_attribs = ['ocean_proximity']

num_pipeline = Pipeline([
    ('selector', DataFrameSelector(num_attribs)),
    ('imputer', Imputer(strategy='median')),
    ('attribs_adder', CombinedAttributesAdder()),
    ('std_scalar', StandardScaler())
])

cat_pipeline = Pipeline([
    ('selector', DataFrameSelector(cat_attribs)),
    ('label_binarizer', CustomLabelBinarizer())
])

full_pipeline = FeatureUnion(transformer_list=[
    ('num_pipeline', num_pipeline),
    ('cat_pipeline', cat_pipeline)
])

housing_prepared = full_pipeline.fit_transform(new_housing)

1
Diese Implementierung von CustomLabelBinarizer verursacht später in diesem Kapitel ein Problem, wenn die Pipeline auf eine Teilmenge von Daten angewendet wird. Unter stackoverflow.com/a/49993974/167920 finden Sie eine Beschreibung des Problems und eine bessere Implementierung von CustomLabelBinarizer
Wallace Kelly

7

Ich bin auf dasselbe Problem gestoßen und habe es zum Laufen gebracht, indem ich die im Github-Repo des Buches angegebene Problemumgehung angewendet habe .

Warnung: In früheren Versionen des Buches wurde zu diesem Zeitpunkt die LabelBinarizer-Klasse verwendet. Auch dies war falsch: Genau wie die LabelEncoder-Klasse wurde die LabelBinarizer-Klasse so konzipiert, dass Etiketten und keine Eingabefunktionen vorverarbeitet werden. Eine bessere Lösung ist die Verwendung der kommenden CategoricalEncoder-Klasse von Scikit-Learn: Sie wird in Kürze zu Scikit-Learn hinzugefügt. In der Zwischenzeit können Sie den folgenden Code verwenden (kopiert von Pull Request # 9151 ).

Um Ihnen einige Probleme zu ersparen, fügen Sie die Problemumgehung ein und führen Sie sie in einer vorherigen Zelle aus:

# Definition of the CategoricalEncoder class, copied from PR #9151.
# Just run this cell, or copy it to your code, do not try to understand it (yet).

from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.utils import check_array
from sklearn.preprocessing import LabelEncoder
from scipy import sparse

class CategoricalEncoder(BaseEstimator, TransformerMixin):
    def __init__(self, encoding='onehot', categories='auto', dtype=np.float64,
                 handle_unknown='error'):
        self.encoding = encoding
        self.categories = categories
        self.dtype = dtype
        self.handle_unknown = handle_unknown

    def fit(self, X, y=None):
        """Fit the CategoricalEncoder to X.
        Parameters
        ----------
        X : array-like, shape [n_samples, n_feature]
            The data to determine the categories of each feature.
        Returns
        -------
        self
        """

        if self.encoding not in ['onehot', 'onehot-dense', 'ordinal']:
            template = ("encoding should be either 'onehot', 'onehot-dense' "
                        "or 'ordinal', got %s")
            raise ValueError(template % self.handle_unknown)

        if self.handle_unknown not in ['error', 'ignore']:
            template = ("handle_unknown should be either 'error' or "
                        "'ignore', got %s")
            raise ValueError(template % self.handle_unknown)

        if self.encoding == 'ordinal' and self.handle_unknown == 'ignore':
            raise ValueError("handle_unknown='ignore' is not supported for"
                             " encoding='ordinal'")

        X = check_array(X, dtype=np.object, accept_sparse='csc', copy=True)
        n_samples, n_features = X.shape

        self._label_encoders_ = [LabelEncoder() for _ in range(n_features)]

        for i in range(n_features):
            le = self._label_encoders_[i]
            Xi = X[:, i]
            if self.categories == 'auto':
                le.fit(Xi)
            else:
                valid_mask = np.in1d(Xi, self.categories[i])
                if not np.all(valid_mask):
                    if self.handle_unknown == 'error':
                        diff = np.unique(Xi[~valid_mask])
                        msg = ("Found unknown categories {0} in column {1}"
                               " during fit".format(diff, i))
                        raise ValueError(msg)
                le.classes_ = np.array(np.sort(self.categories[i]))

        self.categories_ = [le.classes_ for le in self._label_encoders_]

        return self

    def transform(self, X):
        """Transform X using one-hot encoding.
        Parameters
        ----------
        X : array-like, shape [n_samples, n_features]
            The data to encode.
        Returns
        -------
        X_out : sparse matrix or a 2-d array
            Transformed input.
        """
        X = check_array(X, accept_sparse='csc', dtype=np.object, copy=True)
        n_samples, n_features = X.shape
        X_int = np.zeros_like(X, dtype=np.int)
        X_mask = np.ones_like(X, dtype=np.bool)

        for i in range(n_features):
            valid_mask = np.in1d(X[:, i], self.categories_[i])

            if not np.all(valid_mask):
                if self.handle_unknown == 'error':
                    diff = np.unique(X[~valid_mask, i])
                    msg = ("Found unknown categories {0} in column {1}"
                           " during transform".format(diff, i))
                    raise ValueError(msg)
                else:
                    # Set the problematic rows to an acceptable value and
                    # continue `The rows are marked `X_mask` and will be
                    # removed later.
                    X_mask[:, i] = valid_mask
                    X[:, i][~valid_mask] = self.categories_[i][0]
            X_int[:, i] = self._label_encoders_[i].transform(X[:, i])

        if self.encoding == 'ordinal':
            return X_int.astype(self.dtype, copy=False)

        mask = X_mask.ravel()
        n_values = [cats.shape[0] for cats in self.categories_]
        n_values = np.array([0] + n_values)
        indices = np.cumsum(n_values)

        column_indices = (X_int + indices[:-1]).ravel()[mask]
        row_indices = np.repeat(np.arange(n_samples, dtype=np.int32),
                                n_features)[mask]
        data = np.ones(n_samples * n_features)[mask]

        out = sparse.csc_matrix((data, (row_indices, column_indices)),
                                shape=(n_samples, indices[-1]),
                                dtype=self.dtype).tocsr()
        if self.encoding == 'onehot-dense':
            return out.toarray()
        else:
            return out

5

Sie können einfach die folgende Klasse kurz vor Ihrer Pipeline definieren:

class NewLabelBinarizer(LabelBinarizer):
    def fit(self, X, y=None):
        return super(NewLabelBinarizer, self).fit(X)
    def transform(self, X, y=None):
        return super(NewLabelBinarizer, self).transform(X)
    def fit_transform(self, X, y=None):
        return super(NewLabelBinarizer, self).fit(X).transform(X)

Dann ist der Rest des Codes wie der im Buch erwähnte mit einer winzigen Änderung cat_pipelinevor der Verkettung der Pipeline - folgen Sie wie folgt:

cat_pipeline = Pipeline([
    ("selector", DataFrameSelector(cat_attribs)),
    ("label_binarizer", NewLabelBinarizer())])

Bist du fertig!


3

Vergessen Sie LaberBinarizer und verwenden Sie stattdessen OneHotEncoder.

Wenn Sie vor OneHotEncoder einen LabelEncoder verwenden, um Kategorien in Ganzzahlen zu konvertieren, können Sie den OneHotEncoder jetzt direkt verwenden.


Dies könnte ein Kommentar sein, aber trotzdem danke für Ihre Antwort
El.Hum

3

Ich habe auch das gleiche Problem konfrontiert. Der folgende Link hat mir bei der Behebung dieses Problems geholfen. https://github.com/ageron/handson-ml/issues/75

Zusammenfassende Änderungen

1) Definieren Sie die folgende Klasse in Ihrem Notizbuch

class SupervisionFriendlyLabelBinarizer(LabelBinarizer):
    def fit_transform(self, X, y=None):
        return super(SupervisionFriendlyLabelBinarizer,self).fit_transform(X)

2) Ändern Sie den folgenden Code

cat_pipeline = Pipeline([('selector', DataFrameSelector(cat_attribs)),
                         ('label_binarizer', SupervisionFriendlyLabelBinarizer()),])

3) Führen Sie das Notebook erneut aus. Sie können jetzt laufen


1

Ich habe das gleiche Problem und wurde mithilfe von DataFrameMapper behoben (sklearn_pandas muss installiert werden):

from sklearn_pandas import DataFrameMapper
cat_pipeline = Pipeline([
    ('label_binarizer', DataFrameMapper([(cat_attribs, LabelBinarizer())])),
])

LabelBinarizer () erstellt OHE-Funktionen. Sie können sklearn.preprocessing.LabelEncoder () jedoch direkt in einer DataFrameMapper-Pipeline verwenden. Zumindest für mich hat es gut funktioniert.

1

Sie können einen weiteren benutzerdefinierten Transformer erstellen, der die Codierung für Sie übernimmt.

class CustomLabelEncode(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return LabelEncoder().fit_transform(X);

In diesem Beispiel haben wir LabelEncoding durchgeführt, aber Sie können auch LabelBinarizer verwenden


0

Am Ende rollte ich meine eigenen

class LabelBinarizer(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        X = self.prep(X)
        unique_vals = []
        for column in X.T:
            unique_vals.append(np.unique(column))
        self.unique_vals = unique_vals
    def transform(self, X, y=None):
        X = self.prep(X)
        unique_vals = self.unique_vals
        new_columns = []
        for i, column in enumerate(X.T):
            num_uniq_vals = len(unique_vals[i])
            encoder_ring = dict(zip(unique_vals[i], range(len(unique_vals[i]))))
            f = lambda val: encoder_ring[val]
            f = np.vectorize(f, otypes=[np.int])
            new_column = np.array([f(column)])
            if num_uniq_vals <= 2:
                new_columns.append(new_column)
            else:
                one_hots = np.zeros([num_uniq_vals, len(column)], np.int)
                one_hots[new_column, range(len(column))]=1
                new_columns.append(one_hots)
        new_columns = np.concatenate(new_columns, axis=0).T        
        return new_columns

    def fit_transform(self, X, y=None):
        self.fit(X)
        return self.transform(X)

    @staticmethod
    def prep(X):
        shape = X.shape
        if len(shape) == 1:
            X = X.values.reshape(shape[0], 1)
        return X

Scheint zu funktionieren

lbn = LabelBinarizer()
thingy = np.array([['male','male','female', 'male'], ['A', 'B', 'A', 'C']]).T
lbn.fit(thingy)
lbn.transform(thingy)

kehrt zurück

array([[1, 1, 0, 0],
       [1, 0, 1, 0],
       [0, 1, 0, 0],
       [1, 0, 0, 1]])

-1

So führen Sie eine One-Hot-Codierung für mehrere kategoriale Funktionen durch: wir eine neue Klasse erstellen, die unseren eigenen Binärisierer für mehrere kategoriale Features anpasst und ihn wie folgt in die kategoriale Pipeline einfügt.

Angenommen, es CAT_FEATURES = ['cat_feature1', 'cat_feature2']handelt sich um eine Liste kategorialer Merkmale. Die folgenden Skripte sollen das Problem beheben und das produzieren, was wir wollen.

import pandas as pd
from sklearn.pipeline import Pipeline
from sklearn.base import BaseEstimator, TransformerMixin

class CustomLabelBinarizer(BaseEstimator, TransformerMixin):
    """Perform one-hot encoding to categorical features."""
    def __init__(self, cat_features):
        self.cat_features = cat_features

    def fit(self, X_cat, y=None):
        return self

    def transform(self, X_cat):
        X_cat_df = pd.DataFrame(X_cat, columns=self.cat_features)
        X_onehot_df = pd.get_dummies(X_cat_df, columns=self.cat_features)
        return X_onehot_df.values

# Pipeline for categorical features.
cat_pipeline = Pipeline([
    ('selector', DataFrameSelector(CAT_FEATURES)),
    ('onehot_encoder', CustomLabelBinarizer(CAT_FEATURES))
])

Diese Lösung funktioniert für die Verarbeitung des Trainingssatzes, schlägt jedoch fehl, wenn der Testsatz anschließend verarbeitet wird. Wenn dieser Schritt in der Pipeline ausgeführt wird, werden nur Spalten für Kategorien angehängt, die in der aktuell verarbeiteten Gruppe vorhanden sind. Das heißt, wenn der Trainingssatz mehr Kategorien als der Testsatz hatte, fehlen nach der Transformation des Testsatzes einige Spalten.
CodingButStillAlive

Danke für die Diskussion. Um Datenlecks zu vermeiden, müssen wir jedoch im Allgemeinen zuerst die Trainings- und Testdaten trennen, dann Modelle für maschinelles Lernen von den Trainingsdaten trainieren und mit den resultierenden Modellen die zukünftige Reaktion mit Testdaten weiter vorhersagen. Daher möchten wir das Feature-Engineering einschließlich Normalisierung usw. für die Workflow-Automatisierung standardisieren. Und deshalb möchten wir Pipeline verwenden. Daher wird erwartet, dass das Problem für die Funktionskategorien einiger Testdaten möglicherweise fehlt. Genau wie das alte Sprichwort sagt: "Man kann ein Pferd nicht arbeiten lassen, ohne es zu füttern."
Bowenli

-1

Wir können einfach das Attribut sparce_output = False hinzufügen

cat_pipeline = Pipeline([
  ('selector', DataFrameSelector(cat_attribs)),
  ('label_binarizer', LabelBinarizer(sparse_output=False)),   
])
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.