文章目录
排序模型基础
通常的,我们将推荐系统分为四个阶段:召回,粗排,精排,重排。
在召回阶段,我们通常已经通过 embedding 向量的相似度,筛选出几千个候选 item;
排序阶段的任务是:精确地预测每个候选 item 被用户点击/购买/播放的概率(CTR、CVR 等),然后排序输出。
也就是说,几乎所有的排序模型都遵循下面的多目标模型的结构,将所有特征做连接(concatenation)经过一个共享的神经网络(shared-bottom)得到某种表征向量,再通过不同的网络(有些地方叫任务塔)得到各种指标的预测:

我们后面还会提到很多模型,本质都是对上述架构进行改进,改动shard- bottom使用DCN或者引入专家网络预测不同指标(MMoE)等等,在此之前先来看看采样的问题。
采样
在选择样本的时候,类别是不平衡的(点击作为正样本的数量远远小于未点击的负样本数量),所以一般需要对负样本降采样。
降采样的结果是预估点击率比实际点击率高,需要校准;既然如此,为什么不干脆不进行降采样,就用原本的分布?
原因有二:1.如果保留全部负样本,数据量往往是正样本的几十倍甚至上百倍;训练时长较长。对负样本降采样能在几乎不损失精度的前提下,把训练时间缩短一个数量级。
2.负样本大多数都是“显然不相关”的,排序模型不同于召回,前者应当学习正负样本边界处的微妙区别,也就是哪些 item 看起来接近兴趣但没点、点了的有什么不同。这样能让模型学得更有价值。
校准方法:
真实点击率
P
t
r
u
e
=
n
+
n
+
+
n
−
P_{true}=\frac{n_+}{n_++n_-}
Ptrue=n++n−n+,预估点击率
P
=
n
+
n
+
+
α
∗
n
−
P_{}=\frac{n_+}{n_++\alpha*n_-}
P=n++α∗n−n+,修正公式即为:
p
^
t
r
u
e
=
α
⋅
p
^
p
r
e
d
α
⋅
p
^
p
r
e
d
+
(
1
−
p
^
p
r
e
d
)
\hat{p}_{true} = \frac{\alpha\cdot\hat{p}_{pred}}{\alpha\cdot\hat{p}_{pred}+(1-\hat{p}_{pred})}
p^true=α⋅p^pred+(1−p^pred)α⋅p^pred
Q:为什么需要矫正?
在排序模型中,由于只要保证数据的相对顺序正确即可,理论上不需要校准排序也是正确的,那为什么需要校准?主要的原因来自于广告系统,ctr需要参与广告计费的计算,所以需要精确值;另一层考量可能是希望在全链路中保持一个真实的尺度,如果没校准,模型输出 0.3 可能意味着“非常高”也可能是“非常低”。
MMoE
我们不妨以MMoE(Multi-gate Mixture-of-Experts多门控专家系统)为例来介绍排序模型的训练流程,MMoE出自于对shard-bottom模型如下的思考:
shard-bottom模型存在一个不可避免的致命问题,所有独立任务的输入都来自 同一个底层特征表示,当任务之间存在本质上的冲突时,该模型的硬共享机制会引发负迁移问题。
以视频为例,B站上很多视频存在这样的问题,很多用户会把一个看不完的课程/教程之类的视频收藏,导致收藏率与完播率是必然冲突的;再比如电商平台同时优化“点击率”与“客单价”时,低价商品可能会推动点击率的提升,但同时却抑制了客单价的增长。
有ML/DL经验的同学应该能反应过来,在对模型反向传播更新参数时,共享层的梯度更新方向是由所有任务共同决定的,一旦任务目标之间出现冲突,参数优化就会陷入方向性的矛盾。
从数学角度来解释,假设任务 i i i与任务 j j j的损失梯度分别为 ∇ L o s s i \nabla{Loss_i} ∇Lossi与 ∇ L o s s j \nabla{Loss_j} ∇Lossj,当 ∇ L o s s i ⋅ ∇ L o s s j \nabla{Loss_i}\cdot\nabla{Loss_j} ∇Lossi⋅∇Lossj < 0时,共享层参数更新就会产生内在的冲突。这种冲突使得模型在处理矛盾任务时呈现出“零和博弈”的特性,即提升某一目标的性能往往需要以牺牲另一目标为代价,我们一般也称这类问题为跷跷板问题。
出于这种思考,MMoE将底层的shard-bottom改造为Mixture-of-Experts,也就是拆分为多个专家网络,再通过门口网络Multi-gate来控制不同专家输出的权重。


