想象你是一家拥有百万会员的婚介所老板。每位新人进门,你都得在几秒钟内从几百万张候选人卡片里,挑出几十位“可能合得来”的介绍给他。老办法是一对一深聊——聊得越细越准,可一天最多聊几十人。金牌红娘想了个主意:先让会员填一张标准化的“征友需求卡”,再把所有候选人也提前做成同款“资料卡”。你来的时候,红娘只需快速比对卡片上的数字,就能筛出最可能心动的一小撮人,剩下的深度撮合工作,再交给恋爱顾问慢慢做。推荐系统里的双塔模型,就是这位金牌红娘的手艺:一座塔把用户压成一张数字卡(用户向量),一座塔把每个物品也压成一张同格式数字卡(物品向量),两张卡用“内积”算契合度,再用向量检索在千万卡片里毫秒级捞出 Top-K。这篇就用婚介所的故事,把双塔的架构、编码、检索、训练和部署一次讲透。

岗位关联

双塔模型是召回阶段第一道闸门:把“逐一对聊”变成“卡片快筛”。学会它,你就掌握了海量候选毫秒级捞人的基本功——这是推荐、搜索、广告工程师的必备技能。

目录
  1. 可视化演示
  2. 双塔架构设计
  3. 用户塔编码
  4. 物品塔编码
  5. 向量检索方法
  6. 负采样训练
  7. 模型优势分析
  8. 部署上线实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示双塔架构设计、用户物品编码、向量检索、负采样训练与在线部署的交互式动画。

本节配有交互动效(进入视口后自动加载,约 3KB)。

双塔模型架构设计 用户塔和物品塔分别将特征编码为固定维度向量 两塔输出向量通过内积或余弦相似度计算匹配度 sim(u,i) = dot(user_tower(u), item_tower(i)) 双塔解耦设计实现离线预计算和高效在线检索

双塔架构设计

把推荐系统想象成婚介所:用户是前来征婚的会员,物品是堆积如山的候选人卡片。老式算法像一位耐心的红娘,每服务一位会员,都要把“会员 + 候选人”捆绑在一起,进行一场深度面谈——这种做法的优点是聊得细,缺点是几百万候选人根本聊不过来。金牌红娘决定改革:把“面谈”拆成“分别填卡 + 快速比对”两个环节。

双塔模型就是这套改革的产物。它由两座神经网络“塔”组成:用户塔只负责读用户侧信息,物品塔只负责读物品侧信息,两座塔在模型内部老死不相往来,各自把输入压缩成一个固定长度的向量。向量可以理解为数字版“征友需求卡”:128 个格子,每格填一个数字,代表“喜爱户外程度”“预算敏感度”这类看不见摸不着的特质。

两座塔的输出维度必须一致,因为最终要靠内积算契合度——两张卡片逐格相乘再加总,得到一个分数。公式中 \(\mathbf{u}\) 是用户向量,\(\mathbf{v}_i\) 是第 \(i\) 个物品向量,分数越高,表示红娘越看好这对。要注意:除了最后这一下点积,用户塔和物品塔之间没有任何交互,这正是“双塔”与精排模型最大的区别。

有了标准卡,红娘把“几百万次深度面谈”降维成“几百万次数字比对”。速度问题是解决了,可卡片怎么填才准?几百万张卡怎么快速翻?这就是后面几节要展开的答案。

score(u,i)=u⊤vi=∑k=1duk⋅vi,k\displaystyle \text{score}(u,i) = \mathbf{u}^{\top}\mathbf{v}_{i} = \sum_{k=1}^{d} \mathbf{u}_k \cdot \mathbf{v}_{i,k}
这个公式说的是:用户塔输出的向量和物品塔输出的向量,逐位相乘再全部加起来,得到一个标量分数。分数越高,代表这一对越“有戏”。
  1. 用户向量用户塔把用户侧特征编码成一个 \(d\) 维的稠密向量,相当于会员的数字版征友需求卡。
    u=fθ(user features)\displaystyle \mathbf{u} = f_{\theta}(\text{user features})
  2. 物品向量物品塔把物品侧特征也编码成一个同样 \(d\) 维的向量,相当于候选人的标准资料卡。
    vi=gϕ(item features)\displaystyle \mathbf{v}_{i} = g_{\phi}(\text{item features})
  3. 内积打分把两张卡的每一格数字相乘后累加,最终得到一个标量契合度分数,作为召回的排序依据。
    ∑k=1duk⋅vi,k\displaystyle \sum_{k=1}^{d} \mathbf{u}_k \cdot \mathbf{v}_{i,k}

