Bootstrap

Adapter-Tuning:高效适配预训练模型的新任务

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 具备以下特点:

  1. 模型参数冻结:仅训练 Adapter 层,保持预训练模型的权重不变,减少计算资源消耗。
  2. 任务适配灵活:多个任务可以共享同一个预训练模型,仅需加载不同的 Adapter 层。
  3. 存储需求降低:每个任务只需存储 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 的优劣势

优势

  1. 计算成本低:冻结主模型,仅训练 Adapter,显著减少算力需求。
  2. 存储需求小:每个任务只需存储 Adapter 权重,而非整个模型。
  3. 适用于多任务学习:可以为不同任务加载不同 Adapter,而无需训练多个独立模型。

劣势

  1. 适配能力有限:对于复杂任务,Adapter 可能无法完全替代 Fine-Tuning。
  2. 对任务敏感:不同任务可能需要不同类型的 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,并在实际应用中灵活使用这一技术!

;