Delete 4.人工智能 directory
This commit is contained in:
@@ -1,129 +0,0 @@
|
||||
## 背景与动机
|
||||
|
||||
在推荐系统的精排模块,多任务学习的模型结构已成业界的主流,获得了广阔的应用。多任务学习(multi-task learning),本质上是希望使用一个模型完成多个任务的建模。在推荐系统中,多任务学习一般即指多目标学习(multi-label learning),不同目标输入相同的feature进行联合训练,是迁移学习的一种。他们之间的关系如图:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-130d040474f34095ec6d8c81133da538_1440w.jpg" style="zoom:60%;" />
|
||||
</div>
|
||||
|
||||
下面我们先讨论三个问题
|
||||
|
||||
**一、为什么要用多任务学习?**
|
||||
|
||||
(1)很多业界推荐的业务,天然就是一个多目标的建模场景,需要多目标共同优化。以微信视频号推荐为例,打开一个视频,如图,首页上除了由于视频自动播放带来的“播放时长”、“完播率”(用户播放时长占视频长度的比例)目标之外,还有大量的互动标签,例如“点击好友头像”、“进入主页”、“关注”、“收藏”、“分享”、“点赞”、“评论”等。究竟哪一个标签最符合推荐系统的建模目标呢?
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-c18fc1ec65e308ee2e1477d7868007db_1440w.jpg" style="zoom:30%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
如果要用一个词来概括所有各式各样的推荐系统的终极目标,那就是“用户满意度”,但我们无法找到一个显示的指标量化用户满意度。业界一般使用“DAU”、“用户日均使用时长”、“留存率”来作为客观的间接的“用户满意度”(或者说算法工程师绩效)评价指标。而这些指标都是难以通过单一目标建模的,以使用时长为例,长视频播放长度天然大于短视频。所幸的是,虽然没有显式的用户满意度评价指标,但是现在的app都存在类似上述视频号推荐场景的丰富具体的隐式反馈。但这些独立的隐式反馈也存在一些挑战:
|
||||
|
||||
- 目标偏差:点赞、分享表达的满意度可能比播放要高
|
||||
- 物品偏差:不同视频的播放时长体现的满意度不一样,有的视频可能哄骗用户看到尾部(类似新闻推荐中的标题党)
|
||||
- 用户偏差:有的用户表达满意喜欢用点赞,有的用户可能喜欢用收藏
|
||||
|
||||
因此我们需要使用多任务学习模型针对多个目标进行预测,并在线上融合多目标的预测结果进行排序。多任务学习也不能直接表达用户满意度,但是可以最大限度利用能得到的用户反馈信息进行充分的表征学习,并且可建模业务之间的关系,从而高效协同学习具体任务。
|
||||
|
||||
(2)工程便利,不用针对不同的任务训练不同的模型。一般推荐系统中排序模块延时需求在40ms左右,如果分别对每个任务单独训练一个模型,难以满足需求。出于控制成本的目的,需要将部分模型进行合并。合并之后,能更高效的利用训练资源和进行模型的迭代升级。
|
||||
|
||||
**二、为什么多任务学习有效?**
|
||||
|
||||
当把业务独立建模变成多任务联合建模之后,有可能带来四种结果:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-44927ccdd6caf9685d3d9d5367af98dc_1440w.jpg" style="zoom:60%;" />
|
||||
</div>
|
||||
|
||||
多任务学习的优势在于通过部分参数共享,联合训练,能在保证“还不错”的前提下,实现多目标共同提升。原因有以下几种:
|
||||
|
||||
- 任务互助:对于某个任务难学到的特征,可通过其他任务学习
|
||||
- 隐式数据增强:不同任务有不同的噪声,一起学习可抵消部分噪声
|
||||
- 学到通用表达,提高泛化能力:模型学到的是对所有任务都偏好的权重,有助于推广到未来的新任务
|
||||
- 正则化:对于一个任务而言,其他任务的学习对该任务有正则化效果
|
||||
|
||||
**三、多任务学习都在研究什么问题**?
|
||||
|
||||
如上所述,多任务的核心优势在于通过不同任务的网络参数共享,实现1+1>2的提升,因此多任务学习的一大主流研究方向便是如何设计有效的网络结构。多个label的引入自然带来了多个loss,那么如何在联合训练中共同优化多个loss则是关键问题。
|
||||
|
||||
- 网络结构设计:主要研究哪些参数共享、在什么位置共享、如何共享。这一方向我们认为可以分为两大类,第一类是在设计网络结构时,考虑目标间的显式关系(例如淘宝中,点击之后才有购买行为发生),以阿里提出的ESMM为代表;另一类是目标间没有显示关系(例如短视频中的收藏与分享),在设计模型时不考虑label之间的量化关系,以谷歌提出的MMOE为代表。
|
||||
- 多loss的优化策略:主要解决loss数值有大有小、学习速度有快有慢、更新方向时而相反的问题。最经典的两个工作有UWL(Uncertainty Weight):通过自动学习任务的uncertainty,给uncertainty大的任务小权重,uncertainty小的任务大权重;GradNorm:结合任务梯度的二范数和loss下降梯度,引入带权重的损失函数Gradient Loss,并通过梯度下降更新该权重。
|
||||
|
||||
## loss加权融合
|
||||
|
||||
一种最简单的实现多任务学习的方式是对不同任务的loss进行加权。例如谷歌的Youtube DNN论文中提到的一种加权交叉熵:
|
||||
$$
|
||||
\text { Weighted CE Loss }=-\sum_{i}\left[T_{i} y_{i} \log p_{i}+\left(1-y_{i}\right) \log \left(1-p_{i}\right)\right]
|
||||
$$
|
||||
其中![[公式]](https://www.zhihu.com/equation?tex=T_i) 为观看时长。在原始训练数据中,正样本是视频展示后用户点击了该视频,负样本则是展示后未点击,这个一个标准的CTR预估问题。该loss通过改变训练样本的权重,让所有负样本的权重都为 1,而正样本的权重为点击后的视频观看时长 ![[公式]](https://www.zhihu.com/equation?tex=T_i) 。作者认为按点击率排序会倾向于把诱惑用户点击(用户未必真感兴趣)的视频排前面,而观看时长能更好地反映出用户对视频的兴趣,通过重新设计loss使得该模型在保证主目标点击的同时,将视频观看时长转化为样本的权重,达到优化平均观看时长的效果。
|
||||
|
||||
另一种更为简单粗暴的加权方式是人工手动调整权重,例如 0.3\*L(点击)+0.7*L\*(视频完播)
|
||||
|
||||
这种loss加权的方式优点如下:
|
||||
|
||||
- 模型简单,仅在训练时通过梯度乘以样本权重实现对其它目标的加权
|
||||
- 模型上线简单,和base完全相同,不需要额外开销
|
||||
|
||||
缺点:
|
||||
|
||||
- 本质上并不是多目标建模,而是将不同的目标转化为同一个目标。样本的加权权重需要根据AB测试才能确定。
|
||||
|
||||
## Shared-Bottom
|
||||
|
||||
最早的多任务学习模型是底层共享结构(Shared-Bottom),如图所示。
|
||||
|
||||
通过共享底层模块,学习任务间通用的特征表征,再往上针对每一个任务设置一个Tower网络,每个Tower网络的参数由自身对应的任务目标进行学习。Shared Bottom可以根据自身数据特点,使用MLP、DeepFM、DCN、DIN等,Tower网络一般使用简单的MLP。
|
||||
|
||||
代码如下,共享特征embedding,共享底层DNN网络,任务输出层独立,loss直接使用多个任务的loss值之和。
|
||||
|
||||
```python
|
||||
def Shared_Bottom(dnn_feature_columns, num_tasks=None, task_types=None, task_names=None,
|
||||
bottom_dnn_units=[128, 128], tower_dnn_units_lists=[[64,32], [64,32]],
|
||||
l2_reg_embedding=0.00001, l2_reg_dnn=0, seed=1024,dnn_dropout=0,
|
||||
dnn_activation='relu', dnn_use_bn=False):
|
||||
|
||||
features = build_input_features(dnn_feature_columns)
|
||||
inputs_list = list(features.values())
|
||||
|
||||
sparse_embedding_list, dense_value_list = input_from_feature_columns(features, dnn_feature_columns, l2_reg_embedding,seed)
|
||||
#共享输入特征
|
||||
dnn_input = combined_dnn_input(sparse_embedding_list, dense_value_list)
|
||||
#共享底层网络
|
||||
shared_bottom_output = DNN(bottom_dnn_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=seed)(dnn_input)
|
||||
#任务输出层
|
||||
tasks_output = []
|
||||
for task_type, task_name, tower_dnn in zip(task_types, task_names, tower_dnn_units_lists):
|
||||
tower_output = DNN(tower_dnn, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=seed, name='tower_'+task_name)(shared_bottom_output)
|
||||
|
||||
logit = tf.keras.layers.Dense(1, use_bias=False, activation=None)(tower_output)
|
||||
output = PredictionLayer(task_type, name=task_name)(logit)
|
||||
tasks_output.append(output)
|
||||
|
||||
model = tf.keras.models.Model(inputs=inputs_list, outputs=tasks_output)
|
||||
return model
|
||||
```
|
||||
|
||||
优点:
|
||||
|
||||
- 浅层参数共享,互相补充学习,任务相关性越高,模型loss优化效果越明显,也可以加速训练。
|
||||
|
||||
缺点:
|
||||
|
||||
- 任务不相关甚至优化目标相反时(例如新闻的点击与阅读时长),可能会带来负收益,多个任务性能一起下降。
|
||||
|
||||
一般把Shared-Bottom的结构称作“参数硬共享”,多任务学习网络结构设计的发展方向便是如何设计更灵活的共享机制,从而实现“参数软共享”。
|
||||
|
||||
|
||||
|
||||
参考资料:
|
||||
|
||||
[https://developer.aliyun.com/article/793252](https://link.zhihu.com/?target=https%3A//developer.aliyun.com/article/793252)
|
||||
|
||||
https://zhuanlan.zhihu.com/p/291406172
|
||||
|
||||
Gradnorm: Gradient normalization for adaptive loss balancing in deep multitask networks (ICML'2018)
|
||||
|
||||
UWL: Multi-task learning using uncertainty to weigh losses for scene geometry and semantics (CVPR'2018)
|
||||
|
||||
YoutubeDNN: Deep neural networks for youtube recommendations (RecSys'2016)
|
||||
@@ -1,162 +0,0 @@
|
||||
# ESMM
|
||||
|
||||
不同的目标由于业务逻辑,有显式的依赖关系,例如**曝光→点击→转化**。用户必然是在商品曝光界面中,先点击了商品,才有可能购买转化。阿里提出了ESMM(Entire Space Multi-Task Model)网络,显式建模具有依赖关系的任务联合训练。该模型虽然为多任务学习模型,但本质上是以CVR为主任务,引入CTR和CTCVR作为辅助任务,解决CVR预估的挑战。
|
||||
|
||||
## 背景与动机
|
||||
|
||||
传统的CVR预估问题存在着两个主要的问题:**样本选择偏差**和**稀疏数据**。下图的白色背景是曝光数据,灰色背景是点击行为数据,黑色背景是购买行为数据。传统CVR预估使用的训练样本仅为灰色和黑色的数据。
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-2f0df0f6933dd8405c478fcce91f7b6f_1440w.jpg" alt="img" style="zoom:33%;" />
|
||||
</div>
|
||||
|
||||
这会导致两个问题:
|
||||
- 样本选择偏差(sample selection bias,SSB):如图所示,CVR模型的正负样本集合={点击后未转化的负样本+点击后转化的正样本},但是线上预测的时候是样本一旦曝光,就需要预测出CVR和CTR以排序,样本集合={曝光的样本}。构建的训练样本集相当于是从一个与真实分布不一致的分布中采样得到的,这一定程度上违背了机器学习中训练数据和测试数据独立同分布的假设。
|
||||
- 训练数据稀疏(data sparsity,DS):点击样本只占整个曝光样本的很小一部分,而转化样本又只占点击样本的很小一部分。如果只用点击后的数据训练CVR模型,可用的样本将极其稀疏。
|
||||
|
||||
## 解决方案
|
||||
|
||||
阿里妈妈团队提出ESMM,借鉴多任务学习的思路,引入两个辅助任务CTR、CTCVR(已点击然后转化),同时消除以上两个问题。
|
||||
|
||||
三个预测任务如下:
|
||||
|
||||
- **pCTR**:p(click=1 | impression);
|
||||
- **pCVR**: p(conversion=1 | click=1,impression);
|
||||
- **pCTCVR**: p(conversion=1, click=1 | impression) = p(click=1 | impression) * p(conversion=1 | click=1, impression);
|
||||
|
||||
> 注意:其中只有CTR和CVR的label都同时为1时,CTCVR的label才是正样本1。如果出现CTR=0,CVR=1的样本,则为不合法样本,需删除。
|
||||
> pCTCVR是指,当用户已经点击的前提下,用户会购买的概率;pCVR是指如果用户点击了,会购买的概率。
|
||||
|
||||
三个任务之间的关系为:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-7bbeb8767db5d6a157852c8cd4221548_1440w.jpg" alt="img" style="zoom: 50%;" />
|
||||
</div>
|
||||
|
||||
其中x表示曝光,y表示点击,z表示转化。针对这三个任务,设计了如图所示的模型结构:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-6d8189bfe378dc4bf6f0db2ba0255eac_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
如图,主任务和辅助任务共享特征,不同任务输出层使用不同的网络,将cvr的预测值*ctr的预测值作为ctcvr任务的预测值,利用ctcvr和ctr的label构造损失函数:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic3.zhimg.com/80/v2-0098ab4556a8c67a1c12322ea3f89606_1440w.jpg" alt="img" style="zoom: 33%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
该架构具有两大特点,分别给出上述两个问题的解决方案:
|
||||
|
||||
- 帮助CVR模型在完整样本空间建模(即曝光空间X)。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-0b0c6dc7d4c38fa422a2876b7c4cc638_1440w.jpg" alt="img" style="zoom:33%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
从公式中可以看出,pCVR 可以由pCTR 和pCTCVR推导出。从原理上来说,相当于分别单独训练两个模型拟合出pCTR 和pCTCVR,再通过pCTCVR 除以pCTR 得到最终的拟合目标pCVR 。在训练过程中,模型只需要预测pCTCVR和pCTR,利用两种相加组成的联合loss更新参数。pCVR 只是一个中间变量。而pCTCVR和pCTR的数据是在完整样本空间中提取的,从而相当于pCVR也是在整个曝光样本空间中建模。
|
||||
|
||||
- 提供特征表达的迁移学习(embedding层共享)。CVR和CTR任务的两个子网络共享embedding层,网络的embedding层把大规模稀疏的输入数据映射到低维的表示向量,该层的参数占了整个网络参数的绝大部分,需要大量的训练样本才能充分学习得到。由于CTR任务的训练样本量要大大超过CVR任务的训练样本量,ESMM模型中特征表示共享的机制能够使得CVR子任务也能够从只有展现没有点击的样本中学习,从而能够极大地有利于缓解训练数据稀疏性问题。
|
||||
|
||||
模型训练完成后,可以同时预测cvr、ctr、ctcvr三个指标,线上根据实际需求进行融合或者只采用此模型得到的cvr预估值。
|
||||
|
||||
## 总结与拓展
|
||||
|
||||
可以思考以下几个问题
|
||||
|
||||
1. 能不能将乘法换成除法?
|
||||
即分别训练CTR和CTCVR模型,两者相除得到pCVR。论文提供了消融实验的结果,表中的DIVISION模型,比起BASE模型直接建模CTCVRR和CVR,有显著提高,但低于ESMM。原因是pCTR 通常很小,除以一个很小的浮点数容易引起数值不稳定问题。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic3.zhimg.com/80/v2-c0b2c860bd63a680d27c911c2e1ba8a2_1440w.jpg" alt="img" style="zoom:53%;" />
|
||||
</div>
|
||||
|
||||
2. 网络结构优化,Tower模型更换?两个塔不一致?
|
||||
原论文中的子任务独立的Tower网络是纯MLP模型,事实上业界在使用过程中一般会采用更为先进的模型(例如DeepFM、DIN等),两个塔也完全可以根据自身特点设置不一样的模型。这也是ESMM框架的优势,子网络可以任意替换,非常容易与其他学习模型集成。
|
||||
|
||||
3. 比loss直接相加更好的方式?
|
||||
原论文是将两个loss直接相加,还可以引入动态加权的学习机制。
|
||||
|
||||
4. 更长的序列依赖建模?
|
||||
有些业务的依赖关系不止有曝光-点击-转化三层,后续的改进模型提出了更深层次的任务依赖关系建模。
|
||||
|
||||
阿里的ESMM2: 在点击到购买之前,用户还有可能产生加入购物车(Cart)、加入心愿单(Wish)等行为。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-4f9f5508412086315f85d1b7fda733e9_1440w.jpg" alt="img" style="zoom:53%;" />
|
||||
</div>
|
||||
|
||||
相较于直接学习 click->buy (稀疏度约2.6%),可以通过Action路径将目标分解,以Cart为例:click->cart (稀疏 度为10%),cart->buy(稀疏度为12%),通过分解路径,建立多任务学习模型来分步求解CVR模型,缓解稀疏问题,该模型同样也引入了特征共享机制。
|
||||
|
||||
美团的[AITM](https://zhuanlan.zhihu.com/p/508876139/[https://cloud.tencent.com/developer/article/1868117](https://cloud.tencent.com/developer/article/1868117)):信用卡业务中,用户转化通常是一个**曝光->点击->申请->核卡->激活**的过程,具有5层的链路。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-0ecf42e999795511f40ac6cd7b85eccf_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
美团提出了一种自适应信息迁移多任务(**Adaptive Information Transfer Multi-task,AITM**)框架,该框架通过自适应信息迁移(AIT)模块对用户多步转化之间的序列依赖进行建模。AIT模块可以自适应地学习在不同的转化阶段需要迁移什么和迁移多少信息。
|
||||
|
||||
总结:
|
||||
|
||||
ESMM首创了利用用户行为序列数据在完整样本空间建模,并提出利用学习CTR和CTCVR的辅助任务,迂回学习CVR,避免了传统CVR模型经常遭遇的样本选择偏差和训练数据稀疏的问题,取得了显著的效果。
|
||||
|
||||
## 代码实践
|
||||
|
||||
与Shared-Bottom同样的共享底层机制,之后两个独立的Tower网络,分别输出CVR和CTR,计算loss时只利用CTR与CTCVR的loss。CVR Tower完成自身网络更新,CTR Tower同时完成自身网络和Embedding参数更新。在评估模型性能时,重点是评估主任务CVR的auc。
|
||||
|
||||
```python
|
||||
def ESSM(dnn_feature_columns, task_type='binary', task_names=['ctr', 'ctcvr'],
|
||||
tower_dnn_units_lists=[[128, 128],[128, 128]], l2_reg_embedding=0.00001, l2_reg_dnn=0,
|
||||
seed=1024, dnn_dropout=0,dnn_activation='relu', dnn_use_bn=False):
|
||||
|
||||
features = build_input_features(dnn_feature_columns)
|
||||
inputs_list = list(features.values())
|
||||
|
||||
sparse_embedding_list, dense_value_list = input_from_feature_columns(features, dnn_feature_columns, l2_reg_embedding,seed)
|
||||
|
||||
dnn_input = combined_dnn_input(sparse_embedding_list, dense_value_list)
|
||||
|
||||
ctr_output = DNN(tower_dnn_units_lists[0], dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=seed)(dnn_input)
|
||||
cvr_output = DNN(tower_dnn_units_lists[1], dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=seed)(dnn_input)
|
||||
|
||||
ctr_logit = tf.keras.layers.Dense(1, use_bias=False, activation=None)(ctr_output)
|
||||
cvr_logit = tf.keras.layers.Dense(1, use_bias=False, activation=None)(cvr_output)
|
||||
|
||||
ctr_pred = PredictionLayer(task_type, name=task_names[0])(ctr_logit)
|
||||
cvr_pred = PredictionLayer(task_type)(cvr_logit)
|
||||
|
||||
ctcvr_pred = tf.keras.layers.Multiply(name=task_names[1])([ctr_pred, cvr_pred])#CTCVR = CTR * CVR
|
||||
|
||||
model = tf.keras.models.Model(inputs=inputs_list, outputs=[ctr_pred, cvr_pred, ctcvr_pred])
|
||||
return model
|
||||
```
|
||||
|
||||
测试数据集:
|
||||
|
||||
adult:[https://archive.ics.uci.edu/ml/datasets/census+income](https://link.zhihu.com/?target=https%3A//archive.ics.uci.edu/ml/datasets/census%2Bincome)
|
||||
|
||||
将里面两个特征转为label,完成两个任务的预测:
|
||||
|
||||
- 任务1预测该用户收入是否大于50K,
|
||||
- 任务2预测该用户的婚姻是否未婚。
|
||||
|
||||
以上两个任务均为二分类任务,使用交叉熵作为损失函数。在ESMM框架下,我们把任务1作为CTR任务,任务2作为CVR任务,两者label相乘得到CTCVR任务的标签。
|
||||
|
||||
除ESSM之外,之后的MMOE、PLE模型都使用本数据集做测试。
|
||||
|
||||
> 注意上述代码,并未实现论文模型图中提到的field element-wise +模块。该模块实现较为简单,即分别把用户、商品相关特征的embedding求和再拼接,然后输入Tower网络。我们使用数据不具有该属性,暂未区分。
|
||||
|
||||
参考资料:
|
||||
|
||||
https://www.zhihu.com/question/475787809
|
||||
|
||||
https://zhuanlan.zhihu.com/p/37562283
|
||||
|
||||
美团:[https://cloud.tencent.com/developer/article/1868117](https://link.zhihu.com/?target=https%3A//cloud.tencent.com/developer/article/1868117)
|
||||
|
||||
Entire Space Multi-Task Model: An Effective Approach for Estimating Post-Click Conversion Rate (SIGIR'2018)
|
||||
@@ -1,178 +0,0 @@
|
||||
# MMOE
|
||||
|
||||
## 写在前面
|
||||
|
||||
MMOE是2018年谷歌提出的,全称是Multi-gate Mixture-of-Experts, 对于多个优化任务,引入了多个专家进行不同的决策和组合,最终完成多目标的预测。解决的是硬共享里面如果多个任务相似性不是很强,底层的embedding学习反而相互影响,最终都学不好的痛点。
|
||||
|
||||
本篇文章首先是先了解下Hard-parameter sharing以及存在的问题,然后引出MMOE,对理论部分进行整理,最后是参考deepctr简单复现。
|
||||
|
||||
## 背景与动机
|
||||
|
||||
推荐系统中,即使同一个场景,常常也不只有一个业务目标。 在Youtube的视频推荐中,推荐排序任务不仅需要考虑到用户点击率,完播率,也需要考虑到一些满意度指标,例如,对视频是否喜欢,用户观看后对视频的评分;在淘宝的信息流商品推荐中,需要考虑到点击率,也需要考虑转化率;而在一些内容场景中,需要考虑到点击和互动、关注、停留时长等指标。
|
||||
|
||||
模型中,如果采用一个网络同时完成多个任务,就可以把这样的网络模型称为多任务模型, 这种模型能在不同任务之间学习共性以及差异性,能够提高建模的质量以及效率。 常见的多任务模型的设计范式大致可以分为三大类:
|
||||
* hard parameter sharing 方法: 这是非常经典的一种方式,底层是共享的隐藏层,学习各个任务的共同模式,上层用一些特定的全连接层学习特定任务模式。
|
||||
<div align=center>
|
||||
<img src="https://img-blog.csdnimg.cn/ed10df1df313413daf2a6a6174ef4f8c.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA57-75rua55qE5bCPQOW8ug==,size_1,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="在这里插入图片描述" style="zoom:70%;" />
|
||||
</div>
|
||||
这种方法目前用的也有,比如美团的猜你喜欢,知乎推荐的Ranking等, 这种方法最大的优势是Task越多, 单任务更加不可能过拟合,即可以减少任务之间过拟合的风险。 但是劣势也非常明显,就是底层强制的shared layers难以学习到适用于所有任务的有效表达。 **尤其是任务之间存在冲突的时候**。MMOE中给出了实验结论,当两个任务相关性没那么好(比如排序中的点击率与互动,点击与停留时长),此时这种结果会遭受训练困境,毕竟所有任务底层用的是同一组参数。
|
||||
* soft parameter sharing: 硬的不行,那就来软的,这个范式对应的结果从`MOE->MMOE->PLE`等。 即底层不是使用共享的一个shared bottom,而是有多个tower, 称为多个专家,然后往往再有一个gating networks在多任务学习时,给不同的tower分配不同的权重,那么这样对于不同的任务,可以允许使用底层不同的专家组合去进行预测,相较于上面所有任务共享底层,这个方式显得更加灵活
|
||||
* 任务序列依赖关系建模:这种适合于不同任务之间有一定的序列依赖关系。比如电商场景里面的ctr和cvr,其中cvr这个行为只有在点击之后才会发生。所以这种依赖关系如果能加以利用,可以解决任务预估中的样本选择偏差(SSB)和数据稀疏性(DS)问题
|
||||
* 样本选择偏差: 后一阶段的模型基于上一阶段采样后的样本子集训练,但最终在全样本空间进行推理,带来严重泛化性问题
|
||||
* 样本稀疏: 后一阶段的模型训练样本远小于前一阶段任务
|
||||
|
||||
<br>ESSM是一种较为通用的任务序列依赖关系建模的方法,除此之外,阿里的DBMTL,ESSM2等工作都属于这一个范式。 这个范式可能后面会进行整理,本篇文章不过多赘述。
|
||||
|
||||
通过上面的描述,能大体上对多任务模型方面的几种常用建模范式有了解,然后也知道了hard parameter sharing存在的一些问题,即不能很好的权衡特定任务的目标与任务之间的冲突关系。而这也就是MMOE模型提出的一个动机所在了, 那么下面的关键就是MMOE模型是怎么建模任务之间的关系的,又是怎么能使得特定任务与任务关系保持平衡的?
|
||||
|
||||
带着这两个问题,下面看下MMOE的细节。
|
||||
|
||||
## MMOE模型的理论及论文细节
|
||||
MMOE模型结构图如下。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://img-blog.csdnimg.cn/29c5624f2c8a46c097f097af7dbf4b45.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA57-75rua55qE5bCPQOW8ug==,size_2,color_FFFFFF,t_70,g_se,x_16#pic_center" alt="在这里插入图片描述" style="zoom:70%;" />
|
||||
</div>
|
||||
|
||||
这其实是一个演进的过程,首先hard parameter sharing这个就不用过多描述了, 下面主要是看MOE模型以及MMOE模型。
|
||||
|
||||
### 混合专家模型
|
||||
我们知道共享的这种模型结构,会遭受任务之间冲突而导致可能无法很好的收敛,从而无法学习到任务之间的共同模式。这个结构也可以看成是多个任务共用了一个专家。
|
||||
|
||||
先抛开任务关系, 我们发现一个专家在多任务学习上的表达能力很有限,于是乎,尝试引入多个专家,这就慢慢的演化出了混合专家模型。 公式表达如下:
|
||||
$$
|
||||
y=\sum_{i=1}^{n} g(x)_{i} f_{i}(x)
|
||||
$$
|
||||
这里的$y$表示的是多个专家的汇总输出,接下来这个东西要过特定的任务塔去得到特定任务的输出。 这里还加了一个门控网络机制,就是一个注意力网络, 来学习各个专家的重要性权重$\sum_{i=1}^{n} g(x)_{i}=1$。$f_i(x)$就是每个专家的输出, 而$g(x)_i$就是每个专家对应的权重。 虽然感觉这个东西,无非就是在单个专家的基础上多引入了几个全连接网络,然后又给这几个全连接网络加权,但是在我看来,这里面至少蕴含了好几个厉害的思路:
|
||||
1. 模型集成思想: 这个东西很像bagging的思路,即训练多个模型进行决策,这个决策的有效性显然要比单独一个模型来的靠谱一点,不管是从泛化能力,表达能力,学习能力上,应该都强于一个模型
|
||||
2. 注意力思想: 为了增加灵活性, 为不同的模型还学习了重要性权重,这可能考虑到了在学习任务的共性模式上, 不同的模型学习的模式不同,那么聚合的时候,显然不能按照相同的重要度聚合,所以为各个专家学习权重,默认了不同专家的决策地位不一样。这个思想目前不过也非常普遍了。
|
||||
3. multi-head机制: 从另一个角度看, 多个专家其实代表了多个不同head, 而不同的head代表了不同的非线性空间,之所以说表达能力增强了,是因为把输入特征映射到了不同的空间中去学习任务之间的共性模式。可以理解成从多个角度去捕捉任务之间的共性特征模式。
|
||||
|
||||
MOE使用了多个混合专家增加了各种表达能力,但是, 一个门控并不是很灵活,因为这所有的任务,最终只能选定一组专家组合,即这个专家组合是在多个任务上综合衡量的结果,并没有针对性了。 如果这些任务都比较相似,那就相当于用这一组专家组合确实可以应对这多个任务,学习到多个相似任务的共性。 但如果任务之间差的很大,这种单门控控制的方式就不行了,因为此时底层的多个专家学习到的特征模式相差可能会很大,毕竟任务不同,而单门控机制选择专家组合的时候,肯定是选择出那些有利于大多数任务的专家, 而对于某些特殊任务,可能学习的一塌糊涂。
|
||||
|
||||
所以,这种方式的缺口很明显,这样,也更能理解为啥提出多门控控制的专家混合模型了。
|
||||
|
||||
### MMOE结构
|
||||
Multi-gate Mixture-of-Experts(MMOE)的魅力就在于在OMOE的基础上,对于每个任务都会涉及一个门控网络,这样,对于每个特定的任务,都能有一组对应的专家组合去进行预测。更关键的时候,参数量还不会增加太多。公式如下:
|
||||
|
||||
$$
|
||||
y_{k}=h^{k}\left(f^{k}(x)\right),
|
||||
$$
|
||||
where $f^{k}(x)=\sum_{i=1}^{n} g^{k}(x)_{i} f_{i}(x)$. 这里的$k$表示任务的个数。 每个门控网络是一个注意力网络:
|
||||
$$
|
||||
g^{k}(x)=\operatorname{softmax}\left(W_{g k} x\right)
|
||||
$$
|
||||
$W_{g k} \in \mathbb{R}^{n \times d}$表示权重矩阵, $n$是专家的个数, $d$是特征的维度。
|
||||
|
||||
上面的公式这里不用过多解释。
|
||||
|
||||
这个改造看似很简单,只是在OMOE上额外多加了几个门控网络,但是却起到了杠杆般的效果,我这里分享下我的理解。
|
||||
* 首先,就刚才分析的OMOE的问题,在专家组合选取上单门控会产生限制,此时如果多个任务产生了冲突,这种结构就无法进行很好的权衡。 而MMOE就不一样了。MMOE是针对每个任务都单独有个门控选择专家组合,那么即使任务冲突了,也能根据不同的门控进行调整,选择出对当前任务有帮助的专家组合。所以,我觉得单门控做到了**针对所有任务在专家选择上的解耦**,而多门控做到了**针对各个任务在专家组合选择上的解耦**。
|
||||
* 多门控机制能够建模任务之间的关系了。如果各个任务都冲突, 那么此时有多门控的帮助, 此时让每个任务独享一个专家,如果任务之间能聚成几个相似的类,那么这几类之间应该对应的不同的专家组合,那么门控机制也可以选择出来。如果所有任务都相似,那这几个门控网络学习到的权重也会相似,所以这种机制把任务的无关,部分相关和全相关进行了一种统一。
|
||||
* 灵活的参数共享, 这个我们可以和hard模式或者是针对每个任务单独建模的模型对比,对于hard模式,所有任务共享底层参数,而每个任务单独建模,是所有任务单独有一套参数,算是共享和不共享的两个极端,对于都共享的极端,害怕任务冲突,而对于一点都不共享的极端,无法利用迁移学习的优势,模型之间没法互享信息,互为补充,容易遭受过拟合的困境,另外还会增加计算量和参数量。 而MMOE处于两者的中间,既兼顾了如果有相似任务,那就参数共享,模式共享,互为补充,如果没有相似任务,那就独立学习,互不影响。 又把这两种极端给进行了统一。
|
||||
* 训练时能快速收敛,这是因为相似的任务对于特定的专家组合训练都会产生贡献,这样进行一轮epoch,相当于单独任务训练时的多轮epoch。
|
||||
|
||||
OK, 到这里就把MMOE的故事整理完了,模型结构本身并不是很复杂,非常符合"大道至简"原理,简单且实用。
|
||||
|
||||
|
||||
那么, 为什么多任务学习为什么是有效的呢? 这里整理一个看到比较不错的答案:
|
||||
>多任务学习有效的原因是引入了归纳偏置,两个效果:
|
||||
> - 互相促进: 可以把多任务模型之间的关系看作是互相 先验知识,也称为归纳迁移,有了对模型的先验假设,可以更好提升模型的效果。解决数据稀疏性其实本身也是迁移学习的一个特性,多任务学习中也同样会体现
|
||||
> - 泛化作用:不同模型学到的表征不同,可能A模型学到的是B模型所没有学好的,B模型也有其自身的特点,而这一点很可能A学不好,这样一来模型健壮性更强
|
||||
|
||||
## MMOE模型的简单复现之多任务预测
|
||||
### 模型概貌
|
||||
这里是MMOE模型的简单复现,参考的deepctr。
|
||||
|
||||
由于MMOE模型不是很复杂,所以这里就可以直接上代码,然后简单解释:
|
||||
|
||||
```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
|
||||
```
|
||||
这个其实比较简单, 首先是传入封装好的dnn_features_columns, 这个是
|
||||
|
||||
```python
|
||||
dnn_features_columns = [SparseFeat(feat, feature_max_idx[feat], embedding_dim=4) for feat in sparse_features] \
|
||||
+ [DenseFeat(feat, 1) for feat in dense_features]
|
||||
```
|
||||
就是数据集先根据特征类别分成离散型特征和连续型特征,然后通过sparseFeat或者DenseFeat进行封装起来,组成的一个列表。
|
||||
|
||||
传入之后, 首先为这所有的特征列建立Input层,然后选择出离散特征和连续特征来,连续特征直接拼接即可, 而离散特征需要过embedding层得到连续型输入。把这个输入与连续特征拼接起来,就得到了送入专家的输入。
|
||||
|
||||
接下来,建立MMOE的多个专家, 这里的专家直接就是DNN,当然这个可以替换,比如MOSE里面就用了LSTM,这样的搭建模型方式非常灵活,替换起来非常简单。 把输入过多个专家得到的专家的输出,这里放到了列表里面。
|
||||
|
||||
接下来,建立多个门控网络,由于MMOE里面是每个任务会有一个单独的门控进行控制,所以这里的门控网络个数和任务数相同,门控网络也是DNN,接收输入,得到专家个输出作为每个专家的权重,把每个专家的输出加权组合得到门控网络最终的输出,放到列表中,这里的列表长度和task_num对应。
|
||||
|
||||
接下来, 为每个任务建立tower,学习特定的feature信息。同样也是DNN,接收的输入是上面列表的输出,每个任务的门控输出输入到各自的tower里面,得到最终的输出即可。 最终的输出也是个列表,对应的每个任务最终的网络输出值。
|
||||
|
||||
这就是整个MMOE网络的搭建逻辑。
|
||||
|
||||
|
||||
**参考资料**:
|
||||
* [MMOE论文](https://dl.acm.org/doi/pdf/10.1145/3219819.3220007)
|
||||
* [Recommending What Video to Watch Next: A Multitask
|
||||
Ranking System](https://dl.acm.org/doi/pdf/10.1145/3298689.3346997)
|
||||
* [Multitask Mixture of Sequential Experts for User Activity Streams](https://research.google/pubs/pub49274/)
|
||||
* [推荐系统中的多目标学习](https://zhuanlan.zhihu.com/p/183760759)
|
||||
* [推荐精排模型之多目标](https://zhuanlan.zhihu.com/p/221738556)
|
||||
* [Youtube视频推荐中应用MMOE模型](http://t.zoukankan.com/Lee-yl-p-13274642.html)
|
||||
* [多任务学习论文导读:Recommending What Video to Watch Next-A Multitask Ranking System](https://blog.csdn.net/fanzitao/article/details/104525843/)
|
||||
* [多任务模型之MoSE](https://zhuanlan.zhihu.com/p/161628342)
|
||||
@@ -1,390 +0,0 @@
|
||||
# PLE
|
||||
|
||||
**PLE**(Progressive Layered Extraction)模型由腾讯PCG团队在2020年提出,主要为了解决跷跷板问题,该论文获得了RecSys'2020的最佳长论文(Best Lone Paper Award)。
|
||||
|
||||
## 背景与动机
|
||||
|
||||
文章首先提出多任务学习中不可避免的两个缺点:
|
||||
|
||||
- 负迁移(Negative Transfer):针对相关性较差的任务,使用shared-bottom这种硬参数共享的机制会出现负迁移现象,不同任务之间存在冲突时,会导致模型无法有效进行参数的学习,不如对多个任务单独训练。
|
||||
- 跷跷板现象(Seesaw Phenomenon):针对相关性较为复杂的场景,通常不可避免出现跷跷板现象。多任务学习模式下,往往能够提升一部分任务的效果,但同时需要牺牲其他任务的效果。即使通过MMOE这种方式减轻负迁移现象,跷跷板问题仍然广泛存在。
|
||||
|
||||
在腾讯视频推荐场景下,有两个核心建模任务:
|
||||
|
||||
- VCR(View Completion Ratio):播放完成率,播放时间占视频时长的比例,回归任务
|
||||
- VTR(View Through Rate) :有效播放率,播放时间是否超过某个阈值,分类任务
|
||||
|
||||
这两个任务之间的关系是复杂的,在应用以往的多任务模型中发现,要想提升VTR准确率,则VCR准确率会下降,反之亦然。
|
||||
|
||||
上一小节提到的MMOE网络存在如下几个缺点
|
||||
|
||||
- MMOE中所有的Expert是被所有任务所共享,这可能无法捕捉到任务之间更复杂的关系,从而给部分任务带来一定的噪声。
|
||||
- 在复杂任务机制下,MMOE不同专家在不同任务的权重学的差不多
|
||||
- 不同的Expert之间没有交互,联合优化的效果有所折扣
|
||||
|
||||
## 解决方案
|
||||
|
||||
为了解决跷跷板现象,以及优化MMOE模型,PLE在网络结构设计上提出两大改进:
|
||||
|
||||
**一、CGC**(Customized Gate Control) 定制门控
|
||||
|
||||
PLE将共享的部分和每个任务特定的部分**显式的分开**,强化任务自身独立特性。把MMOE中提出的Expert分成两种,任务特定task-specific和任务共享task-shared。保证expert“各有所得”,更好的降低了弱相关性任务之间参数共享带来的问题。
|
||||
|
||||
网络结构如图所示,同样的特征输入分别送往三类不同的专家模型(任务A专家、任务B专家、任务共享专家),再通过门控机制加权聚合之后输入各自的Tower网络。门控网络,把原始数据和expert网络输出共同作为输入,通过单层全连接网络+softmax激活函数,得到分配给expert的加权权重,与attention机制类型。
|
||||
|
||||
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic3.zhimg.com/80/v2-c92975f7c21cc568a13cd9447adc757a_1440w.jpg" style="zoom:40%;" />
|
||||
</div>
|
||||
|
||||
任务A有 ![[公式]](https://www.zhihu.com/equation?tex=m_A) 个expert,任务B有 ![[公式]](https://www.zhihu.com/equation?tex=m_B) 个expert,另外还有 ![[公式]](https://www.zhihu.com/equation?tex=m_S) 个任务A、B共享的Expert。这样对Expert做一个显式的分割,可以让task-specific expert只受自己任务梯度的影响,不会受到其他任务的干扰(每个任务保底有一个独立的网络模型),而只有task-shared expert才受多个任务的混合梯度影响。
|
||||
|
||||
MMOE则是将所有Expert一视同仁,都加权输入到每一个任务的Tower,其中任务之间的关系完全交由gate自身进行学习。虽然MMOE提出的门控机制理论上可以捕捉到任务之间的关系,比如任务A可能与任务B确实无关,则MMOE中gate可以学到,让个别专家对于任务A的权重趋近于0,近似得到PLE中提出的task-specific expert。如果说MMOE是希望让expert网络可以对不同的任务各有所得,则PLE是保证让expert网络各有所得。
|
||||
|
||||
二、**PLE** (progressive layered extraction) 分层萃取
|
||||
|
||||
PLE就是上述CGC网络的多层纵向叠加,以获得更加丰富的表征能力。在分层的机制下,Gate设计成两种类型,使得不同类型Expert信息融合交互。task-share gate融合所有Expert信息,task-specific gate只融合specific expert和share expert。模型结构如图:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-ff3b4aff3511e6e56a3b509f244c5ab1_1440w.jpg" style="zoom:40%;" />
|
||||
</div>
|
||||
|
||||
将任务A、任务B和shared expert的输出输入到下一层,下一层的gate是以这三个上一层输出的结果作为门控的输入,而不是用原始input特征作为输入。这使得gate同时融合task-shares expert和task-specific expert的信息,论文实验中证明这种不同类型expert信息的交叉,可以带来更好的效果。
|
||||
|
||||
三、多任务loss联合优化
|
||||
|
||||
该论文专门讨论了loss设计的问题。在传统的多任务学习模型中,多任务的loss一般为
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-ec1a0ae2a4001fea296662a9a5a1942b_1440w.jpg" style="zoom:33%;" />
|
||||
</div>
|
||||
|
||||
其中K是指任务数, ![[公式]](https://www.zhihu.com/equation?tex=w_k) 是每个任务各自对应的权重。这种loss存在两个关键问题:
|
||||
|
||||
- 不同任务之间的样本空间不一致:在视频推荐场景中,目标之间的依赖关系如图,曝光→播放→点击→(分享、评论),不同任务有不同的样本空间。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic3.zhimg.com/80/v2-bdf39ef6fcaf000924294cb010642fce_1440w.jpg" style="zoom:63%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
PLE将训练样本空间作为全部任务样本空间的并集,在分别针对每个任务算loss时,只考虑该任务的样本的空 间,一般需对这种数据集会附带一个样本空间标签。loss公式如下:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-8defd1e5d1ba896bb2d18bdb1db4e3cd_1440w.jpg" style="zoom:40%;" />
|
||||
</div>
|
||||
|
||||
|
||||
其中, ![[公式]](https://www.zhihu.com/equation?tex=%5Cdelta_%7Bk%7D%5E%7Bi%7D+%5Cin%5C%7B0%2C1%5C%7D%2C+%5Cdelta_%7Bk%7D%5E%7Bi%7D+) 表示样本i是否处于任务k的样本空间。
|
||||
|
||||
- 不同任务各自独立的权重设定:PLE提出了一种加权的规则,它的思想是随着迭代次数的增加,任务的权重应当不断衰减。它为每个任务设定一个初始权重 ![[公式]](https://www.zhihu.com/equation?tex=w_%7Bk%2C0%7D) ,再按该公式进行更新:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-2fbd23599bd2cd62222607e76cb975ec_1440w.jpg" style="zoom:40%;" />
|
||||
</div>
|
||||
|
||||
## 实验
|
||||
|
||||
该论文的一大特点是提供了极其丰富的实验,首先是在自身大规模数据集上的离线实验。
|
||||
|
||||
第一组实验是两个关系复杂的任务VTR(回归)与VCR(分类),如表1,实验结果证明PLE可以实现多任务共赢,而其他的硬共享或者软共享机制,则会导致部分任务受损。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-4a190a8a3bcd810fbe1e810171ddc25c_1440w.jpg" alt="img" style="zoom: 33%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
第二组实验是两个关系简单清晰的任务,CTR与VCR,都是分类任务,且CTR→VCR存在任务依赖关系,如表2,这种多任务下,基本上所有参数共享的模型都能得到性能的提升,而PLE的提升效果最为明显。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-29baaf461d29a4eff32e7ea324ef7f77_1440w.jpg" alt="img" style="zoom: 50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
第三组实验则是线上的A/B Test,上面两组离线实验中,其实PLE相比于其他baseline模型,无论是回归任务的mse,还是分类任务的auc,提升都不是特别显著。在推荐场景中,评估模型性能的最佳利器还是线上的A/B Test。作者在pcg视频推荐的场景中,将部分用户随机划分到不同的实验组中,用PLE模型预估VTR和VCR,进行四周的实验。如表3所示,线上评估指标(总播放完成视频数量和总播放时间)均得到了较为显著的提升,而硬参数共享模型则带对两个指标都带来显著的下降。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic3.zhimg.com/80/v2-d6daf1d58fa5edd9fa96aefd254f71ee_1440w.jpg" alt="img" style="zoom: 50%;" />
|
||||
</div>
|
||||
|
||||
第四组实验中,作者引入了更多的任务,验证PLE分层结构的必要性。如表4,随着任务数量的增加,PLE对比CGC的优势更加显著。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-0b13558bc7e95f601c60a26deaff9acf_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
文中也设计实验,单独对MMOE和CGC的专家利用率进行对比分析,为了实现方便和公平,每个expert都是一个一层网络,每个expert module都只有一个expert,每一层只有3个expert。如图所示,柱子的高度和竖直短线分别表示expert权重的均值和方差。
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic4.zhimg.com/80/v2-557473be41f7f6fa5efc1ff17e21bab7_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
可以看到,无论是 MMoE 还是 ML-MMoE,不同任务在三个 Expert 上的权重都是接近的,但对于 CGC & PLE 来说,不同任务在共享 Expert 上的权重是有较大差异的。PLE针对不同的任务,能够有效利用共享 Expert 和独有 Expert 的信息,解释了为什么其能够达到比 MMoE 更好的训练结果。CGC理论上是MMOE的子集,该实验表明,现实中MMOE很难收敛成这个CGC的样子,所以PLE模型就显式的规定了CGC这样的结构。
|
||||
|
||||
## 总结与拓展
|
||||
|
||||
总结:
|
||||
|
||||
CGC在结构上设计的分化,实现了专家功能的分化,而PLE则是通过分层叠加,使得不同专家的信息进行融合。整个结构的设计,是为了让多任务学习模型,不仅可以学习到各自任务独有的表征,还能学习不同任务共享的表征。
|
||||
|
||||
论文中也对大多数的MTL模型进行了抽象,总结如下图:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-d607cd8e14d4a0fadb4dbef06dc2ffa9_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
不同的MTL模型即不同的参数共享机制,CGC的结构最为灵活。
|
||||
|
||||
可以思考下以下几个问题:
|
||||
|
||||
1. 多任务模型线上如何打分融合?
|
||||
在论文中,作者分享了腾讯视频的一种线上打分机制
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-9a412b82d45877287df2429fc89afac5_1440w.jpg" alt="img" style="zoom:33%;" />
|
||||
</div>
|
||||
|
||||
每个目标的预估值有一个固定的权重,通过乘法进行融合,并在最后未来排除视频自身时长的影响,使用 $ f(videolen)$对视频时长进行了非线性变化。其实在业界的案例中,也基本是依赖乘法或者加法进行融合,爱奇艺曾经公开分享过他们使用过的打分方法:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic1.zhimg.com/80/v2-661030ad194ae2059eace0804ef0f774_1440w.jpg" alt="img" style="zoom: 67%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
在业务目标较少时,通过加法方式融合新增目标可以短期内快速获得收益。但是随着目标增多,加法融合会 逐步弱化各字母表的重要性影响,而乘法融合则具有一定的模板独立性,乘法机制更加灵活,效益更好。融 合的权重超参一般在线上通过A/B test调试。
|
||||
|
||||
2. 专家的参数如何设置?
|
||||
PLE模型存在的超参数较多,其中专家和门控网络都有两种类型。一般来说,task-specific expert每个任务1-2个,shared expert个数在任务个数的1倍以上。原论文中的gate网络即单层FC,可以适当增加,调试。
|
||||
|
||||
3. ESMM、MMOE、PLE模型如何选择?
|
||||
|
||||
- 个人经验,无论任务之间是否有依赖关系,皆可以优先尝试CGC。而多层CGC(即PLE)未必比CGC效果好,且在相同参数规模小,CGC普遍好于MMOE。对于相关性特别差的多任务,CGC相对MMOE而言有多个专有expert兜底。
|
||||
|
||||
- 对于典型的label存在路径依赖的多任务,例如CTR与CVR,可尝试ESMM。
|
||||
|
||||
- 而在业界的实践案例中,更多的是两种范式的模型进行融合。例如美团在其搜索多业务排序场景上提出的模型:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-af16e969a0149aef9c2a1291de5c65d5_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
总框架是ESMM的架构,以建模下单(CVR)为主任务,CTR和CTCVR为辅助任务。在底层的模块中,则使用了CGC模块,提取多任务模式下的特征表达信息。
|
||||
|
||||
4. 不同Tower能否输入不同的特征?不同的expert使用不同的特征?不同的门控使用不同的特征?
|
||||
MMOE、PLE原论文中介绍的模型均是使用同样的原始特征输入各个不同的expert,也输入给第一层的gate。最顶层的Tower网络中则均是由一个gate融合所有expert输出作为输入。在实践中,可以根据业务需求进行调整。
|
||||
|
||||
- 例如上图中美团提出的模型,在CTR的tower下,设置了五个子塔:闪购子网络、买菜子网络、外卖子网络、优选子网络和团好货子网络,并且对不同的子塔有额外输入不同的特征。
|
||||
对于底层输入给expert的特征,美团提出通过增加一个自适应的特征选择门,使得选出的特征对不同的业务权重不同。例如“配送时间”这个特征对闪购业务比较重要,但对于团好货影响不是很大。模型结构如图:
|
||||
|
||||
<div align=center>
|
||||
<img src="https://pic2.zhimg.com/80/v2-2e2370794bbd69ded636a248d8c36255_1440w.jpg" alt="img" style="zoom:50%;" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
特征选择门与控制expert信息融合的gate类似,由一层FC和softmax组成,输出是特征维度的权重。对于每一个特征通过该门都得到一个权重向量,权重向量点乘原始特征的embedding作为expert的输入。
|
||||
|
||||
5. 多任务loss更高效的融合机制
|
||||
|
||||
推荐首先尝试两种简单实用的方法,GrandNorm和UWL,具体实现细节查看下文所附的参考资料。
|
||||
|
||||
- UWL(Uncertainty Weight):通过自动学习任务的uncertainty,给uncertainty大的任务小权重,uncertainty小的任务大权重;
|
||||
- GradNorm:结合任务梯度的二范数和loss下降梯度,引入带权重的损失函数Gradient Loss,并通过梯度下降更新该权重。
|
||||
|
||||
## 代码实践
|
||||
|
||||
主要是分两个层级,在PLE的层级下,由于PLE是分层,上一层是输出是下一层的输入,代码逻辑为:
|
||||
|
||||
```python
|
||||
# build Progressive Layered Extraction
|
||||
ple_inputs = [dnn_input] * (num_tasks + 1) # [task1, task2, ... taskn, shared task]
|
||||
ple_outputs = []
|
||||
for i in range(num_levels):
|
||||
if i == num_levels - 1: # the last level
|
||||
ple_outputs = cgc_net(inputs=ple_inputs, level_name='level_' + str(i) + '_', is_last=True)
|
||||
else:
|
||||
ple_outputs = cgc_net(inputs=ple_inputs, level_name='level_' + str(i) + '_', is_last=False)
|
||||
ple_inputs = ple_outputs
|
||||
```
|
||||
|
||||
其中cgc_net函数则对应论文中提出的CGC模块,我们把expert分成两类,task-specific和task-shared,为了方便索引,expert list中expert的排列顺序为[task1-expert1, task1-expert2,...task2-expert1, task2-expert2,...shared expert 1... ],则可以通过双重循环创建专家网络:
|
||||
|
||||
```python
|
||||
for i in range(num_tasks): #任务个数
|
||||
for j in range(specific_expert_num): #每个任务对应的task-specific专家个数
|
||||
pass
|
||||
```
|
||||
|
||||
注意门控网络也分为两种类型,task-specific gate的输入是每个任务对应的expert的输出和共享expert的输出,我们同样把共享expert的输出放在最后,方便索引
|
||||
|
||||
```python
|
||||
for i in range(num_tasks):
|
||||
# concat task-specific expert and task-shared expert
|
||||
cur_expert_num = specific_expert_num + shared_expert_num
|
||||
# task_specific + task_shared
|
||||
cur_experts = specific_expert_outputs[
|
||||
i * specific_expert_num:(i + 1) * specific_expert_num] + shared_expert_outputs
|
||||
```
|
||||
|
||||
在最后一层中,由于CGC模块的输出需要分别输入给不同任务各自的Tower模块,所以不需要创建task-shared gate。完整代码如下
|
||||
|
||||
```python
|
||||
def PLE(dnn_feature_columns, shared_expert_num=1, specific_expert_num=1, num_levels=2,
|
||||
expert_dnn_hidden_units=(256,), tower_dnn_hidden_units=(64,), gate_dnn_hidden_units=(),
|
||||
l2_reg_embedding=0.00001,
|
||||
l2_reg_dnn=0, seed=1024, dnn_dropout=0, dnn_activation='relu', dnn_use_bn=False,
|
||||
task_types=('binary', 'binary'), task_names=('ctr', 'ctcvr')):
|
||||
"""Instantiates the multi level of Customized Gate Control of Progressive Layered Extraction architecture.
|
||||
:param dnn_feature_columns: An iterable containing all the features used by deep part of the model.
|
||||
:param shared_expert_num: integer, number of task-shared experts.
|
||||
:param specific_expert_num: integer, number of task-specific experts.
|
||||
:param num_levels: integer, number of CGC levels.
|
||||
:param expert_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of expert DNN.
|
||||
:param tower_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of task-specific DNN.
|
||||
:param gate_dnn_hidden_units: list,list of positive integer or empty list, the layer number and units in each layer of gate DNN.
|
||||
:param l2_reg_embedding: float. L2 regularizer strength applied to embedding vector.
|
||||
:param l2_reg_dnn: float. L2 regularizer strength applied to DNN.
|
||||
:param seed: integer ,to use as random seed.
|
||||
:param dnn_dropout: float in [0,1), the probability we will drop out a given DNN coordinate.
|
||||
:param dnn_activation: Activation function to use in DNN.
|
||||
:param dnn_use_bn: bool. Whether use BatchNormalization before activation or not in DNN.
|
||||
:param task_types: list of str, indicating the loss of each tasks, ``"binary"`` for binary logloss, ``"regression"`` for regression loss. e.g. ['binary', 'regression']
|
||||
:param task_names: list of str, indicating the predict target of each tasks
|
||||
:return: a Keras model instance.
|
||||
"""
|
||||
num_tasks = len(task_names)
|
||||
if num_tasks <= 1:
|
||||
raise ValueError("num_tasks must be greater than 1")
|
||||
|
||||
if len(task_types) != num_tasks:
|
||||
raise ValueError("num_tasks must be equal to the length of task_types")
|
||||
|
||||
for task_type in task_types:
|
||||
if task_type not in ['binary', 'regression']:
|
||||
raise ValueError("task must be binary or regression, {} is illegal".format(task_type))
|
||||
|
||||
features = build_input_features(dnn_feature_columns)
|
||||
|
||||
inputs_list = list(features.values())
|
||||
|
||||
sparse_embedding_list, dense_value_list = input_from_feature_columns(features, dnn_feature_columns,
|
||||
l2_reg_embedding, seed)
|
||||
dnn_input = combined_dnn_input(sparse_embedding_list, dense_value_list)
|
||||
|
||||
# single Extraction Layer
|
||||
def cgc_net(inputs, level_name, is_last=False):
|
||||
# inputs: [task1, task2, ... taskn, shared task]
|
||||
specific_expert_outputs = []
|
||||
# build task-specific expert layer
|
||||
for i in range(num_tasks):
|
||||
for j in range(specific_expert_num):
|
||||
expert_network = DNN(expert_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn,
|
||||
seed=seed,
|
||||
name=level_name + 'task_' + task_names[i] + '_expert_specific_' + str(j))(
|
||||
inputs[i])
|
||||
specific_expert_outputs.append(expert_network)
|
||||
|
||||
# build task-shared expert layer
|
||||
shared_expert_outputs = []
|
||||
for k in range(shared_expert_num):
|
||||
expert_network = DNN(expert_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn,
|
||||
seed=seed,
|
||||
name=level_name + 'expert_shared_' + str(k))(inputs[-1])
|
||||
shared_expert_outputs.append(expert_network)
|
||||
|
||||
# task_specific gate (count = num_tasks)
|
||||
cgc_outs = []
|
||||
for i in range(num_tasks):
|
||||
# concat task-specific expert and task-shared expert
|
||||
cur_expert_num = specific_expert_num + shared_expert_num
|
||||
# task_specific + task_shared
|
||||
cur_experts = specific_expert_outputs[
|
||||
i * specific_expert_num:(i + 1) * specific_expert_num] + shared_expert_outputs
|
||||
|
||||
expert_concat = tf.keras.layers.Lambda(lambda x: tf.stack(x, axis=1))(cur_experts)
|
||||
|
||||
# build gate layers
|
||||
gate_input = DNN(gate_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn,
|
||||
seed=seed,
|
||||
name=level_name + 'gate_specific_' + task_names[i])(
|
||||
inputs[i]) # gate[i] for task input[i]
|
||||
gate_out = tf.keras.layers.Dense(cur_expert_num, use_bias=False, activation='softmax',
|
||||
name=level_name + 'gate_softmax_specific_' + task_names[i])(gate_input)
|
||||
gate_out = tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1))(gate_out)
|
||||
|
||||
# gate multiply the expert
|
||||
gate_mul_expert = tf.keras.layers.Lambda(lambda x: reduce_sum(x[0] * x[1], axis=1, keep_dims=False),
|
||||
name=level_name + 'gate_mul_expert_specific_' + task_names[i])(
|
||||
[expert_concat, gate_out])
|
||||
cgc_outs.append(gate_mul_expert)
|
||||
|
||||
# task_shared gate, if the level not in last, add one shared gate
|
||||
if not is_last:
|
||||
cur_expert_num = num_tasks * specific_expert_num + shared_expert_num
|
||||
cur_experts = specific_expert_outputs + shared_expert_outputs # all the expert include task-specific expert and task-shared expert
|
||||
|
||||
expert_concat = tf.keras.layers.Lambda(lambda x: tf.stack(x, axis=1))(cur_experts)
|
||||
|
||||
# build gate layers
|
||||
gate_input = DNN(gate_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn,
|
||||
seed=seed,
|
||||
name=level_name + 'gate_shared')(inputs[-1]) # gate for shared task input
|
||||
|
||||
gate_out = tf.keras.layers.Dense(cur_expert_num, use_bias=False, activation='softmax',
|
||||
name=level_name + 'gate_softmax_shared')(gate_input)
|
||||
gate_out = tf.keras.layers.Lambda(lambda x: tf.expand_dims(x, axis=-1))(gate_out)
|
||||
|
||||
# gate multiply the expert
|
||||
gate_mul_expert = tf.keras.layers.Lambda(lambda x: reduce_sum(x[0] * x[1], axis=1, keep_dims=False),
|
||||
name=level_name + 'gate_mul_expert_shared')(
|
||||
[expert_concat, gate_out])
|
||||
|
||||
cgc_outs.append(gate_mul_expert)
|
||||
return cgc_outs
|
||||
|
||||
# build Progressive Layered Extraction
|
||||
ple_inputs = [dnn_input] * (num_tasks + 1) # [task1, task2, ... taskn, shared task]
|
||||
ple_outputs = []
|
||||
for i in range(num_levels):
|
||||
if i == num_levels - 1: # the last level
|
||||
ple_outputs = cgc_net(inputs=ple_inputs, level_name='level_' + str(i) + '_', is_last=True)
|
||||
else:
|
||||
ple_outputs = cgc_net(inputs=ple_inputs, level_name='level_' + str(i) + '_', is_last=False)
|
||||
ple_inputs = ple_outputs
|
||||
|
||||
task_outs = []
|
||||
for task_type, task_name, ple_out in zip(task_types, task_names, ple_outputs):
|
||||
# build tower layer
|
||||
tower_output = DNN(tower_dnn_hidden_units, dnn_activation, l2_reg_dnn, dnn_dropout, dnn_use_bn, seed=seed,
|
||||
name='tower_' + task_name)(ple_out)
|
||||
logit = tf.keras.layers.Dense(1, use_bias=False, activation=None)(tower_output)
|
||||
output = PredictionLayer(task_type, name=task_name)(logit)
|
||||
task_outs.append(output)
|
||||
|
||||
model = tf.keras.models.Model(inputs=inputs_list, outputs=task_outs)
|
||||
return model
|
||||
```
|
||||
|
||||
参考资料
|
||||
|
||||
Progressive Layered Extraction (PLE): A Novel Multi-Task Learning (MTL) Model for Personalized Recommendations (RecSys'2020)
|
||||
|
||||
https://zhuanlan.zhihu.com/p/291406172
|
||||
|
||||
爱奇艺:[https://www.6aiq.com/article/1624916831286](https://link.zhihu.com/?target=https%3A//www.6aiq.com/article/1624916831286)
|
||||
|
||||
美团:[https://mp.weixin.qq.com/s/WBwvfqOTDKCwGgoaGoSs6Q](https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/WBwvfqOTDKCwGgoaGoSs6Q)
|
||||
|
||||
多任务loss优化:[https://blog.csdn.net/wuzhongqi](https://link.zhihu.com/?target=https%3A//blog.csdn.net/wuzhongqiang/article/details/124258128)
|
||||
|
||||
Reference in New Issue
Block a user