用户塔编码

红娘给会员发“征友需求卡”时,会引导他填三类信息:你是谁(年龄、职业、城市)、你以前处过什么样的人(历史浏览、点击、购买记录)、你最近的状态(时间、地点、当前场景)。双塔里的用户塔,输入正是这三类特征的数字化版本。

用户塔的输入通常是一串稀疏特征:用户 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}}\) 是统计特征。

红娘视角:需求卡填得越全、做卡工艺越好,后面匹配越准。用户塔的原理不过如此——把散落的历史行为,浓缩成一行可以拿去比对的数字。

u=MLP([ eid; ec1; ⋯ ; ecm; xstat ])\displaystyle \mathbf{u} = \mathrm{MLP}\big( [\, e_{\text{id}} ;\ e_{c_1} ;\ \cdots ;\ e_{c_m} ;\ \mathbf{x}_{\text{stat}} \,] \big)
这条公式表示“填卡流程”:先把各类离散 ID 查表变成稠密向量,再拼接成一张长表,最后用多层神经网络提炼成一行浓缩数字。
  1. Embedding 查表用户 ID、行为序列中的物品 ID 都是离散稀疏特征,每个 ID 在词表中查回自己的稠密向量。
    eu=Euser[u]\displaystyle e_u = E_{\text{user}}[u]
  2. 特征拼接把 ID 的 embedding、行为序列的 embedding 和统计特征横向拼接成一个长向量。
    [ eid; ec1; ⋯ ; ecm; xstat ]\displaystyle [\, e_{\text{id}} ;\ e_{c_1} ;\ \cdots ;\ e_{c_m} ;\ \mathbf{x}_{\text{stat}} \,]
  3. MLP 变换拼接向量经过多层全连接与激活函数,输出压缩后的用户向量 \(\mathbf{u}\)。
    u=MLP(z)\displaystyle \mathbf{u} = \mathrm{MLP}(z)

物品塔编码

光有需求卡还不够,候选人那边也得有同款资料卡。物品塔负责制作这张卡:一件商品,输入它的 ID、类目、标题关键词、价格区间、以及历史点击率等统计特征,输出一个和用户向量同维度的物品向量 \(\mathbf{v}_i\)。

物品塔的网络结构和用户塔可以不同——比如物品侧特征更稀疏,可以先用更宽的 embedding 层;用户侧行为序列长,可以用 pooling 压缩。两塔参数也不共享,各自独立训练。唯一要求是输出维度一致,保证最后能点积。

与用户塔最大的不同是:物品塔绝不读取任何用户信息。它不知道“谁正在看”,只知道“我是一件什么样的物品”。这个“守规矩”的设计保证了两侧编码的解耦——物品向量可以离线全部算好存起来,用户来了直接比对。

红娘的档案室里,几百万张候选人卡提前印制、格式统一。无论谁进门,都能秒查。物品塔的价值就在这里:把“人择物”变成了“卡片比卡片”。

vi=MLP([ eid; ea1; ⋯ ; ean; xstat ])\displaystyle \mathbf{v}_{i} = \mathrm{MLP}\big( [\, e_{\text{id}} ;\ e_{a_1} ;\ \cdots ;\ e_{a_n} ;\ \mathbf{x}_{\text{stat}} \,] \big)
这条公式和用户塔几乎同构,只是输入换成了物品侧特征。它强调的是一种“规规矩矩”:物品卡独立制作,不掺入任何用户信息。
  1. 物品特征输入物品 ID、类目、标题关键词等离散特征,外加价格、点击率等统计特征。
    [ eid; ea1; ⋯ ; ean; xstat ]\displaystyle [\, e_{\text{id}} ;\ e_{a_1} ;\ \cdots ;\ e_{a_n} ;\ \mathbf{x}_{\text{stat}} \,]
  2. 独立 MLP 编码物品侧网络结构可与用户塔不同、参数不共享,输出仍为 \(d\) 维向量。
    vi=MLP(zi)\displaystyle \mathbf{v}_{i} = \mathrm{MLP}(z_i)
  3. 维度对齐物品塔输出维度必须等于用户塔维度,才能计算内积,这是两侧“同模板”的硬约束。
    dim⁡(u)=dim⁡(vi)\displaystyle \dim(\mathbf{u}) = \dim(\mathbf{v}_{i})

