fit_transform()需要传入2个参数,但是在使用LabelBinarizer时却传入了3个参数。

97
我是一名有用的助手,可以为您翻译文本。

我完全不了解机器学习,但我一直在使用无监督学习技术。

以下是我的样本数据(清理后)截图:

样本数据

我已经建立了这两个管道来清理数据:

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 个


6
LabelBinarizer不应该与X(特征)一起使用,而是仅用于标签。因此,fit和fit_transform方法被更改为仅包括单个对象y。但是Pipeline(适用于特征)将尝试将X和y都发送给它。因此会出现错误。 - Vivek Kumar
4
你应该在管道之外使用LabelBinarizer将分类特征转换为独热编码,或者可以使用pandas.get_dummies() - Vivek Kumar
17个回答

79

问题:

这个管道假定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. 保持您的代码不变,只需要使用我们创建的类MyLabelBinarizer()替换LabelBinarizer()。


注意:如果您想访问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 return self \n def transform(self, X, y = None): \n return LabelBinarizer().fit_transform(X) - Olivier Tonglet
2
我遇到了一个错误——“str”和“int”之间不支持“<”。这是什么原因呢?分类列中没有缺失值。 - Chandra
@Chandra,我需要看到你的代码才能帮助你,但当你将字符串传递给pos_labels和neg_labels参数之一时(即LabelBinarizer(pos_labels = "good")),可能会生成此错误。 - Zaid E.
@otonglet 我认为这个方法是可行的,但是在其中加入 (fit_transform) 意味着每次你在新类上调用 (transform) 时都会重新进行拟合。如果你在测试集上使用它,并且样本很少而标签类别很多,那么这可能会导致意外的结果。此外,该帖子已更新为更简单的代码。 - Zaid E.
请原谅我的无知,但是您的示例能用于适应4或5个输入吗?(或者这是不好的做法) - kierandes
显示剩余2条评论

67

我认为你的例子来自于《Python机器学习实践指南》这本书。不幸的是,我也遇到了类似的问题。最近 scikit-learn(版本号0.19.0)改变了LabelBinarizerfit_transform 方法。不过,LabelBinarizer从未被设计成用于那个例子所示的方式。你可以在这里这里获取关于这个变化的详细信息。

在他们提出解决方案之前,你可以按照以下方式安装先前的版本(0.18.0):

$ pip install scikit-learn==0.18.0

运行完这个程序之后,你的代码应该能正常运行。

未来看起来正确的解决方案可能是使用 CategoricalEncoder 类或类似的东西。显然他们已经在尝试解决这个问题数年了。你可以在这里查看新类和进一步讨论问题here


1
这并不是一个实际的错误。LabelBinarizer 不应该用于特征(X),而只能用于标签(y)。因此,他们已经停止将 X 和 y 一起发送到该方法中。 - Vivek Kumar
他们正在开发支持字符串特征的OneHotEncoder。https://github.com/scikit-learn/scikit-learn/issues/4920 - Vivek Kumar
6
谢谢您的回复,是的我正在学习“Scikit-Learn和TensorFlow机器学习实战”这本书。所以,我选择使用自定义的二值化工具,而非之前的版本,这对我很有帮助。这是代码链接:https://github.com/scikit-learn/scikit-learn/pull/7375/files#diff-1e175ddb0d84aad0a578d34553f6f9c6 - Viral Parmar
我编辑了问题,进一步解释了问题,并澄清它不是一个 bug。 - Steven Oxley
谢谢。我也遇到了同样的问题,这个方法解决了它。 - gdanton

14

我想你正在阅读书籍中的示例: 《Scikit Learn和Tensorflow机器学习实战》。当我在第2章的示例中遇到同样的问题时,其他人也提到了这个问题。

正如其他人所提到的,问题与sklearn的LabelBinarizer有关。与管道中的其他转换器相比,它在fit_transform方法中需要较少的参数(通常只需要y,而其他转换器则需要X和y,请参见此处了解详情)。这就是为什么当我们运行pipeline.fit_transform时,我们向这个转换器提供了比所需更多的参数的原因。

我使用的一个简单的解决方法是只使用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替换LabelBinarizer时,我们不需要像作者之前在某些页面上使用'reshape()'来处理分类数据?请翻译此内容。 - tobias.henn
@tobias.henn 可能是因为 DataFrameSelector 返回的是一个 numpy 数组而不是 pandas dataframe。我假设这个 numpy 数组的维度是正确的,不需要重新调整形状。 - EssentialAnonymity
@tobias.henn 因为他们将编码器应用于 housing["ocean_proximity"](一个1维numpy数组),而不是 housing[["ocean_proximity"]](一个pandas数据帧)。 - user3187724

10

由于LabelBinarizer不允许使用超过2个位置参数,因此您应该像这样创建自定义二值化器:

由于 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的实现会在后面的章节中导致问题,当将管道应用于数据子集时。请参阅https://stackoverflow.com/a/49993974/167920,了解问题的描述和更好的CustomLabelBinarizer实现。 - Wallace Kelly

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

6

简单地说,你可以在管道之前定义以下类:

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) 重新运行笔记本。现在你就可以运行了。


2
LabelBinarizer类在这个例子中已经过时,而且不幸的是它从来没有被设计成以书中使用的方式使用。
你需要使用sklearn.preprocessing中的OrdinalEncoder类,该类旨在将分类特征编码为整数数组。
因此,只需添加:
from sklearn.preprocessing import OrdinalEncoder

然后在您的代码中将所有提及LabelBinarizer()的地方替换为OrdinalEncoder()


它并没有真正解决问题。它虽然摆脱了错误消息,但是最后的函数(对类别进行编码的函数)却没有起作用。OrdinalEncoder的输出形状就好像这个函数从未执行过一样。@Terrence的答案解决了这个问题。 - Baraa

1

我遇到了同样的问题,通过使用DataFrameMapper(需要安装sklearn_pandas)得到了解决:

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

LabelBinarizer() 将创建 OHE 特征。但是,您可以直接在 DataFrameMapper 管道中使用 sklearn.preprocessing.LabelEncoder()。对我来说,这种方法效果很好。 - user425727

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接