fit_transform()接受2个位置参数,但LabelBinarizer给出了3个


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

Answers:


71

问题:

管道假设LabelBinarizer的fit_transform方法定义为采用三个位置参数:

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返回自我\ n def transform(self,X,y = None):\ n返回LabelBinarizer()。fit_transform(X)
otonglet

2
我收到一个错误-'str'和'int'的实例之间不支持'<'。这可能是什么原因。分类列中没有缺失值。
Chandra

@Chandra我需要看你的代码,以帮助你,但可以产生这个错误,当你传递一个字符串的pos_labels和neg_labels一个参数(即LabelBinarizer(pos_labels =“好”))
扎伊德E.

@otonglet我认为这是可行的,但是其中包含(fit_transform)意味着每次您在新类上调用(transform)都会重新进行拟合。如果您在测试集上使用很少的示例和许多标签类别,则可能会导致意外的行为。此外,帖子已更新为具有更简单的代码。
Zaid E.

62

我相信您的示例来自《Scikit-Learn&TensorFlow上的动手机器学习》一书。不幸的是,我也遇到了这个问题。scikit-learn0.19.0)中的最新更改更改了LabelBinarizerfit_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
Vivek Kumar

6
感谢您的答复,是的,我处于“使用Scikit-Learn&TensorFlow进行动手机器学习”的学习模式。因此,是的,不是使用以前的版本,而是为我提供了自定义Binarizer。链接代码:github.com/scikit-learn/scikit-learn/pull/7375/...
病毒帕尔马

我对问题进行了编辑,以进一步解释该问题,并阐明它不是错误。
史蒂芬·奥克斯利

谢谢。陷入了同样的问题,这工作。
gdanton

11

我认为您正在阅读本书中的示例:使用Scikit Learn和Tensorflow进行机器学习。在阅读第2章中的示例时,我遇到了同样的问题。

正如其他人提到的那样,问题在于sklearn的LabelBinarizer。与管道中的其他转换器相比,其fit_transform方法所需的args更少。(仅当其他变压器通常同时使用X和y时才使用y,有关详细信息,请参见此处)。这就是为什么当我们运行pipeline.fit_transform时,我们向该转换器提供了比所需数量更多的args。

我使用的一个简单修复方法是仅使用OneHotEncoder并将“ sparse”设置为False,以确保输出是与num_pipeline输出相同的numpy数组。(这样一来,您无需编写自己的自定义编码器)

您原来的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
作者更早一些页面在OneHotEncoder中使用了“ reshape()”。现在用OneHotEncoder替换LabelBinarizer时,为什么不用分类数据的reshape()呢?
tobias.henn

@ tobias.henn可能是因为DataFrameSelector返回一个numpy数组而不是pandas数据框。我假设此numpy数组的尺寸正确,不需要重新调整形状。
EternusVia

10

由于LabelBinarizer不允许使用2个以上的位置参数,因此您应该创建自定义二进制文件,例如

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的这种实现会在本章后面引发问题。有关问题的说明和CustomLabelBinarizer的更好实现,请参见stackoverflow.com/a/49993974/167920
Wallace Kelly

7

我遇到了同样的问题,并通过应用本书的Github repo中指定的解决方法使其工作。

警告:这本书的早期版本此时使用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。

如果在OneHotEncoder之前使用LabelEncoder将类别转换为整数,则现在可以直接使用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功能。但是,您可以直接在DataFrameMapper管道中使用sklearn.preprocessing.LabelEncoder()。至少对我来说,它运行良好。

1

您可以再创建一个Custom Transformer来为您编码。

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

感谢您的讨论。尽管如此,总的来说,为了避免数据泄漏,我们必须首先将训练和测试数据分开,然后从训练数据中训练机器学习模型,并使用生成的模型进一步预测测试数据的未来响应。因此,我们希望标准化工作流程自动化的功能工程,包括标准化等。这就是为什么我们要使用管道。因此,预计某些测试数据的特征类别可能会丢失。就像古老的谚语所说的那样:“如果不给它喂食,就无法使马工作。”
bowenli

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.