模型微调方法LoRA
简介:个人学习分享,如有错误,欢迎批评指正。
在近年来,随着大型语言模型(如GPT-3、GPT-4等)的迅速发展,如何高效、经济地微调这些模型以适应特定任务成为了一个重要的研究方向。Low-Rank Adaptation(低秩适配,简称LoRA)是一种创新的方法,旨在通过低秩矩阵分解技术高效地微调大型语言模型。
一、背景与动机
1. 大型语言模型的发展
大型语言模型凭借其在自然语言处理任务中的卓越表现,广泛应用于文本生成、翻译、问答系统等领域。然而,这些模型通常拥有数十亿甚至上千亿的参数,导致以下问题:
- 高昂的计算成本:训练和微调这些模型需要大量的计算资源,导致计算成本高。
- 存储需求巨大:每个微调任务需要存储一份完整的模型参数,导致存储成本高昂。
- 微调效率低:针对不同任务分别微调模型,缺乏共享机制,降低了效率。
2. 微调方法的发展
为了解决上述问题,研究者提出了多种微调方法,包括:
- 全参数微调(Full Fine-Tuning):调整模型的所有参数,适应性强但成本高。
- 冻结部分参数:只微调部分参数,降低计算和存储成本,但可能限制模型的适应能力。
- 参数高效微调(Parameter-Efficient Fine-Tuning, PEFT):通过引入少量可训练参数,实现高效微调。LoRA 即为其中一种典型方法。
二、LoRA的具体实现步骤
1. 确定适配层
在大型语言模型(如 Transformer 架构)中,LoRA 通常应用于
关键的权重矩阵,如自注意力机制中的查询(Query)、键(Key)、值(Value)矩阵
以及前馈网络
中的权重矩阵。这些权重矩阵通常是大型的线性变换矩阵,适合进行低秩分解
。
2. 插入低秩适配模块
对于每个需要适配的权重矩阵 W W W,LoRA 通过插入两个新的可训练矩阵 A A A 和 B B B 来表示权重的更新:
W ′ = W + Δ W = W + A ⋅ B W' = W + \Delta W = W + A \cdot B W′=W+ΔW=W+A⋅B
其中,原始权重 W W W 保持冻结,不参与训练。只有 A A A 和 B B B 参与训练,从而实现参数高效的微调。
3. 参数初始化
- 矩阵
A
A
A 的初始化:通常采用随机初始化,使用
小的随机值
(如从标准正态分布中采样并乘以一个缩放因子),以确保初始的 Δ W \Delta W ΔW 对模型输出的影响较小。 - 矩阵
B
B
B 的初始化:通常初始化为
零矩阵
,这样初始的 Δ W = A ⋅ B = 0 \Delta W = A \cdot B = 0 ΔW=A⋅B=0,确保初始模型行为与原始模型一致
。
4. 训练过程
在微调过程中,只有矩阵 A A A 和 B B B 是可训练的参数。训练目标仍然是最小化任务特定的损失函数,但优化器只更新 A A A 和 B B B,保持 W W W 不变。
训练步骤如下:
- 前向传播:计算 W ′ = W + A ⋅ B W' = W + A \cdot B W′=W+A⋅B,并使用 W ′ W' W′ 进行前向传播。
- 损失计算:根据任务目标计算损失。
- 反向传播:仅计算 A A A 和 B B B 的梯度。
- 参数更新:使用优化器更新 A A A 和 B B B。
5. 推理阶段
在推理阶段,使用微调后的权重 W ′ = W + A ⋅ B W' = W + A \cdot B W′=W+A⋅B 进行计算。由于 W W W 是冻结的,只需要存储 A A A 和 B B B 即可,不需要额外的计算开销。
三、LoRA在Transformer中的集成
Transformer 架构是目前大型语言模型的主流架构,LoRA 主要针对 Transformer 中的线性层进行适配。以下以 Transformer 的自注意力机制为例,说明 LoRA 的集成方式。
1. 自注意力机制中的权重矩阵
在自注意力机制中,有三个主要的权重矩阵:查询矩阵 W Q W_Q WQ、键矩阵 W K W_K WK 和值矩阵 W V W_V WV。这些矩阵用于计算注意力得分和加权输出。
2. 应用 LoRA
对于每个权重矩阵 W W W(即 W Q , W K , W V W_Q, W_K, W_V WQ,WK,WV),LoRA 引入两个低秩矩阵 A A A 和 B B B,并将其更新添加到原始权重上:
W q ′ = W q + A q ⋅ B q W'_q = W_q + A_q \cdot B_q Wq′=Wq+Aq⋅Bq
W k ′ = W k + A k ⋅ B k W'_k = W_k + A_k \cdot B_k Wk′=Wk+Ak⋅Bk
W v ′ = W v + A v ⋅ B v W'_v = W_v + A_v \cdot B_v Wv′=Wv+Av⋅Bv
其中, A q , B q , A k , B k , A v , B v A_q, B_q, A_k, B_k, A_v, B_v Aq,Bq,Ak,Bk,Av,Bv 分别对应于查询、键和值的适配矩阵。
3. 前馈网络中的适配
同样地,Transformer 的前馈网络中的权重矩阵(如 W 1 W_1 W1 和 W 2 W_2 W2)也可以应用 LoRA 进行适配:
W 1 ′ = W 1 + A W 1 ⋅ B W 1 W'_1 = W_1 + A_{W1} \cdot B_{W1} W1′=W1+AW1⋅BW1
W 2 ′ = W 2 + A W 2 ⋅ B W 2 W'_2 = W_2 + A_{W2} \cdot B_{W2} W2′=W2+AW2⋅BW2
通过这种方式,整个 Transformer 层的关键权重矩阵都得到了高效的适配。
四、LoRA的数学基础详解
1. 矩阵的秩与低秩分解
1.1 矩阵的秩(Rank of a Matrix)
定义:一个矩阵的秩
是其行或列中线性无关向量的最大数量
。换句话说,秩衡量了矩阵的“信息量”或“复杂性”。
- 形式定义:对于一个
m
×
n
m \times n
m×n 的矩阵
W
W
W,其秩
rank
(
W
)
\text{rank}(W)
rank(W) 是
W
W
W 的
所有行或列的最大线性无关集的大小
。 - 性质:
- 0 ≤ rank ( W ) ≤ min ( m , n ) 0 \leq \text{rank}(W) \leq \min(m, n) 0≤rank(W)≤min(m,n)。
- 一个矩阵的
秩等于其非零奇异值的数量
(由奇异值分解决定)。
1.2 奇异值分解(Singular Value Decomposition, SVD)
定义:奇异值分解是将任意 m × n m \times n m×n 矩阵 W W W 分解为三个矩阵的乘积:
W = U Σ V T W = U \Sigma V^T W=UΣVT
其中:
- U U U 是一个 m × m m \times m m×m 的正交矩阵。
- Σ \Sigma Σ 是一个 m × n m \times n m×n 的对角矩阵,对角线上是 W W W 的奇异值(非负实数),按降序排列。
- V V V 是一个 n × n n \times n n×n 的正交矩阵。
应用:SVD 可以用于低秩近似,通过保留最大的 r r r 个奇异值及其对应的奇异向量,得到一个近似矩阵:
W ≈ U r Σ r V r T W \approx U_r \Sigma_r V_r^T W≈UrΣrVrT
其中 U r U_r Ur 和 V r V_r Vr 分别是 U U U 和 V V V 的前 r r r 列, Σ r \Sigma_r Σr 是保留前 r r r 个奇异值的对角矩阵。
1.3 低秩矩阵
定义:一个矩阵 W W W 的秩远小于其维度时,称其为低秩矩阵。即对于 W ∈ R m × n W \in \mathbb{R}^{m \times n} W∈Rm×n,如果 rank ( W ) = r \text{rank}(W) = r rank(W)=r 且 r ≪ min ( m , n ) r \ll \min(m, n) r≪min(m,n),则 W W W 是低秩矩阵。
性质:
- 低秩矩阵具有较少的自由参数,便于存储和计算。
- 在许多实际应用中,数据矩阵往往是低秩或近似低秩的,这使得低秩分解成为有效的降维和特征提取工具。
2. LoRA的低秩假设
LoRA 基于一个关键假设,即在微调大型语言模型的过程中,模型权重的更新
Δ
W
\Delta W
ΔW 可以通过两个低秩矩阵的乘积来近似表示
:
Δ W = A ⋅ B \Delta W = A \cdot B ΔW=A⋅B
其中:
- A ∈ R d × r A \in \mathbb{R}^{d \times r} A∈Rd×r
- B ∈ R r × k B \in \mathbb{R}^{r \times k} B∈Rr×k
-
r
r
r 是一个远小于
d
d
d 和
k
k
k 的秩(即
r
≪
d
,
k
r \ll d, k
r≪d,k),称为“
瓶颈维度
”。
2.1 参数量的减少
传统的全参数微调需要调整和存储整个权重矩阵 W W W,其参数量为 d × k d \times k d×k。而 LoRA 通过低秩分解,将更新表示为 A ⋅ B A \cdot B A⋅B,其参数量为:
参数量 = d × r + r × k \text{参数量} = d \times r + r \times k 参数量=d×r+r×k
当 r ≪ d , k r \ll d, k r≪d,k 时,参数量大幅减少。例如,对于 W ∈ R 4096 × 4096 W \in \mathbb{R}^{4096 \times 4096} W∈R4096×4096 和 r = 4 r = 4 r=4:
参数量 = 4096 × 4 + 4 × 4096 = 32768 \text{参数量} = 4096 \times 4 + 4 \times 4096 = 32768 参数量=4096×4+4×4096=32768
相比全量参数( 409 6 2 = 16 , 777 , 216 4096^2 = 16,777,216 40962=16,777,216),参数量减少了约 99.8%。
2.2 低秩假设的合理性
- 参数共享:低秩分解通过共享参数
A
A
A 和
B
B
B,捕捉权重更新中的全局结构和模式,
避免了全参数微调中的冗余
。 - 泛化能力:低秩矩阵具有更高的泛化能力,能够在有限的参数量下捕捉到任务相关的重要信息,
减少过拟合的风险
。 - 经验支持:在实践中,许多微调任务表明,权重更新确实具有低秩特性,尤其是在大规模模型和丰富数据的情况下。
3. 参数化更新的数学表示
3.1 权重更新的表示
在微调过程中,假设原始权重矩阵为 W W W,微调后的权重矩阵为 W ′ W' W′,则:
W ′ = W + Δ W = W + A ⋅ B W' = W + \Delta W = W + A \cdot B W′=W+ΔW=W+A⋅B
这里:
- W W W 保持冻结,不参与训练。
- A A A 和 B B B 是可训练的低秩矩阵,用于表示权重的适配更新。
3.2 多层适配
在 Transformer 架构中,通常有多个线性层(如自注意力机制中的查询、键、值矩阵,以及前馈网络的线性层)。对于每个需要适配的权重矩阵 W i W_i Wi,引入对应的 A i A_i Ai 和 B i B_i Bi:
W i ′ = W i + A i ⋅ B i ∀ i ∈ { 适配层集合 } W'_i = W_i + A_i \cdot B_i \quad \forall i \in \{\text{适配层集合}\} Wi′=Wi+Ai⋅Bi∀i∈{适配层集合}
3.3 缩放因子(Scaling Factor)
为了控制适配更新的幅度,引入一个缩放因子 α \alpha α:
W ′ = W + α r A ⋅ B W' = W + \frac{\alpha}{r} A \cdot B W′=W+rαA⋅B
通常,
α
\alpha
α 被设置为与
r
r
r 成正比,以保持数值稳定性和梯度的适当尺度
。
3.4 初始化策略
- 矩阵 A A A:通常采用随机小值初始化,如正态分布 N ( 0 , σ 2 ) \mathcal{N}(0, \sigma^2) N(0,σ2),其中 σ \sigma σ 是一个小的缩放因子(例如 0.01),确保初始的 Δ W \Delta W ΔW 对模型的影响较小。
- 矩阵 B B B:通常初始化为零矩阵 Δ W = 0 \Delta W = 0 ΔW=0,模型行为与原始模型一致。
4. 理论支持与优势
4.1 表达能力
虽然低秩矩阵的参数量较少,但通过组合 A A A 和 B B B,LoRA 能够捕捉到复杂的权重更新模式。理论上,任何 W W W 的近似可以通过足够大的 r r r 实现,但 LoRA 通过选择适当的 r r r,在参数效率和表达能力之间取得平衡。
4.2 减少过拟合
低秩限制了一组特定的权重更新,使模型更倾向于学习全局一致的适配模式,减少了微调过程中的过拟合风险,尤其是在数据量有限的情况下。
4.3 计算与存储效率
由于 A A A 和 B B B 的低秩特性,微调过程中涉及的矩阵乘法和存储需求显著减少,适合资源受限的环境,如边缘设备和大规模分布式训练。
4.4 参数共享与模块化
LoRA 的低秩适配模块可以被多个任务共享或模块化插入,促进了参数的重用和模型的灵活扩展,适合多任务学习和模型集成。
5. 数学推导与示例
5.1 权重更新的最小化问题
微调的目标是通过调整权重 W ′ W' W′ 最小化任务特定的损失函数 L ( W ′ ) \mathcal{L}(W') L(W′)。在 LoRA 框架下,问题转化为:
min A , B L ( W + A ⋅ B ) \min_{A, B} \mathcal{L}(W + A \cdot B) A,BminL(W+A⋅B)
其中, A ∈ R d × r A \in \mathbb{R}^{d \times r} A∈Rd×r, B ∈ R r × k B \in \mathbb{R}^{r \times k} B∈Rr×k。
5.2 梯度计算
在反向传播过程中,只需计算关于 A A A 和 B B B 的梯度,而不涉及 W W W:
∂ L ∂ A = ∂ L ∂ W ′ ⋅ B ⊤ \frac{\partial \mathcal{L}}{\partial A} = \frac{\partial \mathcal{L}}{\partial W'} \cdot B^\top ∂A∂L=∂W′∂L⋅B⊤
∂ L ∂ B = A ⊤ ⋅ ∂ L ∂ W ′ \frac{\partial \mathcal{L}}{\partial B} = A^\top \cdot \frac{\partial \mathcal{L}}{\partial W'} ∂B∂L=A⊤⋅∂W′∂L
5.3 优化算法
使用标准的优化算法(如 Adam)仅更新 A A A 和 B B B,其参数更新规则为:
A ← A − η ∂ L ∂ A A \leftarrow A - \eta \frac{\partial \mathcal{L}}{\partial A} A←A−η∂A∂L
B ← B − η ∂ L ∂ B B \leftarrow B - \eta \frac{\partial \mathcal{L}}{\partial B} B←B−η∂B∂L
其中 η \eta η 是学习率。
5.4 示例计算
假设:
- W ∈ R 4 × 4 W \in \mathbb{R}^{4 \times 4} W∈R4×4
- r = 2 r = 2 r=2
- 初始化 A A A 和 B B B 如下:
A = [ a 11 a 12 a 21 a 22 a 31 a 32 a 41 a 42 ] , B = [ b 11 b 12 b 13 b 14 b 21 b 22 b 23 b 24 ] A = \begin{bmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ a_{31} & a_{32} \\ a_{41} & a_{42} \end{bmatrix}, \quad B = \begin{bmatrix} b_{11} & b_{12} & b_{13} & b_{14} \\ b_{21} & b_{22} & b_{23} & b_{24} \end{bmatrix} A= a11a21a31a41a12a22a32a42 ,B=[b11b21b12b22b13b23b14b24]
则 Δ W = A ⋅ B \Delta W = A \cdot B ΔW=A⋅B 为:
Δ W = [ a 11 b 11 + a 12 b 21 a 11 b 12 + a 12 b 22 a 11 b 13 + a 12 b 23 a 11 b 14 + a 12 b 24 a 21 b 11 + a 22 b 21 a 21 b 12 + a 22 b 22 a 21 b 13 + a 22 b 23 a 21 b 14 + a 22 b 24 a 31 b 11 + a 32 b 21 a 31 b 12 + a 32 b 22 a 31 b 13 + a 32 b 23 a 31 b 14 + a 32 b 24 a 41 b 11 + a 42 b 21 a 41 b 12 + a 42 b 22 a 41 b 13 + a 42 b 23 a 41 b 14 + a 42 b 24 ] \Delta W = \begin{bmatrix} a_{11}b_{11} + a_{12}b_{21} & a_{11}b_{12} + a_{12}b_{22} & a_{11}b_{13} + a_{12}b_{23} & a_{11}b_{14} + a_{12}b_{24} \\ a_{21}b_{11} + a_{22}b_{21} & a_{21}b_{12} + a_{22}b_{22} & a_{21}b_{13} + a_{22}b_{23} & a_{21}b_{14} + a_{22}b_{24} \\ a_{31}b_{11} + a_{32}b_{21} & a_{31}b_{12} + a_{32}b_{22} & a_{31}b_{13} + a_{32}b_{23} & a_{31}b_{14} + a_{32}b_{24} \\ a_{41}b_{11} + a_{42}b_{21} & a_{41}b_{12} + a_{42}b_{22} & a_{41}b_{13} + a_{42}b_{23} & a_{41}b_{14} + a_{42}b_{24} \end{bmatrix} ΔW= a11b11+a12b21a21b11+a22b21a31b11+a32b21a41b11+a42b21a11b12+a12b22a21b12+a22b22a31b12+a32b22a41b12+a42b22a11b13+a12b23a21b13+a22b23a31b13+a32b23a41b13+a42b23a11b14+a12b24a21b14+a22b24a31b14+a32b24a41b14+a42b24
通过优化 A A A 和 B B B,即可实现 Δ W \Delta W ΔW 的最优表示,从而微调模型权重 W ′ W' W′。
5.5 数值稳定性与缩放因子
引入缩放因子 α r \frac{\alpha}{r} rα 可以缓解数值不稳定性,确保微调过程中梯度和更新幅度适中:
W ′ = W + α r A ⋅ B W' = W + \frac{\alpha}{r} A \cdot B W′=W+rαA⋅B
其中, α \alpha α 通常设为与 r r r 成正比的常数(如 α = r \alpha = r α=r),以保持 Δ W \Delta W ΔW 的整体规模。
6. 理论分析与性能保障
6.1 表达力与近似误差
低秩近似 Δ W = A ⋅ B \Delta W = A \cdot B ΔW=A⋅B 的表达力取决于 r r r 的选择。根据矩阵近似理论,给定矩阵 W W W,存在最佳的低秩近似(通过截断SVD),使得近似误差在Frobenius范数下最小:
min A ∈ R d × r , B ∈ R r × k ∥ W − A ⋅ B ∥ F \min_{A \in \mathbb{R}^{d \times r}, B \in \mathbb{R}^{r \times k}} \|W - A \cdot B\|_F A∈Rd×r,B∈Rr×kmin∥W−A⋅B∥F
虽然 LoRA 的优化目标略有不同(因为它在微调任务中直接优化 A A A 和 B B B 以最小化任务损失),但低秩近似仍然能够有效捕捉权重更新中的主要模式。
6.2 参数效率与泛化
低秩限制通过减少自由参数,有助于提升模型的泛化能力。过多的可训练参数容易导致过拟合,尤其是在数据稀缺或任务复杂度高的情况下
。LoRA 通过低秩适配,在参数效率和模型表现之间实现了平衡。
6.3 结合梯度信息
在训练过程中,LoRA 的低秩矩阵
A
A
A 和
B
B
B 可以高效地捕捉梯度信息中的主成分
,提升优化效率。特别是在大规模模型中,梯度的低秩结构可以被更好地利用,从而加速收敛。
五、LoRA进行微调的实例及Python代码
在本示例中,我们将展示如何使用LoRA(Low-Rank Adaptation)对大型预训练语言模型进行复杂微调。我们将使用Hugging Face的transformers库和peft(Parameter-Efficient Fine-Tuning)库来实现这一过程。具体来说,我们将:
- 选择模型和数据集:使用预训练的GPT-2模型,并在复杂的多任务数据集(如MultiWOZ)上进行微调。
- 集成LoRA:在GPT-2的多个关键层中插入LoRA适配模块,包括自注意力层和前馈网络。
- 优化训练:采用混合精度训练、梯度累积、学习率调度等高级技术,以提高训练效率和模型性能。
- 评估与验证:在验证集上评估微调后的模型性能,确保其在各个任务上的表现。
前提条件
确保已安装以下Python库:
pip install transformers datasets peft torch
1. 选择模型和数据集
我们选择GPT-2作为基础模型,并使用MultiWOZ数据集
进行微调。MultiWOZ是一个复杂的多任务对话数据集,涵盖多个领域(如餐厅预订、酒店预订、出租车服务等),适合展示LoRA在多任务学习中的应用。
2. Python代码实现
下面是一个详细的Python脚本,展示如何使用LoRA对GPT-2进行复杂微调。
import torch
from transformers import GPT2Tokenizer, GPT2LMHeadModel, Trainer, TrainingArguments, DataCollatorForLanguageModeling
from datasets import load_dataset
from peft import get_peft_config, get_peft_model, LoraConfig, TaskType
# 检查GPU是否可用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")
# 1. 加载预训练模型和分词器
model_name = "gpt2-large" # 选择较大的GPT-2模型以展示复杂性
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2LMHeadModel.from_pretrained(model_name)
model.to(device)
# 2. 准备数据集
# 这里以MultiWOZ为例,需先下载并处理成文本格式
# 为简化示例,我们使用HuggingFace的wikitext数据集
dataset = load_dataset("wikitext", "wikitext-103-raw-v1")
# 合并训练和验证集中的文本
def tokenize_function(examples):
return tokenizer(examples["text"], return_special_tokens_mask=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True, num_proc=4, remove_columns=["text"])
# 设置块大小
block_size = 1024
def group_texts(examples):
# Concatenate all texts
concatenated = {k: sum(examples[k], []) for k in examples.keys()}
total_length = len(concatenated[list(examples.keys())[0]])
# Drop the small remainder
total_length = (total_length // block_size) * block_size
# Split by chunks of block_size
result = {k: [t[i:i + block_size] for i in range(0, total_length, block_size)] for k, t in concatenated.items()}
return result
lm_datasets = tokenized_datasets.map(group_texts, batched=True, num_proc=4)
# 3. 配置LoRA
# 定义LoRA配置
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, # 因为我们使用的是GPT-2
inference_mode=False,
r=8, # 低秩的秩,调整为适合的值
lora_alpha=32,
lora_dropout=0.1,
target_modules=["c_attn", "c_proj", "fc1", "fc2"] # 选择要应用LoRA的模块
)
# 将LoRA配置应用到模型
model = get_peft_model(model, peft_config)
model.print_trainable_parameters() # 打印可训练参数,验证LoRA是否正确集成
# 4. 设置训练参数
training_args = TrainingArguments(
output_dir="./lora_gpt2_wikitext",
overwrite_output_dir=True,
num_train_epochs=3,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=16, # 相当于总批量大小为32
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=3e-4,
weight_decay=0.01,
fp16=True, # 使用混合精度
logging_steps=100,
save_total_limit=2,
report_to="none", # 不使用任何报告工具
)
# 5. 数据整理
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer,
mlm=False, # 对于GPT-2是因果语言模型,不是MLM
)
# 6. 定义Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=lm_datasets["train"],
eval_dataset=lm_datasets["validation"],
data_collator=data_collator,
)
# 7. 开始训练
trainer.train()
# 8. 保存模型和LoRA适配模块
model.save_pretrained("./lora_gpt2_wikitext")
tokenizer.save_pretrained("./lora_gpt2_wikitext")
# 可选:评估模型
eval_results = trainer.evaluate()
print(f"Perplexity: {torch.exp(torch.tensor(eval_results['eval_loss']))}")
# 9. 推理示例
from transformers import pipeline
# 加载微调后的模型
model = GPT2LMHeadModel.from_pretrained("./lora_gpt2_wikitext")
model = get_peft_model(model, peft_config)
model.to(device)
# 创建生成文本的pipeline
text_generator = pipeline("text-generation", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1)
# 生成文本
prompt = "In a distant future, artificial intelligence"
generated_text = text_generator(prompt, max_length=100, num_return_sequences=1)
print(generated_text[0]['generated_text'])
3. 代码详解
- 加载预训练模型和分词器
model_name = "gpt2-large" # 选择较大的GPT-2模型以展示复杂性
tokenizer = GPT2Tokenizer.from_pretrained(model_name)
model = GPT2LMHeadModel.from_pretrained(model_name)
model.to(device)
我们选择了gpt2-large作为基础模型,并加载了相应的分词器和模型权重。将模型移动到可用的设备(GPU或CPU)以加速训练。
- 准备数据集
dataset = load_dataset("wikitext", "wikitext-103-raw-v1")
为了简化示例,我们使用了Hugging Face的wikitext-103-raw-v1数据集。你可以根据需要替换为MultiWOZ或其他复杂数据集。
- 数据预处理
def tokenize_function(examples):
return tokenizer(examples["text"], return_special_tokens_mask=True)
tokenized_datasets = dataset.map(tokenize_function, batched=True, num_proc=4, remove_columns=["text"])
block_size = 1024
def group_texts(examples):
concatenated = {k: sum(examples[k], []) for k in examples.keys()}
total_length = len(concatenated[list(examples.keys())[0]])
total_length = (total_length // block_size) * block_size
result = {k: [t[i:i + block_size] for i in range(0, total_length, block_size)] for k, t in concatenated.items()}
return result
lm_datasets = tokenized_datasets.map(group_texts, batched=True, num_proc=4)
将文本数据分词,并将其分割成固定长度的块(block_size=1024),适合GPT-2的输入要求。
- 配置LoRA
peft_config = LoraConfig(
task_type=TaskType.CAUSAL_LM, # 因为我们使用的是GPT-2
inference_mode=False,
r=8, # 低秩的秩,调整为适合的值
lora_alpha=32,
lora_dropout=0.1,
target_modules=["c_attn", "c_proj", "fc1", "fc2"] # 选择要应用LoRA的模块
)
- task_type指定了任务类型,这里为因果语言建模。
- r是LoRA的秩,决定了低秩矩阵的大小。
- lora_alpha和lora_dropout是LoRA的超参数。
- target_modules指定了要插入LoRA适配器的模块名称。对于GPT-2,这些通常是自注意力层中的c_attn和c_proj,以及前馈网络中的fc1和fc2。
model = get_peft_model(model, peft_config)
model.print_trainable_parameters()
使用peft库将LoRA配置应用到模型,并打印出可训练的参数,验证LoRA是否正确集成。
- 设置训练参数
training_args = TrainingArguments(
output_dir="./lora_gpt2_wikitext",
overwrite_output_dir=True,
num_train_epochs=3,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=16, # 相当于总批量大小为32
evaluation_strategy="epoch",
save_strategy="epoch",
learning_rate=3e-4,
weight_decay=0.01,
fp16=True, # 使用混合精度
logging_steps=100,
save_total_limit=2,
report_to="none", # 不使用任何报告工具
)
- 混合精度训练:fp16=True启用混合精度训练,减少显存占用,加速训练。
- 梯度累积:gradient_accumulation_steps=16将有效批量大小扩大为2 * 16 = 32,适用于显存有限的情况。
- 学习率和权重衰减:选择合适的学习率和权重衰减参数以优化训练。
- 保存策略:每个训练周期结束时保存模型。
- 定义Trainer
trainer = Trainer(
model=model,
args=training_args,
train_dataset=lm_datasets["train"],
eval_dataset=lm_datasets["validation"],
data_collator=data_collator,
)
使用Hugging Face的Trainer类来管理训练过程,自动处理训练循环、评估和保存模型。
- 开始训练
trainer.train()
启动训练过程,模型将根据配置的参数进行微调。
- 保存模型和LoRA适配模块
model.save_pretrained("./lora_gpt2_wikitext")
tokenizer.save_pretrained("./lora_gpt2_wikitext")
将微调后的模型和分词器保存到指定目录。
- 推理示例
from transformers import pipeline
# 加载微调后的模型
model = GPT2LMHeadModel.from_pretrained("./lora_gpt2_wikitext")
model = get_peft_model(model, peft_config)
model.to(device)
# 创建生成文本的pipeline
text_generator = pipeline("text-generation", model=model, tokenizer=tokenizer, device=0 if torch.cuda.is_available() else -1)
# 生成文本
prompt = "In a distant future, artificial intelligence"
generated_text = text_generator(prompt, max_length=100, num_return_sequences=1)
print(generated_text[0]['generated_text'])
加载微调后的模型,并使用Hugging Face的pipeline生成文本,验证微调效果。
总结
LoRA通过低秩矩阵分解技术,将大型模型的权重更新表示为两个低秩矩阵的乘积,显著减少了需要训练和存储的参数量。这一方法基于矩阵的秩与低秩近似的数学原理,通过巧妙的参数化更新,实现了高效的模型适配。其数学基础不仅保证了参数效率和计算成本的降低,还在理论上支持了其在实际任务中的有效性和泛化能力。随着进一步的研究和优化,LoRA有望在更多复杂任务和更大规模模型中发挥关键作用。
结~~~