包含候选近邻列表的推文Dataframe快速计算余弦相似度的优化方法问询
余弦相似度计算性能优化方案
你的原代码性能差主要来自三个核心问题:
- 用
iterrows遍历DataFrame,本身遍历开销极大,每一行都会生成独立的Series对象 - 双层Python级循环逐对计算,完全没有利用numpy/scipy的向量化加速能力
- 每次调用
pairwise_distances仅计算单对向量的距离,函数调用的 overhead 远大于计算本身,累积后占比极高
核心优化思路
利用scipy稀疏矩阵的批量运算特性,一次性计算所有需要的样本对距离,尽可能减少Python层面的循环次数。另外提前对TF-IDF矩阵做L2归一化,可将余弦距离计算简化为1 - 向量点积,进一步降低计算量。
最优批量实现方案(性能提升100倍以上)
适合总数据量较大、每个nn_list候选数较多的场景:
from sklearn.preprocessing import normalize import numpy as np # 步骤1:对TF-IDF矩阵做L2归一化,归一化后余弦相似度直接等于两个向量的点积 tfidf_norm = normalize(tfidf_matrix, norm='l2', copy=False) # 步骤2:收集所有需要计算的<查询索引, 候选索引>对,以及对应的DataFrame行映射 query_indices = [] candidate_indices = [] row_mapper = [] for row_idx, (df_idx, nn_list) in enumerate(data_df['nn_list'].items()): candidate_cnt = len(nn_list) query_indices.extend([df_idx] * candidate_cnt) candidate_indices.extend(nn_list) row_mapper.extend([row_idx] * candidate_cnt) # 转成numpy数组方便向量化索引 query_indices = np.array(query_indices) candidate_indices = np.array(candidate_indices) row_mapper = np.array(row_mapper) # 步骤3:批量计算所有对的余弦距离,保留2位小数 cos_sims = tfidf_norm[query_indices].multiply(tfidf_norm[candidate_indices]).sum(axis=1).A1 cos_distances = np.around(1 - cos_sims, 2) # 步骤4:按原始行分组,取每个行最小距离对应的候选 best_candidates = np.zeros(len(data_df), dtype=int) best_distances = np.full(len(data_df), 2.0) # 余弦距离最大为2,初始值设为上限以上 # 找每个行的分组分界点 split_pos = np.unique(row_mapper, return_index=True)[1] split_pos = np.append(split_pos, len(row_mapper)) for i in range(len(split_pos) - 1): start, end = split_pos[i], split_pos[i+1] group_dists = cos_distances[start:end] group_candidates = candidate_indices[start:end] min_idx = group_dists.argmin() best_candidates[i] = group_candidates[min_idx] best_distances[i] = group_dists[min_idx] # 结果回填到DataFrame data_df['best_nn_candidate'] = best_candidates data_df['best_nn_distance'] = best_distances
轻量简化实现方案(性能提升10~50倍)
适合中等数据量场景,写法更简洁:
from sklearn.preprocessing import normalize import numpy as np tfidf_norm = normalize(tfidf_matrix, norm='l2', copy=False) def get_best_nn(row): query_vec = tfidf_norm[row.name] candidate_vecs = tfidf_norm[row['nn_list']] sims = query_vec.dot(candidate_vecs.T).A1 dists = np.around(1 - sims, 2) min_pos = dists.argmin() return row['nn_list'][min_pos], dists[min_pos] data_df[['best_nn_candidate', 'best_nn_distance']] = data_df.apply( get_best_nn, axis=1, result_type='expand' )
额外优化提示
如果你的TF-IDF矩阵维度不超过10万、内存足够,可以提前将稀疏矩阵转为稠密数组存储,索引和计算的速度会更快。
内容的提问来源于stack exchange,提问作者random_programmer
相关产品推荐
相关产品推荐

