余弦相似度结果全为1或nan的常见原因是用户向量全零(未评分)或仅对同一物品评分(向量共线),导致分母为0返回nan,或向量完全相同/成比例而恒得1;实操需过滤空向量、使用稀疏矩阵和sklearn内置函数并添加防错处理。

余弦相似度计算用户相似性时,为什么结果全是1或nan?
常见原因是用户向量全为0(没评过分)或只对同一物品评分(向量共线)。余弦相似度公式 cosθ = (A·B) / (||A||·||B||) 在分母为0时会返回 nan,而两个完全相同的非零向量(如都只评了item1=5)会导致结果恒为1,失去区分度。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 预处理必须过滤掉评分向量为空或仅含一个非零值的用户:
user_ratings = user_ratings[user_ratings.sum(axis=1) > 1] - 使用稀疏矩阵(
scipy.sparse.csr_matrix)存储用户-物品评分表,避免零值膨胀导致内存爆炸 - 用
sklearn.metrics.pairwise.cosine_similarity替代手写循环——它内部自动跳过全零向量并返回0而非nan - 若需自定义,务必加防错:对每个用户向量先检查
np.linalg.norm(vec) == 0,为真则整行相似度设为0
如何高效获取Top-K最相似用户而不遍历全部用户?
协同过滤中,对每个目标用户都要找K个最相似邻居,暴力计算所有用户对相似度是O(N²),N=10万时不可行。关键不是“怎么算”,而是“怎么不全算”。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 用
sklearn.neighbors.NearestNeighbors(algorithm='brute', metric='cosine')构建索引,fit()输入用户-物品评分矩阵(每行一个用户),再用kneighbors()直接返回Top-K邻居及相似度 - 注意:该方法默认返回距离(1−cosine),所以相似度需手动转成
1 - distances;若用metric='precomputed'则需自己先算好全量相似度矩阵,反而更慢 - 对冷启动用户(新用户、评分极少),
NearestNeighbors可能返回空邻居,此时应 fallback 到全局热门物品推荐,而不是报错 - 设置
n_jobs=-1启用多核,但矩阵过大时可能因内存拷贝变慢,建议先用sample_users = users[:5000]小规模验证逻辑
用户相似度矩阵算出来后,怎么生成最终推荐列表?
相似度只是中间结果,真正推荐依赖加权聚合:目标用户u未评过分的物品i,其预测评分 = Σ(相似用户v的评分 × sim(u,v)) / Σ|sim(u,v)|。但直接这么算容易被高分灌水(比如某v给所有物品打5分)。
实操建议:
立即学习“Python免费学习笔记(深入)”;
- 必须中心化处理:对每个用户v,先减去其平均分
ratings_v_centered = ratings_v - ratings_v[ratings_v != 0].mean(),再参与余弦计算和加权预测 - 预测时只考虑对物品i有评分的相似用户v,即动态子集,不能拿全量相似用户去乘——否则未评过i的v会贡献0,拉低权重和
- 用
scipy.sparse的布尔索引加速:假设item_col = 123,则sim_users_mask = train_matrix[neighbor_ids, item_col] != 0快速筛出有效邻居 - 推荐列表按预测分降序取前N,但要剔除目标用户已评过分的物品——用集合差:
set(all_items) - set(already_rated)
为什么线上服务响应慢?瓶颈通常不在余弦计算本身
实际部署中,90%的延迟来自IO和数据结构转换,而非 cosine_similarity 函数。例如每次请求都从数据库查全量评分表、转成稠密array、再喂给sklearn,会反复触发GC和内存分配。
关键优化点:
- 离线预计算用户相似度邻接表(每个用户存top-100 neighbor_id + similarity),序列化为
joblib.dump或npz,加载进内存常驻 - 运行时只做查表+稀疏预测,避免任何
fit()或矩阵重建;用numba.jit加速加权求和循环(对小数组提升明显) - 警惕
pandas.DataFrame.corr(method='cosine')—— 它内部强制转稠密且无稀疏优化,比sklearn慢10倍以上 - 如果用户量超50万,单机扛不住,就不要硬扛相似度矩阵;改用MinHash+LSH近似最近邻,牺牲少量精度换响应时间
余弦相似度本身很轻量,真正的复杂点永远在数据流动路径上:你传进去的是什么格式,中间做了几次copy,结果又怎么落到业务逻辑里——这些细节不抠,算法再“正确”也卡在第一秒。


















