1. 从搜索空间谈起为什么召回阶段必须走向Embedding做推荐系统几年我越来越觉得召回阶段才是真正拉开系统差距的地方。排序模型再怎么精细如果召回环节把用户感兴趣的物品漏掉了后面全是白费功夫。早期很多团队在召回层面用的还是ItemCF、UserCF这类传统协同过滤或者简单的规则召回在数据量小的时候够用但一旦物品规模到了百万、千万级实时行为流涌入传统方案就开始捉襟见肘。先聊聊传统召回的困境。ItemCF的核心是计算物品之间的相似度矩阵这个矩阵的构建需要定期全量更新因为用户行为在持续变化物品相似度也在漂移。我有一次跟进一个日活几十万的内容社区物品量只有几十万但物品相似度矩阵的离线计算已经开始吃紧更麻烦的是线上服务要加载这个矩阵做实时检索内存占用高得吓人。UserCF的问题更突出它对用户实时行为的捕捉能力偏弱用户昨天看了什么、今天突然转向什么话题UserCF在向量空间层面是感知不到的。再说规则召回。基于热门、分类、地域、作者等维度的规则召回胜在可解释和稳定但天花板很低——它本质上是在用人工定义的静态维度切分用户兴趣用户的兴趣是动态变化且高度个性化的规则永远赶不上变化。这时Embedding召回的价值就体现出来了。它的核心逻辑很简单把用户和物品都映射到一个低维稠密向量空间里用户向量和物品向量的相似度直接反映兴趣匹配程度。空间距离近代表用户可能喜欢这个物品空间距离远就大概率不感兴趣。与传统方法相比Embedding召回有三个无法回避的优势一是泛化能力强能够通过向量之间的语义相似性找到用户没有直接交互过但可能感兴趣的物品这在传统协同过滤里很难做到二是支持实时更新用户产生一个新行为就能立刻影响其兴趣向量从而影响召回结果三是工程上天然支持海量检索借助向量检索引擎千万级物品的TopK召回可以在几十毫秒内完成。本文适合谁看如果你正在做推荐系统尤其是负责召回链路、或者准备从传统协同过滤升级到向量召回这篇文章会很有用。我会从构建样本、模型选型、训练细节到线上检索、冷启动、踩坑经验把整个技术链路讲透。里面也会有一些具体的数据和对比是我在项目里一步步测出来的不一定适用所有场景但思路和调试路径是可以直接复用的。2. 召回算法演进链路从协同过滤到Embedding的必然性2.1 传统协同过滤的瓶颈到底卡在哪传统协同过滤的思路可以概括为“物以类聚人以群分”。ItemCF认为用户喜欢某个物品是因为他之前喜欢过相似物品UserCF则反过来找到相似用户把相似用户喜欢的物品推荐出去。这两种思路在小规模数据上效果不错但在工业级场景里有几道绕不过去的坎。第一道坎是稀疏性。用户-物品交互矩阵的稀疏度通常在99%以上绝大多数用户只和极少部分物品发生过交互。ItemCF依赖物品共现次数来算相似度交互稀疏意味着大量物品对根本没有共现记录相似度矩阵里大片是空值或者噪声值。UserCF面临同样的问题用户之间如果没有共同的交互物品相似度就无从算起。第二道坎是马太效应。热门物品和大量用户产生交互相似度计算中占据了主导地位长尾物品交互稀疏即使质量很高也难被召回。我在一个项目中做过统计ItemCF召回的物品里头部20%热门物品占了近70%的曝光机会冷门优质内容几乎无法进入召回列表。这一点对内容生态的打击是长期的。第三道坎是特征利用不足。传统协同过滤只能利用用户-物品交互关系用户画像、物品属性、上下文特征全被浪费掉了。后来出现的LFM隐语义模型通过矩阵分解把用户和物品映射到隐向量空间算是初步走向向量化但LFM本质上还是基于全局交互矩阵的分解对序列行为、特征交叉的表达能力仍然有限而且矩阵分解在大规模稀疏数据上的训练效率一直不理想。这些瓶颈堆叠在一起让我越来越确信召回阶段需要一套能自然融合多种特征、能泛化到长尾物品、能实时捕捉用户兴趣变化的新范式。这就是Embedding召回登场的背景。2.2 Embedding召回的基本范式向量化与最近邻检索Embedding召回的技术范式可以拆成两个独立环节向量化Embedding化和最近邻检索ANN。向量化的目标是把用户兴趣和物品内容都表达成固定维度的稠密向量。用户向量来自他的历史行为序列、画像特征、实时场景特征物品向量来自物品属性、文本描述、图片信息、交互统计等。这些向量共同训练使得“用户喜欢某个物品”这个事实在向量空间里表现为两个向量的距离很近。训练过程通常用度量学习Metric Learning的思路核心是构造正样本和负样本。正样本是用户真实产生过正反馈行为的物品点击、收藏、完播等负样本则是用户没接触过、或者接触但反馈差的物品。模型学习的目标是让用户向量和正样本物品向量的相似度尽可能高和负样本物品向量的相似度尽可能低。最近邻检索环节解决的是线上实时性问题。用户向量算好后要在毫秒级从千万甚至亿级物品向量中找到TopK个最相似的。暴力计算全量内积在这个规模下是不可行的所以工业界普遍使用ANNApproximate Nearest Neighbor检索引擎核心思想是通过空间划分、量化和图索引把搜索空间大幅压缩用可接受的精度损失换取数量级的性能提升。我在项目里常用的是HNSW层级可导航小世界图和IVF-PQ倒排乘积量化两种索引结构。HNSW的召回精度高适合中等规模千万级以内向量IVF-PQ的内存占用小适合超大规模向量场景但需要精细调参否则精度掉得厉害。这两个环节缺一不可。Embedding模型训练得再好检索环节做得粗糙线上召回质量一样上不去。我见过一些团队把精力全花在模型结构上忽视了向量索引的工程调优结果线上延迟是扛住了但召回精度比暴力检索下降了五六个点这是很可惜的。后面我会专门讲这块的调优经验。3. 主流Embedding召回算法全景拆解3.1 Item2VecWord2Vec思想在物品序列上的迁移Item2Vec可以说是Embedding召回的开山之作。它的思路非常直接把用户的交互序列看成一句“句子”序列里的每个物品看成一个“词”然后套用Word2Vec的Skip-gram模型学习每个物品的向量表示。举个例子用户在一小时内的交互序列是A物品、B物品、C物品、D物品。模型以B物品为中心词用上下文窗口里的A、C、D作为正样本通过最大化上下文物品的预测概率来训练B物品的Embedding。本质上它假设序列中相邻出现的物品在语义上是相似的这和语言模型里相邻词之间存在语义关联的假设如出一辙。Item2Vec落地时有一个关键细节负样本的构造方式直接决定Embedding质量。我当时用的是“全局随机负采样频率降采样”的组合负样本从全量物品里按频率做加权随机采样高频物品被采到的概率更高但会通过降采样抑制那些过度活跃的物品防止热门物品的向量挤占整个向量空间的中心区域。实践中我还试过另一种变体把同一次会话内的物品作为正样本对随机从会话外采样负样本这种方式对短会话场景效果更好因为用户在一次会话里的兴趣更聚焦序列内部的语义关联更强。Item2Vec的优势是简单、易实现、训练快在一个几百万物品规模的项目里单机训练就能在几个小时内完成整个物品集合的向量化。它的局限也很明显只利用了物品的共现信息用户侧的个性化向量是缺失的所以方案本身不能直接用于用户召回更多是作为物品向量底座供ItemCF升级版使用或者作为双塔模型物品侧的初始化向量。3.2 DeepWalk与Node2Vec图结构上的随机游走Item2Vec把用户行为序列当作句子但真实场景里的用户行为往往不是线性序列而是复杂的图结构。用户和物品之间是二部图关系物品和物品之间通过用户行为产生关联。DeepWalk的思路就是把这个图结构利用起来。DeepWalk的做法分两步第一步在物品共现图上做随机游走生成大量物品序列第二步把生成的序列当作语料送入Word2Vec训练物品向量。随机游走的关键在于游走策略我当时用的策略是从某个物品出发每次以一定概率沿着行为关系跳到下一个物品游走长度控制在40到80步之间每个起始节点生成若干个游走序列。这样就相当于对图的局部结构做了采样把图结构关系转化成了序列共现关系。Node2Vec在DeepWalk的基础上更进一步引入了两个可调参数p和q来控制游走的广度和深度。p控制回退到上一节点的概率q控制游走是偏向广度优先还是深度优先。p值大游走更偏向在局部探索q值大游走更偏向深入图的远端。这个设计让Node2Vec能够捕捉到不同的结构模式社区结构用广度优先更合适桥梁结构用深度优先更合适。我在一个电商场景的测试里发现对于品类关联召回Node2Vec的效果比DeepWalk提升了大概3%到5%的召回命中率原因是电商场景里用户往往会跨品类浏览比如先看手机后看耳机深度优先的游走能学习到这种远距离的结构关联。但要注意图Embedding的工程复杂度比Item2Vec高不少图的构建、游走序列的生成、参数调优都需要额外的开发量如果你的物品关系相对简单Item2Vec可能就够用了。3.3 双塔模型的崛起Youtube DNN的架构与精华Youtube DNN是双塔模型在推荐召回领域的经典代表作这套架构在后续很多工业级系统中都能看到影子。双塔结构分左右两塔左塔是用户侧输入用户的历史行为序列、画像特征、上下文特征输出用户向量右塔是物品侧输入物品属性、统计特征、Embedding特征输出物品向量。两个塔在训练阶段通过一个损失函数拉近正样本对的向量距离推远负样本对的向量距离。Youtube DNN在训练阶段的几个设计非常值得仔细揣摩。第一个是样本构造。论文里用的是“曝光且点击”作为正样本负样本全部来自全局随机采样。这里有一个坑如果只从曝光未点击里取负样本模型学到的其实是“排序能力”而不是“召回能力”因为曝光未点击的样本本身已经经过了一层粗排过滤分布和全量物品不一致。所以负样本一定要从全量候选集里采样才能让模型真正学到区分“用户喜欢”和“用户不喜欢”的全集分布。第二个是用户侧的Watch Sequence处理。用户历史行为序列的长度差异非常大有的用户只有几次交互有的用户有几千次。Youtube的做法是把行为序列里的物品映射成Embedding后做加权平均权重可以是行为发生时间的衰减因子越近的行为权重越高。这个处理简单但有效后来很多模型在序列建模上越做越复杂但加权平均在绝大多数场景里已经能提供足够好的效果基线。第三个是负采样与Batch内负采样的权衡。Youtube原始论文用的是全局随机负采样但工程实现上batch内负采样是更常见的做法——把同一个batch内其他用户的正样本物品作为当前用户的负样本。这种方式的优势是实现简单不需要额外采样逻辑而且由于batch内样本是随机打乱的负样本的分布近似全局分布。但它的副作用是热门物品更容易被当作负样本导致热门物品的向量被过度压制所以实际使用中需要配合频率降权或额外补充全局随机负样本。整条链路走下来我在实际项目里的体会是双塔的核心竞争力在于特征利用的灵活性和线上服务的便捷性。用户侧特征可以随时加入新维度物品侧也能随时补充新鲜属性训练好之后用户向量和物品向量各自独立存好线上召回时只需要算一次用户向量然后去向量库里做ANN检索就行工程实现非常干净。3.4 MIND从单向量到多兴趣的突破双塔模型把用户压成一个向量这会带来一个天然的信息瓶颈用户明明有多个兴趣点比如一个用户既喜欢科技新闻又喜欢美食视频还喜欢旅行攻略单向量要同时表达这么多兴趣最终学出来的向量往往是“四不像”每个兴趣方向都被稀释了。MINDMulti-Interest Network for Deep Recommendation就是冲着这个问题去的。MIND的核心改动在用户侧不再输出一个向量而是通过一个动态路由网络把用户行为序列聚类成多个兴趣向量每个向量代表用户的一个兴趣方向。具体来说MIND使用了一个类似胶囊网络Capsule Network的动态路由机制。用户的行为序列先经过Embedding层然后进入一个动态路由层在迭代过程中不断调整每个行为属于哪个兴趣胶囊的权重最终输出多个兴趣胶囊每个胶囊就是用户的一个兴趣向量。行为序列中的物品会被动态分配到不同的兴趣分组同一个兴趣分组里的物品在语义上更接近。我在某个短视频推荐项目里实际测过MIND的效果用户兴趣确实能通过多个向量表达得更清晰一个用户的向量里既有一个偏向搞笑类内容的兴趣向量也有一个偏向知识类内容的兴趣向量两路召回都能命中对应的内容。在召回阶段MIND的逻辑是用多个兴趣向量分别去向量库召回一批物品然后做融合、去重、排序再送入粗排和精排。这样每个兴趣方向都有机会贡献召回的物品不会像单向量模型那样被平均掉。不过MIND的工程代价不小。首先是训练复杂度比双塔高很多动态路由的迭代计算在用户行为序列很长时开销可观其次是线上服务时用户侧要产出多个向量检索次数成倍增加需要在延迟和召回质量之间做取舍。我当时的处理方式是兴趣向量数量控制在4到8个并且对每个兴趣向量只取TopK更小的值整体延迟还能压住。3.5 其他不可忽视的变种Airbnb、DSSM与多模态方向除了上述几个主流方案还有几个变种方向值得关注它们在不同场景下有独特的价值。Airbnb的Embedding召回方案在房源搜索场景里做了很多针对性设计。它把用户的行为序列拆成长短期两种短期序列用于捕捉用户在当前会话中的实时兴趣长期序列用于表达用户的稳定偏好。训练时引入了“同屏点击”正样本——用户在一次搜索中点击的多个房源即便没有连续点击关系也被当作正样本对因为同屏点击意味着用户在这些房源之间做过比较它们之间存在隐性的相似性。这个思路后来被很多电商和本地生活场景借鉴因为它把“用户比较行为”这个宝贵信号利用了起来。DSSMDeep Structured Semantic Models是微软提出的语义匹配模型原本用于文本匹配后来被改造用于召回。它的结构同样是双塔但更强调两侧特征的多层语义抽象尤其是对文本类特征的建模能力。如果你的物品有大量文本描述标题、详情、标签DSSM风格的塔式结构会比直接拼接Embedding效果更好。多模态方向则是把物品的图片、视频帧、音频等信号纳入Embedding学习。我在图片内容为主的场景中试过用预训练好的图像模型提取物品视觉向量再和ID Embedding、文本Embedding做融合作为物品侧向量。这种做法对冷门物品特别有效因为视觉特征不依赖交互信号。这几类变种的共同点是在基础双塔框架之上针对特定场景的信号特点做了定制。它们没有改变召回的基本范式但告诉我们一件事——Embedding召回方案不是一套模板打天下要结合你自己的数据特点做设计。4. 工程落地的关键细节样本、训练与ANN检索4.1 训练样本的构造正负样本的选择是命门样本构造是整个Embedding召回链路里最容易被低估的环节。我见过好几个团队模型结构抄得很好但召回效果上不去排查到最后都是样本的问题。正样本的定义要和业务目标对齐。如果你的业务核心指标是点击率正样本取“曝光且点击”就够了如果业务更看重时长或转化正样本可以考虑“深度互动”完播、收藏、加购或者按时长加权。还有一个值得注意的地方正样本的反馈时间窗口要设置合理。我用过3天和7天两种窗口对比窗口太短会丢失一些延迟反馈窗口太长会引入大量过期行为噪声最终看到的效果是7天窗口略微占优但训练数据量大了不少这个需要结合数据规模权衡。负样本的构造是真正拉开水平差距的地方。最基础的方案是全局随机负采样从全量物品库里按均匀分布或频率加权采样。频率加权采样会让模型更关注热门物品的区分度但如果热门物品被过度采样模型会对热门物品的向量产生过拟合。进一步的方案是混合负采样一部分负样本来自全局随机一部分来自曝光未点击一部分来自相似物品hard negative。Hard negative的构造有两种常用手法一种是用上一版本的模型召回的、但用户没有交互的物品另一种是用Item2Vec算出来的相似物品里用户没点过的。Hard negative的作用是让模型学会区分“看起来相似但用户确实不喜欢”的边界能有效提升召回精度。我在内容推荐场景里测试过加入10%到20%的hard negative后线上召回命中率提升了约2个百分点但训练时间也增加了30%需要做工程上的平衡。4.2 线上检索HNSW与IVF-PQ的选型和调参训练完成后的向量检索是另一大工程重点。线上面对的核心矛盾是候选集规模大、延迟要求高、精度损失要小。HNSW是我首选推荐的索引结构。它的原理是构建多层图上层图连接稀疏、跨度大下层图连接密集、跨度小检索时从顶层开始逐层下探可以快速逼近目标区域。HNSW有多个可调参数其中最关键的是M每个节点的最大连接数和efConstruction建图时的搜索宽度。M越大图连接越稠密召回精度越高但内存占用和建图时间也越高。我一般从M16、efConstruction200起步精度不足时先调efConstruction内存吃紧时再考虑降低M。IVF-PQ则是对大规模向量更经济的方案。它先用K-Means把向量空间划分成多个簇检索时只搜索目标簇及相邻簇大幅缩小搜索范围PQ乘积量化则把向量切成子段做压缩编码能把每个向量的存储从32字节降到8字节甚至更低。但PQ是有损压缩精度必然下降需要在召回率和内存之间做权衡。索引参数调优我建议用一个固定评估集做离线评测。从全量向量里随机抽出一些query向量暴力计算精确TopK再和ANN的返回结果做对比算RecallK。我习惯把目标定在Recall100不低于95%如果低于这个线说明索引参数损失过大线上会感知到效果下降。实际调优时先固定M值调efConstruction再固定efConstruction调M一轮一轮来不要同时动多个参数否则没法定位变化来源。4.3 向量统一空间与相似度度量选择训练和检索之间还有一个经常被忽略的问题相似度度量的选择。Embedding模型训练时用的损失函数和线上检索时用的距离函数必须保持一致否则会出大问题。我在早期犯过一个错误训练用的是余弦相似度损失但线上检索时把向量做了L2归一化之后用内积排序。表面上两者差异不大但内积没有归一化会影响向量模长对排序的干扰热门物品的向量模长往往偏大用内积排序会倾向于返回热门物品偏离训练目标。后来我统一成“L2归一化内积”作为线上检索的度量和训练时的余弦相似度对齐召回指标的波动立刻缓解了。这里要提醒一下如果向量没有做归一化内积和余弦相似度在排序结果上会有明显差异因为内积的大小受向量模长影响。统一度量是打通训练和检索的第一原则。5. 避坑指南这些年踩过的Embedding召回坑5.1 序列建模的坑行为序列要不要截断行为序列截断是个看起来小但影响很大的细节。太长线上服务时用户向量的计算延迟会超标太短会丢失用户的长期兴趣。我测试过不同序列长度的效果序列长度从20涨到50召回命中率提升明显从50涨到100提升就趋缓了超过100之后收益基本消失只增加计算成本。所以序列长度建议控制在50到100之间。但这里要结合业务特点如果用户的兴趣变化快比如短视频、资讯流短期序列更重要可以偏向短序列如果用户的兴趣稳定比如电商、长视频长期兴趣更重要序列可以适当加长。5.2 冷启动的坑新物品和低活跃用户的双向困境Embedding召回的冷启动问题比传统方法更突出因为新物品没有交互行为学不到可靠的向量表达。我处理新物品冷启动有三个思路一是用物品的类别、标签、文本描述先拼出一个初始化Embedding这个向量虽然不是最优但至少能进向量空间二是寻找和新物品语义相似的老物品直接用老物品的向量做加权初始化三是等物品有少量交互后立即更新向量把冷启动阶段的时间窗口压缩到最短。低活跃用户的向量表达也是难点历史行为太少用户向量几乎是默认值。一个可用的做法是将用户分群低活跃用户使用群体偏好向量作为初始化随着行为积累逐步过渡到个人向量。这个方法效果好、成本低强烈推荐试一下。5.3 超参数选择的坑留意几个反直觉的现象Embedding维度的选择有不少文章讨论我从实际使用角度说几个经验64维适合物品量百万级以内的场景128到256维适合千万级到亿级场景维度再高对精度提升有限内存开销倒是一个劲地涨。向量维度翻倍内存占用直接翻倍如果收益不明显没有必要盲目堆高维度。负样本数量也是一个需要小心的参数。Youtube论文里说负样本从几百到几千都有实验价值但我实际测试发现负样本超过512之后精度提升微乎其微训练时间倒是成倍增长。这里有一个反直觉的现象负样本太少了模型学不好但负样本太多了模型会把所有精力都花在区分明显不相似的样本上反而忽略了困难样本的精细区分。从128起步逐步加到512就够了。5.4 线上离线一致性避免训练和推理之间的隐形损耗线上离线不一致是召回系统的隐形杀手。常见的表现有训练时用的特征和线上捞不到的特征、特征拼接顺序不一致、序列截断长度不同、标准化参数没有固化。这些问题在离线评测时不一定暴露但线上上线后指标会莫名下跌。我的处理方式是把特征处理和向量计算封装成同一个服务训练和线上共用一份特征计算代码这样能从根本上杜绝不一致。6. 效果评测与持续迭代方法Embedding召回的效果评测不能只看单一的召回命中率我个人习惯用一套组合指标RecallK衡量召回覆盖面ILAIn List Accuracy衡量推荐列表与用户实际行为的重合度多样性指标观察是否只召回同质化内容个性化程度观察不同用户召回结果的差异。这四个维度合在一起基本能反映召回系统的真实状况。持续迭代这件事我的经验是要保持“小步快跑、AB验证”的节奏。不要指望一次全新改造就能把所有问题解决更常见的路径是在现有召回链路上做增量改良比如调整负采样策略、增加一路新召回路、优化序列建模方式每次只改一个变量用AB实验验证效果效果好再逐步全量。一个新模型从离线实验到全量上线我一般会走这样一套流程离线离线指标验证确认模型在评测集上优于线上版本小流量AB实验先跑一周看核心指标走势逐步放量观察新增召回路会不会挤压其他召回通道的流量全量上线后持续监控一段时间防止数据漂移带来的效果退化。7. 几条降本增效的实操建议Embedding召回链路真正运行起来之后成本主要在几个地方模型训练的资源消耗、向量存储的内存开销、在线检索的QPS压力。这里分享几条我在项目中验证过的降本增效方法。训练层面可以先把向量维度降下来跑通全链路再按需增加维度。我在一个项目里用64维跑通了整个流程后续才逐步提到128维这样在早期阶段节省了大量训练资源。负样本数量控制在一个够用的水平没必要盲目堆高。存储层面对于亿级别向量的场景IVF-PQ几乎是必选的但要注意PQ的码本大小和子段数量的配置这两个参数直接决定压缩比和精度损失。如果精度可以接受优先把内存降下来这能省一台不小的机器成本。检索层面利用缓存机制非常有帮助热门用户和热门物品的检索结果是高度可复用的我上线了一个用户级缓存策略热门用户的查询走缓存冷门用户才走全量ANN检索整体QPS压力下降了大约40%。整套Embedding召回链路做下来我对推荐系统的体感是模型结构是骨架样本和特征是血肉工程细节是贯穿全身的经络。骨架可以照搬经典方案但血肉和经络必须根据自己的业务场景精心打磨。希望这篇总结能帮你少踩一些坑把资源花在真正能拉升效果的地方。