【问题标题】:fit_transform() takes 2 positional arguments but 3 were given with LabelBinarizerfit_transform() 接受 2 个位置参数,但 LabelBinarizer 给出了 3 个
【发布时间】:2018-02-20 02:49:28
【问题描述】:

我对机器学习完全陌生,我一直在使用无监督学习技术。

图像显示了我的示例数据(全部清洁后)屏幕截图: Sample Data

我构建了这两个管道来清理数据:

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

现在我正在尝试在 Data 上执行 fit_transform,但它向我显示了错误。

转换代码:

housing_prepared = full_pipeline.fit_transform(housing)
housing_prepared

错误信息:

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

【问题讨论】:

  • LabelBinarizer 不应该与 X(功能)一起使用,而是仅用于标签。因此 fit 和 fit_transform 方法被更改为仅包含单个对象 y。但是管道(适用于功能)将尝试将 X 和 y 都发送给它。因此出现错误。
  • 您应该在管道外使用 LabelBinarizer 将分类特征转换为 one-hot 编码,或者使用pandas.get_dummies()

标签: python scikit-learn data-science


【解决方案1】:

我相信您的示例来自Hands-On Machine Learning with Scikit-Learn & TensorFlow 一书。不幸的是,我也遇到了这个问题。 scikit-learn (0.19.0) 的最新更改更改了 LabelBinarizerfit_transform 方法。不幸的是,LabelBinarizer 从未打算使用该示例如何使用它。您可以看到有关更改的信息herehere

在他们为此提出解决方案之前,您可以按如下方式安装以前的版本 (0.18.0):

$ pip install scikit-learn==0.18.0

运行后,您的代码应该可以正常运行。

在未来,看起来正确的解决方案可能是使用CategoricalEncoder 类或类似的东西。显然,他们多年来一直试图解决这个问题。可以看新类here和问题的进一步讨论here

【讨论】:

  • 这本身不是一个错误。 LabelBinarizer 不应该与特征一起使用 (X),而只能用于标签 (y)。因此,他们已经停止向该方法发送 X 和 y。
  • 他们正在开发支持字符串功能的 OneHotEncoder。 github.com/scikit-learn/scikit-learn/issues/4920
  • 感谢您的回复,是的,我正在使用“使用 Scikit-Learn 和 TensorFlow 进行机器学习”的学习模式。所以是的,我没有使用以前的版本,而是得到了一个对我有用的自定义 Binarizer。代码链接是:github.com/scikit-learn/scikit-learn/pull/7375/…
  • 我编辑了问题以进一步解释问题并澄清这不是错误。
  • 谢谢。遇到了同样的问题,这很有效。
【解决方案2】:

问题:

管道假设 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 return self \n def transform(self, X, y = None): \n return LabelBinarizer().fit_transform(X)
  • 我收到一个错误——在“str”和“int”的实例之间不支持“
  • @Chandra 我需要查看您的代码以帮助您,但是当您将字符串传递给 pos_labels 和 neg_labels 参数之一(即 LabelBinarizer(pos_labels = "good") )
  • @otonglet 我认为这是可行的,但是在其中包含 (fit_transform) 意味着每次您在新类上调用 (transform) 时,它都会重新进行拟合。如果您在包含少量示例和许多标签类别的测试集上使用它,这可能会导致意外行为。此外,该帖子已更新为具有更简单的代码。
  • 请原谅我的无知,但是您的示例可以用于适应 4 或 5 个输入吗? (或者这是不好的做法)
【解决方案3】:

要对多个分类特征执行one-hot 编码,我们可以创建一个自定义我们自己的多个分类特征二值化器的新类,并将其插入分类管道,如下所示。

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

【讨论】:

  • 这个解决方案适用于处理训练集,但在处理测试集时失败。当管道中的这一步被执行时,它只为当前处理集中存在的类别附加列。也就是说,如果训练集的类别多于测试集,则在转换测试集后会丢失一些列。
  • 感谢您的讨论。尽管如此,一般来说,为了避免数据泄露,我们必须先将训练数据和测试数据分开,然后从训练数据中训练机器学习模型,并用得到的模型进一步预测未来的响应和测试数据;所以我们希望标准化特征工程,包括标准化等,以实现工作流自动化。这就是我们想要使用 Pipeline 的原因。因此,某些测试数据的特征类别可能会丢失的问题是意料之中的。就像那句老话:“一匹马不喂它就不能工作。”
