Transformer实战(27)——参数高效微调(Parameter Efficient Fine-Tuning,PEFT)

0. 前言

微调已经成为人工智能领域中一种流行的建模范式,尤其是在迁移学习中。在之前的学习中,所有模型都是基于更新所有参数的方式进行的。因此,可以称为全微调 (Full Fine-Tuning) (也称为全模型微调或全参数微调)。在本节中,我们将介绍部分微调策略。随着大语言模型 (Large Language Model, LLM) 参数的不断增加,微调和推理的成本变得极其高昂。全参数微调需要更新所有参数,并为每个任务单独保存大模型,但这一过程在内存和运行时间方面都非常昂贵。例如 BERT3 亿个参数,T5 有高达 110 亿个参数,GPT1750 亿个参数,而 Pathways Language Model (PaLM) 则有 5400 亿个参数,因此,需要考虑参数高效微调。

1. 参数高效微调

ChatGPT 的时代,我们知道大语言模型 (Large Language Model, LLM) 能够在不需要任何额外更新或微调操作的情况下解决许多问题;那么,我们是否还需要微调操作?答案是肯定的。
我们可以使用 ChatGPTDeepSeek 等模型来高效地解决情感分析、命名实体识别 (Named Entity Recognition, NER)、摘要生成等通用问题。然而,实际应用需要非常具体的自然语言处理 (Natural Language Processing, NLP) 任务,这些任务受到文化、领域、时间和地理等因素的影响。研究表明,微调模型的表现优于类似 ChatGPT 等语言模型。因此,微调模型是更好的选择。
此外,我们还可以通过微调较小的模型,如 BERTT5,将其用于本地部署。这种方法可以减少信息安全泄露的风险。综上,为了满足我们的 NLP 需求,微调仍然是必要的。
参数高效微调 (Parameter Efficient Fine-Tuning, PEFT) 的优势可以总结如下:

  • 多任务处理:在处理多个任务时,使用 PEFT 方法至关重要。全参数微调会为每个任务生成不同的微调模型参数,这对于处理多个任务的模型来说会非常昂贵,PEFT 可以方便地在部署中切换任务
  • 快速适应新任务:参数高效微调不仅节省参数,还能快速适应新任务而不会出现灾难性遗忘。因为我们只更新与任务相关的参数,而不修改主模型的参数,所以主模型不会出现显著的遗忘现象
  • 管理复杂过程:PEFT 方法为我们提供了在时间和内存复杂度方面管理复杂过程(如超参数优化)的机会。即使使用高性能硬件,在资源密集型应用中执行全参数微调也是不可行的
  • 多任务学习的便利性:使用 PEFT 方法可以更轻松地应用多任务学习。例如,AdapterFusion 等框架利用多个源任务的知识来提高目标任务的表现。

在本节中,我们将介绍 PEFT 方法,并观察模型性能和训练效率的差异。我们还将探讨是否可以通过训练部分参数或其他高效的微调方法实现与全参数微调相同的性能。接下来,首先介绍 PEFT 方法。

2. 参数高效微调方法分类

为了普及大语言模型 (Large Language Model, LLM),研究人员已经提出了多种高效推理和微调方法。除了部分微调 (Partial Fine-Tuning, PFT) 外,还有量化(如 int8 量化)、蒸馏和稀疏化等方法。这些方法通过减少内存需求来提高推理和微调效率。PEFT 旨在尽可能减少更新参数的数量,同时保持与全训练 (Full Training) 相当的性能。尽管一些 PEFT 研究得到了比全微调更好的效果,但这些方法可能对超参数非常敏感。
目前,有多种不同的 PEFT 方法,例如:适配器 (Adapters)、提示微调 (Prompt-Tuning)、前缀微调 (Prefix-Tuning)、BitFit 和大语言模型的低秩适配 (Low-rank Adaptation of Large Language Model, LoRA) 等。这些方法可以归类为三种主要类别:

  • 加性方法 (Additive methods)
  • 选择性方法 (Selective methods)
  • 低秩微调 (Low-rank fine-tuning)

2.1 加性方法

加性方法向 Transformer 模型中添加了新的任务特定的神经网络权重(参数)。原始模型的权重保持冻结状态,并在不同任务之间共享。在微调阶段,仅训练新增的权重,同时也支持多任务学习。为了减少需要更新的参数,可以在这类模型中应用瓶颈架构 (bottleneck architecture),将原始维度投影到较低的维度,然后再映射回原始维度。由于新增的权重可以专门用于特定任务,因此这是一种易于使用的方法。adapter-transformers 是一个非常强大的库,可以用于实现这类方法。
适配器 (Adapters) 大致可以分为串行 (serial) 和并行 (parallel) 两种类型。串行适配器是以顺序方式添加的适配器层。适配器微调、前缀微调以及提示微调则是在模型的隐藏层以并行的方式向输入附加额外的词元。前缀微调在每个 Transformer 层的注意力头部添加一个可学习向量,而提示微调则仅将该向量添加到输入嵌入 (input embedding) 上。前缀微调和提示微调利用软提示 (soft prompting),这意味着添加的是可训练的连续提示,即在训练过程中,模型会学习估计这个软提示(或连续提示)的嵌入,而不是像传统的提示工程阶段那样添加离散的单词或词元。这种方法的缺点是,像前缀、插入和后缀这类附加的内容会减少模型的可用序列长度。

