契机
多任务学习一开始比较流行的模型是share-bottom模型,即不同的任务共享一份底层特征(如下图所示),这样做能够令不同任务之间共享信息,但如果任务之间相关度不高,不同塔相似度较低,这样做的效果就会大打折扣。MMOE模型能通过gates和experts的配合来解决上述问题。
![](https://i-blog.csdnimg.cn/blog_migrate/abdee7133a4edca1be210bbc49717406.png#pic_center)
模型结构
MMOE结构是由MOE衍化而来的,因而下文会先讲解MOE,然后再具体解释MMOE的工作原理。
MOE
MOE的全称为Mixture of Experts,其底层由一个长度为N的gate向量 G ( x ) G(x) G(x)和N个expert向量 E i ( x ) E_i(x) Ei(x)组成,运行流程如下:
- 每个expert共享底层输入 x x x,其中第 i i i个expert经过若干全连接层后得到对应的 E i ( x ) E_i(x) Ei(x)
- E i ( x ) E_i(x) Ei(x)与gate向量的第 i i i维向量相乘得到加权输出 G ( x ) i ∗ E i ( x ) G(x)_i * E_i(x) G(x)i∗Ei(x)
- 将所有的加权输出相加得到最终的输出, y = ∑ i N G ( x ) i ∗ E i ( x ) y=\sum_i^N G(x)_i * E_i(x) y=∑iNG(x)i∗Ei(x)
- 将 y y y输入到指定塔,经过若干全连接层后得到塔的最终输出。
![](https://i-blog.csdnimg.cn/blog_migrate/04e29541e2a7bea9a4342f9263b8f7be.png#pic_center)
这里需要注意的是,某一个 x x x计算的结果只会输出到某一个塔上(例如towerA),且不同的 x x x会对应不同的 G ( x ) G(x) G(x)和 E ( x ) E(x) E(x)。
MOE模型中,由gate来控制experts的输出,从而动态地决定哪些experts对某一个塔比较重要,这样即使不同塔的相似较低,experts也能够学习地较好,因为每个expert的学习是相互独立的,这一点很重要。
MMOE
MMOE的全称为Multi-gate Mixture-of-Experts,从名字上可以看出,MMOE是MOE的升级版。它的改进手段相对直观,具体来说,把原先单一gate向量变为M个gate向量,M为塔的个数,示意图如下所示。
这里需要注意的是,上图中towerA和towerB所用到的experts是同一套experts。可以看出,相比于MOE,MMOE对gates和experts做了进一步的解耦,模型对于不同相似度塔的协同学习处理的更好,而且更重要的一点在于MMOE能够得到 x x x相对于每一个塔的得分,这是一个很大的进步。
模型代码
具体应用时,MMOE的自由度会比较大,最一开始gates和experts的输入都是全部底层特征,有时gates底层输入为item侧特征,experts底层输入为全量特征,这种组合下模型的指标会有进一步提升,不过这些都是视业务场景而定的,我们的代码实现如下所示。
# coding=utf-8
import tensorflow as tf
class MMOE(object):
def __call__(self, expert_input, gate_input):
self.target_num = 2
self.expert_num = 4 # expert个数
self.es = 32 # embedding_size
# experts
expert_list = []
for i in range(self.expert_num):
expert = expert_input
for n in [128, 64]:
expert = tf.layers.dense(expert, units=n,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1), activation=tf.nn.relu)
expert = tf.layers.dense(expert, units=self.es,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1))
expert_list.append(expert)
experts = tf.reshape(tf.stack(expert_list, axis=1), [-1, self.es, self.expert_num])
print("experts: %s" % experts)
# gates
gate_list = []
for i in range(self.target_num):
gate_net = gate_input
for n in [128, 32]:
gate_net = tf.layers.dense(gate_net, units=n,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1), activation=tf.nn.relu)
gate_net = tf.nn.softmax(tf.layers.dense(gate_net, self.expert_num,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1)))
gate_list.append(gate_net)
gates = tf.reshape(tf.stack(gate_list, axis=1), [-1, self.expert_num, len(gate_list)])
# merge
merge = tf.reshape(tf.matmul(experts, gates), [-1, self.target_num, self.es])
# towers
towers = merge
for n in [32, 16]:
towers = tf.layers.dense(towers, units=n,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1), activation=tf.nn.relu)
towers = tf.nn.softmax(tf.layers.dense(towers, units=1,
kernel_initializer=tf.truncated_normal_initializer(stddev=0.1),
bias_initializer=tf.constant_initializer(0.1)))
return tf.squeeze(towers, axis=2)