内容概览
矩阵补充模型
矩阵补充模型输入用户ID 和 物品ID,利用embedding把 用户ID 和 物品ID 映射成向量,输出向量的内积(一个实数),内积越大说明用户对这个物品越感兴趣
详见[搜广推]王树森推荐系统——矩阵补充&最近邻查找
没有用到属性
双塔模型
模型结构
除了用户ID,还可以从用户填写的资料和行为中获取很多特征,包括离散特征和连续特征,这些特征不能直接输入神经网络,要先做处理
方法:
- 数据预处理
- 把用户ID经过embedding层映射到一个向量
- 每个离散特征分别用一个embedding层映射成向量(城市,感兴趣的话题)
- 类别很少的离散特征可以直接用one-hot(性别)
- 对连续特征进行处理,不同连续特征有不同的处理方法(年龄,活跃程度,消费金额)
- 常用归一化,把数据变成均值为0方差为1的分布
- 长尾分布的连续特征需要特殊处理,如取log或分桶等方法
- 拼接向量,输入神经网络
- 神经网络输出向量,作为用户的表征
物品的处理方法类似,最后输出物品的表征向量
双塔模型示意图如下所示:
- 用户塔、物品塔各输出一个向量,两个向量的余弦相似度作为兴趣的预估值。
- 双塔模型对矩阵补充模型的改进在于使用了id之外的多种特征和计算兴趣用余弦相似度(输出在0-1之间)
正负样本的选择
正负样本的选择非常重要,甚至大于改进模型结构
- 正样本:曝光而且有点击的 用户-物品 二元组。(用户对物品感兴趣)
- 负样本 :
- 没有被召回的 ?
- 召回但是被粗排、精排淘汰的 ?
- 曝光但是未点击的 ?
正样本选择
通常选择曝光而且有点击的 用户-物品 二元组。(用户对物品感兴趣)
但存在问题:少部分物品占据大部分点击,导致正样本大多是热门物品,对冷门物品不公平。
解决方案:
过采样冷门物品,或降采样热门物品。
- 过采样(up-sampling):一个样本出现多次。
- 降采样(down-sampling):一些样本被抛弃。
负样本选择
三种情况:
简单负样本:全体物品
未被召回的物品,大概率是用户不感兴趣的
因为召回是从几亿数据中选几千个,未被召回的物品相比于召回的物品非常多,未被召回的物品 ≈ 全体物品
所以,可以简单地从全体物品中做随机抽样,作为负样本。
均匀抽样 or 非均匀抽样 ?
- 均匀抽样:对冷门物品不公平
- 正样本通常定义为那些曝光后用户点击的物品。由于热门物品点击频率更高,因此它们成为正样本的机会也更大
- 如果采用均匀抽样来生成负样本,那么由于冷门物品在全体物品中占比较高,因此抽样得到的负样本大多数会是冷门物品。
- 非均抽采样:目的是打压热门物品
- 负样本抽样概率与热门程度(点击次数)正相关
抽样概率 ∝ ( 点击次数 ) 0.75 抽样概率 ∝ (点击次数)^{0.75} 抽样概率∝(点击次数)0.75
- 负样本抽样概率与热门程度(点击次数)正相关
简单负样本:Batch内负样本
一个Batch内有许多用户点击物品的样本,将每个用户点击的物品当做正样本,同一个Batch内其他用户点击的物品作为该用户的负样本。
- 例如,一个 batch 内有n个(用户,点击物品)的正样本,那么每个用户和除了点击物品外的其他n-1个物品组成负样本。
- 这个 batch 内一共有 n(n-1)个负样本。都是简单负样本。(因为第一个用户不喜欢第二个物品。)
问题:
一个物品出现在 batch 内的概率 ∝ 点击次数。因此,物品成为负样本的概率也
∝
(
点击次数
)
∝(点击次数)
∝(点击次数),但在上一种选取策略中提到过,这里应该是
∝
(
点击次数
)
0.75
∝ (点击次数)^{0.75}
∝(点击次数)0.75。
这会导致热门物品成为负样本的概率过大,模型对热门物品的打压太狠,造成偏差
如何修正偏差:
物品
i
i
i 被抽样到的概率:
p
i
∝
点击次数
p_i ∝ 点击次数
pi∝点击次数
预估用户对物品i的兴趣:
c
o
s
(
a
,
b
i
)
cos(a, b_i)
cos(a,bi)
做训练的时候,调整为:
c
o
s
(
a
,
b
i
)
−
l
o
g
p
i
cos(a,b_i)- log p_i
cos(a,bi)−logpi
线上做召回的时候还是用原本的余弦相似度
困难负样本
困难负样本:
- 被粗排淘汰的物品(比较困难)
- 精排分数靠后的物品(非常困难)
对正负样本做二元分类:
- 全体物品(简单)分类准确率高。
- 被粗排淘汰的物品(比较困难)容易分错。
- 精排分数靠后的物品(非常困难)更容易分错。
训练数据
混合几种负样本:
- 50%的负样本是全体物品(简单负样本)
- 50%的负样本是没通过排序的物品(困难负样本)
常见错误:
曝光但是没有点击的物品不能作为召回的负样本
召回模型的任务是快速找到用户可能感兴趣的物品,区分用户不感兴趣的物品和可能感兴趣的物品,而不是区分有点感兴趣的物品和很感兴趣的物品
- (easy):绝大多数是用户根本不感兴趣的全体物品被排序淘汰
- (hard):用户可能感兴趣,但是不够感兴趣。
双塔模型的训练
- Pointwise:独立看待每个正样本、负样本,做简单的二元分类。正负样本组成一个数据集,在数据集上做随机梯度下降,训练双塔模型
- Pairwise:每次取一个正样本、一个负样本,组成二元组来训练
- Listwise:每次取一个正样本、多个负样本,组成list来训练,多元分类
Pointwise 训练(最简单)
把召回看做二元分类任务:
- 对于正样本,鼓励 c o s ( a , b ) cos(a,b) cos(a,b) 接近 +1。
- 对于负样本,鼓励 c o s ( a , b ) cos(a,b) cos(a,b) 接近 -1。
- 控制正负样本数量为 1:2 或者 1:3 。
Pairwise 训练
每组输入是一个三元组:一个用户+两个物品(正样本和负样本)
- 两个物品共享一个物品塔
分别计算用户对两个物品的相似度 - 希望 c o s ( a , b + ) cos(a,b^+) cos(a,b+) 越大越好,接近 +1
- 希望 c o s ( a , b − ) cos(a,b^-) cos(a,b−) 越小越好,接近 -1
基本想法:让用户对正样本的兴趣尽量大,对负样本的兴趣尽量小,鼓励
c
o
s
(
a
,
b
+
)
cos(a,b^+)
cos(a,b+) 大于
c
o
s
(
a
,
b
−
)
cos(a,b^-)
cos(a,b−),二者差值越大越好
损失函数
希望用户对正样本的兴趣大,对负样本的兴趣小
- 如果 c o s ( a , b + ) > c o s ( a , b − ) + m cos(a,b^+) > cos(a,b^-)+m cos(a,b+)>cos(a,b−)+m,则没有损失。
- 如果用户对正样本的兴趣没有那么大,即 c o s ( a , b + ) < c o s ( a , b − ) + m cos(a,b^+) < cos(a,b^-)+m cos(a,b+)<cos(a,b−)+m,此时损失等于 c o s ( a , b − ) + m − c o s ( a , b + ) cos(a,b^-)+m-cos(a,b^+) cos(a,b−)+m−cos(a,b+)
- m 是超参数
有两种损失函数:
- hinge loss的 m 是超参数
- logistic loss的
σ
\sigma
σ 是超参数
Listwise训练
一条数据包含:
- 一个用户,特征向量记作a。
- 一个正样本,特征向量记作 b + b^+ b+
- 多个负样本,特征向量记作 b 1 − , … , b n − b_1^-,…,b_n^- b1−,…,bn−
基本思想:
- y + = 1 y^+ = 1 y+=1,鼓励 c o s ( a , b + ) cos(a,b^+) cos(a,b+)尽量大。
- y 1 − = ⋅ ⋅ ⋅ = y n − = 0 y_1^- = ··· = y_n^- = 0 y1−=⋅⋅⋅=yn−=0鼓励 c o s ( a , b − ) , … , c o s ( a , b n − ) cos(a,b^-),…,cos(a,b_n^-) cos(a,b−),…,cos(a,bn−)尽量小。
- 用交叉熵损失函数,鼓励 s 接近 y
线上召回
离线存储
- 完成训练之后,用物品塔计算每个物品的特征向量b
- 把几亿个物品向量b存入向量数据库(比如 Milvus、Faiss、nswLib)
- 向量数据库建索引,以便加速最近邻查找。
线上计算
- 给定用户ID和画像,线上用神经网络算用户向量a
- 最近邻查找:
- 把向量a作为query,调用向量数据库做最近邻查找。
- 返回余弦相似度最大的k个物品,作为召回结果。
为什么事先存储物品向量b,但线上计算用户向量a?
- 因为每做一次召回只用到一个用户向量a,但要用到几亿物品向量b,线上算物品向量的代价过大。
- 用户兴趣动态变化,所以直接在线上进行计算;而物品特征相对稳定,所以可以把物品向量存起来。
模型更新
全量更新
今天凌晨用昨天全天的数据训练模型,在昨天模型参数的基础上做训练而不是随机初始化
- 用昨天的数据训练1cpoch,即每天数据只用一遍
- 训练完成后,发布新的用户塔神经网络和物品向量供线上召回使用。
- 全量更新对数据流、系统的要求比较低。
增量更新
做 online learning 更新模型参数。
原因:用户兴趣会随时发生变化。
流程:
- 实时收集线上数据
- 做流式处理,生成TFRecord文件
- 对模型做 online learning,增量更新 IDEmbedding 参数(不更新神经网络其他部分的参数。)
- 发布用户 ID Embedding,供用户塔在线上计算用户向量。
常用策略
每天内进行增量更新,凌晨时抛弃增量更新,用昨天数据进行全量更新
能否只做增量更新,不做全量更新?
不可以。
- 小时级数据有偏,分钟级数据偏差更大。如果只进行增量更新,则容易造成很大偏差
- 全量更新random shuffle 一天的数据,做1epoch训练;增量更新按照数据从早到晚的顺序。随机打乱优于按顺序排列数据,因此整体来看全量训练优于增量训练。
不适用于召回的模型
前期融合:在输入神经网络之前融合特征,通常用于粗排或精排(从几千个里选出几百个),不能用于召回(从几亿个选出几千个)
后期融合:经过神经网络之后再融合特征(计算相似度)
为什么前期融合模型不适用于召回?
- 用前期融合模型进行召回时,必须把所有物品的特征挨个拼接用户向量,输入模型,预估用户对所有物品的兴趣,计算量太大
- 没法用近似最近邻查找加速计算
双塔模型+自监督学习
双塔模型的问题
- 推荐系统的头部效应严重,少部分物品占据大部分点击。
- 大部分物品的点击次数不高。
- 高点击物品的表征学得好,长尾物品的表征学得不好。
用自监督学习解决问题:做 data augmentation,更好地学习长尾物品的向量表征。
自监督学习
- 物品 i i i 的两个向量表征 b i ′ b_i' bi′ 和 b i ′ ′ b_i'' bi′′ 有较高的相似度。
- 物品 i i i 和 j j j 的向量表征 b i ′ b_i' bi′ 和 b j ′ b_j' bj′ 有较低的相似度。
目的:鼓励
c
o
s
(
b
i
′
,
b
i
′
′
)
cos(b_i',b_i'')
cos(bi′,bi′′)尽量大,
c
o
s
(
b
i
′
,
b
j
′
)
cos(b_i',b_j')
cos(bi′,bj′)尽量小
自监督方法
特征变换:Random Mask
随机选一些离散特征(比如类目),把它们的值遮住:
例如:
- 某物品的类目特征是 U = { 数码 , 摄影 } U = \{数码,摄影 \} U={数码,摄影}
- Mask 后的类目特征是 U ′ = { d e f a u l t } U'=\{ default \} U′={default}
特征变换:Dropout(仅对多值离散特征生效)
如果一个物品有多个类目,那么类目是一个多值离散特征。
Dropout会随机丢弃特征中 k%的值。(k值可指定)
例如:
- 某物品的类目特征是 U = { 美妆 , 摄影 } U=\{美妆,摄影\} U={美妆,摄影}
- Dropout后的类目特征是 U ′ = { 美妆 } U'=\{美妆\} U′={美妆}
对比:Random Mask把整个类目特征的值都丢掉,Dropout只丢一部分特征值
特征变换:互补特征(complementary)
假设物品一共有4种特征:{ID,类目,关键词,城市}
随机分成两组:
- {ID,default,关键词,default} --> 物品表征1
- {default,类目,default,城市} --> 物品表征2
因为两个表征是根据同一个物品生成的,所以鼓励两个物品表征尽可能相似
特征变换:Mask 一组关联的特征
受众性别:U= {男,女,中性}
类目:V ={美妆,数码,足球,摄影,科技,…}
通常来说:
- u=女 和 v=美妆 同时出现的概率p(u,v)大
- u=女 和 v=数码 同时出现的概率p(u,v)小
记p(u,v)为某特征取值为u,另一个特征取值为v,两个特征值同时发生的概率,那么有:
- p(女性,美妆)=3%
- p(女性,数码)=0.1%
因此,离线计算特征两两之间的关联,用互信息(mutualinformation)衡量:
流程
设一共有k种特征
- 离线计算特征两两之间MI,得到 k ∗ k k * k k∗k 的矩阵。
- 随机选一个特征作为种子,找到种子最相关的 k / 2 k/2 k/2 种特征。
- Mask 种子 及其相关的 k / 2 k/2 k/2 种特征,保留其余的 ( k / 2 ) − 1 (k/2) -1 (k/2)−1 种特征。
好处:比random mask、dropout、互补特征等方法效果更好。
坏处:方法复杂,实现的难度大,不容易维护
自监督训练
- 从全体物品中均匀抽样,得到m个物品,作为一个batch .
- 做两类特征变换,物品塔输出两组向量
- 计算第 i i i 个物品的损失
- 梯度下降,减小自监督学习的损失