专业书籍精读 · ML System Design Interview · 第 8 章
Machine Learning System Design Interview · Ch 8 · Ali Aminian & Alex Xu · 2023
你往下刷社交软件,每隔十几条内容会夹进来一条广告。这一章讲的就是:那一条是怎么被挑中的,为什么是这条、为什么是给你。
你大概以为系统在做的事是「挑一条你最可能感兴趣的」。顺序确实要紧,但这里还有一层别的推荐场景没有的东西:钱按这个判断分。广告主按点击付费,平台要在「谁出价高」和「谁真会被点」之间算账。所以系统不能只说「这条比那条更可能被点」,它得说出一个准数。
天气预报是个好参照:如果你只想知道明天比后天更容易下雨,先后顺序就够了;可要是有人按降雨概率收保费,那「70%」就必须真的十次里下七次。广告就是后一种。
系统手里的线索,绝大多数是一个个「标签」:你在哪座城、用什么手机、这条广告是谁投的、什么时段刷到的。单看每一条都很弱——「在上海」预测不了任何事。
真正有力的线索是组合:买过香蕉、又买过菜谱的人,更可能想要一台搅拌机。可一个把每条线索单独打个分再加起来的系统,永远看不见这种「合起来才成立」的信号。而组合的数量又大得离谱,几百条线索两两配对就是几万种,靠人一对对去挑,挑不完也挑不准。
办法是给每个标签发一张「名片」——一小串数字,机器自己学出来的。两张名片一碰,就得出一个「这两样凑在一起有多对味」的分。机器把所有两两组合都过一遍,哪些组合真管用由数据说了算,不用人事先指定。这是这一章的第一件事。
第二件事是补课的频率。广告池不像电影库——新广告每小时都在上,热点每天都在换。上周训好的判官,这周看到的东西有一半是它没见过的。所以它不是每周考一次试,而是边上班边补课:拿刚刚过去这段时间的曝光和点击,持续把自己往新情况上调。
第三件事,就是前面说的「说准数」。系统会拿历史核对:它说 2% 的那一批广告,实际点击率是不是真有 2%。不是就把整条刻度往回拧。这一步叫校准,不做的话,排序看着还挺对,账全是错的。
值得记住的是力气分在哪三处:怎么让机器自己找组合、多久补一次课、数字准不准。这一章八成的内容都落在这三件事上,模型本身反倒不是主角。
边上班边补课的判官会慢慢忘掉旧知识,而且一个学坏了的版本,几小时内就能铺到所有人面前——迭代快和爆炸半径大,是同一件事的两面。
广告点击预测和别的推荐最大的不同,是它的输出要拿去分钱。所以它既要把顺序排对,又要把数字说准,还要跟得上一个每小时都在变的市场——三件事缺一件,系统就开始默默地亏钱。
想进到具体机制、记号和示意图? → 切到精读版
这一章看着是「再做一道排序题」,内核却是别的推荐场景都没有的一条约束:模型输出的那个数要拿去计费。广告按点击付费,排序依据是出价乘以预测点击率,于是分数的绝对值和它的相对顺序一样重要——「概率校准(calibration)」就此从锦上添花变成硬需求。另外两个约束同样由广告场景逼出:特征几乎全是高基数类别标签,信号藏在特征交叉(feature crossing)里;广告池以小时为单位漂移,模型必须持续学习(continual learning)。
bid × pCTR——广告是按这个值排序的,不是按 pCTR。本章是《ML 系统设计面试》Part II 十道真题的第 7 道。上承 Ch7 活动推荐——那里同样是 pointwise 打分,但重心在手造特征、模型停在 GBDT;这里规模大一个量级,而且分数要拿去计费,于是模型往「自动学交叉」走、评估往「校准」走。下启 Ch9 相似房源,那里换成用行为序列学嵌入。现实对应 Meta Ads、Google Ads、TikTok Ads。
书设定的场景很直接:社交平台上,用户每次刷新都要决定给他看哪条广告,目标是最大化点击(进而是收入)。四个约束把这道题和前面所有推荐题分开。
其一,分数要计费。广告位不是按「谁的 pCTR 高」给的,是按 bid × pCTR——出价乘以预测点击率,即这次曝光的期望收入。所以如果模型把所有 pCTR 统一乘以 1.5,排序一点没变、AUC 一点没掉,但高出价低点击率的广告和低出价高点击率的广告之间,相对位置全乱了。
其二,特征几乎全是稀疏类别。用户 ID、广告 ID、设备型号、地域、时段——独热之后维度轻松到 10⁸ 量级,而每条样本上非零的位置只有几十个。
其三,有用的信号是乘性的。「在上海」和「晚上 10 点」各自都预测不了什么,「在上海 × 晚上 10 点 × 外卖类广告」才有信号。线性模型按定义看不见这种交互。
其四,分布以小时计漂移。新 campaign 上线、预算耗尽下线、热点事件——昨天训好的模型,今天面对的候选里有相当一部分它从没见过。
不解决会怎样?校准错 → 计费与预算分配跟着错;不学交叉 → 只会背「这个广告主普遍点击率高」这种粗粒度规律;不持续学习 → 新广告永远排不上去,广告主最后走人。
本章的建模选择是逐条打分的二分类:给定(用户、广告、上下文),预测 P(click)。标签现成——日志里点了是 1,曝光没点是 0;损失用 log loss。
为什么不用 pairwise / listwise 这类更「排序原生」的方法?因为它们优化的是相对顺序,输出的分不是概率,而广告要的恰恰是那个能乘进 bid × pCTR 的概率值。上一章也选 pointwise,但那里是图省事,这里是别无选择。
书把特征分成三组。广告侧:广告 ID、广告主与 campaign ID、类目、素材内容、历史曝光与点击数。用户侧:人口属性、上下文(设备、时间、位置)与历史互动。用户 × 广告:交互统计,如这个用户在这个类目上的历史点击率。共同麻烦是基数——广告 ID 可能上亿个,直接独热是灾难。
一个只会给每个特征配权重再求和的模型(逻辑回归就是),学不到「买香蕉 且 买菜谱」这种联合信号——它只能分别说「买香蕉加 0.1 分、买菜谱加 0.2 分」。要让它看见,得人工把两个特征拼成第三个喂进去:n 个特征有 n(n-1)/2 对,哪些有用只能靠试。
更糟的是稀疏:「上海 × 周三 23 点 × 某广告主」在训练集里可能只出现过三次,人工交叉出的这一维几乎学不到东西。FM 解决的正是这一点:它不给每个组合单学一个权重,而是给每个特征学一个嵌入向量,交叉权重=两个向量的内积。于是即便这两者从没共现过,它们的向量各自都在别处被训练过,内积依然有意义——参数共享把稀疏化解掉了。
本章按历史脉络把模型排开,每一级都在补上一级的洞:
L 层到 L+1 阶),显式且参数量小;深度网络学任意形状的隐式交互。书的落点是 DCN / DeepFM 这一档。代价是训练与服务都更贵,嵌入表能大到单机内存装不下。
广告的数据分布不是缓慢漂移,是天天换血。所以训练不是「每周跑一次」,而是拿最近的日志在现有模型上持续微调,节奏通常按小时计。
三件必须配套的东西:①在线评估门槛——新版本先在小流量上跑,指标不达标就不 promote,因为高频自动更新意味着坏模型也会自动铺开;②防遗忘——只喂最近一小时的数据会忘掉长期规律,常见做法是把新数据和一份历史样本混着训;③特征一致——训练与服务必须用同一份特征计算逻辑,否则就是训练与服务偏斜(training-serving skew),这类 bug 离线看不出来。
校准的定义很朴素:模型说 2% 的那一批曝光,实际点击率也该接近 2%。它和排序是两个独立性质——把所有预测乘以 0.5,排序不变、AUC 不变,校准却全毁了。
广告场景里有个几乎必然破坏校准的动作:负采样。CTR 常在百分之几的量级,负样本压倒性多,训练时往往按比例 w 抽样负例——这会把输出系统性抬高,必须按 w 换算回真实概率。所以书把校准当成服务链路里一个独立环节:模型出原始分 → 校准层 → 才是能乘进 bid 的 pCTR。
评估上的结论因此是:AUC 不够用——它对任何单调变换都不敏感,校准糟糕的模型照样能有漂亮的 AUC。要看 log loss,以及更直接的「预测总点击数 ÷ 实际总点击数」是否接近 1。
bid × pCTR 分预算时会持续把钱押错地方。表 1 · CTR 模型选型:这一列「什么时候选它」才是面试想听的
| 方案 | 能学到的交叉 | 高基数稀疏 | 增量更新 | 什么时候选它 |
|---|---|---|---|---|
| 逻辑回归 | 只有人工拼进去的 | 配哈希后尚可 | 很容易 | 起步版本、要强可解释性;或作混合模型的最后一级 |
| GBDT | 自动,树深即阶数 | 弱,ID 类吃力 | 难 | 稠密数值特征为主、数据量中等、要快速拿到强基线 |
| GBDT + LR | 树找交叉,LR 收尾 | 中等 | LR 那级可以 | 要自动交叉又要在线更新,但还没有深度学习基建 |
| FM / FFM | 全部二阶,稀疏下也能学 | 强 | 可以 | 特征极稀疏、组合从没共现过、训练要便宜 |
| DCN | 显式到 L+1 阶 + 隐式 | 强 | 可以 | 本章落点:大规模、有 GPU 与嵌入表基建 |
| DeepFM | FM 二阶 + MLP 隐式,共享嵌入 | 强 | 可以 | 同上;更省人工特征工程,高阶显式交叉稍弱 |
表 2 · 训练节奏:新鲜度、成本与爆炸半径的三角
| 节奏 | 新鲜度 | 成本 | 风险 | 适合 |
|---|---|---|---|---|
| 天级全量重训 | 差:新广告等一天 | 高但可预测 | 低,有时间验证 | 广告池变化慢、流量小的平台 |
| 小时级微调 | 好 | 中等 | 中:需要自动化的评估门槛 | 本章落点,主流社交广告平台 |
| 近实时在线学习 | 最好:分钟级 | 低,每条样本只过一次 | 高:一个脏数据窗口立刻带偏线上 | 反作弊、突发流量这类要秒级响应的子问题 |
另一组常被问到的取舍是高基数 ID 怎么装下:嵌入表达最好但长尾学不动、内存吃紧;特征哈希把内存钉死在常数,代价是碰撞把两个无关 ID 混成一个;频次截断最省,但等于放弃长尾。实践中的答案是三者叠用:高频学嵌入、中频走哈希、低频进 OOV 桶。
这套形状远不止广告:凡是预测概率、且这个概率要被下游当数字用的场景都是它——信贷风控的违约概率进定价公式、保险的出险概率进保费、外卖的准时率进承诺时间。反过来,纯内容推荐只要顺序对就行,校准根本不是问题。
面试高频追问三处:为什么 CTR 预估必须校准而普通推荐不用(答「输出要进 bid × pCTR 的乘法」,别背定义)、负采样之后概率怎么修回去、持续学习怎么防止坏模型自动铺开。判断标准也只有一条:下游是只比较这个分数,还是拿它做算术。
--max-ind-range=40000000。facebookresearch/dlrm · README ↗① 这一章的输出要拿去分钱:广告按 bid × pCTR 排序与计费,分数的绝对值和相对顺序一样重要——这是它和其它推荐题的分水岭。
② 建模是 pointwise 二分类,预测 P(click)、用 log loss 训。不选 pairwise / listwise,是因为那些方法的输出不是概率。
③ 特征三类:广告侧、用户侧、用户 × 广告交互。共同麻烦是高基数,对策是嵌入 / 特征哈希 / 频次截断叠用。
④ 有用的信号是乘性的:单个特征弱、组合才强。人工交叉会组合爆炸,且稀有组合学不到东西。
⑤ 模型阶梯 LR → GBDT → GBDT+LR → FM → DCN / DeepFM。FM 的关键贡献是用嵌入内积共享参数,让从未共现的组合也能学。
⑥ 必须持续学习:广告池以小时计换血。配套三件套是在线评估门槛、防遗忘的混合采样、训练与服务的特征一致。
⑦ 校准独立于排序。负采样会系统性抬高预测,必须按采样率换算回去,再用 Platt scaling 或保序回归收尾。只看 AUC 是本章最大的坑。
⑧ 判断一个场景要不要校准,只问一句:下游是只比较这个分数,还是拿它做算术。