news 2026/4/3 1:30:51

Python实现协同过滤推荐:完整示例代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python实现协同过滤推荐:完整示例代码

从零实现协同过滤推荐:Python实战全解析

你有没有想过,为什么抖音总能“猜中”你喜欢的视频?为什么淘宝首页总在推你最近想买的东西?这背后离不开一个古老却强大的算法——协同过滤(Collaborative Filtering, CF)。

它不靠分析内容本身,而是通过“看人下菜碟”,利用群体行为预测个体偏好。哪怕我们不知道一部电影讲什么、一首歌是什么风格,只要知道“谁喜欢它”,就能推测“谁还会喜欢它”。

今天,我们就用 Python 亲手实现两种经典协同过滤:基于用户的基于物品的,并深入剖析其原理、陷阱与优化思路。代码可运行、逻辑可复现,适合初学者入门,也值得工程师温故知新。


协同过滤的本质:用行为说话

推荐系统的核心任务是解决“信息过载”。面对成千上万的商品或内容,如何为每个用户筛选出最可能感兴趣的那几个?

协同过滤的答案很朴素:模仿相似的人,或者推荐相似的东西

它的最大优势在于——不需要理解内容
你不必知道《星际穿越》是科幻片、配乐是谁、导演是不是诺兰;你只需要知道:“喜欢这部电影的人,通常也喜欢《盗梦空间》。” 这就足够了。

这种“群体智慧”的思想,让协同过滤成为推荐系统的基石技术之一。


用户-物品矩阵:所有故事的起点

一切始于一张表——用户-物品评分矩阵

假设有4位用户对5部电影打分(1~5分),数据如下:

import numpy as np import pandas as pd from sklearn.metrics.pairwise import cosine_similarity # 构建示例评分数据 data = { 'Movie A': [5, 4, 1, np.nan], 'Movie B': [4, np.nan, 2, 3], 'Movie C': [1, 2, 5, 4], 'Movie D': [np.nan, 3, 4, 5], 'Movie E': [3, 1, np.nan, 2] } df = pd.DataFrame(data, index=['User 1', 'User 2', 'User 3', 'User 4']) print("原始评分矩阵:") print(df)

输出:

Movie A Movie B Movie C Movie D Movie E User 1 5.0 4.0 1.0 NaN 3.0 User 2 4.0 NaN 2.0 3.0 1.0 User 3 1.0 2.0 5.0 4.0 NaN User 4 NaN 3.0 4.0 5.0 2.0

这个矩阵有几个关键特征:

  • 稀疏性高:大多数值为空(未评分)
  • 隐含偏好:数值越高表示越喜欢
  • 可扩展性强:可以替换为点击、浏览时长等隐式反馈

我们的目标就是:预测空缺位置的值,然后按预测得分排序,生成推荐列表。


基于用户的协同过滤:找“影评小圈子”

思路拆解

“物以类聚,人以群分。”
如果 User 1 和 User 3 看电影口味接近,而 User 3 喜欢 Movie D,那么我们可以推测 User 1 也可能喜欢。

步骤清晰:
1. 计算用户之间的相似度;
2. 找到与目标用户最像的 K 个“邻居”;
3. 根据邻居们对某电影的评分,加权预测目标用户的评分。

相似度怎么算?

最常用的是余弦相似度:把每个用户看作一个向量,计算两个向量夹角的余弦值。

但直接使用原始评分会有偏差——有些人天生打分偏高(比如总是打4、5分),有些人则很苛刻(最多给3分)。所以我们应该关注“偏离平均分的程度”。

因此,在实际计算前,建议先做中心化处理(减去用户均值)。

不过为了简化演示,我们先用fillna(0)填充缺失值进行初步计算:

# 填0便于计算相似度(生产环境应更谨慎) user_ratings_filled = df.fillna(0) # 计算用户间余弦相似度 user_similarity = cosine_similarity(user_ratings_filled) user_sim_df = pd.DataFrame(user_similarity, index=df.index, columns=df.index) print("\n用户相似度矩阵:") print(user_sim_df.round(3))

你会看到类似这样的结果:

User 1 User 2 User 3 User 4 User 1 1.000 0.686 0.178 0.555 User 2 0.686 1.000 0.598 0.832 User 3 0.178 0.598 1.000 0.870 User 4 0.555 0.832 0.870 1.000

可以看到,User 3 和 User 4 最相似(0.87),说明他们看电影的模式高度一致。

如何预测评分?

我们来预测User 1 对 Movie D 的评分

公式如下:
$$
\hat{r}{ui} = \bar{r}_u + \frac{\sum{v \in N(u)} \text{sim}(u,v) \cdot (r_{vi} - \bar{r}v)}{\sum{v \in N(u)} |\text{sim}(u,v)|}
$$

解释一下:
- $\bar{r}u$:用户 $u$ 的平均评分(基准线)
- $(r
{vi} - \bar{r}_v)$:邻居 $v$ 对物品 $i$ 的“超常发挥”
- 加权求和后加上自己的基准,得到最终预测

下面是完整实现:

def predict_rating(user, item, user_df, sim_df): if not pd.isna(user_df.loc[user, item]): return user_df.loc[user, item] # 已评分直接返回 rating_sum = 0.0 sim_sum = 0.0 user_mean = user_df.loc[user].mean() # 用户平均分 for other_user in user_df.index: if other_user == user: continue if pd.isna(user_df.loc[other_user, item]): continue # 邻居没看过该电影,跳过 sim = sim_df.loc[user, other_user] other_user_mean = user_df.loc[other_user].mean() deviation = user_df.loc[other_user, item] - other_user_mean rating_sum += sim * deviation sim_sum += abs(sim) if sim_sum == 0: return user_mean # 没有可用邻居,只能猜自己平均水平 predicted = user_mean + rating_sum / sim_sum return max(1, min(5, predicted)) # 截断到合理范围 [1,5]

现在遍历所有 User 1 没看过的电影:

target_user = 'User 1' unrated_items = df.loc[target_user].isna() predictions = {} for item in df.columns: if unrated_items[item]: pred = predict_rating(target_user, item, df, user_sim_df) predictions[item] = round(pred, 2) print(f"\n{target_user} 对未评分电影的预测评分:") for movie, score in predictions.items(): print(f" {movie}: {score}")

输出可能是:

User 1 对未评分电影的预测评分: Movie D: 3.24

所以系统会建议:“User 1,你可能会给 Movie D 打 3.2 分,要不要试试?”

⚠️ 注意:这里的相似度是基于填0后的向量计算的,存在一定偏差。更优做法是仅在共现项目上计算相似度,或使用皮尔逊相关系数。


基于物品的协同过滤:推荐“同类项”

如果说“用户-based”是在找“和你一样的人”,那“item-based”就是在找“和你喜欢的东西差不多的其他东西”。

比如你喜欢《肖申克的救赎》,系统发现喜欢这部电影的人也都喜欢《阿甘正传》,于是就把后者推荐给你。

这种方法更适合物品数量稳定、更新慢的场景(如电商商品、电影库)。

实现方式

思路也很简单:
1. 转置评分矩阵,让每行是一个物品;
2. 计算物品之间的相似度;
3. 利用用户已评分的相似物品,加权预测他对目标物品的兴趣。

# 物品侧:转置 + 填充(用列均值更合理) item_ratings_filled = df.T.fillna(df.mean(axis=0)) # 按列均值填充缺失 item_similarity = cosine_similarity(item_ratings_filled) item_sim_df = pd.DataFrame(item_similarity, index=df.columns, columns=df.columns) print("\n物品相似度矩阵:") print(item_sim_df.round(3))

你会发现,Movie C 和 Movie D 很相似(因为他们都被 User 3 和 User 4 高分评价),说明它们可能类型相近。

接下来写预测函数:

def predict_rating_item_based(user, item, user_df, item_sim_df): if not pd.isna(user_df.loc[user, item]): return user_df.loc[user, item] rating = 0.0 weight_sum = 0.0 for other_item in user_df.columns: if other_item == item: continue if pd.isna(user_df.loc[user, other_item]): continue sim = item_sim_df.loc[item, other_item] rating += sim * user_df.loc[user, other_item] weight_sum += abs(sim) return rating / weight_sum if weight_sum > 0 else user_df.loc[user].mean() # 示例:预测 User 1 对 Movie D pred_item_cf = predict_rating_item_based('User 1', 'Movie D', df, item_sim_df) print(f"\n基于物品协同过滤预测 User 1 对 Movie D 的评分: {round(pred_item_cf, 2)}")

输出可能是:

基于物品协同过滤预测 User 1 对 Movie D 的评分: 3.18

两种方法结果接近,但逻辑不同。你可以根据业务特点选择:

方法优点缺点适用场景
User-based动态反映用户兴趣变化用户增长快时计算开销大社交推荐、冷启动少
Item-based物品关系稳定,可预计算忽略用户个性化差异电商平台、视频平台

真实世界中的挑战与应对策略

别被上面的例子骗了——真实系统的难度远不止于此。以下是几个必须面对的现实问题:

❗ 数据太稀疏怎么办?

典型用户-物品矩阵稀疏度超过95%,意味着两个人几乎没有共同评分过的项目。

👉对策
- 使用皮尔逊相关系数代替余弦相似度,更能抵抗评分偏移;
- 引入矩阵分解(SVD、ALS)降维补全;
- 结合隐式反馈(点击、停留时间)扩充数据。

❗ 新用户/新物品无法推荐?(冷启动)

新注册用户还没评分,怎么知道他喜欢啥?新上架商品没人互动,怎么曝光?

👉对策
- 新用户:推荐热门榜单、引导评分;
- 新物品:初期随机曝光,积累初始交互;
- 混合策略:结合内容特征(如 genre、tag)做初步匹配。

❗ 百万级用户实时计算太慢?

每次都要算一遍所有用户对的相似度?O(n²) 复杂度不可接受!

👉对策
-离线预计算:每天定时生成相似度表;
-局部敏感哈希(LSH):快速找出潜在相似用户;
-聚类+局部搜索:先分群再在小范围内找邻居;
- 改用模型化方法:如矩阵分解神经网络

