让一个模型同时学会多种任务——从短视频推荐到电商转化预测

举报
yd_276081655 发表于 2026/10/20 17:18:49 2026/10/20
【摘要】 在实际业务中,我们经常希望一个模型同时完成多个预测任务。例如,短视频推荐不仅要预测用户是否会看完视频,还要预测是否会点赞;电商推荐既要关注点击率,也要关注最终购买率。如果分别训练多个模型,可能无法充分利用任务之间的关联;如果强行共享同一个神经网络,又可能出现任务相互干扰的问题。

MMoE(Multi-gate Mixture-of-Experts,多门控混合专家网络)就是为这类多任务学习问题设计的架构。它通过共享专家网络、为不同任务设置独立门控,实现任务间的知识共享与差异化学习。

本文结合短视频推荐和电商转化预测两个典型案例,介绍 MMoE 的原理、PyTorch 实践方法,以及它在工程落地时需要注意的问题。

一、MMoE 究竟解决什么问题?

MMoE 来源于 2018 年 KDD 论文《Modeling Task Relationships in Multi-task Learning with Multi-gate Mixture-of-Experts》,最初主要面向多任务推荐系统。

理解 MMoE,先要理解传统多任务学习的困境。

假设某个短视频推荐系统有两个任务:

  • 任务 A:预测用户是否看完视频。
  • 任务 B:预测用户是否点赞视频。

这两个任务有一定相关性,但并不完全一致。一个用户可能认真看完一段视频,却并不喜欢它;也可能因为内容很有趣,刚看几秒就点赞。

传统 Shared-Bottom 多任务学习架构会让两个任务共用底层特征提取网络,再分别输出预测结果。

问题是,共享网络的参数同时受到两个任务的梯度影响。当任务目标存在冲突时,某个任务的优化可能损害另一个任务,这就是所谓的负迁移(Negative Transfer)。

MMoE 的核心思路是:共享多个专家网络,但让每个任务自己决定如何组合这些专家。

MMoE 的三个核心组件

                输入特征 X
                    |
          +---------+---------+
          |         |         |
       Expert 1  Expert 2  Expert 3
          |         |         |
          +---------+---------+
                    |
          +---------+---------+
          |                   |
       Gate A              Gate B
          |                   |
     加权组合专家          加权组合专家
          |                   |
       Tower A             Tower B
          |                   |
       完播概率             点赞概率

Expert(专家网络):多个可训练的神经网络,共同提取输入特征。它们并不是人为指定的“完播专家”或“点赞专家”,而是在联合训练中学习不同的特征表示。

Gate(门控网络):每个任务拥有自己的 Gate,根据输入特征动态计算不同专家的权重。

Tower(任务塔):接收门控加权后的特征,进一步学习该任务特有的规律,最终输出预测结果。

以某个用户样本为例,假设三个专家的门控权重为:

专家 完播任务 Gate 点赞任务 Gate
Expert 1 0.6 0.1
Expert 2 0.3 0.2
Expert 3 0.1 0.7

这是一组示意权重,并非实验数据。

可以看到,虽然两个任务使用相同的专家网络,但它们关注的专家不同,而且这些权重还可以随输入样本变化。

其数学表达式为:

[
h_k(x)=\sum_{i=1}^{N}g_{k,i}(x)E_i(x)
]

其中:

  • (E_i(x)):第 (i) 个专家网络输出的特征。
  • (g_{k,i}(x)):任务 (k) 分配给专家 (i) 的门控权重。
  • (h_k(x)):任务 (k) 最终获得的混合特征。

门控权重通常通过 Softmax 计算,并满足同一任务下所有专家权重之和为 1。

需要注意,MMoE 与大语言模型中用于条件计算的稀疏 MoE 并不完全相同。标准 MMoE 通常会计算全部专家,再进行加权求和。它主要解决任务之间的信息共享问题,并不天然具有稀疏 MoE 的计算节省优势。


二、实践案例一:短视频推荐——同时预测完播与点赞

1. 业务场景

这是一个能够找到公开代码和样例数据的典型 MMoE 应用。

开源项目 DeepCTR-Torch 提供了基于 ByteRec 样例数据的多任务学习演示,同时预测:

  • finish:用户是否完播视频。
  • like:用户是否点赞视频。

