在Pytorch中為不同層設(shè)置不同學(xué)習(xí)率來提升性能,優(yōu)化深度學(xué)習(xí)模型
在深度學(xué)習(xí)模型的訓(xùn)練過程中,學(xué)習(xí)率作為一個關(guān)鍵的超參數(shù),對模型的收斂速度和最終性能有著重大影響。傳統(tǒng)方法通常采用統(tǒng)一的學(xué)習(xí)率,但隨著研究的深入,我們發(fā)現(xiàn)為網(wǎng)絡(luò)的不同層設(shè)置不同的學(xué)習(xí)率可能會帶來顯著的性能提升。本文將詳細(xì)探討這一策略的實(shí)施方法及其在PyTorch框架中的具體應(yīng)用。
層級學(xué)習(xí)率的理論基礎(chǔ)
深度神經(jīng)網(wǎng)絡(luò)的不同層次在特征提取和信息處理上扮演著不同的角色?;谶@一認(rèn)知,我們可以合理推斷對不同層采用差異化的學(xué)習(xí)策略可能會更有效:
- 底層特征提取:網(wǎng)絡(luò)的前幾層通常負(fù)責(zé)捕獲通用的低級特征,如邊緣、紋理等。這些特征往往具有較強(qiáng)的通用性和可遷移性。
- 高層語義理解:網(wǎng)絡(luò)的后幾層則傾向于提取更為抽象和任務(wù)相關(guān)的高級特征。
- 任務(wù)特定層:如全連接分類層,直接與特定任務(wù)相關(guān)。
基于上述觀察我們可以制定相應(yīng)的學(xué)習(xí)率策略:
- 對于預(yù)訓(xùn)練的底層,使用較小的學(xué)習(xí)率以保持其已學(xué)到的通用特征。
- 對于中間層,可以采用適中的學(xué)習(xí)率。
- 對于任務(wù)特定的頂層,則可以使用較大的學(xué)習(xí)率以快速適應(yīng)新任務(wù)。
PyTorch實(shí)現(xiàn):以ResNet為例
下面我們將以ResNet18為例,演示如何在PyTorch中實(shí)現(xiàn)層級學(xué)習(xí)率設(shè)置。
1、模型定義
首先,我們加載預(yù)訓(xùn)練的ResNet18模型,并修改其最后一層以適應(yīng)新的分類任務(wù):
import torch
import torch.nn as nn
import torchvision.models as models
# 加載預(yù)訓(xùn)練的ResNet18模型
model = models.resnet18(pretrained=True)
# 修改最后的全連接層以適應(yīng)新的分類任務(wù)
num_classes = 10 # 假設(shè)新任務(wù)有10個類別
model.fc = nn.Linear(model.fc.in_features, num_classes)
2、參數(shù)分組
接下來,我們將模型參數(shù)分組,為不同的層設(shè)置不同的學(xué)習(xí)率:
# 定義不同組的學(xué)習(xí)率
backbone_lr = 1e-4 # 較小的學(xué)習(xí)率用于預(yù)訓(xùn)練的主干網(wǎng)絡(luò)
classifier_lr = 1e-3 # 較大的學(xué)習(xí)率用于新的分類器層
# 創(chuàng)建參數(shù)組
params = [
{'params': model.conv1.parameters(), 'lr': backbone_lr},
{'params': model.bn1.parameters(), 'lr': backbone_lr},
{'params': model.layer1.parameters(), 'lr': backbone_lr},
{'params': model.layer2.parameters(), 'lr': backbone_lr},
{'params': model.layer3.parameters(), 'lr': backbone_lr},
{'params': model.layer4.parameters(), 'lr': backbone_lr},
{'params': model.fc.parameters(), 'lr': classifier_lr}
]
此處我們對ResNet的各個組件進(jìn)行了更細(xì)致的劃分,為不同的層組設(shè)置了相應(yīng)的學(xué)習(xí)率。這種方法允許我們對模型的學(xué)習(xí)過程進(jìn)行更精細(xì)的控制。
優(yōu)化器配置與訓(xùn)練過程
3、優(yōu)化器設(shè)置
在確定了參數(shù)分組后,我們需要選擇合適的優(yōu)化器并進(jìn)行配置。這里我們簡單的選用Adam優(yōu)化器。
optimizer = torch.optim.Adam(params)
這種分組策略同樣適用于其他PyTorch支持的優(yōu)化器,PyTorch的優(yōu)化器會自動識別并應(yīng)用在參數(shù)分組中定義的不同學(xué)習(xí)率。這種設(shè)計使得實(shí)現(xiàn)層級學(xué)習(xí)率變得相對簡單。
4、訓(xùn)練循環(huán)
實(shí)現(xiàn)了層級學(xué)習(xí)率后的訓(xùn)練循環(huán)保持不變。PyTorch會在后臺自動處理不同參數(shù)組的學(xué)習(xí)率:
# 定義損失函數(shù)
criterion = nn.CrossEntropyLoss()
# 訓(xùn)練循環(huán)
for epoch in range(num_epochs):
model.train()
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
# 在每個epoch結(jié)束后進(jìn)行驗(yàn)證
model.eval()
# ... [驗(yàn)證代碼]
5、學(xué)習(xí)率調(diào)度
除了設(shè)置初始的層級學(xué)習(xí)率,我們還可以結(jié)合學(xué)習(xí)率調(diào)度器來動態(tài)調(diào)整學(xué)習(xí)率。PyTorch提供了多種學(xué)習(xí)率調(diào)度器,如StepLR、ReduceLROnPlateau等。以下是一個使用StepLR的示例:
from torch.optim.lr_scheduler import StepLR
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
# 在訓(xùn)練循環(huán)中更新學(xué)習(xí)率
for epoch in range(num_epochs):
# ... [訓(xùn)練代碼]
scheduler.step()
這將每30個epoch將所有參數(shù)組的學(xué)習(xí)率降低為原來的0.1倍。
高級學(xué)習(xí)率優(yōu)化技巧
1、漸進(jìn)式解凍
在微調(diào)預(yù)訓(xùn)練模型時,一種有效的策略是漸進(jìn)式解凍。我們可以先鎖定底層,只訓(xùn)練頂層,然后逐步解凍更多的層:
# 初始階段:只訓(xùn)練分類器
for param in model.parameters():
param.requires_grad = False
model.fc.requires_grad = True
# 訓(xùn)練幾個epoch后
model.layer4.requires_grad = True
# 再過幾個epoch
model.layer3.requires_grad = True
以此類推,凍結(jié)其實(shí)意味著學(xué)習(xí)率為0,也就是不對任何參數(shù)進(jìn)行更新。
2、層適應(yīng)學(xué)習(xí)率
我們上面已經(jīng)介紹了手動指定固定的學(xué)習(xí)率,其實(shí)我們還可以通過自定義優(yōu)化器來實(shí)現(xiàn),不同的層的不同的學(xué)習(xí)率范圍。我們可以實(shí)現(xiàn)一個自定義的優(yōu)化器來自動調(diào)整每一層的學(xué)習(xí)率:
class LayerAdaptiveLR(torch.optim.Adam):
def __init__(self, params, lr=1e-3, betas=(0.9, 0.999), eps=1e-8, weight_decay=0):
super().__init__(params, lr, betas, eps, weight_decay)
self.param_groups = sorted(self.param_groups, key=lambda x: id(x['params'][0]))
def step(self, closure=None):
loss = None
if closure is not None:
loss = closure()
for group in self.param_groups:
for p in group['params']:
if p.grad is None:
continue
grad = p.grad.data
state = self.state[p]
# 根據(jù)梯度統(tǒng)計調(diào)整學(xué)習(xí)率
if len(state) == 0:
state['step'] = 0
state['exp_avg'] = torch.zeros_like(p.data)
state['exp_avg_sq'] = torch.zeros_like(p.data)
exp_avg, exp_avg_sq = state['exp_avg'], state['exp_avg_sq']
beta1, beta2 = group['betas']
state['step'] += 1
exp_avg.mul_(beta1).add_(grad, alpha=1 - beta1)
exp_avg_sq.mul_(beta2).addcmul_(grad, grad, value=1 - beta2)
denom = exp_avg_sq.sqrt().add_(group['eps'])
# 動態(tài)調(diào)整學(xué)習(xí)率
step_size = group['lr'] * (exp_avg.abs() / denom).mean().item()
p.data.add_(exp_avg, alpha=-step_size)
return loss
# 使用示例
optimizer = LayerAdaptiveLR(model.parameters(), lr=1e-3)
可以看到,上面我們繼承自Adam優(yōu)化器,這里我們不用實(shí)現(xiàn)優(yōu)化過程只針對于針對層的學(xué)習(xí)率變化即可。
總結(jié)
層級學(xué)習(xí)率設(shè)置是一種強(qiáng)大的優(yōu)化技術(shù),特別適用于遷移學(xué)習(xí)和微調(diào)預(yù)訓(xùn)練模型的場景。通過精心設(shè)計的學(xué)習(xí)率策略,可以在保留預(yù)訓(xùn)練模型通用特征的同時有效地適應(yīng)新任務(wù)。結(jié)合其他高級技巧,如漸進(jìn)式解凍、層適應(yīng)學(xué)習(xí)率,可以進(jìn)一步提升模型的訓(xùn)練效率和性能。
在實(shí)際應(yīng)用中,最佳的學(xué)習(xí)率配置往往需要通過實(shí)驗(yàn)來確定。建議研究者根據(jù)具體任務(wù)和模型架構(gòu)進(jìn)行適當(dāng)?shù)恼{(diào)整和實(shí)驗(yàn),以獲得最佳的訓(xùn)練效果。