向量检索方法

卡片都备齐了,新的问题来了:几百万张卡,一张一张比,红娘翻到下班也翻不完。最朴素的方法是暴力全量计算:把用户向量和每个物品向量都做一次内积,选分数最高的 \(K\) 个。复杂度 \(O(N)\),\(N\) 是物品总量,上亿时就扛不住了。

于是有了近似最近邻(ANN)检索:提前把物品向量组织成索引结构(如 HNSW、Faiss 里的 IVF-PQ),查询时只需沿着索引走几条路径,就能找到大概率最相似的向量,复杂度降到 \(O(\log N)\)。注意“近似”二字——它不保证绝对最优,但对召回来说足够了。

召回阶段真正要算的是 Top-K:在所有物品里取内积分数最高的 \(K\) 个。只是工程上不用真的全算,而是借助 ANN 索引近似完成。公式里 \(\mathcal{T}_{K}(u)\) 表示对用户 \(u\) 召回出的 \(K\) 个物品集合。

红娘把档案室升级成按契合度组织的“分层书架”:不用从头翻到尾,先看最可能合适的那层书架,就能快速报出十几个候选人。书架就是索引,翻书的技巧就是 ANN。

TK(u)=top-Ki∈I  u⊤vi\displaystyle \mathcal{T}_{K}(u) = \underset{i \in \mathcal{I}}{\text{top-}K} \; \mathbf{u}^{\top}\mathbf{v}_{i}
这个公式描述召回目标:在全体物品集合 \(\mathcal{I}\) 中,挑出与用户向量内积最高的前 \(K\) 个物品。实现时用 ANN 索引加速,而不是真的全量算一遍。
  1. 候选集合所有物品向量构成候选库 \(\mathcal{I}\),对应档案室里全部资料卡。
    I={v1,v2,…,vN}\displaystyle \mathcal{I} = \{ \mathbf{v}_1, \mathbf{v}_2, \dots, \mathbf{v}_N \}
  2. 相似度打分把用户向量与每个候选物品向量做内积,衡量两张卡的“合拍程度”。
    u⊤vi\displaystyle \mathbf{u}^{\top}\mathbf{v}_{i}
  3. 取前 K 个对分数排序,保留最大的 \(K\) 个作为召回结果,交给后续精排。
    TK(u)=top-K({u⊤vi})\displaystyle \mathcal{T}_{K}(u) = \text{top-}K \big( \{\mathbf{u}^{\top}\mathbf{v}_{i}\} \big)

负采样训练

红娘培训新人时,光看成功案例是不够的——你得知道什么样的“不合适”,才懂得分辨。推荐系统也一样:我们有的是用户点击/购买记录(正样本),却几乎没有“用户明确不喜欢”的负样本。如果只拿正样本训练,模型会觉得啥都合适。

负采样就是在每个正样本旁边,随机抽 \(k\) 个物品当“反面教材”:比如用户点击了一件黑 T 恤,我们随机抽几件完全不相干的商品,贴上“负号”一起喂给模型。模型的目标很朴素:让正样本的内积分数尽量高,让负样本的分数尽量低。

实际工程里常用 Batch 内负采样:把同一批次里其他用户点击的物品,当作当前用户的负样本,省去额外采样开销。还有一个坑:纯随机采样会偏向热门物品,导致模型对热门物品打分偏低,于是有了 logQ 修正这类手段,按采样概率给分数做补偿。

红娘带徒弟的口诀是:“见一对,泼三盆冷水。”多看看不合适的样本,眼力才练得出来。负采样,就是给模型泼冷水的训练法。