原始数据中还包括用户 ID、视频 ID、作者 ID、城市、设备、视频时长等特征。

过去可能需要两个独立模型分别预测完播与点赞。现在可以让它们共享 MMoE 的专家网络,同时保留各自的任务塔。

2. 环境与数据准备

安装依赖:

pip install deepctr-torch torch pandas scikit-learn

公开项目:

https://github.com/shenweichen/DeepCTR-Torch

进入项目的 docs/source/Examples.md,找到 MultiTask Learning:MMOE,下载对应的 byterec_sample.txt 样例数据。

3. Python 实践

下面使用 DeepCTR-Torch 的 MMoE 模型。为避免测试集信息进入训练阶段,类别编码和数值归一化仅在训练数据上拟合。

import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import roc_auc_score

from deepctr_torch.inputs import SparseFeat, DenseFeat
from deepctr_torch.models import MMOE

# 1. 读取数据
columns = [
    "uid", "user_city", "item_id", "author_id",
    "item_city", "channel", "finish", "like",
    "music_id", "device", "time", "duration_time"
]

df = pd.read_csv(
    "byterec_sample.txt",
    sep="\t",
    header=None,
    names=columns
)

# 2. 按时间划分训练集和测试集
df = df.sort_values("time").reset_index(drop=True)
boundary = int(len(df) * 0.8)
train = df.iloc[:boundary].copy()
test = df.iloc[boundary:].copy()

sparse = [
    "uid", "user_city", "item_id", "author_id",
    "item_city", "channel", "music_id", "device"
]
dense = ["duration_time"]
targets = ["finish", "like"]

# 3. 类别特征编码,测试集未知类别编码为 0
feature_columns = []

for col in sparse:
    train[col] = train[col].fillna("UNK").astype(str)
    vocabulary = {
        value: i + 1
        for i, value in enumerate(train[col].unique())
    }

    test[col] = (
        test[col].fillna("UNK").astype(str)
        .map(vocabulary).fillna(0).astype("int64")
    )
    train[col] = train[col].map(vocabulary).astype("int64")

    feature_columns.append(
        SparseFeat(col, len(vocabulary) + 1, embedding_dim=4)
    )

# 4. 数值特征处理
for col in dense:
    train[col] = pd.to_numeric(train[col], errors="coerce")
    test[col] = pd.to_numeric(test[col], errors="coerce")
    median = train[col].median()
    train[col] = train[col].fillna(median)
    test[col] = test[col].fillna(median)

scaler = MinMaxScaler()
train[dense] = scaler.fit_transform(train[dense])
test[dense] = scaler.transform(test[dense])

feature_columns += [DenseFeat(col, 1) for col in dense]

train_x = {c: train[c].values for c in sparse + dense}
test_x = {c: test[c].values for c in sparse + dense}

# 5. 创建 MMoE 模型
model = MMOE(
    dnn_feature_columns=feature_columns,
    num_experts=4,
    expert_dnn_hidden_units=(64, 32),
    tower_dnn_hidden_units=(16,),
    task_types=("binary", "binary"),
    task_names=("finish", "like"),
    device="cpu"
)

model.compile(
    "adam",
    loss=["binary_crossentropy", "binary_crossentropy"]
)

# 6. 模型训练
model.fit(
    train_x,
    train[targets].values,
    batch_size=256,
    epochs=5,
    validation_split=0.1
)

# 7. 分别评价两个任务
pred = model.predict(test_x, batch_size=256)

for i, name in enumerate(targets):
    if test[name].nunique() == 2:
        auc = roc_auc_score(test[name], pred[:, i])
        print(f"{name} AUC: {auc:.4f}")

这段代码最值得注意的是:task_types 定义了两个二分类任务,而 task_names 分别指定完播和点赞。

虽然两个任务使用同一批输入特征,但模型会学习不同的 Gate 和 Tower。

最后获得两个预测概率,可以分别用于推荐排序策略。

在实际业务中,假设两个任务同样重要,可以先使用:

[
L=L_{\text{finish}}+L_{\text{like}}
]

如果业务更重视完播,则可以调整为:

