趋近智
提供了使用适配器模块微调 (fine-tuning)预训练 (pre-training)Transformer模型的动手指南。它采用了 adapter-transformers 库,该库是 Hugging Face transformers 库的一个扩展,专门设计用于便于适配器和其他PEFT方法的应用。
我们的目标是通过仅训练插入模型中的轻量级适配器模块,使 bert-base-uncased 适应情感分类任务(使用 GLUE SST-2 数据集)。这种方法使原始模型绝大多数参数 (parameter)保持冻结状态,与完全微调相比,大大减少了计算和存储需求。
首先,请确保已安装必要的库。我们需要 adapter-transformers (其中包含 transformers 和 torch),以及用于数据处理的 datasets。
pip install -U adapter-transformers datasets
我们首先加载预训练 (pre-training)模型和分词 (tokenization)器 (tokenizer),就像使用标准 transformers 库一样。我们还将加载斯坦福情感树库 (SST-2) 数据集。
from transformers import AutoTokenizer
from adapter_transformers import AutoAdapterModel # 使用 AutoAdapterModel
from datasets import load_dataset
# 加载分词器和模型
model_name = "bert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoAdapterModel.from_pretrained(model_name) # 重要更改:使用 AutoAdapterModel
# 加载数据集
dataset = load_dataset("glue", "sst2")
# 预处理数据
def encode_batch(batch):
"""将句子分词。"""
return tokenizer(batch["sentence"], max_length=80, truncation=True, padding="max_length")
dataset = dataset.map(encode_batch, batched=True)
dataset = dataset.rename_column("label", "labels") # 为 Trainer 兼容性重命名标签列
dataset.set_format(type="torch", columns=["input_ids", "attention_mask", "labels"])
print("数据集样本:", dataset["train"][0])
使用 AutoAdapterModel 而非 AutoModelForSequenceClassification 很重要,因为它提供了管理适配器所需的各项方法。
现在,我们将适配器模块添加到已加载的BERT模型中。 adapter-transformers 库使此操作变得简单直接。我们将为Transformer模型的每个层添加瓶颈适配器(经典的适配器类型)。
from adapter_transformers.training import AdapterArguments
from transformers import AdapterConfig
# 配置适配器
# 使用 Pfeiffer 配置:瓶颈适配器,降维因子为16
adapter_config = AdapterConfig.load("pfeiffer", reduction_factor=16)
# 将适配器添加到模型
# 给它一个独特名称,例如 "sentiment_adapter"
adapter_name = "sentiment_adapter"
model.add_adapter(adapter_name, config=adapter_config)
# 为与此适配器关联的任务添加一个分类头
num_labels = dataset["train"].features["labels"].num_classes
model.add_classification_head(
adapter_name,
num_labels=num_labels,
id2label={ 0: "NEGATIVE", 1: "POSITIVE" } # 可选的标签映射
)
# 激活适配器进行训练
model.train_adapter(adapter_name)
# 验证哪些参数是可训练的
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"总参数数量:{total_params}")
print(f"可训练参数数量(适配器 + 头部):{trainable_params}")
print(f"可训练参数占比:%:{100 * trainable_params / total_params:.4f}%")
查看输出。您会注意到 trainable_params 仅占 total_params 的一小部分。add_adapter 方法会插入适配器模块(通常在注意力层和前馈层之后),而 add_classification_head 会为我们的特定任务添加一个新的最终层,该层也与适配器关联。重要的一点是,train_adapter(adapter_name) 会冻结整个预训练 (pre-training)BERT模型,并且只解冻属于指定适配器 (sentiment_adapter) 及其关联分类头的参数 (parameter)。这种选择性的冻结/解冻是实现参数高效训练的核心机制。
AdapterConfig.load("pfeiffer", reduction_factor=16) 定义了其架构。"pfeiffer" 指的是一种标准的瓶颈适配器配置,它带有层归一化 (normalization)和特定的激活函数 (activation function)。reduction_factor=16 表示瓶颈维度将是 ,其中 是BERT模型的隐藏维度(bert-base-uncased 为 768)。调整此因子可以直接控制参数数量和潜在性能之间的权衡。
我们使用标准的 Hugging Face Trainer 进行训练。设置过程与完全微调 (fine-tuning)类似,但 Trainer 会自动处理只有适配器参数 (parameter)可训练这一事实。
import numpy as np
from transformers import TrainingArguments, Trainer, EvalPrediction
from datasets import load_metric
# 定义训练参数
# 注意:较小的批处理大小和较少的训练轮次适合演示
training_args = TrainingArguments(
output_dir="./adapter_sst2_output",
learning_rate=1e-4, # 适配器通常受益于稍高的学习率
num_train_epochs=3,
per_device_train_batch_size=16,
per_device_eval_batch_size=16,
logging_steps=100,
evaluation_strategy="epoch",
save_strategy="epoch", # 每个训练轮次保存适配器检查点
load_best_model_at_end=True,
metric_for_best_model="accuracy",
remove_unused_columns=False, # 对适配器训练器很重要
)
# 定义评估指标
metric = load_metric("glue", "sst2")
def compute_metrics(p: EvalPrediction):
preds = np.argmax(p.predictions, axis=1)
return metric.compute(predictions=preds, references=p.label_ids)
# 实例化 Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset["train"],
eval_dataset=dataset["validation"],
tokenizer=tokenizer,
compute_metrics=compute_metrics,
)
请注意,相较于完全微调(约 2e-5),学习率 (learning_rate) 可能会稍高一些(例如 1e-4),因为适配器有时使用较大的步长能更好地收敛。在 Trainer 中使用适配器模型时,通常需要设置 remove_unused_columns=False。
现在,我们可以开始训练过程。在此阶段,梯度将仅计算并应用于适配器和分类头的权重 (weight)。
# 开始训练
train_result = trainer.train()
# 记录训练指标
metrics = train_result.metrics
trainer.log_metrics("train", metrics)
trainer.save_metrics("train", metrics)
# 评估最佳模型
eval_metrics = trainer.evaluate(eval_dataset=dataset["validation"])
trainer.log_metrics("eval", eval_metrics)
trainer.save_metrics("eval", eval_metrics)
监控训练期间和训练后打印的训练进度和评估指标(本例中为准确率)。您应该会看到模型有效地学习了情感分类任务,尽管只更新了参数 (parameter)的一小部分。
适配器微调 (fine-tuning)的一个主要优势是能够独立保存适配器权重 (weight)。基础模型保持不变。
# 定义适配器保存路径
output_adapter_dir = "./saved_adapters/sst2_adapter"
# 保存适配器权重
model.save_adapter(output_adapter_dir, adapter_name)
# 您也可以根据需要单独保存头部,或者它通常会与适配器一起保存。
# model.save_head(output_adapter_dir, adapter_name)
print(f"适配器 '{adapter_name}' 已保存至 {output_adapter_dir}")
定位到 output_adapter_dir 目录。您会发现配置文件 (adapter_config.json) 和权重文件(例如 pytorch_adapter.bin)。请注意这些文件与完整模型检查点(BERT-base 模型数百兆字节)相比是多么小。这说明了适配器在存储方面的效率。
要使用训练好的适配器,您需要加载原始基础模型,然后再加载特定的适配器权重 (weight)。
from adapter_transformers import AutoAdapterModel # 使用相同的类
from transformers import TextClassificationPipeline
# 再次加载基础模型(假设这是一个新的会话)
inference_model = AutoAdapterModel.from_pretrained(model_name)
inference_tokenizer = AutoTokenizer.from_pretrained(model_name)
# 从保存的目录加载适配器权重
loaded_adapter_name = inference_model.load_adapter(output_adapter_dir) # 返回保存时的名称
# 重要:为推理设置活跃适配器
inference_model.set_active_adapters(loaded_adapter_name)
# 如果头部未与适配器一同保存,或者您单独保存了它,则可能需要明确加载头部。
# 通常加载适配器会加载关联的头部。
# 如果出现问题,请查看 `adapter-transformers` 文档以了解头部加载的具体信息。
# 使用 pipeline 进行推理
classifier = TextClassificationPipeline(model=inference_model, tokenizer=inference_tokenizer, device=training_args.device.index if training_args.device else -1)
# 示例句子
sentences = [
"This movie was absolutely fantastic!",
"I was completely bored throughout the entire film.",
"The acting was decent, but the plot was predictable."
]
results = classifier(sentences)
for sentence, result in zip(sentences, results):
print(f"句子:{sentence}")
print(f"预测标签:{result['label']},得分:{result['score']:.4f}\n")
这说明了其模块化特性:大型基础模型可以加载一次,然后在其之上加载不同的轻量级适配器,以便在不同任务之间切换,而无需完整模型的多个副本。set_active_adapters 告知模型在推理期间的前向传播中使用哪个或哪些适配器。
本次实践练习展示了适配器微调 (fine-tuning)的核心工作流程:添加适配器模块、冻结基础模型、仅训练适配器、单独保存它们,以及加载它们以进行高效推理。您已成功为一个特定任务微调了一个强大的大型语言模型,同时只修改了其参数 (parameter)的一小部分,这体现了这种PEFT技术的效率和模块化优势。
这部分内容有帮助吗?
© 2026 ApX Machine Learning用心打造