如何在神经网络模型中通过低秩矩阵调整权重,并只训练少量参数
这段代码展示了 LoRA(Low-Rank Adaptation) 的应用,如何在神经网络模型中通过低秩矩阵调整权重,并只训练少量参数。接下来我会详细解释代码中的关键部分,帮助你理解其工作原理。
🔍 1️⃣ 原始模型 BaseModel
class BaseModel(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(BaseModel, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
fc1
: 输入层到隐藏层的线性变换。fc2
: 隐藏层到输出层的线性变换。F.relu()
: ReLU 激活函数,增加模型的非线性能力。
这是一个简单的两层全连接神经网络。
⚡ 2️⃣ LoRA 模块
class LoRA(nn.Module):
def __init__(self, original_layer, rank=8, alpha=16):
super(LoRA, self).__init__()
self.original_layer = original_layer # 原始全连接层
self.rank = rank
self.scaling = alpha / rank
关键参数解释:
original_layer
: 需要调整的原始全连接层(这里是fc1
)。rank
: 低秩矩阵的秩(维度),控制参数量的压缩程度。alpha
: 缩放因子,用于平衡 LoRA 影响力,最终缩放系数为alpha / rank
。
📐 3️⃣ 低秩矩阵 A 和 B
# 低秩矩阵 A 和 B
self.A = nn.Linear(original_layer.in_features, rank, bias=False) # 降维
self.B = nn.Linear(rank, original_layer.out_features, bias=False) # 升维
-
A
(降维矩阵):- 输入维度 →
rank
维度,起到信息压缩的作用。 - 类似于 Adapter 中的降维层。
- 输入维度 →
-
B
(升维矩阵):rank
维度 → 输出维度,将压缩后的信息重新扩展。- 类似于 Adapter 中的升维层。
这种设计可以有效减少可训练参数的数量。
🚀 4️⃣ 初始化与冻结参数
# 初始化 A 和 B
nn.init.kaiming_uniform_(self.A.weight, a=0.01) # 初始化 A,适用于 ReLU 激活
nn.init.zeros_(self.B.weight) # 初始化 B 为 0
# 冻结原始层参数
for param in self.original_layer.parameters():
param.requires_grad = False
-
初始化策略:
A
的权重 使用 Kaiming 初始化,适合 ReLU 激活函数。B
的权重 初始化为全零,确保初始时不对模型产生影响,后续通过训练逐渐调整。
-
冻结原始参数:
- 设置
requires_grad = False
,意味着原始层的权重在训练时不会更新,只训练 LoRA 的参数。
- 设置
🔄 5️⃣ LoRA 前向传播
def forward(self, x):
return self.original_layer(x) + self.scaling * self.B(self.A(x))
公式解释:
[
\text{Output} = W x + \alpha \cdot BAx
]
original_layer(x)
: 代表原始模型的输出 ( W x )。B(A(x))
: 通过低秩矩阵 ( A ) 和 ( B ) 进行调整的部分。self.scaling
: 控制调整部分的影响力。
这种设计确保了模型在初始状态下性能不受影响,训练时逐渐调整。
🔗 6️⃣ 替换 fc1
层,应用 LoRA
class ModelWithLoRA(BaseModel):
def __init__(self, input_size, hidden_size, output_size):
super(ModelWithLoRA, self).__init__(input_size, hidden_size, output_size)
self.fc1 = LoRA(self.fc1, rank=4) # 低秩矩阵替换 fc1
- 将原始模型中的
fc1
层替换为LoRA
模块。 - 这样,
fc1
的权重不再直接更新,而是通过 LoRA 的调整来间接影响。
⚙️ 7️⃣ 训练设置
# 只训练 LoRA 的 A 和 B
optimizer = torch.optim.Adam(
list(model.fc1.A.parameters()) + list(model.fc1.B.parameters()),
lr=0.001
)
- 只训练 A 和 B:
- 优化器只更新 LoRA 模块的参数,原始模型的参数保持不变。
- 这可以显著减少训练所需的计算资源。
🎯 Adapter vs LoRA 对比总结
特点 | Adapter | LoRA |
---|---|---|
结构 | 降维 → 激活 → 升维 + 残差 | 低秩矩阵分解(A: 降维,B: 升维) + 残差 |
目的 | 在不改变主模型的基础上,增加微调能力 | 高效调整原始权重,减少参数更新量 |
参数量 | 少量增加参数 | 更少参数,尤其适用于大模型 |
适用场景 | 各类微调场景 | 超大模型(如大语言模型)微调,效果更佳 |
初始化 | 正常随机初始化 | B 初始化为 0,初始时不影响模型输出 |