L=−log⁡σ(u⊤v+)−∑j=1klog⁡σ(−u⊤vj−)\displaystyle \mathcal{L} = -\log \sigma\big(\mathbf{u}^{\top}\mathbf{v}^{+}\big) - \sum_{j=1}^{k} \log \sigma\big(-\mathbf{u}^{\top}\mathbf{v}_{j}^{-}\big)
这条损失函数是训练的目标函数:第一项让“用户喜欢的物品”得分越高越好,第二项让“随机抽的负样本”得分越低越好,两者合起来教会模型“见一对、泼三盆冷水”。
  1. 正样本项用户点击过的物品是正样本,让它的内积分数经过 sigmoid 后尽量接近 1。
    −log⁡σ(u⊤v+)\displaystyle -\log \sigma\big(\mathbf{u}^{\top}\mathbf{v}^{+}\big)
  2. 负样本项随机采样的 \(k\) 个负物品,让它们的分数取负后经过 sigmoid 尽量接近 1,也就是原始分数尽量低。
    ∑j=1k−log⁡σ(−u⊤vj−)\displaystyle \sum_{j=1}^{k} -\log \sigma\big(-\mathbf{u}^{\top}\mathbf{v}_{j}^{-}\big)
  3. 联合最小化用梯度下降同时优化两项,正样本分数被拉高、负样本分数被压低,模型学会分辨好坏。
    min⁡θ,ϕL\displaystyle \min_{\theta,\phi} \mathcal{L}

模型优势分析

为什么双塔能成为召回标配?一句话:它把“深算”换成了“浅查”。深算是每来一个请求,都把用户跟几亿物品绑在一起做深度模型计算;浅查是物品这边早就离线算好,用户来了只算一次自己的卡片,然后直接去翻索引。

在线成本低是最大的红利。线上只需要对当前用户做一次前向传播(\(O(d)\)),拿到用户向量;物品向量全部提前存在索引里。配合 ANN,单次召回通常能压到 10 毫秒以内。这个速度是普通精排模型做不到的。

扩展性也强:物品量从百万涨到上亿,线上延迟几乎不变,因为瓶颈只在索引重建而不在请求路径;想提高召回率,就加特征、加深塔、调负采样,架构纹丝不动。这就是“标配”的底气。

红娘语录:真正的效率不是把翻卡片练得多快,而是把大部分卡片提前分好类、放上书架。双塔赢在“离线多干活,在线少干活”。

Tquery≈Tencode(u)+TANN=O(d)+O(log⁡N)\displaystyle T_{\text{query}} \approx T_{\text{encode}}(u) + T_{\text{ANN}} = O(d) + O(\log N)
这条公式拆解了单次召回延迟:线上只需编码一次用户(\(O(d)\)),加上一次对数复杂度的 ANN 检索(\(O(\log N)\)),总耗时几乎与物品总量解耦。
  1. 用户编码耗时线上对单个用户特征做一次前向计算,复杂度只与向量维度 \(d\) 有关。
    Tencode(u)=O(d)\displaystyle T_{\text{encode}}(u) = O(d)
  2. ANN 检索耗时在索引中查找近似最近邻,复杂度与索引层次相关,呈对数增长。
    TANN=O(log⁡N)\displaystyle T_{\text{ANN}} = O(\log N)
  3. 总延迟两段相加,物品再多也只是 \(\log N\) 缓慢增长,这是“离线多干活、在线少干活”的收益。
    O(d)+O(log⁡N)\displaystyle O(d) + O(\log N)

部署上线实践

婚介所开业总动员:档案室每晚会把新人资料卡全部印制一遍、上架归类;白天会员随来随查。双塔上线也遵循同一口诀——“物品离线备,用户在线算,索引定期建,效果天天看。”

离线环节:用训练好的物品塔,对全量物品跑一遍推理,得到所有物品向量,写入向量检索库(Faiss/HNSW)。这一步可以定时做,比如每 10 分钟或每天增量更新,保证新品及时入库。

在线环节:请求到来时实时算出用户向量,走一次 ANN 检索拿到 Top-K,交给精排和重排。配套监控要盯召回率、覆盖率、新鲜度和 p99 延迟。