2.2 选择性方法

这类方法不会向语言模型添加新的参数,而是基于特定的方法选择性地更新某些参数。例如,Bias-onlyBitFit 仅训练网络中的偏置项,而将所有其他参数冻结。稀疏微调方法,如 FISH Mask,基于某些公式(如 FISH Mask 中的 Fisher 信息)选择模型中的一些参数。SparseAdapter 则将稀疏化方法应用于添加的适配器,而不是主模型。因此,SparseAdapter既是选择性方法,也是加性方法。

2.3 低秩微调

低秩结构在人工智能领域非常常见。许多任务具有一定的低秩结构,这有助于在低秩子空间中快速执行各种计算。这类 PEFT 方法中的主要代表是低秩适配 (Low-rank Adaptation of Large Language Model, LoRA),它通过训练自注意力机制中的 W q W_q Wq (查询)和 W v W_v Wv (值)矩阵的低秩分解对来进行低秩微调。所有预训练模型的参数保持冻结,只有 W q W_q Wq (查询)和 W v W_v Wv (值)两个矩阵可以通过重参数化进行训练。LoRA 将它们分解为两个低秩矩阵的乘积。
这种重参数化过程由于其选择性和低秩特性,显著减少了可训练参数的数量,同时实现了与全训练相当的性能水平。此方法旨在解决加性适配器模型的延迟问题,基于适配器的模型会引发推理延迟问题,因为它们必须按顺序处理,而 LLM 依赖于硬件并行性。由于适配器层必须在基础模型的基础上额外计算,这不可避免地引入了额外的延迟,这些串行适配器模型适用于在单个 GPU 上的模型微调。
AdaLoRALoRA 的一种变体,旨在通过两种方式增强 LoRA 方法。首先,AdaLoRA 在微调 LLM 时为矩阵分配不同的权重,并修剪冗余的奇异值。这一方法强调,在微调预训练模型时,权重矩阵在不同模块和层中具有不同的重要性。其次,LoRA 仅对查询和值投影应用奇异值分解 (Singular Value Decomposition, SVD),而 AdaLoRA 对所有权重矩阵都应用 SVD,以提高其性能。

3. 实现参数高效微调

在本节中,我们将使用参数高效微调 ( Parameter Efficient Fine-Tuning, PEFT) 来解决文本分类问题,具体来说,使用 adapter-transformers 高效微调 BERT 模型,用于 IMDb 情感数据集,并与使用全参数微调的方法进行比较。

(1) 首先,安装所需库:

$ pip install adapters

(2) 接下来,导入所需模块:

import torch, os
from torch import cuda
import numpy as np
from adapters import AdapterTrainer
from transformers import (BertTokenizerFast, 
                          BertForSequenceClassification)
from transformers import Trainer, TrainingArguments
from datasets import load_dataset

device = 'cuda' if cuda.is_available() else 'cpu'

(3) 我们将微调 BERT base-uncased 模型:

model_path= 'bert-base-uncased'
tokenizer = BertTokenizerFast.from_pretrained(model_path)

(4) 为了快速训练,从 IMDb 数据集中加载部分数据:4000 用于训练,1000 用于测试,1000 用于验证:

imdb_train= load_dataset('imdb', split="train[:2000]+train[-2000:]")
imdb_test= load_dataset('imdb', split="test[:500]+test[-500:]")
imdb_val= load_dataset('imdb', split="test[500:1000]+test[-1000:-500]")
imdb_train.shape, imdb_test.shape, imdb_val.shape

(5) 定义 tokenize_it() 函数对数据集进行编码:

def tokenize_it(e):
    return tokenizer(e['text'], 
                   padding=True, 
                   truncation=True)

enc_train=imdb_train.map(tokenize_it, batched=True, batch_size=1000)
enc_test=imdb_test.map(tokenize_it, batched=True, batch_size=1000) 
enc_val=imdb_val.map(tokenize_it, batched=True, batch_size=1000)

(6) 训练参数初始化,将学习率 (learning_rate) 设为 2e-4,低于全参数微调时所用的学习率。对于全参数微调来说,选择较高的学习率可能存在风险,因为我们会更新所有参数,这可能会导致灾难性遗忘 (catastrophic forgetting)。但本节中,由于我们只更新添加的参数,并且不会触及整个 BERT 模型,因此可以选择一个较高的学习率。定义训练参数:

