PLE:渐进分层提取网络分离共享与专属信息

FreeGuideOnline 最新 2026-06-24

什么是渐进分层提取(PLE)?

在多任务学习(Multi-Task Learning, MTL)中,我们经常让一个模型同时学习多个相关的任务,例如在推荐系统中同时预测点击率、转化率和分享率。传统的共享底层网络(Shared-Bottom)把参数完全共享,但不同任务之间的冲突会导致“跷跷板效应”——一个任务性能提升,另一个任务性能下降。

渐进分层提取(Progressive Layered Extraction, PLE) 正是为了解决这种任务冲突而设计的网络结构。它的核心思想是:在网络的每一层,都明确地将信息拆分为“任务专属”和“任务共享”两部分,并通过渐进式的路由机制,让不同任务既能获取公共信息,又能保留自己的专属表达。

PLE由腾讯在2020年的论文《Progressive Layered Extraction for Multi-Task Learning》中提出,现已成为工业界多任务学习的经典方案之一。

从MMOE到PLE:为什么需要渐进分离?

MMOE的不足

MMOE(Multi-gate Mixture-of-Experts)是之前的主流结构,它用多个“专家”(Expert)子网络和门控网络(Gate)来为每个任务动态选择不同的专家组合。但这种结构有一个明显的缺陷:

  • 所有专家对每个任务都是“平等”开放的,即一个任务的梯度会更新所有专家参数。
  • 当任务相关性很低时,共享的专家仍然会被拉扯向不同方向,导致负迁移(Negative Transfer)

PLE如何改进

PLE在MMOE的基础上引入了显式的专属模块和共享模块分离

  • 任务专属专家(Task-specific Experts):只接收当前任务的数据和梯度,确保不受其他任务干扰。
  • 共享专家(Shared Experts):被所有任务共同使用,抽取跨任务的通用模式。
  • 渐进分层路由:每一层都同时拥有专属模块和共享模块,并且上一层的输出会通过门控网络重新组合,向下传递。

这样,即使某个任务需要非常特殊的信息,它也可以依靠深层专属模块来保留,而不会污染共享空间。

PLE的网络结构详解

核心组件

PLE网络可以看作一个多层的路由系统,每一层都包含两类“提取器”:

  1. 共享提取器(Shared Extractor):接收上一层的共享输出 + 所有任务的专属输出,生成新的共享表示。
  2. 任务k专属提取器(Task k Specific Extractor):接收上一层的共享输出 + 第k个任务的专属输出,生成第k个任务新的专属表示。

每一层都有一个门控网络来决定如何融合上一层的输出,并送入本层的提取器。

单层结构示例(以两个任务为例)

假设模型有 L 层,在第 l 层:

  • 输入来自上一层的输出:
    • 共享表示 S^{l-1}
    • 任务A专属表示 A^{l-1}
    • 任务B专属表示 B^{l-1}
  • 共享提取器的输入 = 一个门控网络混合 [S^{l-1}, A^{l-1}, B^{l-1}] 的结果。
  • 任务A专属提取器的输入 = 另一个门控网络混合 [S^{l-1}, A^{l-1}] 的结果(注意不包含任务B的专属信息,这是关键隔离措施)。
  • 任务B专属提取器同理。

门控网络根据上一层的表示,自动学习融合权重,再送入对应的提取器(通常为一个全连接网络或一个小型子网络)。这种选择性输入保证了专属模块不会被迫接受无关任务的信息。

渐进式路由

“渐进”体现在多层堆叠上:低层的共享和专属模块捕捉基础特征交互,高层的模块在此基础上提取更抽象的任务相关模式。每一层结束后,新的共享表示和所有专属表示会传给下一层,继续进行分离与融合。

最终,在顶层,每个任务会使用自己最后一层的专属表示,加上最后一层的共享表示,经过一个任务特定的“塔”(Task Tower)输出预测结果(如一个全连接层+sigmoid)。

PLE为什么有效?——分离共享与专属信息

  1. 显式容量控制:通过为每个任务预留专属模块,保证即使任务冲突,它们也有不受干扰的学习空间。
  2. 渐进式知识迁移:共享模块不断从所有任务中吸收共性知识,并通过门控网络传递给专属模块,实现正向迁移。
  3. 梯度隔离:在反向传播时,任务B的梯度不会更新任务A的专属提取器参数,从根本上解决了负迁移问题。

