☰
多任务学习梯度冲突与Nash-MTL纳什议价优化器实践
2026/11/2 11:50:16 网站建设 项目流程

多任务学习做到第三个月,大部分人都会卡在同一个地方:几个 head 的 loss 曲线看着都在降,但总有一两个任务在偷偷拖后腿。你把分割的权重从 0.1 调到 0.5,分割的指标好看了,深度的误差立刻崩;换回等权,训练又变成简单任务主导整个梯度方向,难任务几乎学不动。这件事我踩过不止一次,本质上是梯度冲突——不同任务在共享参数上给出的更新方向互相抵消甚至相反,而加权求和这种线性标量化手段,压根处理不了方向上的矛盾。

《Multi-Task Learning as a Bargaining Game》这篇工作给了一个我读完觉得“早该有人这么干”的视角:把每个任务当成谈判桌上的一方,共享参数是大家要分的那块蛋糕,谁让步、让多少,交给纳什议价解(Nash Bargaining Solution,NBS)来定。落到优化器上就是Nash-MTL:每一步在任务梯度张成的锥里找一个更新方向,让所有任务相对参考点的改进量在对数尺度上的和最大。它天然满足帕累托最优性、对称性和无关备选独立性,而且求解代价是任务数的平方而不是立方——这一点在任务数上到几十个的时候会非常要命。

这篇笔记适合两类人看:正在做多任务、被权重搜索折磨的算法工程师,以及想搞清楚“这个博弈论建模到底是不是花架子”的研究向读者。下面我会把推导、求解器、工程实现、实验结论和复现时踩的坑都摊开讲,公式尽量给到手推的程度,代码给到能直接改的程度。

1. 为什么梯度平衡这件事值得用博弈论重做一遍

1.1 多任务学习的痛点在优化器,不在网络结构

先对齐一个基本事实:绝大多数多任务模型的网络结构是没问题的。共享 backbone 加多个 task head,这个范式从十年前到现在就没怎么变过,真正让人难受的地方集中在参数更新的那一步。假设共享参数是 $\theta$,任务 $i$ 的损失是 $L_i(\theta)$,最朴素的做法是 minimize $\sum_i w_i L_i(\theta)$,然后你就会遇到三个绕不过去的问题。

第一个问题是权重量纲不可比。分割任务的 loss 是交叉熵,量级在个位数;深度回归的 loss 是 L1 或 L2,量级可能是几十甚至上百。等权相加,量级大的那个任务直接吃掉全部梯度预算,这不是模型选择的问题,是数值尺度的问题。第二个问题是梯度方向冲突。两个任务的梯度如果夹角超过 90 度,它们的和方向对其中至少一个任务是“上坡”的,也就是说这个任务在这一步会变差。第三个问题是任务难度差异。简单任务在早期就能把 loss 压得很低,此时它的梯度范数变小,按理说应该自动降权;但如果是用固定的 $w_i$,这个自适应根本不会发生。

我见过太多项目在这三件事上反复折腾,最后靠“手动网格搜索权重 + 早停”硬扛过去。这种做法在小规模实验里能出论文,但一旦任务数量上去、任务之间的相关性变弱,手工调参的搜索空间直接爆炸。真正需要的是一个在每步迭代里自动决定方向和步长分配的优化器层方案,它不依赖任务之间的量纲一致性,也不依赖你事先知道哪个任务难。

博弈论切入的合理性就在这里:多任务学习本质上是一个多方在共享资源上做决策的问题,每个任务有自己的目标,谁也不愿意无限退让,但大家又必须共用同一组参数。这正是议价问题的标准形态。相比“用某种启发式规则拍一个权重出来”,议价的优势在于它能给出一组公理化的性质保证,而不是一堆需要你凭经验验证的超参。

1.2 现有方案的短板:线性标量化、MGDA、PCGrad 各自的边界

要理解 Nash-MTL 补的是哪块空,得先把已有方案摆一摆。我把这几年主流的几类做法整理成一张表,重点看它们的核心假设和失效场景。

