第 24 章 / 共 57 章 · 约 5 分钟
ML 案例 1:推荐排序(视频首页)
先花 10 分钟自己设计:需求、核心实体、API 和高层架构,各写几行。写完或者跳过之后,下面的讲解才会展开。
先写你的思路
只存在这台设备上;登录后会存进你的账号。
推荐是 ML 系统设计里最常见的题。“设计视频 App 的首页推荐”听起来是一个模型,实际上是一条多阶段漏斗:在 200 毫秒内,从几千万个视频里挑出用户最可能看完、最不会反感的二十几个。面试官主要看三点:漏斗每一层为什么这样设计,优化目标和标签怎么定,以及怎么避免系统只会推它推过的东西。
1. 需求澄清
功能需求
- 用户每次打开首页,返回一屏个性化视频,可以下拉刷新
- 新用户、新视频也要能被推荐(冷启动)
- 过滤已看过、已屏蔽和违规的内容
非功能需求
- 规模:1 亿日活,候选视频约 5000 万;平均约 1.2 万 QPS,峰值约 3 万
- 延迟:推荐接口 p99 < 200 ms
- 新鲜度:新上传的视频几小时内就有机会被推荐
- 多样性:同一个创作者、同一个话题不要刷屏
业务目标:提升用户的长期满意度和活跃(人均观看时长、次日回访),而不是单纯的点击率。
2. 问题形式化
没有哪个重模型能在 200 ms 内给 5000 万个视频打分,所以拆成三层:
查看图表源码
flowchart LR
accTitle: 推荐漏斗:召回、精排、重排
pool[("候选池:约 5000 万")] --> recall["多路召回:约 1000"]
recall --> rank["精排模型:打分后取前 100"]
rank --> rerank["重排:多样性、新鲜度、业务规则"]
rerank --> feed["首页一屏:约 20"]
精排模型做多任务预测:点击概率 P(click)、点击后的预期观看时长、点赞概率、“不感兴趣”概率。最终的排序分数是加权组合,例如:
score = P(click) × E[观看时长 | click] + w1 × P(like) − w2 × P(不感兴趣)
权重不是拍脑袋定的,而是通过线上实验调出来,让人均观看时长和留存最好。只优化点击率,会把标题党推上来。
3. 数据与特征
样本来自曝光日志:每次展示记录用户、视频、位置、时间,以及之后的点击、观看时长、点赞、分享、跳过和“不感兴趣”。
标签:点击后观看超过 30 秒(或完播率超过一定比例)记为正样本;曝光后没有点击记为负样本。观看时长作为回归目标单独训练。
特征:
| 类别 | 例子 |
|---|---|
| 用户 | 观看历史的 embedding、偏好话题、语言、活跃度 |
| 视频 | 话题、创作者、时长、上传时间、近 1 小时 / 1 天的点击率和完播率 |
| 上下文 | 时间、设备、网络类型 |
| 交叉 | 用户对这个创作者 / 话题的历史点击率,与本次会话里刚看过的视频的相似度 |
实时特征:本次会话最近看过的几个视频,通过 Kafka 流式写入在线特征库,几秒内就能用上。
两个偏差要主动处理:
- 位置偏差:排在第一位的视频天然点击多。训练时把位置作为特征(或单独的浅层网络)输入,线上打分时统一设成同一个位置,让模型学到与位置无关的兴趣
- 选择偏差与反馈闭环:模型只能从自己展示过的视频里得到反馈,没推过的视频永远没有数据。留出一小部分流量(比如 1%~5%)做探索,展示模型不那么确定的内容
所有特征都按曝光那一刻的值记录,避免用到“未来”的统计量。
4. 模型
召回(多路并行):
- 双塔模型:用户塔和视频塔各输出一个向量,用内积表示匹配程度。视频向量离线算好,放进 ANN 索引(如 HNSW);请求时只算一次用户向量,几毫秒内找出最近的几百个。训练用 batch 内负样本,并对热门视频做采样校正,否则模型只会推热门
- 协同过滤:看过 A 的人也看了 B
- 订阅:关注的创作者发布的新视频
- 热门与新视频:保证新鲜度,也给冷启动内容机会
精排:先用 GBDT 或逻辑回归做基线;主模型是深度多任务网络,ID 类特征做 embedding,共享底层、每个任务一个输出头(如 MMoE)。
重排:去掉已看过和违规的内容;按创作者、话题打散,避免连续出现同类;给新视频适当加权。
冷启动:
- 新用户:按地区和语言的热门内容,加上注册时选的兴趣,前几次会话加大探索比例
- 新视频:用标题、封面、音频的内容 embedding 映射进双塔的视频向量空间,再分配少量探索流量收集真实反馈
5. 训练与评估
离线(按时间切分):
- 召回:Recall@k,用户后来真正看了的视频有没有出现在召回结果里
- 精排:每个任务的 AUC、观看时长的误差;在历史会话上算 NDCG@k
- 校准:预测点击率与实际点击率之比接近 1,加权组合时各项才有可比性
线上 A/B:
- 主指标:人均观看时长、次日 / 7 日回访
- 护栏指标:接口延迟、“不感兴趣”比例、举报率、创作者覆盖度
- 至少跑一到两周,覆盖周末,避开新奇效应
6. 服务与监控
一次请求:Feed API 并行调用各路召回 → 合并去重 → 批量从在线特征库取用户和视频特征 → 精排模型批量打分 → 重排 → 返回,同时把曝光和当时的特征写入日志,作为下一轮的训练样本。
延迟预算大致是:召回 40 ms、取特征 20 ms、精排 60 ms、重排 10 ms,剩下的留给网络和序列化。
- 视频向量和 ANN 索引每天重建,新视频增量插入
- 模型每天重训,或做增量训练,保证新视频的 embedding 及时更新
- 监控:特征缺失率和分布漂移、预测分数分布、各类用户的点击率和观看时长、召回结果为空的比例
- 兜底:精排超时就按召回分数或热门榜返回,首页不能白屏
7. 回答分级
漏斗与召回
- Bad:一个模型给所有视频打分,或者只有一路召回。
- Good:召回、精排、重排三层,说清每层的候选量和延迟;召回用双塔加 ANN,再配协同过滤和热门。
- Great:讲清双塔训练的负样本和热门校正、ANN 索引怎么更新、每一路召回各自解决什么问题,以及新视频怎么进入向量空间。
目标与标签
- Bad:只预测点击率。
- Good:多任务预测点击、时长、点赞和负反馈,再加权组合;标签有明确的观看时长门槛。
- Great:说明权重由线上实验确定,解释点击和长期满意度之间的差距;处理位置偏差,所有特征按曝光时刻记录。
评估与反馈闭环
- Bad:只报离线 AUC。
- Good:召回看 Recall@k,精排看 AUC 和 NDCG,线上用观看时长和留存做 A/B,并设置护栏指标。
- Great:指出反馈闭环和选择偏差,留出探索流量;讨论多样性和创作者生态;给出精排超时时的兜底。
8. 不同级别的期望
| 级别 | 期望 |
|---|---|
| Mid(L4) | 在提示下给出召回加精排的结构、合理的标签和特征,知道离线指标和 A/B 测试 |
| Senior(L5) | 主动设计多路召回和多任务精排,处理位置偏差和冷启动,给出延迟预算和线上指标 |
| Staff(L6+) | 从长期满意度出发定目标,处理反馈闭环和探索,讨论创作者生态与多样性、重训节奏、监控和兜底 |