fit_transform () приймає 2 позиційні аргументи, але 3 були задані за допомогою LabelBinarizer


77

Я абсолютно новачок у машинному навчанні, і я працюю з технікою навчання без нагляду.

Зображення показує мої зразки даних (після очищення) Знімок екрана: Зразки даних

У мене є два Pipline, побудовані для очищення даних:

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())
])

Потім я об’єднав ці два конвеєри, і код для них наведено нижче:

from sklearn.pipeline import FeatureUnion

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

Зараз я намагаюся зробити fit_transform на даних, але це показує мені помилку.

Код трансформації:

housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared

Повідомлення про помилку:

fit_transform () приймає 2 позиційні аргументи, але було надано 3


4
LabelBinarizer не повинен використовуватися з X (Особливості), але призначений лише для міток. Отже, методи fit та fit_transform змінені, щоб включати лише один об'єкт y. Але конвеєр (який працює над функціями) спробує надіслати на нього як X, так і y. Звідси і помилка.
Vivek Kumar

4
Вам слід використовувати LabelBinarizer за межами конвеєра, щоб перетворити категоріальні функції на одноразове кодування або, можливо, використовувати pandas.get_dummies().
Vivek Kumar

Відповіді:


72

Проблема:

Конвеєр передбачає, що fit_transformметод LabelBinarizer визначений для прийому трьох позиційних аргументів:

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

в той час як визначено взяти лише два:

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

Можливе рішення:

Це можна вирішити, створивши власний трансформатор, який може обробляти 3 позиційні аргументи:

  1. Імпортуйте та створіть новий клас:

    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. Зберігайте свій код однаковим, замість того, щоб використовувати LabelBinarizer (), використовуйте створений нами клас: MyLabelBinarizer ().


Примітка: Якщо ви хочете отримати доступ до атрибутів LabelBinarizer (наприклад, classes_), додайте наступний рядок до fitметоду:

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

Я пропоную цю альтернативу для класу. Що ви думаєте (вибачте за форматування)? def fit (self, X, y = None): \ n повернути self \ n def transform (self, X, y = None): \ n повернути LabelBinarizer (). fit_transform (X)
otonglet

2
Я отримую повідомлення про помилку - "<" не підтримується між екземплярами "str" ​​та "int". Що може бути причиною цього. У категоріальних стовпцях відсутні відсутні значення.
Чандра

@Chandra Мені потрібно побачити ваш код, щоб допомогти вам, але ця помилка може виникнути, коли ви передаєте рядок одному з параметрів pos_labels та neg_labels (тобто LabelBinarizer (pos_labels = "good"))
Заїд Е.,

@otonglet Я думаю, що це працює, але наявність (fit_transform) там означає, що кожного разу, коли ти викликаєш (трансформуєш) новий клас, він буде робити примірку знову. Це може призвести до несподіваної поведінки, якщо ви використовуєте її на тестовому наборі з кількома прикладами та багатьма категоріями міток. Крім того, публікація оновлена, щоб мати простіший код.
Заїд Е.

63

Я вважаю, що ваш приклад з книги " Практичне машинне навчання" за допомогою Scikit-Learn & TensorFlow . На жаль, я також зіткнувся з цією проблемою. Недавнє зміна scikit-learn( 0.19.0) змінилося LabelBinarizer«и fit_transformметод. На жаль, LabelBinarizerніколи не передбачалося працювати, як цей приклад використовує його. Інформацію про зміни ви можете побачити тут і тут .

Поки вони не знайдуть рішення для цього, ви можете встановити попередню версію ( 0.18.0) таким чином:

$ pip install scikit-learn==0.18.0

Після цього ваш код повинен працювати без проблем.

У майбутньому, схоже, правильним рішенням може бути використання CategoricalEncoderкласу або щось подібне до цього. Вони намагаються вирішити цю проблему роками, очевидно. Ви можете побачити новий клас тут, а подальше обговорення проблеми - тут .


1
Це не помилка як така. LabelBinarizer не повинен використовуватися з функціями ( X), а лише для міток ( y). Отже, вони перестали надсилати як метод X, так і метод y.
Vivek Kumar

Вони працюють над OneHotEncoder, який підтримує функції рядків. github.com/scikit-learn/scikit-learn/issues/4920
Вівек Кумар

6
Дякуємо за відповідь. І так, я перебуваю в режимі навчання за допомогою "Практичного машинного навчання за допомогою Scikit-Learn & TensorFlow". Так що так. Замість використання попередньої версії я отримав власний Binarizer, який працював у мене. Посилання на код: github.com/scikit-learn/scikit-learn/pull/7375/…
Viral

Я відредагував запитання, щоб додатково пояснити проблему та пояснити, що це не помилка.
Стівен Окслі

Дякую. Застряг у тій же проблемі, і це спрацювало.
gdanton

13

Я думаю, ви переглядаєте приклади з книги: « Руки на машинному навчанні за допомогою Scikit Learn та Tensorflow» . Я зіткнувся з тією ж проблемою, переглядаючи приклад у розділі 2.

Як згадували інші люди, проблема полягає в LabelBinarizer sklearn. Метод fit_transform потребує менше аргументів порівняно з іншими трансформаторами в трубопроводі. (лише y, коли інші трансформатори зазвичай приймають як X, так і y, див. тут для деталей). Ось чому, коли ми запускаємо pipeline.fit_transform, ми подавали в цей трансформатор більше аргументів, ніж потрібно.

