Task Arithmetic
任务算术通过添加、减去或组合任务向量来编辑预训练模型,无需训练即可融合技能或移除行为。
任务算术是一种编辑预训练模型的方法:对任务向量进行加减运算,而任务向量就是各任务微调所产生的权重变化。将多个任务的任务向量加到预训练的模型权重上,可得到能够处理所有这些任务的单个模型;减去某个任务向量,会降低模型在该任务上的表现,同时基本不影响其他任务;通过类比组合向量,则可将某项技能迁移到没有标注数据的场景。每次编辑都是对权重逐元素相加,因此除原始微调过程外,无需训练数据或梯度更新。华盛顿大学、Microsoft Research 和艾伦人工智能研究所的 Gabriel Ilharco 等人在 Editing Models with Task Arithmetic 中介绍了这种方法,该论文发表于 ICLR 2023。
三种操作#
三种操作都会产生新权重 θ_new = θ_pretrained + λτ_new,其中缩放系数 λ 通过留出的验证数据选择。Ilharco 等人对在八项图像分类任务(例如交通标志和卫星图像)上微调的 CLIP 模型进行了测试,也测试了 GPT-2 和 T5 语言模型。
取反使用 τ_new = −τ,让模型遗忘某项任务。对 CLIP 的八个任务向量取反后,ViT-L/14 模型在这些任务上的平均准确率下降了 45.8 个百分点,同时仍保留至少 95% 的 ImageNet 准确率。减去在有毒评论上微调得到的向量后,GPT-2 Large 生成有毒内容的比例从 4.8% 降至 0.8%,而其普通文本困惑度与原始模型相差不到 0.5 个点。
相加使用 τ_new = τ_1 + τ_2 + … 来构建多任务模型。将两个 CLIP 任务的向量相加后,单个模型在这两个任务上的平均准确率达到两个专用模型平均水平的 98.9%;将八个任务的向量全部相加后,准确率达到 91.2%。
类比适用于任务之间存在“A 之于 B,正如 C 之于 D”的关系,此时使用 τ_D ≈ τ_C + (τ_B − τ_A)。Ilharco 等人通过将 Amazon 评论情感的向量加到在 Yelp 和 Amazon 文本上训练的语言模型向量之差中,构建了一个无需 Yelp 标注数据的 Yelp 评论情感分析模型。
这些操作之所以有效,是因为不同任务的任务向量方向几乎正交,其余弦相似度接近零,因此加入一个向量几乎不会改变另一个向量所编码的内容。随着任务数量增加,干扰仍会加剧;模型合并方法(例如 TIES-Merging)会先裁剪每个向量中幅度最小的变化,并解决符号不一致的参数,再进行相加,从而减少干扰。
用代码添加和取反技能#
这个 PyTorch 示例先使用 8x8 手写数字对小型分类器进行短暂预训练,然后微调其第一层来执行两个任务:任务 A 对数字 0-4 分类,任务 B 对数字 5-9 分类;每个任务都从自己独立的五个冻结输出 logit 中读取结果,与论文中每个 CLIP 任务使用独立冻结分类头的做法相同。接着,示例会评估预训练权重与两个任务向量的不同组合:
import copy
import torch
from sklearn.datasets import load_digits
from torch import nn
torch.manual_seed(0)
X, y = load_digits(return_X_y=True)
X, y = torch.tensor(X / 16, dtype=torch.float32), torch.tensor(y)
tasks = {"A": (0, 5), "B": (5, 10)} # task A classifies digits 0-4, task B digits 5-9, each with its own 5 logits
train, test = torch.arange(len(X)) < 1000, torch.arange(len(X)) >= 1000
def task_loss(model, task, rows):
"""Cross-entropy and accuracy of one task's 5-way classifier on the given rows."""
lo, hi = tasks[task]
keep = rows & (y >= lo) & (y < hi)
logits, labels = model(X[keep])[:, lo:hi], y[keep] - lo
return nn.functional.cross_entropy(logits, labels), (logits.argmax(1) == labels).float().mean().item()
def fit(model, params, steps, loss_fn):
"""Run full-batch SGD on the given parameters."""
optimizer = torch.optim.SGD(params, lr=0.05, momentum=0.9)
for _ in range(steps):
optimizer.zero_grad()
loss_fn().backward()
optimizer.step()
return model
# Pretrained model: a few steps on 200 images of all ten digits
base = nn.Sequential(nn.Linear(64, 64), nn.ReLU(), nn.Linear(64, 10))
fit(base, base.parameters(), 50, lambda: nn.functional.cross_entropy(base(X[:200]), y[:200]))
# Fine-tune the first layer for each task (heads stay frozen) and keep the task vectors
vectors = {}
for task in tasks:
tuned = copy.deepcopy(base)
fit(tuned, tuned[0].parameters(), 100, lambda: task_loss(tuned, task, train)[0])
vectors[task] = {k: v - base.state_dict()[k] for k, v in tuned.state_dict().items()}
edits = {"Pretrained": {}, "+A": {"A": 1}, "+B": {"B": 1}, "+A +B": {"A": 1, "B": 1}, "-A": {"A": -1}}
for name, coefs in edits.items():
# Pretrained weights plus a weighted sum of task vectors
model = copy.deepcopy(base)
weights = {k: v + sum(c * vectors[t][k] for t, c in coefs.items()) for k, v in base.state_dict().items()}
model.load_state_dict(weights)
with torch.no_grad():
print(f"{name}: task A {task_loss(model, 'A', test)[1]:.3f}, task B {task_loss(model, 'B', test)[1]:.3f}")在 797 个测试数字上,预训练模型在任务 A 上的得分为 0.802,在任务 B 上的得分为 0.897。只添加任务 A 的向量会将任务 A 的得分提升至 0.925,但会使任务 B 的得分降至 0.850,这就是常规微调中的灾难性遗忘;只添加任务 B 的向量则会产生相反结果。两个向量都添加后,单个模型在两个任务上的得分分别为 0.922 和 0.940,接近两个专用模型各自在对应任务上的表现。减去任务 A 的向量会使任务 A 的得分降至 0.616,而任务 B 的得分仅从 0.897 变为 0.887。
要求与限制#
任务算术要求所有微调都从相同架构、相同预训练权重开始。当 Ultralytics YOLO26 在新数据集上训练时,如果 yolo26n.pt 中的预训练张量形状匹配,就会复制这些张量;分类头的行则按类别名称匹配,并将数量记录为“Transferred N/M items from pretrained weights”。类别数量不同的数据集会导致最终分类卷积的形状不同,因此这些运行产生的任务向量只能在各次运行共有的层中相加。
当向量相互冲突或缩放幅度过大时,算术效果也会变差,因为较大的 λ 值会将权重推离微调模型表现良好的区域。取反会降低模型在微调数据所覆盖范围内的表现,这使其成为一种轻量级的机器遗忘形式。Guillermo Ortiz-Jimenez、Alessandro Favero 和 Pascal Frossard 在 Task Arithmetic in the Tangent Space 中指出,成功的算术操作源于权重解耦:权重空间中的不同方向会改变模型在输入空间中互不重叠区域的输出;他们还表明,对模型的线性化版本进行微调可以加强这种效果。










