news 2026/3/11 17:09:17

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/11 6:10:47

OpenTabletDriver全面指南:释放数位板创作潜能的专业设置方案

在数字艺术创作领域,拥有一款得心应手的数位板驱动至关重要。OpenTabletDriver作为开源跨平台解决方案,为创作者提供了前所未有的设备兼容性和自定义能力。本文将带你从零开始,深入掌握这款强大工具的各项功能配置。 【免费下载链接】OpenTab…

作者头像 李华
网站建设 2026/3/3 22:59:33

2025全新视角:从零开始掌握3D建模的核心技能体系

2025全新视角:从零开始掌握3D建模的核心技能体系 【免费下载链接】Cura 3D printer / slicing GUI built on top of the Uranium framework 项目地址: https://gitcode.com/gh_mirrors/cu/Cura 想要在数字时代掌握一项硬核技能?3D建模正是连接创意…

作者头像 李华
网站建设 2026/3/5 23:20:21

创业公司技术选型:为什么选择IndexTTS 2.0作为核心引擎

创业公司技术选型:为什么选择IndexTTS 2.0作为核心引擎 在短视频日活破亿、虚拟主播席卷直播平台的今天,声音正在成为内容产品的“第二张脸”。一个冷冰冰的AI配音和一段富有情感张力的拟人化语音之间,可能只差一个真正懂创作的技术引擎。我…

作者头像 李华
网站建设 2026/3/11 4:46:54

打造专属宝可梦世界:pkNX编辑器完全解析与实战攻略

打造专属宝可梦世界:pkNX编辑器完全解析与实战攻略 【免费下载链接】pkNX Pokmon (Nintendo Switch) ROM Editor & Randomizer 项目地址: https://gitcode.com/gh_mirrors/pk/pkNX 想要在Switch宝可梦游戏中打造完全个性化的冒险体验吗?pkNX作…

作者头像 李华
网站建设 2026/2/26 10:49:59

音色情感自由组合!IndexTTS 2.0解耦设计让AI配音更灵活

音色情感自由组合!IndexTTS 2.0解耦设计让AI配音更灵活 在短视频、虚拟主播和有声内容爆发的今天,一个声音背后的情绪张力,往往决定了观众是否“入戏”。你有没有遇到过这样的情况:明明写好了充满激情的台词,AI合成的声…

作者头像 李华
网站建设 2026/3/11 5:01:48

BilibiliDown:高效下载B站视频的终极技术方案

还在为无法离线保存精彩B站内容而烦恼吗?BilibiliDown作为一款跨平台视频下载工具,通过智能解析和批量处理技术,彻底解决了视频保存的痛点问题。这款基于Java开发的开源工具支持Windows、macOS和Linux三大系统,让你在任何设备上都…

作者头像 李华