[
L=0.7L_{\text{finish}}+0.3L_{\text{like}}
]

这里的权重仅作示例。实际调整时,需要考虑任务损失的数值尺度,不能将权重直接等同于业务重要性的百分比。

4. 为什么要用 MMoE?

完播和点赞任务具有共享信息,例如用户兴趣、视频内容和作者特征。但二者也具有不同规律。

MMoE 能够同时学习共享表示与任务专属的特征组合。

是否优于两个独立模型,需要在同一数据划分、相近训练预算和统一评价指标下进行实验验证,不能仅凭架构判断。


三、实践案例二:电商推荐——同时预测点击率与购买率

1. 业务场景

第二个案例换成电商商品推荐。

假设平台希望预测两个目标:

  • CTR:商品被曝光后,用户点击它的概率。
  • CTCVR:商品被曝光后,在归因窗口内产生购买的概率。

注意,这里的第二个任务并不是“点击之后的条件购买概率”,而是曝光到购买的联合转化概率。

这是两个相关但不同的任务。

对于价格较高的商品,用户可能愿意点击查看,但最终购买的可能性较低;对于刚需商品,点击概率和购买概率可能呈现另一种关系。

如果只预测 CTR,推荐系统容易偏向吸引眼球但缺少购买价值的商品。

因此,我们可以尝试同时预测点击与购买。

2. 从零实现简化版 MMoE

为了理解其内部结构,这次不调用封装好的 MMoE,而是直接使用 PyTorch。

安装依赖:

pip install torch numpy scikit-learn

以下代码使用模拟数据,展示完整的专家网络、门控网络、任务塔和训练流程。模拟数据用于验证架构与运行逻辑,不代表真实电商平台的效果。

import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score

torch.manual_seed(42)
rng = np.random.default_rng(42)

# 1. 构造模拟数据
n = 8000
x = rng.normal(size=(n, 4)).astype("float32")

sigmoid = lambda z: 1 / (1 + np.exp(-z))

# 模拟点击概率与点击后的购买概率
p_click = sigmoid(
    0.9*x[:, 0] - 0.6*x[:, 1]
    + 0.3*x[:, 2] + 0.4*x[:, 3]
)

p_buy_given_click = sigmoid(
    1.1*x[:, 0] - 0.9*x[:, 1]
    + 0.8*x[:, 2] - 1.0
)

click = rng.binomial(1, p_click)
buy = click * rng.binomial(1, p_buy_given_click)

y = np.stack([click, buy], axis=1).astype("float32")

x_train, x_test, y_train, y_test = train_test_split(
    x, y, test_size=0.2, random_state=42
)

# 2. 定义 MMoE
class MMoE(nn.Module):
    def __init__(self, input_dim, num_experts=4, hidden=32):
        super().__init__()

        # 共享专家网络
        self.experts = nn.ModuleList([
            nn.Sequential(
                nn.Linear(input_dim, hidden),
                nn.ReLU()
            )
            for _ in range(num_experts)
        ])

        # 每个任务独立的 Gate
        self.gates = nn.ModuleList([
            nn.Linear(input_dim, num_experts)
            for _ in range(2)
        ])

        # 每个任务独立的 Tower
        self.towers = nn.ModuleList([
            nn.Linear(hidden, 1)
            for _ in range(2)
        ])

    def forward(self, x):
        expert_out = torch.stack(
            [expert(x) for expert in self.experts],
            dim=1
        )

        outputs = []

        for gate, tower in zip(self.gates, self.towers):
            weight = torch.softmax(gate(x), dim=1)
            mixed = (
                expert_out * weight.unsqueeze(-1)
            ).sum(dim=1)

            outputs.append(tower(mixed))

        return torch.cat(outputs, dim=1)

# 3. 训练
model = MMoE(input_dim=4)
optimizer = torch.optim.Adam(model.parameters(), lr=0.002)

X = torch.tensor(x_train)
Y = torch.tensor(y_train)

for epoch in range(20):
    for ids in torch.randperm(len(X)).split(256):
        logits = model(X[ids])

        loss = F.binary_cross_entropy_with_logits(
            logits, Y[ids]
        )

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

