跳转至

LoRA 方法

译者:糖醋鱼

项目地址:https://huggingface.apachecn.org/docs/peft/task_guides/lora_based_methods

原始地址:https://huggingface.co/docs/peft/task_guides/lora_based_methods

LoRA 方法

一种流行的高效训练大型模型的方法是(通常在注意力块中)插入较小的可训练矩阵,这些矩阵是对微调期间要学习的权重增量矩阵的低秩分解。预训练模型原有的权重矩阵被冻结,仅更新这些较小的矩阵。这样做减少了可训练参数的数量,从而降低了内存使用量和训练时间,这对于大型模型而言可以大幅节省成本。

有多种方法可以将权重矩阵表示为低秩分解,但低秩适应(Low-Rank Adaptation,LoRA)是最常见的方法。PEFT库支持几种其他的LoRA变体,例如低秩Hadamard积(Low-Rank Hadamard Product,LoHa)低秩Kronecker积(Low-Rank Kronecker Product,LoKr)自适应低秩适应(Adaptive Low-Rank Adaptation,AdaLoRA)。您可以在适配器指南中了解这些方法的概念。如果您有兴趣将这些方法应用于其他任务和用例,如语义分割、标记分类,请查看我们的笔记本集合

本指南将向您展示如何运用低秩分解方法,快速训练一个图像分类模型,以识别图像中所示食物的类别。

对训练图像分类模型的一般流程有所了解将非常有帮助,这样你可以更专注于低秩分解方法。如果你是新手,我们建议你先从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)
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模型,但你可以选择任何你想要的图像分类模型。将 label2idid2label 字典传递给模型,以便它知道如何将整数标签映射到它们对应的类别标签。如果你正在微调一个已经被微调过的checkpoint,可以选择性地传递 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 配置 and 模型

每种PEFT方法都需要一个配置,用于保存指定PEFT方法应用方式的所有参数。一旦配置设置完毕,将其与基模型一起传递给 get_peft_model() 函数,以创建一个可训练的PeftModel

调用 print_trainable_parameters() 方法来对比PeftModel与基模型中的参数数量

LoRA

LoRA将权重更新矩阵分解为两个更小的矩阵。这些低秩矩阵的大小由其秩或r决定。更高的秩意味着模型有更多的参数需要训练,但也意味着模型具有更强的学习能力。你还需要指定target_modules,这决定了较小的矩阵插入的位置。在这个指南中,我们将目标定位在注意力模块的 queryvalue 矩阵上。其他需要设定的重要参数包括 lora_alpha(缩放因子)、bias(是否训练无偏置、所有偏置或仅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"

LoHa

LoHa将权重更新矩阵分解为四个更小的矩阵,每对较小的矩阵通过Hadamard乘积结合。这使得与LoRA相比,权重更新矩阵在保持相同数量的可训练参数的同时,具有更高的秩(LoHA为r^2,而LoRA为2*r)。这些较小矩阵的大小由其秩或r决定。同样,你需要指定 target_modules ,以确定这些较小矩阵的插入位置。在这个指南中,我们将目标定位在注意力模块的 queryvalue 矩阵上。其他重要的参数包括alpha(缩放因子)以及modules_to_save(除LoHa层之外,需要训练和保存的模块)。所有这些参数,以及其他更多选项,都可以在LoHaConfig中找到。

from peft import LoHaConfig, get_peft_model

config = LoHaConfig(
    r=16,
    alpha=16,
    target_modules=["query", "value"],
    module_dropout=0.1,
    modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 1,257,317 || all params: 87,133,642 || trainable%: 1.4429753779831676"

LoKr

LoKr将权重更新矩阵表示为Kronecker乘积的分解,形成一个能够保持原权重矩阵秩的块矩阵。这些较小矩阵的大小由其秩或r决定。同样,你需要指定 target_modules ,以确定这些较小矩阵的插入位置。在这个指南中,我们将目标定位在注意力模块的 queryvalue 矩阵上。其他重要的参数包括alpha(缩放因子),以及 modules_to_save(除LoKr层之外,需要训练和保存的模块)。所有这些参数,以及其他更多选项,都可以在 LoKrConfig 中找到。

from peft import LoKrConfig, get_peft_model

config = LoKrConfig(
    r=16,
    alpha=16,
    target_modules=["query", "value"],
    module_dropout=0.1,
    modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 116,069 || all params: 87,172,042 || trainable%: 0.13314934162033282"

AdaLoRA

AdaLoRA 通过为重要的权重矩阵分配更多参数并剪枝较不重要的矩阵,从而有效地管理LoRA参数预算。相比之下,LoRA在所有模块中均匀分配参数。你可以控制矩阵的平均期望秩或r,以及通过 target_modules 参数来指定将AdaLoRA应用于哪些模块。其他需要设置的重要参数包括lora_alpha(缩放因子),以及 modules_to_save(除AdaLoRA层之外,需要训练和保存的模块)。所有这些参数,以及其他更多选项,都可以在 AdaLoraConfig 中找到。

from peft import AdaLoraConfig, get_peft_model

config = AdaLoraConfig(
    r=8,
    init_r=12,
    tinit=200,
    tfinal=1000,
    deltaT=10,
    target_modules=["query", "value"],
    modules_to_save=["classifier"],
)
model = get_peft_model(model, config)
model.print_trainable_parameters()
"trainable params: 520,325 || all params: 87,614,722 || trainable%: 0.5938785036606062"

训练

对于训练,我们使用来自Transformers库的 Trainer 类。Trainer类包含了PyTorch的训练循环,当一切准备就绪,只需调用 train 方法即可开始训练。为了定制训练过程,你可以在 TrainingArguments 类中配置训练超参数。使用类似LoRA的方法,你可以负担得起使用更高的批次大小和学习率。

AdaLoRA有一个update_and_allocate()方法,需要在每个训练步骤中调用,以更新参数预算和掩码,否则不会执行适应步骤。这需要编写自定义训练循环或继承Trainer类来整合这个方法。作为一个例子,可以参考这个自定义训练循环 custom training loop

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,
    evaluation_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方法将你的模型上传到模型中心。首先,你需要登录到你的Hugging Face账户,并在提示时输入你的访问令牌。

from huggingface_hub import notebook_login

notebook_login()

调用 push_to_hub 方法将你的模型保存到你的仓库中。

model.push_to_hub(peft_model_id)

推理

让我们从模型中心加载模型,并在一个食物图像上测试它的效果。

from peft import PeftConfig, PeftModel
from transfomers 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"

我们一直在努力

apachecn/AiLearning

【布客】中文翻译组