一般来说,大部分场景下我们使用更多的都是单任务的模型,即一个模型完成一个任务。如果需要完成多个任务的话,则可以针对每个任务单独训练一个模型,像这样:
似乎完全没有要将多个任务混在一起训练的必要,因为这样极有可能导致参数相互干扰而差于单独训练的效果。
确实,大多数的场景确实没有多任务学习的必要,但是如果你做过推荐系统,就会发现在想要达成某些目标的时候,非得多任务一起上阵不可。就拿给用户推荐视频为例,我们既希望提高用户的点击率,同时也希望提高视频的播放时长,视频点赞、转发等等...,这些目标的达成并非是简单的相辅相成,更多的可能是相互竞争的关系。如果我们只想让模型学习点击率,那么经过训练的模型推荐结果很可能导致标题党和封面党大行其道,真正的好视频却被雪藏了,这显然不是我们希望看到的。反而,如果一味的追求高点赞,也可能就忽略了一些相对冷门或小众佳作。这种对立,对于某些互联网巨头的黄赌毒APP来说尤为严重,那种也许你看起来特别带劲的视频,因为都懂的原因很有可能并不适合转发...。因此,我们无法追求某个单一目标的达成,而需要同时优化这些有利于产品良性循环的任务目标,让它们相互平衡,从而提升用户体验,带来和留住更多的用户。
说起多任务学习,最为常规的思路就是共享底部最抽象的表示层,然后在上层分化出不同的任务:
这实际跟迁移学习有点类似,在图像领域甚是常见,因为图像识别的底层特征往往代表一些像素纹理之类的抽象特征,而跟具体的任务不是特别相关,因此这种低冲突的表示层共享是比较容易出效果的,并且可以减少多任务的计算量。
比如,我们可以很轻松的合并一个识别猫的任务和一个识别狗的任务,因为这两个任务所需要学习的表示很相似,因此同时学好这两个任务是可能的。 但是对于差别比较大的任务来说,例如当你用这种简单的共享底层表示的方式将一个识别车子的任务和一个识别狗的任务合到一起,模型瞬间就懵逼了...
其实从直觉上,我们就能感觉识别车和识别狗的任务相对猫狗的识别任务差异大了很多,因此 Shared Bottom 的方式就不那么有用了。
为了进行不相关任务的多任务学习,很多人做了很多工作都见效甚微,后来就有了Google的这个相当新颖的模型MMoE。
它的脑洞大开之处在于跳出了Shared Bottom那种将整个隐藏层一股脑的共享的思维定式,而是将共享层有意识的(按照数据领域之类的)划分成了多个Expert,并引入了gate机制,得以个性化组合使用共享层。
观察一下上面Shared Bottom的模型结构图和MMoE的图,不难发现,MMoE实际上就是把Shared Bottom层替换成了一个双Gate的MoE层:
我们先来看一下原始的Shared Bottom的方式,假设input为$x$共享的底层网络为$f(x)$,然后将其输出喂到各任务独立输出层$h^k(x)$,其中$k$表示第 $k$个任务的独立输出单元,那么,第个$k$任务的输出即可表示为:
$$y^k=h^k(f(x))$$
而MoE共享层将这个大的Shared Bottom网络拆分成了多个小的Expert网络(如图所示,拆成了三个,并且保持参数个数不变,显然分成多少个Expert、每个多少参数,都是可以根据实际情况自己设定的)。我们把第$i$个Expert网络的运算记为$f_i(x)$,然后Gate操作记为$g(x)$,它是一个$n$元的softmax值($n$是Expert的个数,有几个Expert,就有几元),之后就是常见的每个Expert输出的加权求和,假设MoE的输出为$y$,那么可以表示为:
$$y=\sum\limits_{i=1}^ng(x)_if_i(x)$$
如果只是这样的话,要完成多任务还得像Shared Bottom那样再外接不同的输出层,这样一搞似乎这个MoE层对多任务来说就没什么卵用了,因为它无法根据不同的任务来调整各个Expert的组合权重。所以论文的作者搞了多个Gate,每个任务使用自己独立的Gate,这样便从根源上,实现了网络参数会因为输入以及任务的不同都产生影响。
于是,我们将上面MoE输出稍微改一下,用表示第个任务的们就得到了MMoE的输出表达:
$$y^k=\sum\limits_{i=1}^ng^k(x)_if_i(x)$$
emm...网络结构应该很已经清晰了,当然你也许会比较疑惑gate和Expert到底具体怎么做的,论文也没太说明清楚,然后去看了下相应的代码实现,发现跟我想的完全一样....就是两个非常简单的操作:
把输入通过一个线性变换映射到维,再算个softmax得到每个Expert的权重
简单的基层全连接网络,relu激活,每个Expert独立权重
至于最后到底要怎么连接,要不要跟论文里一样,这些都是个性化自己调整的东西,随自己整就好了。至于损失怎么算,就是几个任务加权求和就可以了。(暂时没看到有什么更加新颖的方法)