【解决方案4】:

由于 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)

【讨论】:

  • CustomLabelBinarizer 的这种实现会在本章稍后将管道应用于数据子集时导致问题。有关问题的描述和 CustomLabelBinarizer 的更好实现,请参阅 stackoverflow.com/a/49993974/167920
【解决方案5】:

最后我自己动手了

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

【讨论】:

    【解决方案6】:

    我遇到了同样的问题,并通过应用book's 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
    

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

      • LabelBinarizer() 将创建 OHE 特征。但是,您可以直接在 DataFrameMapper 管道中使用 sklearn.preprocessing.LabelEncoder()。至少对我来说效果很好。
      【解决方案8】:

      我认为您正在阅读书中的示例:Hands on Machine Learning with Scikit Learn and Tensorflow。在第 2 章的示例中,我遇到了同样的问题。

      正如其他人所说,问题与sklearn的LabelBinarizer有关。与管道中的其他转换器相比,它的 fit_transform 方法需要更少的参数。 (仅当其他变压器通常同时采用 X 和 y 时才使用 y,详见here)。这就是为什么当我们运行 pipeline.fit_transform 时,我们向这个转换器提供了比所需更多的参数。

      我使用的一个简单解决方法是仅使用 OneHotEncoder 并将“稀疏”设置为 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))
      ])
      

      你可以从这里开始,一切都应该正常。

      【讨论】:

      • 一些更早的页面作者在 OneHotEncoder 中使用了“reshape()”。现在用 OneHotEncoder 替换 LabelBinarizer 时,为什么我们不需要使用分类数据的这个 reshape()?
      • @tobias.henn 可能是因为 DataFrameSelector 返回一个 numpy 数组而不是 pandas 数据帧。我假设这个 numpy 数组的尺寸正确,不需要重新整形。
      【解决方案9】:

      忘记 LaberBinarizer 并改用 OneHotEncoder。

      如果您在 OneHotEncoder 之前使用 LabelEncoder 将类别转换为整数,您现在可以直接使用 OneHotEncoder。

      【讨论】:

      • 这可能是评论,但无论如何感谢您的回复
      【解决方案10】:

      您可以再创建一个自定义转换器来为您进行编码。

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

      在本例中,我们已经完成了 LabelEncoding,但您也可以使用 LabelBinarizer

      【讨论】:

        【解决方案11】:

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

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

        你完成了!

        【讨论】:

          【解决方案12】:

          我也遇到了同样的问题。以下链接帮助我解决了这个问题。 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) 重新运行笔记本。你现在就可以跑了

          【讨论】:

            【解决方案13】:

            我们可以只添加属性 sparce_output=False

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

            【讨论】:

              【解决方案14】:

              最简单的方法是将管道中的 LabelBinarize() 替换为 OrdinalEncoder()

              【讨论】:

                【解决方案15】:

                对于这个例子,LabelBinarizer 类已经过时了,不幸的是,它从来没有打算以本书使用它的方式使用。

                您需要使用 sklearn.preprocessing 中的 OrdinalEncoder 类,该类旨在

                “将分类特征编码为整数数组。” (sklearn 文档)。

                所以,只需添加:

                from sklearn.preprocessing import OrdinalEncoder
                

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

                【讨论】:

                  【解决方案16】:

                  我见过很多自定义标签二值化器,但repo 中的一个对我有用。

                  class LabelBinarizerPipelineFriendly(LabelBinarizer):
                      def fit(self, X, y=None):
                          """this would allow us to fit the model based on the X input."""
                          super(LabelBinarizerPipelineFriendly, self).fit(X)
                      def transform(self, X, y=None):
                          return super(LabelBinarizerPipelineFriendly, self).transform(X)
                  
                      def fit_transform(self, X, y=None):
                          return super(LabelBinarizerPipelineFriendly, self).fit(X).transform(X)
                  

                  然后将cat_pipeline编辑成这样:

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

                  祝你好运!

                  【讨论】:

                    猜你喜欢
                    • 2020-03-27
                    • 2017-05-07
                    • 2022-01-17
                    • 2019-09-21
                    • 2019-07-19
                    • 2021-02-02
                    • 2020-02-07
                    • 2017-03-14
                    • 2014-09-07
                    相关资源
                    最近更新 更多