Просте виправлення, яке я використав, - це просто використовувати OneHotEncoder і встановити для "sparse" значення "False", щоб переконатися, що на виході є масив numpy, такий самий, що і вивід num_pipeline. (таким чином вам не потрібно кодувати власний власний кодер)

ваш оригінальний cat_pipeline:

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

Ви можете просто змінити цю частину на:

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

Ви можете піти звідси, і все повинно працювати.


5
Деякі сторінки на ранніх статтях автор використовує 'reshape ()' у OneHotEncoder. Як це означає, що нам не потрібно використовувати цю зміну () категоріальних даних, коли зараз замінюємо LabelBinarizer на OneHotEncoder?
tobias.henn

@ tobias.henn, ймовірно, тому, що DataFrameSelector повертає масив numpy, а не фрейм даних pandas. Я припускаю, що цей масив numpy має правильні розміри і його не потрібно переробляти.
StrugglingStudent42

10

Оскільки LabelBinarizer не допускає більше 2 позиційних аргументів, вам слід створити власний binarizer типу

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
Ця реалізація CustomLabelBinarizer спричиняє проблему далі в главі, коли застосовується конвеєр до підмножини даних. Див stackoverflow.com/a/49993974/167920 для опису проблеми і кращої реалізації CustomLabelBinarizer
Уоллес Келлі

7

Я зіткнувся з тією ж проблемою і змусив її працювати, застосувавши обхідний шлях, зазначений у репозиторії книги Github .

Попередження: на цьому етапі в попередніх версіях книги використовувався клас LabelBinarizer. Знову ж, це було неправильно: так само, як клас LabelEncoder, клас LabelBinarizer був розроблений для попередньої обробки міток, а не вхідних функцій. Кращим рішенням буде використання майбутнього класу Scikit-Learn CategoricalEncoder: він незабаром буде доданий до Scikit-Learn, а тим часом ви можете скористатися наведеним нижче кодом (скопійований із Pull Request # 9151 ).

Щоб заощадити трохи захоплення, ось обхідний шлях, просто вставте та запустіть його у попередній комірці:

# 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

Просто, що ви можете зробити, це визначити наступний клас безпосередньо перед вашим конвеєром:

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)

Тоді решта коду схожа на ту, що згадана в книзі, з незначною модифікацією cat_pipelineдо конкатенації конвеєра - виконуйте як:

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

Ви ГОТОВО!


3

Забудьте LaberBinarizer і замість цього використовуйте OneHotEncoder.

Якщо ви використовуєте LabelEncoder перед OneHotEncoder для перетворення категорій у цілі числа, тепер ви можете використовувати OneHotEncoder безпосередньо.


Це може бути коментар, але все одно дякую за вашу відповідь
El.Hum

3

Я теж стикався з тим самим питанням. Наступне посилання допомогло мені вирішити цю проблему. https://github.com/ageron/handson-ml/issues/75

Підбиваючи підсумки змін, які потрібно зробити

1) Визначте наступний клас у зошиті

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

2) Змініть наступний фрагмент коду

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

3) Повторно запустіть зошит. Ви зможете зараз бігати


1

Я отримав ту ж проблему і вирішив за допомогою DataFrameMapper (потрібно встановити sklearn_pandas):

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

LabelBinarizer () створить функції OHE. Однак ви можете використовувати sklearn.preprocessing.LabelEncoder () безпосередньо в конвеєрі DataFrameMapper. Принаймні для мене це спрацювало нормально.

1

Ви можете створити ще один власний трансформатор, який виконує кодування за вас.

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

У цьому прикладі ми зробили LabelEncoding, але ви також можете використовувати LabelBinarizer


0

У підсумку я закотив свій

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

Здається, працює

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

повертається

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

-1

Щоб виконати одноразове кодування для кількох категоріальних функцій, ми можемо створити новий клас, який налаштовує наш власний декілька категоріальних функцій бінаризатора та підключає його до категоріального конвеєру наступним чином.

Припустимо, CAT_FEATURES = ['cat_feature1', 'cat_feature2']є перелік категоріальних ознак. Наступні сценарії вирішать проблему та видадуть те, що ми хочемо.

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))
])

Це рішення працює для обробки навчального набору, але не вдається під час обробки тестового набору. Коли цей крок у конвеєрі виконується, він додає лише стовпці для категорій, які існують у поточно оброблюваному наборі. Тобто, якщо навчальний набір мав більше категорій, ніж тестовий, деякі стовпці будуть відсутні після перетворення тестового набору.
CodingButStillAlive

Дякую за обговорення. Тим не менше, загалом, щоб уникнути витоку даних, ми повинні спочатку відокремити дані навчання та тестування, потім навчити моделі машинного навчання від даних навчання, а разом з отриманими моделями надалі передбачити майбутню відповідь за допомогою даних тесту; тому ми хотіли б стандартизувати розробку функцій, включаючи нормалізацію тощо, для автоматизації робочих процесів. І тому ми хотіли б використовувати трубопровід. Отже, проблема з деякими категоріями функцій тестових даних може бути відсутнім. Так само, як каже стара приказка: "Не можна змусити коня працювати, не годуючи його".
Боуенлі

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.