Youtube在2019公布了它的MMoE多目标排序系统《Recommending What Video to Watch Next: A Multitask Ranking System》。
本文主要对 MMoE多目标排序系统
进行描述,同时也将给出MMoE的代码,并进行详细介绍
写在前面
在本文中,介绍了一个大规模多目标排序系统,用于在工业界视频分享平台上推荐下一个要观看的视频。
该系统会面临许多挑战,包括:
存在
多个竞争性的排序目标(ranking objectives)
,以及在user feedback中的
隐式选择偏差(implicit selection biases)
。
为了解决这些挑战,论文探索了多种
软参数共享技术
(soft-parameter sharing techniques),比如:
Multi-gate Mixture-of-Experts,以便对多个排序目标进行有效最优化(optimize)。
另外,本文会采用一个
Wide&Deep框架
来减缓选择偏差(selection biases)。
介绍
在本paper中,描述了一个关于视频推荐的大规模排序系统。也就是说: 在给定用户当前观看的一个视频的情况下,推荐该用户可能会观看和享受的下一个视频
。通常推荐系统会遵循一个two-stage设计: candidate generation、ranking
。该paper主要关注ranking。在该stage,推荐器会具有数百个候选,接着会应用一个复杂的模型来对它们进行排序,并将最可能观看的items推荐给用户。
设计一个真实世界的大规模视频推荐系统充满挑战:
通常有许多不同的、有时甚至 有冲突的待优化目标
。例如,我们想推荐用户点击率高、愿与朋友共享的、包括观看高的视频
在该系统中通常有 隐式偏差(implicit bias)
。例如,一个用户通常点击和观看一个视频,仅仅只因为它的排序高,而不是因为用户最喜欢它。因此,从当前系统的数据生成来进行模型训练会是有偏的,这会造成(feedback loop effect)效应。如何有效和高效地学习减少这样的biases是个开放问题。
为了解决这样的挑战,为ranking system提出了一个有效的多任务神经网络架构,如下图所示。它会扩展Wide&Deep模型,通过采用**Multi-gate Mixture-of-Experts(MMoE) 来进行多任务学习。另外,它会引入一个
浅层塔结构(shallow tower)**来建模和移除选择偏差。都会应用该结构到视频推荐中:给定当前用户观看的视频,推荐下一个要观看的视频。在实验和真实环境中均有较大提升。
如上图,是论文提出的ranking系统的模型架构。它会消费 user logs
作为训练数据,构建Multi-gate Mixture-of-Experts layers来预测两类 user behaviors
,比如:engagement和satisfaction。它会使用一个 side-tower
来纠正 ranking selection bias
。在顶部,会组合多个预测到一个最终的ranking score
特别地,首先将多任务目标分组成两类:
参与度目标(engagement objectives),比如:
用户点击(user clicks),推荐视频的参与度
满意度目标(satisfaction objectives),比如:
用户喜欢一个视频的程度,在推荐上留下一个评分
为了学习和估计多种类型的用户行为,论文使用MMoE来自动化学习那些跨潜在冲突的多目标共享的参数。 Mixture-of-Experts架构
会将input layer模块化成experts,每个expert会关注input的不同部分。这可以提升从复杂特征空间(由多个模块生成)中学到的表示。
接着,通过使用多个 gating network
,每个objective可以选择experts来相互共享或不共享。
为了建模和减小来自有偏训练数据的选择偏差(selection bias,比如:position bias),论文提出了添加一个 shallow tower
到主模型中,如上图左侧所示。shallow tower会将input与selection bias(比如:由当前系统决定的ranking order)相关联,接着输出一个scalar作为一个bias项来服务给主模型的最终预测。 该模型架构会将训练数据中的label分解成两部分
:
1.从主模型中学到的无偏用户效用(unbiased user utility)
2.从shallow tower学到的估计倾向评分(estimated propensity score)
论文提出的模型结构可以被看成是Wide&Deep模型的一个扩展, shallow tower表示Wide部分
。通过直接学习 shallow tower
和 main model
,具有优点:学习selection bias,无需对随机实验resort来获取propensity score。
为了评估,论文提出的ranking系统,设计了offline和live实验来验证以下的效果:
多任务学习
移除一个常见的selection bias (position bias)
对比state-of-art的baseline方法,论文展示了提出的框架的改进,并在Youtube上进行实验。
主要贡献有:
介绍了一种end-to-end的排序系统来进行视频推荐
将ranking问题公式化成一个多目标学习问题,并扩展了Multi-gate Mixture-of-Experts架构来提升在所有objectives上的效果
论文提出使用一个Wide&Deep模型架构来建模和缓和position bias
已经在一个真实世界的大规模视频推荐系统上评估论文所提的方法,以及相应的提升
问题描述
本节,首先描述了推荐下一次要观看的视频的问题,引入了一个two-stage setup。
除了上述提到的使用隐式反馈来构建ranking systems挑战外,对于真实的大规模视频推荐问题,我们需要考虑以下因素:
多模态特征空间(Multimodal feature space)。在一个context-aware个性化推荐系统中,需要从多模态(例如:视频内容、预览图、音频、标题、描述、用户demographics)来学习候选视频的user utility。从多模态特征空间中为推荐学习表示,对比其它机器学习应用来说是独一无二的挑战。它分为两个难点:
为协同过滤学习items的稀疏表示
桥接来自low-level的内容特征中的语义gap,以进行内容过滤(content filtering)
可扩展性(Scalability)。可扩展性相当重要,因为作者正构建一个数十亿用户和视频的推荐系统。模型必须在训练期间有效训练,在serving期间高效运行。尽管ranking system在每个query会对数百个candidates进行打分,真实世界场景的scoring需要实时完成,因为一些query和context信息不仅仅需要学习数十亿items和users的表示,而且需要在serving时高效运行。
回顾下论文中推荐系统的目标是:在给定当前观看的视频和上下文(context)时,提供一个关于视频的ranked list。为了处理多模态特征空间,对于每个视频,会抽取以下特征(比如:视频的meta-data和视频内容信号)来作为它的表示。对于context,会使用以下特征(比如:人口统计学user demographics、设备device、时间time、地点location)。为了处理可扩展性,论文中推荐系统具有两个stages:候选生成、ranking。
候选生成
论文中的视频推荐系统会使用多种候选生成算法,每种算法会捕获query video和candidate video间的某一种相似性。例如,一个算法会通过将query video的topics相匹配来生成candidates;另一个算法则会基于该视频和query video一起被观察的频次来检索candiate videos。作者构建了一个序列模型通过用户历史来生成个性化候选视频。作者也会生成context-aware high recall relevant candiadtes。最后,所有的candidates都会放到一个set中,给ranking system进行打分。
Ranking
Ranking系统会从数百个candidates中生成一个ranked list。不同于candidate generation,它会尝试过滤掉大多数items并只保留相关items,ranking system的目标是提供一个ranked list以便具有最高utility的items可以展示在top前面。因此,使用大多数高级机器学习技术常用的NN结构,以便能足够的建模表现力来学习特征关联和utility关系。
模型结构
系统总览
Ranking system会从两类用户反馈数据中学习:
engagement行为 (比如:
点击和观看)
satisfaction行为 (比如:
喜欢(likes)和dismissals)
给定每个candidate,ranking system会使用该candidate、query和context的的特征作为输入,学习预测多个user behaviors。
对于问题公式,作者采用l2r的框架。会将ranking问题建模成:一个具有多个objectives的分类问题和回归问题的组合。 给定一个query、candidate和context,ranking模型会预测用户采用actions(比如:点击、观看、likes和dismissals)的概率
。
为每个candidate做出预测的方法是point-wise的方法。作为对比,pair-wise或list-wise方法可以在两个或多个candidates的顺序上做出预测。pair-wise或list-wise方法可以被用于潜在提升推荐的多样性(diversity)。然而, 基于serving的考虑主要使用point-wise ranking。在serving时,point-wise ranking很简单,可以高效地扩展到大量candidates上
。作为比较,对于给定的candidates集合,pair-wise或list-wise方法需要对pairs或lists打分多次,以便找到最优的ranked list,限制了它们的可扩展性。
ranking objectives
作者使用user behaviors作为训练labels。 由于用户可以对推荐items具有不同类型的behaviors,ranking system被设计成可以支持多个objectives
。每个objective的目标是预测一种类型的与user utility相关的user behavior。为了描述,以下将objectives分离成两个类别:engagement objectives和satisfaction objectives。
Engagement objectives会捕获user behaviors(比如:clicks和watches)。将这些行为的预测公式化为两种类型的任务:对于像点击这样行为的二元分类任务,以及对于像时长(time spent)相关的行为的回归任务。相似的, 对于satisfaction objectives,将与用户满意度相关的行为预测表示成二元分类任务或者回归任务
。例如,像点击/like这样的行为可以公式化成一个二元分类任务,而像rating这样的行为被公式化成regression任务。对于二元分类任务,会计算cross entropy loss。而对于regression任务,会计算squared loss。
一旦多个ranking objectives和它们的问题类型被定下来,可以为这些预测任务训练一个multitask ranking模型。对于每个candidate,将它们作为多个预测的输入,并 使用一个形如加权乘法的组合函数(combination function)来输出一个组合分(combined score)
。该权值通过 人工调参
,以便在user engagements和user satisfactions上达到最佳效果。
使用MMoE建模任务关系和冲突
多目标的ranking systems常使用一个共享的bottom模型架构。然而,当任务间的关联很低时,这样的 hard-parameter sharing技术有时会伤害到多目标学习
。为了缓和多目标间的冲突,作者采用并扩展了MMoE(Multi-gate Mixture-of-Experts)模型架构。
MMoE是一个soft-parameter sharing模型结构,它的设计是为了建模任务的冲突(conflicts)与关系(relation)。通过在跨多个任务上共享experts,它采用Mixture-of-Experts(MoE)结构到多任务学习中,而 对于每个task也具有一个gating network进行训练
。MMoE layer的设计是为了捕获任务的不同之处,对比起shared-bottom模型它无需大量模型参数。关键思路是,使用MoE layer来替代共享的ReLU layer,并为每个task添加一个独立的gating network。
对于本文的ranking system,作者提出 在一个共享的hidden layer的top上添加experts
,如图下图(b)所示。这是因为MoE layer可以帮助学习来自input的模态信息(modularized information)。当在input layer的top上、或lower hidden layers上直接使用它时,它可以更好地建模多模态特征空间。然而,直接在input layer上应用MoE layer将极大增加模型training和serving的开销。这是因为,通常 input layer的维度要比hidden layers的要更高
。
从上图可以看出,使用MMoE来替换shared-bottom layers。关于expert networks的实现,等同于使用ReLU activations的multilayer perceptrons。给定task k、 prediction、以及最后的hidden layer,对于task k的具有n个experts output的MMoE layer为:,可以用以下的等式表示:
其中:
是一个lower-level shared hidden embedding
是task k的gating network
是第i个entry
是第i个expert
gating networks是使用一个softmax layer的关于input的简单线性转换。
其中是线性变换的自由参数,这里的experts的数目会大些,每个训练样本只利用top experts,使用一个相当小数目的experts。 这样的设置是为了鼓励在多个gating networks间共享experts,并高效进行训练
。
建模和移除Position和Selection Baises
隐式反馈被广泛用于训练l2r模型。大量隐式反馈从user logs中抽取,从而训练复杂的DNN模型。然而,隐式反馈是有偏的,因为它由已经存在的ranking system所生成。Position Bias以及其它类型的selection biases,在许多不同的ranking问题中被研究和验证。
在本文的ranking系统中,query是当前被观看过的视频,candidates是相关视频,用户倾向于点击和观看更接近toplist展示的视频,不管它们实际的user utility——根据观看过的视频的相关度以及用户偏好。 主要目标是移除从ranking模型中移除这样的position bias
。在训练数据中、或者在模型训练期间,建模和减小selection biases可以产生模型质量增益,打破由selection biases产生的feedback loop。
作者提出的模型结构与Wide&Deep模型结构相似,将模型预测分解为两个components:
来自main tower的一个 user-utility component
以及来自shallow tower的一个 bias component
特别的,作者使用对selection bias有贡献的features来训练了一个shallow tower,比如:position bias的position feature,接着将它添加到main model的最终logit中,如下图所示。
在训练中,所有曝光(impressions)的positions都会被使用,有10%的feature drop-out rate来阻止模型过度依赖于position feature。
在serving时,position feature被认为是缺失的(missing)。
为什幺将position feature和device feature相交叉(cross)的原因是: 不同的position biases可以在不同类型的devices上观察到
。
上图添加一个 shallow side tower
来学习 selection bias
(比如:position bias)
实验结果
本节将描述ranking system实验,它会在youtube上推荐next watch的视频。使用由YouTube提供的隐式反馈,训练ranking models,并进行offline和live实验。
Youtube的规模和复杂度是一个完美的测试。它有19亿月活用户。每天会有数千亿的user logs关于推荐结果与用户活动的交互。Youtube的一个核心产品是提供推荐功能,为给定一个观看过的视频推荐接下来要看的,如下图所示,在youtube上推荐watch next。
Gating Network分布
为了进一步理解MMoE是如何帮助 multi-objective optimization
的,作者在每个expert上的每个task在 softmax gating network
中绘制了累积概率。
建模和减小Position Bias
使用用户隐式反馈作为训练数据的一个主要挑战是,很难建模在隐式反馈和true user utility间的gap。使用多种类型的隐式信号和多种ranking objectives,在serving时在item推荐中具有更多把手(knobs)来tune以捕获从模型预测到user utility的转换。然而,仍需要建模和减小在隐式反馈中普遍存在的biases。例如:在用户和当前推荐系统交互中引起的selection biases。
这里,作者使用提出的轻量级模型架构,来评估如何来建模和减小一种类型的selection biases(例如:position bias)。论文的解决方案避免了在随机实验或复杂计算上花费太多开销。
用户隐反馈分析
为了验证在训练数据中存在的position bias,对不同位置做了CTR分析。下图表明,在相对位置1-9的CTR分布。所图所示,可以看到随着位置越来越低,CTR也会降得越来越低。在更高位置上的CTR越高,这是因为推荐更相关items和position bias的组合效果。作者提出的方法会采用一个shallow tower,展示了该方法可以分离user utility和position bias的学习。位置1-9的CTR
Baseline方法
为了评估论文提出的模型架构,使用了以下的baseline方法进行对比。
直接使用position feature做为一个input feature:这种简单方法已经在工业界推荐系统中广泛使用来消除position bias,大多数用于线性l2r rank模型中。
对抗学习(Adversarial learning):受域适应(domain adaptation)和机器学习公平性(machine learning fairness)中Adversarial learning的广泛使用的启发,使用一个相似的技术来引入一个辅助任务(auxiliary task),它可以预测在训练数据中的position。随后,在BP阶段,不让梯度传递到主模型(main model)中,以确保主模型的预测不依赖于position feature。
学到的position biases
下图展示了每个position学到的position biases。从图中可知,越低的position,学到的bias越小。学到的biases会使用有偏的隐式反馈(biased implicit feedback)来估计倾向评分(propensity scores)。使用足够训练数据通过模型训练运行,可以使我们有效学到减小position biases。每个position上学到的position bias
MMoE代码实现
代码地址位于: https://github.com/drawbridge/keras-mmoe/blob/master/mmoe.py, 只需要看该文件中关于 MMoE
的实现即可.
初始化创建 Experts
和 Gate
网络 (代码中删除非重点内容)。
class MMoE(Layer):
"""
Multi-gate Mixture-of-Experts model.
"""
def __init__(self,
units, ## 隐藏层单元个数
num_experts, ## Experts 的个数
num_tasks, ## 任务个数
use_expert_bias=True,
use_gate_bias=True,
expert_activation='relu',
gate_activation='softmax',
## .... 其他参数
**kwargs):
def build(self, input_shape):
"""
这里我们假设输入 tensor 的 shape 为 [B, I]
其中 B 为 Batch_size, I 为输入 Embedding 的大小
隐藏层 units 单元个数使用 E 表示
Experts 网络的个数设为 N
Task 任务的个数设置为 K
"""
assert input_shape is not None and len(input_shape) >= 2
input_dimension = input_shape[-1]
"""
初始化 Experts 网络, 其大小为 [I, E, N],
其中 I 为输入 embedding 的大小, E 为 Experts 网络的输出结果大小,
N 为 Experts 网络的个数
"""
self.expert_kernels = self.add_weight(
name='expert_kernel',
shape=(input_dimension, self.units, self.num_experts),
initializer=self.expert_kernel_initializer,
regularizer=self.expert_kernel_regularizer,
constraint=self.expert_kernel_constraint,
)
"""
初始化 Experts 网络的 Bias, 大小为 [E, N]
"""
if self.use_expert_bias:
self.expert_bias = self.add_weight(
name='expert_bias',
shape=(self.units, self.num_experts),
initializer=self.expert_bias_initializer,
regularizer=self.expert_bias_regularizer,
constraint=self.expert_bias_constraint,
)
"""
初始化 Gate 网络, 注意 Gate 网络的个数和 Task 的个数相同, 均为 K,
因此 self.gate_kernels 列表的大小为 K, 每个 Gate 中 weight 的
大小均为 [I, N], I 为输入 Embedding 的大小, 而 N 为 Experts 网络的个数
Gate 网络的输出结果保存着各 Experts 网络的权重系数
"""
self.gate_kernels = [self.add_weight(
name='gate_kernel_task_{}'.format(i),
shape=(input_dimension, self.num_experts),
initializer=self.gate_kernel_initializer,
regularizer=self.gate_kernel_regularizer,
constraint=self.gate_kernel_constraint
) for i in range(self.num_tasks)]
"""
初始化 Gate 网络的 Bias, self.gate_bias 大小为 K,
每个 Bias 的大小为 (N,)
"""
if self.use_gate_bias:
self.gate_bias = [self.add_weight(
name='gate_bias_task_{}'.format(i),
shape=(self.num_experts,),
initializer=self.gate_bias_initializer,
regularizer=self.gate_bias_regularizer,
constraint=self.gate_bias_constraint
) for i in range(self.num_tasks)]
self.input_spec = InputSpec(min_ndim=2, axes={-1: input_dimension})
super(MMoE, self).build(input_shape)
MMoE 的具体实现如下, 实现 MMoE 网络的前向传播:
看代码之前, 先了解下 tf.tensordot
, 可以参考: tf.tensordot TensorFlow 官方文档, 下面代码中 tf.tensordot(a, b, axes=1)
相当于 tf.tensordot(a, b, axes=[[1], [0]])
.
def call(self, inputs, **kwargs):
"""
"""
gate_outputs = []
final_outputs = []
# f_{i}(x) = activation(W_{i} * x + b), where activation is ReLU according to the paper
"""
inputs 输入 Tensor 的大小为 [B, I],
self.expert_kernels 的大小为 [I, E, N],
其中 I 为输入 embedding 大小, E 为 Experts 网络的输出大小, N 为 Experts 的个数
tf.tensordot(a, b, axes=1) 相当于 tf.tensordot(a, b, axes=[[1],[0]]),
因此 expert_outputs 的大小为 [B, E, N]
"""
expert_outputs = K.tf.tensordot(a=inputs, b=self.expert_kernels, axes=1)
# Add the bias term to the expert weights if necessary
if self.use_expert_bias:
expert_outputs = K.bias_add(x=expert_outputs, bias=self.expert_bias)
"""
加上 Bias 以及通过激活函数 (relu) 后, expert_outputs 大小仍为 [B, E, N]
"""
expert_outputs = self.expert_activation(expert_outputs)
# g^{k}(x) = activation(W_{gk} * x + b), where activation is softmax according to the paper
"""
针对 K 个 Task 分别学习各自的 Gate 网络, 这里采用 for 循环实现,
其中 inputs 的大小为 [B, I],
gate_kernel 的大小为 [I, N], 其中 I 为输入 embedding 的大小,
而 N 为 Experts 的个数. 因此 K.dot 对 inputs 和 gate_kernel 进行矩阵乘法,
得到 gate_output 的大小为 [B, N].
注意 gate_activation 为 softmax, 因此经过 Bias 以及 gate_activation 后,
gate_output 的大小为 [B, N], 保存着各 Experts 网络的权重系数
"""
for index, gate_kernel in enumerate(self.gate_kernels):
gate_output = K.dot(x=inputs, y=gate_kernel)
# Add the bias term to the gate weights if necessary
if self.use_gate_bias:
gate_output = K.bias_add(x=gate_output, bias=self.gate_bias[index])
gate_output = self.gate_activation(gate_output)
gate_outputs.append(gate_output)
# f^{k}(x) = sum_{i=1}^{n}(g^{k}(x)_{i} * f_{i}(x))
"""
gate_outputs 为大小等于 K (任务个数) 的列表, 其中 gate_output 的大小等于 [B, N],
而 expert_outputs 的大小为 [B, E, N];
因此, 首先对 gate_output 使用 expand_dims, 按照 axis=1 进行, 得到
expanded_gate_output 大小为 [B, 1, N];
K.repeat_elements 将 expanded_gate_output 扩展为 [B, E, N],
之后再乘上 expert_outputs, 得到 weighted_expert_output 大小为 [B, E, N];
此时每个 Experts 网络都乘上了对应的系数, 最后只需要对各个 Experts 网络的输出进行加权
求和即可, 因此 K.sum(weighted_expert_output, axis=2) 的结果大小为 [B, E];
"""
for gate_output in gate_outputs:
expanded_gate_output = K.expand_dims(gate_output, axis=1) ## [B, 1, N]
weighted_expert_output = expert_outputs * K.repeat_elements(expanded_gate_output, self.units, axis=1) ## [B, E, N]
final_outputs.append(K.sum(weighted_expert_output, axis=2)) ## [B, E]
return final_outputs
Be First to Comment