第一次接触多任务学习时,我遇到了一个奇怪的现象:明明单独训练每个任务时效果都不错,但把它们放在一起训练后,某些任务的性能反而下降了。这就像让一个学生同时学习数学和语文,结果语文成绩提高了,数学却退步了。后来才知道,这就是典型的"跷跷板效应"。
造成这种现象的根本原因在于梯度冲突。想象一下,两个任务对同一组模型参数提出了不同的更新要求:任务A希望参数往左调整,任务B却要求参数往右移动。这种互相拉扯的情况会导致三个典型问题:
梯度方向不一致:就像两个人在拔河,一个往东拉,一个往西拽,最终模型参数在原地打转。我在做商品推荐系统时就遇到过这种情况,点击率预测任务希望增加某些特征的权重,而购买预测任务却要降低这些特征的权重。
收敛速度差异:不同任务的难度不同,简单任务可能几轮训练就收敛了,复杂任务还在艰难摸索。比如在图像识别中,物体分类任务通常比边界框回归收敛得快得多。
Loss量级不平衡:当使用不同损失函数时,它们的数值范围可能相差几个数量级。在视频内容理解项目中,分类任务的交叉熵损失通常在0-1之间,而检测任务的L2损失可能高达几百,模型自然会被大数值的损失主导。
最开始解决这个问题,我尝试了最直接的方法——手动调整每个任务的loss权重。就像调鸡尾酒一样,给不同的任务分配不同的"份量"。具体操作起来很简单:
python复制total_loss = w1 * loss1 + w2 * loss2 + w3 * loss3
但这个方法有几个明显的坑:
后来我发现可以先对各个loss进行标准化处理,让它们处于相近的数值范围。常用的方法包括:
python复制# 初始loss归一化示例
initial_loss1 = get_initial_loss()
normalized_loss1 = loss1 / initial_loss1
这个方法在视觉多任务模型中特别有用。比如同时处理语义分割和深度估计时,两种loss的量级可能相差100倍,归一化后就能平等对待。
论文《Multi-Task Learning Using Uncertainty to Weigh Losses》提出了一种优雅的方案:让模型自己学习每个任务的不确定性,并据此调整权重。实现起来也很直观:
python复制# 不确定性加权实现
log_var1 = torch.log(var1) # 可学习参数
loss = 0.5 * torch.exp(-log_var1) * loss1 + 0.5 * log_var1
我在医疗影像分析中应用这个方法时发现,模型会自动给噪声更大的模态(如低质量X光片)分配较小权重,非常智能。
PCGrad(Project Conflicting Gradients)是更激进的解决方案。它不像加权那样调和矛盾,而是直接对冲突的梯度做手术:
python复制# PCGrad核心步骤
for i in range(num_tasks):
for j in range(i+1, num_tasks):
if cos_sim(grad_i, grad_j) < 0:
grad_i = grad_i - grad_j * (grad_i.dot(grad_j)/grad_j.dot(grad_j))
在自动驾驶多任务模型中,这个方法显著改善了目标检测和道路分割的协同效果,mAP和IoU指标同步提升。
固定权重在整个训练周期可能不是最优的。我借鉴课程学习(Curriculum Learning)的思路,实现了动态权重调整:
python复制# 基于训练进度的动态权重
progress = current_step / total_steps
weight1 = 1.0 - 0.5 * progress # 线性衰减
weight2 = 0.5 * progress # 线性增加
在推荐系统的多目标优化中,初期更关注点击率,后期逐渐增加转化率目标的权重,最终A/B测试显示CTR提升12%,CVR提升8%。
除了调整权重,直接操作梯度也能取得不错效果。我常用的组合拳是:
python复制# 梯度归一化示例
for param in model.parameters():
if param.grad is not None:
param.grad = param.grad / (param.grad.norm() + 1e-8)
在训练多语言翻译模型时,这个方法有效防止了某些低频语言主导训练过程的现象。
经过多个项目的实践,我总结了几个关键经验:
监控指标要全面:不能只看总loss,每个任务的loss曲线、指标都要单独监控。曾经有个项目总loss一直在降,但后来发现是一个任务在"作弊"——它通过让其他任务变差来降低自己的loss。
验证策略要科学:多任务学习的效果评估不能只看单任务指标。我习惯用"帕累托前沿"分析——看调整权重时,能否找到一个所有任务都不比单任务训练差的操作点。
初始化很重要:先单独预训练每个任务,再用这些参数初始化多任务模型,通常能获得更好的起点。就像让运动员先单项训练,再参加全能比赛。
架构设计要合理:有时梯度冲突源于不合理的参数共享方案。在视频理解项目中,我发现让低级特征完全共享、高级特征部分独立,效果比全共享或全独立都好。
在推荐系统的最新实践中,我们结合了不确定性加权和动态调整策略,设计了一个自适应权重网络。这个网络不仅学习各任务的权重,还会根据用户实时反馈动态调整。上线后,不仅各项业务指标均有提升,更重要的是大大减少了人工调参的工作量。