❗ 推荐越来越窄?(马太效应)

热门电影被反复推荐,小众佳作永远不见天日。

👉对策
- 在相似度中加入流行度惩罚项
- 推荐列表强制加入一定比例的长尾物品;
- 使用多样性指标(如覆盖率、熵)评估推荐质量。


工程落地:推荐系统的典型架构

一个工业级推荐系统不会只依赖协同过滤,但它往往是召回阶段的重要一环。

典型的四层架构如下:

[用户行为日志] ↓ [数据清洗 & 特征工程] → 构建用户-物品交互矩阵 ↓ [召回层] —— 协同过滤 / 向量检索 / 规则引擎 → Top-100候选 ↓ [排序层] —— LR / GBDT / DNN → 精排打分 ↓ [重排层] —— 加入多样性、新鲜度、业务规则 ↓ [前端展示]

其中,协同过滤常用于第一轮召回,快速圈定一批可能感兴趣的候选集,后续再由复杂模型精细排序。


小结:为什么你还应该学协同过滤?

尽管现在有 Graph Neural Network、Two-Tower、Transformer-based Recommender 等前沿模型,但协同过滤依然是不可绕过的起点。

因为它具备三大特质:

逻辑直观:容易理解和解释,“因为你喜欢A,所以推荐B”
实现简单:几行代码就能跑通原型
效果扎实:在很多场景下依然表现优异

更重要的是,它是通往更高级模型的桥梁。比如:

  • 矩阵分解(MF)本质上是对协同过滤的隐语义扩展;
  • 神经协同过滤(NCF)只是把相似度计算换成了神经网络;
  • 双塔模型的思想也源于“用户向量 vs 物品向量”的匹配。

下一步你可以做什么?

  1. 动手改数据:增加更多用户/物品,观察稀疏性影响;
  2. 尝试皮尔逊相关系数:替换余弦相似度,看看效果差异;
  3. 加入Top-K邻居限制:只考虑最相似的3个用户,提升效率;
  4. 集成到Flask API:封装成服务接口,支持实时请求;
  5. 对比Surprise库:用from surprise import KNNBasic验证你的结果。

如果你在实现过程中遇到问题,欢迎留言交流。也可以分享你的改进版本,我们一起打造一个更健壮的推荐引擎。

毕竟,每一个伟大的推荐系统,都曾从一行predict_rating()开始。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/27 19:15:11

动态漫画配音神器:IndexTTS 2.0严格对齐分镜时长不卡顿

动态漫画配音新范式:IndexTTS 2.0 如何实现音画精准对齐 在动态漫画、短视频和虚拟内容创作日益普及的今天,一个看似微小却极为恼人的难题始终困扰着创作者——配音总是慢半拍或快了一截。明明写好了台词,录好了语音,导入剪辑软件…

作者头像 李华
网站建设 2026/3/21 8:52:05

梯度反转层GRL技术拆解:IndexTTS 2.0如何实现音色情感分离

梯度反转层GRL技术拆解:IndexTTS 2.0如何实现音色情感分离 在当前AIGC浪潮席卷内容创作领域的背景下,语音合成早已不再满足于“把文字读出来”。无论是虚拟主播的情绪起伏、动漫角色的个性呐喊,还是有声书中的细腻演绎,用户期待的…

作者头像 李华
网站建设 2026/4/2 23:34:38

Ant Design Pro集成IndexTTS 2.0语音控制面板案例

Ant Design Pro 集成 IndexTTS 2.0 构建语音控制面板实践 在短视频、虚拟人、AI主播等应用爆发的今天,高质量语音生成已不再是专业工作室的专属工具。越来越多的内容创作者希望以极低门槛获得“像真人一样说话”的语音能力——既要声音像自己,又要情绪丰…

作者头像 李华
网站建设 2026/3/26 19:55:27

FlicFlac音频转换工具:从入门到精通的全方位指南

FlicFlac音频转换工具:从入门到精通的全方位指南 【免费下载链接】FlicFlac Tiny portable audio converter for Windows (WAV FLAC MP3 OGG APE M4A AAC) 项目地址: https://gitcode.com/gh_mirrors/fl/FlicFlac 在数字音频处理日益普及的今天,…

作者头像 李华
网站建设 2026/3/31 19:11:31

构建面向未来的迁移学习组件:从理论到异构任务实践

好的,收到您的需求。基于随机种子 1767574800071 的引导,我将为您撰写一篇关于迁移学习组件化设计的深度技术文章,聚焦于构建灵活、可复用的迁移学习流水线,并探讨在异构任务(如从文本到时间序列)中的新颖应…

作者头像 李华
网站建设 2026/3/29 10:04:25

SpleeterGUI:AI音乐源分离终极指南 [特殊字符]

想要轻松分离音乐中的人声和伴奏吗?SpleeterGUI就是你的完美选择!这款基于AI技术的Windows桌面应用,让复杂的音频分离变得像拖放文件一样简单。无需编程知识,无需安装繁琐的依赖,一切都已经为你准备好了。 【免费下载链…

作者头像 李华