使用scikit-learn合并不同维度特征时维度不匹配问题求助
解决scikit-learn Pipeline + FeatureUnion中的维度不匹配问题
你遇到的这个ValueError核心问题是两个特征分支返回的矩阵行数不一致:book_summary返回了2000行(对应你的训练集样本数),但book_contents只返回了1行,导致FeatureUnion无法合并特征。我帮你梳理下问题根源和修复方案:
问题根源拆解
- 自定义Transformer不符合scikit-learn规范:你的
book_content_count类没有继承BaseEstimator和TransformerMixin,这会让scikit-learn无法正确识别它的接口,容易引发维度处理的隐藏错误。 - 变量名错误导致文本读取异常:
count_contents2里你定义了book_data = pd.read_csv(...),但后面却用了user_data['text']——这个未定义的变量会导致读取书籍文本失败,返回的内容格式错误,最终让TF-IDF输出异常维度的矩阵。 - TF-IDF拟合时机错误:你在
transform方法里每次都重新创建并拟合TF-IDF Pipeline,这不仅效率低,还会导致数据泄露(测试时用测试数据拟合),甚至可能因为单样本处理异常返回行数不对的结果。 - ItemSelector列名错误:你的数据示例里摘要列是
Summary,但book_summaryPipeline里用了key='book',这会导致该分支无法正确提取特征,进一步加剧维度问题。
修复后的完整代码
1. 修正自定义书籍内容Transformer
from sklearn.base import BaseEstimator, TransformerMixin import pandas as pd from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.pipeline import Pipeline, FeatureUnion from sklearn.svm import SVC class BookContentTransformer(BaseEstimator, TransformerMixin): def __init__(self): # 初始化TF-IDF向量器,参数可以根据需求调整 self.tfidf = TfidfVectorizer(min_df=1, lowercase=False, ngram_range=(1,1), use_idf=True, stop_words='english') def _load_single_book(self, bookid): """加载单个书籍的文本内容并拼接成字符串""" try: book_path = f'C:/TheCorpus/{int(bookid)}_book.csv' book_df = pd.read_csv(book_path, header=0, delimiter=',', encoding='latin1', error_bad_lines=False, dtype=str) # 把所有文本行拼接成一个完整的书籍文本 full_text = ' '.join(book_df['text'].astype(str)) return full_text except Exception as e: # 处理文件不存在或读取错误的情况,返回空字符串避免中断 print(f"加载书籍{bookid}失败: {e}") return "" def fit(self, X, y=None): """在fit阶段拟合TF-IDF,使用训练集的书籍文本构建词汇表""" # 从输入数据中提取所有bookid,加载对应书籍文本 all_book_texts = X['bookid'].apply(self._load_single_book) self.tfidf.fit(all_book_texts) return self def transform(self, X, y=None): """将输入的bookid转换为TF-IDF特征矩阵""" book_texts = X['bookid'].apply(self._load_single_book) # 返回与输入样本数一致的(n_samples, n_features)稀疏矩阵 return self.tfidf.transform(book_texts)
2. 修正Pipeline和FeatureUnion定义
# 先定义通用的列选择Transformer class ItemSelector(BaseEstimator, TransformerMixin): def __init__(self, key): self.key = key def fit(self, X, y=None): return self def transform(self, X): return X[self.key] # 书籍摘要特征流水线 book_summary_pipeline = Pipeline([ ('selector', ItemSelector(key='Summary')), # 修正列名为你的数据集实际列名 ('tfidf', TfidfVectorizer(analyzer='word', ngram_range=(1,3), min_df=1, lowercase=True, stop_words=my_stopword_list, sublinear_tf=True)) ]) # 书籍内容特征流水线 book_content_pipeline = Pipeline([ ('selector', ItemSelector(key='bookid')), # 选择bookid列 ('content_tfidf', BookContentTransformer()) # 使用修正后的Transformer ]) # 最终完整流水线 full_pipeline = Pipeline([ ('feats', FeatureUnion([ ('summary_feats', book_summary_pipeline), ('content_feats', book_content_pipeline) ])), ('clf', SVC(kernel='linear', class_weight='balanced')) ])
关键修复点说明
- 遵循scikit-learn接口规范:让自定义Transformer继承
BaseEstimator和TransformerMixin,确保scikit-learn能正确处理它的fit/transform流程。 - 修正文本读取逻辑:修复变量名错误,正确拼接单本书籍的所有文本内容,保证每个样本对应一个完整的书籍文本字符串。
- 正确的TF-IDF拟合时机:把TF-IDF的拟合放在
fit方法中,只在训练阶段构建词汇表,测试阶段直接复用,避免数据泄露同时保证维度一致。 - 匹配列名:确保
ItemSelector使用的key和你数据集的列名完全一致,保证每个分支都能提取到正确的样本特征。
这样修改后,两个特征分支都会返回行数等于训练集样本数的特征矩阵,FeatureUnion就能正常合并,后续的SVC分类器也能正常运行了。
内容的提问来源于stack exchange,提问作者Abrial
相关产品推荐
相关产品推荐

