想象你是一家拥有百万会员的婚介所老板。每位新人进门,你都得在几秒钟内从几百万张候选人卡片里,挑出几十位“可能合得来”的介绍给他。老办法是一对一深聊——聊得越细越准,可一天最多聊几十人。金牌红娘想了个主意:先让会员填一张标准化的“征友需求卡”,再把所有候选人也提前做成同款“资料卡”。你来的时候,红娘只需快速比对卡片上的数字,就能筛出最可能心动的一小撮人,剩下的深度撮合工作,再交给恋爱顾问慢慢做。推荐系统里的双塔模型,就是这位金牌红娘的手艺:一座塔把用户压成一张数字卡(用户向量),一座塔把每个物品也压成一张同格式数字卡(物品向量),两张卡用“内积”算契合度,再用向量检索在千万卡片里毫秒级捞出 Top-K。这篇就用婚介所的故事,把双塔的架构、编码、检索、训练和部署一次讲透。
双塔模型是召回阶段第一道闸门:把“逐一对聊”变成“卡片快筛”。学会它,你就掌握了海量候选毫秒级捞人的基本功——这是推荐、搜索、广告工程师的必备技能。
可视化演示
展示双塔架构设计、用户物品编码、向量检索、负采样训练与在线部署的交互式动画。
本节配有交互动效(进入视口后自动加载,约 3KB)。
双塔模型架构设计 用户塔和物品塔分别将特征编码为固定维度向量 两塔输出向量通过内积或余弦相似度计算匹配度 sim(u,i) = dot(user_tower(u), item_tower(i)) 双塔解耦设计实现离线预计算和高效在线检索
双塔架构设计
把推荐系统想象成婚介所:用户是前来征婚的会员,物品是堆积如山的候选人卡片。老式算法像一位耐心的红娘,每服务一位会员,都要把“会员 + 候选人”捆绑在一起,进行一场深度面谈——这种做法的优点是聊得细,缺点是几百万候选人根本聊不过来。金牌红娘决定改革:把“面谈”拆成“分别填卡 + 快速比对”两个环节。
双塔模型就是这套改革的产物。它由两座神经网络“塔”组成:用户塔只负责读用户侧信息,物品塔只负责读物品侧信息,两座塔在模型内部老死不相往来,各自把输入压缩成一个固定长度的向量。向量可以理解为数字版“征友需求卡”:128 个格子,每格填一个数字,代表“喜爱户外程度”“预算敏感度”这类看不见摸不着的特质。
两座塔的输出维度必须一致,因为最终要靠内积算契合度——两张卡片逐格相乘再加总,得到一个分数。公式中 \(\mathbf{u}\) 是用户向量,\(\mathbf{v}_i\) 是第 \(i\) 个物品向量,分数越高,表示红娘越看好这对。要注意:除了最后这一下点积,用户塔和物品塔之间没有任何交互,这正是“双塔”与精排模型最大的区别。
有了标准卡,红娘把“几百万次深度面谈”降维成“几百万次数字比对”。速度问题是解决了,可卡片怎么填才准?几百万张卡怎么快速翻?这就是后面几节要展开的答案。
- 用户向量用户塔把用户侧特征编码成一个 \(d\) 维的稠密向量,相当于会员的数字版征友需求卡。
- 物品向量物品塔把物品侧特征也编码成一个同样 \(d\) 维的向量,相当于候选人的标准资料卡。
- 内积打分把两张卡的每一格数字相乘后累加,最终得到一个标量契合度分数,作为召回的排序依据。
用户塔编码
红娘给会员发“征友需求卡”时,会引导他填三类信息:你是谁(年龄、职业、城市)、你以前处过什么样的人(历史浏览、点击、购买记录)、你最近的状态(时间、地点、当前场景)。双塔里的用户塔,输入正是这三类特征的数字化版本。
用户塔的输入通常是一串稀疏特征:用户 ID、历史点击的物品 ID 序列、品类偏好、各种统计特征。稀疏特征不能直接喂给神经网络,要先经过 Embedding 层“查表”——每个 ID 在词表里查到自己对应的一小段稠密向量,相当于把“东北人”“爱看篮球”翻译成一组数字,让机器能理解。
查表得到的多个 embedding 会拼成一个长向量,再送入多层 MLP 做非线性变换。MLP 的作用是把这些基础信号揉合成一个综合画像,最终输出用户向量 \(\mathbf{u}\)。公式中 \(e_{\text{id}}\) 是用户 ID 的 embedding,\(e_{c_1}\cdots e_{c_m}\) 是行为序列里各物品的 embedding,\(\mathbf{x}_{\text{stat}}\) 是统计特征。
红娘视角:需求卡填得越全、做卡工艺越好,后面匹配越准。用户塔的原理不过如此——把散落的历史行为,浓缩成一行可以拿去比对的数字。
- Embedding 查表用户 ID、行为序列中的物品 ID 都是离散稀疏特征,每个 ID 在词表中查回自己的稠密向量。
- 特征拼接把 ID 的 embedding、行为序列的 embedding 和统计特征横向拼接成一个长向量。
- MLP 变换拼接向量经过多层全连接与激活函数,输出压缩后的用户向量 \(\mathbf{u}\)。
物品塔编码
光有需求卡还不够,候选人那边也得有同款资料卡。物品塔负责制作这张卡:一件商品,输入它的 ID、类目、标题关键词、价格区间、以及历史点击率等统计特征,输出一个和用户向量同维度的物品向量 \(\mathbf{v}_i\)。
物品塔的网络结构和用户塔可以不同——比如物品侧特征更稀疏,可以先用更宽的 embedding 层;用户侧行为序列长,可以用 pooling 压缩。两塔参数也不共享,各自独立训练。唯一要求是输出维度一致,保证最后能点积。
与用户塔最大的不同是:物品塔绝不读取任何用户信息。它不知道“谁正在看”,只知道“我是一件什么样的物品”。这个“守规矩”的设计保证了两侧编码的解耦——物品向量可以离线全部算好存起来,用户来了直接比对。
红娘的档案室里,几百万张候选人卡提前印制、格式统一。无论谁进门,都能秒查。物品塔的价值就在这里:把“人择物”变成了“卡片比卡片”。
- 物品特征输入物品 ID、类目、标题关键词等离散特征,外加价格、点击率等统计特征。
- 独立 MLP 编码物品侧网络结构可与用户塔不同、参数不共享,输出仍为 \(d\) 维向量。
- 维度对齐物品塔输出维度必须等于用户塔维度,才能计算内积,这是两侧“同模板”的硬约束。
向量检索方法
卡片都备齐了,新的问题来了:几百万张卡,一张一张比,红娘翻到下班也翻不完。最朴素的方法是暴力全量计算:把用户向量和每个物品向量都做一次内积,选分数最高的 \(K\) 个。复杂度 \(O(N)\),\(N\) 是物品总量,上亿时就扛不住了。
于是有了近似最近邻(ANN)检索:提前把物品向量组织成索引结构(如 HNSW、Faiss 里的 IVF-PQ),查询时只需沿着索引走几条路径,就能找到大概率最相似的向量,复杂度降到 \(O(\log N)\)。注意“近似”二字——它不保证绝对最优,但对召回来说足够了。
召回阶段真正要算的是 Top-K:在所有物品里取内积分数最高的 \(K\) 个。只是工程上不用真的全算,而是借助 ANN 索引近似完成。公式里 \(\mathcal{T}_{K}(u)\) 表示对用户 \(u\) 召回出的 \(K\) 个物品集合。
红娘把档案室升级成按契合度组织的“分层书架”:不用从头翻到尾,先看最可能合适的那层书架,就能快速报出十几个候选人。书架就是索引,翻书的技巧就是 ANN。
- 候选集合所有物品向量构成候选库 \(\mathcal{I}\),对应档案室里全部资料卡。
- 相似度打分把用户向量与每个候选物品向量做内积,衡量两张卡的“合拍程度”。
- 取前 K 个对分数排序,保留最大的 \(K\) 个作为召回结果,交给后续精排。
负采样训练
红娘培训新人时,光看成功案例是不够的——你得知道什么样的“不合适”,才懂得分辨。推荐系统也一样:我们有的是用户点击/购买记录(正样本),却几乎没有“用户明确不喜欢”的负样本。如果只拿正样本训练,模型会觉得啥都合适。
负采样就是在每个正样本旁边,随机抽 \(k\) 个物品当“反面教材”:比如用户点击了一件黑 T 恤,我们随机抽几件完全不相干的商品,贴上“负号”一起喂给模型。模型的目标很朴素:让正样本的内积分数尽量高,让负样本的分数尽量低。
实际工程里常用 Batch 内负采样:把同一批次里其他用户点击的物品,当作当前用户的负样本,省去额外采样开销。还有一个坑:纯随机采样会偏向热门物品,导致模型对热门物品打分偏低,于是有了 logQ 修正这类手段,按采样概率给分数做补偿。
红娘带徒弟的口诀是:“见一对,泼三盆冷水。”多看看不合适的样本,眼力才练得出来。负采样,就是给模型泼冷水的训练法。
- 正样本项用户点击过的物品是正样本,让它的内积分数经过 sigmoid 后尽量接近 1。
- 负样本项随机采样的 \(k\) 个负物品,让它们的分数取负后经过 sigmoid 尽量接近 1,也就是原始分数尽量低。
- 联合最小化用梯度下降同时优化两项,正样本分数被拉高、负样本分数被压低,模型学会分辨好坏。
模型优势分析
为什么双塔能成为召回标配?一句话:它把“深算”换成了“浅查”。深算是每来一个请求,都把用户跟几亿物品绑在一起做深度模型计算;浅查是物品这边早就离线算好,用户来了只算一次自己的卡片,然后直接去翻索引。
在线成本低是最大的红利。线上只需要对当前用户做一次前向传播(\(O(d)\)),拿到用户向量;物品向量全部提前存在索引里。配合 ANN,单次召回通常能压到 10 毫秒以内。这个速度是普通精排模型做不到的。
扩展性也强:物品量从百万涨到上亿,线上延迟几乎不变,因为瓶颈只在索引重建而不在请求路径;想提高召回率,就加特征、加深塔、调负采样,架构纹丝不动。这就是“标配”的底气。
红娘语录:真正的效率不是把翻卡片练得多快,而是把大部分卡片提前分好类、放上书架。双塔赢在“离线多干活,在线少干活”。
- 用户编码耗时线上对单个用户特征做一次前向计算,复杂度只与向量维度 \(d\) 有关。
- ANN 检索耗时在索引中查找近似最近邻,复杂度与索引层次相关,呈对数增长。
- 总延迟两段相加,物品再多也只是 \(\log N\) 缓慢增长,这是“离线多干活、在线少干活”的收益。
部署上线实践
婚介所开业总动员:档案室每晚会把新人资料卡全部印制一遍、上架归类;白天会员随来随查。双塔上线也遵循同一口诀——“物品离线备,用户在线算,索引定期建,效果天天看。”
离线环节:用训练好的物品塔,对全量物品跑一遍推理,得到所有物品向量,写入向量检索库(Faiss/HNSW)。这一步可以定时做,比如每 10 分钟或每天增量更新,保证新品及时入库。
在线环节:请求到来时实时算出用户向量,走一次 ANN 检索拿到 Top-K,交给精排和重排。配套监控要盯召回率、覆盖率、新鲜度和 p99 延迟。
红娘几十年的经验浓缩成一句话:好服务不是现场手忙脚乱,而是提前把功课做足。双塔部署的秘密,就是“备好卡、快翻册、勤更新”九个字。
- 离线推理用训练好的物品塔对全量物品计算向量,提前备好所有“候选人资料卡”。
- 索引更新把新增或变化的向量写入索引,并定期重建,保证新鲜度。
- 线上查询用户向量实时编码后,直接走 ANN 索引得到 Top-K 召回结果。
AI 应用场景
电商商品召回
阶段:召回(离线向量预计算 + 在线 ANN 检索)
解决的问题:电商平台商品动辄上亿,逐商品做深度打分延迟不可接受;传统规则召回(协同过滤、热度)覆盖不全、个性化弱。双塔把用户和商品分别编码成向量,离线算好商品向量建索引,线上用户请求毫秒级捞出 Top-K,把精排成本从亿级砍到几百。
具体案例:某电商大促高峰期,算法团队用双塔模型每日离线更新 2 亿商品向量到 Faiss 索引,用户打开 App 的瞬间实时计算用户向量,10ms 内召回 Top500 商品,再交给精排模型交叉打分。上线后召回页点击率提升 8%,系统高峰期 p99 延迟仍保持在 50ms 以内。
短视频信息流召回
阶段:召回 + 在线流式更新
解决的问题:短视频库存大、新鲜度要求高,新视频要在几分钟内获得曝光机会。双塔物品塔可对新增视频即时编码入库,用户塔结合实时点击行为更新用户向量,用向量检索捕捉最新兴趣变化。
具体案例:某短视频 App 的内容池每秒涌入数百条新视频,系统用物品塔对新视频实时生成向量并增量写入 HNSW 索引,用户塔结合实时点击行为更新用户向量,混合召回后进入粗排。新视频平均冷启动时间从 2 小时缩短到 10 分钟。
广告候选召回
阶段:召回(约束条件下候选筛选)
解决的问题:广告系统候选池巨大且有预算、定向约束,双塔可对广告主与流量位分别建塔,先粗筛出可能有意向的广告候选,再进入精排与竞价排序。
具体案例:某信息流广告平台用双塔做“流量-广告”匹配召回,广告塔离线编码广告素材与定向条件,请求到来时实时编码流量侧上下文,结合预算过滤快速召回 Top300 广告,再交给 CTR 精排模型。相比纯规则召回,CTR 提升 12%,同时大幅降低精排计算压力。
Python 演示
import numpy as np np.random.seed(42) N_USERS, N_ITEMS, D, K = 200, 1500, 16, 16NEG, EPOCHS, LR = 4, 30, 0.05 users = np.random.randn(N_USERS, D)items = np.random.randn(N_ITEMS, D)Wu, bu = 0.1 * np.random.randn(D, K), np.zeros(K)Wi, bi = 0.1 * np.random.randn(D, K), np.zeros(K) # 模拟每个用户点击过5个物品 构成正样本pos_pairs = {u: [(u * 7 + i) % N_ITEMS for i in range(5)] for u in range(N_USERS)} def sigmoid(x): return 1.0 / (1.0 + np.exp(-x)) def user_tower(x): return np.tanh(x @ Wu + bu) def item_tower(x): return np.tanh(x @ Wi + bi) for epoch in range(EPOCHS): loss = 0.0 dWu = np.zeros_like(Wu) dbu = np.zeros_like(bu) dWi = np.zeros_like(Wi) dbi = np.zeros_like(bi) for u in range(N_USERS): xu = users[u] u_vec = user_tower(xu) for p in pos_pairs[u]: v_pos = item_tower(items[p]) s_pos = float(u_vec @ v_pos) loss += -np.log(sigmoid(s_pos)) g_pos = sigmoid(s_pos) - 1.0 g_u = g_pos * v_pos g_v = g_pos * u_vec for n in np.random.choice(N_ITEMS, NEG, replace=False): v_neg = item_tower(items[n]) s_neg = float(u_vec @ v_neg) loss += -np.log(sigmoid(-s_neg)) g_n = sigmoid(s_neg) g_u += g_n * v_neg dWi += np.outer(items[n], g_n * u_vec * (1.0 - v_neg ** 2)) dbi += g_n * u_vec * (1.0 - v_neg ** 2) dWu += np.outer(xu, g_u * (1.0 - u_vec ** 2)) dbu += g_u * (1.0 - u_vec ** 2) dWi += np.outer(items[p], g_v * (1.0 - v_pos ** 2)) dbi += g_v * (1.0 - v_pos ** 2) Wu -= LR * dWu / N_USERS bu -= LR * dbu / N_USERS Wi -= LR * dWi / N_USERS bi -= LR * dbi / N_USERS if epoch % 5 == 0: print("epoch %d loss %.3f" % (epoch, loss / N_USERS)) def hit_rate(u, topk=10): u_vec = user_tower(users[u]) scores = np.tanh(items @ Wi + bi) @ u_vec top = np.argsort(-scores)[:topk] return len(set(pos_pairs[u]) & set(top)) / 5.0 print("HitRate@10 = %.3f" % np.mean([hit_rate(u) for u in range(N_USERS)]))
Wu, bu = 0.1 * np.random.randn(D, K), np.zeros(K)随机初始化用户塔参数,让网络从零开始学。def user_tower(x): return np.tanh(x @ Wu + bu)用户塔:线性变换加 tanh 激活,把用户特征压成 16 维向量。pos_pairs = {u: [(u * 7 + i) % N_ITEMS for i in range(5)] for u in range(N_USERS)}模拟每个用户点击过 5 个物品,作为训练正样本。for p in pos_pairs[u]:遍历该用户的正样本,逐条计算损失并累积梯度。for n in np.random.choice(N_ITEMS, NEG, replace=False):随机抽 4 个负样本物品,这是负采样核心代码。loss += -np.log(sigmoid(-s_neg))负样本项的损失:让负样本的分数取负后 sigmoid 尽量大,也就是分数尽量低。dWi += np.outer(items[n], g_n * u_vec * (1.0 - v_neg ** 2))用外层积计算物品塔参数的梯度,反向传播更新负样本路径。Wu -= LR * dWu / N_USERS用全量累积的平均梯度更新用户塔,对应一个 epoch 的 SGD。scores = np.tanh(items @ Wi + bi) @ u_vec离线把全量物品向量算好,再与用户向量打分——这就是部署时的“物品向量预计算”。
常见误区
误区双塔模型效果这么好,直接把它放进精排阶段,把召回和排序一起做了不就行了?我在线上试过,TopK 结果不够准,是不是我特征没加够?
正解双塔为了支持大规模召回,刻意让用户塔和物品塔在模型内部零交互,只保留最后的内积打分,牺牲了用户与物品之间的深层特征交叉。精排阶段候选只有几百个,可以放心使用交叉特征和更重的模型。把双塔当精排,属于“让短跑选手跑马拉松”——擅长快,不代表擅长细。
为何常见:双塔和精排都是神经网络打分模型,外表相似,容易误以为能力可以通用;忽略了召回与排序对速度、交叉能力的不同要求。
误区用户塔和物品塔应该用一模一样的网络结构,参数也共享,不然两边编码风格不一致,内积就没意义了。
正解双塔只要求输出向量维度一致、能够做内积,并不要求网络结构相同,更不共享参数。用户特征是 ID + 行为序列为主,物品特征是属性 + 内容为主,两塔完全可以各自定制结构。好比男方简历和女方简历模板都有一项“常住城市”,但填写指引不必完全一样。
为何常见:看到“塔”这个对称词,误以为是镜像结构;其实“塔”强调的是独立编码,不是结构对称。
误区负采样很简单啊,全库随机抽就行,抽得越多模型学得越充分,不用管什么采样分布。
正解随机采样确实简单,但物品在曝光和点击上呈长尾分布,纯随机会让热门物品总被当负例,导致模型对热门物品打分偏低。实践中需要调节正负比例、按频次采样,或用 logQ 修正补偿采样偏置。负样本不是多多益善,质量与分布才是关键。
为何常见:训练时负样本数量确实在增加,容易产生“加了就有效”的错觉;但样本分布改变的副作用往往被忽略。
误区召回是漏斗第一道门,漏掉就全完了,所以检索必须精确计算,一个都不能错。
正解召回的目标是“大概率把对的放进 Top-K”,后面的精排会重新打分,所以允许 ANN 近似的少量误差。追求精确全量计算会让延迟暴涨,反而拖垮整条链路。工程上常用 HNSW 这类近似索引,在召回率与延迟之间取平衡。
为何常见:把召回误当成最终打分,担心漏检一票否决;不理解漏斗结构里“每层承担不同任务”的分工。
面试题
Q1. 双塔模型中,用户塔和物品塔在结构上的关系是?
- A.结构独立但输出维度一致← 正确答案
- B.结构独立且输出维度随意
- C.结构共享且输出维度一致
- D.结构共享且输出维度随意
答案:A。双塔各自独立编码,参数不共享;但输出维度必须一致,否则无法计算内积。因此“独立但同维”是对的。维度随意会导致点积维度不匹配,结构共享则违背了“两塔分离”的设计初衷。
Q2. 双塔模型的最终匹配分数是如何计算的?
- A.用户向量与物品向量点积← 正确答案
- B.用户向量与物品向量拼接
- C.用户向量与物品向量取平均
- D.用户向量与物品向量做卷积
答案:A。双塔唯一的交互是最后的点积(内积),逐位相乘再求和。拼接、取平均是特征融合的常见手段,但那是精排模型的做法;双塔为了可检索性,刻意把交互限制在点积上。
Q3. 双塔模型的物品塔输入特征,通常不包含以下哪项?
- A.物品ID与属性特征
- B.物品类目与画像特征
- C.用户历史点击序列← 正确答案
- D.物品标题与内容特征
答案:C。物品塔只输入物品侧特征;用户历史点击序列属于用户侧信息,若放进物品塔就破坏了“两侧隔离”的设计,等于在塔间偷偷做了特征交叉。这是双塔与精排的本质区别之一。
Q4. 以下哪项是双塔模型适合召回阶段的主要原因?
- A.可以提前离线算好物品向量← 正确答案
- B.可以实时训练全部模型参数
- C.可以在塔间建模特征交叉
- D.可以彻底免除物品特征输入
答案:A。物品向量可离线算好并建索引,线上只算用户向量加 ANN 检索,这是召回快的根本。实时训练全量参数不现实;塔间交叉是精排的职责;没有特征输入模型就无法编码物品。
Q5. ANN 向量检索相比暴力全量计算,主要特点是?
- A.牺牲少量精度换取大幅提速← 正确答案
- B.牺牲大量速度换取更高精度
- C.保证绝对精度且速度不变
- D.完全精确且比暴力检索更快
答案:A。ANN 是近似最近邻检索,用少量精度损失换来从 \(O(N)\) 到 \(O(\log N)\) 的提速。召回阶段允许近似误差,因为后续精排会重新打分兜底。其他选项不符合 ANN 的工程权衡。
Q6. logQ 修正(采样偏置修正)的主要作用是?
- A.修正负样本的采样偏置← 正确答案
- B.提升正样本的采样数量
- C.减少用户侧的输入特征
- D.增加物品塔的网络层数
答案:A。随机负采样会让热门物品频繁出现在负样本中,导致模型对热门物品打分偏低。logQ 修正按采样概率对分数做补偿,消除这种偏置。它与正样本数量、用户特征、网络层数无关。
Q7. 关于负样本选择,下列说法正确的有?
- A.随机负采样实现简单← 正确答案
- B.负样本条数可以调节← 正确答案
- C.负样本越多效果一定越好
- D.采样分布可做偏置修正← 正确答案
答案:ABD。随机负采样简单有效,负样本数量是超参可以调,采样分布有偏置时可用 logQ 等方式修正。“越多越好”是常见误区——负样本的质量与比例同样重要,过多或分布失衡都会损害模型。
Q8. 下列哪些属于双塔模型的优点?
- A.物品向量可离线复用← 正确答案
- B.线上只需编码用户特征← 正确答案
- C.用户塔与物品塔充分交叉
- D.能够支撑上亿物品召回← 正确答案
答案:ABD。双塔的核心优势是物品向量离线预计算、线上只算用户向量、配合 ANN 支撑海量召回。C 是误解——双塔刻意不做塔间交叉,交叉是精排模型的工作。
Q9. 双塔模型训练时的正样本通常来自哪些?
- A.用户点击过的物品← 正确答案
- B.用户购买过的物品← 正确答案
- C.随机抽样得到的物品
- D.用户忽略过的物品
答案:AB。正样本来自用户真实的正向反馈行为,如点击、购买、观看完成。随机抽样得到的物品和用户忽略过的物品应作为负样本,用来教会模型“什么不该推荐”。
Q10. 双塔模型最核心的召回思想可以概括为?
- A.分开编码再快速匹配← 正确答案
- B.联合编码再做精细排序
- C.聚类合并再做全局打分
- D.随机采样再做人工审核
答案:A。双塔的精华就是“分开编码、快速匹配”:用户塔和物品塔各自独立编码成向量,用内积和 ANN 检索快速匹配。联合编码是精排思路,聚类合并与随机采样都不是双塔的核心机制。