下面将详细介绍 开源大模型适配器(Adapters) 微调技术的核心思想、常见应用场景以及如何通过一段可执行的代码示例来完成适配器的微调流程。文末还将结合实际应用给出如何自定义、修改和优化的建议,帮助读者在自己的项目中灵活落地。
一、适配器(Adapters)微调的基本原理
1.1 适配器(Adapters)是什么
适配器(Adapters) 是一种 参数高效微调(PEFT, Parameter-Efficient Fine-Tuning) 方法,最初由 Houlsby 等人在 2019 年提出,旨在应对大模型在不同下游任务中大规模微调带来的高昂计算和存储成本问题。适配器方法在 Transformer 层之间插入额外的 小型可训练模块(Adapter Modules),仅微调这些额外参数,而保持原有预训练模型的主干参数 冻结 不变。
核心思路:
- 在 Transformer 的 多头自注意力层 或 前馈网络层(FFN) 中插入一个或多个“瓶颈”结构的小网络。
- 新增模块的参数量相对主干模型非常小(通常在百万级别以下),适合资源受限场景。
- 训练时,仅更新适配器的参数,保留主干模型的权重,能显著减小存储和计算需求。
1.2 优势与特点
-
节省计算和存储
- 只需训练和保存少量参数,主干模型可重复使用,极大降低部署成本。
-
多任务与跨领域迁移灵活
- 在同一个主干模型里可插入多个不同任务的适配器(多 Adapter Fusion),根据需求切换或组合。
-
与其他微调方法兼容
- 适配器可与 LoRA、Prompt-Tuning 等方法结合,实现更高效或更精细的微调。
-
训练稳定性更好
- 适配器引入的小网络可减少对主干参数的干扰,降低灾难性遗忘风险。
二、适配器微调的应用场景
- 文本分类:在不同领域(如金融、医疗、法律)的文本分类任务上,使用同一个大型预训练模型(如 BERT、RoBERTa),通过插入不同的适配器来实现领域定制化。
- 序列标注(如命名实体识别):适配器可只针对特定数据集或语言进行专门训练,而不需改动主干模型。
- 机器翻译/文本生成:可在多语言或特定领域插入多组适配器,方便快速切换翻译方向或主题。
- 多任务学习:在同一模型中为不同任务(问答、情感分析、文本摘要等)插入各自的适配器,随时切换,主干参数可共享。
三、代码示例:基于 Hugging Face 的适配器微调
本示例将使用 adapter-transformers 库,它是在 Hugging Face Transformers 之上开发的适配器管理和微调工具包。下面示例以一个 文本分类 任务(如 SST-2 或自定义数据集)为例,演示如何插入适配器、进行微调并评估效果。
前置环境准备
pip install transformers adapter-transformers datasets
transformers
:Hugging Face 预训练模型库adapter-transformers
:适配器扩展库datasets
:方便加载公开数据集或自定义数据集
3.1 加载模型与数据
from transformers import AutoTokenizer
from transformers.adapters import AdapterConfig
from transformers import TrainingArguments, Trainer
from transformers import AutoModelForSequenceClassification
from datasets import load_dataset
# 1. 加载数据(以SST-2情感分类数据集为例)
dataset = load_dataset("glue", "sst2")
# 数据集分为 train / validation / test
train_dataset = dataset["train"]
val_dataset = dataset["validation"]
test_dataset = dataset["test"]
# 2. 加载预训练模型和Tokenizer
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
# 3. 数据预处理函数
def preprocess_function(examples):
return tokenizer(examples["sentence"], truncation=True, padding="max_length", max_length=128)
# 将原始文本转为模型可处理的输入格式
train_dataset = train_dataset.map(preprocess_function, batched=True)
val_dataset = val_dataset.map(preprocess_function, batched=True)
test_dataset = test_dataset.map(preprocess_function, batched=True)
# 转换为torch的格式
train_dataset.set_format("torch", columns=["input_ids", "attention_mask", "label"])
val_dataset.set_format("torch", columns=["input_ids", "attention_mask", "label"])
test_dataset.set_format("torch", columns=["input_ids", "attention_mask", "label"])
解释:
- 这里示例使用了
SST-2
数据集(情感分类),也可替换为自定义数据。 - 加载
bert-base-uncased
作为初始主干模型。 - 对文本进行分词、截断并指定最大长度为 128。
- 转换为
torch
格式,便于后续Trainer
训练。
3.2 插入适配器
# 1. 定义适配器配置
config = AdapterConfig.load(
"houlsby", # 或者 "pfeiffer", "parallel", "compacter" 等其他Adapter类型
reduction_factor=16, # 控制瓶颈层大小,越小越省参数
non_linearity="relu" # 在适配器网络中使用的激活函数
)
# 2. 为模型添加一个适配器
adapter_name = "sst2_adapter"
model.add_adapter(adapter_name, config=config)
# 3. 激活此适配器作为训练的默认头部
model.set_active_adapters(adapter_name)
# 4. 设置分类头
model.add_classification_head(
adapter_name,
num_labels=2, # SST-2是2分类
overwrite_ok=True
)
# 5. 激活这个任务头
model.set_active_adapters(adapter_name)
解释:
AdapterConfig.load("houlsby")
:加载 Houlsby 适配器结构。你也可以选择pfeiffer
或其他变体。reduction_factor
:设置瓶颈大小,越小意味着适配器更轻量。model.add_adapter(adapter_name, config=config)
:给模型插入新的适配器模块并命名为"sst2_adapter"
。model.add_classification_head()
:因为是分类任务,需要一个输出层(classification head)。model.set_active_adapters(adapter_name)
:指定当前适配器和输出头为激活状态,后续的训练和推理都将使用这个适配器。
3.3 训练设置与微调
# 定义超参数
training_args = TrainingArguments(
output_dir="./sst2_adapter_checkpoints",
evaluation_strategy="epoch", # 每个epoch进行评估
save_strategy="epoch", # 每个epoch保存
learning_rate=5e-4, # 适配器通常可用更大学习率
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
num_train_epochs=3,
logging_steps=100,
load_best_model_at_end=True,
)
# 定义评估指标(accuracy为例)
import evaluate
accuracy_metric = evaluate.load("accuracy")
def compute_metrics(eval_pred):
logits, labels = eval_pred
preds = logits.argmax(axis=-1)
return {"accuracy": accuracy_metric.compute(predictions=preds, references=labels)["accuracy"]}
# 训练器
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
eval_dataset=val_dataset,
tokenizer=tokenizer,
compute_metrics=compute_metrics
)
# 开始训练
trainer.train()
解释:
- 学习率(learning_rate=5e-4):适配器方法一般可以使用比全参数微调稍高的学习率,因为训练参数量更少。
- compute_metrics:使用
accuracy
度量测试集表现,你也可添加 F1、Precision/Recall 等。 - trainer.train():仅训练适配器和分类头的参数,而 BERT 主干权重被冻结。
3.4 测试与推理
# 在验证集或测试集上进行评估
eval_results = trainer.evaluate(eval_dataset=val_dataset)
print("Validation Results:", eval_results)
test_results = trainer.evaluate(eval_dataset=test_dataset)
print("Test Results:", test_results)
# 推理示例
sample_text = "This film is absolutely fantastic!"
inputs = tokenizer(sample_text, return_tensors="pt")
outputs = model(**inputs)
pred_class = outputs.logits.argmax(dim=-1).item()
print("Prediction (0=negative, 1=positive):", pred_class)
解释:
trainer.evaluate()
:调用评估流程,输出 accuracy 等指标。- 推理阶段也需要
model.set_active_adapters(adapter_name)
,确保使用对应的适配器来进行预测。 - 输出分类结果,如
1
代表正面情感,0
代表负面情感。
四、如何修改并在自己项目中使用
-
自定义数据集
- 如果你有自己的文本分类数据,可在
datasets.Dataset.from_dict
或load_dataset("csv", data_files=...)
方式加载,然后按照相似处理流程进行分词和格式转换即可。 - 注意修改
num_labels
、适配器的名字和分类头输出维度,以符合实际标签数量。
- 如果你有自己的文本分类数据,可在
-
更换适配器结构
- 在
AdapterConfig.load("houlsby")
中,可选择"pfeiffer"
,"houlsby"
,"parallel"
,"compacter"
等不同变体。 - 不同结构在瓶颈维度、激活函数、插入位置有所差异,你可以多做实验,对比性能与速度。
- 在
-
更换预训练模型
- 适配器方法不仅适用于 BERT,也支持 RoBERTa、GPT-2、T5 等大模型。只需替换
AutoModelForSequenceClassification
或相应的其他任务模型即可。
- 适配器方法不仅适用于 BERT,也支持 RoBERTa、GPT-2、T5 等大模型。只需替换
-
多任务、多语言微调
- 可以
model.add_adapter("taskA")
,再model.add_adapter("taskB")
,分别加载不同任务或语言的适配器,极大增强可扩展性。
- 可以
-
使用混合精度或量化
- 在
TrainingArguments
中启用fp16=True
或bf16=True
可加速训练。 - 如果模型太大,可进一步结合量化技术(如 8-bit、4-bit 量化)提升推理效率。
- 在
-
保存并加载适配器
- 适配器库支持
model.save_adapter()
和model.load_adapter()
,只保存/加载适配器参数,方便分发给其他同事或合作者使用,无需共享主干模型权重。
- 适配器库支持
五、优化与实践建议
-
选择合适的瓶颈维度(reduction_factor)
- 值越小,适配器越轻量;值越大,则有可能带来更好效果,但占用更多参数。
-
控制学习率
- 适配器层能承受 略高 的学习率(如 1e-3 到 5e-4)并加快收敛。遇到过拟合或不稳定,可以尝试 减少 学习率。
-
冻结/微调策略
- 适配器方法默认冻结主干参数,但也可以选择 逐层解冻 或保留前几层冻结,仅微调后几层和适配器,视任务数据规模和效果而定。
-
多适配器融合
- 对于多任务或多语言场景,可以在推理时 融合多个适配器 (AdapterFusion),可以获得跨任务或跨语言的综合能力。
-
监控训练过程
- 通过 TensorBoard 或其他可视化工具实时查看损失和评价指标的曲线,及时调参并早停(early stopping)以避免过拟合。
-
实际部署注意事项
- 推理时,需要加载主干模型 + 已训练好的适配器权重文件。
- 适配器可能会稍微增加推理开销(额外的前向传播分支),但相比全参数微调重新部署要 大幅节省 存储与内存。
六、总结
适配器(Adapters)作为 参数高效微调 领域的经典方法,具有 高灵活性、低计算量、易于多任务扩展 等显著优势。通过在 Hugging Face 生态中的简单配置,即可在预训练大模型里插入适配器模块,然后仅更新少量参数就能完成针对任务或领域的定制化微调。
- 若 计算资源有限 或 需要同时处理多个任务,适配器微调是一个非常值得尝试的方案。
- 结合 LoRA、P-Tuning 等其他 PEFT 手段,也能进一步提高微调效率或兼顾多种使用场景。
- 在生产部署中,通过保存和共享适配器参数文件,可大幅减少存储与分发成本,让团队协作与模型迭代更加轻松。
通过上面的代码示例和实践建议,你可以快速掌握适配器的使用流程,并根据需要在自有项目中自由配置和调整,从而在 文本分类、序列标注、机器翻译、对话生成 等多种 NLP 任务上高效地发挥开源大模型的潜力。祝你在实践中取得理想效果!
【哈佛博后带小白玩转机器学习】 哔哩哔哩_bilibili
总课时超400+,时长75+小时