损失函数
得到预估分数后,我们使用加权累加各指标交叉熵的方式计算多目标损失:
∑
i
=
1
k
α
i
⋅
C
r
o
s
s
E
n
t
r
o
p
y
(
y
i
,
p
i
)
\sum_{i=1}^{k} \alpha_{i} \cdot \mathrm{CrossEntropy}(y_{i}, p_{i})
i=1∑kαi⋅CrossEntropy(yi,pi)
但由于不同任务的损失值量级有差异,导致大损失主导优化方向,也就是量级失衡问题,权重的选择相当重要。实践中有主流的三种优化方法:Uncertainty Weight:基于不确定性的自适应加权;GradNorm:梯度标准化方法;Pareto Optimization:帕累托优化框架。受篇幅限制这里不展开了,感兴趣的可以参阅FunRec的教程FunRec
极化问题
MMoE 理论上希望:不同 Expert 各司其职、互补学习,不同任务的 Gate 能根据样本动态选择不同 Expert。但现实训练中经常出现:某几个 Expert 被所有 Gate 都抢着用;其他 Expert 几乎没被选中,梯度更新极少;少数 Expert 变得非常强,其他 Expert 退化闲置。
这种情况很可能是由于训练初期如果某个 expert 稍微表现好一点, gate 的权重会更倾向它,从而使它获得更多梯度更新,这种正反馈导致其它 expert 被忽视。一般的我们可以通过正则化约束,或者在gate的softmax上加上温度参数,让分布更加平滑,防止极化现象的发生。
预估分数融合
训练好模型后,我们就可以使用模型预估的各项分数根据不同的权重来对候选项排序,不同场景下使用的加权方法不同,例如电商平台侧重点击率和转化率,视频平台侧重播放时长等等,根据具体场景及线上测试效果选择相应的权重即可。
特征交叉
何为特征交叉?用户兴趣往往取决于特征之间的相互作用而不是单个特征。特征交叉就是让模型显式或隐式地学习“特征 A 和 特征 B”一起出现时对预测结果的影响。
举个例子,特征有房屋面积 x 1 x_1 x1,和单位房价 x 2 x_2 x2,传统LR预估房价模型会是 y = w 1 x 1 + w 2 x 2 + b y=w_1x_1+w_2x_2+b y=w1x1+w2x2+b,效果显然不好,应该引入一项 w ⋅ x 1 x 2 w\cdot x_1x_2 w⋅x1x2.
FM 因子分解机
Factorized Machine来自于前深度学习时代,引入了所谓的二阶交叉特征。 为了避免手动设计特征之间的交叉关系,一个直接的想法是直接增加所有特征的二阶组合项,通过训练参数
U
i
j
U_{ij}
Uij来保留有用的特征组合。(这里的特征指的是所有的输入,不单指User或Item)
y
=
w
0
+
∑
i
=
1
n
w
i
x
i
+
∑
i
=
1
n
−
1
∑
j
=
i
+
1
n
u
i
j
x
i
x
j
y = w_{0} + \sum_{i=1}^{n} w_{i}x_{i} + \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} u_{ij}x_{i}x_{j}
y=w0+i=1∑nwixi+i=1∑n−1j=i+1∑nuijxixj
这样的做法问题在于引入了
n
2
n^2
n2 个要训练的参数。而要将这么多的参数训练好,就需要更多的数据,否则就容易过拟合。只有遇到
x
i
x_i
xi和
x
j
x_j
xj 都不为0的样本, 才得到一次训练机会。但是推荐系统的一大特点就是类别特征高维稀疏,符合条件的样本少之又少,导致 得不到充分训练。
FM思想是把参数矩阵U分解为两个矩阵的乘积,这样将逐元素的交互权重
U
i
j
U_{ij}
Uij分解为两个低维隐向量的内积
<
V
i
,
V
j
>
<V_i,V_j>
<Vi,Vj> 。这样,模型的预测公式就演变为:
y
=
w
0
+
∑
i
=
1
n
w
i
x
i
+
∑
i
=
1
n
−
1
∑
j
=
i
+
1
n
⟨
v
i
,
v
j
⟩
x
i
x
j
y = w_{0} + \sum_{i=1}^{n} w_{i}x_{i} + \sum_{i=1}^{n-1} \sum_{j=i+1}^{n} \langle \mathbf{v}_{i}, \mathbf{v}_{j} \rangle x_{i}x_{j}
y=w0+i=1∑nwixi+i=1∑n−1j=i+1∑n⟨vi,vj⟩xixj

