PLE:渐进分层提取网络分离共享与专属信息
什么是渐进分层提取(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网络可以看作一个多层的路由系统,每一层都包含两类“提取器”:
- 共享提取器(Shared Extractor):接收上一层的共享输出 + 所有任务的专属输出,生成新的共享表示。
- 任务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为什么有效?——分离共享与专属信息
- 显式容量控制:通过为每个任务预留专属模块,保证即使任务冲突,它们也有不受干扰的学习空间。
- 渐进式知识迁移:共享模块不断从所有任务中吸收共性知识,并通过门控网络传递给专属模块,实现正向迁移。
- 梯度隔离:在反向传播时,任务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的关键在于明白“为何要隔离”以及“如何渐进地路由信息”,这将为你设计自己的多任务模型提供坚实基础。