<p>正确做法是加权求和后单次反传:total_loss = w1 loss_a + w2 loss_b;total_loss.backward()。分步backward会累加梯度、导致权重失效甚至梯度爆炸,而单次反传保证梯度干净且可控。</p>

多任务损失怎么加权求和才不会梯度爆炸
直接把多个损失 loss_a + loss_b 相加是最常见的错误起点——权重没归一、量纲不一致、收敛速度差异大,会导致某个任务主导更新,另一个任务几乎不学习。PyTorch 本身不提供多任务损失封装,必须手动控制加权逻辑。
推荐做法是用可学习权重或固定但经验证的系数:
- 固定权重:先分别训练单任务,记录各自典型 loss 值(如分类 loss≈0.3,回归 loss≈1.2),反比设为
weight_a=1/0.3,weight_b=1/1.2,再归一化到和为 1 - 可学习权重:定义
self.log_sigma_a = nn.Parameter(torch.zeros(1)),损失项写成loss_a / (2 * torch.exp(self.log_sigma_a)**2) + 0.5 * self.log_sigma_a**2(源自不确定性加权论文 Kendall et al., 2018) - 避免用
loss = alpha * loss_a + (1-alpha) * loss_b这种硬约束——alpha 更新慢,容易卡在次优解
如何共享 backbone 同时保持 task-specific head 不干扰
关键不是“能不能共享”,而是“梯度怎么流”。常见错误是把所有 head 的 loss 都 backward 到同一个 backbone,却没检查 head 是否有独立参数更新路径。
实操要点:
立即学习“Python免费学习笔记(深入)”;
- 定义 backbone 时确保它只被调用一次:
features = self.backbone(x),不要在每个 head 里重复调用 - 每个 head 必须是独立
nn.Module(如self.classifier_head,self.regressor_head),且它们的parameters()不重叠 - 验证方法:打印
set(id(p) for p in model.classifier_head.parameters()) & set(id(p) for p in model.regressor_head.parameters()),结果应为空集 - 如果用了
nn.DataParallel或DistributedDataParallel,注意 head 的 buffer(如 BatchNorm 统计值)可能被意外共享,建议对每个 head 单独 wrap
optimizer.step() 前要检查哪些梯度异常
多任务下 torch.nn.utils.clip_grad_norm_ 很容易失效——因为不同任务梯度 scale 差异大,统一裁剪会误伤小梯度任务。
更稳妥的做法:
- 在
loss.backward()后,逐任务检查梯度范数:torch.norm(torch.cat([p.grad.flatten() for p in model.classifier_head.parameters() if p.grad is not None])) - 若某任务 head 梯度全为 0,大概率是 loss 没正确连接到该 head 的输出(比如用了 detached tensor 或中间变量没 require_grad)
- 若 backbone 梯度远小于任一头部,说明该头反向传播断了(常见于用了
.item()或.detach()在中间) - 别依赖
optimizer.zero_grad()自动清空——多任务中常因分支条件导致部分 head 没参与本次 forward,其上一次梯度残留会污染更新
验证阶段怎么避免任务间指标互相污染
训练时 loss 可联合,但验证必须严格分任务计算。最易错的是在 val_step 中复用训练时的混合 loss 计算逻辑,导致指标不可比。
正确姿势:
- 验证时禁用所有加权逻辑,每个任务单独 forward:
pred_a = model.classifier_head(model.backbone(x)),再用对应 metric(如F1Score或MAE)计算 - 不要在验证 loop 里调用训练用的
compute_joint_loss()函数——哪怕它只是返回各 loss 字典,也可能隐含 .mean() 或 device 转换 bug - 如果用了
torch.no_grad(),确认它包裹了整个 forward+metric 计算,而不是只包 forward(否则 metric 内部 tensor 可能意外 require_grad) - 多任务验证指标必须保存为独立字段(如
val_acc,val_mae),不能塞进一个叫val_loss的标量里上报——后者会让早停逻辑失效
多任务真正的复杂点不在结构设计,而在梯度流动的可见性。只要每次 backward 后能快速定位「哪个模块的梯度消失了/爆炸了/尺度异常」,问题就解决了一大半。其他都是围绕这个目标做隔离和验证。


