训练与优化技巧

  • 损失加权:多任务训练时需要合理加权各任务损失。PLE通常配合不确定性加权(Uncertainty Weighting)或动态加权平均(DWA)使用。
  • 专家数量与层数:建议从1-2层、每任务2个专属专家、2个共享专家开始,根据任务相关性逐步调整。任务差异越大,专属专家应该越多。
  • 防止过拟合:对专属提取器施加较大的dropout,共享提取器则用较小的dropout,因为共享模块看到更多数据。
  • 学习率:共享模块学习率可以稍高,因为梯度来自所有任务;专属模块学习率可稍低,保持稳定性。

使用PyTorch实现一个简单的PLE层

下面给出一个同时处理两个任务的单层PLE实现框架,帮助理解数据流转:

import torch
import torch.nn as nn
import torch.nn.functional as F

class PLELayer(nn.Module):
    def __init__(self, input_dim, shared_expert_num, specific_expert_num,
                 expert_output_dim, task_num):
        super(PLELayer, self).__init__()
        self.task_num = task_num
        
        # 共享专家
        self.shared_experts = nn.ModuleList([
            nn.Sequential(nn.Linear(input_dim, expert_output_dim), nn.ReLU())
            for _ in range(shared_expert_num)
        ])
        # 每个任务的专属专家
        self.task_experts = nn.ModuleList([
            nn.ModuleList([
                nn.Sequential(nn.Linear(input_dim, expert_output_dim), nn.ReLU())
                for _ in range(specific_expert_num)
            ]) for _ in range(task_num)
        ])
        
        # 门控网络:用于生成当前层输入的混合权重
        # 共享模块的门控输入维度:input_dim (共享+所有专属的拼接)
        self.shared_gate = nn.Sequential(
            nn.Linear(input_dim * (1 + task_num), 1 + task_num),
            nn.Softmax(dim=-1)
        )
        # 每个任务专属模块的门控,只接收共享和本任务专属
        self.task_gates = nn.ModuleList([
            nn.Sequential(
                nn.Linear(input_dim * 2, 2),
                nn.Softmax(dim=-1)
            ) for _ in range(task_num)
        ])
        
    def forward(self, shared_input, task_inputs):
        """
        shared_input: [batch, input_dim]
        task_inputs: list of [batch, input_dim] 长度 = task_num
        """
        # 1. 生成共享提取器的输入
        all_input = torch.cat([shared_input] + task_inputs, dim=-1)  # [batch, input_dim*(1+task_num)]
        gate_weight = self.shared_gate(all_input)  # [batch, 1+task_num]
        shared_combined = gate_weight[:, 0:1] * shared_input
        for i in range(self.task_num):
            shared_combined += gate_weight[:, i+1:i+2] * task_inputs[i]
        # 送入所有共享专家,并对输出求和(或平均)
        shared_expert_outputs = [expert(shared_combined) for expert in self.shared_experts]
        next_shared = torch.stack(shared_expert_outputs, dim=1).sum(dim=1)  # [batch, expert_output_dim]
        
        # 2. 生成各任务专属提取器的输入
        next_task_outputs = []
        for i in range(self.task_num):
            gate_in = torch.cat([shared_input, task_inputs[i]], dim=-1)
            gate_weight_i = self.task_gates[i](gate_in)  # [batch, 2]
            task_combined = gate_weight_i[:, 0:1] * shared_input + gate_weight_i[:, 1:2] * task_inputs[i]
            task_expert_outputs = [expert(task_combined) for expert in self.task_experts[i]]
            next_task = torch.stack(task_expert_outputs, dim=1).sum(dim=1)
            next_task_outputs.append(next_task)
        
        return next_shared, next_task_outputs

实际使用中,会将多个 PLELayer 堆叠,最后每个任务用自身的最后一层专属表示和最后一个共享表示拼接,进入输出塔。

小结

PLE通过渐进式、逐层显式分离共享与专属专家,在多任务学习中成功抑制了负迁移现象,尤其适合任务相关性弱或任务数量较多的场景。它结构清晰,易于工程化部署,已在广告、推荐等多个领域取得了显著收益。作为初学者,理解PLE的关键在于明白“为何要隔离”以及“如何渐进地路由信息”,这将为你设计自己的多任务模型提供坚实基础。