Scipy余弦相似度在Pandas DataFrame中运行过慢问题求助
首先得说,你现在的嵌套循环逻辑不仅速度慢,还有个隐藏的逻辑问题:每次外层循环(遍历用户)都会覆盖item_matrix_cpy['cosine']列的值,最后你得到的其实只是最后一个用户和所有物品的相似度,而不是每个用户对应物品的相似度矩阵——这可不是你想要的协同过滤结果对吧?
为什么你的代码这么慢?
你的代码时间复杂度是O(M*N),其中M=17000个用户,N=1500个物品,总共有25,500,000次循环迭代,而且每次迭代都要调用Python层面的cosine函数。Python的循环本身执行效率就低,这种规模下完全不可行。
最优解决方案:向量化矩阵运算
余弦相似度的计算可以完全转化为归一化矩阵的点积运算(因为两个归一化向量的余弦相似度等于它们的点积:cosθ = (A·B)/(||A||*||B||),如果A和B都已经归一化到L2范数为1,分母就是1,直接点积即可)。我们可以用numpy或scikit-learn的向量化操作替代循环,这些操作底层是C实现的,速度能提升几个数量级。
方法1:用scikit-learn的cosine_similarity
如果你的user_normalized和item_matrix_same_shape都是已经做了L2归一化的DataFrame,直接用这个函数一次性计算所有用户-物品的相似度矩阵:
from sklearn.metrics.pairwise import cosine_similarity import numpy as np # 把DataFrame转成numpy数组(可选,但运算更快) user_arr = user_normalized.values item_arr = item_matrix_same_shape.values # 计算相似度矩阵:形状是(用户数, 物品数) similarity_matrix = cosine_similarity(user_arr, item_arr)
这个矩阵里的similarity_matrix[i][j]就是第i个用户和第j个物品的余弦相似度,一次运算完成,速度会快到离谱——17000x1500的矩阵计算可能只需要几秒到几十秒(取决于你的机器性能)。
方法2:手动用numpy点积(如果已经归一化)
如果确认数据已经做了L2归一化,直接用点积等价于余弦相似度:
# 确保数组是float类型,减少内存占用并提升运算速度 user_arr = user_normalized.values.astype(np.float32) item_arr = item_matrix_same_shape.values.astype(np.float32) # 点积计算相似度,注意转置物品矩阵以匹配维度 similarity_matrix = user_arr @ item_arr.T
进阶优化:近似最近邻(如果只需要Top-N推荐)
如果你的需求是给每个用户推荐Top K个最相似的物品,不需要计算所有用户-物品的相似度,可以用近似最近邻算法进一步提速,比如Facebook的faiss库,它能在大规模数据下快速找到每个用户的Top-N相似物品,比计算全矩阵更高效:
import faiss # 把物品矩阵转成faiss需要的格式(float32,行向量) item_arr = item_matrix_same_shape.values.astype(np.float32) user_arr = user_normalized.values.astype(np.float32) # 构建索引:L2距离等价于1-余弦相似度(数据归一化后) index = faiss.IndexFlatL2(item_arr.shape[1]) index.add(item_arr) # 每个用户找Top 10相似物品 k = 10 distances, indices = index.search(user_arr, k) # 转换为余弦相似度:归一化后,cosθ = 1 - (distances²)/2 similarities = 1 - (distances ** 2) / 2
最后修正你的原始逻辑
刚才提到的,你的循环里每次i迭代都会覆盖item_matrix_cpy['cosine'],这会导致最终只保留最后一个用户的结果。用上面的方法得到的similarity_matrix才是正确的用户-物品相似度矩阵,你可以根据需求把它转成DataFrame或者直接用于推荐计算。
内容的提问来源于stack exchange,提问作者Nedunuri Rajesh

