From Gradient-Boosted Trees to Deep Recommenders:一次生产客服推荐系统迁移的实战复盘¶
研究动机与背景¶
商品目录变化的速度超过了模型能适应的速度¶
Intuit 这篇论文写的不是一个新方法,而是一次真实发生在生产环境里的模型迁移:把一套线上运行的客服会话推荐系统,从"按商品做多分类的梯度提升树(CatBoost)"换成"pairwise 二分类的深度推荐器"。
迁移的起点是商品目录形态的变化。服务型业务正在从"独立定价的 SKU"转向"服务时刻动态组装的捆绑包 + 折扣组合"。论文强调这不只是规模问题,而是推荐问题形状本身的改变:一个 bundle 的身份是组合性的(由哪些 SKU、配哪种折扣构成),而不是原子性的。
被替换的旧系统是一个 per-product 的 GBDT 多分类器,它是为慢速变化的目录设计的。作者列出了三条结构性局限:
- 问题形式化(Problem formulation)。"在商品上做多分类"假定了一个固定的标签集合;新增一个 bundle 意味着新增一个类别,并要重训标签空间本身。改成 pairwise-binary 形式 $(\text{contact}, \text{product}) \rightarrow \text{match}/\text{no-match}$ 就绕开了这一点:一个 bundle 或新 SKU 只是一个新的 item 侧特征向量,而不是一个新的输出单元。
- 多模态信号。旧模型只在表格化、聚合后的特征上打分,无法原生消费实时会话转录文本。深度推荐器可以通过 attention pooling 直接以转录表示为条件——它学的是逐 chunk 的相关性权重,而不是对整段会话做均匀池化。
- 架构与目标函数的灵活性。一旦问题变成 pairwise,就可以在共享的 user/item 嵌入空间上用对比目标;也可以在 two-tower、DeepFM 等架构族之间按延迟/表达力权衡取舍。
迁移的代价¶
论文很坦诚地列出了三项具体成本:
- 调参面积暴涨:超参数比提升树模型多一个数量级;
- 监督信号更稀疏、更不平衡:正例率约 4.5%,154 个类别字段、约 2k 个稠密特征;
- 单请求推理成本更高:每个 (contact, product) 对都要走一遍 embedding 查表 + DNN 前向,而不是一次提升树遍历。
硬约束:推荐质量不能回退¶
这是理解全文最关键的一句话。这次迁移不是绿地研究,而是在一条硬性的非回归约束下做的。这套客服推荐器往下游喂给一个话术增强系统 DynaPitch(同团队另一篇工作),DynaPitch 的质量直接依赖它推荐的质量——这里一旦回退,不是一个孤立的指标掉点,而会传播到下游系统的输出。
更进一步,这个推荐器是多个生态增长与留存计划的骨干,喂给的升级推荐支撑着约 3.6 亿美元量级的营收组合。所以本文写作时对齐的目标是"保住质量",而不只是"提升质量"。因此每一个实验都在同一份固定正例 contact 群体上、用同一套指标定义、对着已部署的 CatBoost 基线报告,让"我们有没有回退"在每一步都有一个无歧义的答案。
问题设置与评估方法论¶
实时推荐设置¶
客服会话是随进程增量打分的,推荐在通话中的某个时刻被服务出去。生产系统把转录文本切成 chunk_num 索引的、每块恰好 20 条 utterance 的片段,每块打分一次。Case-Load 和 Early-Reco 就是第 0 块和第 1 块("通话开始"是对 chunk 0 的直觉描述,不是一个挂钟时间断言)。选 20 条 utterance 是在"上下文足够打分"和"服务时每块调用成本低"之间的折中。
数据与切分¶
全文把一次客服会话称为一个 contact。
| 项 | 设置 |
|---|---|
| 训练/验证池 | 2025-01 ~ 2026-03 的 contact |
| 切分方式 | 在 contactid 级别按 75/25 随机切分(seed 42),保证同一 contact 的所有 chunk 行落在同一分区 |
| 测试集 | 时序 out-of-time holdout:2026-04 ~ 2026-06,训练/验证阶段完全未见 |
| 规模 | 训练 903,829 行 / 验证 302,122 行 / 测试 360,022 行(切块与负采样之后) |
| 商品数 | 9 个(计划扩张到低百量级) |
注意:本文所有数据均为 Intuit 生产内部数据,不含任何公开学术数据集。
两级指标¶
- Chunk / pair 级:扁平的 (contact-chunk, product) 行上的 log-loss、ROC-AUC、PR-AUC(average precision),都在验证集上拟合;
- Contact 级:每个 contact 在通话中一个定义好的时刻产出一个决策。
三个业务指标:CL / ER / GZ¶
表 1:Contact 级业务指标定义
| 指标 | 选取方式 | 回答什么问题 |
|---|---|---|
| CL(Case-Load) | 第一块(chunk_num = 0) |
最早可能给出的推荐的质量 |
| ER(Early-Reco) | 第二块(chunk_num = 1) |
稍晚一点、信号多一些时的质量 |
| GZ(Ground-Zero) | 每个 contact 上权重最大的那一块 | 推荐最可能真正触发的那一刻的质量 |
三者都是在同一份固定正例 contact 群体(当前数据快照下 16,709 个 contact,与评估旧 CatBoost 模型时完全一致)上计算的 micro-F1,因此本文每一个数字都可以和已部署基线直接比较,不存在群体错配。
贯穿全文的基线参考值(Legacy CatBoost):CL 0.4985 / ER 0.5310 / GZ 0.5676。
基线设置:负样本分类学与 CatBoost/DeepFM 基线¶
负样本的分类学¶
负样本的选择是这个问题上最主导的建模杠杆——严重的 pair 级不平衡和不可见的未转化候选,会把模型每个 contact 的 top-1 选择扭向过度代表的商品。论文在两个层次上定义了一套统一词汇:
第一层:显式(explicit)vs 隐式(implicit)——按"我们怎么知道这个候选是负的"划分。
- 显式:客户给了明确回答——他们对这个具体商品说了"不";这是
product_explicit_negative流水线机制。 - 隐式:从未给出过明确拒绝,我们只知道这个 contact 在这个候选上没有转化。
第二层:硬(hard)vs 软(soft)——在显式与隐式各自内部,按负信号的强度划分。
- 显式侧:硬 = 明确、即时的"不";软 = 更弱或更含糊的非接受。
- 隐式侧:硬 = 候选来自一个"该 contact 什么都没转化"的 chunk——在没有明确拒绝的前提下最强的"无信号",实现为
overall_negative与product_random_negative机制;软 = 候选与另一个该 contact 确实转化了的商品同处一个 chunk,因此它的未转化是更弱的证据(可能只是"没被选中"而非"被主动拒绝"),实现为implicit_negative机制。
还有一个独立于上述行级分类学的概念:纯负 contact(pure-negative contact)——整个 contact 全程什么都没转化。这是 contact 的群体级属性,与描述单行的显式/隐式/硬/软是两个维度。
在 contact 级,采样起点大致是 1/3 正 : 2/3 负(保留每个正例 contact,负例 contact 采样到大致匹配,先取 explicit-hard 再取 explicit-soft)——这是一个刻意平衡、而非群体代表性的样本。但训练行是 contact-chunk 而不是 contact:负例 contact 比正例更长(它们不会因转化而结束,会持续产生 chunk),且我们还为每个正例 contact-chunk 额外构造隐式负样本,于是一个按 1/3 正例构建的 contact 级样本,落到 chunk 级只有 ≈4.1% 正例。
Chunk 级 vs contact 级的采样粒度¶
隐式硬负样本(overall_negative 与 product_random_negative)可以在两种粒度上采样:每个 contact 采一次(同一套负样本盖在它的每个 chunk 上),或每个 chunk 独立采。这个结论对 CatBoost 与 DeepFM 同样成立,因为它关心的是训练行怎么构造,而不是谁来消费。在匹配的 $K=2$ 下,按 chunk 独立采样在两个可比切面上都更好(CL 0.437 vs 0.407;ER 0.530 vs 0.525;contact 级那次运行没评 GZ),本文其余部分均采用这一约定。
建立基线¶
范围说明——全文都是 pairwise-binary 形式。本文不与更早的多分类形式做对比。这里研究的两个模型(Legacy CatBoost 与 DeepFM 候选)已经跑在同一套 pairwise-binary 形式上、同一份数据上:每行是一个 (contact-chunk, candidate product) 对,带二值 match/no-match 标签,负样本行按上面的分类学构造。§1.1 里关于多分类的讨论只是背景动机,不是本文报告结果的对比对象。
文本特征。 CatBoost 用单一 TF-IDF 向量器表示 chunk 文本(最多 3500 维、1–3 gram、去停用词、词项文档频率在 10–75% 之间)。DeepFM 的 with-text 配置对同一段文本做 TF-IDF,再用 truncated SVD 压到约 40 个成分,拼进表格稠密向量(1980 → 2020 维),而不是单独一个学习出来的文本塔;更丰富的文本表示留到 §8。
表 2:基线结果(pairwise-binary F1)
| Configuration | CL | ER | GZ |
|---|---|---|---|
| Legacy (CatBoost), with text | 0.4985 | 0.5310 | 0.5676 |
| DeepFM, no text | 0.3426 | 0.4362 | 0.4293 |
| DeepFM, with text | 0.4678 | 0.5283 | 0.5158 |
分析:这张表定下了全文的基调——朴素迁移过去的 DeepFM 在三个指标上全面落后于已部署的 CatBoost;而且"有没有文本"是最大的单一落差来源(CL 0.3426 → 0.4678,+12.5pp)。文本信号从一开始就是这个问题的主线。
DeepFM 架构。 全文使用的 DeepFM 骨干是一个共享 DNN 塔 (2048, 1024, 512, 256),学习率 $3\times10^{-4}$,weight decay $10^{-4}$,10 个 warm-up epoch。表格特征分两条路径喂进 factorization-machine(FM)层:一条是约 1980 个连续特征的稠密投影;一条是分箱后嵌入的稀疏路径(按互信息选 top-100 类别特征、50 个分位数箱、嵌入维度 32)。
CatBoost 基线配置:depth 10、1000 iterations、学习率 0.0406、$\ell_2$ leaf 正则 8.51、scale_pos_weight 1.003,通过 25 次评估的贝叶斯搜索选出。
共享的负采样底座:表 2 的两个基线共享同一套负采样配置——overall_negative_n = 1 与 product_random_negative_k = 2,均在 contact 粒度上,再配一个二维行权重方案:每个负样本行的最终权重 = 一个按负样本类型索引的权重(overall_negative、product_explicit_negative、product_random_negative 各有自己的权重)× 一个按商品索引的权重(9 个商品各有自己的权重,因为有些商品更罕见、需要更多强调)。是两张独立查找表相乘,而不是一行一个扁平权重。这是本文每个模型训练与评估共同的底座。
架构对比:DeepFM vs Two-Tower¶
在叠加任何额外特征工程、attention pooling、对比目标之前,作者先问一个更窄的问题:把文本处理各自保持在本架构的标准机制上,哪个基础架构更强?
- Two-tower:用分离的 user/context 编码器与 item 编码器,顶上做点积或学习出的相似度——很适合 retrieval 式服务,但对显式特征交互的建模较弱。
- DeepFM:在共享特征空间上联合 FM、DNN 与 wide 组件——交互建模更丰富,但在推理时不太适合最近邻式服务。
作者特别澄清:two-tower 的 retrieval 式服务优势是为"大到无法穷举打分的物品目录"设计的,而这不是我们当前面对的约束(今天 9 个商品,扩张后大概率也就低百量级,完全在 DeepFM 穷举打分的射程内)。所以那个优势是潜在的、还没变成承重结构;下面的对比测的是"在我们的数据、我们当前的规模上哪个架构赢",不是"哪个通用更好"。
表 3:DeepFM vs two-tower(都带文本)
| Architecture | CL | ER | GZ | Top-1 |
|---|---|---|---|---|
| DeepFM (with text) | 0.4678 | 0.5283 | 0.5158 | — |
| TT-base: residual (with text) | 0.4575 | 0.5537 | 0.6152 | 0.5822 |
分析:DeepFM 在通话开始及附近(CL)胜出,而 two-tower 在观察到更多会话之后(ER、GZ)更强。作者据此判断:在本文非回归约束下最要紧的那个区间——"最早可能给出的推荐,此时还没有任何转录信号可依"——DeepFM 是更好的基础架构,因此把它选为全文主架构;two-tower 这条线在 §8.2 单独按它自己的方式推进。
负采样:一次彻底的调查¶
结构化负采样¶
除了改变"采多少个随机隐式负样本",作者还测试了一个结构化、基于规则的采样器:通过观测到的客户与商品数据里可解释的模式来选隐式负样本,而不是均匀随机抽——这样采样决策就可以独立于神经模型被检视。在 $K=2$ 与 $K=4$ 两点上与随机采样对比。
表 4:随机 vs 结构化负采样,两个 $K$ 值
| $K$ | Sampling | CL | ER | GZ | Val PR-AUC | Test PR-AUC |
|---|---|---|---|---|---|---|
| 2 | Random | 0.4649 | 0.4924 | 0.4797 | 0.6845 | 0.5669 |
| 2 | Structured | 0.4572 | 0.4987 | 0.4836 | 0.7137 | 0.5527 |
| 4 | Random | 0.4857 | 0.5084 | 0.4982 | 0.5904 | 0.5244 |
| 4 | Structured | 0.4495 | 0.5069 | 0.4900 | 0.6722 | 0.6293 |
分析:结构化采样在若干设置下改善了 pairwise 排序行为,但这些收益没有一致地传导到 contact 级指标。在 $K=4$ 上,test PR-AUC 大涨(0.5244 → 0.6293),CL 却反而下跌(0.4857 → 0.4495);在 $K=2$ 上它改善了 validation PR-AUC,但 ER/GZ 只有微小移动,CL 与 test PR-AUC 没有收益。作者给出的机制解释很值得记:PR-AUC 度量的是正候选与负候选之间的 pairwise 区分度,而 CL/ER/GZ 度量的是"对一个正例 contact,正确的商品有没有真的浮到最上面"——更有针对性的负样本可以锐化 pairwise 分离,却不改善最终的推荐决策。
隐式随机负样本数量 $K$ 的扫描¶
表 5 隔离出"每个正例 contact-chunk 采多少个隐式负样本"这一个变量,固定架构与训练目标,在排序质量与训练集规模之间找平衡点。为了让扫描跑得快,每个配置只在 50% 训练数据子采样上训练。
表 5:隐式随机负样本 $K = 0, \ldots, 7$(50% 训练子采样) 超参:DNN (2048, 1024, 512, 256),dropout 0.50/0.60,LR $3\times10^{-4}$,weight decay $10^{-4}$,10 个 warm-up epoch,batch 512。
| $K$ | CL | ER | GZ | Val PR-AUC | Test PR-AUC |
|---|---|---|---|---|---|
| 0 | 0.4715 | 0.5072 | 0.4901 | 0.7727 | 0.5832 |
| 1 | 0.4706 | 0.5119 | 0.4932 | 0.7250 | 0.5810 |
| 2 | 0.4649 | 0.4924 | 0.4797 | 0.6845 | 0.5669 |
| 3 | 0.4680 | 0.5021 | 0.4906 | 0.6548 | 0.5614 |
| 4 | 0.4857 | 0.5084 | 0.4982 | 0.5904 | 0.5244 |
| 5 | 0.4405 | 0.5100 | 0.4994 | 0.6274 | 0.5832 |
| 6 | 0.4677 | 0.5132 | 0.4978 | 0.5834 | 0.5883 |
| 7 | 0.4629 | 0.5007 | 0.4897 | 0.5762 | 0.5805 |
分析:这次扫描有一个很干净的观察——validation 与 test PR-AUC 之间的间隙随 $K$ 增大单调收窄(从 $K=0$ 的 0.19 收到 $K=6$–$7$ 时的近乎为零),但这并没有转化为我们真正在乎的指标上的可靠改善:CL、ER、GZ 随 $K$ 非单调移动,且不跟随那个正在收窄的 val-test 间隙。据此作者选择 $K \in [2, 4]$ 作为后续全文使用的范围。
这是一个典型的"代理指标改善 ≠ 目标指标改善"的实战案例:泛化间隙收窄看起来很像"过拟合被治好了",但业务指标不买账。
对比学习与 BCE 联合训练¶
动机与目标函数¶
数据里正向转化信号相对稀少,作者希望模型显式地学到它们并把它们与有意义的负样本对比,同时仍然照顾到数量大得多的负样本。因此 BCE 与对比损失联合使用:BCE 抓住整体转化分布,对比学习锐化模型区分正信号与竞争负样本的能力。
对每个包含真实正例商品 $p$ 的 contact-chunk $u$,构造一个对比组:
$$G_u = \{p, n_1, \ldots, n_K\} \tag{1}$$
其中 $u$ 是 contact-chunk,$p$ 是与真实正向转化关联的商品,$n_j$ 是第 $j$ 个隐式负样本商品,$K$ 是从有效非正例商品中采样的隐式负样本数。在最优配置中 $K = 2$ 且隐式负样本随机采样。
minibatch 中所有正、负样本都参与 BCE 目标:
$$\mathcal{L}_{\mathrm{BCE}} = -\frac{1}{N}\sum_{i=1}^{N}\left[y_i \log \hat{y}_i + (1-y_i)\log(1-\hat{y}_i)\right] \tag{2}$$
其中 $N$ 是 minibatch 里的样本总数,$y_i \in \{0,1\}$ 是样本 $i$ 的真实转化标签,$\hat{y}_i$ 是对应的预测概率。正例商品 $y_i = 1$;隐式负样本与来自纯负 contact 的样本 $y_i = 0$。
除 BCE 外,对至少含一个真实正例商品的组计算一个 user–item 对比目标。令 $\mathbf{z}_u$ 表示掩掉商品特有输入特征之后由共享 DNN 产出的 contact-chunk 表示——这个掩码阻止 contact 表示直接观察到当前正在打分的是哪个候选商品(这是让对比目标有意义的关键设计)。令 $\mathbf{z}_p$ 表示商品 $p$ 的表示,由它的商品文本嵌入投影到与 $\mathbf{z}_u$ 同一潜空间得到。
contact-chunk 与候选商品的兼容性用余弦相似度度量:
$$s(u, p) = \cos(\mathbf{z}_u, \mathbf{z}_p) \tag{3}$$
对正例商品 $p$ 及其 $K$ 个隐式负样本 $\{n_1, \ldots, n_K\}$,user–item 对比损失是一个 InfoNCE 式目标:
$$\mathcal{L}_{\mathrm{CL}} = -\log \frac{\exp\!\left(s(u,p)/\tau\right)}{\exp\!\left(s(u,p)/\tau\right) + \sum_{j=1}^{K}\exp\!\left(s(u,n_j)/\tau\right)} \tag{4}$$
其中 $\tau$ 是对比温度,控制相对相似度分布的锐度。实验中固定 $\tau = 1.0$。
纯负 contact 不含正例锚点,因此不贡献 $\mathcal{L}_{\mathrm{CL}}$,但它们仍通过 BCE 参与训练。对比损失权重固定为 1.0,合并训练目标为:
$$\mathcal{L} = \mathcal{L}_{\mathrm{BCE}} + \mathcal{L}_{\mathrm{CL}} \tag{5}$$
批次组成(Batch Composition)¶
训练数据天然大约是 1/3 含正例、2/3 负例。在标准 BCE + 随机分批下,minibatch 期望上就反映这个 1/3–2/3 分布。在 BCE–对比混合设置下,作者额外控制正对比组与纯负单元在 minibatch 中的组成。
令 $r$ 表示批次构造时正对比组的目标比例:一个采样单元以概率 $r$ 是正对比组,以概率 $1-r$ 是纯负单元。作者考察 $r = \frac{1}{3}$(约等于数据的自然组成)与 $r = \frac{1}{2}$(50:50,提升正例组在优化中的代表性)。
这个比例指的是采样单元的组成,而不是正/负 BCE 标签的数量:当 $K=2$ 时,一个正例组贡献 1 正 2 负共 3 个样本,三者都进 BCE,而这个组整体进对比目标;一个纯负单元只进 BCE。
表 6(左):$K=3$、仅 BCE、无对比项下的组平衡分批
| Config. | CL | ER | GZ |
|---|---|---|---|
| Random ($r = 1/3$) | 0.4680 | 0.5021 | 0.4906 |
| 50:50 ($r = 1/2$) | 0.4781 | 0.5441 | 0.5286 |
表 6(右):仅对比损失 vs BCE 分批 + 对比损失组合,两个隐式负样本数
| $K$ | Setting | CL | ER | GZ |
|---|---|---|---|---|
| 2 | CL only | 0.4636 | 0.4606 | 0.4635 |
| 2 | BCE+CL | 0.4963 | 0.5433 | 0.5279 |
| 4 | CL only | 0.4661 | 0.5455 | 0.5048 |
| 4 | BCE+CL | 0.4981 | 0.5510 | 0.5354 |
分析:这一组实验的结论非常干净,而且是本文最有实操价值的发现之一。
- 只控制 minibatch 组成——不加对比项、不改架构——就已经改善了所有指标(ER 0.5021 → 0.5441,GZ 0.4906 → 0.5286)。这是一个独立、可加的杠杆,而不是对比目标收益的一部分被错误归因。
- 但它也不是对比目标的替代品:在 50:50 分批之上再加对比项($K=4$)达到 CL 0.4981 / ER 0.5510 / GZ 0.5354,在每个 contact 级指标上都优于只做分批。
- 只用对比损失(CL only)会明显变差,尤其 $K=2$ 时 ER 掉到 0.4606。
最强的 contact 级行为来自把有信息量的负样本、批次组成、对比目标三者结合,而不是任何单独一项。
Double Descent:刻画容量/正则区间¶
动机¶
DeepFM 的带文本基线模型(147.8M 参数)表现出教科书式的插值起始过拟合:训练损失单调下降,validation PR-AUC 在第 5 个 epoch 早早见顶(0.752)然后衰减——正是这个症状让实践者不信任深度模型在稀疏表格数据上的表现、退回树模型。
作者不接受这是天花板,而是问:这会不会只是一条 double-descent 风险曲线的左半支,而它的右半支同时优于 DeepFM 基线和它要替换的 CatBoost? 本节是一个平行的、更详细的研究(作者同时投给另一个 venue)的结果子集。
实验设计¶
四个条件臂(isolating specific mechanisms),与一个 9 点的 DNN 宽度/深度容量阶梯(648K 到数千万参数)做叉乘,架构族(DeepFM)与数据固定:
表 7:Double-descent 条件臂
| Arm | Regularization | Early stop | Label noise |
|---|---|---|---|
clean_prod |
production | on (patience 15) | 0% |
nostop |
production | off | 0% |
lowreg |
none | off | 0% |
noisy |
none | off | 15% |
noisy 臂对应 Nakkiran 等人最锐利的插值峰配置。4 臂 × 9 容量点 = 36 个配置。
结果¶
表 8:Double-descent 容量扫描,蒸馏到与迁移相关的对比
| Configuration | DNN shape (params) | CL | ER | GZ | Test PR-AUC |
|---|---|---|---|---|---|
| Legacy (CatBoost) | — | 0.4985 | 0.5310 | 0.5676 | — |
| DeepFM baseline, 40ep | (2048,1024,512,256), 147.8M | 0.4678 | 0.5283 | 0.5158 | 0.6561 |
| Best size (lowreg), 40ep | (2048,1024,512,256), 17.2M† | 0.4772 | 0.5508 | 0.5244 | 0.6921 |
| Best noise (noisy), 40ep | (2048,1024,512,256), 17.2M† | 0.5003 | 0.5528 | 0.5368 | 0.6365 |
| 200ep check (noisy) | (4096,2048), 37.1M, 200ep | 0.4800 | 0.5544 | 0.5222 | 0.6504 |
† 与 DeepFM 基线的 DNN 塔形状相同,但去掉了那个约 130M 参数的稠密投影层(DeepFM 基线用它路由表格特征)——所以这是一个真正更小的模型,不是同一个模型换个训法;它的总参数量比 DeepFM 基线小 72 倍。
分析:
- 头条发现:完整网格里 36 个配置中几乎每一个都在 test PR-AUC 与 CL F1 上打败了 DeepFM 基线模型,而参数量只是它的一小部分——容量本身并不是 DeepFM 基线排序质量的来源。
- 模型尺寸与正则选择是两个独立的杠杆:固定同样的 17.2M 参数 DNN 形状,只把"低正则"换成"噪声注入",CL 就从 0.4772 移动到 0.5003——这是整个网格中唯一一个打败 CatBoost 自身 CL(0.4985)的配置,并且它的 ER 也清过了 CatBoost(0.5528 vs 0.5310)。
- 诚实报告:这个配置的 GZ 落后 CatBoost(0.5368 vs 0.5676),所以没有任何配置能在三个 contact 指标上同时清过 CatBoost——作者明确写"we report this honestly rather than rounding up"。
- 噪声注入是设计杠杆,不只是诊断工具:标签噪声在小容量下作为隐式正则改善而非损害召回,这是一个可用的旋钮,作者把它连同更小的模型尺寸一起,采纳为下一次生产迭代的设计杠杆。
更长训练时程能否恢复?一个空结果¶
上面的容量扫描在固定 epoch 预算下改变模型大小。作为独立检查,作者问:如果固定容量、单纯训练更久,经典的 epoch-wise double descent 恢复会不会出现?
他们在 (4096, 2048) 形状(37.07M 参数)上把 noisy 臂训了 200 个 epoch——比本文任何其它 run 长一个数量级。
结果:validation PR-AUC 只有一个早期峰(epoch 16,0.7428),随后在整个 200-epoch 预算里单调衰减(epoch 199 降到 0.2902),任何位置都没有出现 dip → rise → dip 的恢复;test PR-AUC 0.6504,CL 0.4800,ER 0.5544,GZ 0.5222。
作者把这作为一个真正的空结果报告,而不是一句用来搪塞的 caveat:在这个容量点上,训练更久本身不是恢复插值起始质量损失的杠杆——本文观察到的恢复(表 8)来自容量与正则选择,而不是训练时长。
以转录文本为条件:跨架构的注意力机制¶
动机¶
会话 chunk 的信息量并不相等,因此固定的、内容无关的池化会随长度增长稀释或丢弃信号。基于注意力的池化通过学习逐 chunk 的相关性权重修正这一点。因为跑一次完整 DeepFM 迭代很贵,作者先在更小更便宜的 two-tower 架构上做轻量探索;一旦确认"在 chunk 序列上做注意力"是最高杠杆的那个变量,就把它搬进 DeepFM。
论文的 Figure 1 用一张图并置了两种注意力机制。原文该图为矢量绘制、无法从 PDF 中抽出位图,此处按原图语义重绘:
graph TD
subgraph A["(a) DeepFM:共享 query 的 attention pooling"]
A1["u₁"] --> AS["scorer → masked softmax"]
A2["u₂"] --> AS
A3["u₃"] --> AS
A4["u₄"] --> AS
AQ["shared learned query"] --> AS
AS --> AP["pooled c_u"]
AP --> AD["DNN tower input<br/>(与表格特征拼接)"]
end
subgraph B["(b) Two-tower:候选条件化的 target attention"]
B1["u₁"] --> BS["scorer"]
B2["u₂"] --> BS
B3["u₃"] --> BS
B4["u₄"] --> BS
BQ["query = [item p embed. ;<br/>target-enc. feats]"] --> BS
BS --> BD["Δ_p(transcript)"]
BD --> BR["对零初始化 base logit<br/>的加性修正"]
end
Figure 1(重绘):两种机制都用一个学习出的、逐 chunk 的相关性权重替代固定池化,作用在同一条 per-utterance 嵌入序列 $(u_1, \ldots, u_4)$ 上。(a) DeepFM 使用一个共享 query,产出单个 pooled 向量,只喂给 DNN 塔;(b) two-tower 的 query 以具体候选商品 $p$ 为条件,因此不同候选会对同一通电话给出不同的注意力,产出的是对 cold-safe base logit 的加性修正。
Two-Tower:残差 base 路径 + target-attention 修正¶
架构:cold-safe 的 base 路径 + 零初始化的修正路径¶
设计约束正是本文硬性非回归要求的来源:模型必须在通话开始、还没有任何转录信号时保持可信,同时在转录信号到来后激进地利用它。作者用结构而不是仅靠损失加权来满足它:
- Base 路径(cold-safe):标准 two-tower 打分器——一个覆盖表格、行为、target-encoded 特征的 user/context 塔,一个覆盖候选商品表示的 item 塔,融合成 base logit。这条路径不接收任何转录序列输入,因此它在
chunk_num = 0时行为也有定义。 - 修正路径(转录感知、零初始化):第二个 head 读会话 chunk 序列,输出一个加性修正:
$$\mathrm{logit} = \mathrm{logit}_{\mathrm{base}} + \Delta(\mathrm{transcript}) \tag{6}$$
修正 head 的最后一层零初始化,因此初始化时模型精确等于它的 cold-safe base——转录路径只能在训练中挣得自己的影响力。后续每一项架构增补都被要求保持这个契约(用一个单元测试验证:初始化时 $\mathrm{logit} \equiv \mathrm{logit}_{\mathrm{base}}$)。这是一个值得直接借鉴的工程范式。
- 修正路径中的 target attention:影响最大的增补——DIN 式 target attention,其中在转录 chunk 上做注意力的 query 是候选商品的学习嵌入与该行 per-product target-encoded 特征的拼接。于是每个候选商品都会注意到同一通电话的不同部分——这正是 pairwise-binary 打分的天然契合点:同一个上下文要为每个候选各打一次分。
- 面向冷启的损失塑形:两个同时施加的杠杆——(i) 冷样本权重再平衡,把
chunk_num = 0的行按下式上调权重
$$w_{\mathrm{cold}} = 1 + \alpha(r - 1), \quad \alpha = 0.5 \tag{7}$$
其中 $r$ 是 warm-to-cold 的行数比;(ii) 只在冷样本行上对 base logit 计算一个辅助损失($\lambda = 0.5$),直接训练 base 路径,而不是让修正路径吸走全部梯度。
消融战役:什么真正移动了指标¶
整个战役训练了 25 个变体,论文只报告里程碑。每一行后继配置都要复现之后才被采纳,因为这份数据上的 run-to-run 种子噪声在 CL/Top-1 上约 1pp。两个影响最大的实验:
- 修正路径中的 target attention:Top-1 +3.3pp。加入 DIN 式注意力 head 把 Top-1 从 0.5822 抬到 0.6152——整个战役中最大的单杠杆收益,在三次独立运行中的两次复现成功,同时 ER 保持不变。不以候选为条件的池化(均匀或按时近加权,即他们的 v14 base)会留下这块空间,因为一通客服电话通常讨论好几个需求,单个 pooled 摘要会把它们糊在一起。
- 冷启损失杠杆:找回通话开始时的质量。激进的转录建模倾向于侵蚀通话开始(CL)的质量——梯度集中到修正路径。上面两个冷启杠杆把 CL 恢复到与 base 架构持平(0.4546 vs 0.4575),同时保留注意力带来的收益,产生最佳平衡配置 TT-full(战役中最好的 ER 与 GZ)。只做全局重加权是不够的。
TT-attn 与 TT-base 的差别是 target-attention head 加冷启杠杆;TT-full 额外在 58% chunk-stride 子采样上训练。这条线在有转录信号之后决定性地打败旧模型(TT-full:ER +2.8pp,GZ +5.2pp),只在通话开始(CL −4.4pp,此时无转录可用)落后。
DeepFM:Attention Pooling¶
把上面确认的杠杆直接搬进 DeepFM,实现为一个作用在同一条 per-utterance 嵌入序列上的 attention-pooling 层。
Per-utterance 嵌入存储。稠密文本表示用 nomic-embed-text-v1.5(768 维)按 utterance 计算一次而不是按 chunk_num 计算一次,缓存在每个 contact 的一个按时间排序的扁平存储里;一行 (contact, chunk_num) 读取该存储的一个前缀切片。这一个设计消除了朴素重嵌入在这份数据上会带来的 6.19× 冗余嵌入调用。
池化层。给定某个 chunk 上可用的变长 per-utterance 嵌入序列,一个单一学习 query 的注意力头为每条 utterance 打分——打分器是两层 MLP($768 \rightarrow 128$、tanh、$128 \rightarrow 1$),跨位置共享——再用 masked softmax 组合,padding 位置在 softmax 前被打成 $-\infty$。尚无任何 utterance 可用的 contact(chunk_num = 0,转录还不存在)被显式赋一个零向量,而不是对一个空的/全被 mask 的序列做 softmax(那会产生未定义的 NaN 注意力权重)。得到的 pooled 向量只拼接到 DNN 塔的输入上,与表格特征并列;它不进入 FM 或线性组件。
集成。池化器被包在它所替代的 TF-IDF 文本投影器已经使用的同一个调用接口后面,因此周边训练与评估代码无需任何结构性改动——只有文本表示模块本身变了。这是一条很实用的工程纪律。
结果¶
表 9:架构与文本表示对比,固定群体 CL/ER/GZ 评估器
| Configuration | CL | ER | GZ |
|---|---|---|---|
| Legacy model (CatBoost) | 0.4985 | 0.5310 | 0.5676 |
| TT-base: residual two-tower | 0.4575 | 0.5537 | 0.6152 |
| TT-attn: + target attention | 0.4472 | 0.5537 | 0.6113 |
| TT-full: + cold levers, stride | 0.4546 | 0.5585 | 0.6195 |
| Tabular-only (DeepFM) | 0.3426 | 0.4362 | 0.4293 |
| DeepFM + TF-IDF/SVD + dense embeddings | 0.4678 | 0.5283 | 0.5158 |
| DeepFM + Attention pooling (ours) | 0.4867 | 0.5630 | 0.5943 |
分析:
- DeepFM 的 tabular-only vs text-enabled 对比是"文本信号确实重要"的有力证据,它填掉了到 CatBoost 基线(0.4985 / 0.5310 / 0.5676)大部分的 CL/ER/GZ 差距。
- Attention pooling 走得更远:它是本文第一个在 ER 与 GZ 上直接打败 CatBoost 的配置(0.5630 vs 0.5310;0.5943 vs 0.5676),并且在 CL 上只差 1.2pp。作者称这是本文最强的结果,且与 §7 的损失/正则图景一致。
综合¶
跨两种架构,深度模型只在 ER 与 GZ 上直接打败旧 CatBoost 模型——那是两个"实时转录确实存在"的评估点——而在 CL 上没有,因为那里还不存在转录(填这个缺口是负采样/正则的杠杆,而非架构的杠杆,见 §7)。
- Two-tower 通过候选条件化的 chunk 序列注意力拿下 ER/GZ,但在通话开始处只恢复到与自己的 cold-safe base 接近持平;
- DeepFM 通过同样的底层机制作用在一个结构上不同的基础架构上拿下 ER/GZ,并在 CL 上更接近 CatBoost(差 1.2pp)。
这个 ER/GZ 效应通过两种不同的注意力机制、在两种不同的架构上出现了两次——这正是作者把"以转录文本为条件"(而不是"DeepFM 还是 two-tower")当作证成这次迁移的因素的理由。两个架构互不支配:TT-full 在 GZ 上领先(0.6195 vs DeepFM attention-pooling 配置的 0.5943,比 CatBoost 高 5.2pp)——而 GZ 正是"推荐最可能真正触发"那一刻的指标——作者把这归因于 two-tower 的 target-attention 修正路径,它以候选商品为条件做转录池化,因此恰好在转录最长时收益最大。
推理时的挑战与优化¶
从提升树集成换到深度推荐器,改变的是服务时时间花在哪里。作者在同一台机器上端到端 benchmark 了两套系统的真实推理路径(只测 base model,不含 calibration),用的是生产 CatBoost artifact 与表 9 的 attention-pooling checkpoint。
两套系统在模型跑起来之前都要付一笔文本特征化成本——而 CatBoost 的那笔大得多。 CatBoost 的模型调用本身很便宜(2.4ms),它 641ms 端到端成本里几乎全是 TF-IDF 向量化。DeepFM 付的是同类成本,但规模小得多,通过一个小 transformer(nomic-embed)文本嵌入完成。
表 10:DeepFM 单请求分步成本(平均,冷请求)
| Step | CPU | GPU | % of CPU total |
|---|---|---|---|
Text embedding (nomic-embed) |
59.8ms | 34.3ms | 48% |
| Tabular featurization | 12.5ms | 22.6ms | 10% |
| DeepFM forward pass | 52.3ms | 56.4ms | 42% |
| Total (end to end) | 124.5ms | 113.3ms | 100% |
分析:在 CPU 上,文本嵌入实际是 DeepFM 两笔成本中较大的那笔——文本特征化是两套系统上请求里最贵的单一部分,比任何一个模型自己的打分逻辑都贵。
在无批处理的单请求上,DeepFM 端到端已经比 CatBoost 快(124ms/113ms vs 641ms)——但对一个同时处理很多请求的生产系统来说这是错误的比较维度,而正是在那个维度上局面反转。
批处理才是真正的杠杆¶
树集成的 predict() 调用本来就能在一遍里几乎免费地为很多行打分,因此 CatBoost 的吞吐几乎随 batch size 线性扩展(batch 64 下 9,954 req/s)。神经网络只有在服务代码真的把请求攒批时才拿到同样的好处——朴素地把每个请求丢到自己的线程上在负载下反而更糟(超过 4 个并发请求后吞吐下降,因为内部并行与线程并行抢同一批核心)。正确攒批后,DeepFM 的吞吐从 batch 1 的 17 req/s 升到 CPU 323、GPU 575(batch 64)——同样的权重上 19× / 34×。
这填掉了大部分而非全部差距:CatBoost 单行成本本质上更便宜。批处理把 DeepFM 从"不可用"带到"生产负载下可用",但不会让 DeepFM 比 CatBoost 更便宜。
批处理降低的是每请求成本,而不是任一单请求的延迟——更大的 batch 让单请求延迟更差而不是更好。一个请求必须等到攒够别的请求填满一个 batch,或者等待计时器到期,才会被打分。
表 11:单请求与 batch-64 的服务成本(无 coalescing) (CL/ER/GZ/PR-AUC 是模型权重的属性,与服务设置无关)
| Model | Setup | P50 | Thpt. | CL | ER | GZ | PR-AUC |
|---|---|---|---|---|---|---|---|
| CatBoost, fp32 | batch 64 | — | 9954/s | 0.4985 | 0.5310 | 0.5676 | — |
| CatBoost, fp32 | single req. | 641ms | — | 0.4985 | 0.5310 | 0.5676 | — |
| DeepFM, fp32 | CPU, single req. | 124ms | 323/s ᵃ | 0.4799 | 0.5550 | 0.5918 | 0.7431 |
| DeepFM, fp32 | GPU, single req. | 113ms | 575/s ᵃ | 0.4799 | 0.5550 | 0.5918 | 0.7431 |
| DeepFM, INT8 | CPU, single req. | 74ms | 591/s ᵃ | 0.4788 | 0.5342 | 0.5989 | 0.7217 |
ᵃ batch-64 吞吐上限(req/s),不是真实到达流量下 coalescing 服务器的测量值——见表 12。
表 12:DeepFM 在请求 coalescing 服务器下(最多持有请求 8ms,或攒满 64 个),持续 Poisson 到达流量
| Setup | P50 | P95 | Throughput |
|---|---|---|---|
| CPU, coalesced, 150 req/s | 288–377ms | 455–747ms | 82–103 req/s |
| GPU, coalesced, 150 req/s | 116–121ms | 168–365ms | 99 req/s |
| GPU, coalesced, 500 req/s | 477ms | 648ms | 201 req/s |
分析:在这个中等到达率下,CPU coalescing 服务器的典型延迟(288–377ms)比不攒批、来一个打一个(124ms)还差——因为在等待窗口内到达的请求不够填满一个 batch,请求大多只是坐着等超时。批处理只有在到达量高到能快速填满 batch 时才划算;最大等待时长这个旋钮是在最坏延迟与 batch 大小之间做交换,而作者明确表示"在没有生产流量数据前我们没有默认推荐值"。这是一条非常克制、很有工程诚意的结论。
量化:一个温和的、依赖指标的杠杆,不是免费午餐¶
INT8 动态量化(32-bit → 8-bit 权重,不重训)把模型文件缩小 75%、把 batch-64 吞吐提高 11%(表 11)——真实但远不及批处理的 19–34×——同时不均匀地移动质量:CL 与 GZ 基本不变甚至略好,但 Early-Reco F1 与 Test PR-AUC 都掉约 2 个点。鉴于收益温和而代价真实,作者默认不采纳;若要用,应对着部署最在乎的那个指标来权衡。
对生产意味着什么¶
DeepFM 在一个实时系统里以可用的成本与速度是可服务的,但只有在批处理到位的前提下——而大多数推理框架不会自动做这件事;这个成本相对于表 9 的推荐质量收益是值得的。更小或蒸馏过的文本编码器是一个有前景但未测试的杠杆,因为文本嵌入占了 DeepFM CPU 成本的近一半(表 10);GPU 只在吞吐上必需,而不是为了装下模型(batch 64 下峰值 VRAM 仅 1.4GB)。
核心贡献总结¶
- pairwise-binary 问题重构:让任意 item/context 特征都能进模型,无需固定标签空间——这是为"给训练中从未见过的商品打分"(新 SKU、快速增长目录)准备的结构性前提。
- 一套负样本分类学与彻底的负采样调查:显式/隐式 × 硬/软的二维词汇,加上纯负 contact 这个群体级概念;chunk 粒度采样优于 contact 粒度;结构化采样能锐化 PR-AUC 却不必然改善 contact 级决策;$K \in [2,4]$ 是实用区间。
- 批次组成 + InfoNCE 对比目标:证明二者可加而非冗余——单独控制 minibatch 组成已经是独立杠杆,但叠加对比项后每个 contact 级指标都更好。
- Double-descent 刻画:36 配置网格证明 147.8M 参数基线的排序质量不来自容量;72× 更小的模型 + 噪声注入是整个网格里唯一打败 CatBoost CL 的配置;并给出"训练更久不能恢复"的空结果。
- 跨架构的注意力池化:DeepFM 共享 query 池化 + two-tower DIN 式候选条件 target attention,是本文唯一直接打败 CatBoost(ER/GZ)而非只是缩小差距的干预。
- cold-safe 零初始化残差契约:$\mathrm{logit} = \mathrm{logit}_{\mathrm{base}} + \Delta(\mathrm{transcript})$ 加单元测试保证初始化时等于 base,是把"新信号路径不能伤害既有质量"写进架构而非损失权重的范式。
- 推理时优化的完整成本表:文本特征化是两套系统最贵的一步;批处理是 19–34× 的真正杠杆但会恶化单请求延迟;INT8 量化收益温和且指标依赖,默认不采纳。
讨论与局限性¶
值得借鉴的设计¶
- 固定群体评估器:所有实验都在同一批 16,709 个正例 contact 上、用同一套 micro-F1 定义评估,让"有没有回退"在每一步都有无歧义答案。对任何有非回归约束的线上迁移,这是最该先建的基础设施。
- 零初始化 + 单元测试的契约式架构:把安全性写进结构而非损失权重,并用测试固化,避免后续每一次架构增补悄悄破坏冷启行为。
- 复现门槛纪律:因为 run-to-run 种子噪声在 CL/Top-1 上约 1pp,每个被采纳的里程碑都要求复现。25 个 two-tower 变体中只报告里程碑,也避免了"挑最好那次 run"的陷阱。
- 对代理指标的怀疑:val-test PR-AUC 间隙单调收窄却不带动 CL/ER/GZ,结构化负采样提升 PR-AUC 却掉 CL——这两处都被明确写成"代理指标改善不等于目标指标改善"。
- 空结果与不利结果被如实报告:200-epoch 无恢复、没有任何配置能三指标同时清过 CatBoost、INT8 不采纳、coalescing 在中等流量下反而更慢——这在工业论文里相当少见。
局限与争议¶
- 没有线上 A/B。这是最大的局限。全文所有比较都是离线的(虽然对着已部署基线、用生产群体),新模型尚未有线上实验结果,也就无法验证离线 CL/ER/GZ 提升能否转化为真实业务收益。论文标题说的是 "Migrating",但严格讲这是一次迁移前的可行性论证,而非迁移完成后的复盘。
- 技术全部是成熟手段的组合。pairwise-binary 重构、负采样、InfoNCE、DeepFM、two-tower、DIN target attention、attention pooling、double descent、INT8 量化——没有一项是本文提出的。论文的价值在组合方式、评估纪律与工程细节,不在方法新颖性。
- 不含任何公开数据集实验。全部结果基于 Intuit 内部生产数据(90.4 万训练行 / 36.0 万时序 holdout / 9 个商品),外部无法复现,也无法与其它工作横向比较。
- 规模很小。9 个商品(计划扩到低百量级),这让 two-tower 的 retrieval 优势"潜在而未承重",也意味着结论未必外推到大目录场景。反过来说,论文自己承认 CL 上的落后本质上来自"通话开始时没有转录",这是场景内生的,换个规模未必同形。
- CatBoost 的 CL 优势未被真正攻克。除了 §7 那个孤立的 17.2M + 噪声注入配置,attention-pooling 主方案在 CL 上仍差 1.2pp。而 CL 是"最早可能给出的推荐",在业务上未必不重要。
- 成本增加是确定的。DeepFM 即使正确批处理后(323/575 req/s)仍远不及 CatBoost 的 9,954 req/s;论文明确说批处理"没有让 DeepFM 比 CatBoost 更便宜"。这次迁移是拿显著更高的服务成本,换 ER/GZ 上 3–5pp 的 F1。
- 公平性未审计。作者在 Ethical Considerations 里主动承认:没有跨客户分群做公平性审计,也没有做任何显式的偏差纠正,把它作为一个开放局限而非已解决的问题陈述。
- 表 11 与表 9 数值不一致。表 11 里 DeepFM 的 CL/ER/GZ 是 0.4799/0.5550/0.5918,而表 9 的 attention-pooling 行是 0.4867/0.5630/0.5943。论文说服务 benchmark 用的就是表 9 的 checkpoint("base model only, no calibration"),这个差异未被解释。
工业落地价值¶
这套系统服务的是 Intuit 的实时客服会话推荐:客服与客户通话时,系统按每 20 条 utterance 增量打分,把最相关的商品推给客服,再由下游 DynaPitch 系统转成实时话术。它是多个生态增长与留存计划的骨干,喂给的升级推荐支撑约 3.6 亿美元量级的营收组合。论文给出了完整的服务成本剖面(单请求 124.5ms CPU / 113.3ms GPU,coalescing 下 P50 116–477ms,峰值 VRAM 1.4GB),这类数字在学术论文里几乎看不到。
但必须强调:新模型尚未上线 A/B,噪声注入与更小模型尺寸被"采纳为下一次生产迭代的设计杠杆",也就是说这仍是迁移路上的一站。
作者自己的定位¶
论文结尾那段写得很克制,也很值得引用:这是一份生产迁移案例研究,不是 benchmark 结果;上面这些杠杆是在一个实时、受非回归约束的系统里成立的——那正是大多数要从树模型迁走的实践者真正身处的环境,而不是 benchmark 规模的环境。一个面临同样迁移的实践者不会在这里找到一个普适赢家,而作者认为这恰恰是诚实且有用的发现。