一个Adapter处理多任务:任务条件化特征变换缓解灾难性遗忘
2026/9/6 19:34:31 网站建设 项目流程

做过多任务或增量式业务模型训练的同学,大概率遇到过这样一个场景:模型先学会了 A 任务的数据分布,等训练完 B 任务再回头测试 A 任务时,A 的准确率掉得让人怀疑代码是不是写错了。这种现象并不是玄学,而是深度学习中非常经典且棘手的问题——灾难性遗忘(Catastrophic Forgetting)。

业内已经提出了很多缓解思路:回放旧样本、对重要参数做约束、给不同任务分配独立分支等。但它们在真实业务中往往会遇到存储开销大、参数膨胀快、训练不稳定等问题。最近持续学习方向有个非常值得关注的范式:用“一个 Adapter”,通过任务条件化的特征变换(Task-Conditioned Feature Transformations),让一套共享基础网络服务多个任务。这类思路不仅论文越来越多,也逐渐被引入到多任务视觉模型、增量分类、个性化推荐等工程场景中。

本文将围绕“One Adapter, Many Tasks”这条路线做一次系统的原理拆解与思路演示。我会先讲清楚它解决什么问题,再分析模块结构与训练方式,最后给出可复现的 PyTorch 思路代码和避坑建议。适合正在做持续学习、多任务学习或模型增量更新的研究者和工程师阅读。

1. 背景与核心问题

1.1 持续学习的基本设定

持续学习(Continual Learning)要解决的核心问题是:模型在一系列任务上顺序训练,需要同时做到三点:

  • 学会当前任务。
  • 不遗忘历史任务。
  • 能够复用之前学到的通用特征,而不是每个任务从头学。

按照测试时的假设,持续学习通常被分成几类:

设置训练阶段测试阶段难度
Task-Incremental Learning按任务顺序训练知道当前测试样本属于哪个任务相对容易
Domain-Incremental Learning按域顺序训练不需要知道任务标识中等
Class-Incremental Learning按类别组顺序训练不仅要分类,还要推断任务来源较难

本文讨论的 Task-Conditioned Feature Transformations,天然适合“测试时任务编号已知或可推断”的设置。这也是很多持续学习论文默认的评估方式。

1.2 为什么普通微调会导致灾难性遗忘

假设模型已经学会了 A 任务,我们要继续学 B 任务。如果直接让整网络参与梯度更新,优化器会为了降低 B 的 loss,把大量权重推向适合 B 的方向。此时那些对 A 很关键、但对 B 并不重要的权重也会被修改。

从特征空间的角度看,B 任务训练会让特征提取器逐渐把表征空间“扭曲”成更适合 B 的形状。任务 A 的旧样本如果不再出现,A 的分类边界就缺少梯度约束,因此很容易被覆盖。

常见的防遗忘方案有:

  • 回放真实样本或生成样本。
  • 对重要权重施加二次惩罚。
  • 新任务增加新分支或扩展新容量。

回放方法效果直观,但需要维护样本缓冲区,并要考虑隐私与存储成本。正则化方法只更新共享参数,但容易在任务数量较多时约束过强或过弱。动态扩展新分支的方法效果不错,可模型体积会随着任务数量线性增大。

1.3 Adapter 与“参数高效”思路的引入

近年来,很多研究者开始转向一种更经济的做法:固定预训练骨干网络的绝大部分参数,只训练插入其中的少量 Adapter 模块。每个任务用很小的额外参数去适配新的数据分布。

“One Adapter, Many Tasks”的范式,则更进一步:不打算为每个任务保存一份独立 Adapter,而是希望所有任务共享同一个 Adapter 实例,通过任务条件(Task Conditioning)机制去控制这个 Adapter 对特征施加的变换。

这样做的好处显而易见:

  • 额外参数不会随任务数量线性膨胀。
  • 共享模块保留了任务间可迁移的通用知识。
  • 任务特有信息被压缩在低维的任务嵌入中。
  • 特征变换的任务条件化让模型可以“按需调整”特征空间。

这就是本文将重点分析的思路核心。

2. Task-Conditioned Feature Transformations 核心概念

2.1 Adapter 的经典形态

自然语言处理中非常常见的 Adapter 结构是一个“瓶颈”子网络:

  1. 先对输入特征做降维。
  2. 经过非线性激活。
  3. 再升维回到原始维度。
  4. 通过残差连接叠加到原始特征上。

在视觉骨干网络里,Adapter 通常插入在每个 Transformer Block 或卷积 Block 之后,把主干特征做一次轻量变换。这样做的好处是:骨干网络可以保持预训练状态,大幅减少需要更新的参数量,同时又能贴近目标任务进行特征调整。

2.2 特征变换的两种基本类型

在“One Adapter”路线中,特征变换通常可以归纳为两种类型:

第一种是“特征级仿射变换”。模型根据任务标识,为每个特征通道生成一组缩放系数 gamma 和偏置 beta。设共享特征为 h,任务条件特征变换可以写成:

h' = gamma_t ⊙ h + beta_t

其中 gamma_t 和 beta_t 是根据任务 t 生成的。这类方法与 FiLM、Conditional Normalization 的思想一脉相承,优点是计算成本极低,只需要逐通道操作即可完成对特征分布的调整。

第二种是“低秩变换”。我们可以把共享特征 h 乘上一个任务相关的小矩阵 Delta_t,再叠加到原特征上:

h' = h + Delta_t(h)

如果 Delta_t 被打成低秩形式,例如通过两个低维矩阵相乘实现,那么不同任务共享同一套参数骨架,只是低秩矩阵的组合系数不同。

在实际论文中,这两种操作经常被合在一起使用。底层 Base Model 提供通用表示,Adapter 提供任务相关的特征调整,Task Embedding 则负责生成调整参数。

2.3 “任务条件”到底条件化什么

很多人第一次看到“Task-Conditioned”时会困惑:任务编号是一个离散标签,怎么和神经网络联系起来?

最常见的做法是为每个任务学习一个低维任务向量(Task Embedding)。这个任务向量的长度可能只有 16、32 或 64 维。它参与预测 Adapter 参数,而不是直接作为输入拼接到图像或文本特征上。

例如:

  • 任务嵌入输入到一个小型 Hypernetwork 中。
  • Hypernetwork 输出每个通道的 gamma 和 beta。
  • 或者输出低秩矩阵的系数。

测试阶段如果任务编号已知,直接根据 task id 读取对应的任务向量,计算该任务的变换参数即可。任务编号未知时,就需要额外的任务推断模块,或者用“每轮输入特征选出最相似任务向量”的方式来近似。

2.4 为什么共享 Adapter 不容易遗忘

传统全量微调容易遗忘的一个重要原因,是模型几乎没有“任务隔离”机制。而共享 Adapter 模式下,事情变得不一样:

  • 骨干网络被冻结,通用特征分布稳定。
  • 每个任务通过自己的 Task Embedding 决定特征的调整方向。
  • 学习新任务时,Adapter 只负责更新所有任务共享的“变换能力”,而任务本身的信息被压缩在任务向量中。

也就是说,任务 A 的表征空间并没有被破坏。每次训练后,任务 A 对应的 Task Embedding 和 Adapter 配合后,依然能还原出“A 任务想要的特征变换”。遗忘的风险从“整个网络权重漂移”降低为“共享 Adapter 内部发生冲突”,而后者更容易通过正则、低秩约束或不共享骨干等方式缓解。

3. 系统结构与训练流程拆解

3.1 一个通用系统框架

为了实现“一个 Adapter,多任务共用”,整个系统通常由以下部分组成:

组件作用是否随任务增加
骨干网络提取通用特征
共享 Adapter对特征做任务条件变换
Task Embedding描述任务特性是,每个任务一个向量
Task Condition Generator由任务向量生成变换参数
Task Classifier当前任务分类头看设计,可共享可独立

这里最值得注意的点是:额外参数的增长只有 Task Embedding,其余模块可跨任务复用。Task Embedding 如果每任务只占几十个浮点数,那即使训练几百个任务,额外参数量也远小于给每个任务单独存一份 Adapter。

3.2 主干特征提取

用卷积网络举例,去掉分类层后,模型对输入图片 x 输出一个特征图或特征向量:

h = Backbone(x)

为了让 Adapter 尽量轻量,通常会在特征维度较高时先降维。例如 Transformer 中每层特征可能为 768 维,Adapter 内部先降到 96 维,再做变换。

3.3 Adapter 的任务条件化生成

当需要处理第 t 个任务时,系统会读取任务向量 e_t。然后,任务条件生成器根据 e_t 生成 Affine 参数:

[gamma_t, beta_t] = G(e_t)

其中 G 可以是一个两层 MLP。为了让不同任务尽量使用不同的特征变换方向,可以对 gamma_t 初始化成接近 1 的数,对 beta_t 初始化成接近 0 的数。这样在训练初期,Adapter 近似于恒等映射,不会破坏骨干网络的原有表征。

3.4 分类与损失

经过 Adapter 变换后的特征 h_t 会被送入任务 t 对应的分类头。

有两种常见分类头设计:

  • 如果有共享的语义空间,所有任务共享一个分类头,只在旧类别输出位置做 Mask。
  • 如果每个任务标签空间独立,可以为每个任务保留一个轻量分类头。

第二种更符合 Task-Incremental 的设定。由于任务向量本身已包含任务信息,分类头甚至可以直接由任务向量动态生成,进一步减少任务独立参数量。

3.5 训练阶段的“串行”逻辑

持续学习的训练过程不是多任务联合训练,而是按顺序进行的:

  1. 训练任务 1 时,随机初始化 Task Embedding 1,和共享 Adapter、分类头一起更新。
  2. 训练任务 2 时,冻结骨干网络,新加入 Task Embedding 2。
  3. 优化器继续更新共享 Adapter,同时更新 Task Embedding 2。
  4. 为了增强稳定性,可以在更新共享 Adapter 时加一些正则约束,或者使用较低的学习率。

这样可以保证当前任务学到的新知识不会对旧任务产生毁灭性影响。

4. 用一个 PyTorch 示例演示核心思路

论文中的完整代码通常比较工程化。为了帮助你理解原理,这里我用一个简化示例演示整套流程。这个 Demo 不是论文官方实现,而是“任务条件特征变换”思想的最小复现版本。

4.1 创建任务条件 Adapter

假设骨干网络输出一个 256 维的特征向量。我们为每个任务维护一个 32 维的任务向量,用一个生成器输出缩放系数与偏置。

import torch import torch.nn as nn import torch.nn.functional as F class TaskConditionedAdapter(nn.Module): """ 任务条件化的 Feature Transform: 1. 每个任务有一个 task embedding。 2. 条件生成器根据 task embedding 生成逐通道 gamma 和 beta。 3. 特征经过 affine 变换后与残差连接。 """ def __init__(self, feature_dim=256, cond_dim=32): super().__init__() self.feature_dim = feature_dim self.cond_dim = cond_dim # 每个任务的 embedding,会在训练中按任务索引读取 self.register_buffer("task_embeddings", None) self.task_embedding_dim = cond_dim # 条件生成器:task embedding -> (gamma, beta) self.condition_net = nn.Sequential( nn.Linear(cond_dim, feature_dim), nn.ReLU(inplace=True), nn.Linear(feature_dim, feature_dim * 2), ) # 一个简单的瓶颈变换,作为共享特征变换能力 self.transform = nn.Sequential( nn.Linear(feature_dim, feature_dim // 2), nn.ReLU(inplace=True), nn.Linear(feature_dim // 2, feature_dim), ) # 初始化 for m in self.condition_net.modules(): if isinstance(m, nn.Linear): nn.init.zeros_(m.weight) nn.init.zeros_(m.bias) # 这样初始时 gamma ≈ 1, beta ≈ 0 # 后续真正实现时需要将最后一个 Linear 的 bias 设置为 [1, 0] # 简单起见,这里在 forward 中对输出做了数学处理 def set_task_embeddings(self, num_tasks): """ 为 num_tasks 个任务创建可学习的 task embedding。 """ self.task_embeddings = nn.Parameter( torch.randn(num_tasks, self.task_embedding_dim) * 0.02 ) def forward(self, x, task_id): """ x: [B, feature_dim] task_id: [B] 或标量 """ if not isinstance(task_id, torch.Tensor): task_id = torch.tensor(task_id, dtype=torch.long, device=x.device) if task_id.dim() == 0: task_id = task_id.unsqueeze(0).expand(x.size(0)) task_emb = self.task_embeddings[task_id] # [B, cond_dim] cond_out = self.condition_net(task_emb) # [B, feature_dim * 2] gamma, beta = cond_out.chunk(2, dim=-1) # 让初始 gamma 接近 1,beta 接近 0 gamma = torch.tanh(gamma) * 0.1 + 1.0 beta = torch.tanh(beta) * 0.1 transformed = x * gamma + beta residual = self.transform(transformed) return F.relu(transformed + residual)