方法族核心做法强项典型失效场景
线性标量化固定或学习权重 $w_i$ 加权求和实现简单,开销几乎为零任务量纲差异大、梯度方向冲突严重
梯度归一化类按梯度范数或损失下降速率动态调权缓解量纲问题方向冲突仍然存在,权重本身仍需调
冲突投影类两两投影,冲突分量置零或取正交能解掉成对冲突任务数多时两两处理顺序影响结果,且可能过度修正
最小范数类求梯度凸包中范数最小的点有帕累托最优保证严重偏向“好说话”的任务,难任务被牺牲
议价类(本文)求纳什议价解对应的梯度方向有公理化保证,任务数多时开销低需要构造 Gram 矩阵,对梯度噪声敏感

线性标量化的问题前面说过了,不重复。梯度归一化这一类,思路是按某种规则把各任务的梯度范数拉到同一量级再相加,量纲问题确实缓解了,但方向冲突一点没解决——两个反向的梯度归一化之后还是反向的,加起来接近零,谁也没学到。冲突投影这一类,思路是把任务 $i$ 的梯度中与任务 $j$ 冲突的分量减掉,问题在于这个操作是非对称的:先处理谁、后处理谁,结果不一样,任务数一多就变成了顺序敏感的随机过程。

最小范数这一类(也就是 MGDA 那一系)是我个人觉得最优雅的基线之一。它在梯度的凸包上找范数最小的点,理论上这个点对应的方向对任何任务都不会造成损失上升,帕累托意义上是干净的。但它有一个很实际的问题:最小范数点往往落在凸包的某个顶点附近,也就是把权重几乎全压在一两个任务上,剩下那些梯度范数小的任务被彻底忽略。在任务难度不均衡的设定里,这个偏差非常明显,你会在训练曲线上看到某些任务的指标长期横盘。

顺带提一句,MGDA 的求解是一个带单纯形约束的二次规划,复杂度是任务数的三次方。10 个任务的时候还能忍,50 个任务的时候单步求解的耗时就会开始跟真正的反传时间抢戏。这也是本文在工程侧最硬的卖点之一。

注意:这几类方法并不是互斥的。实际项目里我经常看到“先做梯度归一化,再用投影解冲突”的组合,效果比单用任何一种都稳。选型的时候不要预设只能选一个。

2. 把每个任务当成谈判桌一方:模型的核心假设与数学形式

2.1 从损失向量到效用向量的映射

议价理论里有个基本设定:每个参与者有一个效用函数,有一个“谈崩了点”(disagreement point),谈判的结果必须让所有人都比谈崩更好,否则没人愿意签字。搬到多任务学习上,参与者就是各个任务,谈判结果就是这一 step 的参数更新方向 $d$,谈崩点就是“完全不合作”,也就是没有任何改进。

具体怎么定义效用?最直接的想法是用损失本身,但损失是越小越好,而议价理论里效用是越大越好,所以要翻个号。更稳的做法是用相对参考点的改进量:记初始参数为 $\theta_0$,任务 $i$ 在当前位置的效用定义为

$$u_i(\theta) = L_i(\theta_0) - L_i(\theta)$$

这个定义有两个好处。第一,它把不同任务的绝对量纲差异压平了一部分——大家都是“比起点好了多少”,而不是“现在损失是多少”。第二,它把谈崩点明确定义成所有效用同时为零,正好对应“谁也没进步”这个状态,符合博弈论的设定。

接下来做一阶近似。考虑一步更新 $\theta \leftarrow \theta - d$,那么

$$\Delta L_i = L_i(\theta) - L_i(\theta - d) \approx g_i^\top d$$

其中 $g_i = \nabla_\theta L_i(\theta)$ 是任务 $i$ 在当前点的梯度。于是效用可以近似写成 $u_i \approx g_i^\top d$,这一步是整个推导的支点,它把“损失空间”的问题转成了“梯度内积空间”的问题,后面的所有计算都只跟梯度的 Gram 矩阵有关,跟损失的具体数值脱钩了。

这里有个容易忽略的细节:一阶近似只在 $d$ 的范数不大的时候成立。如果把 $d$ 的范数放开,泰勒展开的余项会污染整个目标函数,解出来的方向看着漂亮,实际走一步反而掉点。所以后面求解的时候必须给 $d$ 或者等价地给权重向量加范数约束,这不是可选项,是保证近似有效的前提。

2.2 纳什议价解为什么符合直觉

把更新方向限制在所有任务梯度的非负线性组合里,也就是 $d = G\alpha$,其中 $G$ 是把 $g_1,\dots,g_T$ 按列拼起来的矩阵,$\alpha \ge 0$。此时效用向量是