training_args = TrainingArguments(
    "/tmp",
    do_train=True,
    do_eval=True,
    num_train_epochs=3,
    learning_rate=2e-4,              
    per_device_train_batch_size=16,  
    per_device_eval_batch_size=16,
    warmup_steps=100,                
    weight_decay=0.01,
    eval_strategy="epoch",
    save_strategy="epoch",
    fp16=True,
    load_best_model_at_end=True
)

compute() 函数中定义在训练过程中需要监控的指标,简单起见,我们只监控准确率 (Accuracy) 指标:

(7) 接下来,加载 bert-base-uncased 模型预训练权重:

def compute_acc(p):
    preds = np.argmax(p.predictions, axis=1)
    acc={"Accuracy": (preds == p.label_ids).mean()}
    return acc

(8) 定义适配器 imdb_sentiment,冻结模型中的所有原始参数,只允许更新添加的新参数:

from adapters import BertAdapterModel
model = BertAdapterModel.from_pretrained(model_path)
import adapters
adapters.init(model)
model.add_adapter("imdb_sentiment")

(9) 添加一个可训练的分类头,并将其与添加的适配器 imdb_sentiment 关联。对于分类头,我们还需要指定它有多少个类别:

model.add_classification_head('imdb_sentiment', num_labels=2)

(10) 训练指定适配器,因为在某些情况下,我们可能不希望训练所有新添加的参数,特别是在多任务学习阶段:

model.train_adapter("imdb_sentiment")

(11) 查看参数效率:

trainable_params=model.num_parameters(only_trainable=True)/(2**20) 
all_params=model.num_parameters() /2**20
print(f"{all_params=:.2f} M\n"+
      f"{trainable_params=:.2f} M\n"+
      f"The efficiency ratio is \
      {100*trainable_params/all_params:.2f}%")

输出结果如下所示,可以看到,参数增益约为 1.89%

all_params=106.42 M
trainable_params=2.01 M
The efficiency ratio is       1.89%

(12) 使用自定义 AdapterTrainer 类,而并未使用 Hugging Face 的标准 Trainer 类。启动训练过程:

trainer = AdapterTrainer(
    model=model,
    args=training_args,
    train_dataset=enc_train,
    eval_dataset=enc_val,
    compute_metrics=compute_acc,
)
trainer.train()

训练输出如下,可以看到,我们在 3 分 10 秒内完成了 3epoch 的微调:

输出结果

(13) 打印总体准确率表现:

import pandas as pd
q=[trainer.evaluate(eval_dataset=data) for data in [enc_train, enc_val, enc_test]]
pd.DataFrame(q, index=["train","val","test"]).iloc[:,:5]

执行结果如下所示,可以看到,测试准确率大约为 0.921

输出结果

以上结果表明,模型已经快速且成功地进行了微调。为了进行验证,我们对比标准全微调的结果。下图显示了标准全微调过程在三个 epoch 训练中的输出:

输出结果

使用标准的全微调,训练过程花费了 4 分 7 秒,而模型微调只用了 3 分 10 秒,同时准确率表现相当接近。

小结

在本节中,我们讨论了如何利用参数高效微调 (Parameter Efficient Fine-Tuning, PEFT) 使微调过程更加高效。我们介绍了三种不同的 PEFT 方法:加性、选择性和低秩方法。使用 adapter-transformersHugging FacePEFT 框架进行实践,解决了文本分类任务,在不需要训练整个语言模型的情况下,实现了相近的性能,并节省了大量时间。

系列链接

Transformer实战(1)——词嵌入技术详解
Transformer实战(2)——循环神经网络详解
Transformer实战(3)——从词袋模型到Transformer:NLP技术演进
Transformer实战(4)——从零开始构建Transformer
Transformer实战(5)——Hugging Face环境配置与应用详解
Transformer实战(6)——Transformer模型性能评估
Transformer实战(7)——datasets库核心功能解析
Transformer实战(8)——BERT模型详解与实现
Transformer实战(9)——Transformer分词算法详解
Transformer实战(10)——生成式语言模型 (Generative Language Model, GLM)
Transformer实战(11)——从零开始构建GPT模型
Transformer实战(12)——基于Transformer的文本到文本模型
Transformer实战(13)——从零开始训练GPT-2语言模型
Transformer实战(14)——微调Transformer语言模型用于文本分类
Transformer实战(15)——使用PyTorch微调Transformer语言模型
Transformer实战(16)——微调Transformer语言模型用于多类别文本分类
Transformer实战(17)——微调Transformer语言模型进行多标签文本分类
Transformer实战(18)——微调Transformer语言模型进行回归分析
Transformer实战(19)——微调Transformer语言模型进行词元分类
Transformer实战(20)——微调Transformer语言模型进行问答任务
Transformer实战(21)——文本表示(Text Representation)
Transformer实战(22)——使用FLAIR进行语义相似性评估
Transformer实战(23)——使用SBERT进行文本聚类与语义搜索
Transformer实战(24)——通过数据增强提升Transformer模型性能
Transformer实战(25)——自动超参数优化提升Transformer模型性能
Transformer实战(26)——通过领域适应提升Transformer模型性能

评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盼小辉丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值