这个模块的核心是“条件生成器 + 共享特征变换”。不同任务虽然共享同一个 Adapter 实例,但由于任务 embedding 不同,gamma 和 beta 也不同,因此产生的特征变换是具有任务倾向性的。

但这段代码还比较粗糙,真实使用时需要注意最后一层初始化和标准化方式。如果希望逐通道缩放,还可以让 gamma、beta 保持和 x 第二维相同;如果 x 是[B, C, H, W],则需要按通道维度缩放。

4.2 构造一个简单的持续学习训练循环

为了让整个流程更清晰,我写出一个模拟训练循环。这里没有用真实数据集,只是演示“如何让新任务与旧任务协同训练”。

def train_one_task(model, adapter, classifier, train_loader, task_id): """ 训练单个任务。 假设模型 backbone 已冻结,需要训练 adapter、task embedding 和当前任务分类头。 """ # 取出当前任务的 embedding task_embedding = adapter.task_embeddings[task_id] # 需要优化的参数:adapter 中除 task embedding 外的共享参数 + 当前 task embedding + 当前任务分类头 params = [] params.append(task_embedding) params += list(adapter.transform.parameters()) params += list(adapter.condition_net.parameters()) params += list(classifier.parameters()) optimizer = torch.optim.Adam(params, lr=1e-3) loss_fn = nn.CrossEntropyLoss() for epoch in range(3): for x, y in train_loader: x = x.cuda() y = y.cuda() adapter.train() classifier.train() with torch.no_grad(): feat = model(x) # 特征 feat_adapted = adapter(feat, task_id) # 任务条件特征变换 logits = classifier(feat_adapted) loss = loss_fn(logits, y) optimizer.zero_grad() loss.backward() optimizer.step()

实际训练中,我们不建议完全零梯度冻结 backbone,因为低学习率微调骨干网络往往能提升新任务表现。但这时要配合 EWC 等正则方法,防止灾难性遗忘。

4.3 测试阶段如何利用任务标识

在 Task-Incremental 设置下,测试时知道样本属于哪个任务。因此我们可以直接读取对应任务的 task embedding,并调用与任务配套的分类头。

@torch.no_grad() def eval_task(model, adapter, classifiers, test_loader, task_id): """ 验证第 task_id 个任务的准确率。 classifiers 是一个 list,每个任务一个分类头。 """ model.eval() adapter.eval() classifier = classifiers[task_id] classifier.eval() correct = 0 total = 0 for x, y in test_loader: x = x.cuda() y = y.cuda() feat = model(x) feat_adapted = adapter(feat, task_id) logits = classifier(feat_adapted) pred = logits.argmax(dim=1) correct += (pred == y).sum().item() total += y.size(0) return correct / total

这个测试逻辑说明了一件事:在 Task-Incremental 场景下,task id 是模型推理时的重要先验信息。如果你的实际场景拿不到 task id,那么还需要额外增加任务推断分支,或者在特征空间设计“最近邻任务匹配”策略。

4.4 评估持续学习效果:平均准确率与遗忘度

学术界评估一个持续学习算法通常不只关心最后一个任务。常见指标是“所有任务的平均准确率”。它可以简单实现如下:

def compute_average_accuracy(results): """ results: dict, key 为训练到第 k 个任务后测试得到的准确率矩阵 这里简化为 list of lists,第 i 行表示训练完第 i 个任务后, 分别测试第 0..i 个任务的准确率。 """ total = 0.0 for history in results: total += history[-1] return total / len(results)

更常用的指标是 BWT(Backward Transfer),表示在学到新任务后,旧任务准确率的变化。负值越大说明遗忘越严重。

def compute_bwt(acc_matrix): """ acc_matrix[i][j]:训练完第 i 个任务后,测试第 j 个任务的准确率。 BWT 通常是 (训练完最后一个任务后对任务 j 的准确率 - 刚训练完任务 j 时的准确率) 的平均。 """ n_tasks = len(acc_matrix) total_bwt = 0.0 for j in range(n_tasks - 1): total_bwt += acc_matrix[-1][j] - acc_matrix[j][j] return total_bwt / (n_tasks - 1)