$$u = G^\top G \alpha$$

记 $M = G^\top G$ 为 $T \times T$ 的 Gram 矩阵,那么 $u = M\alpha$。纳什议价解要求最大化效用的对数之和:

$$\max_{\alpha \ge 0} \ \sum_{i=1}^{T} \log\left((M\alpha)_i\right)$$

用对数而不是直接求和,是因为对数函数是凹的、严格单调的,它带来几个非常关键的性质。第一个是帕累托最优性:如果存在另一个方向能让某个任务严格变好而没有任何任务变差,那个方向的解一定比当前解的对数值更大,所以最优解不可能是被支配的。这一点直接排除了“牺牲难任务换简单任务”的解。

第二个是对称性:如果两个任务的梯度完全相同(或者说是可交换的),那么它们在解里分到的权重也必然相同。这条性质听起来平淡,但它恰好是线性标量化做不到的——手工调的权重不可能对任意任务对都自动对称。

第三个也是最实用的一个,无关备选独立性:新增或删除一个与现有任务无关的选项,不会影响其他任务之间的相对权重分配。翻译成人话就是,你在一个已经跑通的三任务实验里加一个新任务,原来那三个任务的平衡关系不会被整体打乱。这一条对工程迭代特别友好,因为现实项目里任务集合是经常变的。

还有一条线性不变性:把所有任务的效用同时乘上一个正的常数,解不变。这正好对应“任务损失量纲不一致”的问题——交叉熵和 L1 回归的量级差几十倍,在线性标量化下是灾难,在对数议价下因为是对数尺度,量级差异被自然压缩了,而且缩放不变性保证了它不会被数值大小带偏。

提示:这四条性质(帕累托最优、对称、线性不变、无关备选独立)就是纳什议价解的公理化刻画,也是这篇论文敢说“我们不是在拍权重”的底气所在。写论文或者做方案评审的时候,这四条是最有力的论据。

2.3 一阶近似下的目标函数推导

把上面的东西串起来,完整的问题形式是

$$\max_{\alpha} \ \sum_i \log\left((M\alpha)_i\right) \quad \text{s.t.} \quad \alpha \ge 0, \ |G\alpha| \le 1$$

那个范数约束不是装饰。前面提过,如果没有它,目标函数沿 $\alpha$ 的射线方向是无界的——你把 $\alpha$ 整体放大 $c$ 倍,效用向量也放大 $c$ 倍,对数之和会加上 $T\log c$,趋向正无穷,问题就没有内点解了。加上范数约束之后,解才被钉在一个紧集上。

对 $\alpha_k$ 求偏导并令其为零,得到一阶条件:

$$\sum_i \frac{g_i^\top g_k}{(M\alpha)_i} - 2\lambda (M\alpha)_k = 0$$

写成矩阵形式是 $M \cdot \frac{1}{M\alpha} = 2\lambda M\alpha$,其中除法是逐元素的。如果 $M$ 可逆,可以把它化简成

$$M\alpha = \frac{1}{2\lambda \alpha} \quad \Longleftrightarrow \quad \alpha \odot (M\alpha) = \frac{1}{2\lambda}\mathbf{1}$$

也就是说,在最优点上,每个任务的“权重乘以它的效用”是一个常数。这个结论其实很有画面感:效用大的任务,权重就小;效用小的任务,权重反而被拉大。这正是议价理论里“弱的一方需要补偿”的直观含义,也是它为什么会自动照顾难任务的数学解释。

由于 $\alpha$ 的绝对尺度不影响最终方向(方向只由 $\alpha$ 的相对比例决定),那个 $2\lambda$ 常数可以直接吸收掉,于是得到一个干净的、可以直接迭代求解的方程组:

$$M\alpha = \frac{1}{\alpha}$$

这就是论文里那个核心最优性条件的来源。它的非线性只来自右边的逐元素取倒数,左边的 $M$ 是个常数矩阵,所以用牛顿法或者序列二次规划处理起来非常顺手。

3. 求解器的实现细节:SQP、线性化与 O(T²) 的复杂度

3.1 最优性条件的矩阵形式与牛顿迭代

要解 $M\alpha = 1/\alpha$,最自然的做法是定义残差函数

$$f(\alpha) = M\alpha - \frac{1}{\alpha}$$

然后跑牛顿法。它的雅可比矩阵是

