我在处理下面的代码时遇到了问题(这是imblearn
管道)
features = training_data.loc[:, training_data.columns[:-1]]labels = training_data.loc[:, training_data.columns[-1:]]X_train, X_test, y_train, y_test = train_test_split(features, labels, test_size=0.2)
print(X_train.shape, y_train.shape)
形状的打印输出是:(80, 6) (80, 1)
algorithms = [ svm.LinearSVC(), ensemble.RandomForestClassifier(),]def train(algorithm, X_train, y_train): model = Pipeline([ ('vect', TfidfVectorizer()), ('smote', SMOTETomek()), ('chi', SelectKBest(chi2, k=1000)), ('classifier', algorithm) ]) model.fit(X_train, y_train) return modelscore_dict = {}algorithm_to_model_dict = {}for algorithm in algorithms: model = train(algorithm, X_train, y_train) score = model.score(X_test, y_test) score_dict[algorithm] = int(score * 100) algorithm_to_model_dict[algorithm] = model
特征和标签都是文本(我在进行文本分析)。
异常是在fit
调用时引发的
我做错了什么?
回答:
出现这种情况是因为你的管道中有一个文本转换器对象。这种方法的问题在于,管道会将整个数据框传递给TfidfVectorizer
。然而,scikit-learn
的文本转换器期望输入是一维的。
将二维数据框传递给TfidfVectorizer
会导致一些奇怪的处理,它会将列名误认为是文档。你可以用这个简单的例子来检查:
X = pd.DataFrame({ 'f1': ['This is doc1', 'This is doc2', 'This is doc3', 'This is doc4', 'This is doc5'], 'f2': [0, 1, 1, 0, 0]})vec = TfidfVectorizer()print(vec.fit(X).get_feature_names())>>> ['f1', 'f2']
这解释了为什么错误消息称样本数不一致:TfidfVectorizer
认为数据框中的6列是样本,并将其名称视为特征。
如果你想在你的管道中使用TfidfVectorizer
,你必须确保只将包含文本文档的列传递给它。你可以通过将其包装在ColumnTransformer
中来实现这一点:
# 如果只需要转换一列transformer = ColumnTransformer( [('vec', TfidfVectorizer(), column)], # column 应为字符串或整数 remainder='passthrough')# 如果需要转换多列(不建议,参见下面的注意事项)transformer = ColumnTransformer( [('vec', TfidfVectorizer(), col_1), # col_1 应为字符串或整数 ... ('vec', TfidfVectorizer(), col_n)], # col_n 应为字符串或整数 remainder='passthrough')
将上面的column
替换为TfidfVectorizer
需要转换的列的索引或名称,它将只处理这一特定列。remainder='passthrough'
将确保其他列保持原样,并与结果连接。你可以像这样在你的管道中使用它:
model = Pipeline([ ('vect', transformer), ('smote', SMOTETomek()), ('chi', SelectKBest(chi2, k=1000)), ('classifier', algorithm)])
注意
如果你需要转换多个包含文本的列,你应该考虑将列条目合并成一个单一的、组合的文档,并且只转换这个组合文档。否则,每列将被视为新的词汇表,尽管这些词汇表可能在某种程度上重叠,你最终可能会得到非常高的维度/很多特征。