推荐系统工程师们常常面临一个棘手难题:当我们需要同时优化点击率(CTR)和观看时长这两个指标时,传统的共享底层网络(Shared-Bottom)结构往往表现不佳。这两个目标看似相关,实则数据分布和模式可能存在显著差异,导致模型在训练过程中出现"任务打架"现象。这种现象在视频推荐、电商推荐等场景中尤为常见——用户可能点击了某个视频(高CTR),但很快跳出(低观看时长);或者某些内容虽然点击率不高,但一旦点击就能产生很长的观看时间。
多任务学习(MTL)的核心思想是通过共享表示来让相关任务相互促进。在理想情况下,相似的任务应该能够共享底层特征表示,从而实现数据效率和模型性能的双重提升。然而现实往往比理论复杂得多:
提示:判断任务是否适合MTL的一个实用方法是计算各任务标签间的皮尔逊相关系数。相关系数低于0.3的任务组合可能需要特殊处理。
Google Research在2018年提出的MMoE(Multi-gate Mixture-of-Experts)架构,正是为解决这些痛点而生。与简单共享底层或完全独立建模不同,MMoE通过两个关键创新实现了灵活的任务协同:
这种结构既保留了参数共享的效率优势,又通过门控机制实现了任务特异性适配,在多个公开数据集和工业级推荐系统中都展现了显著优势。
理解MMoE需要从两个基本概念入手:混合专家(MoE)和多门控机制。我们将通过结构对比和数学表达来揭示其工作原理。
传统MTL模型通常采用以下三种架构之一:
| 架构类型 | 参数共享方式 | 优点 | 缺点 |
|---|---|---|---|
| Shared-Bottom | 完全共享底层 | 参数效率高 | 任务冲突严重 |
| Tower-specific | 仅共享部分特征 | 任务独立性好 | 参数量大 |
| OMoE | 共享专家+单门控 | 平衡效率与灵活 | 门控单一 |
MMoE的创新之处在于为每个任务配备了专属门控网络,形成了"共享专家+专属门控"的混合结构。这种设计带来了几个关键优势:
MMoE的数学表达清晰地展现了其工作原理。对于输入特征x,第k个任务的输出可以表示为:
python复制y_k = h_k(f_k(x)), 其中 f_k(x) = ∑ g_k(x)_i * f_i(x)
式中:
f_i(x):第i个专家网络的输出g_k(x):第k个任务的门控网络输出(softmax归一化)h_k:任务k的特有塔网络维度分析有助于理解模型的参数规模:
W_{n×h×d} (n个专家,输出维度h,输入维度d)W_{k×n×d} (k个任务,n个专家,输入维度d)n×h×d + k×n×d + k×h (相比独立建模大幅减少)这种结构在保持合理参数规模的同时,通过门控网络的灵活组合实现了对不同任务的适配。实验表明,即使任务相关性低至0.2,MMoE仍能保持稳定性能,而Shared-Bottom模型的表现则会显著下降。
理论需要实践验证。下面我们构建一个完整的MMoE实现,用于同时预测CTR和观看时长。假设我们的输入特征包括用户特征、内容特征和上下文特征共128维。
MMoE层的实现是其关键所在。我们通过自定义Keras层来封装专家网络和门控网络:
python复制import tensorflow as tf
from tensorflow.keras.layers import Layer, Dense, Concatenate
class MMoE_Layer(Layer):
def __init__(self, expert_dim, n_expert, n_task):
super(MMoE_Layer, self).__init__()
self.n_task = n_task
# 初始化专家网络(一组全连接层)
self.expert_layers = [Dense(expert_dim, activation='relu')
for _ in range(n_expert)]
# 初始化门控网络(每个任务一个)
self.gate_layers = [Dense(n_expert, activation='softmax')
for _ in range(n_task)]
def call(self, inputs):
# 计算各专家输出 [bs, expert_dim]*n_expert
expert_outputs = [expert(inputs) for expert in self.expert_layers]
expert_outputs = tf.stack(expert_outputs, axis=1) # [bs, n_expert, expert_dim]
# 计算各门控输出 [bs, n_expert]*n_task
gate_outputs = [gate(inputs) for gate in self.gate_layers]
# 组合专家与门控
task_outputs = []
for i in range(self.n_task):
# 门控加权 [bs, 1, n_expert]
gate = tf.expand_dims(gate_outputs[i], axis=1)
# 加权求和 [bs, 1, expert_dim]
weighted_expert = tf.matmul(gate, expert_outputs)
task_outputs.append(tf.squeeze(weighted_expert, axis=1))
return task_outputs # [task1: bs, expert_dim, task2: bs, expert_dim]
基于MMoE层构建完整的双任务推荐模型:
python复制def build_mmoe_model(input_dim=128, expert_dim=64, n_expert=4):
# 输入层
input_layer = tf.keras.Input(shape=(input_dim,))
# MMoE层
mmoe_outputs = MMoE_Layer(expert_dim=expert_dim,
n_expert=n_expert,
n_task=2)(input_layer)
# 任务特定塔网络
# CTR预测任务(二分类)
ctr_output = Dense(32, activation='relu')(mmoe_outputs[0])
ctr_output = Dense(1, activation='sigmoid', name='ctr_out')(ctr_output)
# 观看时长预测任务(回归)
watch_output = Dense(32, activation='relu')(mmoe_outputs[1])
watch_output = Dense(1, activation='relu', name='watch_out')(watch_output)
# 构建模型
model = tf.keras.Model(
inputs=input_layer,
outputs=[ctr_output, watch_output]
)
# 编译模型(多损失函数加权)
model.compile(
optimizer='adam',
loss={
'ctr_out': 'binary_crossentropy',
'watch_out': 'mse'
},
loss_weights=[1.0, 0.5], # 根据任务重要性调整
metrics={
'ctr_out': ['AUC'],
'watch_out': ['mae']
}
)
return model
实际训练MMoE模型时,有几个关键点需要注意:
loss_weights平衡不同任务的重要性python复制# 示例训练代码
model = build_mmoe_model()
history = model.fit(
train_data,
validation_data=val_data,
epochs=20,
batch_size=1024,
callbacks=[
tf.keras.callbacks.EarlyStopping(patience=3),
tf.keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=2)
]
)
可视化门控分布可以帮助理解模型工作原理:
python复制# 提取门控网络权重示例
gate_weights = model.get_layer('mmoe_layer').gate_layers[0].get_weights()[0]
print(f"CTR任务门控分布:{gate_weights}")
基础MMoE已经能解决大部分任务冲突问题,但在工业级推荐系统中,我们还可以进一步优化:
MMoE可以与其他推荐技术栈无缝结合:
| 结合技术 | 实现方式 | 预期收益 |
|---|---|---|
| 特征交叉 | 在输入MMoE前进行显式特征交叉 | 提升特征表达能力 |
| 序列建模 | 用RNN/Transformer替换部分专家 | 捕捉用户行为序列 |
| 强化学习 | 用策略网络调整门控分布 | 实现长期收益优化 |
在实际生产环境中部署MMoE模型时,需要注意:
python复制# 生产环境优化示例(使用TF Serving)
model.save('mmoe_model', save_format='tf')
在千万级用户的视频推荐系统中,采用MMoE结构后,我们观察到以下改进: