
推荐系统(二)—— 协同过滤与矩阵分解
深入讲解协同过滤与矩阵分解:User-CF、Item-CF、相似度度量、潜因子模型、SVD++、ALS、BPR、因子分解机,附直觉解释、推导和可运行 Python。
刚看完《肖申克的救赎》,想找一部类似感觉的电影。用类型筛选,结果会塞满一堆糟糕的监狱题材烂片;而协同过滤则换了个思路:它不关心电影本身,而是问了一个简单的问题——和你看过相同内容的人,还喜欢什么?
这个简单的想法——让群体行为说话——撑起了 Amazon、YouTube、Spotify 以及你每天刷到的推荐流。本文将详细讲解背后的算法演变:从 1990 年代的邻域方法,到拿下 Netflix Prize 的矩阵分解模型。
你将学到:
- 邻域协同过滤的两条路线(User-CF 和 Item-CF)及其适用场景
- 用余弦、皮尔逊、调整余弦衡量“相似口味”的方法
- 稀疏数据上矩阵分解比邻域方法更有效的理由
- SGD 和 ALS:同一模型的不同训练方式
- BPR 用于排序,FM 处理边信息,加权 MF 应对隐式反馈
- 每个算法都有可运行的 Python 实现,文末解答常见问题和坑点

· 协同过滤的核心思想#
协同过滤(Collaborative Filtering,CF)基于一个简单的假设:
历史行为相似的用户,需求也相似;被同一群人喜欢的物品,彼此也相似。
这就够了。CF 不需要类型标签、演员列表或用户画像,只需记录谁对什么有过交互。基于这一假设,衍生出两种方法:
- User-CF:找到与你历史行为相似的用户,推荐他们喜欢的东西。
- Item-CF:找到与你已喜欢的物品相似的其他物品,推荐给你。

一个玩具评分矩阵#
5 个用户 × 5 部电影,评分范围 1–5,缺失值用 ? 表示:
| 用户 | 肖申克 | 阿甘 | 当幸福 | 泰坦尼克 | 教父 |
|---|---|---|---|---|---|
| Alice | 5 | 5 | ? | 3 | 4 |
| Bob | 5 | 5 | 4 | 2 | ? |
| Carol | 4 | 4 | 3 | 5 | 5 |
| David | 2 | 1 | ? | 4 | 3 |
| Eve | ? | ? | 2 | 3 | 4 |
User-CF 的逻辑:Alice 和 Bob 在《肖申克》和《阿甘》上的评分完全一致,说明他们的品味接近。Bob 给《当幸福》打了 4 分,因此把《当幸福》推荐给 Alice。
Item-CF 的逻辑:《肖申克》和《阿甘》在所有用户中的评分分布几乎相同,说明这两部电影相似。喜欢其中一部的人大概率也会喜欢另一部。
优势与局限#
CF 的优势在于简单(不需要特征工程)和惊喜感(能挖掘出非显而易见的关联,如经典的“啤酒与尿布”案例),还能跨品类推荐,例如把播客推荐给爱看小说的用户。
但它的局限也很明显:
- 冷启动:新用户或新物品没有历史数据;
- 稀疏性:真实场景中的评分矩阵 99% 以上是空的;
- 热度偏差:热门物品会挤压长尾物品的曝光机会。
后面要讲的矩阵分解方法,正是为了解决稀疏性问题而设计的。
· 基于用户的协同过滤(User-CF)#
核心步骤#
- 选择一种相似度度量,计算每对用户之间的相似度。
- 对每个预测目标,找到与目标用户最相似的 $k$ 个邻居,称为邻域。
- 用邻域用户的评分加权平均,预测缺失评分。
- 排序、截取 Top-N,生成推荐。
如何衡量“品味相似”#
设用户 $u$ 和 $v$ 都评过分的物品集合为 $I_{uv}$ 。文献中最常用的三种相似度度量如下:
$$ \text{sim}(u, v) = \frac{\sum_{i \in I_{uv}} r_{ui}\, r_{vi}}{\sqrt{\sum_{i \in I_{uv}} r_{ui}^2}\;\sqrt{\sum_{i \in I_{uv}} r_{vi}^2}} $$它只关注方向。比如用户 A 打分范围是 4–5,用户 B 是 1–2,但打分形状一致,余弦相似度仍会认为他们高度相似。
$$ \text{sim}(u, v) = \frac{\sum_{i \in I_{uv}} (r_{ui} - \bar{r}_u)(r_{vi} - \bar{r}_v)}{\sqrt{\sum_{i \in I_{uv}} (r_{ui} - \bar{r}_u)^2}\;\sqrt{\sum_{i \in I_{uv}} (r_{vi} - \bar{r}_v)^2}} $$调整余弦也用了类似的去均值方法;当物品集对齐时,它和皮尔逊在数学上等价。
下图直观展示了两者的差异。用户 A 打分慷慨,用户 B 打分严格,但他们的偏好曲线形状完全一致——只是评分水位不同。

