基于Sklearn TF-IDF的特征提取与相似度计算优化咨询
优化建议
一、TF-IDF特征生成阶段优化
1. 修复分词器的冗余计算
你的自定义分词器每次调用都会重复加载停用词,浪费资源。提前初始化停用词和词干提取器,同时优化文本清理逻辑(处理标点、统一小写):
from nltk.stem import PorterStemmer from nltk.corpus import stopwords import re # 提前初始化,避免重复加载 stop_words = set(stopwords.words('english')) porter = PorterStemmer() punct_pattern = re.compile(r'[^\w\s]') # 清理标点的正则 def tokenizer_stemmer(text: str) -> list: clean_text = punct_pattern.sub('', text.lower()) return [porter.stem(word) for word in clean_text.split() if word not in stop_words]
另外,你在TfidfVectorizer中同时设置了tokenizer和stop_words,会导致重复过滤停用词,直接去掉stop_words参数即可。
2. 削减特征维度
9万维度的特征矩阵过于庞大,是后续计算缓慢的核心原因之一。通过max_features限制特征数量,保留信息量最高的词汇/ngram:
tfidf = TfidfVectorizer( ngram_range=(1,2), tokenizer=tokenizer_stemmer, max_features=30000, # 可根据效果调整,建议2-4万区间 norm='l2' # 开启L2归一化,为后续快速计算相似度铺路 )
3. 提前预处理文本
把文本的清理、分词、词干提取提前完成并存储,避免TF-IDF每次都重复执行这些操作:
# 提前预处理所有oracle_text df_not_na['processed_text'] = df_not_na['oracle_text'].apply(tokenizer_stemmer) # 此时TF-IDF直接使用analyzer='word'即可 tfidf = TfidfVectorizer( ngram_range=(1,2), analyzer='word', max_features=30000, norm='l2' ) token_mat = tfidf.fit_transform(df_not_na['processed_text'].apply(' '.join))
二、距离/相似度计算阶段优化
1. 彻底放弃稀疏矩阵转稠密数组
2万×9万的稠密数组会占用14GB以上内存,转数组和后续计算都会极慢。直接用稀疏矩阵运算:
# 假设chosen_card_sparse是目标卡牌的稀疏TF-IDF向量(不要转成数组) # L2归一化后,余弦相似度等价于向量点积,计算速度远快于欧氏距离 similarities = token_mat.dot(chosen_card_sparse.T).toarray().flatten() # 取Top10最相似的索引(相似度越高越接近,与欧氏距离逻辑相反) nearest_10 = np.argpartition(-similarities, 10)[:10] # 按相似度排序 sorted_indices = nearest_10[np.argsort(-similarities[nearest_10])] print(df_not_na.iloc[sorted_indices][['name', 'oracle_text']])
2. 使用Sklearn内置近邻工具
sklearn.neighbors.NearestNeighbors对稀疏矩阵做了优化,比手动循环高效得多:
from sklearn.neighbors import NearestNeighbors # 初始化近邻模型,支持稀疏矩阵 nn = NearestNeighbors(n_neighbors=10, metric='cosine', algorithm='brute') nn.fit(token_mat) # 查询目标卡牌的近邻 distances, indices = nn.kneighbors(chosen_card_sparse) print(df_not_na.iloc[indices[0]][['name', 'oracle_text']])
3. 尝试近似近邻库(可选)
如果后续数据量持续增长,用近似近邻库(如Annoy、FAISS)可以进一步提速,牺牲微小精度换取速度:
# Annoy示例 from annoy import AnnoyIndex # 此时维度已被max_features限制,转稠密数组内存可控 token_arr = token_mat.toarray() dim = token_arr.shape[1] # 构建索引 annoy_idx = AnnoyIndex(dim, 'angular') # angular对应余弦相似度 for i, vec in enumerate(token_arr): annoy_idx.add_item(i, vec) annoy_idx.build(10) # 树的数量,越多精度越高 # 查询Top10 nearest_10 = annoy_idx.get_nns_by_vector(chosen_card_array, 10) print(df_not_na.iloc[nearest_10][['name', 'oracle_text']])
三、其他细节优化
- 用
nltk的word_tokenize替代split():更准确处理英文分词(比如连字符、缩写),避免把draw,这类带标点的内容当成一个词。 - 并行计算:TF-IDF支持
n_jobs=-1参数(利用多核),也可以用joblib加速文本预处理。 - 缓存模型:训练好的TF-IDF模型用
joblib.dump保存,后续直接加载,避免重复执行fit_transform。
内容的提问来源于stack exchange,提问作者FelipeB
相关产品推荐
相关产品推荐

