动量(momentum)
简述
之前学习的用梯度更新参数
w
w
w的公式:
w
k
+
1
=
w
k
−
α
⋅
∇
f
(
w
k
)
w_{k+1}=w_k - \alpha \cdot \nabla f(w_k)
wk+1=wk−α⋅∇f(wk)
其中
α
\alpha
α是学习率。
现用
z
k
+
1
=
β
⋅
z
k
+
∇
f
(
w
k
)
w
k
+
1
=
w
k
−
α
⋅
z
k
+
1
z_{k+1}=\beta \cdot z_k + \nabla f(w_k) \\ w_{k+1}=w_k - \alpha \cdot z_{k+1}
zk+1=β⋅zk+∇f(wk)wk+1=wk−α⋅zk+1
来代替。即相当于原式子又减去了一个
α
⋅
β
⋅
z
k
\alpha \cdot \beta \cdot z_k
α⋅β⋅zk这一项。其中
z
z
z表征了上次更新的方向和大小,而
∇
f
(
w
)
\nabla f(w)
∇f(w)则是梯度,所以新的式子综合考虑了梯度下降的方向和上次更新的方向,用
β
\beta
β来决定了惯性的大小。
优点
(1)不仅考虑了当前的梯度下降的方向,还综合考虑了上次更新的情况,使得学习器学习过程的幅度和角度不会太尖锐,特别是遇到来回抖动的情况时大有好转。
(2)当遇到局部极小值时,因为设置了惯性,所以可能可以冲出局部最小值,这样就更可能找到一个全局最小值。
使用
某些优化器(如Adam)内置了momentum,所以没有这个参数,对多数优化器直接设置就可以了,传进去的参数是 β \beta β的取值。其取值越大则考虑之前的更新方向的程度就越大,取值为0时即相当于没有惯性。
optimizer = optim.SGD(net.parameters(), lr=learning_rate, momentum=0.78)
这里试了一下,仅添加了这个动量的参数,没加正则化项,最后得到的performance比之前好:
Test set: Average loss: 0.0009, Accuracy: 9475.0/10000 (95%)
Learning Rate Decay
在使用梯度更新参数时,学习率如果选取太小的话,学习过程会很慢;’如果学习率选取太大,那么很可能会出现来回抖动的情况,这时最终的模型可能很难收敛,而且看起来和"梯度弥散"的情况也很像(但其实不是)。
选取合适的固定的学习率是很难的, 可以在训练的一开始选取比较大的学习率加快训练过程,然后逐渐让其衰减到比较小的值,最终让模型收敛。
ReduceLROnPlateau
Plateau是平原的意思,这个类即用来监控某个量,当其在训练过程中多次没有发生下降(或上升,取决于mode
参数)时,就减少学习率。首先,在定义优化器时:
from torch.optim.lr_scheduler import ReduceLROnPlateau
optimizer = ......
# 传入优化器让学习率受其管理,当连续200次没有减少loss时就会减少学习率(乘以0.7)
scheduler = ReduceLROnPlateau(optimizer, mode="min", patience=200, factor=0.7)
在每次训练时,改用这个scheduler
来执行.step()
方法,并传入要监控的量,这里即是传入计算出的loss:
optimizer.step()
# 传入要监控的量,每调用一次step就会监控一次loss
# 前面定义时的mode="min"即是监控量没有减少时触发减小lr的操作的
scheduler.step(loss)
在前面(带动量)的基础上运行了一下,performance没有什么提升,可能是有一点over-fitting了:
Test set: Average loss: 0.0011, Accuracy: 9353.0/10000 (94%)
StepLR
这个就和监控量没关系了,是固定的每执行step_size
个.step()
方法,就把学习率乘以gamma
即可。
定义优化器时:
from torch.optim.lr_scheduler import StepLR
optimizer = ......
# 每跑800个step就把学习率乘以0.9
scheduler = StepLR(optimizer, step_size=800, gamma=0.9)
训练时(注意不用传监控量进去了):
optimizer.step()
scheduler.step()
运行结果:
Test set: Average loss: 0.0010, Accuracy: 9411.0/10000 (94%)