余弦相似度给出约 0.99,因为两个向量几乎指向同一方向;皮尔逊相关系数给出 1.00——去均值后两条曲线完全重合。在真实系统中,用户打分习惯差异较大,皮尔逊通常是更稳妥的选择。
用皮尔逊相关系数计算玩具矩阵,得到以下热力图。Alice 和 Bob 紧密正相关,David 则是个反例。

评分预测#
$$ \hat{r}_{ui} = \bar{r}_u + \frac{\sum_{v \in N_k(u)} \text{sim}(u, v) \cdot (r_{vi} - \bar{r}_v)}{\sum_{v \in N_k(u)} |\text{sim}(u, v)|} $$可以理解为:“从我的平均分出发,根据邻居们对这个物品的评分调整,越像我的邻居权重越大。” 其中 $r_{vi} - \bar{r}_v$ 表示邻居相对于自己基准的评分偏差——这和皮尔逊的去均值思路一脉相承。
可运行的实现#
| |
User-CF 的局限#
User-CF 直观且易于解释,但离线计算相似度的时间复杂度是 $O(m^2 \cdot n)$ ——用户数平方级增长。稀疏数据下,用户间共评物品太少,相似度难以准确计算;用户冷启动问题更是致命。下一节将问题翻转,探索基于物品的协同过滤。
· 基于物品的协同过滤(Item-CF)#
为什么工业界偏爱 Item-CF#
Amazon 在 2003 年发表的论文让 Item-CF 成为推荐系统的默认选择。原因很简单,但非常关键:
- 物品数量通常少于用户数量,所以相似度矩阵更小、计算成本更低。
- 物品相似度稳定。一部电影的受众特征不会一夜之间改变,但用户的心情会变。我可以提前计算好物品相似度并缓存起来。
- 推荐结果可解释性强。“因为你喜欢 A,所以推荐类似的 B”,这种话用户一听就懂。
调整余弦:首选的相似度度量#
$$ \text{sim}(i, j) = \frac{\sum_{u \in U_{ij}} (r_{ui} - \bar{r}_u)(r_{uj} - \bar{r}_u)}{\sqrt{\sum_{u \in U_{ij}} (r_{ui} - \bar{r}_u)^2}\;\sqrt{\sum_{u \in U_{ij}} (r_{uj} - \bar{r}_u)^2}} $$一个关键点是减去的是用户均值,而不是物品均值。这一步去掉了“谁倾向于打高分”的噪声,剩下的就是两件物品是否在同一个人身上引发一致反应。
预测公式#
$$ \hat{r}_{ui} = \bar{r}_u + \frac{\sum_{j \in N_k(i)} \text{sim}(i, j) \cdot (r_{uj} - \bar{r}_u)}{\sum_{j \in N_k(i)} |\text{sim}(i, j)|} $$这里用的是物品 $i$ 的 $k$ 个最近邻进行投票。
实现#
| |
Item-CF 是推荐系统的核心方法,但在百万级物品库中,预计算的相似度矩阵会占用大量内存。这就促使我寻找一种全新的思路:不再直接比较行和列,而是对矩阵进行压缩。

· 矩阵分解#
从邻域到隐向量#
评分矩阵 $R \in \mathbb{R}^{m \times n}$ 又大又稀疏,但信号是低秩的:少数几个潜在的“品味维度”就能解释大部分方差。矩阵分解直接捕捉了这个直觉。
$$ R \approx P \cdot Q^{\!T} $$ $$ \hat{r}_{ui} = \mathbf{p}_u \cdot \mathbf{q}_i = \sum_{f=1}^{k} p_{uf}\, q_{if} $$
为什么比邻域法强得多#
两个原因:
- 存储从 $O(mn)$ 降到 $O((m+n)k)$ 。 百万用户 × 百万物品 × 8 字节 = 8 TB 密集矩阵;分解到 $k=50$ 后大约只剩 0.8 GB。
- 缺失值成为一等公民: 邻域方法需要共同评分才能比较两行;矩阵分解直接从学到的隐向量预测每个单元格。
几何视角#
把 $k$ 个轴看作模型自动发现的“品味维度”——比如 硬核剧情 vs. 轻喜剧、现代爆米花 vs. 文艺经典。没人手工标注这些维度,模型从数据中自己学出来。