其中
v
i
v_i
vi
v
j
v_j
vj分别是特征i和特征j的k维隐向量,这样的做法将参数量从
n
2
n^2
n2变为
n
k
nk
nk,同时只要
x
i
≠
0
x_i\neq0
xi=0或
x
j
≠
0
x_j\neq0
xj=0 的样本都能训练,也就数据利用率更高,训练更加充分。无论多么小众的特征组合模式都有可能被识别出来。
DCN 深度交叉网络(Deep Cross Network)
对于高阶的特征交叉,引入DNN(深度神经网络Deep Neural Network)是个常见的思路。常见的观点认为,只要网络层数足够多,每层足够宽,DNN就能够模拟任何函数。由于DNN各层非线性激活函数的使用,DNN产生的交叉是无穷高阶的,无法明确用一个多项式表示。因此,我们称DNN产生的交叉为隐式交叉。
但是随着研究的日渐深入,研究发现某些场景下,DNN甚至连二阶或三阶特征交叉都模拟不好。真要实现高阶的特征交叉,恐怕需要非常宽与深的网络,但是各种梯度消失、梯度爆炸的问题也都会随之而来。
出于这种限制,DCN的作者提出了交叉层(Cross Network)的概念,显式的与原始输入做交叉,同时类似于Resnet的结构有效解决的梯度消失的问题。(关于梯度消失与Resnet的讨论可以移步其它博客,在此不再赘述),交叉层的设计如下:

对于第l层网络的原始输出
y
=
w
l
⊤
x
l
+
b
l
y = w_l^\top x_l + b_l
y=wl⊤xl+bl,将y与原始输入
x
0
x_0
x0做哈达玛乘积(逐元素相乘)得到z,再加上第l层的输入
x
l
x_l
xl,也即:
x
l
+
1
=
x
0
(
w
l
⊤
x
l
+
b
l
)
+
x
l
x_{l+1} = x_0 (w_l^\top x_l + b_l) + x_l
xl+1=x0(wl⊤xl+bl)+xl

交叉层让原始输入
x
0
x_0
x0和当前层输出做特征交叉。层数越深,特征交叉的阶数就越高。比如第一层时, 包含二阶交叉项;第二层时, 本身就有二阶信息,再和
x
0
x_0
x0交叉就产生三阶交叉项。所以Cross Network有多深,就能学到多高阶的特征交叉。
把DNN的全连接层替换为交叉层就可以得到DCN,但是DCN的作者认为应当将显示特征交叉与隐式特征交叉融合。工业上往往使用并联融合,DCN-v2中也提到串联的方法,让DNN进一步加工Cross Tower 生成的多阶交叉特征,抽取更复杂的表示。

基于注意力机制的AutoInt
AutoInt的提出基于这样一种思考:DCN的交叉方式比较固定,每一层都与初始输入进行交叉,有没有一种方法能够让模型自动学习到哪些特征应该交叉?交叉的权重该多大?
AutoInt的本质很简单,它将所有输入特征(无论是类别型还是数值型)都转换为相同维度的Embedding,也就是每个特征作为一个Token,输入一个多头注意力网络。(如果你不知道什么是attention/Transformer,这里有一个简单的科普Transformer 其实是个简单到令人困惑的模型,看完后对你理解Autoint以及后面的模型会有很大帮助)