$$J(\alpha) = M + \text{diag}\left(\frac{1}{\alpha_1^2}, \dots, \frac{1}{\alpha_T^2}\right)$$

每次迭代解一个 $T \times T$ 的线性方程组 $J \delta = -f$,然后 $\alpha \leftarrow \alpha + \delta$。这就是序列二次规划在这个问题上的具体形态,本质上是把非线性的倒数项在当前点做一阶展开后求二次子问题。

实现上有三个点必须注意。第一,$M$ 是半正定的,加上对角正项之后是正定的,所以线性方程组有唯一解,可以直接用 Cholesky 分解,比通用求逆快一倍左右。第二,$\alpha$ 必须保持严格正,否则倒数会炸。我一般把它初始化成均匀分布 $1/T$,每次更新后加一个很小的下限截断,比如 $10^{-8}$。第三,迭代次数不用多,论文里报告的量级是个位数到十几次就能收敛,实际我测下来 5 到 10 次已经足够,再多收益极小但耗时线性增长。

3.2 复杂度到底省在哪里

MGDA 那一系解的是带单纯形约束的二次规划,标准求解器(有效集法、内点法)的复杂度是 $O(T^3)$ 甚至更差,而且是一个完整的 QP,不是一个轻量的迭代。Nash-MTL 把这个换成了固定次数的牛顿迭代,每次迭代解一个 $T\times T$ 的线性系统。

论文报告的做法是:先算一次 $M^{-1}$,然后利用对角更新结构,让每次牛顿迭代的开销降到 $O(T^2)$ 量级。整个求解器的总开销大致是 $O(kT^2)$,$k$ 是迭代次数,通常是个小常数。在 $T=50$ 的时候,这个差距会实实在在地体现在 wall-clock 时间上——你在训练日志里能直接看到每步耗时曲线分叉。

不过我得说句实话:这个复杂度优势是有前提的。它假设 $M$ 可以先算好并求逆,但 $M$ 本身的计算开销是 $O(T^2 d)$,$d$ 是参数维度。如果 $d$ 非常大(比如整个 backbone 全共享),那 $M$ 的构造反而可能成为瓶颈。我的经验是针对大模型采用分层策略:只在最后一个 block 或者 head 之前的层上跑议价,前面的层用普通梯度平均带过。这样既拿到了平衡收益,又不会把 Gram 矩阵算爆。

3.3 边界情况:非内点、秩亏与数值稳定

理论推导假设了内点解存在,也就是存在某个 $\alpha > 0$ 使得 $M\alpha$ 的所有分量都为正。实践里这个假设经常不成立,尤其是训练早期各个任务梯度方向差异极大的时候。这时候最优解会落在边界上,某些 $\alpha_i = 0$,对应任务在这一步被完全放弃。

处理方式有两种。一种是给 $\alpha$ 加一个极小的下界 $\epsilon$,强行把解拉回内部,代价是轻微破坏最优性,但换来数值稳定。另一种是显式做有效集判断,把 $u_i \le 0$ 的任务先剔除出优化,在剩下的子集上求解,再回代检查。论文的实现更接近后一种思路的软化版本,实际代码里通常两者混用。

第二个边界情况是秩亏。当任务数 $T$ 大于梯度所在空间的维度时,$M$ 必然是奇异的,直接求逆会炸。这时候要用伪逆或者加 Tikhonov 正则,也就是解 $(M + \gamma I)\alpha = 1/\alpha$,$\gamma$ 取 $10^{-6}$ 到 $10^{-4}$ 之间。这个正则项还有额外的好处:它能抑制梯度噪声。多任务训练里小 batch 的梯度噪声很大,Gram 矩阵的条件数会很糟,加一点正则相当于做了平滑。

第三个是数值下溢。$\alpha$ 的倒数在某些任务权重极小时会溢出,我一般在算 $1/\alpha$ 之前先把 $\alpha$ 截断到 $[\epsilon, 1]$ 区间内,$\epsilon$ 取 $10^{-8}$ 左右。这个操作看着粗暴,但实测几乎不影响收敛方向,纯粹是防 NaN。

3.4 一份可以直接改的 PyTorch 实现

下面这段是我按论文思路整理的简化版实现,去掉了论文里的加速技巧,优先保证可读和可跑。实际项目里建议把 Gram 矩阵的计算换成分块或者用torch.cdist之类的方式减少内存峰值。

