
1. 项目概述MTFM全称 Multi-Task Feature fusion Model多任务特征融合模型。这个缩写我第一次看到是在组里师弟转发的一篇论文里后来自己动手做项目时才发现它已经被各行各业用“烂”了——有的拿它做金融舆情情感分析有的拿它做视频多模态特征融合还有的往机械故障诊断那边靠。但不管场景怎么换MTFM的核心逻辑从来没变过用一套共享的底层特征在多任务之间做融合让一个模型同时把几件事干好而不是为每件事单独训练一个模型。我最初接触MTFM是被一个实际需求逼的。当时我手上有个内容审核的项目同一个内容既要做情感极性判断正/负/中立又要抽取其中的关键情绪词还要判断是否包含营销意图。按老办法这三个任务至少三个模型每个模型都要单独标注、单独调参、单独部署最要命的是三个模型各算各的特征表达能力完全割裂。后来我查到MTFM这类多任务特征融合的思路才意识到问题的解法不在于“加多少个模型”而在于“怎么共享和融合特征”。这篇博文我会从MTFM的底层设计逻辑讲起拆解它在NLP场景下最常用的一套落地架构然后给出一套可以直接抄作业的训练方案和开源组件选型。如果你正在做信息抽取、情感分析、意图识别这类多标签/多任务NLP项目或者你只是听说过“多任务学习”但一直不知道特征融合到底怎么融、融合到哪里这篇文章值得你花一刻钟看完。顺带说一嘴MTFM这个名字本身其实没有统一标准。有的文章里写 Multi-Task Feature Modification有的写 Multi-scale Temporal Feature Module但我这篇讲的是 NLP 多任务里最主流的那一支——共享编码器 任务分支 特征融合模块。理解了这一支其他变体你基本都能一眼看懂。2. MTFM为什么要做特征融合先回答一个很多人心里的疑问我单独训练三个模型每个模型都能跑得很好为什么要非得多任务加特征融合这不是给自己找麻烦吗2.1 单任务模型的三大痛点单任务模型看着简单但你在生产环境里跑一阵就会撞见三件烦心事。第一是数据利用率低。我当时的三个任务里情感标注数据有5万条情绪词抽取数据只有8000条营销意图判断数据只有1.2万条。如果各训各的情绪词抽取这个任务就只能在8000条数据里折腾模型很容易过拟合泛化能力很弱。但多任务模型可以把5万条情感数据里学到的语义信息“迁移”给情绪词任务这就是多任务学习中常说的辅助任务正则化效应。第二是推理成本高。三个独立模型部署上去要么三个接口要么一个大接口里串行跑三个模型。单条请求延迟是三个模型的延迟之和如果中间还有数据预处理不一样的坑排错能把人逼疯。MTFM用一个模型搞定三件事显存占用、推理延迟、运维复杂度全部降下来。第三是特征割裂。人类理解一句话的情感本来就会同时调用词汇、句法、上下文、语气等多重信息。三个独立模型各自闭门造车学到的特征根本不对齐。比如情感模型学到了“这家店服务真烂”是负面的但情绪词模型未必能精准定位“烂”这个关键词因为它在自己的任务里接触不到情感上下文的信息。2.2 特征融合到底融的是什么多任务特征融合本质上是在回答一个问题多个任务之间哪些特征是通用的哪些特征是各自独立的拿我的内容审核项目举例。“这家店服务真烂等了40分钟还没上菜”这句话情感任务需要判断整体情感倾向情绪词任务需要抽取出“烂”“等了40分钟”这类关键片段营销意图任务需要判断这句话是不是有推广目的。三个任务需要的底层能力高度重叠分词、词性、实体识别、句法关系、上下文语义。这部分就是“共享特征”应该放在底层一起学。但三个任务也有各自的独门需求。情感任务需要全局语义汇总情绪词任务需要局部片段的细粒度表征营销意图任务需要跨句子的关联信息。这部分就是“任务专属特征”需要在分支网络里各学各的。特征融合模块要做的就是设计一套机制让共享特征和专属特征能够有效地交互。融合得好三个任务相互促进融合得不好反而会互相干扰——这就是多任务学习里著名的“悬崖”问题negative transfer。2.3 MTFM在NLP里的主流设计逻辑如果去翻近几年的论文你会发现NLP领域做特征融合基本都围绕三个位置展开输入端融合、编码器中间层融合、输出端融合。输入端融合最常见的就是特征拼接。比如你既有文本序列又有词性标注序列还可能加一个情感词典匹配结果把这些特征在embedding层直接拼起来拼成一个更高维的向量喂给模型。这种融合方式简单粗暴但优点是几乎没有额外计算开销。编码器中间层融合就复杂一些。常见做法是在Transformer的某一层或者多层之间加入一个跨任务的特征交互模块。比如情感任务的中间表征可以拿来指导情绪词任务的注意力分布这种融合方式更精细但工程实现难度和调参难度都会上一个台阶。输出端融合则是每个任务先各自输出预测结果再用规则或一个轻量网络把多个输出综合起来。这种融合算是最“偷懒”的但对某些场景很有效尤其是任务之间在输出层面存在强依赖关系的时候。MTFM这个名字强调的通常就是第二种在编码器中间层做跨任务特征融合。因为只有在这个位置你才能既保留共享编码器的高效性又能给每个任务注入其他任务带来的“上下文信息”。3. MTFM核心架构拆解这一节我会完整拆解一套我实际用过的MTFM架构你可以在自己的项目里直接参考。这套架构在大部分文本分类、抽取、匹配任务组合上表现稳定兼容HuggingFace生态用PyTorch就能实现。3.1 总体架构一个主干两个分支我用的MTFM结构可以分为三层共享主干一个预训练语言模型我常用的是BERT-base或者它的轻量版ALBERT负责把原始文本转换成语义向量序列。主干的所有参数由全部任务共享。特征融合层从主干输出位置引出各任务的中间表征经过一个多头注意力交互模块进行融合融合后的特征再分别送回各任务分支。任务分支每个任务一个轻量分类头或抽取头。分类头用一层全连接加softmax抽取头用CRF或者指针网络。用一句话描述整体前向过程文本输入共享主干得到序列特征序列特征送入融合模块做跨任务交互交互后的特征分别送到各任务分支做预测。3.2 共享主干到底该选什么模型我实测下来如果你任务数据总量在5万条以下别盲目上BERT-large。BERT-large参数太多在多任务场景下特别容易过拟合到数据量最大的那个任务上导致小数据任务完全学不动。这个现象叫“任务间不平衡”后面我会详细讲。个人建议按数据量和算力条件选择数据总量卡资源推荐主干说明 2万条单卡V100以下ALBERT-base / distilled参数少收敛快不易过拟合2~10万条单卡V100/3090BERT-base / RoBERTa-base性能和速度的平衡点 10万条多卡训练BERT-large / DeBERTa-v3大模型更吃数据但效果上限更高我当时项目是8万条左右选了BERT-base中文版。如果你做英文任务直接选bert-base-uncased就行。另外一个小技巧如果你用的是中文场景建议拿通用预训练模型在你自己领域的语料上再做一遍领域自适应预训练DAPT领域差异大的时候这个操作收益非常明显。3.3 特征融合层的设计与张量形状变化这是MTFM最核心的模块。我从最简单的方案开始讲再逐步升级。简单版拼接融合假设共享主干对一条长度为L的文本输出一个张量h形状是[batch, L, hidden]。你有K个任务每个任务从主干引出各自的表征h_k形状相同。最简单的融合方式就是直接拼接# 两个任务举例h1 和 h2 形状都是 [batch, L, hidden] fusion torch.cat([h1, h2], dim-1) # [batch, L, hidden * 2] # 再用一个线性层压回原维度 fusion self.fusion_proj(fusion) # [batch, L, hidden]这个做法的优点是一切从简缺点是没有学习“如何融合”的权重——所有位置、所有特征通道都是同样重要的。在很多场景下这不够用。进阶版注意力融合我最终采用的方案是对每个任务都用一个多头注意力模块去“观察”其他任务的表征从中挑选和自己最相关的信息。class CrossTaskAttention(nn.Module): def __init__(self, hidden_size, num_heads): super().__init__() self.num_heads num_heads self.q nn.Linear(hidden_size, hidden_size) self.k nn.Linear(hidden_size, hidden_size) self.v nn.Linear(hidden_size, hidden_size) def forward(self, source, target): # source: [batch, L, hidden] 被融合的任务表征 # target: [batch, L, hidden] 当前任务表征 q self.q(target) # 当前任务作为查询 k self.k(source) v self.v(source) attn_weights torch.matmul(q, k.transpose(-2, -1)) / (self.hidden_size ** 0.5) attn_weights F.softmax(attn_weights, dim-1) attended torch.matmul(attn_weights, v) return attended target # 残差连接这里的关键是QKV的设计查询来自当前任务键和值来自被融合的任务。意思就是“站在当前任务的角度去其他任务的特征里找对自己有用的信息”。加了残差连接之后即使跨任务注意力没有学到有效信息也不会损伤主干特征训练更稳。完整版层级融合如果你追求更好的效果可以把单一融合层扩展为多个层级。比如BERT共有12层Transformer我在第6层和第12层各做一次融合浅层融合词法和短语信息深层融合语义和上下文信息。但这个方案显存消耗大训练时间也长。我的建议是先跑通简单版和进阶版确认收益之后再考虑层级融合不要一上来就上重武器。3.4 任务分支分类头与抽取头的取舍任务分支的设计主要取决于你的任务类型。如果是分类任务分支就是一层线性加softmax输入是融合后序列的CLS位向量class ClassificationHead(nn.Module): def __init__(self, hidden_size, num_labels): super().__init__() self.dropout nn.Dropout(0.1) self.classifier nn.Linear(hidden_size, num_labels) def forward(self, fused_seq): cls_token fused_seq[:, 0, :] # 取CLS位 output self.classifier(self.dropout(cls_token)) return output如果是序列标注/抽取任务我一般会在融合序列上接一个线性层映射到标签空间再用CRF做解码约束。这里有个细节抽取任务的标签数量通常远大于分类任务如果不做类别权重平衡模型会倾向于预测“O”标签非实体导致抽取结果全是空白。这个后面在训练策略部分细讲。如果是匹配任务比如判断两个句子是否相似分支可以把两个序列分别池化再拼起来过一层MLP。总的来说任务分支的复杂度应该远低于共享主干因为大部分学习能力已经放在主干和融合层了分支结构简单反而更不容易过拟合。4. MTFM完整落地实操记录前面讲的是架构这一节把从数据处理到模型训练、评估的完整流程走一遍。我以“情感分类 情绪词抽取 营销意图识别”三个任务为例数据是用爬虫抓取的外部公开评价数据加上人工标注产出的。这个组合很有代表性一个分类任务、一个抽取任务、一个二分类任务刚好能体现MTFM的多任务融合优势。4.1 数据准备与任务构造多任务学习的数据组织方式和单任务有本质区别。单任务是一个样本一行标签多任务需要为每个样本准备一个字典包含所有任务的标签。# 数据格式示例 { text: 这家店服务真烂等了40分钟还没上菜再也不来了, sentiment: 0, # 0负面 1中性 2正面 emotion_words: [烂, 等了40分钟, 再也不来了], marketing_intent: 0 # 0非营销 1营销 }但实际标注时不是每条样本都有三个任务标签。当时我们标注预算有限只给一部分数据标了情绪词营销意图也存在漏标的情况。多任务框架天然能处理这种“部分标注”场景——某条样本缺某个任务标签时计算损失就跳过这个任务只计算有标签的任务。这里有一个注意点任务标签缺失要有机制保证不要默认填0。如果情绪词没标你填了空列表模型会把这个当成“无情绪词”来学白白给模型喂了大量错误负样本。正确做法是给每个任务一个mask标记缺标签的样本不参与该任务的损失计算。4.2 数据采样与平衡策略多任务训练里有一个很容易被忽视的坑如果三个任务的数据量差异很大而每个batch都等量采样各任务数据那么大数据任务会在batch里占主导小数据任务每轮只能看到很少的样本收敛慢还容易被大任务带偏。我没有用最复杂的动态采样策略而是先做了静态调整把三个任务的数据分别按各自batch_size采样在每个训练步里拼成一个大的multi-task batch每个任务都吃到等量的数据。每个任务的batch_size先统一设成16效果不好再单独调。如果某任务数据量特别小比如只有几千条会给它额外增加采样倍数或者用回放缓冲区让它在每个epoch里被重复看到2~3次。这个“等量采样”的调整看似不起眼但影响非常大。有一次我把它改成按数据量比例采样也就是数据量大的任务每次拿更多样本结果情绪词抽取的F1直接跌了6个点。原因就是营销意图数据量太大模型被它“带节奏”了。4.3 损失函数与多任务权重损失函数是MTFM训练中分歧最大的地方。网上能查到很多高级的动态权重算法比如GradNorm、 Uncertainty Weighting但以我的经验大部分项目先把等权重跑通再用简单的加权调优就够了一上来就上动态权重反而容易踩坑。我初始设置是这样的# 三个任务的损失分别为 # loss_sentiment: 交叉熵三分类 # loss_emotion: 序列标注交叉熵 CRF损失 # loss_marketing: 二元交叉熵 total_loss loss_sentiment 0.8 * loss_emotion 0.5 * loss_marketing为什么初始权重不均匀因为三个任务的loss量级不同。序列标注的loss通常是按token算的一个序列几十个tokenloss值天然比整句分类的loss大好几倍。如果不做权重缩放标注任务会主导梯度情感分类就学不好。我这里的0.8和0.5就是用来“拉平量级”的不是随意拍脑袋而是先跑一个batch看各任务的loss数值再按比例反推初始权重。如果你想做得更精细一些可以用不确定性加权Uncertainty Weighting。它的思路是让模型自己学习每个任务loss的置信度loss置信度低也就是方差大的任务自动获得更低的权重。我试过效果还行但收敛速度更慢而且对学习率和初始化特别敏感。我的建议是等权重方案跑通了再对比不确定性加权哪个好留哪个。4.4 训练策略交替训练还是联合训练多任务训练有两种主流方式交替训练和联合训练。交替训练是每个step只优化一个任务的loss四个step一个循环联合训练是每个step把所有任务的loss加起来一起优化。我实测下来的结论是联合训练在大部分场景下更稳交替训练除非你有特殊原因否则别碰。联合训练的好处是每个step对所有任务都更新一次任务之间交互充分坏处是如果某个任务loss量级过大会盖过其他任务。交替训练的优点是每个step只专注一个任务不会互相干扰坏处是任务之间信息交互不充分特征融合就名存实亡了而且收敛稳定性很差需要大量调试学习率和epoch数。我最终用的是联合训练但加了一个小技巧前5%的训练步只优化情感分类这一个任务让主干先稳定下来之后再打开所有任务的loss一起训练。这个“warmup”阶段相当于先让模型学会通用的语义表征再让其他任务在这个基础上“微调”比从头就三任务一起训练最终效果能提升1~2个点。4.5 评估指标与结果解读多任务模型不能只看一个指标这是我吃过大亏的地方。最开始我只关注整体loss下降忽略了对每个任务的单独评估结果情感分类准确率很高但情绪词抽取F1一路跌到谷底营销意图识别的召回率也明显偏低——整体loss好看完全是因为情感分类数据量大、贡献了主要loss。后来我做了三件事修正评估体系每个epoch结束时分别计算三个任务各自的指标。情感分类看准确率和F1情绪词抽取看F1和精确率营销意图识别看AUC和召回率。保存模型时以“最弱任务”的指标为基准。比如三个任务中情绪词抽取最差就以它的F1达到最高时的epoch作为最优模型保存点而不是只看整体loss或某单个任务的指标。新增了一个“多任务综合分”就是三个任务指标的加权平均权重根据业务重要度来定。这个分数用来横向对比不同架构、不同超参的效果。我最终的模型效果大致是情感分类准确率89%情绪词抽取F1在72%营销意图识别AUC 0.93。如果单看每个任务单独训练的模型指标会略高两三个点但MTFM的整体优势体现在推理效率和数据利用率上。对我来说这两个优势远比那两三个百分点的指标差距重要。5. MTFM训练调参与避坑指南这一节写我踩过的坑和总结的排查经验每一件都是真实发生的。5.1 任务间“跷跷板”效应多任务学习最典型的问题就是跷跷板效应一个任务涨了另一个任务就跌。我第一次遇到时怎么调都不对后来总结出三个应对手段降低共享主干的学习率。共享主干是多个任务共同作用的载体学习率太高容易在任务间反复横跳。我把主干学习率设为任务分支的十分之一用分组参数分别设lr效果立竿见影。# 参数分组示例主干和分支用不同学习率 optimizer_grouped_parameters [ {params: backbone.parameters(), lr: 2e-5}, {params: fusion_layer.parameters(), lr: 1e-4}, {params: task_heads.parameters(), lr: 1e-4}, ]检查梯度冲突。两个任务的梯度如果在共享主干上方向相反就会互相抵消。最简单的方法是打印共享主干的梯度余弦相似度如果负值频繁出现说明任务冲突严重。这时候可以试试“梯度裁剪”或者“PCGrad”Projecting Conflicting Gradients这类梯度手术方法。我当时用了PCGrad的简化版把冲突任务的梯度投影到正交方向跷跷板现象明显缓解。给冲突任务加独立特征通道。如果两个任务冲突严重靠梯度调整很难根除更实际的办法是在融合层后面给冲突任务单独接一路特征提取网络让它不完全依赖共享特征。说白了我们在融合信息但也不能融合到“你我不分”。5.2 融合层不生效的排查思路有一段时间我发现加了融合层和没加融合层模型效果几乎没差别一度怀疑是融合模块写得有问题。后来定位到两个原因第一是融合层太浅信息交互不够。如果只在最后一层做一次注意力融合模型还来不及利用融合后的特征做深层推理就输出了。解决方法是把融合层放在Bert的倒数第二层这样融合后的特征还能再经过一层Transformer做深度加工。第二是残差连接用错了。我用attended source还是attended target效果差别很大。正确的残差设计应该参考注意力机制的标准做法如果融合想保留当前任务的原始信息残差应该连回到当前任务的输入如果希望融合结果更多反映被融合任务的信息那残差连到source端。我在项目里两个都试了残差连target当前任务输入效果更稳定因为模型不能完全依赖其他任务的特征来做自己的预测保留自己的原始信息对缓解负迁移很重要。5.3 训练不收敛或loss震荡MTFM训练不收敛我遇到的情况基本分三类第一类是学习率设太大。共享主干加上多个任务分支参数量比单任务模型大不少需要把整体学习率适当调低。单任务BERT我用3e-5没问题MTFM我一般降到2e-5起步。第二类是任务权重分配不合理。某个任务loss量级特别大把模型梯度带偏。排查方法是在训练早期打印每个任务的loss数值如果发现某个任务的loss比其他任务大一个数量级立马给它的权重乘一个0.1。第三类是batch_size太小。多任务模型每个batch要同时满足所有任务的数据分布batch_size太小的话小数据任务的样本在单个batch里出现次数太少更新不稳定。我在多任务场景下batch_size通常不小于16如果显存够用会开到32。5.4 多任务没比单任务强先检查这4个地方常有朋友跟我抱怨说MTFM跑出来还不如单任务模型。我会让他们按顺序检查数据量是否充足。多任务模型参数多如果总数据量很小反而会过拟合。一般来说总数据量少于1万条时MTFM优势不明显优先考虑单任务加预训练。任务之间是否相关。图像分类和OCR识别这种毫无关联的任务硬融在一起反而互相干扰。多任务学习的前提是任务之间存在共享知识。如果你不确定任务相关性可以做一次简单的task similarity分析用共享编码器的中间特征对任务标签做相关性检验。融合位置是否合理。某些任务可能更适合在浅层融合某些更适合在深层融合。如果融合位置和任务特性不匹配融合层就学不到有效信息。基线是否公平。对比单任务多模型和MTFM时基线模型是否用了同样的预训练模型、同样的数据增强、同样的调参力度不公平的对比很容易得出“MTFM还不如单任务”的错误结论。6. MTFM后续可以怎么扩展说几个我目前尝试过或看到有价值的方向方便你在这套架构上继续演进。最直接的一步是换融合模块。我在项目里用的是cross-attention你有兴趣可以试试门控融合或低秩双线性融合。门控融合是给每个任务学一个门控向量控制融合时各任务特征的进出比例比注意力融合多了一层可解释性也更容易控制负迁移。第二个方向是加入对比学习目标。多任务模型的特征表示如果能在语义空间里对齐任务间的迁移效果会更好。具体来说可以给共享主干加一个额外的对比损失让相同情感极性但不同文本的样本表征更接近让不同情感极性的样本表征更远。这个操作能提升整体效果但训练时间会增加大概30%。第三个方向是模型轻量化。如果你要部署到线上每个任务分支和融合层都是额外的计算开销。我的做法是用蒸馏把MTFM压回单任务模型先用MTFM作为教师模型把它的融合特征作为软标签蒸馏一个更小的单任务模型出来。实测下来学生模型能保留MTFM大约90%的效果但推理速度和单任务模型持平部署成本可控。再往远了说MTFM的思路也能迁移到多模态场景。比如既看文本又看图片的内容分类可以把图像特征和文本特征在同样的融合框架下做跨模态交互。我最近就在尝试用MTFM做图文联合审核核心逻辑基本一致就是特征来源从纯文本变成了两种模态。最后说一个方向动态任务调度。我之前的MTFM是三个任务始终一起训练但实际场景中任务数量可能会动态增减。比如这周只做情感分类和情绪词抽取下周突然要加一个“举报原因识别”任务。理想的做法是模块化设计让新任务接入时只新加一个任务分支和对应的融合头共享主干和已有任务分支的参数全部冻结。这样新任务就能在前人经验的基础上快速收敛而不是从零开始。当然这里也要权衡如果新任务的数据量很大冻结主干反而浪费了预训练模型的拟合能力可能得重新从头训练一轮。7. 关于MTFM我最后想说的我真正把MTFM用明白是经历了三次迭代之后的事。第一次照搬论文结构效果惨淡后来才明白是任务采样策略的问题第二次调好了采样赔上了融合模块——融合后的特征没有经过深层网络加工就被丢到任务分支等于白做第三次才把架构、采样、损失权重、训练技巧全部串起来拿到了能上线的效果。所以如果你正准备在自己的项目里用MTFM我最大的建议是不要一上来就追求复杂结构。先把共享主干加简单的拼接融合跑通验证多任务之间的相关性再逐步引入注意力融合、层级融合、动态损失权重。每一步都做A/B对比确认收益再加。多任务学习和单任务最大的不同在于任何一个环节的改变都会通过共享主干传导到所有任务上牵一发而动全身没有对照实验你根本分不清是哪一步起了作用。还有一个小细节看似不起眼但影响很大MTFM的随机种子一定要固定好。我在调参时因为随机种子没固定同样的代码跑两次结果差了3个点差点以为新改动有问题。多任务整体的随机性比单任务大得多固定种子虽然不能完全消除随机性但至少能让你在对比实验时有个相对公平的参考。做多任务特征融合这件事前期搭建架构的成就感很足后面漫长的调参和排错才是真正考验人的部分。希望这篇文章能让你的路走得顺一点。