Datawhale学习笔记——深度语义匹配模型DSSM详解、实战与FAQ
一、 核心思想:它是什么?为什么有效?
DSSM(Deep Structured Semantic Model),又称“双塔模型”,是深度学习在搜索、推荐、广告领域应用的奠基性工作之一。其核心思想是将用户(User)和物品(Item)映射到同一个低维、稠密的向量空间(Embedding Space),并通过计算向量之间的距离(如余弦相似度)来衡量它们的相关性。
为什么有效?
- 语义匹配:传统的检索模型(如BM25)依赖于关键词的直接匹配,无法处理“一词多义”和“一义多词”的问题。DSSM通过深度网络学习到的分布式表示(Embedding),能够捕捉到文本深层的语义信息。
- 维度统一:无论用户查询(Query)多长,物品(Document)多长,最终都被映射为固定长度的向量,便于后续高效的相似度计算和大规模检索。
- 端到端学习:模型直接优化用户和物品向量的相似度目标(如点击率),让最终的Embedding更适合下游任务。
二、 模型架构:双塔是怎么工作的?
DSSM的架构非常直观,就像两座对称的“塔”,因此得名“双塔模型”。
-
输入层(Input Layer):
- 用户塔输入:通常是用户的历史行为、搜索词(Query)、人口属性等特征。在本文的U2I场景中,输入是
(user_id, history_sequence)
。 - 物品塔输入:通常是物品的标题、描述、类别等特征。输入是
(item_id, item_features)
。
- 用户塔输入:通常是用户的历史行为、搜索词(Query)、人口属性等特征。在本文的U2I场景中,输入是
-
表示层(Representation Layer) - “塔身”:
- 两边各是一个深度神经网络(DNN),通常结构相同(对称),但不共享权重。
- 网络可以是简单的全连接层(MLP)、CNN或RNN,用于将高维稀疏的输入特征转化为低维稠密的向量。
- 最终,用户塔输出一个向量 u,物品塔输出一个向量 i。
-
匹配层(Matching Layer):
- 计算两个向量 u 和 i 的余弦相似度,得到它们的相关性分数。
score(u, i) = cosine(u, i) = (u · i) / (||u|| * ||i||)
-
损失函数(Loss Function):
- 模型的目标是最大化正样本(用户点击过的物品)对的相似度,最小化负样本(随机采样或未点击的物品)对的相似度。
- 最常用的损失函数是 Softmax交叉熵损失 或 Pairwise Ranking Loss(如BPR, Hinge Loss)。
三、 实战步骤(U2I场景)
参考教程中的代码,我们可以将DSSM的实现分为以下几步:
步骤一:数据准备与采样
这是最关键的一步,数据质量直接决定模型效果。
- 正样本:用户真实点击、购买、观看等交互行为记录
(user_id, item_id, label=1)
。 - 负样本:
- 曝光未点击:最理想的负样本,但通常难以获取。
- 全局随机采样:从全量物品中随机抽取,简单有效,是最常用的方法。
- 批量内随机采样:在同一个训练batch内,将其他用户的正样本作为当前用户的负样本(如Sampled Softmax Loss)。
# 伪代码: 构造训练样本
def generate_samples(click_df, items_pool, neg_ratio=4):samples = []for user_id, hist in click_df.groupby('user_id'):# 正样本pos_items = hist['item_id'].tolist()for pos_i in pos_items:samples.append([user_id, pos_i, 1])# 负样本:随机采样neg_items = random.sample(items_pool, len(pos_items) * neg_ratio)for neg_i in neg_items:samples.append([user_id, neg_i, 0])return samples
步骤二:定义双塔模型
使用TensorFlow
或PyTorch
定义模型结构。
# TensorFlow 伪代码
import tensorflow as tf
from tensorflow.keras.layers import Input, Embedding, Dense, Flatten, Concatenatedef create_dssm_model(user_feature_dim, item_feature_dim, emb_dim=64):# User Toweruser_input = Input(shape=(user_feature_dim,))u_emb = Dense(128, activation='relu')(user_input)u_emb = Dense(64, activation='relu')(u_emb)user_embedding = Dense(emb_dim, activation=None)(u_emb) # 最终用户向量# Item Toweritem_input = Input(shape=(item_feature_dim,))i_emb = Dense(128, activation='relu')(item_input)i_emb = Dense(64, activation='relu')(i_emb)item_embedding = Dense(emb_dim, activation=None)(i_emb) # 最终物品向量# 计算余弦相似度dot = tf.reduce_sum(user_embedding * item_embedding, axis=1)user_norm = tf.norm(user_embedding, axis=1)item_norm = tf.norm(item_embedding, axis=1)cosine_sim = dot / (user_norm * item_norm + 1e-8)output = tf.sigmoid(cosine_sim) # 将相似度映射为0-1的概率model = tf.keras.Model(inputs=[user_input, item_input], outputs=output)model.compile(optimizer='adam', loss='binary_crossentropy')return model
步骤三:模型训练
将准备好的样本输入模型进行训练。
model = create_dssm_model(user_feature_dim, item_feature_dim)
history = model.fit(x=[train_user_features, train_item_features],y=train_labels,epochs=10,batch_size=256,validation_data=([val_user_features, val_item_features], val_labels)
)
步骤四:生成向量与召回
训练完成后,使用模型生成所有物品的向量,并存入向量数据库(如FAISS)以供快速检索。
# 生成物品向量库
item_model = tf.keras.Model(inputs=model.input[1], outputs=model.get_layer('item_embedding_layer').output)
all_item_vectors = item_model.predict(all_item_features)# 存入FAISS索引
import faiss
index = faiss.IndexFlatIP(emb_dim) # Inner Product 索引,余弦相似度需归一化
faiss.normalize_L2(all_item_vectors) # 归一化,使得内积等于余弦相似度
index.add(all_item_vectors)# 为用户生成向量并召回
user_vector = user_model.predict(user_features)
faiss.normalize_L2(user_vector)
D, I = index.search(user_vector, topk=100) # D是距离,I是物品索引
四、 常见问题解答(FAQ)
Q1: 负样本应该如何选择?
A: 负样本选择是DSSM模型成功的关键。
- 全局随机采样:最简单基础的方法,可能包含很多“简单负样本”。
- 曝光未点击:高质量负样本,代表了用户真正看到但不感兴趣的物品。
- Hard Negative Sampling:选择那些模型容易判错的负样本(即相似度较高的负样本),能进一步提升模型分辨困难样本的能力。通常需要在训练过程中动态调整。
Q2: 用户特征和物品特征应该如何处理?
A: 特征需要经过编码才能输入网络。
- 类别型特征(如user_id, item_id, category):使用Embedding层将其转换为稠密向量。
- 数值型特征(如价格、时长):可以直接输入,或进行分桶(Binning)后当作类别特征处理。
- 序列特征(用户历史行为):可以通过Pooling(如Sum, Mean)、RNN或Attention机制来聚合成一个定长向量。
Q3: 双塔模型的“塔”是否要对称?权重是否要共享?
A: 通常不需要对称也不需要共享权重。用户侧和物品侧的特征类型、分布、意义完全不同,使用独立的网络可以更好地学习各自独特的表示。只有在两侧输入是同构数据(例如文本 vs 文本)时,才考虑共享权重。
Q4: DSSM的缺点是什么?
A:
- 信息损失:双塔结构在顶层才进行交互,是一种“迟交互”(Late Interaction),相比“早交互”(Early Interaction)模型(如Deep&Cross),特征交叉能力较弱。
- 冷启动问题:新用户或新物品没有行为数据,难以生成准确的向量。解决方案是更多地依赖侧信息(Side Information)特征,如物品的类别、作者,用户的年龄、性别等。
Q5: 线上服务如何保证低延迟?
A: DSSM的优势就在于高效的线上服务。
- 所有物品向量可以预先计算好并存入向量索引库(如FAISS, HNSW)。
- 线上服务时,只需要实时计算一次用户向量(计算量很小)。
- 通过向量索引库进行近似最近邻(ANN)搜索,可以在毫秒级时间内从百万甚至十亿级物品中召回Top-K结果。
五、 总结
DSSM通过其经典的双塔结构,巧妙地解决了大规模推荐系统中的语义匹配和高效检索问题。它虽然不是特征交叉能力最强的模型,但其在工程上的简洁性和高效性使其成为工业界召回阶段的首选模型之一。掌握DSSM的原理、实现细节以及其中的“坑”(如负样本采样),是构建推荐系统的重要基础。
参考资料Datawhale组队学习