推荐系统的多任务目标
在业务场景中,常常不只有一个业务目标。比如Youtube:用户点击率,完播率,满意度指标等。 在模型中,如果采用一个网络同时完成多个任务,那么就可以把这样的网络模型称为多任务模型,这种模型能在不同任务之间学习共性以及差异性,能够提高建模的质量以及效率。常见的多任务可以分为三类:
- hard parameter sharing:底层是共享的隐藏层,上层用一些特定的全连接层学习特定任务模式

优势:比较简单;减少任务之间过拟合的风险(Task越多,单任务更加不可能过拟合);
劣势:底层强制的shared layers难以学习到适用于所有任务的有效表达。,尤其是两个任务相关性不高的时候。 2. soft parameter sharing:软参数共享,底层不是使用共享的一个shared bottom,而是有多个tower,称为多个专家,对于不同的任务,可以允许使用底层不同的专家组合去进行预测,相较于上面所有任务共享底层更加灵活。MOE -> 一个任务,多个专家网络,一个gate门控进行选择性地组合输出; MMOE -> k个任务,多个专家网络,k个门控进行选择性的输出; PLE -> 将专家分为任务专属专家和共享专家,为每个任务分配专家。
- 任务序列依赖关系建模:适合于不同任务之间有一定的序列依赖关系。比如电商场景里面的点击率ctr和转化率cvr,其中转化率cvr这个行为只有在点击之后才会发生。所以这种依赖关系如果能加以利用。可以解决任务预估中的样本选择偏差(SSB)和数据稀疏性(DS)问题【样本选择偏差:后一阶段的模型基于上一阶段采样后的样本子集训练,但最终在全样本空间进行推理,带来严重泛化性问题;样本稀疏:后一阶段的模型训练样本远小于前一阶段任务】
MOE网络(混合专家)
困难:一个专家在多任务学习上的表达能力很有限。于是引入多个专家,这就慢慢演化出了混合专家模型。
y表示多个专家的汇总输出,g对应f(x)的权重,f是专家网络。
MOE可供参考的思路:
- 模型集成思想:很像bagging的思路,即训练多个模型进行决策,这个决策的有效性显然要比单独一个模型来的靠谱一点,无论是从泛化能力,表达能力,学习能力上,都强于一个模型
- 注意力思想:为了增加灵活性,为不同的模型学习重要性权重,这可能考虑了学习任务的共性模型上,不同的模型学习的模式不同,那么聚合的时候一定不能按照相同的重要度聚合
- multi-head机制:多个专家代表了不同head,而不同的head代表了不同的非线性空间,之所以说表达能力增强了,是因为把输入特征映射到了不同空间学习。
MOE使用了多个混合专家增加了各种表达能力,但是一个门控并不灵活,因为所有的任务最终只能选定一组专家组合,即这个专家组合是在多个任务上综合衡量的结果,并没有针对性了。如果多个任务之间差的很多,单门门控就不行,比较难收敛。
MMOE网络(Multi-gate Mixture-of-Experts)
困难:如果针对多个任务,使用硬共享但是多个任务相似性不是很强,底层的embedding学习反而相互影响,最终都学不好。
解决方法:每个任务都会设计一个门控网络;这样对于每个特定的任务,都能有一组对应的专家组合去进行预测。关键的是,参数不会增加太多。
MMOE的理解:
- 在进行多任务时,只使用一个门控网络会产生冲突,这样结构无法进行权衡
- 灵活的参数共享
- 训练时可以快速收敛
多任务学习为什么有效:
- 互相促进:多任务模型之间可以看作互相先验知识
- 泛化作用:不同模型学到的表征不同,可能A,B模型学到的东西不同;模型的健壮性更强。
python
def MMOE(dnn\_feature\_columns, num\_experts=3, expert\_dnn\_hidden\_units=(256, 128), tower\_dnn\_hidden\_units=(64,),
gate\_dnn\_hidden\_units=(), l2\_reg\_embedding=0.00001, l2\_reg\_dnn=0, dnn\_dropout=0, dnn\_activation='relu',
dnn\_use\_bn=False, task\_types=('binary', 'binary'), task\_names=('ctr', 'ctcvr')):
num_tasks = len(task_names)
# 构建Input层并将Input层转成列表作为模型的输入
input_layer_dict = build_input_layers(dnn_feature_columns)
input_layers = list(input_layer_dict.values())
# 筛选出特征中的sparse和Dense特征, 后面要单独处理
sparse_feature_columns = list(filter(lambda x: isinstance(x, SparseFeat), dnn_feature_columns))
dense_feature_columns = list(filter(lambda x: isinstance(x, DenseFeat), dnn_feature_columns))
# 获取Dense Input
dnn_dense_input = []
for fc in dense_feature_columns:
dnn_dense_input.append(input_layer_dict[fc.name])
# 构建embedding字典
embedding_layer_dict = build_embedding_layers(dnn_feature_columns)
# 离散的这些特特征embedding之后,然后拼接,然后直接作为全连接层Dense的输入,所以需要进行Flatten
dnn_sparse_embed_input = concat_embedding_list(sparse_feature_columns, input_layer_dict, embedding_layer_dict, flatten=False)
# 把连续特征和离散特征合并起来
dnn_input = combined_dnn_input(dnn_sparse_embed_input, dnn_dense_input)
# 建立专家层
expert_outputs = []
for i in range(num_experts):
expert_network = DNN(expert_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=2022, name='expert_'+str(i))(dnn_input)
expert_outputs.append(expert_network)
expert_concat = Lambda(lambda x: tf.stack(x, axis=1))(expert_outputs)
# 建立多门控机制层
mmoe_outputs = []
for i in range(num_tasks): # num_tasks=num_gates
# 建立门控层
gate_input = DNN(gate_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=2022, name='gate_'+task_names[i])(dnn_input)
gate_out = Dense(num_experts, use_bias=False, activation='softmax', name='gate_softmax_'+task_names[i])(gate_input)
gate_out = Lambda(lambda x: tf.expand_dims(x, axis=-1))(gate_out)
# gate multiply the expert
gate_mul_expert = Lambda(lambda x: reduce_sum(x[0] * x[1], axis=1, keep_dims=False), name='gate_mul_expert_'+task_names[i])([expert_concat, gate_out])
mmoe_outputs.append(gate_mul_expert)
# 每个任务独立的tower
task_outputs = []
for task_type, task_name, mmoe_out in zip(task_types, task_names, mmoe_outputs):
# 建立tower
tower_output = DNN(tower_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=2022, name='tower_'+task_name)(mmoe_out)
logit = Dense(1, use_bias=False, activation=None)(tower_output)
output = PredictionLayer(task_type, name=task_name)(logit)
task_outputs.append(output)
model = Model(inputs=input_layers, outputs=task_outputs)
return model
PLE(Progressive Layered Extraction)
多任务学习中的困难:
- 负迁移(Negative Transfer):针对相关性较差的任务 ,使用shared-bottom这种硬参数共享的机制会出现负迁移现象,不同任务之间存在冲突时,会导致模型无法有效进行参数的学习,不如对多个任务单独训练。
- 跷跷板现象(Seesaw Phenomenon):针对相关性较为复杂的场景,通常不可避免出现跷跷板现象。多任务学习模式下,往往能够提升一部分任务的效果,但同时需要牺牲其他任务的效果。
PLE场景
两个核心任务:回归任务:播放完成率VCR【播放时间占视频时长的比例】 分类任务:有效播放率VTR【播放时间是否超过某个阈值】
这两个任务之间的关系是复杂的,以往经验想要提升VCR,就会令VTR下降。
MMOE存在的缺陷
- MMOE中所有的专家网络是被所有任务共享,这可能无法捕捉到任务之间更复杂的关系,从而给部分任务带来一定的噪声.
- 在复杂任务机制下,MMOE不同专家在不同任务的权重学的差不多
- 不同Expert之间没有交互,联合优化的效果有所折扣
PLE解决方案
- CGC(Customized Gate Control)定制门控
PLE将共享的部分和每个任务特定的部分显式的分开,强化任务自身独立特性。把MMOE中提出的Expert分成两种,任务特定task-specific和任务共享task-shared。保证expert"各有所得",更好的降低了弱相关性任务之间参数共享带来的问题。
网络结构如图所示,同样的特征输入分别送往三类不同专家模型(任务A专家,任务B专家,任务共享专家),再通过门控机制加权聚合之后输入各自的Tower网络。门控网络,把原始数据和expert网络输出共同作为输入,通过单层全连接网络+softmax激活函数,得到分配给expert的加权权重,与attention机制类型。

- PLE(Progressive Layered Extraction)分层萃取
其实就是对于CGC网络的多层纵向叠加,以获得更加丰富的表征能力。在分层的机制下,分层的Gate可以选择别的任务输出作为输入,也就是任务之间的选择知识迁移:使用其它专家作为知识补充。实验结果证明这种方法的效果更好。

PLE 多任务的loss优化

PLE总结
CGC -> 专家功能的优化; PLE -> 分层叠加,使得不同专家的信息进行融合。整个结构的设计,是为了让多任务学习模型,不仅可以学习到各自任务独有的表征,还能学习不同任务共享的表征。
论文中也对大多数的MTL模型进行了抽象,总结如下图:

Funrec问题:
- 多任务模型如何打分?

-
专家的参数如何设置?PLE模型存在的超参数较多,其中专家和门控网络都有两种类型。一般来说,task-specific expert每个任务1-2个,shared expert个数在任务个数的1倍以上。原论文中的gate网络即单层FC。
-
ESMM、MMOE、PLE模型选择 优先尝试CGC【PLE的单层结构】 典型的label存在路径依赖的多任务,例如CTR和CVR,可以尝试ESMM