import torch def nash_mtl_direction(grads, n_iters=8, eps=1e-8, reg=1e-5): """ grads: List[Tensor], 每个 tensor 已 flatten 成一维, 长度 D 返回: 合成后的更新方向 d (shape [D]) 与权重 alpha (shape [T]) """ G = torch.stack(grads, dim=1) # [D, T] M = G.t() @ G # [T, T] Gram 矩阵 T = M.shape[0] # Tikhonov 正则, 缓解秩亏与病态 M = M + reg * torch.eye(T, device=M.device, dtype=M.dtype) alpha = torch.full((T,), 1.0 / T, device=M.device, dtype=M.dtype) for _ in range(n_iters): u = M @ alpha # 保正, 避免 log/倒数失效 u = torch.clamp(u, min=eps) a = torch.clamp(alpha, min=eps) f = u - 1.0 / a # 残差 J = M + torch.diag(1.0 / (a ** 2)) # 雅可比 delta = torch.linalg.solve(J, -f) alpha = torch.clamp(a + delta, min=eps) alpha = alpha / alpha.sum() # 只关心相对比例 d = G @ alpha return d, alpha

调用侧的逻辑非常直白:先对每个任务单独反传拿到梯度,拼成列表,调用上面这个函数拿到合成方向,再手动写回p.grad。注意一定要用retain_graph=True或者干脆对每个任务分别做一次 backward 并把梯度存下来,别指望一次 backward 就能拿到所有任务独立梯度。

# 训练循环骨架 for batch in loader: task_grads = [] for i, loss_fn in enumerate(loss_fns): opt.zero_grad() loss = loss_fn(model(batch), batch[f"label_{i}"]) loss.backward(retain_graph=True) g = torch.cat([p.grad.flatten() for p in shared_params]) task_grads.append(g) d, alpha = nash_mtl_direction(task_grads) offset = 0 for p in shared_params: numel = p.numel() p.grad = d[offset:offset + numel].view_as(p).clone() offset += numel opt.step()

注意:上面这段每步要反传 $T$ 次,任务数一多就非常慢。实际生产代码里我用的是torch.autograd.grad配合is_grads_batched或者手写的并行反传,能把开销压到接近单次反传的两三倍。这一点在论文里没有展开,但对落地来说是决定性的。

4. 实验解读:它到底在哪些场景赢了,赢多少

4.1 多任务监督学习:三任务与四十任务的两组对照

论文在多任务监督学习上做了两组比较典型的设定。第一组是三个任务的组合:语义分割、深度估计、表面法线估计,数据来自一个室内场景数据集。这三个任务的输出形态完全不同(离散标签、连续深度、单位法向量),损失函数量纲差异极大,是检验权重平衡方法的经典擂台。第二组在城市街景数据上,任务是语义分割、实例分割和视差估计,任务之间的相关性更强,但梯度冲突也更隐蔽。

在这两组里,Nash-MTL 相对线性标量化和几个强基线都有稳定提升,而且提升不是某一两个任务暴涨换来的,是三个任务同时不退化。这一点比绝对数值更重要——多任务方法最容易翻车的方式就是“平均分上去了但某个任务崩了”,而议价解因为帕累托最优的约束,天然不容易出现这种偏科。

第三组实验是我觉得最有信息量的:人脸属性的多标签分类,四十个左右的二分类属性共享一个 backbone。这个设定的特点是任务数量多、单任务信号弱、任务之间有明显的语义相关性(比如“微笑”和“开心”)。在这种“多而弱”的设定下,固定权重的方法基本调不动,因为你要给四十个任务各调一个权重,搜索空间根本不可行。而议价方法只引入求解器内部的少量迭代,超参几乎为零,这个对比很能说明问题。

4.2 多任务强化学习:十任务与五十任务的规模压力

强化学习的实验更能体现复杂度优势。论文用的是操作类任务的基准集,分别有 10 个任务和 50 个任务的版本。这类设定的难点在于每个任务都需要独立的 rollout,单步开销本来就高,如果优化器再叠一个 $O(T^3)$ 的求解,整体训练时间会被拖到不可接受。