5. 几个高频问题与排查思路

5.1 为什么新任务会降低旧任务准确率?

即使我们冻结了骨干网络,只训练共享 Adapter,理论上遗忘风险已经降低,但仍然可能出现旧任务准确率下降。原因通常有三个:

  • 条件生成器或共享 Adapter 被新任务过度优化,扰动了旧任务的变换方向。
  • 任务 embedding 初始化太差,导致新旧任务的任务向量在高维空间发生冲突。
  • 学习率过高,共享参数更新步长太大。

排查时可以先冻结 Adapter 的共享参数,只训练任务 embedding 和分类头;再逐步放开限制。如果这种方式旧任务表现稳定,就说明问题出在共享 Adapter 的更新策略上。

5.2 增大 Task Embedding 维度一定更好吗?

不一定。任务向量维度过高,可能让条件生成器过拟合训练时的任务分布,也容易导致不同任务向量之间正交性不足。维度太低则可能无法充分表达不同任务的差异。

建议先从 16 或 32 维开始尝试。在训练时可以对 Task Embedding 加 L2 正则,甚至加一个正交化约束,促使任务向量之间保持区分度。

5.3 测试时 task id 未知怎么办?

这是从 Task-IL 走向 Class-IL 时最核心的难点。几种常见思路:

  • 用一个小型任务预测网络,在特征层面判断当前样本属于哪个任务。
  • 把每个任务的分类头向量存下来,测试样本特征经过不同 Adapter 变换后,用熵值最低的那个任务作为结果。
  • 设计完全 task-agnostic 的条件机制,例如通过聚类或查询模型来自动决定变换。

最后一种难度更高,但如果业务场景不允许任务标签,就必须认真设计。

5.4 是每个 Block 都插入 Adapter,还是只插最后几层?

取决于特征空间的性质。

  • 如果不同任务输入分布差异很大,例如自然图像与医学图像,建议在较浅层也做任务条件化变换。
  • 如果任务差异主要体现在高层语义,例如类别不同但图像背景相似,只在最后部分插入 Adapter 就足够。

任务条件化 Feature Transform 通常比普通 Adapter 更灵活,因为它直接调制特征通道,对浅层特征也能产生较强调整。但从训练稳定性的角度看,插入位置过多会导致优化负担增大。

5.5 Adapter 参数量可以进一步压缩吗?

可以。常见压缩策略:

  1. 把 task embedding 先降维,再输入条件生成器。
  2. gamma 和 beta 只作用于部分通道,例如每隔几个通道共享一组参数。
  3. 使用低秩矩阵近似替代 MLP 形式的条件生成器。
  4. 共享条件生成器,不同任务只学习极低维的调制向量。

6. Adapter、Prompt、Hypernetwork 之间的对比

在“One Adapter, Many Tasks”范式中,很容易联想到它的近亲:Prompt、Hypernetwork、条件归一化。

方法类别基本假设额外参数稳定性适用场景
每个任务独立 Adapter任务间完全不共享适配参数随任务数量线性增长任务差异极大
本文讨论的任务条件 Adapter任务共享多数参数,仅用任务向量调制每任务仅一个低维向量中高任务数量多、差异可控
Prompt / Prefix Tuning在输入或特征序列中加可学习前缀每任务 prompt 参数预训练 Transformer 模型
Hypernetwork用超网络批次生成任务参数每任务一个 embedding需要动态生成较多网络参数
全量微调 + EWC直接更新骨干很少任务数量少、关系强

从参数效率与性能之间的平衡来看,任务条件 Adapter 的思路非常优秀。它的关键优势在于:任务之间的冲突被限制在一个极小的变换模块内,并且这个模块不仅是“存储任务信息”,还带着建模共享特征变换规律的能力。

Prompt 也很有竞争力,但 Prompt 更适合 Transformer 结构,且很难把“特征缩放”这种操作直接做出来。而 Task-Conditioned Feature Transformations 可以自由应用在 CNN、MLP、Transformer 任意特征层上,应用范围更广。

7. 实验设计与最佳实践

7.1 适合验证该思路的数据集

学术研究中常用来评估持续学习算法效果的数据集包括:

  • Split-CIFAR-10 / Split-CIFAR-100:把类别分成多个任务。
  • Split Tiny-ImageNet:每轮 20 或 25 个类。
  • DomainNet / Core50:更偏域增量场景。

如果只是自己快速验证思路,可以先从较小的 Split-CIFAR-10 开始。它任务数量少,训练成本低,便于观察模型是否遗忘。

7.2 工程设计层面的注意事项

如果要在项目中落地这套思路,我建议从以下几个角度做好设计。

第一,将“任务无关能力”和“任务相关能力”分离。骨干网络最好使用预训练模型并冻结,或者用很小的学习率微调。任务向量负责任务差异,Adapter 的共享能力负责跨任务迁移。

第二,明确新旧任务的更新边界。新任务加入时,不应随意改动旧任务的分类头。必要情况下可以把旧分类头设置为不可训练,只通过共享 Adapter 去兼容新旧任务。

第三,设计好实验对照组。不要只对比“从头训练”和“当前模型”。最好对比:

  • 全量微调。
  • 每个任务独立 Adapter。
  • 每个任务独立 Prompt。
  • 本文所示的任务条件共享 Adapter。

这样才能清楚看出“任务条件化”带来的增益到底来自共享参数量少,还是来自任务间信息迁移。

第四,调参时重点关注条件生成器的大小和学习率。条件生成器如果太大,相当于引入了一个小型网络,反而增加优化难度;学习率太大,则会让不同任务的任务向量在训练末期剧烈漂移。

第五,使用余弦退火或更低的学习率来训练 Task Embedding。持续学习任务在后期往往进入“微调瓶颈”,此时过大的学习率会导致旧任务信息被覆盖,但过小的学习率又会拖慢新任务收敛。

7.3 面向真实业务的改造思路

如果业务场景是“用户分类模型每个月新增一个类别组”,可以考虑:

  1. 预训练一个通用骨干网络。
  2. 骨干网络后面接一个任务条件 Adapter。
  3. 每个新增类别组只创建一个新的 Task Embedding 和一个对应的分类头。
  4. 旧类别的分类头可以冻结,让旧特征流过任务条件变换后再进入旧分类器。

这种方式在工程上的最大收益是:不需要长期保存全量历史训练数据,也不需要重训整个模型,甚至可以把 Task Embedding 当作一种轻量记忆单元存到数据库或配置中心。每次要支持新业务,只需要更新少量参数。

7.4 安全与生产环境注意事项

如果这套思路用于生产环境,还有两点必须强调:

  • 任何删除或覆盖旧任务参数的操作,都应该先做模型版本备份,并准备回滚机制。
  • 在涉及用户隐私的增量学习场景中,不要为了提升效果而违规留存用户原始数据。回放样本和旧任务特征都应该经过脱敏或去标识化处理。

8. 总结与下一步学习建议

这篇文章从持续学习中的灾难性遗忘出发,拆解了“One Adapter, Many Tasks”背后的任务条件化特征变换思想。重点可以归纳为:

  • 共享骨干网络提供任务无关的基础表征。
  • 一个轻量 Adapter 对特征做变换。
  • 不同任务共享 Adapter 参数,任务差异由低维 Task Embedding 决定。
  • 条件生成器把任务描述转换成通道级别的缩放因子和偏移量。
  • 整个系统额外参数增长极少,适合任务数较多、不能全量存储模型的场景。

文章中的 PyTorch 示例只用于演示“任务条件生成 + 特征调制”的操作关系,并不能直接替换论文实现。真要复现或改造,还建议结合具体骨干网络的层结构、输入大小与任务的类别划分来推敲网络深度和参数维度。

如果你正在做持续学习方向的科研,下一步可以从三个方向深入:

  • 阅读 Parameter-Efficient Fine-Tuning(PEFT)的经典工作,理解 Adapter 的插入位置与初始化方式。
  • 尝试把 Task Embedding 替换成可在线更新的原型向量,探索更贴近 Class-IL 的设置。
  • 对比 Prompt、Adapter、Hypernetwork 三者在你业务数据集上的实际差距。

这套思路最迷人的地方,是把“记忆”变成了一种可以训练的变换。真正做好它,需要你在任务表征、特征空间稳定性和参数共享之间反复权衡。建议动手先用一个小的数据集搭起训练框架,然后把全量微调、独立 Adapter、共享任务条件 Adapter 三组实验跑出来。数据不会骗人,对比结果会告诉你,这个思路是否适合你的场景。

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

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

立即咨询