清水泥沙

Transformer 学习之路 - BitFit 实战与深入解析

BitFit 实战与深入解析

BitFit 是一种参数高效微调(PEFT, Parameter-Efficient Fine-Tuning)方法,特别适用于大型语言模型的微调。它的核心思想是只微调模型的偏置项(bias),而冻结其他参数。这样可以减少微调所需的参数数量和存储成本,同时保持较高的性能。

BitFit 方法的基本概念

在深度学习模型中,每一层都有权重参数(weights)和偏置参数(biases)。在 BitFit 中:

  1. 只更新偏置参数:冻结模型的权重(weights),只调整偏置(biases)。
  2. 减少存储和计算需求:由于偏置项的数量远少于权重,这种方法大大降低了训练过程中的计算量和存储需求。

BitFit 的优缺点

优点

  1. 节省存储空间和计算资源:只更新偏置项,节省了大量的计算和存储成本。
  2. 迁移性强:由于权重参数不变,可以将微调后的偏置项应用到不同模型实例上,便于模型部署和版本管理。
  3. 适用于少量数据的任务:BitFit 在数据量较少的任务上表现尤为出色,能显著提高效率。
  4. 减少过拟合风险:只微调少量参数(偏置),可以避免模型在小数据集上过度拟合。

缺点

  1. 性能上限:BitFit 仅调整偏置参数,限制了模型的微调能力,因此在高复杂度或需要更深层次调整的任务中效果可能不及全面微调。
  2. 任务依赖性较高:BitFit 对某些任务(如高复杂度分类)可能不如全参数微调效果好。
  3. 局限性:BitFit 对需要大幅度知识迁移的任务(如极度不同的领域任务)效果有限,适合与预训练任务较相似的场景。

示例:使用 BitFit 微调情感分析模型

假设我们有一个预训练的 BERT 模型,并希望用少量的计算资源微调它来完成情感分类任务。以下是 BitFit 的实现步骤示例:

  1. 加载预训练模型,并将所有权重冻结。
  2. 解冻偏置项:只解冻每一层的偏置(bias),权重保持冻结。
  3. 训练模型:在情感分类数据集上训练,但只更新偏置项。

具体代码(使用 PyTorch 和 Hugging Face 的 Transformers 库)如下:

import torch
from transformers import BertTokenizer, BertForSequenceClassification

# 加载预训练模型和分词器
model = BertForSequenceClassification.from_pretrained("bert-base-uncased", num_labels=2)
tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")

# 冻结模型的所有权重
for param in model.parameters():
    param.requires_grad = False

# 解冻偏置项(仅对偏置项设置 requires_grad 为 True)
for name, param in model.named_parameters():
    if "bias" in name:
        param.requires_grad = True

# 准备数据
texts = ["I love this product!", "This is terrible..."]
labels = [1, 0]  # 假设 1 表示正面情感,0 表示负面情感
inputs = tokenizer(texts, return_tensors="pt", padding=True, truncation=True)

# 定义损失函数和优化器
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-3)

# 训练
model.train()
for epoch in range(2):
    optimizer.zero_grad()
    outputs = model(**inputs, labels=torch.tensor(labels))
    loss = criterion(outputs.logits, torch.tensor(labels))
    loss.backward()
    optimizer.step()
    print(f"Epoch {epoch+1}, Loss: {loss.item()}")

解释

  • 在这个例子中,只有偏置项的参数会被优化,所有其他参数保持不变。
  • 通过这种方式,BitFit 能在保持模型原有知识的同时,适应新的情感分类任务。

总结

BitFit 是一种有效的参数高效微调方法,适合资源有限和特定应用场景,在数据量较少的任务中表现出色。

进一步实践:BitFit 在大型语言模型中的应用

在实际应用中,BitFit 同样适用于大型语言模型的微调。以下是一个使用 BitFit 微调 BLOOM 模型的示例:

from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForCausalLM, DataCollatorForSeq2Seq, TrainingArguments, Trainer

# 加载数据集
ds = Dataset.load_from_disk("/content/drive/MyDrive/ai-learning/3-PEFT/data/alpaca_data_zh/")

# 数据集预处理
tokenizer = AutoTokenizer.from_pretrained("Langboat/bloom-1b4-zh")

def process_func(example):
    MAX_LENGTH = 256
    instruction = tokenizer("\n".join(["Human: " + example["instruction"], example["input"]]).strip() + "\n\nAssistant: ")
    response = tokenizer(example["output"] + tokenizer.eos_token)
    input_ids = instruction["input_ids"] + response["input_ids"]
    attention_mask = instruction["attention_mask"] + response["attention_mask"]
    labels = [-100] * len(instruction["input_ids"]) + response["input_ids"]
    if len(input_ids) > MAX_LENGTH:
        input_ids = input_ids[:MAX_LENGTH]
        attention_mask = attention_mask[:MAX_LENGTH]
        labels = labels[:MAX_LENGTH]
    return {
        "input_ids": input_ids,
        "attention_mask": attention_mask,
        "labels": labels
    }

tokenized_ds = ds.map(process_func, remove_columns=ds.column_names)

# 创建模型
model = AutoModelForCausalLM.from_pretrained("Langboat/bloom-1b4-zh", low_cpu_mem_usage=True)

# BitFit 配置
for name, param in model.named_parameters():
    if "bias" not in name:
        param.requires_grad = False

# 配置训练参数
args = TrainingArguments(
    output_dir="./chatbot",
    per_device_train_batch_size=1,
    gradient_accumulation_steps=8,
    logging_steps=10,
    num_train_epochs=1
)

# 创建训练器
trainer = Trainer(
    model=model,
    args=args,
    tokenizer=tokenizer,
    train_dataset=tokenized_ds,
    data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)

# 模型训练
trainer.train()

# 模型推理
ipt = "Human: {}\n{}".format("考试有哪些技巧?", "").strip() + "\n\nAssistant: "
output = model.generate(**tokenizer(ipt, return_tensors="pt").to(model.device), max_length=128, do_sample=True)
print(tokenizer.decode(output[0], skip_special_tokens=True))

解释

  • 在这个例子中,我们使用 BitFit 方法微调了一个 BLOOM 模型,只更新了模型中的偏置项。
  • 这种方法在资源有限的情况下,能够有效地微调大型语言模型,同时保持较高的性能。

结论

BitFit 是一种非常实用的参数高效微调方法,特别适合在资源有限的情况下进行模型微调。通过只更新偏置项,BitFit 能够在保持模型性能的同时,显著减少计算和存储需求。在实际应用中,BitFit 可以帮助我们更高效地完成各种自然语言处理任务。