1. 引言
近年来,预训练语言模型(PLM)如 BERT、GPT 和 T5 在自然语言处理(NLP)任务中取得了巨大成功。然而,Fine-Tuning 这些大型模型通常需要大量计算资源,并且每个新任务都需要存储一套完整的微调权重,这导致存储成本高昂。
Adapter-Tuning 作为一种高效的模型调优方法,允许我们在预训练模型的基础上,通过引入轻量级 “Adapter” 层来进行任务特定的学习。Adapter 层只占用少量参数,并且可以在多个任务之间共享,从而大幅降低计算和存储成本。
本文介绍 Adapter-Tuning 技术,并通过一个意图识别任务的代码示例展示其应用。
2. Adapter-Tuning 的原理
Adapter-Tuning 通过在 Transformer 层中插入可训练的 “Adapter” 模块,而不改变原始预训练模型的参数。这些 Adapter 模块通常由小型的前馈神经网络(如 Bottleneck 层)组成,并且可以独立于原始模型进行训练。
Adapter-Tuning 具备以下特点:
- 模型参数冻结:仅训练 Adapter 层,保持预训练模型的权重不变,减少计算资源消耗。
- 任务适配灵活:多个任务可以共享同一个预训练模型,仅需加载不同的 Adapter 层。
- 存储需求降低:每个任务只需存储 Adapter 参数,而不必复制整个预训练模型。
3. Adapter-Tuning 代码示例
以下示例展示了如何使用 Adapter-Transformers
库在 DistilBERT
上进行意图分类任务。
3.1 数据集构建
import torch
from torch.utils.data import Dataset
from transformers import AutoTokenizer
class IntentDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_length=128):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer(
text, truncation=True, padding='max_length', max_length=self.max_length, return_tensors="pt"
)
return {
'input_ids': encoding['input_ids'].squeeze(0),
'attention_mask': encoding['attention_mask'].squeeze(0),
'labels': torch.tensor(label, dtype=torch.long)
}
3.2 加载模型并配置 Adapter
from adapters import AutoAdapterModel, AdapterConfig
from transformers import AutoTokenizer
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 使用 AutoAdapterModel 加载模型
model = AutoAdapterModel.from_pretrained(model_name)
# 设置分类任务的输出头(3 个意图类别)
model.add_classification_head("intent_recognition", num_labels=3)
# 配置 Adapter
adapter_config = AdapterConfig.load("pfeiffer") # 使用 Pfeiffer 结构
model.add_adapter("intent_adapter", config=adapter_config)
# 激活 Adapter 并设置为训练模式
model.set_active_adapters("intent_adapter")
model.train_adapter("intent_adapter") # 只训练 Adapter,冻结其他参数
3.3 训练模型
from torch.optim import AdamW
from torch.utils.data import DataLoader
texts = [
"What's the weather like today?",
"Set a reminder for 3 PM.",
"Tell me a joke.",
"How's the weather tomorrow?",
"Remind me to call mom at 6 PM."
]
labels = [0, 1, 2, 0, 1]
dataset = IntentDataset(texts, labels, tokenizer)
train_dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
optimizer = AdamW(model.parameters(), lr=1e-4) # Adapter 通常需要稍高的学习率
model.train()
for epoch in range(3):
total_loss = 0
for batch in train_dataloader:
outputs = model(
input_ids=batch['input_ids'],
attention_mask=batch['attention_mask'],
labels=batch['labels']
)
loss = outputs.loss
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item()
print(f"Epoch {epoch + 1}, Average Loss: {total_loss:.4f}")
3.4 测试(推理)
model.eval()
test_texts = [
"What's the forecast for this weekend?",
"Set an alarm for 7 AM."
]
intent_map = {0: "Ask about weather", 1: "Set reminder", 2: "Tell joke"}
with torch.no_grad():
for test_text in test_texts:
encoded_input = tokenizer(test_text, return_tensors="pt")
outputs = model(**encoded_input)
predicted_label = torch.argmax(outputs.logits, dim=1).item()
print(f"Text: '{test_text}' -> Predicted Intent: {intent_map[predicted_label]}")
4. Adapter-Tuning 的应用场景
Adapter-Tuning 适用于以下 NLP 任务:
- 文本分类(情感分析、意图识别)
- 命名实体识别(NER)
- 机器翻译(多语言适配)
- 问答系统(任务特定知识嵌入)
- 跨任务迁移学习(快速适配多个任务)
5. Adapter-Tuning 的优劣势
优势
- 计算成本低:冻结主模型,仅训练 Adapter,显著减少算力需求。
- 存储需求小:每个任务只需存储 Adapter 权重,而非整个模型。
- 适用于多任务学习:可以为不同任务加载不同 Adapter,而无需训练多个独立模型。
劣势
- 适配能力有限:对于复杂任务,Adapter 可能无法完全替代 Fine-Tuning。
- 对任务敏感:不同任务可能需要不同类型的 Adapter 配置。
6. 完整代码实例
import torch
from adapters import AutoAdapterModel, AdapterConfig
from torch.optim import AdamW
from torch.utils.data import DataLoader, Dataset
from transformers import AutoTokenizer
# 定义数据集类
class IntentDataset(Dataset):
def __init__(self, texts, labels, tokenizer, max_length=128):
self.texts = texts
self.labels = labels
self.tokenizer = tokenizer
self.max_length = max_length
def __len__(self):
return len(self.texts)
def __getitem__(self, idx):
text = self.texts[idx]
label = self.labels[idx]
encoding = self.tokenizer(
text,
truncation=True,
padding='max_length',
max_length=self.max_length,
return_tensors="pt"
)
return {
'input_ids': encoding['input_ids'].squeeze(0),
'attention_mask': encoding['attention_mask'].squeeze(0),
'labels': torch.tensor(label, dtype=torch.long)
}
# 加载预训练模型和分词器
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 使用 AutoAdapterModel 加载模型
model = AutoAdapterModel.from_pretrained(model_name)
# 设置分类任务的输出头(3 个意图类别)
model.add_classification_head("intent_recognition", num_labels=3)
# 配置 Adapter
adapter_config = AdapterConfig.load("pfeiffer") # 使用 Pfeiffer 架构的 Adapter
model.add_adapter("intent_adapter", config=adapter_config)
# 激活 Adapter 并设置为训练模式
model.set_active_adapters("intent_adapter")
model.train_adapter("intent_adapter") # 只训练 Adapter,其他参数冻结
# 准备训练数据
texts = [
"What's the weather like today?",
"Set a reminder for 3 PM.",
"Tell me a joke.",
"How's the weather tomorrow?",
"Remind me to call mom at 6 PM."
]
labels = [0, 1, 2, 0, 1] # 0=询问天气, 1=设置提醒, 2=讲笑话
# 创建数据集和数据加载器
dataset = IntentDataset(texts, labels, tokenizer)
train_dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 定义优化器(只优化 Adapter 参数)
optimizer = AdamW(model.parameters(), lr=1e-4) # Adapter 通常需要稍高的学习率
# 训练循环
for epoch in range(3):
total_loss = 0
for batch in train_dataloader:
input_ids = batch['input_ids']
attention_mask = batch['attention_mask']
labels = batch['labels']
# 前向传播
outputs = model(
input_ids=input_ids,
attention_mask=attention_mask,
labels=labels
)
loss = outputs.loss
# 反向传播
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item()
avg_loss = total_loss / len(train_dataloader)
print(f"Epoch {epoch + 1}, Average Loss: {avg_loss:.4f}")
# 保存 Adapter(可选)
model.save_adapter("intent_adapter_output", "intent_adapter")
# 测试(推理)
model.eval()
test_texts = [
"What's the forecast for this weekend?",
"Set an alarm for 7 AM."
]
intent_map = {0: "Ask about weather", 1: "Set reminder", 2: "Tell joke"}
with torch.no_grad():
for test_text in test_texts:
encoded_input = tokenizer(test_text, return_tensors="pt")
outputs = model(**encoded_input)
logits = outputs.logits
predicted_label = torch.argmax(logits, dim=1).item()
predicted_intent = intent_map[predicted_label]
print(f"Text: '{test_text}' -> Predicted Intent: {predicted_intent}")
7. 结论
Adapter-Tuning 是一种高效、低成本的 NLP 任务微调方法,适用于多任务学习和跨任务迁移。相比 Fine-Tuning,Adapter-Tuning 计算资源消耗更低,并且存储占用小,是预训练模型调优的一个理想选择。希望本文的示例能帮助你理解 Adapter-Tuning,并在实际应用中灵活使用这一技术!