红娘几十年的经验浓缩成一句话:好服务不是现场手忙脚乱,而是提前把功课做足。双塔部署的秘密,就是“备好卡、快翻册、勤更新”九个字。

vi←gϕ(xi),∀i∈I,每间隔 Δt 更新索引\displaystyle \mathbf{v}_{i} \leftarrow g_{\phi}\big( x_{i} \big), \quad \forall i \in \mathcal{I}, \quad \text{每间隔 } \Delta t \text{ 更新索引}
这条公式描述上线流程:用物品塔对全量物品定期生成向量并写入索引,线上用户请求则走“实时编码 + 快速检索”,形成稳定的召回流水线。
  1. 离线推理用训练好的物品塔对全量物品计算向量,提前备好所有“候选人资料卡”。
    vi=gϕ(xi)\displaystyle \mathbf{v}_{i} = g_{\phi}\big( x_{i} \big)
  2. 索引更新把新增或变化的向量写入索引,并定期重建,保证新鲜度。
    index←rebuild({vi})\displaystyle \text{index} \leftarrow \text{rebuild}\big( \{\mathbf{v}_{i}\} \big)
  3. 线上查询用户向量实时编码后,直接走 ANN 索引得到 Top-K 召回结果。
    T(u)=ANN(u, index)\displaystyle \mathcal{T}(u) = \text{ANN}\big( \mathbf{u},\ \text{index} \big)

AI 应用场景

电商商品召回

阶段:召回(离线向量预计算 + 在线 ANN 检索)

解决的问题:电商平台商品动辄上亿,逐商品做深度打分延迟不可接受;传统规则召回(协同过滤、热度)覆盖不全、个性化弱。双塔把用户和商品分别编码成向量,离线算好商品向量建索引,线上用户请求毫秒级捞出 Top-K,把精排成本从亿级砍到几百。

具体案例:某电商大促高峰期,算法团队用双塔模型每日离线更新 2 亿商品向量到 Faiss 索引,用户打开 App 的瞬间实时计算用户向量,10ms 内召回 Top500 商品,再交给精排模型交叉打分。上线后召回页点击率提升 8%,系统高峰期 p99 延迟仍保持在 50ms 以内。

短视频信息流召回

阶段:召回 + 在线流式更新

解决的问题:短视频库存大、新鲜度要求高,新视频要在几分钟内获得曝光机会。双塔物品塔可对新增视频即时编码入库,用户塔结合实时点击行为更新用户向量,用向量检索捕捉最新兴趣变化。

具体案例:某短视频 App 的内容池每秒涌入数百条新视频,系统用物品塔对新视频实时生成向量并增量写入 HNSW 索引,用户塔结合实时点击行为更新用户向量,混合召回后进入粗排。新视频平均冷启动时间从 2 小时缩短到 10 分钟。

广告候选召回

阶段:召回(约束条件下候选筛选)

解决的问题:广告系统候选池巨大且有预算、定向约束,双塔可对广告主与流量位分别建塔,先粗筛出可能有意向的广告候选,再进入精排与竞价排序。

具体案例:某信息流广告平台用双塔做“流量-广告”匹配召回,广告塔离线编码广告素材与定向条件,请求到来时实时编码流量侧上下文,结合预算过滤快速召回 Top300 广告,再交给 CTR 精排模型。相比纯规则召回,CTR 提升 12%,同时大幅降低精排计算压力。

Python 演示

