假设目前需要对”猫“、”狗“、”狼“等多个物种进行多任务识别。MMOE的做法:假设自身训练出多个领域的”专家“(猫、狗等),再增加一个”阀门“来对不同领域的”专家“预测进行不同的权重分配(”猫专家“对”猫“权重大、对”狗“权重小)来达到多任务训练的效果
多任务学习:基于共享表示,把多个相关的任务放在一起学习的一种机器学习方法。
多任务学习的优势
- Statistical Data Amplification:数据增强:由于多任务之间有一定的相关性,并具有各自的噪声,因此多任务学习相当于是一种隐式的数据增强。
- Attribute Selection:特征注意力:MTL可以帮助模型将注意力集中在重要的特征上,因为其它任务将为这些特征的相关性或不相关性提供额外的证据
- Representation Bias:隐式正则:MTL倾向于学习通用化的表征,类似一种正则化;
- Eavesdropping:模型窃听:某特征G很容易被任务B学习,但难以被另一个任务A学习。通过MTL,可以允许模型“窃听”,即通过任务B来学习特征G;
MMOE之前
- 该方法在相关性较高的多任务之间效果会比较好,且任务越多,单个任务越不容易过拟合,即泛化能力强;
- 缺点是当任务之间不相关时底层共享层难以学到各个任务之间比较通用的特征和模式‘
MMOE算法原理
MMOE是Google的研究人员提出的一种NN模型中多目标优化的模型结构。MMOE为每个模型目标设置一个gate,所有目标共享多个expert,每个expert通常是数层规模比较小的全连接层。gate用来选择每个expert的信号占比。每个expert都有其擅长的预测方向,最后共同作用于上面的多个目标。由于共享参数一定程度上限制了不同目标的特异性,预测目标之间的相关性比较高模型才会具备好的结果。因此MMOE引入门结构作为不同任务之间学习的注意力引入。
模型:
其中 表示每个tower的输出, 表示底层的share bottom, 表示第k个tower的网络
模型:
其中, , 表示 第 个 ,这里的 表示第 个expert
模型:
其中和 模型的区别在于 的 , 模型的 ;
,其中 , 是expert的数量, 是feature的size;
代码实现
class mmoe_layer(Layer): def __init__(self, hidden_units, num_experts, num_tasks, use_expert_bias=True, use_gate_bias=True, **kwargs): super(mmoe_layer, self).__init__(**kwargs) self.hidden_units = hidden_units self.num_experts = num_experts self.num_tasks = num_tasks self.use_expert_bias = use_expert_bias self.use_gate_bias = use_gate_bias def build(self, input_shape): # expert网络, 形状为[input_shape[-1], hidden_units, num_experts] # 每个expert将输入维度从input_shape[-1]映射到hidden_units self.expert_matrix = self.add_weight( name='expert_matrix', shape=(input_shape[-1], self.hidden_units, self.num_experts), trainable=True, initializer=tf.random_normal_initializer(), regularizer=l2(1e-4) ) # expert网络偏置项,形状为[hidden_units, num_experts] if self.use_expert_bias: self.expert_bias = self.add_weight( name='expert_bias', shape=(self.hidden_units, self.num_experts), trainable=True, initializer=tf.random_normal_initializer, regularizer=l2(1e-4) ) # gate网络,每个gate形状为[input_shape[-1], num_experts] # 总共num_tasks个gate,每个对应一个任务 self.gate_matrix = [self.add_weight( name='gate_matrix' + str(i), shape=(input_shape[-1], self.num_experts), trainable=True, initializer=tf.random_normal_initializer(), regularizer=l2(1e-4) ) for i in range(self.num_tasks)] # gate网络偏执项,形状为[num_experts],总共num_tasks个 if self.use_gate_bias: self.gate_bias = [self.add_weight( name='gate_bias' + str(i), shape=(self.num_experts,), trainable=True, initializer=tf.random_normal_initializer(), regularizer=l2(1e-4) ) for i in range(self.num_tasks)] def call(self, inputs, *args, **kwargs): # inputs x expert = [None, input_shape[-1]] x [input_shape[-1], hidden_units, num_experts] # get [None, hidden_units, num_experts] expert_output = [] for i in range(self.num_experts): expert_out = tf.matmul(inputs, self.expert_matrix[:, :, i]) expert_output.append(expert_out) expert_output = tf.transpose( tf.convert_to_tensor(expert_output), [1, 2, 0]) # [None, hidden_units, num_experts] # 加偏执,形状保持不变 if self.use_expert_bias: expert_output += self.expert_bias expert_output = tf.nn.relu(expert_output) # inputs x gate = [None, input_shape[-1]] x [input_shape[-1], num_experts] # num_tasks个gate得到输出列表 num_tasks x [None, num_experts] gate_outputs = [] for i, gate in enumerate(self.gate_matrix): gate_out = tf.matmul(inputs, gate) if self.use_gate_bias: gate_out += self.gate_bias[i] gate_out = tf.nn.softmax(gate_out) gate_outputs.append(gate_out) # list: num_tasks x [None, num_experts] # gate与expert的输出相乘 outputs = [] for gate_out in gate_outputs: gate_out = tf.expand_dims(gate_out, axis=1) # 维度扩展 [None, 1, num_experts] gate_out = tf.tile(gate_out, [1, self.hidden_units, 1]) # 维度复制 [None, hidden_units, num_experts] out = tf.multiply(gate_out, expert_output) # 元素乘 [None, hidden_units, num_experts] out = tf.reduce_sum(out, axis=-1) # 取平均 [None, hidden_units] outputs.append(out) return outputs # list: num_tasks x [None, hidden_units] class tower_layer(Layer): def __init__(self, hidden_units, output_dim, activation='relu'): self.hidden_layer = [Dense(i, activation=activation) for i in hidden_units] self.output_layer = Dense(output_dim, activation=None) super(tower_layer, self).__init__() def call(self, inputs, *args, **kwargs): x = inputs for layer in self.hidden_layer: x = layer(x) output = self.output_layer(x) return output