在 10 任务设定上,各方法的差距还算温和;到了 50 任务设定,差距被明显放大。这个放大有两层原因:一是任务数多了之后线性标量化的权重组合空间爆炸,固定权重几乎不可能覆盖所有任务;二是 MGDA 那一系的单步求解开销随 $T$ 三次增长,实际训练里要么跑得极慢,要么被迫减少迭代步数,两个都会掉点。论文报告的结果是,在这个规模下议价方法不仅性能更好,单步耗时也更低,等于同时赢了质量和成本。

这里的结论我建议这样理解:任务数少的时候(3 到 5 个),梯度平衡方法的收益主要体现在“不用调参”上,绝对指标提升可能只有一两个点,你用固定权重仔细调一调也能接近。任务数多的时候(20 个以上),收益才变成结构性的,因为人工调参这条路在物理上走不通了。

4.3 消融与开销对比:哪些设计是必需的

论文的消融实验里,我认为值得关注的有两点。第一是随机种子下的稳定性。多任务方法在小 batch 下的方差往往很大,论文报告了多次运行的均值和标准差,从数字看议价方法的方差控制得不错。这跟它的构造有关:解出来的 $\alpha$ 是 Gram 矩阵的连续函数,不会像投影类方法那样因为两两处理顺序不同而跳变。

第二是求解器迭代次数的影响。次数太少,$\alpha$ 没收敛,方向偏离最优;次数太多,收益递减但耗时线性上涨。论文里给出的经验值是固定的小常数,实际部署时我建议把它做成可配置项,先用 8 次跑通,再根据训练日志里的收敛曲线微调。

开销方面,论文给了一张单步耗时的对比表,议价方法的曲线随任务数增长最平缓。这里我要提醒一句:这些数字强依赖于实现质量(是否有向量化、是否复用了 Gram 矩阵的分解),在自己环境里复现时不要拿论文的绝对数字做预期,看趋势就行。

5. 复现与落地时的坑:我的实操清单

5.1 梯度尺度与反传方式,比公式更容易翻车

理论上议价解对任务的线性缩变是不变的,所以你不必担心任务 A 的 loss 是 0.1 而任务 B 是 100。但这条性质成立的前提是,你拿到的梯度确实是各任务单独反传得到的梯度,不是被某种累积方式污染过的。

我踩过的一个坑是在共享参数上开了梯度累积,结果第二个任务反传时梯度直接累加到第一个任务的梯度上,Gram 矩阵算出来的对角项远大于真实值,解出来的权重严重偏向先反传的任务。表现就是训练曲线里某个任务的指标异常好,其他任务纹丝不动。排查的时候只要打印一下 $M$ 的对角线,如果不同任务的对角元量级跟单独反传时算出的范数对不上,就是这个问题。

第二个坑是有参数的requires_grad状态不一致。有些 head 的最后一层冻结了,但共享层的梯度还在,如果按参数列表遍历时漏了或者多算了一些参数,拼出来的向量维度跟后面的写回逻辑会对不上,报错还算好,怕的是维度正好对上但语义错位,那训练出来的模型就是个谜。我现在的做法是把共享参数的列表在训练开始前固定下来,存成一个 list,后面所有反传和写回都只遍历这个 list。

第三个坑跟混合精度有关。AMP 下的梯度是 fp16 的,算 Gram 矩阵时内积很容易溢出或者下溢。我的处理方式是在算 $M$ 之前统一转成 fp32,求解完再把方向转回 fp16 写回。这一步会带来一点显存和耗时的开销,但比遇到 NaN 之后再回滚要划算得多。

5.2 求解器超参怎么定,给一组能直接用的初值

论文列了几个超参,但没给太细的调参指南。我按复现经验给一组默认值,可以直接拿来当起点:

参数建议初值调整方向说明
迭代次数n_iters8收敛慢就加到 12,想省时就减到 5超过 15 基本没有收益
正则系数reg$10^{-5}$出现 NaN 或条件数爆炸时提高到 $10^{-3}$任务数大于梯度维度时要显著加大
权重下限eps$10^{-8}$一般不用动防止倒数溢出
步长与单任务训练持平或略小训练不稳定时先降 30%合成方向的范数跟单任务不同
是否归一化 $\alpha$是基本必须只关心相对比例,归一化更稳

关于步长,这里有个细节值得说。合成方向 $d = G\alpha$ 的范数跟单个任务梯度的范数是不同量级的,$\alpha$ 归一化之后 $d$ 的范数大致等于各任务梯度的加权平均范数。如果你原本给单任务调好的学习率是 $1e-4$,直接搬到多任务上通常偏大,因为合成方向在参数空间里走的距离更远。我的习惯是先按 0.7 倍缩放,观察前 500 步的 loss 曲线再决定要不要调回去。

