手动调整学习率的策略相对前面梯度下降优化算法自动调整的方式,比较少用。原因是实际工作神经网络的结构是比较复杂,手动很难调整到比较好的状态
为什么要进行学习率优化
在训练神经网络时,一般情况下学习率都会随着训练而变化。这主要是由于,在神经网络训练的后期,如果学习率过高,会造成loss的振荡,但是如果学习率减小的过慢,又会造成收敛变慢的情况。
展开查看更多
运行下面代码,观察学习率设置不同对网络训练的影响:
观察不同学习率对训练的影响
"""
案例:
通过代码 观察不同的学习率 对 参数更新 的影响.
"""
import torch
import matplotlib.pyplot as plt
# x看成是权重,y看成是loss,下面通过代码来理解学习率的作用
def func(x_t):
return torch.pow(2*x_t, 2) # y = 4 x ^2
# 采用较小的学习率,梯度下降的速度慢
# 采用较大的学习率,梯度下降太快越过了最小值点,导致不收敛,甚至震荡
def dm01(lr=0.3):
x = torch.tensor([2.], requires_grad=True)
# 记录loss迭代次数,画曲线
iter_rec, loss_rec, x_rec = list(), list(), list()
# 实验学习率: 0.01 0.02 0.03 0.1 0.2 0.3 0.4
# lr = 0.1 # 正常的梯度下降
# lr = 0.125 # 当学习率设置0.125 一下子求出一个最优解
# x=0 y=0 在x=0处梯度等于0 x的值x=x-lr*x.grad就不用更新了
# 后续再多少次迭代 都固定在最优点
# lr = 0.3 # x从2.0一下子跨过0点,到了左侧负数区域
# lr = 0.3 # 梯度越来越大 梯度爆炸
max_iteration = 40
for i in range(max_iteration):
y = func(x) # 得出loss值
y.backward() # 计算x的梯度
print("Iter:{}, X:{:8}, X.grad:{:8}, loss:{:10}".format(
i, x.detach().numpy()[0], x.grad.detach().numpy()[0], y.item()))
x_rec.append(x.item()) # 梯度下降点 列表
# 更新参数
x.data.sub_(lr * x.grad) # x = x - x.grad
x.grad.zero_()
iter_rec.append(i) # 迭代次数 列表
loss_rec.append(y.item()) # 损失值 列表,这里将y改为y.item()以获取标量值
# 迭代次数-损失值 关系图
plt.subplot(121).plot(iter_rec, loss_rec, '-ro')
plt.grid()
plt.xlabel("Iteration X")
plt.ylabel("Loss value Y")
# 函数曲线-下降轨迹 显示图
x_t = torch.linspace(-3, 3, 100)
y = func(x_t)
plt.subplot(122).plot(x_t.detach().numpy(), y.detach().numpy(), label="y = 4*x^2")
y_rec = [func(torch.tensor(i)).item() for i in x_rec]
print('x_rec--->', x_rec)
print('y_rec--->', y_rec)
# 指定线的颜色和样式(-ro:红色圆圈,b-:蓝色实线等)
plt.subplot(122).plot(x_rec, y_rec, '-ro')
plt.grid()
plt.legend()
plt.show()
if __name__ == '__main__':
# dm01(0.01) # 学习率过小,梯度下降缓慢,需要迭代计算多次
# dm01(0.1)
dm01(0.2) # 学习率设置的不合理,会出现梯度震荡
# dm01(0.3) # 学习率过大,会出现梯度爆炸运行效果图如下:
可以看出:采用较小的学习率,梯度下降的速度慢;采用较大的学习率,梯度下降太快越过了最小值点,导致震荡,甚至不收敛(梯度爆炸)。

等间隔学习率衰减
等间隔学习率衰减方式如下所示:

在PyTorch中实现时使用:
# step_size:调整间隔数=50
# gamma:调整系数=0.5
# 调整方式:lr = lr * gamma
optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.5)
具体使用方式如下:
StepLR 使用示例
import matplotlib.pyplot as plt
import torch
import torch.nn
def demo():
# 定义真实值、特征值、权重值
y_true = torch.tensor([0])
x = torch.tensor([1.0],dtype=torch.float32)
w = torch.tensor([1.0],requires_grad=True,dtype=torch.float32)
# 梯度下降优化器
optimizer = torch.optim.SGD([w],lr=0.1,momentum=0.9)
# 学习率等间隔下降策略
"""
step_size:每隔多少个轮次更新一次学习率
gamma:学习率的更新系数
"""
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=50,gamma=0.5)
# 训练的总轮次
epochs = 200
iteration = 10
epoch_list = [] # 记录循环到哪个轮次了
lr_list = [] # 记录对应轮次的学习率
for epoch in range(epochs):
for i in range(iteration):
# 获得预测值
y_pred = w*x
# 计算损失值
loss_value = (y_pred-y_true)**2
# 梯度清零、反向传播、更新权重
optimizer.zero_grad()
loss_value.sum().backward()
optimizer.step()
# 记录对应轮次和对应的学习率
epoch_list.append(epoch)
lr_list.append(scheduler.get_last_lr())
print(f"第{epoch}轮次,学习率是多少{scheduler.get_last_lr()}")
# 按照等间隔(epoch的等间隔)更新学习率
scheduler.step()
# 绘制折线图
plt.plot(epoch_list,lr_list)
plt.xlabel("Epoch")
plt.ylabel("lr")
plt.show()
if __name__ == '__main__':
demo()指定间隔学习率衰减
指定间隔学习率衰减的效果如下:

在PyTorch中实现时使用:
# milestones:设定调整轮次:[50, 125, 160]
# gamma:调整系数
# 调整方式:lr = lr * gamma
optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1)
具体使用方式如下所示:
MultiStepLR 使用示例
# 导包
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
# 2. 定义函数, 演示: 如何实现 指定间隔学习率优化.
def dm02():
# 1. 定义变量, 记录: 训练的总轮数, 初始学习率, 每轮的迭代器个数.
epochs, lr, iteration = 200, 0.1, 10
# 2. 定义变量, 记录: 真实值.
y_true = torch.tensor([0], dtype=torch.float32)
# 3. 定义变量, 记录: w(权重), x(特征)
x = torch.tensor([1.0], dtype=torch.float32)
w = torch.tensor([1.0], dtype=torch.float32, requires_grad= True)
# 4. 创建优化器对象, 采用: 动量法.
optimizer = optim.SGD([w], lr=lr, momentum=0.9)
# 5. 创建 学习率衰减优化对象.
# 思路1: 等间隔学习率衰减优化对象, 设置 50轮/次, 更新学习率.
# 参1: 优化器对象. 参2: 衰减的轮数间隔. 参3: 衰减系数, 即: lr新 = lr旧 * gamma
# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5) # [0.1, 0.05, 0.0125...]
# 思路2: 指定间隔学习率衰减优化对象, 设置第50, 125, 160轮 更新学习率.
# milestones = [24, 50, 60, 90, 125, 130, 160]
milestones = [50, 125, 160]
# 参1: 优化器对象. 参2: 衰减的(指定)轮数. 参3: 衰减系数, 即: lr新 = lr旧 * gamma
scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.5)
# 6. 定义列表, 分别记录: 训练的轮数, 该轮的学习率.
epoch_list, lr_list = [], []
# 7. 具体的 每轮 训练过程
# 7.1 逐轮训练.
for epoch in range(epochs): # epoch: 0 ~ 200, 包左不包右
# 7.2 添加轮数, 以及本轮的 学习率
epoch_list.append(epoch)
lr_list.append(scheduler.get_last_lr())
# 7.3 每轮的训练过程, 即: 每轮有 10个 迭代器.
for i in range(iteration):
# 7.4 计算预测值.
y_pred = w * x
# 7.5 计算损失值.
loss = (y_pred - y_true) ** 2
# 7.6 反向传播.
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 7.7 走这里,说明: 本轮(一轮)训练完成. 更新本轮的学习率.
scheduler.step()
# 8. 打印下: 每轮的 学习率
print(f'epoch_list: {epoch_list}')
print(f'lr_list: {lr_list}')
# 9. 画图, 绘制: 训练的轮数 以及 每轮的 学习率
plt.plot(epoch_list, lr_list)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.show()
# 测试
if __name__ == '__main__':
dm02()按指数学习率衰减
按指数衰减调整学习率的效果如下:

在PyTorch中实现时使用:
# gamma:指数的底
# 调整方式
# lr= lr∗gamma^epoch
optim.lr_scheduler.ExponentialLR(optimizer, gamma)
具体使用方式如下所示:
ExponentialLR 使用示例
"""
案例:
演示 学习率衰减优化方式.
背景:
之前学的 AdaGrad, RMSProp, Adam这些梯度下降优化方式, 都可以修改 学习率, 但是底层无法人为精准控制, 例如: *轮 更新一次, 制定轮更新等...
针对于此, PyTorch中加入了 学习率衰减优化方式, 可以人为的控制学习率的变化.
方式:
1. 等间隔学习率优化.
间隔制定的轮数, 优化(更新)学习率.
lr新 = lr旧 * gamma
2. 指定间隔学习率优化.
设置指定的轮数, 当达到指定的轮数后, 即可自动优化 学习率.
lr新 = lr旧 * gamma
3. 指数学习率优化.
lr新 = lr旧 * gamma ^ epoch
"""
# 导包
import torch
import torch.optim as optim
import matplotlib.pyplot as plt
# 3. 定义函数, 演示: 如何实现 指数学习率优化.
def dm03():
# 1. 定义变量, 记录: 训练的总轮数, 初始学习率, 每轮的迭代器个数.
epochs, lr, iteration = 200, 0.1, 10
# 2. 定义变量, 记录: 真实值.
y_true = torch.tensor([0], dtype=torch.float32)
# 3. 定义变量, 记录: w(权重), x(特征)
x = torch.tensor([1.0], dtype=torch.float32)
w = torch.tensor([1.0], dtype=torch.float32, requires_grad= True)
# 4. 创建优化器对象, 采用: 动量法.
optimizer = optim.SGD([w], lr=lr, momentum=0.9)
# 5. 创建 学习率衰减优化对象.
# 思路1: 等间隔学习率衰减优化对象, 设置 50轮/次, 更新学习率.
# 参1: 优化器对象. 参2: 衰减的轮数间隔. 参3: 衰减系数, 即: lr新 = lr旧 * gamma
# scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=50, gamma=0.5) # [0.1, 0.05, 0.0125...]
# 思路2: 指定间隔学习率衰减优化对象, 设置第50, 125, 160轮 更新学习率.
# milestones = [24, 50, 60, 90, 125, 130, 160]
# milestones = [50, 125, 160]
# 参1: 优化器对象. 参2: 衰减的(指定)轮数. 参3: 衰减系数, 即: lr新 = lr旧 * gamma
# scheduler = optim.lr_scheduler.MultiStepLR(optimizer, milestones=milestones, gamma=0.5)
# 思路3: 指数衰减学习率衰减优化对象. 计算公式: lr新 = lr旧 * gamma ^ epoch
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, gamma=0.9)
# 6. 定义列表, 分别记录: 训练的轮数, 该轮的学习率.
epoch_list, lr_list = [], []
# 7. 具体的 每轮 训练过程
# 7.1 逐轮训练.
for epoch in range(epochs): # epoch: 0 ~ 200, 包左不包右
# 7.2 添加轮数, 以及本轮的 学习率
epoch_list.append(epoch)
lr_list.append(scheduler.get_last_lr())
# 7.3 每轮的训练过程, 即: 每轮有 10个 迭代器.
for i in range(iteration):
# 7.4 计算预测值.
y_pred = w * x
# 7.5 计算损失值.
loss = (y_pred - y_true) ** 2
# 7.6 反向传播.
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 7.7 走这里,说明: 本轮(一轮)训练完成. 更新本轮的学习率.
scheduler.step()
# 8. 打印下: 每轮的 学习率
print(f'epoch_list: {epoch_list}')
print(f'lr_list: {lr_list}')
# 9. 画图, 绘制: 训练的轮数 以及 每轮的 学习率
plt.plot(epoch_list, lr_list)
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.show()
# 测试
if __name__ == '__main__':
dm03()小结
| 方法 | 等间隔学习率衰减 (Step Decay) | 指定间隔学习率衰减 (Exponential Decay) | 指数学习率衰减 (Exponential Moving Average Decay) |
|---|---|---|---|
| 衰减方式 | 固定步长衰减 | 指定步长衰减 | 平滑指数衰减,历史平均考虑 |
| 实现难度 | 简单易实现 | 相对简单,容易调整 | 需要额外历史计算,较复杂 |
| 适用场景 | 大型数据集、较为简单的任务 | 对训练平稳性要求较高的任务 | 高精度训练,避免过快收敛 |
| 优点 | 直观,易于调试,适用于大批量数据 | 易于调试,稳定训练过程 | 平滑且考虑历史更新,收敛稳定性较强 |
| 缺点 | 学习率变化较大,可能跳过最优点 | 在某些情况下可能衰减过快,导致优化提前停滞 | 超参数调节较为复杂,可能需要更多的计算资源 |