【问题标题】:Sklearn custom transformers with pipeline: all the input array dimensions for the concatenation axis must match exactly带有管道的 Sklearn 自定义转换器:连接轴的所有输入数组维度必须完全匹配
【发布时间】:2022-01-02 03:38:56
【问题描述】:

我正在学习 sklearn 自定义转换器,并阅读了创建自定义转换器的两种核心方法:

  1. 通过设置继承自 BaseEstimatorTransformerMixin 的自定义类,或者
  2. 通过创建转换方法并将其传递给FunctionTransformer

我想通过实现“元向量化器”功能来比较这两种方法:支持CountVectorizerTfidfVectorizer 并根据指定的向量化器类型转换输入数据的向量化器。

但是,将它们传递给sklearn.pipeline.Pipeline 时,我似乎无法完成这两项工作。我在fit_transform() 步骤中收到以下错误消息:

ValueError: all the input array dimensions for the concatenation axis must match 
exactly, but along dimension 0, the array at index 0 has size 6 and the array 
at index 1 has size 1

我的选项 1 代码(使用自定义类):

class Vectorizer(BaseEstimator, TransformerMixin):
    def __init__(self, vectorizer:Callable=CountVectorizer(), ngram_range:tuple=(1,1)) -> None:
        super().__init__()
        self.vectorizer = vectorizer
        self.ngram_range = ngram_range
    def fit(self, X, y=None):
        return self 
    def transform(self, X, y=None):
        X_vect_ = self.vectorizer.fit_transform(X.copy())
        return X_vect_.toarray()

pipe = Pipeline([
    ('column_transformer', ColumnTransformer([
        ('lesson_type_category', OneHotEncoder(), ['Type']),
        ('comment_text_vectorizer', Vectorizer(), ['Text'])],
        remainder='drop')),
    ('model', LogisticRegression())])

param_dict = {'column_transformer__comment_text_vectorizer__vectorizer': \
[CountVectorizer(), TfidfVectorizer()]
}

randsearch = GridSearchCV(pipe, param_dict, cv=2, scoring='f1',).fit(X_train, y_train)

还有我的选项 2 代码(使用 FunctionTransformer 从函数创建自定义转换器):

def vectorize_text(X, vectorizer: Callable):
    X_vect_ = vectorizer.fit_transform(X)
    return X_vect_.toarray()

vectorizer_transformer = FunctionTransformer(vectorize_text, kw_args={'vectorizer': TfidfVectorizer()})

pipe = Pipeline([
    ('column_transformer', ColumnTransformer([
        ('lesson_type_category', OneHotEncoder(), ['Type']),
        ('comment_text_vectorizer', vectorizer_transformer, ['Text'])],
        remainder='drop')),
    ('model', LogisticRegression())])

param_dict = {'column_transformer__comment_text_vectorizer__kw_args': \
    [{'vectorizer':CountVectorizer()}, {'vectorizer': TfidfVectorizer()}]
}

randsearch = GridSearchCV(pipe, param_dict, cv=2, scoring='f1').fit(X_train, y_train)

导入和样本数据:

import pandas as pd 
from typing import Callable
import sklearn
from sklearn.preprocessing import OneHotEncoder, FunctionTransformer
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import GridSearchCV

df = pd.DataFrame([
    ['A99', 'hi i love python very much', 'c', 1],
    ['B07', 'which programming language should i learn', 'b', 0],
    ['A12', 'what is the difference between python django flask', 'b', 1],
    ['A21', 'i want to be a programmer one day', 'c', 0],
    ['B11', 'should i learn java or python', 'b', 1],
    ['C01', 'how much can i earn as a programmer with python', 'a', 0]
], columns=['Src', 'Text', 'Type', 'Target'])

注意事项:

  • 按照in this question 的建议,我在矢量化之后将所有稀疏矩阵转换为密集数组,正如您在这两种情况下看到的那样:X_vect_.toarray()

【问题讨论】:

    标签: python machine-learning scikit-learn pipeline hyperparameters


    【解决方案1】:

    问题在于CountVectorizerTfidfVectorizer 都要求它们的输入是一维的(而不是二维的)。在这种情况下,ColumnTransformerdoc 声明 transformers 元组的参数 columns 应该作为 字符串 而不是作为列表传递。

    列:str、str 的类数组、int、int 的类数组、bool、slice 或可调用的类数组

    索引第二个轴上的数据。整数被解释为位置列,而字符串可以按名称引用 DataFrame 列。 应在转换器期望 X 是一维数组(向量)的地方使用标量字符串或整数,否则将向转换器传递一个二维数组。传递输入数据 X 并且可以返回上述任何一个。要按名称或数据类型选择多个列,可以使用 make_column_selector。

    因此,以下内容适用于您的情况(即将['Text'] 更改为'Text')。

    class Vectorizer(BaseEstimator, TransformerMixin):
        def __init__(self, vectorizer:Callable=CountVectorizer(), ngram_range:tuple=(1,1)) -> None:
            super().__init__()
            self.vectorizer = vectorizer
            self.ngram_range = ngram_range
        def fit(self, X, y=None):
            return self 
        def transform(self, X, y=None):
            X_vect_ = self.vectorizer.fit_transform(X.copy())
            return X_vect_.toarray()
    
    pipe = Pipeline([
        ('column_transformer', ColumnTransformer([
            ('lesson_type_category', OneHotEncoder(handle_unknown='ignore'), ['Type']),
            ('comment_text_vectorizer', Vectorizer(), 'Text')], remainder='drop')),
        ('model', LogisticRegression())])
    
    param_dict = {'column_transformer__comment_text_vectorizer__vectorizer': [CountVectorizer(), TfidfVectorizer()]
    }
    
    randsearch = GridSearchCV(pipe, param_dict, cv=2, scoring='f1',).fit(X_train, y_train)
    

    您可以使用FunctionTransformer 相应地调整示例。最后请注意,我必须将handle_unknown='ignore' 传递给OneHotEncoder,以防止在交叉验证的测试阶段看到未知类别时出现错误的可能性(并且在交叉验证期间没有看到)训练阶段)。

    【讨论】:

      猜你喜欢
      • 2020-02-19
      • 2020-07-25
      • 2021-05-09
      • 2018-07-08
      • 2017-10-20
      • 2019-05-16
      • 2017-11-26
      • 2020-02-03
      • 2020-08-02
      相关资源
      最近更新 更多