5.3 常见问题排查速查表

现象最可能的原因排查与处理
训练早退 NaNfp16 溢出或倒数溢出转 fp32 算 Gram;$\alpha$ 加下限;加正则
某个任务指标长期不涨该任务梯度太小被淹没打印 $\alpha$,确认它是否接近 0;考虑单独给该任务加 warmup
所有任务一起变差合成方向范数过大降学习率;检查 $\alpha$ 归一化是否生效
单步耗时随任务数暴涨Gram 矩阵构造是瓶颈只对最后几层做议价;分块计算内积
换随机种子结果波动大小 batch 梯度噪声加大 batch;提高正则系数;对 $M$ 做移动平均
加了新任务后老任务退化任务间存在强冲突这是正常现象,先确认帕累托性;考虑任务分组
$\alpha$ 全挤在一个任务上该任务效用远大于其他任务检查效用是否用了相对参考点的定义,而不是原始损失

提示:打印 $\alpha$ 这件事一定要做。它不是给人看的装饰性指标,而是这套方法最直接的可解释性输出。$\alpha$ 长期极端分布,说明任务之间存在结构性矛盾,这时候该动的是任务定义和损失设计,而不是继续调优化器。

6. 这东西在你自己的项目里怎么用

6.1 什么时候不该用它

先把不适合的场景说清楚,省得有人盲目套。第一种是任务数很少且量纲接近的场景,比如两个都是分类任务、loss 都是交叉熵、数据量也差不多。这种情况下线性标量化调一调就能达到接近的效果,引入议价求解器纯属给自己加复杂度。

第二种是梯度维度远小于任务数的极端情况。比如你只在最后一个小 head 上做多任务,参数量可能还不如任务数多,这时候 Gram 矩阵严重秩亏,议价解的方向基本由正则项主导,意义不大。我的判断标准是任务数不超过共享参数维度的百分之一,否则优先考虑别的方案。

第三种是任务之间存在明确的因果或时序依赖的设定。议价模型假设各任务是平等的谈判方,但如果任务 B 必须在任务 A 收敛之后才有意义(比如先检测后跟踪),那把它们放在同一层博弈里是不合适的,应该做分阶段训练或者分层控制。

6.2 与其他技巧的组合姿势

实际项目里我很少单独用议价方法,更多是把它当成一个“方向合成器”,前后各挂一些工程手段。前面那层负责把梯度弄干净:用大的有效 batch 降噪、用梯度裁剪限幅、对 Gram 矩阵做指数移动平均平滑。后面那层负责把方向用稳:学习率 warmup、按层做不同的缩放、必要时对合成方向再做一次裁剪。

一个我觉得特别有效的组合是“分层议价”。backbone 浅层用普通的梯度平均(因为浅层特征本来就是共享的,冲突不严重),中间层开始引入议价,最后的 task head 完全独立不参与。这样既拿到了平衡收益,又把 Gram 矩阵的维度控制在几百以内,求解开销可以忽略。我在几个内部项目上都是这么配的,收敛曲线比全局面议价更平滑。

还有一个组合是把它当成诊断工具用。就算你最后决定不用议价优化器,也可以在每个 epoch 跑一次求解,看看 $\alpha$ 的分布。如果某个任务的 $\alpha$ 长期接近零,说明它在这个共享结构下根本没有被优化的空间,那你要做的可能是给它单独加一路分支,或者干脆拆成两个模型。这个用法比它作为优化器本身的价值还大,因为参数平衡问题很多时候不是优化问题,是结构设计问题。

最后再分享一个小技巧:把 $\alpha$ 的熵作为一个正则项加进去,鼓励权重分布不要太极端。这不是论文里的东西,是我自己在几个任务数很多的设定下试出来的。做法是在损失里加 $-\beta \sum \alpha_i \log \alpha_i$,$\beta$ 取很小的值($10^{-3}$ 量级),作用是防止求解器在早期就把某些任务的权重压死。效果上就是训练曲线更平缓,长期指标基本不变,但早期崩掉的概率明显降低。这个改动没有理论保证,纯粹是工程上的稳妥起见,介意这一点的可以不加。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询