# 4. 测试
model.eval()

with torch.no_grad():
    pred = model(torch.tensor(x_test)).sigmoid().numpy()

for i, name in enumerate(["CTR", "CTCVR"]):
    auc = roc_auc_score(y_test[:, i], pred[:, i])
    print(f"{name} AUC: {auc:.4f}")

3. 如何理解结果?

输出会分别显示 CTR 和 CTCVR 的 AUC。

AUC 衡量模型将正样本排在负样本前面的能力。AUC 越高,通常说明模型的区分能力越强。

上述合成数据具有预先设定的规律,因此模型相对容易学到有效的预测关系。

真正有意义的不是获得某个漂亮的 AUC 数字,而是比较三种方案:

  1. 两个完全独立的单任务模型。
  2. 一个 Shared-Bottom 多任务模型。
  3. 一个 MMoE 多任务模型。

如果 MMoE 在两个任务上都能取得更好的泛化表现,就说明它的共享机制确实产生了价值。

这里还有一个容易忽略的问题:虽然训练数据规定购买必须发生在点击之后,但上述两个输出头彼此独立,并不保证预测值始终满足 (P(\text{购买})\leq P(\text{点击}))。

如果业务目标是严谨地估计点击后转化率,可以进一步研究 ESMM,通过概率链式关系建模 CTR、CTCVR 与 CVR,而不是直接把两个 MMoE 输出相除就当作可靠的 CVR。


四、MMoE 的主要参数如何选择?

MMoE 的调参重点通常不是单纯增加网络深度,而是确定需要多少专家、每个专家的表达能力,以及多个任务之间如何平衡。

参数 作用 实践建议
num_experts 共享专家数量 可以从 4 个开始,与 2、8 个对比
Expert hidden units 专家网络宽度和深度 数据较少时避免过大的网络
Tower hidden units 任务专属网络结构 可根据任务复杂度分别设计
Gate network 专家权重分配 先采用简单线性 Gate 作为基线
Task loss weights 多任务损失权重 结合业务目标与梯度规模调整
Learning rate 参数更新步长 Adam 优化器可从 0.001 附近尝试

实践中还建议监控不同任务的训练损失、验证集指标,以及 Gate 的专家权重分布。

如果多个专家始终获得近似相同的权重,可能说明门控没有形成明显的选择性;如果几乎全部权重长期集中于单个专家,则需要分析是否存在专家使用不均衡。

不过,权重分布本身并不能证明模型训练成功或失败,最终仍以验证集和线上业务效果为准。

五、MMoE、Shared-Bottom、PLE,到底应该怎么选?

模型 主要特点 适合场景
Shared-Bottom 完全共享底层特征,结构简单 任务关系密切、数据量有限
MMoE 多个共享专家,每个任务独立门控 任务相关但存在一定差异
PLE 显式区分共享专家与任务专属专家 任务冲突明显、需要更强隔离

MMoE 在 Shared-Bottom 与 PLE 之间提供了一种灵活的折中方案。

但必须明确:MMoE 能够缓解负迁移,并不意味着一定能够消除负迁移。多个任务仍然会共同更新共享专家,任务损失权重和数据质量依然可能影响模型效果。

六、最后总结

MMoE 是一种面向多任务学习的神经网络架构,其核心价值在于:通过共享专家网络实现知识复用,再通过任务专属门控实现有区别的特征选择。

短视频推荐中的完播、点赞,电商推荐中的点击、购买,都是典型的应用方向。

从工程角度看,真正需要解决的是三个问题:

  • 哪些任务适合联合建模,哪些任务存在明显冲突?
  • 共享专家是否带来了超越独立模型的实际收益?
  • 不同任务的损失、指标和业务目标如何平衡?

学会搭建 MMoE 只是第一步。只有在同一数据集上完成单任务模型、Shared-Bottom 与 MMoE 的对照实验,才能判断这种架构是否值得投入生产。

如果希望进一步深入,可以继续研究 PLE(渐进式分层提取)、**ESMM(全空间多任务模型)**和 CGC(定制门控控制)。它们分别从任务知识隔离、转化链路建模和专家结构设计等方向,对多任务学习做出了进一步拓展。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。