python72 行
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)]))
目的:从零训练一个简化版双塔模型——用户塔和物品塔各自把特征编码成向量,用内积打分,配合随机负采样做损失优化,最后用 HitRate@10 验证召回效果。
  1. Wu, bu = 0.1 * np.random.randn(D, K), np.zeros(K)随机初始化用户塔参数,让网络从零开始学。
  2. def user_tower(x): return np.tanh(x @ Wu + bu)用户塔:线性变换加 tanh 激活,把用户特征压成 16 维向量。
  3. pos_pairs = {u: [(u * 7 + i) % N_ITEMS for i in range(5)] for u in range(N_USERS)}模拟每个用户点击过 5 个物品,作为训练正样本。
  4. for p in pos_pairs[u]:遍历该用户的正样本,逐条计算损失并累积梯度。
  5. for n in np.random.choice(N_ITEMS, NEG, replace=False):随机抽 4 个负样本物品,这是负采样核心代码。
  6. loss += -np.log(sigmoid(-s_neg))负样本项的损失:让负样本的分数取负后 sigmoid 尽量大,也就是分数尽量低。
  7. dWi += np.outer(items[n], g_n * u_vec * (1.0 - v_neg ** 2))用外层积计算物品塔参数的梯度,反向传播更新负样本路径。
  8. Wu -= LR * dWu / N_USERS用全量累积的平均梯度更新用户塔,对应一个 epoch 的 SGD。
  9. scores = np.tanh(items @ Wi + bi) @ u_vec离线把全量物品向量算好,再与用户向量打分——这就是部署时的“物品向量预计算”。

常见误区

误区双塔模型效果这么好,直接把它放进精排阶段,把召回和排序一起做了不就行了?我在线上试过,TopK 结果不够准,是不是我特征没加够?

正解双塔为了支持大规模召回,刻意让用户塔和物品塔在模型内部零交互,只保留最后的内积打分,牺牲了用户与物品之间的深层特征交叉。精排阶段候选只有几百个,可以放心使用交叉特征和更重的模型。把双塔当精排,属于“让短跑选手跑马拉松”——擅长快,不代表擅长细。

为何常见:双塔和精排都是神经网络打分模型,外表相似,容易误以为能力可以通用;忽略了召回与排序对速度、交叉能力的不同要求。

误区用户塔和物品塔应该用一模一样的网络结构,参数也共享,不然两边编码风格不一致,内积就没意义了。

正解双塔只要求输出向量维度一致、能够做内积,并不要求网络结构相同,更不共享参数。用户特征是 ID + 行为序列为主,物品特征是属性 + 内容为主,两塔完全可以各自定制结构。好比男方简历和女方简历模板都有一项“常住城市”,但填写指引不必完全一样。

为何常见:看到“塔”这个对称词,误以为是镜像结构;其实“塔”强调的是独立编码,不是结构对称。

误区负采样很简单啊,全库随机抽就行,抽得越多模型学得越充分,不用管什么采样分布。

正解随机采样确实简单,但物品在曝光和点击上呈长尾分布,纯随机会让热门物品总被当负例,导致模型对热门物品打分偏低。实践中需要调节正负比例、按频次采样,或用 logQ 修正补偿采样偏置。负样本不是多多益善,质量与分布才是关键。

为何常见:训练时负样本数量确实在增加,容易产生“加了就有效”的错觉;但样本分布改变的副作用往往被忽略。

误区召回是漏斗第一道门,漏掉就全完了,所以检索必须精确计算,一个都不能错。

正解召回的目标是“大概率把对的放进 Top-K”,后面的精排会重新打分,所以允许 ANN 近似的少量误差。追求精确全量计算会让延迟暴涨,反而拖垮整条链路。工程上常用 HNSW 这类近似索引,在召回率与延迟之间取平衡。

为何常见:把召回误当成最终打分,担心漏检一票否决;不理解漏斗结构里“每层承担不同任务”的分工。

面试题

Q1. 双塔模型中,用户塔和物品塔在结构上的关系是?

单选题 · easy

  • A.结构独立但输出维度一致← 正确答案
  • B.结构独立且输出维度随意
  • C.结构共享且输出维度一致
  • D.结构共享且输出维度随意

答案:A。双塔各自独立编码,参数不共享;但输出维度必须一致,否则无法计算内积。因此“独立但同维”是对的。维度随意会导致点积维度不匹配,结构共享则违背了“两塔分离”的设计初衷。

Q2. 双塔模型的最终匹配分数是如何计算的?

单选题 · easy

  • A.用户向量与物品向量点积← 正确答案
  • B.用户向量与物品向量拼接
  • C.用户向量与物品向量取平均
  • D.用户向量与物品向量做卷积

