推荐系统(二)—— 协同过滤与矩阵分解
深入讲解协同过滤与矩阵分解:User-CF、Item-CF、相似度度量、潜因子模型、SVD++、ALS、BPR、因子分解机,附直觉解释、推导和可运行 Python。
刚看完《肖申克的救赎》,想再找一部"那种感觉"的电影。按类型筛选会把你淹死在一堆烂片监狱题材里。协同过滤换了个思路:它根本不看电影本身,只问一个问题——和你看过相似东西的人,还都喜欢什么?
这一个想法,撑起了 Amazon、YouTube、Spotify 和你今天刷到的每一条 Feed。本文要讲清楚背后的算法谱系:从 1990 年代的邻域方法,一路到拿下 Netflix Prize 的矩阵分解。
你将学到:
- 邻域协同过滤的两条路线(User-CF 与 Item-CF)以及各自的适用场景
- 余弦、皮尔逊、调整余弦——三种相似度度量的差别究竟在哪
- 为什么稀疏数据下矩阵分解比邻域方法更稳
- SGD 与 ALS:同一个模型,截然不同的训练故事
- BPR 解决排序、FM 处理边信息、加权 MF 处理隐式反馈
- 每个算法都有可跑的 Python 实现,文末附实战常见坑
1 · 协同过滤的核心思想
协同过滤(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% 以上都是缺失。
- 流行度偏差:热门挤压长尾。
后面要讲的矩阵分解,正面解决了第二个痛点。
2 · 基于用户的协同过滤(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 是逆向的那一个。

评分预测
拿到目标用户 $u$ 的 $k$ 个最近邻 $N_k(u)$ 之后:
$$ \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)$——用户数平方级;稀疏数据下两个用户共评物品太少,相似度算不准;用户冷启动是最致命的一刀。下一节把问题翻过来。
3 · 基于物品的协同过滤(Item-CF)
为什么工业界默认 Item-CF
Amazon 2003 年那篇论文奠定了 Item-CF 的工业地位,原因朴素但决定性:
- 物品数往往少于用户数,相似度矩阵更小、更便宜。
- 物品相似度稳定。一部电影的受众画像不会一夜变化;用户的心情会。可以提前算好物品相似度,长期缓存。
- 可解释性更强。“因为你看了 A,所以推 B”,这句话用户真的看得懂。
调整余弦:首选度量
对被同一批用户 $U_{ij}$ 共同评分的物品 $i$ 和 $j$:
$$ \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 是骨干,但在百万级物品库下,预算的相似度矩阵也会变成内存怪物。这就引出了一种根本不同的思路:不再两两比较,而是把矩阵压缩。
4 · 矩阵分解
从邻域到潜因子
评分矩阵 $R \in \mathbb{R}^{m \times n}$ 又大又空。但其中的信号是低秩的:少数几个潜在的"品味维度"就能解释绝大部分方差。矩阵分解把这个直觉显式写出来:
$$ R \approx P \cdot Q^{\!T} $$其中 $P \in \mathbb{R}^{m \times k}$ 一行一个用户,$Q \in \mathbb{R}^{n \times k}$ 一行一个物品。维度 $k$(通常 20–200)就是潜因子的数量。预测评分就是简单的内积:
$$ \hat{r}_{ui} = \mathbf{p}_u \cdot \mathbf{q}_i = \sum_{f=1}^{k} p_{uf}\, q_{if} $$
比邻域法好在哪
两点:
- 存储从 $O(mn)$ 降到 $O((m+n)k)$。 100 万用户 × 100 万物品 × 8 字节 = 8 TB 稠密矩阵;分解到 $k=50$ 后大约只剩 0.8 GB。
- 缺失值变成一等公民。 邻域方法必须依赖共同评分才能算相似度;分解模型可以从学到的因子里直接预测任意一格。
几何解读
把 $k$ 个轴想成被模型自动发现的"品味维度"——硬核剧情 vs. 轻喜剧、现代爆米花 vs. 文艺经典……没有人手工标注,模型从数据中自己学出来。

用户向量 $\mathbf{p}_u$ 记录"你在每个轴上有多在意",物品向量 $\mathbf{q}_i$ 记录"它在每个轴上有多少含量",二者内积就是匹配分。
目标函数
我们让 $P$ 和 $Q$ 在已观测评分上最小化平方误差,再加一项 L2 正则压住因子的范数:
$$ \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)
对每条已观测的评分 $(u, i)$,定义误差 $e_{ui} = r_{ui} - \hat{r}_{ui}$,然后沿负梯度方向小步走:
$$ \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$ 中的任一个,损失就退化成带正则的线性回归,有解析解。 那就交替来。
固定 $Q$,最优用户向量是
$$ \mathbf{p}_u = (Q_u^{\!T} Q_u + \lambda I)^{-1} Q_u^{\!T} \mathbf{r}_u $$其中 $Q_u$ 把用户 $u$ 评过的所有物品向量堆成矩阵。对称地,固定 $P$:
$$ \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 在线增量更新。
5 · 偏置项 —— 性价比最高的一招
朴素分解假设评分完全来自用户–物品的潜在交互。但现实噪音很多:
- 有人逢片必给 4–5 分(慷慨),有人天生只打 1–2(严苛)。
- 有些物品被普世追捧,另一些被普世吐槽。
- 整个平台还有一个全局水位(1–5 分制下通常约 3.5)。
加偏置项几乎不花什么力气,却经常把 RMSE 直接砍掉 10–20%:
$$ \hat{r}_{ui} = \mu + b_u + b_i + \mathbf{p}_u \cdot \mathbf{q}_i $$$\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"其实就是它——不是教科书里的奇异值分解,而是带偏置的矩阵分解借了一个熟脸名字。
6 · SVD++ —— 把隐式反馈也用起来
用户的大部分行为是隐式的:点击、滚动、停留时长、买了但不评分。SVD++ 把这些"沉默的信号"也吸进用户向量里。
设 $I_u$ 是用户 $u$ 以任何方式接触过的物品集合(评分或仅交互)。给每个这样的物品再学一组向量 $\mathbf{y}_j$。用户表示变为:
$$ \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++ 大约再砍 ~2% 的 RMSE。绝对值不大,在排行榜上就是决定性的差距。
7 · BPR —— 当排序比评分更重要
如果你真正在意的指标是"用户点了置顶那条吗",那么平方误差是个错误的损失函数。RMSE 把"4.6 预测真值 5"罚得和"把《当幸福》排到《肖申克》前面"一样狠——后者才是真的伤。
贝叶斯个性化排序(BPR)换了一个目标:构造三元组 $(u, i, j)$,其中用户 $u$ 与物品 $i$ 有交互、与 $j$ 没有,然后最大化"模型给 $i$ 的分高于 $j$“的概率:
$$ \mathcal{L} = \sum_{(u, i, j) \in D_S} \ln \sigma(\hat{r}_{ui} - \hat{r}_{uj}) - \lambda \|\Theta\|^2 $$$\sigma$ 是 sigmoid。BPR 只看 pair 的先后顺序,因此对"没有显式负样本"这件事天然免疫——这恰好就是隐式反馈数据的常态。
SGD 更新由梯度直接给出。对一个三元组 $(u, i, j)$,记 $x_{uij} = \sigma(\hat{r}_{uj} - \hat{r}_{ui})$:
| |
经验上,BPR 在隐式数据上的 AUC 比朴素 MF 高出 5–10%。手里只有点击不是评分时,它就是默认选择。
8 · 因子分解机(FM)—— 把边特征也吃进去
纯 MF 只认识用户 ID 和物品 ID。但你手里通常远不止这些:用户年龄、物品类目、当前时段……Steffen Rendle 在 2010 年提出的因子分解机把 MF 推广到任意稀疏特征向量 $\mathbf{x}$:
$$ \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 $$每个特征都有一组潜向量 $\mathbf{v}_i$,两两交互通过内积打分。最妙的一处是:表面上 $O(n^2)$ 的交互项可以化简为 $O(kn)$:
$$ \sum_{i如果把 $\mathbf{x}$ 设成 [用户 one-hot ; 物品 one-hot],FM 正好退化为矩阵分解——MF 是 FM “只用 ID 特征” 的特例。
9 · 隐式反馈的几种处理思路
真实平台很少有评分,更多的是行为:浏览、点击、收听、购买。三种主流套路:
- 加权矩阵分解:把每条已观测交互看成"目标值 1,置信度 $c_{ui} = 1 + \alpha \log(1 + \text{count})$",把缺失看成"目标值 0,置信度低”。损失变成 $\sum c_{ui} (1 - \hat{r}_{ui})^2 + \lambda \dots$
- 负采样:每条正样本配若干个未交互物品作为负样本,按 0 训练。
- BPR:如上节所述,用排序对绕开"什么是负样本"这件事。
| |
10 · 实战 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 收敛快、可分布式。许多团队晚上 ALS 离线初始化,白天 SGD 增量微调。
为什么 BPR 在隐式数据上比 RMSE 训练的 MF 好? 因为 RMSE 优化的根本是错误目标。下游指标(CTR、NDCG)是排名的函数,不是评分的函数;BPR 直接优化排名。
对付稀疏性的招数。 矩阵分解本身、加正则、引入隐式反馈、用 FM 引入边特征——没有银弹,是一套组合拳。
偏置项凭什么这么有效? 它把"结构性效应"(这位用户慷慨、这部片子普世受捧)和真正的交互信号剥离开来,让潜因子专心建模品味。一接入,RMSE 通常就下降 10–20%。
初始化。 因子用 $\mathcal{N}(0, 0.1)$,偏置用 0(数据会在第一轮把它们推到合理位置)。千万别用全零因子——梯度会卡住。
预测越界怎么办? 直接 clip 到 $[r_{\min}, r_{\max}]$。Sigmoid 缩放在数学上更优雅,但在离线评估里很少能赢过 clip。
怎么做实时推荐? 把每个用户的向量缓存住;对头部用户预算 Top-N;只在长尾用户上实时计算。需要持续学习时,用 SGD 在流上更新,并对嵌入表做版本管理。
怎么防过拟合? L2 正则取 $\lambda \in [0.01, 0.1]$,验证集早停,再不行就先把 $k$ 砍小,最后才考虑更花哨的招。
总结
协同过滤是现代推荐系统的地基,整条线索其实很短:
- 邻域方法把"和你像的人也喜欢……“翻译成相似度加权平均。Item-CF 是工业默认。
- 矩阵分解把稀疏评分矩阵压成低秩用户/物品嵌入,用内积预测评分。
- 偏置项剥离结构性噪音;SVD++ 把隐式反馈也吸进来。
- ALS 与 SGD 是两条优化路线——闭式解 vs. 流式更新,并行 vs. 在线。
- BPR 把损失从 RMSE 换成排序;FM 把 MF 推广到任意稀疏特征。
这些思想会一直跟着你走。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 篇:工业实践(即将发布)