在这里,每一个注意力头相当于处理一种特征交互模式,模型将所有Head的输出拼接起来,形成一个更丰富的特征表示。
序列建模
在上一节我们提到过,用户的兴趣很有可能与最近行为序列有关。一个自然的想法是将用户最近的历史行为LastK对应的Embedding向量通过池化操作,压缩成一个固定长度的向量来作为一种用户特征输入神经网络。
DIN
DIN的作者认为,传统的序列建模,无论系统准备向这个用户推荐“跑鞋”还是“手机”,代表他的都是同一个向量。这个向量试图“一视同仁”地蕴含该用户所有的兴趣点,在面对具体推荐任务时显得不够聚焦。用户的某一次具体点击行为,通常只由其历史兴趣中的一部分所“激活”。当向一位数码爱好者推荐“机械键盘”时,真正起决定性作用的,很可能是他最近浏览“游戏鼠标”和“显卡”的行为,而不是他上个月购买的“跑鞋”。
因此,DIN提出了局部激活的概念,本质很简单:对于一个候选物品,分别计算它与Last N个物品向量的相似度
α
i
\alpha_i
αi,用相似度
α
i
\alpha_i
αi作为权重进行加权累加最后得到最终的序列特征向量。

其实这就是简化的单头注意力机制, Query
Q
Q
Q 是当前候选物品 embedding
q
q
q ;Keys/Values
K
V
K V
KV 是 行为序列每个 item 的 embedding
x
i
x_i
xi。总结一下就是把当前候选 item 作为 Query,通过注意力机制动态筛选用户历史行为,从而拟合用户不同情境下的兴趣。
其中的细节是,DIN计算出的注意力权重没有经过Softmax归一化。这意味着 ∑ w i \sum{w_i} ∑wi不一定等于1。这样设计的目的是为了保留用户兴趣的绝对强度。
SIM(长序列建模)
由于在DIN中,候选物品需要对LastN中每一个物品计算一次相似度,也就是说复杂度与序列长度直接相关,导致DIN无法保留长期的序列。要对长序列进行建模,就需要对DIN进行改进。SIM出于这样的考虑,LastN中有很大一部分物品可能是和候选物品q毫无关联的,于是不妨提前对LastN进行过滤,提取出TopK物品再做局部激活。这个过滤的过程就是General Search Unit(GSU,通用搜索单元)。
而根据在GSU中如何搜索,SIM又有Hard Search(硬搜索)和Soft Search(软搜索)两种实现方式。
硬搜索
所谓硬搜索,就是直接简单粗暴地拿候选物料q的某个属性(比如物料分类或标签),在用户完整的长期历史中搜索与其有相同属性的历史物料,组成Subuser Behavior Sequence(SBS,子用户行为序列)。比如当前候选物料是一件衣服,SIM将该用户过去所有购买过的衣服挑选出来,组成针对这个候选物料的SBS。
当然,真正的搜索过程中不可能为每个候选物料把用户全部历史都遍历一遍。为了加速这一过程,阿里巴巴特别设计了User Behavior Tree(UBT,用户行为树)数据库,将每个用户的长期行为序列分门别类地缓存起来。具体实现细节可以去查看原本的论文SIM
虽然简单,但是据SIM原论文中所述,硬搜索的搜索效果和接下来要介绍的软搜索差不多,反而实时性能优秀,维护更加方便。
软搜索
软搜索基于Item Embedding,在用户长期行为序列中通过ANN搜索,查找与之距离最近的前K个历史物料,组成SBS。
至于Item Embedding从何而来?在原论文中,是只用候选物料和长期行为序列构建了一个小模型预测CTR。模型训练完成后的副产品就是Item Embedding。除此之外,用双塔召回/粗排得到的Item Embedding行不行?用Word2Vec算法套用在长期行为序列上得到的Item Embedding行不行?作者觉得,理论上是没问题的,都可以试一试,让离线指标和在线A/B测试的结果来告诉我们具体用哪种是最好的。
从行为序列到会话序列(DSIN,Deep Session Interest Network)
上面的模型关注的都是行为序列,事实上用户的浏览行为常常是会话式的。例如某个时间段用户想买相机,就会搜索一堆相机相关内容;有的时候想打游戏,又会搜索游戏相关内容。行为模型很难体会到这种突然跳跃式的兴趣转变,DSIN将“会话”作为分析用户行为的基本单元,并采用一种分层的思想来建模。

首先,将行为序列根据行为发生的时间间隔等划分为不同的会话,并通过自注意力机制提取出每个会话的兴趣向量 I k I_k Ik(DSIN认为,一个会话内的行为虽然意图集中,但彼此之间的重要性也不同。因此,它没有使用简单的池化,而是采用自注意力机制);(会话兴趣提取层 (Session Interest Extractor Layer))
然后,对于会话兴趣向量序列建立一个双向长短期记忆网络(Bi-LSTM)来对这个会话序列进行建模,从而捕捉不同会话之间的演进和依赖关系。(会话兴趣交互层 (Session Interest Interacting Layer))
由于我不太懂NLP里面这些RNN的内容,以下是我对这一层作用粗略的理解。既然 DSIN 已经对每个 session 得到了兴趣向量,为什么还要在再加一层 Bi-LSTM?
我的理解是Session 之间其实也有顺序关系,如果直接把每个 session 单独变成一个 embedding,然后直接做 attention那么会导致session之间的时序演化信息完全丢失,模型无法区分兴趣是持续还是突然跳变。没有这一层的话,DSIN就是粗粒度的DIN。
最后与DIN的思想一脉相承。模型会根据当前的候选物品,使用注意力机制来计算每个会话兴趣的重要性,并进行加权求和,得到最终的用户兴趣表示。注意DSIN分别对会话兴趣提取层和交互层的输出都进行了激活然后进行拼接,得到用户的最终兴趣表示。
视频播放建模(DNN for Youtube)
Deep Neural Networks for YouTube Recommendation 这篇论文实际上有很多很多内容,实际上我们上文和之前的篇章已经聊过,这里主要讨论一个关键问题:如何对播放时长进行预测?
不同于CTR等概率数值可以直接用Sigmoid拟合,播放时长是一个连续数值量,一般的我们会直接用线性回归(LR)来建模,但这样事实上极度不work。
假设我们用线性模型拟合:
t
^
=
w
⊤
x
+
b
\hat{t} = w^\top x + b
t^=w⊤x+b
并用 MSE 损失:
L
=
(
t
−
t
^
)
2
L = (t - \hat{t})^2
L=(t−t^)2
在这种情况下会遇到这样的问题,不同于其它数值量,t 的分布是极度不平衡的。绝大多数视频观看时长少于30秒,但少数样本t可达几万秒。这就导致 MSE 被少数长尾样本主导,训练很不稳定。并且线性模型输出是无界的,如果预测出现超大值,会严重影响排序得分。
YoutubeDNN的思路是预测
y
=
t
1
+
t
y = \frac{t}{1 + t}
y=1+tt
这样模型最后的输入就可以通过sigmoid函数映射到0到1区间内。
也就是说YouTube 的模型其实预测的是:
y
^
=
sigmoid
(
f
θ
(
x
)
)
\hat{y} = \text{sigmoid}(f_\theta(x))
y^=sigmoid(fθ(x))
然后训练目标是:
L
=
(
y
^
−
t
1
+
t
)
2
L = (\hat{y} - \frac{t}{1+t})^2
L=(y^−1+tt)2
训练完成后,预测时可以反变换:
t
^
=
y
^
1
−
y
^
\hat{t} = \frac{\hat{y}}{1 - \hat{y}}
t^=1−y^y^
得到估计的时长。
其实事实上这种变换就是在模拟用户看完视频的概率。
写在最后
到这里我们就讲完了面经八股中排序阶段大部分常见的模型,事实上,以上这些模型往往都是精排阶段的模型,都需要大规模的神经网络来进行预测。还有一些遗漏的模型后续我会单独开设一个篇章进行补充,我们下期再见。
——排序篇&spm=1001.2101.3001.5002&articleId=155203327&d=1&t=3&u=0a39ce5e32de4ca4a67353d527f30608)

被折叠的 条评论
为什么被折叠?



