应使用sklearn.metrics.pairwise.cosine_similarity而非手写实现,因其自动l2归一化、适配二维输入;输入需为float型二维矩阵(如用户×物品),稀疏矩阵须转float并reshape;物品协同过滤需用ratings.t;相似度范围[-1,1],降序取top-k;推荐分数用稀疏矩阵.dot()高效计算,避免for循环。

余弦相似度计算要用 sklearn.metrics.pairwise.cosine_similarity,别手写
自己用 numpy 实现余弦公式容易忽略归一化或维度对齐问题,比如用户-物品矩阵转置没处理好,结果全是 nan 或全为 1.0。直接调 cosine_similarity 更稳——它内部做了 L2 归一化,且自动适配二维输入(如用户向量矩阵)。
常见错误:把稀疏评分矩阵(如 scipy.sparse.csr_matrix)直接丢进函数却不指定 dtype=float,导致整数溢出或精度丢失;或者传入一维数组却没 reshape 成二维([[v]]),报 ValueError: Expected 2D array。
- 输入必须是二维:用户×物品矩阵,每行是一个用户的向量(缺失值填 0)
- 若原始数据稀疏,先转成
float类型的csr_matrix,再传入 - 输出是方阵:
similarity_matrix[i][j]表示用户 i 和 j 的相似度
用户冷启动时拿不到相似用户?用物品相似度兜底
新用户没有任何行为,无法在用户相似度矩阵中找邻居,硬算会得到全零行。这时候该切到物品侧:用物品共现矩阵(转置后)算 cosine_similarity,得到物品间相似度,再基于用户已评物品推荐相似物品。
关键点在于矩阵转置时机:用户协同过滤用原始矩阵;物品协同过滤必须用 ratings.T(物品×用户),再喂给 cosine_similarity。别漏掉这一步,否则相似度全是 0。
- 物品相似度矩阵形状是
(n_items, n_items),不是(n_users, n_users) - 用户只评过 1 个物品?那该物品的相似物品列表就是唯一推荐源
- 避免重复推荐:过滤掉用户已交互过的物品 ID
cosine_similarity 返回值不是距离,别当距离用
余弦相似度范围是 [-1, 1],值越大越相似。有人误把它当欧氏距离用,做 argsort 时升序取 top-K,结果拿到最不相似的几个用户。正确做法是降序排列,或用 1 - similarity 转成距离再升序——但没必要,直接 np.argsort(-similarity_row)[:k] 更直观。
另一个坑:相似度为负值时(比如用户兴趣完全相反),是否该排除?视场景而定。电影推荐通常保留负值(反向推荐小众片),但电商场景常加阈值过滤,如只取 similarity > 0.1 的邻居。
- 排序前检查是否有有效相似值:
np.any(similarity_row > 0) - 阈值设太低(如 0.01)会引入噪声邻居;太高(如 0.8)可能导致无推荐
- 推荐分数 = Σ(邻居评分 × 相似度) / Σ|相似度|,注意分母是绝对值和,防负值干扰
稀疏矩阵乘法比 for 循环快十倍,但得用 .dot() 不是 *
生成最终推荐分数时,常见写法是遍历每个用户、每个未评分物品、每个邻居,三层循环——10 万用户就卡死。正确姿势是用稀疏矩阵乘法:user_similarity @ user_ratings,其中 @ 或 .dot() 自动利用 CSR 格式加速。
但 * 是逐元素乘,@ 才是矩阵乘。如果用了 *,结果形状不对,还会触发隐式广播错误。另外,user_similarity 必须是 float64,否则乘法后精度塌缩,推荐分全为 0。
- 确保两个矩阵 dtype 一致:
user_similarity.astype(np.float64) - 结果需 mask 掉用户已评物品位置,用布尔索引:
pred[already_rated_mask] = 0 - 内存不够?分块计算:
np.array_split切用户批次,逐块.dot()
实际跑起来你会发现,相似度计算本身不慢,慢在后续的 top-K 检索和去重逻辑——尤其是用 Python 做 item ID 映射时,字典查表比 numpy index 慢一个数量级。这部分没法靠 cosine_similarity 解决,得提前建好 ID → index 的映射数组。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











