简介
深度学习中的学习率是模型训练中至关重要的超参数之一。合适的学习率可以加速模型的收敛,提高训练效率,而不恰当的学习率可能导致训练过慢或者无法收敛。为了找到合适的学习率,LR Finder成为了一种强大的工具。
学习率范围测试(LR Finder)是一种通过逐渐增加学习率来观察模型在不同学习率下的性能变化的方法。这个过程可以帮助我们找到一个合适的初始学习率,有助于训练过程的稳定和加速。在本文中,我们将深入探讨 LR Finder 的原理、实现和应用,以及如何在实际的分类项目中充分利用这个强大的工具。
学习率在深度学习中的关键
深度学习当中,学习率是一个至关重要的超参数,直接影响了模型的训练性能和收敛速度。因为它决定了每一次参数更新的步长,过大的学习率可能会导致模型在训练过程中发散,而过小的学习率则可能导致模型训练缓慢甚至停滞。
学习率调整方法
下面是一些常见的学习率调整方法,它们在不同的场景和问题上表现出色。
1.常数学习率
常数学习率是最简单的学习率调整方法,即在整个训练过程中,学习率始终保持不变,但是它只是比较适用于数据集较小、模型已经比较稳定的情况。
用法很简单,就是在训练开始时选择一个合适的学习率,比如0.001或0.01,后续的训练过程中就不再变化了。这个值的设定就比较依赖于的经验了。
2.学习率衰减
学习率衰减是随着训练的进行逐渐减小学习率。它可以分为定期调整和根据模型性能调整两种方式,它适用于需要在训练初期更加收敛,而在后期更加稳定的情况。
- 定期调整:每隔一段训练轮次或步骤,学习率按照一定的衰减率进行调整。
- 性能调整:根据模型在验证集上的性能进行调整,性能停滞时降低学习率,性能提高时保持学习率不变或轻微提高。
3.自适应学习率
自适应学习率方法根据模型参数的梯度信息和历史信息来自动调整学习率。主要应用于不同参数具有不同特性或数据分布不均匀的情况。
- Adam: 结合了动量法和自适应学习率的方法,对不同参数应用不同的学习率。
- Adagrad: 根据参数的历史梯度信息自适应地调整学习率。
- RMSProp: 在 Adagrad 的基础上加入了一个衰减系数,以防止学习率过早地降低。
学习率范围测试(LR Finder)
学习率范围测试是一种通过逐渐增加学习率来观察模型性能的方法,从而找到一个合适的初始学习率,适合在训练初期选择合适的学习率范围。
实现的方法:
逐渐增加学习率,从一个较小的学习率开始,逐渐增加学习率直到性能开始下降,在学习率范围内观察模型的性能曲线,找到性能峰值对应的学习率。
class FindLR(_LRScheduler):
"""
exponentially increasing learning rate
Args:
optimizer: optimzier(e.g. SGD)
num_iter: totoal_iters
max_lr: maximum learning rate
"""
def __init__(self, optimizer, max_lr=10, num_iter=100, last_epoch=-1):
self.total_iters = num_iter
self.max_lr = max_lr
super().__init__(optimizer, last_epoch)
def get_lr(self):
return [base_lr * (self.max_lr / base_lr) ** (self.last_epoch / (self.total_iters + 1e-32)) for base_lr in self.base_lrs]
在 get_lr 方法中,通过指数增长的方式计算当前迭代次数下每个参数的学习率。这个方法返回一个学习率列表,其中每个元素对应网络中的一个参数。这个学习率列表将被用于更新优化器中的学习率。
import torch
import torch.optim as optim
from pyzjr.core.lr_scheduler import _LRScheduler
import matplotlib.pyplot as plt
class FindLR(_LRScheduler):
def __init__(self, optimizer, max_lr=10, num_iter=100, last_epoch=-1):
self.total_iters = num_iter
self.max_lr = max_lr
super().__init__(optimizer, last_epoch)
def get_lr(self):
return [base_lr * (self.max_lr / base_lr) ** (self.last_epoch / (self.total_iters + 1e-32)) for base_lr in self.base_lrs]
x = torch.arange(0, 100, 1)
y = torch.sin(0.1 * x) + 0.1 * torch.randn(100)
model = torch.nn.Linear(1, 1)
optimizer = optim.SGD(model.parameters(), lr=0.01)
lr_finder = FindLR(optimizer, max_lr=10, num_iter=100)
lr_values = []
for epoch in range(100):
outputs = model(x.unsqueeze(1).float())
loss = torch.nn.functional.mse_loss(outputs.squeeze(), y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
lr_finder.step()
current_lr = optimizer.param_groups[0]['lr']
lr_values.append(current_lr)
print(f"Iteration: {epoch}, Loss: {loss.item():.4f}, LR: {current_lr:.8f}")
plt.switch_backend('TkAgg')
plt.plot(lr_values)
plt.xlabel('Iteration')
plt.ylabel('Learning Rate')
plt.title('Learning Rate Range Test')
plt.show()
迭代与学习率的曲线图:
下面我们将采用pyzjr中定义的 lr_finder 对CIFAR-100进行学习率范围测试:
import torch.nn as nn
import matplotlib
matplotlib.use('Agg')
from utils import get_network
from dataset import get_train_loader
from pyzjr.dlearn.learnrate import lr_finder
CIFAR100_TRAIN_MEAN = (0.5070751592371323, 0.48654887331495095, 0.4409178433670343)
CIFAR100_TRAIN_STD = (0.2673342858792401, 0.2564384629170883, 0.27615047132568404)
if __name__ == '__main__':
class parser_args():
def __init__(self):
self.net = "vgg16"
self.batch_size = 64
self.base_lr = 1e-7
self.max_lr = 10
self.num_iter = 100
self.Cuda = True
args = parser_args()
mean = CIFAR100_TRAIN_MEAN
std = CIFAR100_TRAIN_STD
train_loader = get_train_loader(mean, std, batch_size=4)
net = get_network(args) # 网络模型定义
loss_function = nn.CrossEntropyLoss()
lrfinder = lr_finder(net, train_loader, loss_function)
lrfinder.update() # 不断迭代
lrfinder.plotshow() # 绘制图像并显示
lrfinder.save(path="result.jpg") # 保存图像到指定路径
输出图像result.jpg: