适配器微调(Adapter Tuning)
适配器微调(Adapter Tuning)是一种参数高效微调(Parameter-Efficient Fine-Tuning, PEFT) 方法,用于在不修改预训练模型全部参数的情况下,将模型适配到特定任务或数据集。它的核心思想是在预训练模型的结构中插入小型的、可训练的神经网络模块(称为适配器,Adapters),只更新这些适配器的参数,而冻结原始模型的参数,从而以较低的计算和存储成本实现高效微调。
以下是对适配器微调的详细解释:
1. 适配器微调的定义与原理
适配器微调通过在预训练模型(如 Transformer 模型)的每一层中插入轻量级的适配器模块来实现任务适配。这些适配器模块通常是小型的全连接神经网络,参数量远小于原始模型。训练时,只优化适配器的参数,而预训练模型的权重保持不变。这种方法特别适合资源受限的场景或需要快速适配多个任务的情况。
工作原理:
- 插入适配器:在 Transformer 模型的每一层(通常在注意力模块或前馈网络(FFN)之后)插入适配器模块。
- 适配器结构:适配器通常采用瓶颈结构(bottleneck architecture),包括:
- 一个降维层(down-projection),将输入维度压缩到较低维度。
- 一个非线性激活函数(如 ReLU)。
- 一个升维层(up-projection),将维度恢复到原始大小。
- 可选的残差连接(residual connection),将适配器的输出与输入相加。
- 训练:冻结预训练模型的原始参数,仅训练适配器的参数。
- 推理:适配器模块与原始模型一起运行,处理输入数据,但由于适配器参数量少,额外计算开销有限。
数学表示:
假设输入为
x
x
x,适配器的计算过程可以表示为:
h
=
x
+
Up
(
σ
(
Down
(
x
)
)
)
h = x + \text{Up}(\sigma(\text{Down}(x)))
h=x+Up(σ(Down(x)))
其中:
- Down ( ⋅ ) \text{Down}(\cdot) Down(⋅) 是降维层(线性变换)。
- σ ( ⋅ ) \sigma(\cdot) σ(⋅) 是非线性激活函数(如 ReLU)。
- Up ( ⋅ ) \text{Up}(\cdot) Up(⋅) 是升维层(线性变换)。
- x + ⋅ x + \cdot x+⋅ 表示残差连接。
2. 适配器微调的优点
-
参数效率高:
- 适配器模块的参数量通常只占预训练模型总参数的 0.5%-10%,显著降低了存储和计算需求。
- 例如,对于一个 7B 参数的模型,适配器可能只增加几百万个参数。
-
模块化设计:
- 每个任务可以训练一个独立的适配器,保存为小文件(几 MB 到几十 MB),便于存储和切换。
- 适配器可以动态插入或移除,适合多任务场景。
-
性能接近全参数微调:
- 适配器微调在许多任务上的性能可以媲美全参数微调,尤其是在自然语言处理(NLP)和计算机视觉任务中。
-
保留预训练知识:
- 冻结原始模型参数有助于保留预训练模型的泛化能力,减少过拟合风险。
-
灵活性:
- 适配器可以插入到模型的不同位置(注意力层、FFN 层等),并支持多种结构设计。
3. 适配器微调的缺点
-
推理延迟:
- 适配器模块会引入额外的计算层,导致推理时延迟略有增加(通常较小,但对延迟敏感的场景需注意)。
-
超参数调优:
- 适配器的设计(如瓶颈维度、插入位置)需要调优,可能增加实验复杂性。
-
任务适配性:
- 对于某些复杂任务或与预训练数据差异较大的任务,适配器微调可能需要更大的适配器规模才能达到理想性能。
4. 适配器微调的代表性实现
以下是几种常见的适配器设计和相关工作:
-
Houlsby Adapter(Houlsby et al., 2019):
- 在 Transformer 的每一层插入两个适配器:一个在多头注意力(Multi-Head Attention)之后,一个在前馈网络(FFN)之后。
- 使用瓶颈结构,瓶颈维度通常为原始维度的 1/8 或更小。
- 广泛应用于 NLP 任务,如文本分类、机器翻译等。
-
Pfeiffer Adapter(Pfeiffer et al., 2020):
- 简化设计,仅在 FFN 之后插入一个适配器。
- 参数效率更高,推理开销更小。
- 适合资源受限场景,同时保持较好的性能.
-
AdapterHub:
- 一个开源框架,支持在预训练 Transformer 模型(如 BERT、RoBERTa)中插入和训练适配器。
- 提供多种适配器配置,方便用户实验和部署。
-
Compacter:
- 一种改进的适配器方法,使用低秩分解和参数共享技术进一步减少参数量。
- 适合超大规模模型的微调。
5. 适配器微调的应用场景
适配器微调广泛应用于以下场景:
- 多任务学习:为不同任务训练独立的适配器,共享同一预训练模型。
- 跨语言迁移:将预训练模型适配到低资源语言的任务。
- 领域适配:将通用模型适配到特定领域(如医疗、法律)。
- 资源受限环境:在边缘设备或低内存设备上部署模型。
- 快速原型开发:快速测试模型在特定任务上的表现,而无需全参数微调。
6. 适配器微调的代码示例
以下是一个使用 Hugging Face 的 transformers
库和 adapters
库在 BERT 模型上为文本分类任务实现适配器微调的示例。示例基于 GLUE 数据集的 SST-2(情感分类)任务。
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from adapters import AutoAdapterModel, AdapterTrainer
from transformers import TrainingArguments
from datasets import load_dataset
# 1. 加载预训练模型和分词器
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoAdapterModel.from_pretrained(model_name)
# 2. 添加适配器
model.add_adapter("sst2_adapter")
# 为分类任务添加分类头(2 分类:正向/负向)
model.add_classification_head("sst2_adapter", num_labels=2)
# 激活适配器
model.set_active_adapters("sst2_adapter")
model.train_adapter("sst2_adapter") # 冻结原始模型参数,启用适配器训练
# 3. 加载数据集并预处理
dataset = load_dataset("glue", "sst2")
def preprocess_function(examples):
return tokenizer(examples["sentence"], padding="max_length", truncation=True, max_length=128)
encoded_dataset = dataset.map(preprocess_function, batched=True)
train_dataset = encoded_dataset["train"]
eval_dataset = encoded_dataset["validation"]
# 4. 设置训练参数
training_args = TrainingArguments(
output_dir="./adapter_output",
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
evaluation_strategy="epoch",
save_strategy="epoch",
load_best_model_at_end=True,
)
# 5. 初始化适配器训练器
trainer = AdapterTrainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=eval_dataset,
compute_metrics=lambda eval_pred: {"accuracy": (eval_pred.predictions.argmax(1) == eval_pred.label_ids).mean()},
)
# 6. 训练适配器
trainer.train()
# 7. 保存适配器
model.save_adapter("./sst2_adapter", "sst2_adapter")
# 8. 推理示例
text = "This movie is fantastic!"
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=128)
outputs = model(**inputs)
logits = outputs.logits
prediction = logits.argmax(-1).item()
print(f"Prediction: {'Positive' if prediction == 1 else 'Negative'}")
代码说明:
- 模型和适配器:使用
bert-base-uncased
作为预训练模型,添加适配器。 - 数据集:使用 GLUE 的 SST-2 数据集(二分类情感分析)。
- 训练:仅训练适配器参数和分类头,冻结 BERT 的原始参数。
- 保存和推理:训练完成后,适配器权重保存为小文件(几 MB),可用于推理。
- 依赖:需要安装
transformers
,adapters
, 和datasets
库(pip install transformers adapters datasets
)。
运行结果:
- 适配器微调通常只需 1-2 GB 的 GPU 内存(远低于全参数微调的 10+ GB)。
- 训练后的适配器在 SST-2 数据集上可达到 ~90% 的准确率,接近全参数微调。
7. 与其他 PEFT 方法的对比
方法 | 参数效率 | 推理延迟 | 性能(相对全参数微调) | 模块化设计 |
---|---|---|---|---|
Adapter Tuning | 高 | 轻微增加 | 接近 | 强 |
LoRA | 极高 | 无增加 | 接近 | 强 |
Prompt Tuning | 极高 | 无增加 | 稍逊 | 弱 |
Prefix Tuning | 极高 | 无增加 | 稍逊 | 弱 |
- 与 LoRA 相比:适配器微调引入额外计算层(推理延迟略高),但模块化设计更灵活,适合多任务场景。LoRA 更适合单一任务的高效适配。
- 与 Prompt Tuning 相比:适配器微调通常性能更稳定,适合复杂任务,但参数量略高。
- 与全参数微调相比:适配器微调显著降低计算和存储成本,但在某些极端任务上可能略逊。
8. 总结
适配器微调是一种高效、灵活的微调方法,通过在预训练模型中插入小型适配器模块实现任务适配。它在参数效率、模块化设计和性能之间取得了良好平衡,特别适合多任务学习、跨语言迁移和资源受限场景。如果你有具体的任务(如适配 BERT 进行其他任务)或想深入探讨实现细节(如调整适配器配置或优化超参数),请告诉我,我可以进一步提供帮助!
9. 学习网站
AdapterHub:https://adapterhub.ml
AdapterHub Documentation:https://docs.adapterhub.ml/index.html
Using Adapters at Hugging Face:https://huggingface.co/docs/hub/adapters