三、神经网络——网络优化方法
三、网络优化方法
1.梯度下降算法
-
梯段下降算法是一种寻找使损失函数最小化的方法,从数学上的角度来看,梯度的方向是函数增长速度最快的方向,那么梯度的反方向就是函数减少最快的方向,所以有Wijnew=Wijold−η∂E∂WijW_{ij}^{new} = W_{ij}^{old}-\eta\frac{\partial E}{\partial W_{ij}}Wijnew=Wijold−η∂Wij∂E
- 其中,η\etaη是学习率,如果学习率太小,那么每次训练之后得到的效果都太小,增大训练的时间成本;如果学习率太大,那就有可能直接跳过最优解,进入无限的训练中。解决的方法就是,学习率也需要随着训练的进行而变化
-
在进行模型训练的时候,有三个基础的概念
- EpochEpochEpoch:使用全部数据对模型进行一次完整训练,训练轮次
- Batch_sizeBatch\_sizeBatch_size:使用训练集中的小部分样本对模型权重进行以此反向传播的参数更新,每次训练每批次样本的数量(越大越好)
- IteraionIteraionIteraion:使用一个Batch数据对模型进行一次参数更新的过程
- eg.eg.eg.:假设数据集有500005000050000个训练样本,现在选择BatchSize=256Batch Size= 256BatchSize=256对模型进行训练,每个EpochEpochEpoch要训练的图片数量为500005000050000,训练集具有BatchBatchBatch个数为50000/256+1=19650000/256 + 1 = 19650000/256+1=196;每个EpochEpochEpoch具有的IterationIterationIteration个数为196196196;101010个EpochEpochEpoch具有的IterationIterationIteration个数为196019601960
-
在深度学习中,梯度下降的几种方式的根本区别就在于Batch Size不同
梯度下降方式 Training Set Size Batch Size Number of Batch BGD N N 1 SGD N 1 N Mini-Batch N B N / B + 1 - 注:N/ B+ 1是针对未整除的情况。整除则是 N/ B
- 在工作的时候通常使用的是 Mini-Batch
2.反向传播算法过程
(1)反向传播(BP算法)
- 前向传播:指的是数据输入的神经网络中,逐层向前传输,一直运算到输出层为止
- 反向传播(BackPropagation):利用损失函数ERROR,从后往前,结合梯度下降算法,依次求各个参数的偏导并进行参数更新
- 解释:
- 前向传播:获取预测结果
- 计算损失:交叉熵/MSE(先计算W3W_3W3,再计算W2W_2W2,最后计算W1W_1W1)
- 反向传播:利用梯度下降算法对参数进行更新
for _ in range(epochs):for train_x, train_y in dataloader:# 将一个batch的训练数据送入模型y_pred = model(train_x.type(torch.float32))# 计算损失值loss = criterion(y_pred, train_y, reshape(-1,1).type(torch.float32))total_loss += loss.item()train_sample += len(train_y)# 梯度清零optimizer.zero_grad()# 自动微分loss.backward()# 更新参数optimizer.step()
3.梯度下降的优化方法
-
梯度下降优化算法中,可能会碰到以下情况:
-
- 碰到平缓区域,梯度值较小,参数优化变慢
- 碰到鞍点,梯度为0,参数无法优化
- 碰到局部最小值,参数不是最优
-
对于这些问题,出现了一些对梯度下降算法的优化方法,例如:Momentum, AgaGrad,PMSprop,Adam等
(1)指数加权平均
- 指数移动加权平均:参考各数值,并且各数值的权重都不同,距离越远的数字对平均数计算的贡献就越小(权重较小),距离越近则对平均数的计算贡献就越大(权重越大)
- 计算公式:st={Y1,t=0β⋅st−1+(1−β)⋅Yt,t>0{s}_t = \begin{cases} \displaystyle Y_1, & t = 0 \\ \displaystyle \beta \cdot {s}_{t-1} + (1 - \beta) \cdot Y_t, & t > 0 \end{cases}st={Y1,β⋅st−1+(1−β)⋅Yt,t=0t>0
-
- StS_tSt表示指数加权平均值
- YtY_tYt表示ttt时刻的值
- β\betaβ调整权重参数,该值越大平均数越平缓
(2)动量算法Momentum
- 梯度计算公式:Dt=β⋅St−1+(1−β)⋅WtD_t = \beta \cdot S_{t - 1} + (1-\beta) \cdot W_tDt=β⋅St−1+(1−β)⋅Wt
- St−1S_{t - 1}St−1表示;表示梯度移动加权平均值
- WtW_tWt表示当前时刻的梯度值
- DtD_tDt为当前时刻的梯度值
- β\betaβ为权重系数
- 示例:权重β\betaβ为0.9, 则第一次梯度值S1=D1=W1S_1 = D_1 = W_1S1=D1=W1;第二次梯度值D2=S2=0.9×S1+W2×0.1D_2 = S_2 = 0.9 \times S_1 + W_2 \times0.1D2=S2=0.9×S1+W2×0.1;第三次梯度值D3=S3=0.9×S2+W3×0.1D_3 = S_3 = 0.9 \times S_2 + W_3 \times 0.1D3=S3=0.9×S2+W3×0.1;第四次梯度值D4=S4=0.9×S3+W4×0.1D_4 = S_4 = 0.9 \times S_3 + W_4 \times 0.1D4=S4=0.9×S3+W4×0.1
- 梯度下降公式中梯度的计算,就不再是当前时刻t的梯度值,而是历史梯度值的指数移动加权平均值,公式修改为:Wt+1=Wt−α⋅DtW_{t + 1} = W_t - \alpha \cdot D_tWt+1=Wt−α⋅Dt
拓展:Monmentum优化方法是如何一定程度上克服“平缓”,“鞍点”的问题呢?
- 当处于鞍点位置时,由于当前的梯度为0,参数无法更新。但是Momentum动量梯度下降算法已经在先前累计了一些梯度值,很有可能使得跨过鞍点
- 由于mini-batch普通的梯度下降算法,每次选取少数的样本梯度确定前进方向,可能会出现震荡,使得训练时间变长。Momentum使用移动加权平均,平滑了梯度的变化,使得前进方向更加平缓,有利于加快训练过程
"""加了动量之后的结果"""import torchw = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)loss = ((w ** 2)*0.5).sum()optimizer = torch.optim.SGD([w], lr = 0.01, momentum=0.9)optimizer.zero_grad()
loss.backward()
optimizer.step()print("第一次更新(加了动量之后)梯度:",w.grad)
print("第一次更新(加了动量之后)w:",w.detach())loss = ((w ** 2)*0.5).sum()
optimizer.zero_grad()
loss.backward()
optimizer.step()print("第二次更新(加了动量之后)梯度:",w.grad)
print("第二次更新(加了动量之后)w:",w.detach()) # tensor([0.9711])改变了,没加动量时 tensor([0.9801])"""tensor([0.9900])--->tensor([0.9711]) 减少得更多,更新得更加快一点"""
输出结果:
第一次更新(加了动量之后)梯度: tensor([1.])
第一次更新(加了动量之后)w: tensor([0.9900])
第二次更新(加了动量之后)梯度: tensor([0.9900])
第二次更新(加了动量之后)w: tensor([0.9711])
(3)AdaGrad
-
AdaGrad通过对不同的参数分量使用不同的学习率,AdaGrad的学习率总体会逐渐减小,计算步骤如下
-
初始化学习率α\alphaα,初始化参数θ\thetaθ(weight&bias)小常数σ=1e−6\sigma = 1e - 6σ=1e−6(放在分母上,防止分母为0)
-
初始化梯度累计变量s=0s = 0s=0
-
从训练集中采样mmm个样本的小批量,计算梯度ggg
-
累积平方梯度s=s+g⊙g{s} = {s} + {g} \odot {g}s=s+g⊙g,⊙\odot⊙表示各个分量相乘
-
学习率α\alphaα的计算公式如下:α=αs+σ\alpha = \frac{\alpha}{\sqrt s + \sigma}α=s+σα
-
参数更新公式如下:θ=θ−αs+σ⋅g\theta = \theta - \frac{\alpha}{\sqrt s + \sigma} \cdot gθ=θ−s+σα⋅g
-
重复2-4步骤,即可完成网络训练
- AdaGrad缺点:可能会使得学习率过早,过量的降低(学习率太小了,则更新速度变慢了,迭代相同次数就不能够到最优解),导致训练后期学习率大小较难找到最优解
import torchw = torch.tensor([1.0], requires_grad=True, dtype=torch.float32)
loss = ((w ** 2)*0.5).sum()optimizer = torch.optim.Adagrad([w], lr = 0.01)optimizer.zero_grad()
loss.backward()print("第一次更新 梯度:",w.grad) # 获取梯度的值
print("第一次更新 w:",w.detach()) # 获取w的值# 再次更新参数
loss = ((w ** 2)*0.5).sum()
optimizer.zero_grad()
loss.backward()
optimizer.step()print("第二次更新 梯度:",w.grad) # 获取梯度的值
print("第二次更新 w:",w.detach()) # 获取w的值
输出结果:
第一次更新 梯度: tensor([1.])
第一次更新 w: tensor([1.])
第二次更新 梯度: tensor([1.])
第二次更新 w: tensor([0.9900])
(4)RMSProp
-
RMSProp优化算法是对AdaGrad的优化,最主要的不同是,其使用指数移动加权平均梯度替换历史梯度的平方和。计算过程如下:
- 初始化学习率α\alphaα,初始化参数θ\thetaθ,小常数σ=1e−6\sigma = 1e-6σ=1e−6
- 初始化参数θ\thetaθ
- 初始化梯度累计变量sss
- 从训练集中采样mmm个样本的小批量,计算梯度ggg
- 使用指数移动平均累积历史梯度,公式如下:s=β⋅s+(1−β)g⊙gs = \beta \cdot s + (1 - \beta)g \odot gs=β⋅s+(1−β)g⊙g
- 学习率α\alphaα的计算公式如下:α=αs+σ\alpha = \frac{\alpha}{\sqrt s + \sigma}α=s+σα
- 参数更新公式如下:θ=θ−αs+σ⋅g\theta = \theta - \frac{\alpha}{\sqrt s + \sigma} \cdot gθ=θ−s+σα⋅g
import torchw = torch.tensor([0.1], requires_grad=True, dtype = torch.float32)
loss = ((w ** 2)*0.5).sum()optimizer = torch.optim.RMSprop([w], lr = 0.01, alpha = 0.9)optimizer.zero_grad()
loss.backward()print("第一次更新 梯度:", w.grad)
print("第一次更新 w:", w.detach())# 再次更新参数
loss = ((w ** 2)*0.5).sum()
optimizer.zero_grad()
loss.backward()
optimizer.step()print("第二次更新 梯度:",w.grad) # 获取梯度的值
print("第二次更新 w:",w.detach()) # 获取w的值
输出结果:
第一次更新 梯度: tensor([0.1000])
第一次更新 w: tensor([0.1000])
第二次更新 梯度: tensor([0.1000])
第二次更新 w: tensor([0.0684])
4.学习率衰减方法
- 通常是和 动量算法Momentum 组合在一起
- 后面学习中,通常使用指定间隔学习率衰减
(1)等间隔学习率衰减
import torch
import matplotlib.pyplot as plot# 参数初始化
LR = 0.1
iteration = 100
epochs = 200
# 网络数据初始化
x = torch.tensor([1.0])
w = torch.tensor([1.0], requires_grad = True)
y = torch.tensor([1.0])
# 优化器
optimizer = torch.optim.SGD([w], lr = LR, momentum=0.9)
# 学习率策略
scheduler_lr = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma = 0.8)
# 遍历轮次
epoch_list = []
lr_list = []
for epoch in range(epochs):lr_list.append(scheduler_lr.get_last_lr())epoch_list.append(epoch)# 遍历batchfor i in range(iteration):# 计算损失loss = ((w*x-y)**2)*0.5# 更新参数optimizer.zero_grad()loss.backward()optimizer.step()# 更新lrscheduler_lr.step()
# 绘制结果
plt.plot(epoch_list, lr_list)
plt.grid()
plt.show()
(2)指定间隔学习率衰减
import torch
import matplotlib.pyplot as plot# 参数初始化
LR = 0.1
iteration = 100
epochs = 200
# 网络数据初始化
x = torch.tensor([1.0])
w = torch.tensor([1.0], requires_grad = True)
y = torch.tensor([1.0])
# 优化器
optimizer = torch.optim.SGD([w], lr = LR, momentum=0.9)
# 学习率策略
scheduler_lr = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones = [20, 60, 90, 135, 180], gamma = 0.8)
# 遍历轮次
epoch_list = []
lr_list = []
for epoch in range(epochs):lr_list.append(scheduler_lr.get_last_lr())epoch_list.append(epoch)# 遍历batchfor i in range(iteration):# 计算损失loss = ((w*x-y)**2)*0.5# 更新参数optimizer.zero_grad()loss.backward()optimizer.step()# 更新lrscheduler_lr.step()
# 绘制结果
plt.plot(epoch_list, lr_list)
plt.grid()
plt.show()
(3)按指数学习率衰减
- 这种策略用得很少,一般不会选择
- gamma值通常是小于1,它是指 指数的底
- 调整方式:lr=lr⋅gammaepochlr = lr \cdot gamma^{epoch}lr=lr⋅gammaepoch
import torch
import matplotlib.pyplot as plot# 参数初始化
LR = 0.1
iteration = 100
epochs = 200
# 网络数据初始化
x = torch.tensor([1.0])
w = torch.tensor([1.0], requires_grad = True)
y = torch.tensor([1.0])
# 优化器
optimizer = torch.optim.SGD([w], lr = LR, momentum=0.9)
# 学习率策略
scheduler_lr = torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma = 0.8)
# 遍历轮次
epoch_list = []
lr_list = []
for epoch in range(epochs):lr_list.append(scheduler_lr.get_last_lr())epoch_list.append(epoch)# 遍历batchfor i in range(iteration):# 计算损失loss = ((w*x-y)**2)*0.5# 更新参数optimizer.zero_grad()loss.backward()optimizer.step()# 更新lrscheduler_lr.step()
# 绘制结果
plt.plot(epoch_list, lr_list)
plt.grid()
plt.show()