ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

推荐系统召回模型全景解析:从双塔到多路召回实践

推荐系统召回模型全景解析:从双塔到多路召回实践 如果你去面推荐算法岗十有八九会被问到这样一个问题召回和精排有什么区别。很多人都会说召回是海选精排是精选这话没错但真到动手做推荐系统的时候你会发现召回模型才是整条推荐算法链路里最考验工程判断力的环节。精排模型做的是在几百个候选里挑TopK而召回模型要做的是在千万甚至上亿的物品池里把这几百个候选捞出来。这个捞的动作做得好不好直接决定了后面所有模型的天花板。这篇文章我想把召回模型这条线整体捋一遍。不按教科书的方式从定义讲起而是按照一个做推荐系统的工程师实际会经历的技术演进路径来展开召回在系统里的位置是什么传统召回和向量召回各自的适用场景双塔模型为什么能成为主流图召回、序列召回、多兴趣召回各自解决什么问题以及多路召回融合和评估里那些容易被忽视的细节。写这篇文章的目标读者是刚接触推荐系统、准备做召回方向的算法工程师也包括想系统整理召回知识体系的朋友。内容会尽量务实每个模型我都会讲清楚它出现的背景和它解决的痛点而不是罗列论文公式。1. 召回在推荐系统里的位置为什么说它决定了效果天花板1.1 漏斗视角下召回承担的任务边界一个典型的推荐系统数据流向是全量物品池 - 召回 - 粗排 - 精排 - 重排 - 展示给用户。这个漏斗的第一道闸门就是召回。全量物品池的规模在大厂场景里是亿级别的中小型产品也是百万千万级别。精排模型哪怕是轻量级的DIN单次推理也要跑在毫秒级但面对几百万候选做全量打分几毫秒乘以几百万延迟直接爆炸。所以必须在精排之前把候选集压缩到几百这个量级。你可以把召回想象成一个图书管理员。图书馆里有一百万本书用户进来报了一个模糊的需求他不可能把一百万本书都拿给用户翻一遍他得根据自己对用户的初步判断先从书架上抽出几十本可能相关的书放到桌子上让用户接着挑。这几十本就是召回的产出。注意这个管理员不需要精确知道用户喜欢哪一本他只需要保证用户可能想看的书不要缺席把精确筛选的工作留给后面的精排。这个任务边界决定了召回模型和精排模型有一个本质区别精排模型面对的是一个判别问题给定一个user和一个item输出一个精确的偏好分数召回模型面对的是一个检索问题它需要在极短的时间内从超大候选池里找到最可能相关的子集。判别问题的评估标准是AUC这类排序指标而检索问题的核心指标是召回率、命中率——简单说好的召回模型追求的是别漏掉而好的精排模型追求的是排得准。1.2 召回和粗排、精排的分工差异很多刚入行的同学会把召回和粗排搞混觉得两者都是缩小候选集。其实它们的工作方式完全不同。召回是基于索引的暴力匹配。它利用的通常是向量近邻检索、倒排索引、图结构游走这类数据结构核心特点是快和可扩展。不管用双塔还是图召回item侧的向量表征都是预先计算好并建好索引的线上请求进来只需要算一个user向量然后去索引里做KNN检索就能拿到结果。整个过程的计算量跟全量物品池的规模是对数关系而不是线性关系。粗排则还是基于模型的逐条打分。它一般用的模型叫双塔的爸爸版或者更简单的LR、笛卡尔积交叉特征模型把召回返回的几百个候选逐条算一个分数然后按分数截断到精排的候选规模。粗排的本质是用便宜的计算做一个足够好的排序所以它的特征交叉能力比召回强但比精排弱。从这个分工就能看出来召回模型的核心矛盾在于表达能力和检索效率之间的权衡。如果召回模型做得太重比如把精排模型的特征全部塞进去那么在线检索的时候就没法用索引加速这个模型就失去了作为召回的意义。理解了这一点你就能理解为什么双塔结构在召回领域统治了这么多年——因为它的user塔和item塔解耦天然适配近邻检索。2. 召回技术的演进主线从多路检索到统一向量化2.1 传统召回Item-CF为什么至今还没被淘汰十年前做推荐系统召回基本靠三类方法协同过滤、基于内容、基于规则。其中Item-CF物品协同过滤是绝对的主力。它的核心逻辑是如果用户A喜欢物品1那么和物品1相似的物品也大概率会被用户A喜欢。这里的相似不是内容上的相似而是行为上的相似——两个物品被同一批用户喜欢它们就相似。Item-CF到现在都没有完全退出历史舞台原因是它太稳了。它不需要复杂的特征工程只需要一张user-item交互矩阵计算物品两两之间的共现关系线上根据用户最近交互过的物品去查相似物品表就能完成召回。它还有一个向量模型不太好替代的特性可解释性强。召回结果出了问题可以直接追溯到因为用户点了A所以召回了和A相似的B排查问题非常方便。但Item-CF的问题也很致命。最典型的是矩阵稀疏性对于长尾物品交互次数太少相似度算不准对于新用户没有任何交互记录根本无法召回。另外Item-CF本质上是在利用共现信息它看不到用户和物品的属性特征比如用户的城市、设备、消费水平物品的价格、品牌、内容标签这些信息在纯CF里基本用不上。所以后来几乎所有工业级的召回系统都走向了多路并行一路Item-CF一路热门召回一路新物品召回再加一路基于内容的召回每一路覆盖不同的需求场景。2.2 Embedding化的分水岭DSSM双塔向量化召回的核心标志是DSSMDeep Structured Semantic Models。这个模型最早是2013年微软提出来做语义匹配的后来被迁移到推荐领域成了召回模型的标准范式。DSSM的结构非常简单一个user塔一个item塔两个塔分别把各自的特征映射成一个固定维度的向量然后在输出层计算两个向量的内积或者余弦相似度作为匹配分数。训练的时候用用户真实交互过的user-item对作为正样本随机采样一些没有交互的item作为负样本让模型学会正样本对的分数要高于负样本对的分数。这个结构之所以能统治召回领域关键在于它的user塔和item塔是解耦的。线上服务时item塔的输出可以全部预计算建好向量索引用户过来只需要实时算一下user塔的向量然后去向量索引里做ANN检索。整个线上流程的耗时可以控制在几十毫秒以内。这个特性是那些user和item特征深度融合的模型做不到的——你让DIN或者BST这类精排模型去做召回它们没法预计算item向量因为模型输入的交叉特征要同时取决于user和item线上只能逐条算几百万次前向推理的耗时是完全不可接受的。如果用代码来表达双塔的核心结构大致是这样PyTorch风格伪代码class DSSMRecall(nn.Module): def __init__(self, user_feature_dims, item_feature_dims, embedding_dim128): super().__init__() self.user_tower nn.Sequential( nn.Linear(user_feature_dims, 256), nn.ReLU(), nn.Linear(256, embedding_dim) ) self.item_tower nn.Sequential( nn.Linear(item_feature_dims, 256), nn.ReLU(), nn.Linear(256, embedding_dim) ) def forward(self, user_features, item_features): user_vec self.user_tower(user_features) item_vec self.item_tower(item_features) score (user_vec * item_vec).sum(dim-1) # 内积 return score实际做的时候两个塔的输出向量还要做归一化和温度缩放这个是后话放到损失函数部分细说。2.3 损失函数与负采样召回训练的胜负手双塔模型的结构几十年没大变真正拉开效果差距的是训练技巧。其中最重要的两块损失函数设计和负采样策略。召回任务的本质是一个多分类问题——从全量物品中选出正样本。标准的Softmax损失直接计算全量物品的概率分布但物品数量太大分母根本算不动所以实践中普遍用Sampled Softmax来做近似。又因为线上检索用内积训练时也要用内积所以业界更常用的其实是带温度系数的InfoNCE损失或者后面谷歌提出来的双塔损失YouTube那篇经典论文里用的就是batch内负采样的softmax变体。负采样是另一个决定模型质量的关键因素。很多新手第一次搭双塔正负样本1:1随机采样训练出来的模型在线上一测找出来的全是热门爆款。原因是负采样太容易了——随机采样的负样本绝大多数是热门物品模型很快就学会了只要像热门物品就是对的。正确的做法是混合采样一部分随机负样本保证基础区分度一部分hard negative比如精排中排在后面但用户没点的物品来逼模型学到更精细的语义边界还有一小部分batch内负样本也就是拿同一个batch里其他用户的正样本物品作为当前样本的负样本。batch内负样本还有一个好处是计算效率高不需要额外采样。另外要强调一个细节双塔模型线上用的是内积相似度检索所以训练时的query塔和item塔输出一定要做L2归一化并且在相似度上乘一个温度系数。不做归一化的双塔两个向量的模长会成为隐形的偏置项模型会倾向于把热门物品的向量模长练得很大线上检索的结果会被热门item刷屏。温度系数的作用是调节分数的分布陡峭程度通常是训练时调节的重点超参经验上从0.05到1.0之间用验证集去试。3. 从双塔到复杂结构图召回、序列召回、多兴趣召回3.1 图召回EGES和PinSAGE背后的工程折中双塔模型虽然结构简洁但它有一个先天弱点太各自为政了。user塔和item塔是分开编码的它们之间唯一的交互就是最终的内积而且训练时每个样本只看得到一个user和一个item的配对关系物品之间的关联、用户之间的关联在双塔的框架里很难显式建模。图召回就是想解决这个问题。图召回的代表工作是阿里巴巴的EGES和Pinterest的PinSAGE。它们的共同点是先构建一个图结构节点是用户和物品边是行为关系点击、购买、共现等然后在图上做游走采样或图卷积学习每个节点的embedding。这样学出来的item向量天然携带了图邻居的信息——一个物品的向量会受它周围物品的影响这比双塔里item塔只吃自身特征要丰富得多。EGES做的事情实际是带物品属性信息的图Embedding它把物品的ID embedding和品牌、类别等side information embedding做加权平均然后用DeepWalk在用户行为序列构建的图上做随机游走得到最终的item向量。这个方案比纯ID embedding效果好原因是冷门item在ID维度过稀疏side information能起到补充作用。PinSAGE更进一步把GCN的思路引入工业级推荐。它在图上做局部的邻居采样和卷积聚合并且用随机游走计算的正相关概率来定义训练样本的权重。PinSAGE是当时第一批能在大规模图上做实时推理的图神经网络方案它引入了在邻居聚合时用重要度池化的做法让离当前节点近、关系强的邻居贡献更大。但图召回有个绕不开的工程代价图的构建和更新比普通双塔麻烦得多。双塔模型可以做到小时级甚至分钟级更新item向量图模型因为要维护全图的拓扑结构全量更新一次往往要跑好几天。所以业界对图召回的态度普遍是让它做补充不指望它做主力。如果你的团队刚起步不建议一上来就上Pinterest那套架构先跑通双塔再考虑在双塔的item表征里加入图embedding作为特征这种折中方案更容易落地。3.2 序列召回SIM与SDM如何利用长短期行为双塔模型的user塔如果只输入用户静态特征和统计特征它其实是脸谱化的——把用户的所有历史行为压缩成几个平均值比如近7天点击了哪个类目最多、平均消费价格是多少。但用户的兴趣是动态变化的今天想买的东西和昨天可能完全无关。序列召回模型就是要把用户最近的行为序列作为输入建模用户的实时兴趣。这个方向的经典工作是阿里的SDM和SIM。SDM把用户行为分成长期会话和短期会话分别建模短期行为序列用LSTM或者Transformer编码获得当前兴趣长期行为用attention去把长期和短期兴趣做交互。SIM则是一个两阶段的思路先用一个轻量的检索模块从用户全量历史行为里筛选出和当前候选item相关的子序列再用一个复杂的模型去建模这个子序列从而降低建模长期兴趣的算力消耗。序列召回模型的共同难题是序列长度不能太长。Transformer自注意力是O(n^2)的复杂度用户行为序列动辄几百上千没法直接喂进去。所以实际部署中一般会对原始行为序列做截断只保留最近20到50个行为再配合时间衰减、行为类型过滤这些手段。对于长周期兴趣用统计特征来兜底。这不是理论上的最优解但是工程上的可行解。3.3 多兴趣召回MIND的胶囊网络要把用户拆成几个人双塔召回还有一个被很多人忽视的问题它把用户表示成一个向量但用户往往有多个并行兴趣。一个用户可能既关注数码产品又关注运动装备还关注美食。单向量表示相当于把这几个兴趣平均了结果每个兴趣都没表达好召回的时候每个方向的物品都捞不深。阿里的MINDMulti-Interest Network with Dynamic Routing就是冲着这个痛点来的。它用胶囊网络动态路由算法把用户的行为序列动态聚类成若干个兴趣向量每个向量代表用户某一方面的兴趣。MIND做召回的姿势是每个兴趣向量分别去向量索引里检索一批item然后汇总并集。这样用户有3个兴趣向量就可以从3个不同的方向去捞物品覆盖度明显提升。MIND之后这类多兴趣模型出了不少变体比如Comirec把用户行为序列用聚类的方法分成多个兴趣簇再用每个簇训练一个双塔还有PEPRec、DeLinkRec这类的纠偏模型处理的是历史行为数据本身有偏的问题。思路虽有差异核心目标都是一样的让用户表示从一维变成多维提高召回的多样性覆盖。需要提醒一点多兴趣召回做出来之后一定要配合融合策略来使用否则会出现一个很有意思的现象——每个兴趣向量单独评价都很好但最终的召回列表里全是某个主导兴趣的结果。因为用户不同的兴趣本身强度不一样如果对所有兴趣向量一视同仁地取各自TopK并集经典兴趣会占据大量配额长尾兴趣被挤压。后面讲到多路融合的时候会细说这个问题。4. 多路召回的系统设计如何把不同模型组织成矩阵4.1 一个典型的多路召回配置生产环境里的召回系统几乎不会只跑一个模型而是多路召回并行。每一路召回用一个独立的召回策略解决一个特定维度的问题然后把各路的结果合并、去重、截断交给下游。一个比较典型的中型内容推荐产品召回路数大致是这样的召回路策略核心优势劣势热门召回全局行为统计按热度排序覆盖新用户冷启动稳定兜底个性化弱同质化严重Item-CF召回用户近期行为物品的相似物品可解释性强部署简单稀疏性差长尾覆盖不足向量召回双塔user向量与item向量的近邻检索泛化能力强特征利用充分依赖训练样本质量对冷启不友好类目/标签召回用户偏好类目下的高热度物品业务可控性强、方便出规则算法含量低容易滚入热门陷阱图召回图模型学习到的item embedding近邻能捕捉行为关系网络更新成本高一般作补充每一路召回的数量配比也需要设计。假设召回总配额是2000个位置热门召回可能只占200Item-CF占400向量召回占800类目召回占300图召回占200剩下的留给新物品探索位。这个配比不是拍脑袋定的是要根据每路召回单独上线时的各项指标后面讲评估和业务目标来调的。如果你想提高个性化程度就提高向量召回的配额如果发现首页内容太单一就要压热门和类目召回的比例把配额让给路召回。4.2 召回结果的融合与配额分配多路召回的输出怎么合并看起来是个简单的工程问题实际上藏着不少策略空间。最简单的融合方式是瀑别式——按固定顺序依次取各路结果某一路的取完后如果配额没用完再取下一路。这种方式实现简单但没法根据用户实时状态做动态调整。更常见的做法是配额式优先级打分先给每路召回设置一个基础配额上限各路先按自己的顺序输出然后统一汇集到一个候选池里用一个召回级融合模型或者简单的加权规则给每个候选item一个召回阶段得分最终按这个得分做全局截断。这里的权重可以是固定系数也可以通过一个小模型在线学习。阿里的Mind和淘宝的召回系统通常就在这层做了一个RRRecall Ranker或者融合权重的学习模块。需要注意的是多路召回的配额和权重不能只看单路指标。经常遇到的情况是某一路召回的单独指标很漂亮但和别的路集合并集后整体多样性下降业务指标反而变差了。原因是各路召回之间有比较大的重叠——比如向量召回里可能包含大量类目召回的item类目召回里也包含热门item这些重叠的item挤占了其他路的配额。所以做融合分析时不能只看这路召回了哪些item还要看这路召回贡献了多少增量item。我建议每次调整配额后跑一个去重增量分析把每一路召回在未出现在其他路结果中的item数量作为监控指标这是控制多路召回边际效益递减的关键。另外还有一个通用技巧给每路召回加一个花样翻新的扰动项。比如热门召回不直接取TopK而是从Top100里按概率抽样取50个类目召回不只是取用户最偏好类目而是从Top3偏好类目里各取一部分。这种带随机性的召回能明显提升推荐结果的新鲜度和探索性代价是离线指标可能小幅下降但线上用户体验指标通常会变好。5. 召回模型的效果评估离线指标和线上指标的差距5.1 离线评估召回率、命中率、AUC其实都不完美召回模型的离线评估是最容易被做糊的环节。很多同学直接用精排那套方法论给模型输出算AUC。这个做法不是完全不行但它没有抓住召回评估的重点。召回评估的核心问题是模型能不能把用户真正会交互的物品从全量池子中找出来。所以最常用的指标是RecallK和HitRateK。RecallK的定义是在测试集里用户实际交互过的item有多大比例被召回到了TopK结果里。HitRateK更简单粗暴TopK结果里有没有至少一个测试集里的正样本有就算命中。这两个指标从两个角度反映了召回能力——Recall看重的是捞全的能力HitRate看重的是捞到的能力。这两个指标在工程上有个大坑测试集的构造方式直接影响数值。如果测试集只用最近一次行为构造那冷启动用户和高活用户混在一起指标会整体偏低如果测试集按行为时间切分训练集和测试集之间的时间跨度太长模型学的兴趣已经过时指标也会失真。所以离线评估一定要做时间一致性设计训练集和测试集都要按用户行为时间切分并且要保证测试集用户至少在训练集中有行为记录否则评估出来的结果没法区分是模型能力不行还是冷启动问题。AUC在召回评估里的正确用法是作为辅助参考而不是主要指标。召回模型的AUC往往虚高因为负采样让负样本非常容易区分AUC很容易刷到0.9以上这时候AUC的区分度已经丧失你跟别人说我的模型AUC提升了0.01别人根本没法判断这个提升是否有意义。5.2 线上观测如何判断召回变好了还是变坏了离线指标再好最终还是要看线上效果。召回模型上线后最需要盯的线上指标有两类一类是行为指标包括点击率、停留时长、转化率、次日留存另一类是系统指标包括召回覆盖率、单请求召回候选数、精排候选池的多样性。这里有个很反直觉的经验有时候线上点击率没有明显变化但用户人均时长提升了或者次日留存涨了这往往是召回端做对了一件提升多样性的事。因为点击率主要反映的是精排模型在候选集上的排序能力如果精排已经很强了召回的改善并不会立刻反映到CTR上但召回提升了覆盖度让用户看到更多以前看不到的东西行为深度和回访频次才会变好。所以做召回的工程师一定要学会看多个指标不要死盯一个CTR。还有一种线上评估思路是分桶对照把用户随机分到实验组和对照组实验组用新的召回策略对照组用旧的观察14天或者更长时间段内的用户留存和活跃度变化。为什么要观察足够长时间因为召回策略的改善往往不是即时生效的用户需要多刷几次推荐系统才有机会把新策略带来的新鲜物品展示给用户用户的兴趣变化和行为反馈才能被捕捉到。跑3天就得结论很容易把有效的召回策略误杀。6. 召回落地的工程坑位从特征、训练到上线6.1 特征穿越最容易在召回里犯的错特征穿越是我见过新人做召回模型时踩得最狠的坑没有之一。所谓特征穿越是指模型在训练时用到了未来信息——某个特征在预测时刻根本不应该被获得。一个很典型的例子训练双塔模型时你在user特征里加了用户近期是否购买过同类商品这个特征。如果这个特征是基于全部历史数据统计的训练时它确实能显著提升指标——因为正样本本身就包含了购买行为这个特征几乎成了正样本的作弊器。但线上做召回预测时这个特征是没法实时更新的或者更新频率远低于训练时的统计周期线上线下特征分布不一致模型效果直接崩掉。避免特征穿越的原则其实很简单每条训练样本里所有特征的时间戳都必须早于这条样本的label事件发生时间。实操时最稳妥的方法是统一用特征天级快照来拼接训练数据也就是把用户和物品的特征切成按天存储的版本训练样本用T-1天的特征去预测T天是否发生交互。这样虽然会损失一些特征新鲜度但能保证线上线下特征口径一致。上线后如果发现线上效果明显不如离线第一反应就应该是查特征穿越。6.2 向量索引训练和线上检索的一致性向量召回模型的线上服务核心依赖ANN近似最近邻检索常见工具包括Faiss、HNSW、Milvus这类向量数据库。这里有一个非常隐蔽的坑离线用全量向量做精确KNN评估出来的指标和线上用ANN索引检索出来的指标往往不一致。原因是ANN索引是有损的。比如HNSW的召回率这个召回率指索引召回率即ANN找到的结果和精确KNN结果的重合比例通常设置在95%左右这个损失在指标上看起来不大但它往往不是均匀分布的——低热度、低交互量的item在索引里更容易丢。结果就是线上召回结果偏向热门item长尾覆盖被悄悄削弱。我个人的建议是上线前必须用线上同款索引配置做一次索引效果审计。做法很简单拿一批线上真实请求的user向量分别用精确KNN和ANN检索Top200算两者的重合度并且分开统计在热门item和长尾item上的重合度差异。如果长尾item上的重合度明显低于热门item需要调索引参数比如增加HNSW的efSearch值、提高图连通度或者直接换索引类型。另外item向量如果周期性更新索引也要同步做增量更新更新频率跟不上会导致线上检索到的是旧向量和训练时学到的语义空间错位。6.3 实时召回与增量更新的取舍召回系统要不要做实时更新这个问题没有标准答案取决于业务形态。新闻资讯类产品的用户兴趣时效性极强连分钟级更新都嫌慢电商和视频类产品对实时的要求则相对宽松小时级更新基本够用。实时召回有两种实现路径一种是实时更新user向量item向量保持离线全量更新。这种方式成本低因为每次请求只需要实时算一个user向量再去做检索。另一种是实时更新item向量比如刚上架的商品和突然爆火的内容需要及时进入索引。第二种的工程复杂度高很多——item向量更新意味着索引要更新频繁全量重建索引的成本不可接受只能做增量而增量索引在ANN里的表现经常和全量不一致。我经历过的项目里最稳妥的方案是双层索引主力索引小时级全量重建保证基础效果另设一个实时桶用于放置新物品和热点突发物品查询时一次性查两个索引做合并。实时桶里的item数量控制在总池子的5%以内索引成本可控效果也基本够用。等到系统成熟后再考虑把实时桶的规模逐步扩大但不要一上来就追求全量实时性价比太低。再补充一个和实时性相关的细节刚上线的召回模型不要直接一股脑全量切流量。先开小流量观察候选集命中率这个中间指标——也就是新召回的item集和旧召回item集的重合度。如果重合度太高说明新模型相比旧模型没有产生增量价值如果重合度太低说明新模型学到的语义空间和旧模型差异过大直接全量替换风险很高最好新旧并存跑一段时间用融合的方式平滑过渡。这套流程做下来基本可以避免模型离线指标很好、上线后被在线环境打得措手不及的经典事故。说了这么多最后回到最开始的那个观点召回决定了推荐的天花板。精排做的是在限定集合里找最优召回做的却是决定这个限定集合到底是什么。很多团队花大力气优化精排模型的AUC效果可能都不如好好理一遍召回链路——把负采样调对、把特征穿越堵住、把多路配额调平衡每一个动作带来的收益可能都比精排涨0.001个AUC要实在得多。召回这块的内容一直很多每篇论文单独拿出来都能写几千字但核心的主线逻辑就是上面这些在检索效率和表达能力之间找平衡在多样性和精准性之间找平衡。希望这篇整理能帮你把零散的知识串成一条线后面再接触具体模型细节的时候能更清楚它在整条链路里所处的位置和职责。
返回列表