1. 双塔召回模型到底在解决什么问题
先聊一个场景。你在刷短视频App,系统从几十亿的候选视频里,只用几十毫秒就给你挑出了十几个你可能感兴趣的。这个“从海量候选里快速筛出少量靠谱候选”的过程,就是召回。召回不是排序,它不需要精准到把最好的排第一,它只需要保证“好东西别被漏掉”,把候选从亿级压到千级甚至百级,交给后面的精排模型去细抠。
说到召回,早期业界主流是ItemCF、UserCF这类协同过滤,或者FM、DeepFM直接拿全量特征打分类似的分数。但这些方案在超大规模候选集下有一个绕不开的痛点:推理时要把用户和所有物品做交叉计算,复杂度随物品量线性增长,线上根本扛不住。于是“双塔召回模型(Two-Tower Retrieval Model)”成了工业界最主流的解决方案——把用户侧特征和物品侧特征分别编码成两个独立的向量,线上只用算一次用户向量,然后去物品向量库里做近邻检索,复杂度和物品总量解耦,只在检索阶段承担ANN的开销。
我最早接触双塔是几年前在做推荐系统召回优化的时候,当时团队被线上延迟和召回覆盖率两头折磨,换了双塔之后,效果立竿见影。但这玩意儿上手容易,做好很难,坑也很多。这篇就系统地把双塔召回的来龙去脉、架构细节、训练技巧、线上部署和踩坑经验一次讲清楚,给正准备上手做召回的同学一份能直接抄作业的参考。
2. 双塔架构的核心设计与选型思路
2.1 从“交叉特征”到“向量内积”的思路转变
传统排序模型的核心是特征交叉,比如用户性别和物品类目的组合,模型能学到“男性用户点击游戏类物品的概率更高”这种细粒度规律。但召回阶段面对的候选集是亿级的,排序模型那套复杂交叉根本跑不动。双塔的思路是反过来的:用户侧特征和物品侧特征各自独立过网络,分别得到user embedding和item embedding,最后用内积(或者余弦相似度)作为匹配分。
为什么要用内积而不是其他交互方式?核心原因是内积可以拆解成“先算用户向量,再和物品向量做内积”,而物品向量可以提前算好存进向量库。如果换成MLP拼接、bilinear交互这类方式,用户和物品的特征就必须实时交叉,没法离线预计算物品向量,线上就退化成全量计算了。所以双塔的结构看似是“两个塔”,本质上是“为了支持向量化检索而做出的结构妥协”。
这里有个容易误解的点:双塔不是“不用交叉特征”,而是把交叉能力压缩到了向量内积这么一步。内积本身就是一种最简单的交叉。那用户侧和物品侧各自的塔内,可以做特征内部的交叉(比如FM式的特征嵌入加和),也可以做特征之间的注意力交互,只要不跨塔就行。塔内结构可以很复杂,塔间结构必须简单,这是双塔设计的第一性原则。
2.2 两个塔各自该喂什么特征
用户塔输入的是用户侧特征,常见的有用户ID、年龄、性别、城市、近期点击的ItemID序列、行为统计特征(近7天点击品类分布)等。物品塔输入的是物品侧特征,常见的有ItemID、类目、标签、标题分词、发布时间、作者ID、物品统计特征(近3天曝光量、点击率)等。
特征处理上要注意几点:
- 用户ID和ItemID这类高基数类别特征,需要做embedding,embedding维度一般取32到128之间,基数特别大的可以适当调高,但不能无脑高,维度太高训练参数爆炸且容易过拟合。
- 多值特征(比如用户近7天点击的ItemID序列)需要对每个ID取embedding再池化,池化方式首选加权平均,权重可以用行为时间衰减系数(越近的点击权重越高),也可以直接用注意力机制学权重。
- 连续特征要做归一化或者分箱,直接扔原始值进网络,数值差异太大会让梯度不稳定。
- 时间类特征(比如物品发布时间距今天数)建议做log变换,效果比线性归一化好很多。
我还见过一种做法是把“用户最近点击的物品的类目分布”做成一个变长稀疏特征,喂给用户塔。这类“统计类交叉特征”虽然没有直接实现用户和物品的特征交叉,但通过行为序列把用户侧和物品侧的语义信息在塔内打通了,实际效果提升非常明显。如果你做双塔召回总觉得塔内表达能力不够,可以优先考虑在特征层面加强这类跨侧信息的注入。
2.3 为什么双塔是召回任务的工业标准答案
相比其他召回方案,双塔的工业优势非常清晰:
- 训练和推理解耦:用户塔和物品塔可以分别训练、分别上线,用户侧模型更新不影响已构建好的物品向量索引。
- 支持大规模检索:物品向量可以离线计算并建ANN索引(如HNSW、IVF),线上查询时用近似最近邻检索代替全量打分,QPS能做到很高。
- 扩展性好:新增物品只影响物品塔和索引,不需要重训用户侧,新增用户同理。
- 冷启动能力:物品的特征embedding可以覆盖未登录ID,对长尾物品的召回明显优于纯协同过滤。
当然,双塔也有众所周知的缺陷:塔间交互太弱,难以建模用户和物品的细粒度交叉。所以业界通常的做法是“双塔召回负责广度,精排负责精度”,双塔的目标不是做到完美排序,而是做到“不漏掉好东西”。如果想在双塔基础上增强交叉能力,可以用senet双塔(对特征做动态权重)、多兴趣双塔(用户塔产出多个embedding)、或者双塔+图模型的混合方案。
3. 数据构造与负样本工程
3.1 正样本:点击还是完播还是下单
正样本的定义直接决定了模型学到的“匹配关系”是什么。如果做信息流推荐,正样本可以是点击、有效播放、完播、点赞、收藏等。不同业务对正样本的偏好不同,我见过的最常用组合是“点击+完播/阅读时长加权”。比如某视频App的正样本定义:用户点击且播放时长超过5秒,或者播放时长超过视频总时长的60%。这种定义兼顾了点击率和内容质量,避免模型只学到标题党。
正样本采样的时候还有几个细节:
- 一个用户一天内对同一个物品多次点击,只保留一次,不然高频重复行为会主导梯度。
- 正样本数量要做上限截断(比如每个用户每天最多取200个正样本),不然头部用户的正样本把模型带偏。
- 负反馈(点了不感兴趣、刷走)要不要当成正样本?我建议单独建模,不要混入正样本。用户的“不喜欢”是另一种语义,混在一起会让向量空间互相拉扯。
3.2 负样本:随机负样本和难负样本的平衡
负样本是双塔召回里最值得花时间的部分。经典的负样本策略是随机负采样:从全量物品里随机抽一批,当作“用户不感兴趣”的样本。随机负样本的问题在于太简单,模型很快就学会区分“用户点过的”和“随机物品”,学到的向量空间区分度很高但泛化性差。
后来业界普遍采用“随机负样本+难负样本”的混合策略。难负样本指的是那些“看起来可能感兴趣、但用户实际没有交互”的物品。怎么构造难负样本?常用方法:
- 全局热门榜采样:把高热物品随机当作负样本,模型被迫学会区分“用户是否真的对这个热门感兴趣”,而不是套热门流量。
- 同类目采样:随机选一个与正样本同二级类目的物品作为负样本,模型要学到类目内部的精细匹配信号。
- Batch内负采样:当前batch内其他用户的正样本,对于当前用户来说就是天然的负样本。这个策略成本极低,而且能近似利用全量的物品分布,是双塔训练最核心的负样本来源。
负样本比例上,随机负样本:Batch内负样本:类目负样本 = 1:1:1是我用下来比较稳的组合。如果总负样本量是正样本的3到5倍,效果通常不错。注意别把负样本加太多,否则训练耗时和显存压力都会变大,收益却不明显。
3.3 Batch内负采样与温度系数的纠缠
Batch内负采样是双塔训练里最常用的技巧,其实就是把batch内其他用户的正样本物品当成当前用户的负样本。这个做法的好处是负样本随模型训练动态更新,越到后期负样本越“难”,模型训练质量越高。但缺陷也很明显:热门物品出现在其他用户正样本里的概率高,所以热门物品被当成负样本的次数也更多,会产生“热门打压”偏差。
解决方案有两个方向。一个是在损失函数里对负样本做曝光量校正,比如用物品的曝光频次对logits做减项修正,这个在Google的《Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations》里有详细的推导。另一个是调整温度系数,温度系数τ越小,softmax分布越尖锐,模型越聚焦于“最难区分的负样本”;τ越大,分布越平滑,负样本之间的差异被弱化。实践中温度系数取0.05到0.2之间效果最好,我常用的起点是0.1,然后根据负样本难度做微调。
还需要注意Batch size对负采样的影响:batch越大,负样本池越大,模型见到的物品分布越接近全量,效果越好。但batch太大显存扛不住,所以业界普遍用64到512的batch size配Async SGD并行训练,在保证负采样充分的同时控制显存。如果算力允许,尽量把batch size往大了调,能明显改善长尾物品的召回效果。
4. 模型结构设计与特征工程细节
4.1 塔内结构:Embedding层、池化层与多层MLP
双塔的塔内结构一般包含这几个部分:稀疏特征Embedding层、多值特征池化层、全连接层。Embedding层把高基数类别特征映射为稠密向量,多值特征通过池化把变长序列转成定长向量,然后拼接所有特征向量,输入几层MLP得到最终的塔输出向量。
MLP的层数和维度设置,我的经验是:用户塔可以做到3层,每层256或512维,因为用户行为复杂;物品塔2层到3层,每层128或256维,因为物品特征相对简单。塔输出维度一般取32到128,然后做L2归一化。输出维度太小表达力不够,太大则ANN检索的精度和性能都会受影响。
这里还要重点提一下“特征拼接顺序”对训练的影响。实践中发现,把ID类特征放在拼接的前段、把统计类特征放在后段,收敛速度会更快。原因可能是ID类特征本身就承载了主要的语义信息,网络早期更容易学到ID之间的共现关系。虽然这个现象没有特别强的理论解释,但我在多个数据集上验证过,效果差异是存在的。
4.2 归一化、Embedding初始化与数值稳定性
双塔训练里数值稳定性是个容易被忽略的大坑。两个塔的输出做内积,如果向量维度是128,每个维度取值在0到1之间,内积的结果会随维度累积变大。这时候如果不做任何处理,softmax里的logits会非常大,梯度消失或者梯度爆炸都有可能发生。所以两个塔的最终输出一般都要做L2归一化,把向量长度固定为1,这样内积就是余弦相似度,取值范围被限制在[-1,1],数值上稳定很多。
Embedding初始化同样关键。我的建议是采用均值为0、标准差为0.01到0.05的正态分布初始化,太小会让前向信号微弱,太大会让训练早期梯度不稳定。另外一个技巧是给ID类特征的Embedding加一个小的L2正则,权重设置在1e-5到1e-4之间,能有效缓解高基数ID的过拟合。
4.3 多兴趣与多向量方案
经典的匹配模型是“用户一个向量,物品一个向量,内积打分”。但用户的兴趣是多样的,一个用户既喜欢科技新闻又喜欢美食视频,用一个用户向量去和所有物品向量做内积,会把这些兴趣全部揉在一起,导致最终检索结果互相稀释。MIND(Multi-Interest Network with Dynamic Routing)就是解决这个问题的代表性工作——它让用户塔产出多个兴趣向量,每个兴趣向量分别去物品库检索,最后合并候选集。
多向量方案在线上检索的时候,用户侧需要产出K个向量,每个向量都去做一次ANN检索,然后再做结果合并去重。K的经验值是4到8,太大则检索开销成倍增加。多兴趣模型对用户行为序列建模的要求更高,需要类似动态路由或者注意力聚类的机制,实现成本比单向量方案大不少,但召回效果的提升通常值得这个成本。
我个人的建议是:如果当前单向量双塔的召回效果已经不错,不要为了追新而强行上多向量。先把单向量的特征工程和负样本做到位,如果长尾兴趣的召回仍有明显缺失,再考虑升级多兴趣方案。工程上小步快跑比大步重构稳妥得多。
5. 损失函数与训练细节
5.1 Softmax损失、NCE与Sampled Softmax
双塔召回最标准的损失函数是“batch内softmax”形式:
[ L = -\log \frac{\exp(s(u, i^+) / \tau)}{\exp(s(u, i^+) / \tau) + \sum_{j \in N} \exp(s(u, i^-_j) / \tau)} ]
其中 (s(u,i)) 是用户向量和物品向量的内积(或余弦相似度),(\tau) 是温度系数。这个损失的本质是:让正样本的物品在所有候选物品中打分最高。分母里的负样本来自batch内其他用户的正样本,相当于用近似的方式估计了全量物品的softmax分母,这叫sampled softmax。
如果物品量特别大(比如超过千万),sampled softmax的负样本数量相对全量来说依然很少,估计的分母会有偏差。这时候可以用NCE(Noise Contrastive Estimation)或者其变体来校正。但NCE需要知道物品的噪声分布,计算上更复杂,工程上除非特别需要(比如YouTube推荐系统这种超大物品量场景),用sampled softmax加温度系数就够了。
5.2 双塔专属:温度系数与内积尺度
温度系数这个超参数,很多人忽略它的重要性,但它在双塔训练里的作用比想象中大得多。温度系数控制着softmax分布的“锐度”:温度低,模型会非常激进地去压制最难负样本的得分;温度高,模型对负样本的压制会比较温和,更看重整体区分度。
我的调参经验:当负样本质量参差不齐、难负样本比例低时,温度系数可以稍微调低(比如0.05),帮助模型在难负样本上多花功夫;当负样本整体质量较高时,温度系数调高(比如0.15到0.2),防止模型过拟合到个别难样本上。温度系数不是一个“调一次就不管”的参数,它和负样本策略是耦合的,改负样本策略时必须回来重新调。
5.3 双塔的常见训练trick:EMA、梯度截断与异步训练
双塔训练的在线学习场景很常见,因为用户行为数据是持续产生的。训练方式上我有几个亲测有效的建议:
- EMA(指数滑动平均)参数更新:对模型参数做滑动平均,能在不显著增加训练时间的情况下让模型更稳定,AUC一般能涨0.2到0.5个点。
- 梯度截断:双塔的梯度方差偏大,给梯度设置一个clip(比如最大范数5.0),可以避免偶发的异常样本把模型权重冲飞。
- 异步并行训练:双塔的两个塔结构相对独立,可以用异步SGD并行训练,加快处理大规模用户行为数据的速度。但异步训练需要控制staleness,否则模型收敛不稳定。
还有一个经验是训练轮次不宜过多。双塔学的是“粗粒度匹配”,不是精排序,训练到验证集指标不再上升后,继续训练只会让模型对训练数据过拟合,线上效果反而可能下降。建议在训练过程中保存多个checkpoint,用线上召回率指标做选择,而不是只看离线指标。
6. 线上推理与向量检索架构
6.1 从模型输出到向量索引的构建流程
模型训练完,线上并不是直接跑模型,而是:物品侧离线推理得到所有物品的embedding,灌入向量检索库(如Faiss、Milvus、Elasticsearch的kNN插件),构建ANN索引。用户侧则实时推理出用户向量(也可以用定时任务预计算来降低延迟),然后到向量库里做TopK检索。
建索引注意几个细节:
- 索引类型选择:亿级物品量用IVFPQ或HNSW,千万级用HNSW就够。HNSW的参数里,M(每个节点的最大连接数)取值16到64,efConstruction取值200到800,检索时efSearch取值100到500。M越大,索引质量和召回率越高,但内存和构建时间也越大。
- 向量维度与内存:128维的float向量,1000万条占内存约5GB,如果要上亿,务必用PQ压缩或者降维。
- 索引更新频率:物品向量更新不需要全量重建,可以增量追加,但删除和修改需要定期做全量合并。经验做法是每天一次全量重建,加实时增量。
6.2 召回结果的后处理与多样性控制
向量检索返回的TopK物品,不能直接当成最终召回结果。原因有二:一是向量空间里相似度高的物品容易扎堆,比如返回的全是同一个作者或者同一个细分品类的物品;二是热门物品的向量在空间中往往处于中央位置,容易被大量用户召回,导致马太效应。
我的后处理实践经验:
- 按作者/来源做去重或限流,比如每个作者最多保留2个物品。
- 按类目做多样性约束,可以设定一个配额,确保召回结果覆盖3到4个用户感兴趣的大类。
- 加入轻量的规则兜底:比如TopK中必须保留至少1条“今日热门”,保证新用户或行为稀疏用户不会拿到太冷的候选。
- 对召回结果进行一次轻量过滤,用简单的逻辑规则排除明显不合适的物品(比如用户已拉黑作者的内容)。
这些后处理逻辑虽然简单,但对线上真实体验的影响往往比模型本身还大,千万别省。
6.3 全链路一致性:训练和推理的特征对齐
做过双塔的人都知道,线上推理最大的坑不是模型质量,而是特征不一致。训练时用的是“用户点击时”的历史特征,线上推理时用的是“当前时刻”的特征,两者如果分布差太多,模型效果会明显衰减。典型问题包括:
- 训练时用了未来特征(比如物品当天的总点击量),上线后这些特征在推理时刻还不存在,属于数据泄漏。
- 用户行为的聚合窗口(比如“近7天点击”),线上取的时间点和训练时不完全一致,导致特征分布偏移。
- 物品侧特征更新滞后,比如物品类目改了,但向量索引没有及时更新,召回结果就会和线上物品属性不一致。
我的做法是在训练脚本里对特征做严格的“时间截断”测试:模拟推理时刻的特征分布,重新评估模型AUC和召回率,如果与在线指标差异过大,优先排查特征的可用性和时效性。这个习惯能帮你省去大量线上排查时间。
7. 评估体系:离线指标与线上AB实验
7.1 离线评估:召回率、HitRate与长尾覆盖率
双塔召回模型的离线评估,不能直接用精排那套AUC或者GAUC,因为召回阶段关注的是“候选集是否包含用户真实感兴趣的物品”。常用指标:
- HitRate@K:用户真实交互的物品是否出现在召回TopK里。反映了召回的基本准确度。
- Recall@K:检索出来的相关物品占用户所有相关物品的比例,反映覆盖率。
- 长尾覆盖率:召回结果中长尾物品(曝光量低、交互少)的占比。这个指标往往被忽视,但它是判断双塔是否“学废了”的关键——如果模型只召回热门物品,说明向量空间已经被热度主导,模型本身存在严重偏差。
我自己还有一个习惯:把召回结果按类目分布和用户行为序列的类目分布做对比,看两者是否接近。如果模型召回的教育类内容占比明显低于用户历史行为中的教育类占比,说明这一块的召回能力偏弱,可以针对性地增强该类目的特征表达能力或者负样本构造。
7.2 线上指标:时长、点击率与留存
双塔召回上线后的核心指标,按我的优先级排序是:人均时长/人均阅读量(反映用户整体满足度)> 点击率(反映单次匹配精度)> 互动率(点赞评论)> 留存。召回层的改动,最先体现在人均时长上,其次才是点击率。如果你上线一个双塔模型,点击率涨了但时长没涨,要警惕是不是只是把用户吸引到了低质量内容上。
线上AB实验的注意事项:
- 实验分组前先确认流量是否均匀,避免辛普森悖论的坑。
- 观测周期至少一周,覆盖工作日和周末,召回类的改动在周末的数据表现往往不同。
- 用逐步放量的方式:先5%流量观测稳定性,再逐步扩到20%、50%。如果早期出现指标异常,及时回滚比继续观察更重要。
7.3 数据漂移监控与模型迭代
双塔模型上线后不能放着不管。用户兴趣在变化,物品库在更新,时间长了模型效果必然衰减。我建议建立一套监控体系,关键指标:
- 用户向量空间分布的变化度:定期跑一批用户向量,计算分布距离(比如Wasserstein距离),如果变化过大,说明用户兴趣漂移了,需要重训。
- 物品向量的更新覆盖率:确保新物品在合理时间内得到向量并被索引。
- 召回HitRate的趋势监控:这个指标掉了,优先看数据质量和特征时效性。
一个实用的迭代频率参考:热门场景的推荐系统,双塔模型建议每1到2周重训一次;行为数据波动较大的内容社区,可以缩短到每天增量更新+每周全量重训。
8. 常见问题与排查技巧实录
8.1 离线指标高,线上却没什么效果
这个问题的头号原因是“离线评估和线上业务场景不一致”。离线指标用的是历史行为做label,线上用户面对的是实时的内容池和上下文。排查思路是先看特征一致性,再看出力分布,最后看召回结果是否有收益空间——如果当前召回位置已经基本被规则策略填满,模型提升自然不明显。
8.2 模型只召回热门物品,长尾物品几乎不出现
典型的热度偏差问题。排查点:
- 负样本里是否热门物品占比过高导致打压过度,还是占比过低导致模型无脑学热度。
- 物品塔的统计特征(如曝光量)是否权重过大,导致热门物品在向量空间居中。
- 温度系数是否过小,softmax过度聚焦难负样本,而难负样本多是热门物品。
解法上,可以尝试对高曝光物品做降权负采样、对物品塔热度特征做无偏化处理(比如用曝光量取log后再进网络),或者干脆去掉热度特征只保留ID和内容特征。
8.3 双塔训练Loss下降,但召回率波动剧烈
典型原因是负采样策略不稳定。Batch内负采样依赖当前batch的组成,如果batch的分布抖动大,Loss的波动也会大。解法是增大batch size、固定随机种子分片、或者使用更稳定的“全局负采样+少量batch内负采样”组合。
8.4 用户向量和物品向量的模长差异很大
如果只对用户向量做L2归一化而物品向量没做,内积的数值会受物品向量的模长影响,热门物品因为出现频次高,向量模长通常偏大,模型容易被模长主导。正确做法是两个塔的最终输出都做L2归一化,只保留方向信息。行业内的许多公开方案也都验证了这一点的必要性。
8.5 向量检索的召回结果和模型打分不一致
这通常不是模型的锅,而是ANN索引的近似误差。HNSW这类索引牺牲了一部分精度换速度,如果检索参数调得激进,召回结果和暴力全量打分的重合率会下降。排查方式很简单:用全量暴力检索和ANN检索在同一个测试集上做对比,看TopK重合率,一般要求做到95%以上。如果达不到,把efSearch、M这类参数往大了调。
9. 从双塔到下一代召回:增强与演进的方向
聊到这儿,双塔已经不只是一个“模型结构”,而是一整套召回系统设计范式。它的核心思想——把匹配建模拆成两个独立编码器再去检索——在后续很多工作中都有延续和增强。
下一代召回的几个重要方向:
- 多兴趣召回:用户向量从1个变成K个,代表用户在不同兴趣维度的表征,MIND、ComiRec是代表工作。
- 图模型增强:把用户和物品的行为交互建模成图,用GCN产出embedding作为双塔的增强特征,能大幅提升冷门物品的召回能力。
- 大模型语义召回:用预训练语言模型把文本语义编码进物品塔,对冷启动和语义相似召回有明显帮助,尤其适合短视频、资讯等多模态内容场景。
- 双塔与精排的联动优化:让双塔的召回结果直接面向精排目标优化,而不是只面向“匹配”优化,这类端到端联调的研究越来越多。
但不管演进到哪一步,双塔背后的工程思路——可离线化、可检索化、可扩展化——始终是工业推荐系统落地时必须遵守的原则。模型的精巧程度永远要让位于系统的稳定性和可运维性。
10. 一些踩坑后的真心话
做了几年双塔召回,我最有感触的一点是:双塔模型不是“模型结构”问题,而是“系统问题”。用户塔、物品塔、embeddding、负样本、索引、特征一致性,任何一个环节出了纰漏,最后都会表现为线上指标变差,而排查起来往往要跨多个系统。
给准备上双塔的团队几个总结性建议:
- 先定好评估指标再动手,离线指标和线上指标的口径最好一开始就统一。
- 负样本策略的优先级高于模型结构,双塔的性能上限很大程度由负样本质量决定。
- 特征一致性是线上效果的最大变量,训练代码里一定要留特征时点检查。
- 模型先简单再复杂,先用标准双塔跑通全链路,再逐步加难负样本、多兴趣、注意力等增强模块。
如果你正在做召回,或者正准备从精排扩展到召回,希望这篇能帮你少踩一些坑。双塔这个概念听起来简单,真正跑起来,里面值得琢磨的细节远比想象中多。