用户向量 $\mathbf{p}_u$ 表示“你对每个维度有多在意”,物品向量 $\mathbf{q}_i$ 表示“它在每个维度上有多强”。二者内积就是匹配分。
目标函数#
$$ \mathcal{L} = \sum_{(u,i) \in \mathcal{R}} (r_{ui} - \mathbf{p}_u \cdot \mathbf{q}_i)^2 + \lambda \big(\|\mathbf{p}_u\|^2 + \|\mathbf{q}_i\|^2\big) $$优化方法主要有两种:SGD 和 ALS。
优化方法一:随机梯度下降(SGD)#
$$ \mathbf{p}_u \leftarrow \mathbf{p}_u + \alpha\, (e_{ui}\, \mathbf{q}_i - \lambda\, \mathbf{p}_u) $$ $$ \mathbf{q}_i \leftarrow \mathbf{q}_i + \alpha\, (e_{ui}\, \mathbf{p}_u - \lambda\, \mathbf{q}_i) $$SGD 极简单,容易在线化,内存占用小。缺点是每次只更新一个单元格,收敛慢。
| |
优化方法二:交替最小二乘(ALS)#
ALS 利用了一个漂亮的特点:固定 $P$ 或 $Q$ 中的一个,损失退化成带正则的线性回归,有解析解。 我们交替更新。
$$ \mathbf{p}_u = (Q_u^{\!T} Q_u + \lambda I)^{-1} Q_u^{\!T} \mathbf{r}_u $$ $$ \mathbf{q}_i = (P_i^{\!T} P_i + \lambda I)^{-1} P_i^{\!T} \mathbf{r}_i $$每个半步是一次小型线性求解,更重要的是不同用户之间彼此独立——天生适合 Spark MLlib 这种分布式引擎。
SGD 与 ALS 实战对比#
下图是同一个 80×60 合成评分矩阵上两种优化器的训练曲线。ALS 头几步就插到地板,因为每步是闭式解;SGD 要慢慢蹭到差不多的位置。