答案:A。双塔唯一的交互是最后的点积(内积),逐位相乘再求和。拼接、取平均是特征融合的常见手段,但那是精排模型的做法;双塔为了可检索性,刻意把交互限制在点积上。

Q3. 双塔模型的物品塔输入特征,通常不包含以下哪项?

单选题 · mid

  • A.物品ID与属性特征
  • B.物品类目与画像特征
  • C.用户历史点击序列← 正确答案
  • D.物品标题与内容特征

答案:C。物品塔只输入物品侧特征;用户历史点击序列属于用户侧信息,若放进物品塔就破坏了“两侧隔离”的设计,等于在塔间偷偷做了特征交叉。这是双塔与精排的本质区别之一。

Q4. 以下哪项是双塔模型适合召回阶段的主要原因?

单选题 · mid

  • A.可以提前离线算好物品向量← 正确答案
  • B.可以实时训练全部模型参数
  • C.可以在塔间建模特征交叉
  • D.可以彻底免除物品特征输入

答案:A。物品向量可离线算好并建索引,线上只算用户向量加 ANN 检索,这是召回快的根本。实时训练全量参数不现实;塔间交叉是精排的职责;没有特征输入模型就无法编码物品。

Q5. ANN 向量检索相比暴力全量计算,主要特点是?

单选题 · mid

  • A.牺牲少量精度换取大幅提速← 正确答案
  • B.牺牲大量速度换取更高精度
  • C.保证绝对精度且速度不变
  • D.完全精确且比暴力检索更快

答案:A。ANN 是近似最近邻检索,用少量精度损失换来从 \(O(N)\) 到 \(O(\log N)\) 的提速。召回阶段允许近似误差,因为后续精排会重新打分兜底。其他选项不符合 ANN 的工程权衡。

Q6. logQ 修正(采样偏置修正)的主要作用是?

单选题 · extreme

  • A.修正负样本的采样偏置← 正确答案
  • B.提升正样本的采样数量
  • C.减少用户侧的输入特征
  • D.增加物品塔的网络层数

答案:A。随机负采样会让热门物品频繁出现在负样本中,导致模型对热门物品打分偏低。logQ 修正按采样概率对分数做补偿,消除这种偏置。它与正样本数量、用户特征、网络层数无关。

Q7. 关于负样本选择,下列说法正确的有?

多选题 · hard

  • A.随机负采样实现简单← 正确答案
  • B.负样本条数可以调节← 正确答案
  • C.负样本越多效果一定越好
  • D.采样分布可做偏置修正← 正确答案

答案:ABD。随机负采样简单有效,负样本数量是超参可以调,采样分布有偏置时可用 logQ 等方式修正。“越多越好”是常见误区——负样本的质量与比例同样重要,过多或分布失衡都会损害模型。

Q8. 下列哪些属于双塔模型的优点?

多选题 · hard

  • A.物品向量可离线复用← 正确答案
  • B.线上只需编码用户特征← 正确答案
  • C.用户塔与物品塔充分交叉
  • D.能够支撑上亿物品召回← 正确答案

答案:ABD。双塔的核心优势是物品向量离线预计算、线上只算用户向量、配合 ANN 支撑海量召回。C 是误解——双塔刻意不做塔间交叉,交叉是精排模型的工作。

Q9. 双塔模型训练时的正样本通常来自哪些?

多选题 · mid

  • A.用户点击过的物品← 正确答案
  • B.用户购买过的物品← 正确答案
  • C.随机抽样得到的物品
  • D.用户忽略过的物品

答案:AB。正样本来自用户真实的正向反馈行为,如点击、购买、观看完成。随机抽样得到的物品和用户忽略过的物品应作为负样本,用来教会模型“什么不该推荐”。

Q10. 双塔模型最核心的召回思想可以概括为?

单选题 · easy

  • A.分开编码再快速匹配← 正确答案
  • B.联合编码再做精细排序
  • C.聚类合并再做全局打分
  • D.随机采样再做人工审核

答案:A。双塔的精华就是“分开编码、快速匹配”:用户塔和物品塔各自独立编码成向量,用内积和 ANN 检索快速匹配。联合编码是精排思路,聚类合并与随机采样都不是双塔的核心机制。