在繁华都市的一角,有一家神秘的“双塔婚介所”。档案室里立着两座直通天花板的档案塔:左边塔里存着几亿份用户档案,右边塔里存着几亿份物品档案——电影、商品、文章、短视频。每天无数人在这里“相亲”(点击、购买、观看),而红娘必须在几十毫秒内从几百万候选人中挑出最可能成功的几段缘分。她不可能把每对都叫来当面深聊,于是立下一条规矩:每位用户和每件物品都填写同一套标准化“择偶量表”,红娘只用量表算出的“匹配度”来决定是否安排见面。 这套机制,正是深度推荐模型中的“双塔模型”DSSM(Deep Structured Semantic Models)。它最早由微软提出,用来解决搜索中 query 与文档的语义匹配,后来成了推荐系统召回阶段最经典的骨架。用户侧一个网络(用户塔)、物品侧一个网络(物品塔),两座塔把各自的原始特征映射进同一个“语义向量空间”,最后用一把余弦尺子量匹配度——就像红娘把两份不同风格的档案都翻成同一格式的量表,再直接比对。
对 AI 从业者而言,双塔模型是理解推荐系统“召回—排序”体系的第一块基石:看懂它,你就看懂了向量化检索的底层逻辑,也能顺利上手 YouTube DNN、Dual Encoder 等主流架构,直接参与广告与电商的召回工程。
目录
可视化演示
展示双塔模型DSSM核心架构:双塔模式演示用户塔与物品塔分别编码为Embedding向量;相似度模式展示点积与余弦相似度计算;检索模式展示Top-K近邻检索流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
双塔架构 用户塔与物品塔独立编码 emb=f(x) DNN编码后输出Embedding
模型背景:推荐系统的“海选”难题
婚介所早年只用“兴趣爱好关键词”硬匹配。男生档案写着“露营”,女生写着“户外运动”,系统逐词对比发现一个都对不上,就判定“不合适”。可资深红娘一眼看出这两人其实很般配——关键词对不上,语义上却是一路人。这就是推荐系统里著名的“语义鸿沟”:用户表达和物品描述往往用词不同,但语义相近。
推荐系统要解决的核心问题,是从海量物品中找出用户可能喜欢的东西。线上往往有几百万甚至几亿候选,精排模型再强也不可能逐一打分,所以系统把流程拆成“召回 → 粗排 → 精排”三个阶段。召回的目标不是精准,而是“快且别把对的漏掉”——从百万候选里先捞出几百个大概率相关的。DSSM(Deep Structured Semantic Models)最初由微软提出用于搜索语义匹配,恰好天然适配召回:它把“语义匹配”转化成“向量检索”,让“想找什么”和“有什么”在同一个空间里直接比。
就这样,婚介所的档案塔给每个候选都预写好了“打分小卡”,红娘服务新用户时只需翻卡片,不必重读几亿本档案。双塔的“海选”优势,从这里就埋下了伏笔。
- 用户编码把用户特征(年龄、历史点击、搜索词)送进用户塔,得到用户向量。
- 物品编码把每件物品特征(标题、类目、标签)送进物品塔,得到物品向量。
- 截断取K对所有候选计算余弦相似度,挑出分数最高的 K 个交给下游。
核心思想:两座塔,一张共享量表
介绍所最重要的发明是“标准择偶量表”。不管档案原本写得多么花哨——有人写小作文,有人只留三行字——填进量表后都必须变成同样格式的一串分数:[年龄契合度、收入水平、兴趣相投、生活习惯……]。这样任何两份档案都能直接比较,红娘不必理解每一份档案的文学风格。
双塔模型的结构正是如此。它由两个参数完全独立的前馈网络组成:左边是用户塔(Query Tower),输入用户特征;右边是物品塔(Document Tower),输入物品特征。两边的输入可以完全异构——用户侧是浏览历史、年龄、地域,物品侧是标题、图片标签、价格区间——但输出必须落在同一个 d 维向量空间里。所谓“共享空间”,指的是两座塔输出的每一维都遵循相同的语义约定:用户向量 u 的第二维和物品向量 v 的第二维表达同一个含义,所以它们可以直接点积、求夹角、算相似度。
从此,用户塔和物品塔成了婚介所的两位金牌“量表翻译官”——再花哨的原始描述,经它们之手都能变成同一把尺子量得动的数字。这套“各写各的、最后互比”的哲学,是双塔区别于其它深度学习模型的灵魂。
- 分子点积逐维相乘后求和,衡量两向量同向程度。
- 分母归一化除以各自的模长,消除“分数量级”的影响,只比较方向形状。
- 余弦值区间结果落在 [-1, 1],越接近 1 越般配。
网络结构:档案是怎么被“翻译”的
档案翻译不能一步到位。介绍所的工作流是:前台先登记原始资料(输入层),文字整理员把大段自述拆成标准标签(词哈希),资深红娘逐层提炼出“这个人最核心的择偶观”(多层全连接),最后浓缩成一张量表的最终打分(嵌入向量)。
DSSM 的每座塔是一个三层全连接网络。第一层输入通常是稀疏的“词袋”向量——用户搜过什么词、物品标题里有什么词,对应位置写 1。但如果直接用 one-hot 词表,维度会膨胀到几百万。为此 DSSM 引入了 Word Hashing:把每个词拆成字母三元组(letter-trigram)再哈希到固定大小的桶里。例如 “good“ 被拆成 ”#go goo ood od#”,投影到一个 3 万维的桶向量上。词表再大也不会爆炸,而且生词也能被拆成字母组合后落到部分重叠的桶里,顺带缓解了未登录词问题。隐藏层使用 ReLU 激活,逐层把高维稀疏输入压缩成低维稠密向量(经典实现常用 128 或 256 维)。
三层下来,花哨的档案被“翻译”成一行简洁的数字——这正是两座塔每天在做的苦活,也是它们能支撑百万级检索效率的基础。
- 第一层映射原始稀疏特征进入第一层全连接,ReLU 把关口,让有用信号通过。
- 第二层提炼在第一层结果上继续做线性变换与非线性激活,提取更抽象的组合特征。
- 第三层输出压缩成最终低维向量 y,用户塔与物品塔的 y 都落在共享向量空间里。
训练机制:红娘是怎么学会看人的
新入职的红娘当然不会算命。她的学习方式是复盘历史“成功案例”:小明最终和 A 女士牵手成功,那就拿“小明档案 + A 女士档案”当正面教材;再随手从档案柜里抽 N 个没见过面的女士当反面教材。反复比对之后,她慢慢调整自己对“量表各项权重”的理解——直到正面例子的匹配分总能排在所有反面例子前面。
这就是“负采样 + Softmax”的训练范式:对每个用户 Q,取他真实交互过的物品 D⁺ 作为正样本,随机采样 N 个未交互物品作为负样本。把用户向量分别与正负样本向量做余弦相似度(再乘一个放大系数 γ),经过 softmax 归一化成“用户选择各候选”的概率;训练目标就是最大化选中正样本的概率,等价于最小化负对数似然。实际工程里,人们还常用“批内负样本”(batch 中其它样本当负样本)来提升训练效率。
红娘正是在一次次“命中与落空”的复盘里练就了火眼金睛。训练结束时,两座塔的权重里就沉淀了“什么样的人与什么样的物品最般配”的全部经验。
- 正样本打分用户向量与正样本向量的余弦相似度乘上温度系数,作为分子。
- 负样本打分N 个随机负样本逐一计算余弦相似度并取指数,构成分母的一部分。
- 概率归一化正样本“得分”除以全体候选得分之和,得到用户选中正样本的概率。
- 负对数损失概率越接近 1 损失越趋近 0,梯度下降不断调整两座塔的权重。
双塔变体:同门师兄弟与拼桌对照
双塔的成功让介绍所开了连锁分店。有的分店把“电影”换成“短视频”,有的专做“问题与答案匹配”,还有一家分店坚持传统的“面对面拼桌面试”——把两个人拉到一张桌子上深聊。它们都是双塔家族的近亲或对照。
先说同门师兄弟。YouTube DNN 是 2016 年 YouTube 用双塔思想做视频召回的经典:用户侧输入观看历史、搜索历史、人口属性的 embedding 汇聚,物品侧输入视频 ID、频道、时长的 embedding,候选视频向量离线算好,用户请求时在向量空间里找近邻。对偶编码器(Dual Encoder)则是学术界给“双编码器 + 点积/余弦 + softmax 损失”这类框架起的统一名字,广泛用于密集检索、语义匹配与句子相似度任务。而拼接模型(Concat Model)是双塔的对照物:把用户特征与物品特征拼成一个长向量,送进同一个 MLP——相当于让双方同坐一桌,请资深红娘直接观察“化学反应”。其特征交叉能力强,但物品特征无法离线预计算,线上必须实时跑完整模型,速度远慢于双塔。
各分店的招牌虽不同,但“两塔分而治之”的祖传思想始终是看家本领——区别只在于,谁把塔建得更高,谁又悄悄开了张“拼桌业务”。
- 双塔打分两个塔独立编码后仅用余弦交互,物品侧因此可以离线缓存。
- 拼接输入把用户特征与物品特征首尾相连成一个更长的向量。
- 单塔打分拼接向量送入共享 MLP,特征在每一层充分交叉。
工程落地:从红娘大脑到相亲速配系统
红娘再能干,每天亲自翻档案也撑不住。介绍所的实际做法是:每天打烊后,把所有候选人的“量表打分小卡”全部预写好,按分数体系编成索引卡片柜;白天新用户一来,只需现场填一张用户表,然后飞速扫描卡片柜,抽出分数最高的几十张递过去。
双塔的工程链路同样分成“离线”与“在线”两段。离线阶段:把全量物品特征(标题、类目、标签、多模态表征)喂进物品塔,批量产出物品向量,写入近似近邻索引(如 FAISS、Annoy、HNSW)。在线阶段:用户请求到达后,实时组装用户特征 → 用户塔做一次前向 → 在 ANN 索引中查找与用户向量最接近的 Top-K 物品 → 与其它召回源合并后交给粗排与精排。因为物品向量全部离线预计算,线上只剩“一次小 MLP 前向 + 一次索引查询”,耗时通常只有几毫秒。模型更新也有讲究:用户侧特征时效性强(用户刚刚点击了什么),可以实时计算用户向量;物品侧向量则采用每天/每小时的定时全量刷新,配合增量更新保证索引一致性。
白天门庭若市,介绍所依旧从容——因为最难的计算早在打烊后的夜里完成,这正是双塔能支撑千万级日活流量的工程秘密。
- 离线预计算所有物品向量由物品塔一次性算出并缓存。
- 内积检索召回的本质是找与用户向量内积最大的 K 个物品向量。
- ANN 近似用近似近邻索引把逐个遍历的 O(N) 复杂度降到检索可接受的水平。
权衡取舍:双塔的强项与软肋
量表的发明大大提高了速配效率,却也留下一个遗憾:红娘再也看不到那些“只可意会”的化学反应。有位用户档案写着“喜欢安静”,另一位写着“喜欢弹琴”,量表给分不低;可如果双方都想找“话少的人”来互补,这种细致入微的匹配,双塔是看不见的。
先看强项:第一,物品向量离线预计算,线上成本极低;第二,百万量级候选毫秒级检索;第三,离线在线结构一致,训练与部署简单;第四,物品侧表征天然可复用、可扩展。再看软肋:最核心的问题是“信息瓶颈”——用户向量和物品向量直到最后的点积/余弦才发生交互,模型无法在早期建模用户与物品的高阶特征交叉;此外,每个用户或物品最终只被压缩成一个向量(单向量瓶颈),多兴趣会被平均稀释。因此双塔更适合粗放的海选,不适合需要精细交叉建模的精排环节。
聪明的介绍所因此把双塔只用在“海选”环节——两塔负责广撒网,精排模型负责深交心。认清双塔的边界,比追捧它的光环更重要。
- 线性内积最终分数只是各维度乘积之和,只表达线性交互。
- 缺失高阶交叉u 与 v 的逐元素乘积、拼接后的非线性组合,双塔都无法表达。
- 单向量表达每个用户/物品最终只得到一个向量,多兴趣被平均稀释。
AI 应用场景
电商“猜你喜欢”的候选召回
阶段:候选召回
解决的问题:电商商品动辄数亿,精排模型无法全量打分;传统标签匹配又忽略语义。双塔把用户行为序列与商品信息分别编码,离线预计算商品向量,线上毫秒级召回首屏候选。
具体案例:某电商 App 用双塔做召回:用户塔输入近 14 天点击/加购商品的 embedding 均值、价格敏感度;商品塔输入标题词哈希、类目、品牌。每晚构建 FAISS 索引,请求时取 Top-200,与协同过滤结果合并后进入精排。
广告定向召回
阶段:广告检索
解决的问题:DSP 需要实时从几十万广告中找出与当前用户兴趣匹配的创意;广告主数量大、创意更新频繁。双塔让广告离线向量化并建立索引,竞价前毫秒级产出候选。
具体案例:信息流广告平台用双塔:用户塔采用实时兴趣特征(近 1 小时点击的广告类目),广告塔采用创意标题词哈希、行业、出价档位。广告上线即算向量入索引,点击反馈驱动每日增量训练。
语义检索:问题与答案的匹配
阶段:召回/匹配
解决的问题:搜索场景中 query 与文档用词常常不重合,同一问题有上百种说法。Word Hashing 缓解了未登录词问题,双塔把 query 与 doc 放进共享空间,实现“语义级”匹配,这正是 DSSM 诞生的初衷。
具体案例:社区问答平台的“相似问题”检测:把问题标题做 trigram 哈希送入 query 塔,历史问题与最佳答案送入 doc 塔,离线构建索引,线上在几毫秒内召回相似问题,用于去重与相关问题推荐。
Python 演示
import torchimport torch.nn as nnimport torch.nn.functional as F VOCAB = 40HIDDEN = 48OUT_DIM = 16NEG_NUM = 4GAMMA = 5.0 class Tower(nn.Module): """一座塔:三层全连接加ReLU,把输入映射成低维向量""" def __init__(self, vocab=VOCAB, hidden=HIDDEN, out_dim=OUT_DIM): super().__init__() self.net = nn.Sequential( nn.Linear(vocab, hidden), nn.ReLU(), nn.Linear(hidden, hidden), nn.ReLU(), nn.Linear(hidden, out_dim), ) def forward(self, x): return self.net(x) class DSSM(nn.Module): """双塔模型:用户塔与物品塔参数完全独立""" def __init__(self): super().__init__() self.user_tower = Tower() self.item_tower = Tower() def encode(self, users, items): u = self.user_tower(users) v = self.item_tower(items) return u, v def dssm_loss(u, v_pos, v_negs, gamma=GAMMA): pos_score = F.cosine_similarity(u, v_pos, dim=1) * gamma neg_score = F.cosine_similarity(u.unsqueeze(1), v_negs, dim=2) * gamma logits = torch.cat([pos_score.unsqueeze(1), neg_score], dim=1) target = torch.zeros(u.size(0), dtype=torch.long) return F.cross_entropy(logits, target) def make_batch(batch_size, neg_num=NEG_NUM): base = torch.randint(0, 2, (batch_size, VOCAB)).float() neg = torch.randint(0, 2, (batch_size * neg_num, VOCAB)).float() return base, base, neg def main(): torch.manual_seed(0) model = DSSM() optimizer = torch.optim.Adam(model.parameters(), lr=0.01) for epoch in range(50): users, items_pos, items_neg = make_batch(32) u, v_pos = model.encode(users, items_pos) v_negs = model.item_tower(items_neg).view(32, NEG_NUM, OUT_DIM) loss = dssm_loss(u, v_pos, v_negs) optimizer.zero_grad() loss.backward() optimizer.step() if epoch % 10 == 0: print("epoch", epoch, "loss", round(loss.item(), 4)) eye = torch.eye(VOCAB) u, v = model.encode(eye, eye) sim = F.cosine_similarity(u, v, dim=1) print("平均自相似度:", round(float(sim.mean()), 3)) if __name__ == "__main__": main()
self.net = nn.Sequential(...)塔身本体:三层线性层穿插 ReLU,输入稀疏特征、输出低维向量self.user_tower = Tower()` 与 `self.item_tower = Tower()两座塔参数完全独立,分别编码用户与物品pos_score = F.cosine_similarity(u, v_pos, dim=1) * gamma正样本相似度乘以温度系数 γ,拉开分数差距neg_score = F.cosine_similarity(u.unsqueeze(1), v_negs, dim=2) * gamma对 N 个负样本批量计算余弦相似度logits = torch.cat([pos_score.unsqueeze(1), neg_score], dim=1)把正样本分数与负样本分数拼成打分向量v_negs = model.item_tower(items_neg).view(32, NEG_NUM, OUT_DIM)物品塔编码负样本后重排为 [batch, neg, dim] 形状
常见误区
误区我以为双塔模型就是把用户和物品的特征拼接在一起,然后喂给同一个神经网络,只不过输入时分成两段而已。
正解双塔的关键恰恰是“参数独立”。用户塔和物品塔是两个完全不同的网络,各有各的权重;训练时只是通过最后的相似度损失被间接拉近。这与“特征拼接进同一个 MLP”的单塔结构有本质差别:单塔特征交叉强但无法预计算物品向量,双塔牺牲交叉换取可离线缓存。就像两张档案表分别由两位不同顾问填写,而不是拼在一张纸上让同一个人写。
为何常见:很多教程的流程图把两侧画成完全对称的网络,新手自然以为“长得一样就是同一个网络”,忽略了“是否共享权重”这一关键区别。
误区既然余弦相似度分子就是点积,那直接用点积评分也一样,反正向量一样时排序结果相同。
正解只有向量都做了 L2 归一化之后,点积排序才与余弦排序等价。双塔训练中通常会对输出做 L2 归一化(这也是需要乘 γ 的原因之一),此时两者确实排序等价;但如果没归一化,模长会影响点积,排序结果会和余弦不同。余弦多了一步“除以模长”,对尺度更鲁棒,概念上不能无条件混为一谈。
为何常见:点积就是余弦公式的分子,教程里经常混着写;再加上归一化后排序等价这一事实,很容易让新手以为两个概念可以随意互换。
误区负样本当然是越多越好,模型看过更多反面例子,应该学得更准。
正解负样本数量需要平衡。N 越大,softmax 分母越接近全量候选,理论上更接近真实分布;但内存和算力开销线性上涨,而且如果负样本全是“太容易区分”的随机样本,模型学到的判别能力很有限。工程上常混合使用随机负样本、批内负样本与困难负样本,N 通常取几十到几百,并配合温度系数调节。
为何常见:“更多数据更好”在监督学习里经常成立,但负采样会稀释正样本信号,收益递减而成本线性上升,直觉在这里失效。
误区DSSM 最早用于搜索 query 与文档的匹配,所以双塔模型只能做文本类的语义匹配任务。
正解双塔的输入完全可以是任意特征:用户的年龄、近 7 天点击序列、商品价格带、视频时长、广告主行业……只要能特征化,就能分别编码进两座塔。推荐领域的 YouTube DNN、电商召回、广告定向用的都不是纯文本特征。Word Hashing 只是 DSSM 处理文本输入的一种技巧,不是双塔模型的必要条件。
为何常见:论文和教程都从 query-document 场景讲起,新手容易把“来源场景”误当成“适用范围”。
面试题
Q1. 双塔模型 DSSM 中,两座塔分别负责编码什么?
- A.用户与物品双塔编码← 正确答案
- B.两个相同物品的编码
- C.两个不同用户的编码
- D.用户与场景的编码
答案:A。DSSM 的核心是用户塔编码用户特征、物品塔编码物品特征,把两者映射到同一向量空间后用余弦打分。B、C 都是同一侧重复编码,D 把上下文与用户混淆,都不符合“一塔对用户、一塔对物品”的设计。
Q2. 双塔模型最终用什么指标衡量用户与物品的匹配度?
- A.余弦相似度衡量← 正确答案
- B.欧氏距离衡量
- C.编辑距离衡量
- D.曼哈顿距离衡量
答案:A。双塔把用户与物品编码成同一空间中的向量,标准做法是用余弦相似度衡量方向的接近程度。编辑距离、欧氏距离、曼哈顿距离都不是双塔匹配打分的标准选择,也不适合稠密向量匹配。
Q3. 双塔输入层引入 Word Hashing 的主要目的是什么?
- A.压缩词表降维度← 正确答案
- B.扩大词表增维度
- C.过滤停用词汇
- D.标注词性信息
答案:A。Word Hashing 把词拆成 letter-trigram 并哈希到固定大小的桶中,把百万级词表压到可控范围,同时缓解未登录词问题。它不改变网络的非线性能力,也不会专门过滤停用词或做词性标注。
Q4. 训练双塔模型时,除了正样本还需要什么?
- A.随机采样负样本← 正确答案
- B.仅用同类正样本
- C.由生成模型产出
- D.无需任何负样本
答案:A。Softmax 损失需要分母中有负样本才能做归一化,通常对每个用户随机采样 N 个未交互物品作为负样本。只用正样本无法形成对比信号;负样本来自采样而非生成模型,更不能没有负样本。
Q5. 双塔相比“特征拼接单塔”模型,核心优势是什么?
- A.物品向量可离线预计算← 正确答案
- B.模型预测精度更高
- C.特征交叉能力更强
- D.模型参数量更少
答案:A。双塔把物品侧编码与用户侧解耦,物品向量可离线算好存入索引,线上只需计算用户向量再检索——这是召回阶段可用性的关键。特征交叉强是拼接模型的优势;双塔精度未必更高;参数量也未必更少。
Q6. 双塔模型的“信息瓶颈”指的是什么?
- A.特征交互延迟到点积← 正确答案
- B.词哈希产生冲突
- C.模型训练速度慢
- D.向量维度设置太低
答案:A。双塔在最终内积/余弦之前,用户与物品特征完全不交互,任何高阶特征组合都无法在早期建模,这就是“信息瓶颈”。词哈希冲突、训练速度、向量维度都不是双塔特有的瓶颈问题。
Q7. 相似度乘以温度系数 γ 再进 softmax,主要作用是什么?
- A.让相似度分布更合理← 正确答案
- B.加快反向传播的速度
- C.防止模型严重过拟合
- D.减少模型内存占用
答案:A。未放大时余弦值集中在 [-1,1],softmax 输出太平滑、梯度很小;γ 大于 1 时拉大分数差距,让训练更尖锐有效。γ 不改变传播速度、不降低过拟合、也不节省内存。
Q8. 双塔模型适合用在哪些环节?
- A.候选集召回环节← 正确答案
- B.跨域语义检索场景← 正确答案
- C.特征工程预处理
- D.用户标签冷启动
答案:AB。双塔擅长“广撒网”的召回与检索:物品向量可离线预计算,线上快速找 Top-K。特征工程是数据准备工作,与模型架构无关;双塔本身解决不了冷启动,所以 C、D 不选。
Q9. 关于负采样,下列说法正确的有哪些?
- A.常取未点击物品样本← 正确答案
- B.负样本数量影响训练← 正确答案
- C.负样本越少效果越好
- D.负样本与正样本应相同
答案:AB。随机负采样通常从未交互物品中选取,负样本数量 N 直接影响 softmax 分母的质量与训练代价,所以 A、B 正确。负样本过少会让模型欠学习;负样本也无需与正样本分布完全相同,C、D 错误。
Q10. 提升双塔召回效果的常见工程手段有哪些?
- A.引入困难负样本挖掘← 正确答案
- B.调节温度系数γ← 正确答案
- C.拼接用户物品特征
- D.随机丢弃用户特征
答案:AB。困难负样本与批内负样本能提升判别力,调节 γ 能改善梯度分布,二者都是双塔召回常用的优化手段。C 是单塔做法,会破坏双塔的解耦结构;D 属于随机失活且无关检索效果提升,二者都不选。