但“ALS 更快”这句话需要加注脚。ALS 内存开销更大——每次更新都要装一个 $k \times k$ 的 Gram 矩阵;SGD 一条样本一条样本地走,内存友好。粗略经验:
- 小数据、需要在线更新:选 SGD。
- 大批量、有分布式集群:选 ALS。
- 两者通吃:先用 ALS 离线初始化,再用 SGD 在线增量更新。
· 偏置项 —— 提升准确率最划算的方法#
简单的矩阵分解假设评分完全由用户和物品的潜在交互决定。但实际情况要复杂得多:
- 有些用户打分特别宽松,动不动就给 4–5 分;另一些用户则很严格,基本只打 1–2 分。
- 有些物品天生受欢迎,几乎人人都喜欢;另一些物品则普遍不讨喜。
- 整个平台还有一个全局平均分(比如在 1–5 分制下,通常在 3.5 左右)。
其中,$\mu$ 是全局均值,$b_u$ 是用户偏置,$b_i$ 是物品偏置。因子向量只需要专注于拟合真正反映用户品味的交互部分。
$$ b_u^{(0)} = \frac{1}{|I_u|}\sum_{i \in I_u}(r_{ui} - \mu), \qquad b_i^{(0)} = \frac{1}{|U_i|}\sum_{u \in U_i}(r_{ui} - \mu - b_u^{(0)}) $$SGD 更新时只需多加两行代码:
| |
这就是 Netflix Prize 时代所谓的 “SVD”——它并不是教科书上的奇异值分解,而是借了个熟悉的名字,实际指的是带偏置的矩阵分解。
· SVD++ —— 融入隐式反馈#
用户的行为大多是隐式的:点击、滑动、停留时长、购买但不评分。SVD++ 利用这些“无声信号”构建更丰富的用户向量。
$$ \hat{r}_{ui} = \mu + b_u + b_i + \mathbf{q}_i^{\!T} \left(\mathbf{p}_u + |I_u|^{-1/2}\sum_{j \in I_u} \mathbf{y}_j\right) $$括号里的意思是:“从显式因子出发,加上用户交互过的所有物品隐式嵌入的平均值。” $|I_u|^{-1/2}$ 防止活跃用户主导模型。
| |
在 Netflix Prize 中,从带偏置的 MF 升级到 SVD++,RMSE 再降约 2%。绝对值虽小,但在排行榜上却是决定性的优势。
· BPR —— 当排序比评分更重要#
如果我的核心指标是“用户是否点击了置顶的那条内容”,那么平方误差就不是一个合适的损失函数。RMSE 会把“预测值 4.6 对应真实值 5”的误差,和“把《当幸福》排在《肖申克》前面”的错误等同对待——但后者才是真正的痛点。
$$ \mathcal{L} = \sum_{(u, i, j) \in D_S} \ln \sigma(\hat{r}_{ui} - \hat{r}_{uj}) - \lambda \|\Theta\|^2 $$这里 $\sigma$ 是 sigmoid 函数。BPR 只关注物品对的相对顺序,因此天然规避了“没有显式负样本”的问题——而这正是隐式反馈数据的典型特征。
SGD 更新直接由梯度推导得出。对于一个三元组 $(u, i, j)$ ,令 $x_{uij} = \sigma(\hat{r}_{uj} - \hat{r}_{ui})$ ,更新规则如下:
| |
实际效果上,BPR 在隐式数据上的 AUC 比朴素 MF 高出 5–10%。当手头只有点击数据而没有评分数据时,BPR 是最自然的选择。
· 因子分解机(FM)—— 支持边特征的场景#
$$ \hat{y}(\mathbf{x}) = w_0 + \sum_{i=1}^{n} w_i x_i + \sum_{i=1}^{n}\sum_{j=i+1}^{n} \langle \mathbf{v}_i, \mathbf{v}_j \rangle\, x_i\, x_j $$ $$ \sum_{i<j} \langle \mathbf{v}_i, \mathbf{v}_j \rangle x_i x_j = \tfrac{1}{2}\sum_{f=1}^{k}\!\left[\Big(\sum_i v_{i,f} x_i\Big)^2 - \sum_i v_{i,f}^2 x_i^2\right] $$FM 的计算复杂度对特征数和因子数都是线性的,因此能够轻松应对百万维 one-hot 编码的工业级 CTR 数据集。
如果把 $\mathbf{x}$
设置为 [用户 one-hot ; 物品 one-hot],FM 就会退化为矩阵分解(MF)。换句话说,MF 是 FM 的一个特例,只用 ID 特征。
· 隐式反馈的三种处理方法#
真实推荐系统很少有评分数据,更多的是用户行为数据:浏览、点击、收听、购买。针对隐式反馈,主要有三种处理方式:
- 加权矩阵分解(Weighted MF):将每次观测到的交互视为目标值为 1 的正样本,并赋予置信度 $c_{ui} = 1 + \alpha \log(1 + \text{count})$ ,未观测到的交互则视为目标值为 0 的低置信度样本。损失函数变为 $\sum c_{ui} (1 - \hat{r}_{ui})^2 + \lambda \dots$
- 负采样(Negative Sampling):为每个正样本随机选取若干未交互的物品作为负样本,按目标值为 0 进行训练。
- BPR:直接基于排序对进行优化,完全绕开“如何定义负样本”的问题。
| |
· 实战 Q&A#
User-CF 还是 Item-CF? 优先选 Item-CF。物品数量少且更稳定,相似度矩阵小、生命周期长。只有在用户稀少但活跃的场景下才考虑 User-CF。
潜因子维度 $k$ 怎么选? 用户量 10 万以下,从 20–50 开始;百万级选 50–100;千万级以上用 100–200。然后用交叉验证调参,找到验证集 RMSE 不再下降的最小 $k$ ,这就是最佳值。
冷启动怎么处理? 新用户:用注册特征(年龄、地域)初始化隐向量,或者直接推荐热门物品兜底。新物品:用内容特征(类别、标签)初始化隐向量,或者基于物品侧特征做 Item-CF。混合策略一般是“先靠内容顶上,积累到 10–20 次交互后再切换到 CF”。
SGD 还是 ALS? SGD 内存占用低、支持在线更新;ALS 收敛快、适合分布式计算(如 Spark 集群)。很多团队晚上用 ALS 离线训练初始化,白天用 SGD 增量更新。
为什么 BPR 在隐式数据上比 RMSE 训练的 MF 效果好? 因为 RMSE 优化的是错误目标。下游指标(CTR、NDCG)关注的是排序,而不是绝对评分;BPR 直接优化排序。
如何应对稀疏性? 矩阵分解本身、加正则化、引入隐式反馈信号、通过 FM 引入边特征。没有一招制胜的办法,必须多管齐下。
偏置项为什么这么重要? 它剥离了“结构性效应”(比如某个用户打分普遍高、某物品普遍受欢迎),让隐向量专注于建模真实的用户偏好。加入偏置项后,RMSE 通常能下降 10–20%。
初始化怎么做? 因子用 $\mathcal{N}(0, 0.1)$ 初始化,偏置项设为 0(第一轮训练会自动调整)。避免全零初始化——梯度会卡住。
预测值越界怎么办? 直接截断到 $[r_{\min}, r_{\max}]$ 。Sigmoid 缩放数学上更优雅,但在离线评估中很少优于简单截断。
如何实现实时推荐? 缓存每个用户的隐向量;对头部用户预计算 Top-N;仅对长尾用户实时计算。需要持续学习时,用 SGD 在流式数据上更新,并对嵌入表进行版本管理。
如何防止过拟合? 使用 L2 正则,$\lambda \in [0.01, 0.1]$ ;验证集早停;如果还不行,先降低 $k$ ,最后再考虑其他复杂方法。
总结#
协同过滤是现代推荐系统的基础,整个发展脉络其实很清晰:
- 邻域方法把“和你相似的人也喜欢……”转化为相似度加权平均。Item-CF 是工业界的默认选择。
- 矩阵分解将稀疏的评分矩阵压缩为低维的用户和物品嵌入,通过内积预测评分。
- 偏置项去除结构性噪声;SVD++ 引入隐式反馈进一步提升效果。
- ALS 和 SGD 是两种优化思路——闭式解与流式更新、并行与在线的对比。
- BPR 用排序损失替代 RMSE;FM 将矩阵分解推广到任意稀疏特征。
这些思想换了个形式,依然贯穿在深度推荐模型中。DeepFM 的嵌入层本质就是矩阵分解;双塔召回是带神经网络编码器的矩阵分解;序列模型依旧依赖类似 SVD++ 方法学到的物品嵌入。掌握这一章内容,后续的系列你会觉得不过是同一主题的不同变奏。
参考文献#
- Koren, Y., Bell, R., & Volinsky, C. (2009). Matrix factorization techniques for recommender systems. Computer, 42(8), 30–37. IEEE
- Rendle, S., Freudenthaler, C., Gantner, Z., & Schmidt-Thieme, L. (2009). BPR: Bayesian personalized ranking from implicit feedback. UAI. arXiv:1205.2618
- Rendle, S. (2010). Factorization machines. ICDM. DOI
- Koren, Y. (2008). Factorization meets the neighborhood: a multifaceted collaborative filtering model. KDD. DOI
- Hu, Y., Koren, Y., & Volinsky, C. (2008). Collaborative filtering for implicit feedback datasets. ICDM. DOI
本文是推荐系统系列的第 2 篇,共 16 篇。
- 第 1 篇:入门与基础概念
- 第 2 篇:协同过滤与矩阵分解(当前)
- 第 3 篇:深度学习基础模型
- 第 4 篇:CTR 预估与点击率建模
- 第 5 篇:Embedding 表示学习
- 第 6 篇:序列推荐与会话建模
- 第 7 篇:图神经网络与社交推荐
- 第 8 篇:知识图谱增强推荐系统
- 第 9 篇:多任务学习(即将发布)
- 第 10 篇:深度兴趣网络(即将发布)
- 第 11 篇:对比学习(即将发布)
- 第 12 篇:大语言模型推荐(即将发布)
- 第 13 篇:公平性与可解释性(即将发布)
- 第 14 篇:跨域推荐与冷启动(即将发布)
- 第 15 篇:实时推荐与在线学习(即将发布)
- 第 16 篇:工业实践(即将发布)
推荐系统 16 篇
- 01 推荐系统(一)—— 入门与基础概念
- 02 推荐系统(二)—— 协同过滤与矩阵分解 当前
- 03 推荐系统(三)—— 深度学习基础模型
- 04 推荐系统(四)—— CTR 预估与点击率建模
- 05 推荐系统(五)—— Embedding 表示学习
- 06 推荐系统(六)—— 序列推荐与会话建模
- 07 推荐系统(七)—— 图神经网络与社交推荐
- 08 推荐系统(八)—— 知识图谱增强推荐系统
- 09 推荐系统(九)—— 多任务学习与多目标优化
- 10 推荐系统(十)—— 深度兴趣网络与注意力机制
- 11 推荐系统(十一)—— 对比学习与自监督学习
- 12 推荐系统(十二)—— 大语言模型与推荐系统
- 13 推荐系统(十三)—— 公平性、去偏与可解释性
- 14 推荐系统(十四)—— 跨域推荐与冷启动解决方案
- 15 推荐系统(十五)—— 实时推荐与在线学习
- 16 推荐系统(十六)—— 工业级架构与最佳实践