DSSM模型全称:Deep Structure Semantic Model
在应⽤于推荐系统时,通过两个塔分别去建模user侧和item侧的embedding,计算embedding之间 的内积,最后⽤真实的label计算loss。
双塔模型的⿐祖,是微软在CIKM2013发表的⼀篇⼯作,它主要是⽤来解决NLP领域语义相似度任务 的。也是最初的DSSM。如果把document换成item或是⼴告,就演变成了⼀个推荐模型。
基本思想:
◦ 将搜索场景中的query 和 document映射到同⼀个低维空间
◦ query 和docment在低维空间的相似度表征两者的相关度
◦ 最⼤化点击document的条件概率
WordHashing:letter n-gram
英⽂单词维度是⽆限的,但是字⺟级别的n-gram是有限的,极⼤压缩维度
字⺟n-gram 可以捕捉同⼀单词的不同语态时态语境变化
out-of-vocubulary 鲁棒性,(前后缀,语态时态词的相似变化)
collision:不同单词的letter n-gram表⽰⼀样认为是⼀次碰撞
Multi-View-DNN联合了多个域的丰富特征,使⽤multi-view DNN模型构建推荐,包括app、新 闻、电影和TV,相⽐于最好的算法,⽼⽤⼾提升49%,新⽤⼾提升110%。并且可以轻松的涵盖⼤ 量⽤⼾,很好的解决了冷启动问题。但其使⽤前提是同⼀个主体公司下有很多APP,不同APP之间 的⽤⼾数据是可以互相使⽤的,从其设计思路上讲就很容易的将该算法的使⽤限定在了为数不多的 公司⾥。
训练模型使⽤的数据集:
User Features:⽤⼾在Bing搜索引擎中搜索的关键词和点击的链接数据(这⾥使⽤TF-IDF剔除 掉不重要的词汇特征数据)
News Features:⽤⼾在Bing News中的新闻点击数据,每篇新闻选择了三部分特征(新闻的标 题使⽤letter tri-gram技术进⾏编码、新闻的类别数据编码为⼆进制的特征、新闻的命名实体词 提取然后使⽤letter tri-gram技术进⾏编码)
App Features:⽤⼾在Windows AppStore中的下载历史数据,每个APP的的标题(通过letter tri-gram技术进⾏编码)和APP的类别数据(编码为⼆进制的特征)
Movie/TV Features:⽤⼾在XBOX上的浏览数据,主要利⽤的是标题、描述、流派
在MV-DNN中,userfeatures 被⽤来作为user view,其余的特征映射到不同的view中,训练时⼀ 个user features中的样本和⼀个其他的样本形成样本对,为了达到这个训练⽬的,使⽤微软公司的 唯⼀⽤⼾ID进⾏关联,News Features、App Features、Movie/TV Features分别和User Features 中的进⾏关联。
MV-DNN ⾥⾯的 MV 为 Multi-View ,⼀般可以理解为多视⻆的 DSSM ,在原始的DSSM中需要训练 的有 Query 和 Doc这两类的embedding,同时⾥⾯DNN的所有权重都是共享的,⽽MV-DSSM他可 以训练不⽌两类的训练数据,同时⾥⾯的深度模型的参数是相互独⽴,基于Multi-View的DSSM的 参数变多了,由于是多视⻆的训练,输⼊的语料也变得不同,⾃由度更⼤,但是训练时也会变得更 加困难。
YouTube使⽤的视频召回双塔模型。
• 训练Label
训练集Label并不是点击即为1,未点击为0。⽽是当⼀个视频被点击但是观看时⻓⾮常短时,label 同样是0。当视频被完整看完时,label才是1。
• 视频侧特征
视频侧包含的特征包含类别特征(如视频ID、频道ID)和连续特征。类别特征中有分为单值类别特 征和多值类别特征,对于多值类别特征,采⽤对embedding加权平均的⽅式得到最终的 embedding。
• ⽤⼾侧特征
⽤⼾侧特征主要是基于⽤⼾的历史观看记录来捕获⽤⼾的兴趣。⽐如使⽤⽤⼾最近观看过的k个视 频的embedding的平均值。对于类别特征,embedding在模型的两侧是共享的。
YouTube这个模型最⼤的不同是,训练过程是基于流数据的,每⼀天都会产⽣新的训练数据。因此, 负样本的选择只能在batch内进⾏,batch内的所有样本会作为彼此的负样本去做batch softmax。这 种采样的⽅式带来了⾮常⼤的bias,热⻔视频的采样概率会更⾼,也会更⼤概率被当做负样本,不符合 实际情况。因此论⽂核⼼解决的问题是减⼩batch内负采样带来的bias。
负采样概率计算采⽤的⽅式为使⽤hash策略来对物品的采样概率进⾏更新。通过hash函数h,对物品 ID进⾏映射。同时使⽤两个⻓度为H的数组A和B,通过h(y)来得到其在数组A和B中下标。A[h(y)]记录 上⼀次物品y被采样到的训练时刻,B[h(y)]记录物品y采样的预估频率。hash过程中的冲突情况会导致 B[h(y)]较⼩,导致采样概率预估过⾼。因此使⽤multiple hashings,采⽤了多组hash⽅程和数组A和 B。训练完成时使⽤最⼤的⼀个B[h(y)]去计算采样概率。
论⽂还提出两点经验
• 对两侧输出的embedding做L2标准化
• 计算user embedding和item embedding内积时除以一个超参数,使softmax效果更明显
模型结构为简单的双塔,两边塔的输入都是文本特征、社交特征和位置特征,其中社交特征和位置特征是他们在实验中发现对效果提升比较好的两种特征。这篇工作的两个核心亮点是hard negative mining和embedding ensemble。
Hard negative mining是指,他们发现如果将随机负样本这种比较easy的样本与上次召回中排名101-500名的比较hard的样本以100:1的比例去训练模型,得到的效果会比较好。
Embedding ensemble是指,可以将不同负样本训练得到的模型做融合来进行召回。融合的方式可以是相似度结果的直接加权或者是模型的串行融合,比如先用easy负样本训练模型进行初步的筛选,再用hard负样本训练模型进行最终的召回。
另外,虽然使用unified的特征,就是输入中包含社交特征和位置特征,来进行召回效果会比较好,但是召回结果在一定程度上也会损失文本的匹配,因此也可以先通过只输入文本特征的模型来做筛选再用输入unified特征的模型来召回,这样可以保证文本的匹配。
双塔线上服务
FB开始采用的是在现有系统的基础上再搭建一套向量召回的服务,但是这样会遇到以下几个问题。
*性能上撑不住
*维护两套索引系统,成本太高
*term match与embedding match两套系统召回的doc重复度很高
因此FB重建了一同融合term match与embedding match的召回系统,搞了大名鼎鼎的Faiss,通过Faiss进行向量压缩并且将它融合进倒排索引中。 这套系统有两个优势 :
1. 可以同时优化term matching和embedding matching。
2. 支持在向量召回的基础上添加term matching的限制,从而提升召回的效率
向量压缩主要包括两个部分:
Coarse quantization:粗糙压缩,通过k-means等手段进行聚类,通过聚类的结果来表达embedding。
product quantization:精细化的压缩,从而可以更高效的计算embedding间距离。
整个框架分为两个阶段,数据增强阶段是绿色箭头的部分,采样并利用样本中的用户请求与广告构造出更多样本,教师网络计算相似度后将低相似度的样本输入学生网络去预测CTR,通过采样的方式得到高CTR低相似度的样本存入buffer,这类样本称为bad case。
第二个阶段是橙色箭头表示的CTR模型训练阶段,将原先采样得到的原始样本也存入buffer,利用buffer中的三种样本去训练CTR模型。
虽然百度提出了这样一种框架,但是召回和排序的直接统一在实现的过程中还是比较困难的,因为面临的候选广告集数量太大,在性能方面还是难以保证。但是Mobius的这种将商业指标提前引入召回阶段的思想是非常具有探索价值的,比如文章中提到将cosine相似度直接乘上一个商业指标作为系数,就是一个很简单的方式。
双塔模型特点
双塔模型最大的特点就是“双塔分离”。只不过“部署时分离”成了双塔最大的优点,而“训练时分离”成了制约双塔性能的最大因素。
○ 由于item tower完全不依赖于user信息,所以海量的item embedding可以周期性、批量、离线生成,大大减轻了线上serving的压力
○ 由于user tower完全不依赖于item信息,所以无论候选集是几千(粗排)或十万级、百万级(召回),user embedding只需要生成一遍
○ user信息只能喂入user tower, item信息只能喂入item tower,没有地方喂入user & item之间的交叉特征
○ user侧信息与item侧信息,只有唯一一次交叉机会,就是在双塔生成各自的embedding之后的那次点积或cosine。但是这时参与交叉的user/item embedding,已经是高度浓缩的了。一些细节信息已经损失,永远失去了与对侧信息交叉的机会。
○ 为了线上快速serving,交叉只能是简单的dot或cosine。一些复杂的、依赖于底层信息的交叉结构,比如target item对user action history的attention,也在双塔中找不到位置。
实际应用流程
线下训练
线上serving
示例代码
import faiss
# faiss索引构建
d = 64
nlist = 10
index = faiss.IndexFlatL2(d)
index.add(item_embs)
# 用户推荐结果索引
D, I = index.search(user_embed, 50)
负样本挖掘
1. batch随机负采样
输入数据只有正例,在训练的时候,在batch内随机采样一定比例的负样本,一定程度上可以解决Sample Selection Bias问题
2. hard sample
要根据具体业务场景去判定了,Airbnb hard negative的选取策略:(1)增加与正样本同城的房间作为负样本(2)增加“被房主拒绝”作为负样本
3. 随机负例+热门打压
随机选择,但是越是流行的Item,越大概率会被选择作为负例。例如word2vec中,某物料成为正样本的概率
其中z(ωi)是第i个物料的曝光或点击占比,理解为降低热门item成为正样本的概率。某物料成为负样本的概率
其中n(ωi)是第i个物料的出现次数,而α一般取0.75,理解为增加热门item成为负样本的概率。
随机负采样
点击样本为正样本,随机样本为负样本,但不是等概率随机
热门内容为正样本时,降低概率;
冷门内容为负样本时,提高概率
正样本还可根据停留时长去除误点击样本
百度《Sample Optimization For Display Advertising》-关于召回模型如何选取训练样本
根据广告的曝光频率,以α为分解点,把广告分成两部分Ah和Al,然后随机生成一个0到1之间的随机数p,如果p 如果p > pl,从Ah中基于一元模型分布(unigram distribution)采样一个负样本广告 由于Al的数量远大于Ah的数量,与原始的负采样方法相比,我们的策略在保留原始属性的同时显着减少了内存使用。 在百度的凤巢系统统计中,总的ctr值大概只有0.03%,少数的头部广告会占据很大的曝光,它们可能同时出现在正样本(曝光并点击)和真实负样本(曝光但未点击)中。 我们不希望这些头部广告过多出现在负样本中,因为它们通常具有很高的商业价值,所以会对这些头部广告的负样本进行降采样,具有高曝光的广告的负样本,基于以下的概率进行抛弃: f是曝光的频率,也就是说曝光越多的广告的p就越大,那么它从负样本集合中被抛弃的概率就越大。 由于曝光未点击的广告并不能直接的定性为真正的负样本,所以将样本划分为了标记的样本 (点击的广告,这部分已经完全的确定了是正样本),另一部分为未标记样本,这部分由可靠的负样本和有可能是正样本的广告组成。百度要做的就是针对未标记的样本,将真正的负样本从中划分出来。划分的方式是训练SVM分类器来给样本进行打分。 为了缓解数据稀缺问题,百度引入了模糊逻辑来增加正样本。 为了增加正样本,解析未显示的事件日志,并在最终列表中收集所有三元组(用户,广告,CPM),并且CPM高于预定义的阈值。 将这些三元组(用户,广告,每千次展示费用/出价)称为“模糊的正样本”,并将其添加到正样本的训练集中。 值得注意的是,由于模糊正样本的标签不是点击记录,因此其标签小于1。 Test 数据源 MovieLens数据集,包含多个用户对多部电影的评级数据,也包括电影元数据信息和用户属性信息。结构为 数据工程 三个数据文件进行【MovieID, UserID】维度的merge “genres”列通过“|”进行split,转换为数字list表示 负样本生成 方案1 将用户评分的电影作为正样本,在所有的电影中随机采样其余若干个电影作为用户的负样本; 方案2 对数据集中用户评分较高的作为正样本,用户评分低的作为负样本; 若用于召回中,一般采用方案一。召回是从全量电影资源中选择用户感兴趣的电影,面对的是整个电影数据资源,感兴趣的对用户来说是一个较为模糊的结果,对精度的要求没有那么高。方案2所面对的资源是全量电影资源中很少一部分,与实际应用过程的数据分布明显不符合,同时由于对电影的评分进行了细粒度的量化,模型能很好的区分哪些电影是用户喜欢的哪些是不喜欢的精度较高,方案2更适合排序。方案1的数据是全量电影数据,对于正负样本的定义也没那么精准,所以模型学习到的也是一个较为模糊的偏好。 负样本生成的同时,生成一列新的特征“hist_movie”,表示用户在此前看过的电影。 输入特征定义为 user_feature_columns = [SparseFeat('user_id', feature_max_idx['user_id'], embedding_dim), 模型结构 User tower Item tower 损失函数 二元交叉熵-binary_crossentropy 改进loss binary_crossentropy * (1 - y_true) + weight * binary_crossentropy * (y_true) 效果: 当weight大于1时,增加对正样本判断错误的惩罚度 embedding获取 model.compile(optimizer=optimizer, loss="binary_crossentropy", metrics=["acc"])
SparseFeat("gender", feature_max_idx['gender'], embedding_dim),
SparseFeat("age", feature_max_idx['age'], embedding_dim),
SparseFeat("occupation", feature_max_idx['occupation'], embedding_dim),
SparseFeat("zip", feature_max_idx['zip'], embedding_dim),
VarLenSparseFeat(SparseFeat('hist_movie_id', feature_max_idx['movie_id'], embedding_dim,
embedding_name="movie_id"), SEQ_LEN, 'mean', 'hist_len'),
]
item_feature_columns = [SparseFeat('movie_id', feature_max_idx['movie_id'], embedding_dim),
VarLenSparseFeat(SparseFeat('genres', 19, embedding_dim,
embedding_name="genres_list"), 8, 'mean')]
history = model.fit(train_model_input, train_label, batch_size=64, epochs=100, verbose=1, validation_split=0.0, )
user_embedding_model = Model(inputs=model.user_input, outputs=model.user_embedding)
item_embedding_model = Model(inputs=model.item_input, outputs=model.item_embedding)
# 获取embedding
user_embs = user_embedding_model.predict(test_user_model_input, batch_size=2 ** 12)
item_embs = item_embedding_model.predict(all_item_model_input, batch_size=2 ** 12)