1. 概述
多任务学习(multi-task learning),本质上是希望使用一个模型完成多个任务的建模,在推荐系统中,多任务学习一般即指多目标学习(multi-label learning),不同目标输入相同的feature进行联合训练,是迁移学习的一种。他们之间的关系如图:
2.分类
通过对任务关系的建模,可以将基于多任务学习的推荐系统分为以下几种:
2.1并行任务建模
在这种MTL就是把这些任务分开来,单独建模,不用考虑它们之间是不是有先后顺序的影响。这种模型一般会把目标函数设成损失的加权和,而且这些权重都是固定的。还有些研究用上了注意力机制,来抓取一些可以在不同任务之间共享的特征。
比较有代表模型有:Rank and Rate (RnR)、Multiple Relational Attention Network (MRAN)。
2.2级联任务建模
这些MTL的建模,它们会考虑任务之间是有先后顺序的,就像是多米诺骨牌一样,一个任务的结果会影响下一个任务。这种模型在电商、广告和金融这些领域挺常见的,它们通常会根据用户的行为模式来设定一个序列,比如“先展示商品,然后用户点击,最后购买”。在这个类别里,有几个做得不错的。
代表模型有:Entire Space Multi-task Model (ESMM) Adaptive Information Transfer Multi-task (AITM)
ESMM模型结构如下:
AITM模型结构如下:
2.3辅助任务学习
这种MTL建模技术里,挑一个任务当主角,其他的都算是配角,它们存在的意义就是帮主角提升表现。在好几个任务一起优化的时候,很难做到每个任务都获益。所以,有的MTL技术就是以提高主要任务的性能为目标,哪怕牺牲一些辅助任务的性能。用上整个空间的辅助任务,能在预测主要任务的时候提供更丰富的背景信息。
代表模型有:Multi-gate Mixture-of-Experts (MMoE)、Progressive Layered Extraction (PLE)
3.优点
减少过拟合:在多任务学习框架下,模型通过共享的表示层学习编码更加通用的特征,而不是仅针对单一任务的特征表示。一般来说,神经网络能够从输入数据中提取出有用的特征,这些特征随后将被用于执行特定的任务(如分类或回归等)。如果我们有多个相关的任务,就没有必要重复提取特征,而只需要一次性提取出这些特征,然后将其输入到各个任务专用的模型中进行处理即可。这正是多任务架构的核心思想所在。
提高效率:通过单一模型同时执行多个任务,多任务架构能极大地加快推理过程,对于“效能要求”苛刻的边缘应用场景尤为重要。提升速度和效率的一个常被忽视的好处是,可以通过减少训练和推理阶段的整体计算量来缓解服务器成本的压力
基于正迁移提高效率:在多任务学习中,存在这样一种情况:当将某些任务一同学习时,会导致各个任务的性能都得到提升,这种现象被称为“正迁移”(Positive Transfer)。正迁移的发生源于不同任务之间存在一定的共性和相关性。当模型通过共享表示层同时学习这些相关任务时,任务间的共性知识会在底层得到很好的提炼和内化,从而形成通用的特征表示。
4.使用技巧
1. 整合损失函数:
最简单的办法,我们可以整合不同tasks的loss function,然后简单求和。这种方法存在一些不足,比如当模型收敛时,有一些task的表现比较好,而另外一些task的表现却惨不忍睹。其背后的原因是不同的损失函数具有不同的尺度,某些损失函数的尺度较大,从而影响了尺度较小的损失函数发挥作用。这个问题的解决方案是把多任务损失函数“简单求和”替换为“加权求和”。加权可以使得每个损失函数的尺度一致,但也带来了新的问题:加权的超参难以确定。幸运的是,有一篇论文《Multi-Task Learning Using Uncertainty to Weigh Losses for Scene Geometry and Semantics》通过“不确定性(uncertainty)”来调整损失函数中的加权超参,使得每个任务中的损失函数具有相似的尺度。该算法的keras版本实现,详见github:https://github.com/yaringal/multi-task-learning-example/blob/master/multi-task-learning-example.ipynb
2, 学习率选择
在神经网络的参数中,learning rate是一个非常重要的参数。在实践过程中,我们发现某一个learnig rate=0.001能够把任务A学习好,而另外一个learning rate=0.1能够把任务B学好。选择较大的learning rate会导致某个任务上出现dying relu;而较小的learning rate会使得某些任务上模型收敛速度过慢。怎么解决这个问题呢?对于不同的task,我们可以采用不同的learning rate。
all_variables = shared_vars + a_vars + b_vars
all_gradients = tf.gradients(loss, all_variables)
shared_subnet_gradients = all_gradients[:len(shared_vars)]
a_gradients = all_gradients[len(shared_vars):len(shared_vars + a_vars)]
b_gradients = all_gradients[len(shared_vars + a_vars):]
shared_subnet_optimizer = tf.train.AdamOptimizer(shared_learning_rate)
a_optimizer = tf.train.AdamOptimizer(a_learning_rate)
b_optimizer = tf.train.AdamOptimizer(b_learning_rate)
train_shared_op = shared_subnet_optimizer.apply_gradients(zip(shared_subnet_gradients, shared_vars))
train_a_op = a_optimizer.apply_gradients(zip(a_gradients, a_vars))
train_b_op = b_optimizer.apply_gradients(zip(b_gradients, b_vars))
train_op = tf.group(train_shared_op, train_a_op, train_b_op)
3. 任务A的评估作为其他任务的特征
当我们构建了一个MTL的神经网络时,该模型对于任务A的估计可以作为任务B的一个特征。在前向传播时,这个过程非常简单,因为模型对于A的估计就是一个tensor,可以简单的将这个tensor作为另一个任务的输入。但是后向传播时,存在着一些不同。因为我们不希望任务B的梯度传给任务A。幸运的是,Tensorflow提供了一个API tf.stop_gradient。当计算梯度时,可以将某些tensor看成是constant常数,而非变量,从而使得其值不受梯度影响。
all_gradients = tf.gradients(loss, all_variables, stop_gradients=stop_tensors)