LoRA 方法
高效训练大型模型的一种流行方法是在注意力块(通常在注意力块中)插入更小的可训练矩阵,这些矩阵是微调期间要学习的增量权重矩阵的低秩分解。预训练模型的原始权重矩阵被冻结,并且仅在训练期间更新较小的矩阵。这减少了可训练参数的数量,从而减少了内存使用量和训练时间,对于大型模型而言,这些时间和内存使用量可能非常昂贵。
有多种方法可以将权重矩阵表示为低秩分解,但 低秩自适应(LoRA) 是最常见的方法。PEFT 库支持几种其他 LoRA 变体,例如 低秩哈达玛积(LoHa)、低秩克罗内克积(LoKr) 和 自适应低秩自适应(AdaLoRA)。你可以在 适配器 指南中了解更多关于这些方法在概念上的工作原理。如果你有兴趣将这些方法应用于语义分割、标记分类等其他任务和用例,请查看我们的 笔记本集合!
此外,PEFT 支持 X-LoRA LoRA 专家混合方法。
本指南将向您展示如何使用低秩分解方法快速训练一个图像分类模型,以识别图像中显示的食物类别。
对图像分类模型训练的一般过程有一定的了解将非常有用,可以帮助您专注于低秩分解方法。如果您是新手,建议您先阅读 Transformers 文档中的 图像分类 指南。准备好了之后,回来看看将 PEFT 融入您的训练有多容易吧!
在开始之前,请确保您已安装所有必要的库。
pip install -q peft transformers datasets
数据集
在本指南中,您将使用 Food-101 数据集,其中包含 101 类食物的图像(查看 数据集查看器 以更好地了解数据集的样子)。
使用 load_dataset 函数加载数据集。
from datasets import load_dataset
ds = load_dataset("food101")
每个食物类别都用一个整数标记,为了便于理解这些整数的含义,您将创建一个 label2id
和 id2label
字典,将整数映射到其类别标签。
labels = ds["train"].features["label"].names
label2id, id2label = dict(), dict()
for i, label in enumerate(labels):
label2id[label] = i
id2label[i] = label
id2label[2]
"baklava"
加载一个图像处理器,以正确调整训练和评估图像的像素值大小并对其进行归一化。
from transformers import AutoImageProcessor
image_processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224-in21k")
您还可以使用图像处理器准备一些用于数据增强和像素缩放的转换函数。
from torchvision.transforms import (
CenterCrop,
Compose,
Normalize,
RandomHorizontalFlip,
RandomResizedCrop,
Resize,
ToTensor,
)
normalize = Normalize(mean=image_processor.image_mean, std=image_processor.image_std)
train_transforms = Compose(
[
RandomResizedCrop(image_processor.size["height"]),
RandomHorizontalFlip(),
ToTensor(),
normalize,
]
)
val_transforms = Compose(
[
Resize(image_processor.size["height"]),
CenterCrop(image_processor.size["height"]),
ToTensor(),
normalize,
]
)
def preprocess_train(example_batch):
example_batch["pixel_values"] = [train_transforms(image.convert("RGB")) for image in example_batch["image"]]
return example_batch
def preprocess_val(example_batch):
example_batch["pixel_values"] = [val_transforms(image.convert("RGB")) for image in example_batch["image"]]
return example_batch
定义训练和验证数据集,并使用 set_transform 函数对数据进行实时转换。
train_ds = ds["train"]
val_ds = ds["validation"]
train_ds.set_transform(preprocess_train)
val_ds.set_transform(preprocess_val)
最后,您需要一个数据整理器来创建一批训练和评估数据,并将标签转换为 torch.tensor
对象。
import torch
def collate_fn(examples):
pixel_values = torch.stack([example["pixel_values"] for example in examples])
labels = torch.tensor([example["label"] for example in examples])
return {"pixel_values": pixel_values, "labels": labels}
模型
现在让我们加载一个预训练的模型用作基础模型。本指南使用 google/vit-base-patch16-224-in21k 模型,但您可以使用任何您想要的图像分类模型。将 label2id
和 id2label
字典传递给模型,以便它知道如何将整数标签映射到其类别标签,您还可以选择传递 ignore_mismatched_sizes=True
参数,如果您正在微调已经微调过的检查点。
from transformers import AutoModelForImageClassification, TrainingArguments, Trainer
model = AutoModelForImageClassification.from_pretrained(
"google/vit-base-patch16-224-in21k",
label2id=label2id,
id2label=id2label,
ignore_mismatched_sizes=True,
)
PEFT 配置和模型
每种 PEFT 方法都需要一个配置,其中包含指定应如何应用 PEFT 方法的所有参数。配置设置好后,将其与基础模型一起传递给 get_peft_model() 函数,以创建一个可训练的 PeftModel。
调用 print_trainable_parameters() 方法,比较 PeftModel 的参数数量与基础模型中的参数数量!
LoRA 将权重更新矩阵分解为两个较小的矩阵。这些低秩矩阵的大小由其秩或 r
决定。秩越高,模型需要训练的参数就越多,但也意味着模型具有更大的学习能力。您还需要指定 target_modules
,它决定了较小矩阵的插入位置。在本指南中,您将针对注意力块的查询和值矩阵。其他需要设置的重要参数包括 lora_alpha
(缩放因子)、bias
(是否训练 none
、all
或仅 LoRA 偏差参数)和 modules_to_save
(除 LoRA 层之外需要训练和保存的模块)。所有这些参数(以及更多)都可以在 LoraConfig 中找到。
from peft import LoraConfig, get_peft_model
config = LoraConfig(
r=16,
lora_alpha=16,
target_modules=["query", "value"],
lora_dropout=0.1,
bias="none",
modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 667,493 || all params: 86,543,818 || trainable%: 0.7712775047664294"
训练
对于训练,让我们使用 Transformers 中的 Trainer 类。Trainer
包含一个 PyTorch 训练循环,准备好了之后,调用 train 开始训练。要自定义训练运行,请在 TrainingArguments 类中配置训练超参数。对于 LoRA 类方法,您可以承受使用更高的批次大小和学习率。
AdaLoRA 有一个 update_and_allocate() 方法,应该在每个训练步骤中调用,以更新参数预算和掩码,否则不会执行自适应步骤。这需要编写一个自定义训练循环或对 Trainer 进行子类化,以包含此方法。例如,请查看这个 自定义训练循环。
from transformers import TrainingArguments, Trainer
account = "stevhliu"
peft_model_id = f"{account}/google/vit-base-patch16-224-in21k-lora"
batch_size = 128
args = TrainingArguments(
peft_model_id,
remove_unused_columns=False,
eval_strategy="epoch",
save_strategy="epoch",
learning_rate=5e-3,
per_device_train_batch_size=batch_size,
gradient_accumulation_steps=4,
per_device_eval_batch_size=batch_size,
fp16=True,
num_train_epochs=5,
logging_steps=10,
load_best_model_at_end=True,
label_names=["labels"],
)
使用 train 开始训练。
trainer = Trainer( model, args, train_dataset=train_ds, eval_dataset=val_ds, tokenizer=image_processor, data_collator=collate_fn, ) trainer.train()
分享您的模型
训练完成后,您可以使用 push_to_hub 方法将模型上传到 Hub。您需要先登录您的 Hugging Face 帐户,并在提示时输入您的令牌。
from huggingface_hub import notebook_login
notebook_login()
调用 push_to_hub 将您的模型保存到您的存储库。
model.push_to_hub(peft_model_id)
推理
让我们从 Hub 加载模型,并在食物图像上对其进行测试。
from peft import PeftConfig, PeftModel
from transformers import AutoImageProcessor
from PIL import Image
import requests
config = PeftConfig.from_pretrained("stevhliu/vit-base-patch16-224-in21k-lora")
model = AutoModelForImageClassification.from_pretrained(
config.base_model_name_or_path,
label2id=label2id,
id2label=id2label,
ignore_mismatched_sizes=True,
)
model = PeftModel.from_pretrained(model, "stevhliu/vit-base-patch16-224-in21k-lora")
url = "https://huggingface.co/datasets/sayakpaul/sample-datasets/resolve/main/beignets.jpeg"
image = Image.open(requests.get(url, stream=True).raw)
image
将图像转换为 RGB 并返回底层的 PyTorch 张量。
encoding = image_processor(image.convert("RGB"), return_tensors="pt")
现在运行模型并返回预测的类别!
with torch.no_grad():
outputs = model(**encoding)
logits = outputs.logits
predicted_class_idx = logits.argmax(-1).item()
print("Predicted class:", model.config.id2label[predicted_class_idx])
"Predicted class: beignets"