外观
推荐系统
一句话定义:推荐系统(Recommender System)是从用户与物品的历史交互中学习用户的兴趣模式,并在海量候选物品中自动筛选、排序、呈现最可能被用户喜欢的物品的软件系统。它是机器学习在互联网行业落地最广、商业价值最直接的场景之一——Netflix 声称其约 80% 的观看时长来自推荐,YouTube 的推荐系统每年直接影响数十亿用户的内容消费。
推荐问题的本质不是"预测一个数字",而是"在正确的时间,把正确的物品,以正确的顺序,推给正确的人"。这篇实战文章沿着一条从经典到现代的主线展开:先定义问题,再讲透协同过滤与矩阵分解这两代经典方法,然后剖析今天工业界的"召回→排序→重排"三段式架构,最后落到评估、冷启动、探索-利用与一段可运行的代码。读之前建议先浏览什么是机器学习与模型评估与验证建立框架。
一、推荐问题:定义与形式化
1. 用户-物品交互矩阵
推荐系统的一切数据都可以压缩成一张用户-物品交互矩阵(interaction matrix):行是用户,列是物品,单元格是二者之间的交互。这张矩阵有两种形态:
物品1 物品2 物品3 物品4 物品5
用户A 5 ? 4 ? 2
用户B ? 3 ? 5 ?
用户C 4 ? ? 1 3
用户D ? ? 2 ? 4
? = 未观测到的交互(用户没见过,或见过没留下痕迹)这张矩阵有两个决定性的统计特征,也是整个推荐系统技术栈的出发点:
- 稀疏性:可观测的交互只占全部潜在交互的极小比例。MovieLens 10M 数据集的密度仅约 1%;真实电商平台这个比例往往是 10⁻⁴ 甚至更低。我们真正要做的,是根据少量观测值推测海量未观测值——这正是机器学习擅长的事。
- 不确定性:单元格的值只是"该用户对该物品的某种反馈",不是用户内心的真实评分。显式打分可能是 5 星,但用户点开推荐页之后是否真的喜欢,是另一个问题。
2. 显式反馈与隐式反馈
交互数据按反馈的"明示程度"分两类:
| 类型 | 形式 | 示例 | 特点 |
|---|---|---|---|
| 显式反馈 | 用户主动表达的偏好 | 星级评分、点赞、收藏、差评 | 信号强、语义清晰;但获取成本高、数据稀疏、易受评分偏差影响(多数人只给自己喜欢的打分) |
| 隐式反馈 | 行为留下的痕迹 | 点击、浏览时长、播放、购买、转发 | 数量巨大、几乎零成本;但只有正向信号可观测("没点"≠"不喜欢",可能只是没看到),噪声大,且反映的是"可及性"而非纯粹偏好 |
这个区别深刻影响建模方式。对显式反馈可以直接做"评分预测";对隐式反馈,主流做法是把行为二值化(有交互=1,无交互=0,然后加权),或者用观看时长、购买金额这类行为强度作为置信度。2008 年 Hu、Koren 与 Volinsky 提出的 ALS-WR 方法(见参考资料)就是专门为隐式反馈设计的矩阵分解,它把每个"没交互"的样本也纳入训练,只是置信度设得很低——这是隐式反馈建模的标准起点。
3. 评分预测 vs Top-N 排序
推荐任务常被误当成一个回归问题,其实它分两个层次:
任务形态 A:评分预测(rating prediction)
目标:尽可能准确地估计 r̂ᵤᵢ(用户 u 会给物品 i 打几分)
度量:RMSE、MAE
视角:全局误差最小化
任务形态 B:Top-N 排序(ranking / Top-N recommendation)
目标:对每个用户给出一个长度为 N 的、尽可能让用户满意的前 N 列表
度量:Recall@K、NDCG@K、AUC
视角:头部排序质量最大化这两个目标并不等价:一个把 RMSE 压到极低、对每部电影评分都猜得八九不离十的模型,可能把用户最想看的电影排在第 5 位;而一个"只关心头几名"的模型可能评分误差很大但推荐体验极好。工业界的真实产品(商品流、信息流、视频流)几乎全部是 Top-N 排序问题——用户看的是排序后的列表,不是模型输出的分数。这就是为什么本章第五节要单独讲排序类指标。
4. 问题变体
除经典的双人矩阵补全外,推荐系统还有几个常见变体:
- 序列推荐(sequential recommendation):把交互看成时间序列,预测"下一个要看的物品"(会话推荐、Next-Item Recommendation)。
- 上下文感知推荐:把时间、地点、设备、天气纳入特征。
- 多目标推荐:同时优化点击率、转化率、时长、满意度等互相矛盾的指标。
- 社交/关系推荐:利用好友关系等图结构信息。
这些变体绝大多数仍以内核"用户×物品匹配度"为基础,下面两节的经典方法是一切变体的地基。
二、协同过滤:最经典的"人以群分"
1. 核心思想
协同过滤(Collaborative Filtering,简称 CF)是推荐系统最古老也最核心的方法族,1994 年 GroupLens 团队的开创性论文(见参考资料)奠定了其基本框架。它的思想用一句话概括:
利用群体的集体行为来预测个体的偏好——不必理解物品内容,也不必理解用户动机,只需要"许多人和你做过相似的事,那么他们喜欢的很可能也是你喜欢的"。
协同过滤的推理闭环:
用户A 喜欢 {物品1, 物品3, 物品5}
用户B 喜欢 {物品1, 物品3, 物品7} ← 与 A 高度相似
问题:A 会喜欢物品7 吗?
推断:很可能喜欢(B 与 A 相似,且 B 喜欢 7)这个方法之所以叫"协同"过滤,是因为预测依赖于用户之间的协同信号,而不是物品自身的属性。与此相对的**基于内容的推荐(content-based)**用物品属性(电影的类型、演员,文章的标签、词向量)构造特征,只利用用户自己的历史,不需要"别人"的数据。两者的分工和融合是现代推荐系统的常态。
2. 基于用户的协同过滤(UserCF)
算法分三步:
① 相似用户:对目标用户 u,用交互记录计算他与所有其他用户的相似度,取 Top-K 邻居
② 候选生成:收集邻居们交互过、而 u 没交互过的物品
③ 打分排序:按"邻居对该物品的评分 × 邻居与 u 的相似度"加权求和,排序取前 N预测评分公式(用相似度加权):
r̂ᵤᵢ = ( Σ_{v∈N(u)} sim(u,v) · rᵥᵢ ) / ( Σ_{v∈N(u)} |sim(u,v)| )其中 N(u) 是用户 u 的邻居集合,sim(u,v) 是用户相似度。实际工程中更常用的是以均值为中心的加权:先减去用户自己的平均评分再加权,以消除"有人习惯给高分、有人习惯给低分"的评分尺度偏差。
3. 基于物品的协同过滤(ItemCF)
把上面的逻辑对调:先算物品之间有多像(被同一批人消费/评分的物品更像),再为用户推荐与他历史物品相似的物品。2001 年 Sarwar 等人的论文(见参考资料)系统化了这个方法,随后被 Amazon 大规模应用并写成了著名的论文 Amazon.com Recommendations: Item-to-Item Collaborative Filtering。
① 物品相似度:sim(i,j) = 同时交互过 i 和 j 的用户数 / 交互过 i 或 j 的用户数的某种归一化
② 候选生成:用户 u 的历史物品集 H(u),对 H(u) 中每个物品找 Top-K 相似物品,并集去重
③ 打分排序:r̂ᵤᵢ = Σ_{j∈H(u)} sim(i,j) · rᵤⱼ,排序取前 NItemCF 在工程上全面占优,原因是它把"物品相似度"做成了可以离线预计算的静态表(相似度不随用户变化),在线服务时只需要查表聚合,不需要实时做 N 次全量比较。同时它具备很强的可解释性——"因为你看过《星际穿越》,推荐《盗梦空间》"这句话可以直接摆给用户看,这对信任建立至关重要。
4. 相似度度量
无论 UserCF 还是 ItemCF,"相似"都要有一个量化定义。三种常用度量:
| 度量 | 公式(示意) | 特点 |
|---|---|---|
| 余弦相似度 | cos(u,v) = (u·v)/(‖u‖·‖v‖) | 只关心方向、不关心尺度;处理交互向量最常用 |
| 皮尔逊相关系数 | 对每个向量先减均值再算余弦 | 消除评分尺度偏差,显式评分场景更准 |
| Jaccard 系数 | A∩B |
一个重要的工程细节:冷门物品的相似度比热门物品的更有价值。两个物品都被 100 万人看过,相似度虚高但信息量低;两个物品都只有 50 人看过,同时被 40 人看过则强烈说明二者高度相关。为此常用 log 逆频率或"热门物品降权"来修正:相似度乘以 1/log(1+出现次数)。对热门物品打压制裁,是推荐系统中反复出现的主题——它同时服务于准确性、新颖性和公平性。
5. 两种 CF 的工程取舍
| 维度 | UserCF | ItemCF |
|---|---|---|
| 依赖 | 用户-用户相似度(随用户增长而膨胀,重算成本高) | 物品-物品相似度(物品数相对稳定,可离线预计算) |
| 在线成本 | 高:需实时找相似用户 | 低:查预计算相似度表即可 |
| 可解释性 | 弱("和你相似的人看过") | 强("因为你看过 X") |
| 新颖性 | 较好,能跨领域推荐 | 较差,容易陷入"和过去高度同质"的茧房 |
| 冷启动 | 新用户几乎没有邻居 | 新物品没有相似物品 |
两者共同的致命弱点是稀疏性下的失效:矩阵越稀疏,"相似"的邻居/物品越难找齐,预测质量断崖式下跌。矩阵分解正是为此而生。
三、矩阵分解:从隐因子看透偏好
1. 背景:Netflix Prize
2006 年,Netflix 发起 Netflix Prize 竞赛:谁的算法能把评分预测的 RMSE 比 Netflix 自家(Cinematch)提升 10%,就奖励 100 万美元。竞赛公开了 1 亿条真实评分,历时近三年才被贝尔实验室团队(BellKor's Pragmatic Chaos)攻克。这场竞赛有两个历史意义:一是把**矩阵分解(matrix factorization)**从学术冷门推成推荐系统的标准方法;二是确立了"集成多模型"在排行榜竞赛中的统治地位。获奖方案的细节与竞赛本身见参考资料中的《The Netflix Prize》描述论文与 Koren 等人的综述。
2. 隐因子(latent factor)思想
矩阵分解的出发点是对"评分矩阵为什么是这个形状"做一个结构性假设:
用户的偏好和物品的属性,都只是少数 k 个不可直接观测的隐因子的不同组合。
- 一个"隐因子"可以粗俗地理解为一条偏好轴,比如"对科幻的喜爱程度""对文艺片的耐受度""对主流大片的接受度"。
- 每个用户用一个 k 维向量 pᵤ 表示:他在各条轴上的取值。
- 每个物品用一个 k 维向量 qᵢ 表示:它在各条轴上的取值。
- 用户对该物品的喜欢程度 = 两个向量的点积(向量对齐则高,正交则低)。
隐因子(k 维)解读示意:
因子1(科幻) 因子2(文艺) 因子3(主流)
用户A 0.9 0.2 0.6
用户B 0.1 0.8 0.3
《星际穿越》 0.95 0.1 0.9 → A 会打高分,B 会打低分
《海边的曼彻斯特》 0.05 0.85 0.2 → B 会打高分,A 会打低分与聚类(见聚类)的关系:隐因子可以理解为一种"软聚类"——用户和物品都被分配到 k 个隐维度上,但不要求互斥。它们之间的区别在于,聚类只做"物以类聚",而矩阵分解直接以"预测打分"为目标反推出这些维度。
3. 数学形式与损失函数
用 P(m×k 用户因子矩阵)和 Q(n×k 物品因子矩阵)逼近交互矩阵 R:
R ≈ P · Qᵀ , 即 r̂ᵤᵢ = pᵤᵀ qᵢ在观测到的交互子集 Ω 上最小化带 L2 正则的均方误差:
L = Σ_{(u,i)∈Ω} (rᵤᵢ − pᵤᵀ qᵢ)² + λ( Σᵤ ‖pᵤ‖² + Σᵢ ‖qᵢ‖² )
↑ 拟合项:只在有观测的格子上算误差 ↑ 正则项:防止因子向量过大导致过拟合正则化的意义要放在泛化框架下理解:观测格子越稀疏,自由参数(m×k + n×k 个)越多,越容易把已知评分背下来而丢掉对新评分的预测力。λ 是模型评估与验证中正则化与偏差-方差权衡在推荐场景的直接体现。
再引入**偏置(bias)**项之后,模型变成完整形态(这也是 surprise 库中 SVD 的默认模型,即业界俗称的 Funk SVD):
r̂ᵤᵢ = μ + bᵤ + bᵢ + pᵤᵀ qᵢ其中 μ 是全局平均评分,bᵤ 是用户偏置(有些人天生给分高),bᵢ 是物品偏置(《肖申克的救赎》整体被高估)。偏置项极其廉价却极其有效——它吃掉了很大一部分可解释的方差,让因子向量专心刻画"用户×物品"的交互结构而非全局均值。
4. 与经典 SVD 的区别
线性代数中的 SVD(奇异值分解)是精确的数学工具:任意稠密矩阵 R 都能唯一分解为 R = U·Σ·Vᵀ,其中 U、V 是正交矩阵,Σ 是对角阵。推荐领域说的"SVD"只是在思想上借了它的壳,实质上是两回事:
| 维度 | 经典 SVD(线性代数) | 推荐领域的"SVD"(Funk SVD / 潜在因子模型) |
|---|---|---|
| 输入 | 稠密、完整的矩阵 | 极端稀疏、含大量缺失值的交互矩阵 |
| 缺失值 | 不存在 | 是问题的主体,绝不能当作 0(用户没打分≠打 0 分) |
| 目标 | 精确重构整张矩阵 | 只在观测值上最小化预测误差,忽略缺失值 |
| 分解性质 | 唯一、正交、满秩分解 | 非唯一,因子向量只服从损失函数 |
| 求解 | 特征值分解(精确、昂贵) | 梯度下降/ALS(近似、可扩展) |
| 本质 | 数学工具 | 带正则化的统计模型 |
一个必须记住的陷阱:如果把缺失值补零再直接做经典 SVD,等于把"没看过"硬说成"给 0 分",模型会被海量的假 0 淹没,预测严重有偏。所以真正的推荐 SVD 只在观测集 Ω 上优化——这就是"用 SGD 逐个采样观测样本更新"的做法。Simon Funk 在 2006 年正是以这种方式写了个只有几行的 SGD 程序,把 Netflix Prize 排行榜搅得天翻地覆,因此这个模型也被称作 Funk SVD。
5. 求解:SGD 与 ALS
矩阵分解的损失对因子向量可微,梯度下降是自然选择。对单个观测样本 (u,i),误差为 eᵤᵢ = rᵤᵢ − r̂ᵤᵢ,参数更新为:
pᵤ ← pᵤ + γ( eᵤᵢ·qᵢ − λ·pᵤ )
qᵢ ← qᵢ + γ( eᵤᵢ·pᵤ − λ·qᵢ )
bᵤ ← bᵤ + γ( eᵤᵢ − λ·bᵤ )
bᵢ ← bᵢ + γ( eᵤᵢ − λ·bᵢ )(γ 为学习率,λ 为正则系数。)每轮把所有观测样本过一遍称为一个 epoch,几十个 epoch 通常即可收敛。随机梯度下降(SGD)的替代方案是交替最小二乘(ALS):固定 Q 时损失对 P 是凸的二次函数,有闭式解,于是交替求解 P 和 Q。ALS 的优点是可以并行化、且天然适配 Spark 这类分布式框架,工业界(如当年的 Yahoo! 音乐推荐)多用它。
6. SVD++:把隐式反馈喂进去
前面说过隐式反馈信息量巨大,SVD++(Koren 2008,见参考资料)给出了优雅的融合方案:把用户的隐式交互(如浏览过、点击过的物品集合)编码进用户向量:
r̂ᵤᵢ = μ + bᵤ + bᵢ + qᵢᵀ ( pᵤ + |N(u)|^(-1/2) · Σ_{j∈N(u)} yⱼ )其中 N(u) 是用户 u 产生过隐式行为的物品集合,yⱼ 是物品 j 的"隐式因子向量"。括号里的整体可以理解为"融合了隐式信号的用户兴趣向量"。这一项几乎不增加计算成本,却能在 Netflix 数据上显著提升精度——它告诉我们的经验是:用户没有明说、但行为暗示过的信号,值得被模型认真对待。SVD++ 是经典矩阵分解与深度学习双塔之间的关键桥梁:双塔模型本质上就是把"SVD++ 的显式项和隐式项"推广成了任意复杂度的神经网络函数。
四、现代工业架构:召回→排序→重排
1. 为什么需要三段式
经典 CF 和矩阵分解能对付"给几千部电影排序";但真实平台面对的是百万到十亿级的候选物品(淘宝的商品、YouTube 的视频、TikTok 的内容)。任何模型如果对全部候选逐一打分,在线延迟会爆炸。工业界因此把推荐拆成漏斗式的三段:
全量物品(百万~十亿级)
│
┌────────────────────────────▼────────────────────────────┐
│ ① 召回(Recall):双塔 + ANN 向量检索 │ 全量 → ~1000 条候选
│ 目标:快。从海量候选中粗筛出"可能有兴趣"的子集 │
└────────────────────────────┬────────────────────────────┘
▼
┌────────────────────────────▼────────────────────────────┐
│ ② 排序(Ranking):粗排 + 精排 │ ~1000 → ~50 条
│ 目标:准。用更复杂的模型对候选精打细算 │
└────────────────────────────┬────────────────────────────┘
▼
┌────────────────────────────▼────────────────────────────┐
│ ③ 重排(Re-rank):多样性、打散、业务约束、探索注入 │ ~50 → Top-N 展示
│ 目标:体验。让最终列表更合理、更可解释 │
└────────────────────────────┬────────────────────────────┘
▼
最终 Top-N 展示每一层牺牲精度换速度、或牺牲速度换精度,各层模型复杂度递增而候选规模递减。向量化检索是支撑这个漏斗的第一块基石。
2. 召回(一):双塔模型
2016 年 YouTube 发表的 Deep Neural Networks for YouTube Recommendations(arXiv:1606.07792,见参考资料)把推荐建模明确表述为"把用户和视频各编码成一个向量,用内积做匹配"——这就是**双塔模型(two-tower)**的标准蓝图:
用户侧特征: 物品侧特征:
历史观看序列 (embedding) 物品 id (embedding)
人口属性 (年龄/性别/地域) 物品类别 / 标签 / 时长 / 上线时间
搜索词 / 上下文(时间设备) 内容特征 (文本/图像 embedding)
│ │
▼ ▼
user tower (MLP) item tower (MLP)
│ │
▼ ▼
u_vec (k 维) v_vec (k 维)
└───────────────┬──────────────┘
▼
匹配度 score = <u_vec, v_vec> (内积 / 余弦)双塔之所以统治工业召回,核心优势是解耦带来的部署形态:
- 物品塔离线预计算:所有物品的 v_vec 在模型更新后一次性算好,写入向量数据库(Faiss、Milvus 等,见参考资料中的 FAISS 论文)。
- 用户塔在线推理:线上只算一次用户向量 u_vec,然后做一个 k 维向量的最近邻搜索。
- 匹配度计算从"十亿次模型前向"降为"一次向量检索",延迟从秒级降到毫秒级。
训练时一个关键细节是负样本的采样。真实数据里只有"曝光且被点击"是正样本,而模型需要在全量物品中区分好坏,所以要用随机负采样补足负样本;直接取"曝光未点击"当负样本会引入选择偏差(能被曝光本身就不是随机的),随机采样又会让热门物品被过度惩罚,实践中往往两者结合(随机负采样为主 + 适量 hard negative)。损失函数常用采样后的 softmax 或二分类交叉熵,详见深度学习基础。
3. 召回(二):近似最近邻检索 ANN
双塔给出向量后,"找最相似的 k 个物品"就是最近邻搜索问题。精确 KNN 在十亿规模下不可行,工业界全部使用近似最近邻(ANN,Approximate Nearest Neighbor),核心思路是用"允许少量误差"换"百倍速度"。主流技术分三类:
| 方法 | 思路 | 代表 |
|---|---|---|
| 哈希类 | 用随机超平面把向量空间切成桶,同桶视为候选 | LSH(局部敏感哈希) |
| 量化类 | 把向量空间量化成码本,用码字近似向量、按查表距离排序 | PQ(乘积量化)、IVF+PQ |
| 图类 | 建一张"向量为节点、相近为边"的图,从入口节点贪心走向查询点 | HNSW(分层小世界图)、NSG |
其中 HNSW(Malkov & Yashunin,2016,见参考资料)是当下最流行的方案:它构建多层图,高层图"跳得远"负责粗定位,低层图"走得细"负责精定位,召回率-速度比极佳,Faiss、Milvus、Elasticsearch 都内置了它。ANN 引入的误差会传导到上层,因此工业上会用"向量检索出 2000 条 → 粗排模型过滤"来兜底。
4. 粗排与精排
召回给出的上千条候选质量粗放,进入排序层精雕细琢。排序层又分两级:
- 粗排(candidate reranking):用一个轻量模型(通常是浅层双塔或简单树模型)对上千条候选快速打分,截断到几百条,为精排省下宝贵的延迟预算。粗排追求"别把好苗子刷掉",要求比召回更准。
- 精排(ranking):对几百条候选逐一预估 CTR(点击率)/ CVR(转化率)/ 观看时长 等目标,把分数精确排序。这是整个漏斗中模型最重的部分。
精排模型是监督学习标准框架的直接应用:特征 + 标签 + 模型。
特征体系(精排是"特征工程"的极致舞台):
├── 用户侧:id、年龄性别、历史点击分布、品类偏好向量
├── 物品侧:id、类目、价格、发布时间、近 7 天点击率
├── 交叉特征:用户偏好品类 × 物品品类、用户年龄 × 物品内容分级
└── 上下文:时间、设备、渠道、展位特征交叉是精排模型的核心竞争力。从 LR 手工交叉,到 FM(因子分解机) 用隐向量自动学二阶交叉,再到 DeepFM / Wide & Deep / DCN 用深度网络学高阶非线性交叉,精排模型沿着"自动特征交叉能力"这条线进化(详见特征工程与深度学习基础)。需要注意的是:模型能学的交叉越多,对特征质量和负采样设计的依赖越大——精排的收益一半来自模型结构,一半来自特征和样本。
5. 重排:让列表像"人排的"
精排给出的分数是单点最优,但用户看的是一个列表。逐点最优的列表往往呈现三大问题,都由重排层解决:
| 问题 | 原因 | 重排手段 |
|---|---|---|
| 同质化 | 相似物品分数接近,占满前几名 | MMR(最大化边际相关性):贪心选取时对"与已选集合过于相似"的候选扣分 |
| 类目失衡 | 用户点得多的一类霸榜 | 打散:相邻 k 个位置不允许同类物品超过阈值 |
| 商业规则 | 运营位、限流、广告插入 | 硬约束插入,通常是规则引擎而非模型 |
| 探索缺失 | 永远推荐最可能的,用户失去新鲜感 | 按概率注入探索流量(与第七节衔接) |
重排层还负责多样性-准确性这笔关键交易:列表多样性提升时,点击率往往先微降、但长期留存和满意度上升——因为用户"被猜透"的体验反而会厌倦。现代重排越来越多地用 DPP(行列式点过程) 这类"集合级"优化器替代启发式,从"逐点打分"走向"序列决策",这部分已经和强化学习产生交集。
五、评估:怎么知道推荐好不好
推荐系统的评估是一个完整的方法论问题(建议先读模型评估与验证),它的特殊性在于:目标不是预测准某个数字,而是用户满意;而用户满意无法被单个公式完全刻画。评估分两层:离线用历史数据算指标,在线用流量实验验证。
1. 离线评估设置
离线评估首先回答"在什么数据上、以什么方式评估"。标准做法:
① 切分:把交互矩阵按用户随机切分 train/test(80/20 或 5 折交叉)
注意:按用户切分而非按行切分,防止同一用户的信息泄漏进训练集
② 训练:模型只在 train 上拟合
③ 预测:对 test 中的 (u, i) 打分
④ 度量:不同任务形态用不同指标(见下)一个工业级细节是时间切分:推荐是强时间相关的任务,用"前 6 个月训练、后 1 个月测试"往往比随机切分更接近真实线上表现(模型服务于未来)。离线评估的完整注意事项(泄漏、偏差、切分陷阱)见评估实战。
2. 排序类指标:Recall@K 与 NDCG@K
Top-N 推荐用排序类指标。设对用户 u,系统给出前 K 个推荐列表 R_u(K),用户真实喜欢的物品集合为 G_u(测试集中有交互的物品):
Recall@K(覆盖率导向,也是很多论文的主指标):
Recall@K = |R_u(K) ∩ G_u| / |G_u|它回答"用户喜欢的物品里,有多少被推荐到了前 K"。
NDCG@K(排序质量导向,来自信息检索的 DCG 家族,2002 年 Järvelin & Kekäläinen 提出,见参考资料):
DCG@K = Σ_{i=1}^{K} ( 2^rel_i − 1 ) / log₂(i + 1) rel_i = 位置 i 物品的相关性(0/1 或分级)
NDCG@K = DCG@K / IDCG@K IDCG = 理想排序下的 DCG(最优排列)NDCG 的聪明之处:位置越靠前,权重越大(log₂(i+1) 分母惩罚靠后的位次),且除以 IDCG 归一化后可在不同用户间平均。它比 Recall@K 更能捕捉"用户要的是前几名,而不是'在列表里' "这一诉求。
3. 点预估类指标:AUC
如果推荐被建模为二分类(点击/未点击),则用 AUC。AUC 的含义是"随机抽一个正样本、一个负样本,模型给正样本打更高分的概率",它不依赖阈值、对类别不平衡鲁棒,是排序类问题最通用的标尺。AUC 也可以直接用于评估 Top-N:把"被推荐到的正样本 vs 未被推荐到的正样本"比较。注意 AUC 衡量的是整体排序质量,对头部位置的敏感性远低于 NDCG,所以头部应用场景要 NDCG 优先。
| 指标 | 问的问题 | 适用 |
|---|---|---|
| RMSE / MAE | 分数猜得多准? | 评分预测任务(研究传统) |
| Recall@K | 喜欢的物品被推荐到前 K 的比例? | Top-N 推荐 |
| NDCG@K | 推荐顺序是否把最相关的放最前? | 信息流/视频流排序 |
| MRR | 第一个命中出现在第几位? | 单目标搜索式推荐 |
| AUC | 正负样本的整体可分性? | CTR 预估的离线代理 |
| 覆盖率 / 新颖度 / 多样性 | 长尾物品是否也有机会? | 生态健康度,需组合使用 |
4. 在线评估:A/B 实验
离线指标与用户真实体验之间存在系统性鸿沟:离线好 0.5% 的 NDCG,线上可能完全无感,甚至因为"过于精准而失去惊喜"导致留存下降。因此上线前必须做 A/B 测试:把流量随机分桶,实验组跑新模型、对照组跑旧模型,用统计检验对比业务指标(点击率、时长、购买、留存、人均推荐贡献)。在线评估的设计要点(样本量计算、多重比较、长期指标)是评估实战的主题,这里只强调一个原则:
离线指标用于筛选,在线实验用于决策。离线排名在前的模型不一定赢,赢的定义永远是线上业务指标。
六、冷启动:新用户与新物品
推荐系统对"没有历史"的对象天然失能,这就是**冷启动(cold start)**问题,分三种:
| 类型 | 场景 | 典型表现 |
|---|---|---|
| 用户冷启动 | 新注册用户 | 没有历史交互,CF/矩阵分解无法算相似度与隐因子 |
| 物品冷启动 | 新上架商品/新视频 | 没有交互,无法被"相似物品"检索到 |
| 系统冷启动 | 全新平台 | 既无用户数据也无物品数据,只有内容或属性 |
缓解策略按"能否拿到更多信号"分几层:
① 借用内容/属性特征(内容推荐与 CF 的融合):
新物品没有交互,但可以用其属性特征(类目、标签、文本 embedding、封面图像特征)
估算一个"内容向量"作为冷启动期的物品向量——双塔模型里物品塔天然支持这种做法,
因为物品向量本来就是由特征生成的。
② 流行度兜底(popularity backfill):
冷启动用户先推热门榜 / 编辑精选,用"大众偏好"作为第一层先验,等攒够交互再个性化。
注意:流行度兜底必须配合探索机制,否则新人永远只看到头部内容。
③ 主动获取信号(warm-up):
注册时引导用户选择兴趣标签、看几张封面图做"隐式评分"(用户行为即反馈),
用这些低成本信号快速初始化用户向量。
④ 探索性流量(exploration bucket):
把新物品注入探索流量池,让"少量用户试水"产生第一批交互(详见第七节)。
⑤ 迁移学习 / 冷启动模型:
用平台级"通用用户向量"或"物品内容 embedding"预训练,冷启动时直接套用
(与[大语言模型](/case-studies/llm)的预训练-微调思想同源)。冷启动还有一个常被忽略的评估问题:离线评测天然低估冷启动模型——测试集里本来就很少包含"新物品的交互",因此要单独构造"只含上线后前 7 天物品/前 3 次交互用户"的测试切片来验证冷启动表现,否则你的"冷启动优化"可能从未被检验过。这是常见陷阱里反复出现的"评估与目标错位"。
七、探索与利用:推荐不是一锤子买卖
1. 为什么需要探索
前面所有模型的假设是"最大化用户对已知物品的偏好"——这对应利用(exploitation):把当下预测最可能被喜欢的物品推给用户。但用户的偏好不是静态的,而且模型永远有不确定性:
- 新物品、新内容需要被"试"才能产生数据;
- 用户口味会漂移(看腻了类型 A,转向类型 B);
- 模型对长尾物品的预测置信度很低,纯利用会让它们永远得不到曝光,形成"马太效应"。
纯利用的推荐系统会在短期点击率上最优,却在长期上萎缩:用户的新鲜感耗尽、长尾供给消失。探索(exploration)就是主动把一部分流量用于"测试未知",用今天的微小损失换明天的数据红利。这个问题在数学上正是强化学习中经典的探索-利用权衡(exploration-exploitation tradeoff),推荐系统本质上是持续在线决策,而非一次性监督学习。
2. 经典策略
| 策略 | 机制 | 特点 |
|---|---|---|
| ε-greedy | 以概率 ε 随机推荐(探索),以 1−ε 推最优(利用) | 最简单;ε 固定时收敛慢,长尾效果一般 |
| UCB(上置信界) | 对候选按"期望收益 + 不确定度红利"打分,不确定越大的物品越有机会 | 理论优雅,适合"物品臂"有限且可枚举的场景(如新闻推荐) |
| Thompson Sampling | 给每个物品维护一个贝叶斯后验分布,每次从后验中采样一次作为该物品的得分 | 实践中效果最好、实现也简单,被多家公司采用 |
一个工程上很重要的现实约束:探索不能在精排层随心所欲。把"随机物品"塞进用户首页前 5 名,体验损失立竿见影。工业界的妥协是:
- 分层探索:召回层加一路"探索通道"(如冷门新物品通道、随机通道),由重排层以很低的比例混入最终列表;
- 探索预算:按流量比例分配(如 2% 探索流量),并把探索对象的反馈实时回灌训练。
3. 在线学习与模型更新
探索产生的新数据必须被模型及时吸收,这引出在线学习(online learning):
离线训练(batch):每天/每小时用全量日志重训一次模型(离线全量+增量)
↓ 产出新一轮参数,作为线上推理的基线
在线学习(online):对实时流式日志做增量参数更新(如 FTRL、在线 SGD)
↓ 捕获秒级、分钟级的兴趣变化(热点事件、临时偏好)
模型服务:参数热更新 + 线上推理主流做法是"离线重训为主、在线微调为辅":模型结构离线定期重训(保障稳定性),实时信号用在线学习快速吸收(保障时效性)。探索与在线学习合在一起,构成了推荐系统持续进化的闭环——这也正是从监督学习走向强化学习的自然延伸,完整框架见强化学习。
八、实战:用 surprise 实现矩阵分解推荐
surprise(Surprise: A Python library for Recommender Systems,2017,见参考资料)是一个专注推荐算法的 Python 库,内置了 SVD、SVD++、KNN 协同过滤等算法和标准数据集。下面用它跑通一个完整流程。
1. 安装与数据
bash
pip install scikit-surprisepython
from surprise import Dataset, Reader, SVD, accuracy
from surprise.model_selection import train_test_split
# 加载内置 MovieLens 100K 数据集(首次运行会自动下载)
data = Dataset.load_builtin("ml-100k")
# 数据格式:user id | item id | rating | timestamp
trainset, testset = train_test_split(data, test_size=0.2, random_state=42)
print(f"训练样本数: {trainset.n_ratings}, 用户数: {trainset.n_users}, 物品数: {trainset.n_items}")2. 训练 SVD 并评估
python
# SVD = μ + bᵤ + bᵢ + pᵤᵀqᵢ,即带偏置与正则的矩阵分解(Funk SVD)
model = SVD(n_factors=20, n_epochs=30, lr_all=0.005, reg_all=0.02)
model.fit(trainset)
predictions = model.test(testset)
print("RMSE:", accuracy.rmse(predictions))
print("MAE :", accuracy.mae(predictions))
# 典型输出:RMSE 约 0.94,MAE 约 0.74(MovieLens 100K 的合理水平)3. 单点预测与 Top-N 推荐
python
# 对 (user 196, item 302) 打分
pred = model.predict(uid="196", iid="302")
print(f"预测评分: {pred.est:.2f} (真实评分存在时也会打印)")
# 手动实现 Top-N:对用户没交互过的物品全部预测并排序
def top_n_recommend(model, trainset, uid, k=10):
# 用户已交互过的物品集合(训练集中)
inner_uid = trainset.to_inner_uid(uid)
seen = {iid for (iid, _) in trainset.ur[inner_uid]}
# 对所有物品预测,排除已交互的,取前 k
scored = []
for inner_iid in trainset.all_items():
if inner_iid in seen:
continue
raw_iid = trainset.to_raw_iid(inner_iid)
est = model.predict(uid=uid, iid=raw_iid, verbose=False).est
scored.append((raw_iid, est))
scored.sort(key=lambda x: -x[1])
return [iid for iid, _ in scored[:k]]
print("Top-10 推荐:", top_n_recommend(model, trainset, uid="196"))surprise 还内置了 ItemCF(KNNBasic)、SVD++(SVDpp)等,可以直接横向对比,也可以用它算 NDCG 等排序指标。注意 surprise 面向研究教学,工业级实现在数据规模和分布式上需要工程化改造,但算法思想与这里的代码一一对应。
4. 手写一个 30 行的矩阵分解(理解本质)
抛开库封装,矩阵分解的 SGD 求解就是下面这段代码的全部:
python
import numpy as np
def funk_svd(R, k=10, lr=0.01, reg=0.02, epochs=40):
"""R: (m, n) 评分矩阵,0 表示缺失(不要当真实评分用)"""
m, n = R.shape
mu = R[R > 0].mean()
P = np.random.randn(m, k) * 0.1 # 用户隐因子
Q = np.random.randn(n, k) * 0.1 # 物品隐因子
bu = np.zeros(m) # 用户偏置
bi = np.zeros(n) # 物品偏置
for _ in range(epochs):
for u, i in zip(*np.where(R > 0)): # 只在观测值上更新
err = R[u, i] - (mu + bu[u] + bi[i] + P[u] @ Q[i])
P[u] += lr * (err * Q[i] - reg * P[u]) # 梯度下降 + L2 正则
Q[i] += lr * (err * P[u] - reg * Q[i])
bu[u] += lr * (err - reg * bu[u])
bi[i] += lr * (err - reg * bi[i])
return P, Q, bu, bi, mu
# 一个 4 用户 × 5 物品的微型评分矩阵
R = np.array([
[5, 0, 4, 0, 2],
[0, 3, 0, 5, 0],
[4, 0, 0, 1, 3],
[0, 0, 2, 0, 4],
])
P, Q, bu, bi, mu = funk_svd(R, k=3)
user0_pred = P[0] @ Q.T + mu + bu[0] + bi
print("用户 0 对全部物品的预测评分:", np.round(user0_pred, 2))这段代码中"只在 R > 0 的位置更新"这一行,就是矩阵分解区别于经典 SVD 的全部秘密。动手调一调 k、lr、reg,观察 RMSE 与因子向量的变化,比读十遍理论更有用——也别忘了用评估实战的方法严谨地评测你的改动。
九、权衡与取舍
推荐系统是"多目标下求平衡"的工程,没有免费午餐,几组最常见的张力:
- 准确率 vs 多样性/新颖性:优化 NDCG 的模型倾向于推荐"同类爆款",形成过滤气泡与信息茧房;牺牲一点头部准确率换来列表多样性,长期留存往往更高。用 MMR/DPP 做系统化的平衡,而不是事后拍脑袋。
- 离线指标 vs 在线业务:离线 NDCG 提升 ≠ 在线点击率提升。离线评估在筛选候选模型、在线 A/B 在做最终决策——这条原则值得重复一百遍。
- 模型复杂度 vs 服务延迟/成本:双塔快而糙、精排准而慢,漏斗分层就是在精度和延迟之间做预算分配。盲目加宽精排模型会让延迟预算失控。
- 个性化 vs 流行度兜底:纯个性化冷启动失能、长尾能力弱;纯流行度毫无个性。正确的做法是按用户数据量自适应:数据少的用户多靠流行度,数据多的用户多靠个性化。
- 利用 vs 探索:短期指标与长期健康的矛盾,见第七节。任何"纯最大化"的优化器都需要显式的探索预算。
- 可解释 vs 表达力:ItemCF 能说出"因为你看过 X",深度双塔则是个黑箱;在需要向用户解释"为什么推荐这个"的场景(新闻、金融),解释性本身是产品价值。
- 隐私 vs 个性化:越个性化的模型越需要细粒度的用户数据,在隐私合规的压力下,联邦学习、差分隐私、本地化建模正在成为新约束。
- 简单模型 + 好特征 vs 复杂模型:Kaggle 与工业界的经验一致——先做线性/浅层基线,确认特征质量,再上深度模型。特征是上限,模型只是逼近它。
十、延伸阅读
- 打基础:什么是机器学习、监督学习、模型评估与验证、特征工程
- 进阶:深度学习基础、强化学习
- 相邻案例:聚类(隐因子与软聚类的关系)、大语言模型(预训练-微调与冷启动的类比)
- 工程与避坑:评估实战、常见陷阱
- 术语速查:术语表
参考资料
- Bennett, Lanning. The Netflix Prize (KDD Cup 2007) —— Netflix Prize 竞赛的官方描述
- Koren, Bell, Volinsky. Matrix Factorization Techniques for Recommender Systems (IEEE Computer 2009) —— 矩阵分解方法的标准综述
- Koren. Factorization Meets the Neighborhood: a Multifaceted Collaborative Filtering Model (KDD 2008) —— SVD++ 原始论文
- Covington, Adams, Sargin. Deep Neural Networks for YouTube Recommendations (RecSys 2016, arXiv:1606.07792) —— 双塔召回与排序的经典工业论文
- Resnick, Iacovou, Suchak, Bergstrom, Riedl. GroupLens: An Open Architecture for Collaborative Filtering of Netnews (CSCW 1994) —— 协同过滤的开山之作
- Sarwar, Karypis, Konstan, Riedl. Item-based Collaborative Filtering Recommendation Algorithms (WWW 2001) —— ItemCF 的奠基论文
- Linden, Smith, York. Amazon.com Recommendations: Item-to-Item Collaborative Filtering (IEEE Internet Computing 2003) —— Amazon 的 Item-to-Item 系统
- Hu, Koren, Volinsky. Collaborative Filtering for Implicit Feedback Datasets (ICDM 2008) —— 隐式反馈的 ALS-WR 方法
- Herlocker, Konstan, Terveen, Riedl. Evaluating Collaborative Filtering Recommender Systems (ACM TOIS 2004) —— 推荐系统评估方法论综述
- Järvelin, Kekäläinen. Cumulated Gain-Based Evaluation of IR Techniques (ACM TOIS 2002) —— DCG/NDCG 原始论文
- Malkov, Yashunin. Efficient and Robust Approximate Nearest Neighbor Search Using Hierarchical Navigable Small World Graphs (IEEE TPAMI 2018, arXiv:1603.09320) —— HNSW 算法
- Johnson, Douze, Jégou. Billion-Scale Similarity Search with GPUs (IEEE Trans. Big Data 2017, arXiv:1702.08734) —— FAISS 库论文
- Harper, Konstan. The MovieLens Datasets: History and Context (ACM TIIS 2015) —— MovieLens 数据集文档
- Hug. Surprise: A Python library for Recommender Systems (Journal of Open Source Software 2020) —— 实战所用 surprise 库的论文