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

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

    • [0. 前言](#0. 前言)
    • [1. 参数高效微调](#1. 参数高效微调)
    • [2. 参数高效微调方法分类](#2. 参数高效微调方法分类)
      • [2.1 加性方法](#2.1 加性方法)
      • [2.2 选择性方法](#2.2 选择性方法)
      • [2.3 低秩微调](#2.3 低秩微调)
    • [3. 实现参数高效微调](#3. 实现参数高效微调)
    • 小结
    • 系列链接

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) 首先,安装所需库:

shell 复制代码
$ pip install adapters

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

python 复制代码
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 模型:

python 复制代码
model_path= 'bert-base-uncased'
tokenizer = BertTokenizerFast.from_pretrained(model_path)

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

python 复制代码
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() 函数对数据集进行编码:

python 复制代码
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 模型,因此可以选择一个较高的学习率。定义训练参数:

python 复制代码
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 模型预训练权重:

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

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

python 复制代码
from adapters import BertAdapterModel
model = BertAdapterModel.from_pretrained(model_path)
import adapters
adapters.init(model)
model.add_adapter("imdb_sentiment")

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

python 复制代码
model.add_classification_head('imdb_sentiment', num_labels=2)

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

python 复制代码
model.train_adapter("imdb_sentiment")

(11) 查看参数效率:

python 复制代码
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%

shell 复制代码
all_params=106.42 M
trainable_params=2.01 M
The efficiency ratio is       1.89%

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

python 复制代码
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) 打印总体准确率表现:

python 复制代码
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模型性能

相关推荐
拉姆哥的小屋5 小时前
从400维向量到160000维矩阵:基于深度学习的火焰参数预测系统全解析
开发语言·人工智能·python·深度学习·线性代数·算法·矩阵
SACKings6 小时前
神经网络的层是什么?
人工智能·深度学习·神经网络
德卡先生的信箱6 小时前
深度学习图像处理(2)----一阶段目标检测
图像处理·深度学习·目标检测
祝余Eleanor6 小时前
Day 41 训练和测试的规范写法
python·深度学习·机器学习
Blossom.1186 小时前
基于图神经网络+大模型的网络安全APT检测系统:从流量日志到攻击链溯源的实战落地
人工智能·分布式·深度学习·安全·web安全·开源软件·embedding
怎么全是重名6 小时前
Stacked U-Nets: A No-Frills Approach to Natural Image Segmentation
深度学习·神经网络·计算机视觉·语义分割
心疼你的一切6 小时前
生成式AI_GAN与扩散模型详解
人工智能·深度学习·神经网络·机器学习·生成对抗网络
All The Way North-6 小时前
AdaGrad 深度解析:从数学原理到 PyTorch 实现,为什么它在稠密问题中“学不动”?
pytorch·深度学习·adagrad算法·梯度下降优化算法
中國龍在廣州6 小时前
“太空数据中心”成AI必争之地?
人工智能·深度学习·算法·机器学习·机器人
心疼你的一切7 小时前
自然语言处理_NLP与Transformer架构
人工智能·深度学习·目标检测·机器学习·计算机视觉·自然语言处理·transformer