先放一张公式汇总图:
Contents
1 Momentum动量优化算法
1.1 梯度下降的问题
SGD方法的一个缺点是其更新方向完全依赖于当前batch计算出的梯度,因而十分不稳定。下图显示了在优化过程中x2方向的变化量远大于x1方向上的。
%matplotlib inline
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
import torch
eta = 0.4 # 学习率
def f_2d(x1, x2):
return 0.1 * x1 ** 2 + 2 * x2 ** 2
def gd_2d(x1, x2, s1, s2):
return (x1 - eta * 0.2 * x1, x2 - eta * 4 * x2, 0, 0)
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
epoch 20, x1 -0.943467, x2 -0.000073
# 略微增大学习率,x2方向已发散
eta=0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(gd_2d))
epoch 20, x1 -0.387814, x2 -1673.365109
1.2 动量法
Momentum算法借用了物理中的动量概念,使得参数更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。
其中,动量超参数γ满足0≤γ<1。当γ=0时,动量法等价于小批量随机梯度下降。
def momentum_2d(x1, x2, v1, v2):
v1 = gamma * v1 +eta * 0.2*x1
v2 = gamma * v2 +eta * 4*x2
return x1-v1, x2-v2, v1, v2
eta, gamma = 0.4, 0.5
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
epoch 20, x1 -0.062843, x2 0.001202
# 可以看到,在lr = 0.6时,使用动量法后x2方向不再发散
eta=0.6
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
epoch 20, x1 0.007188, x2 0.002553
features, labels = d2l.get_data_ch7()
def init_momentum_states():
v_w = torch.zeros((features.shape[-1],1 ),dtype = torch.float32)
v_b = torch.zeros(1,dtype = torch.float32)
return (v_w, v_b)
def sgd_momentum(params, states, hyperparams):
for p, v in zip(params, states):
v.data = hyperparams['momentum'] * v.data+hyperparams['lr']*p.grad.data
p.data -= v.data
d2l.train_ch7(sgd_momentum,init_momentum_states(), {'lr':0.02, 'momentum': 0.5 }, features, labels )
loss: 0.243766, 0.113696 sec per epoch
d2l.train_ch7(sgd_momentum,init_momentum_states(), {'lr':0.02, 'momentum': 0.9 }, features, labels )
loss: 0.257525, 0.084765 sec per epoch
d2l.train_ch7(sgd_momentum,init_momentum_states(), {'lr':0.004, 'momentum': 0.9 }, features, labels )
loss: 0.246566, 0.081785 sec per epoch
1.3 动量法的简洁实现
# 在PyTorch中,只需要通过参数momentum来指定动量超参数即可使用动量法。
d2l.train_pytorch_ch7(torch.optim.SGD, {'lr':0.004, 'momentum': 0.9 }, features, labels )
loss: 0.242601, 0.087179 sec per epoch
2 ADAGRA算法
2.1 算法简介
AdaGrad算法根据自变量在每个维度的梯度值的大小来调整各个维度上的学习率,从而避免统一的学习率难以适应所有维度的问题。
Adagrad算法能够在训练中自动的对learning rate进行调整,对于出现频率较低参数采用较大的α更新;相反,对于出现频率较高的参数采用较小的α更新。因此,Adagrad非常适合处理稀疏数据。
AdaGrad算法在迭代过程中不断调整学习率,并让目标函数自变量中每个元素都分别拥有自己的学习率。使用AdaGrad算法时,自变量中每个元素的学习率在迭代过程中一直在降低。
%matplotlib inline
import math
import torch
import sys
sys.path.append("..")
import d2lzh_pytorch as d2l
def adagrad_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4*x2, 1e-6
s1 += g1**2
s2 += g2**2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1*x1**2+2*x2**2
eta = 0.4
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1 -2.382563, x2 -0.158591
# 将学习率增大到2, 自变量更为迅速地逼近了最优解
eta = 2
d2l.show_trace_2d(f_2d, d2l.train_2d(adagrad_2d))
epoch 20, x1 -0.002295, x2 -0.000000
2.2 从零开始实现
# 根据AdaGrad算法中的公式实现该算法
features, labels = d2l.get_data_ch7()
def init_adagrad_states():
s_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
s_b = torch.zeros(1, dtype = torch.float32)
return (s_w, s_b)
def adagrad(params, states, hyperparams):
eps = 1e-6
for p, s in zip(params, states):
s.data +=(p.grad.data**2)
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
d2l.train_ch7(adagrad, init_adagrad_states(), {'lr':0.1}, features, labels)
loss: 0.243151, 0.089867 sec per epoch
2.3ADAGRAD简洁实现
# pytorch中提供名为Adagrad的优化器
d2l.train_pytorch_ch7(torch.optim.Adagrad, {'lr': 0.1}, features, labels)
loss: 0.243228, 0.133283 sec per epoch
3 RMSprop算法
3.1 算法简介
不同于AdaGrad算法里状态变量st是截至时间步t所有小批量随机梯度gt按元素平方和,RMSProp算法将梯度 按元素平方做指数加权移动平均,因此可缓解Adagrad算法学习率下降较快的问题。
def rmsprop_2d(x1, x2, s1, s2):
g1, g2, eps = 0.2 * x1, 4*x2, 1e-6
s1 = gamma*s1 + (1-gamma)* g1**2
s2 = gamma*s2 + (1-gamma)* g2**2
x1 -= eta / math.sqrt(s1 + eps) * g1
x2 -= eta / math.sqrt(s2 + eps) * g2
return x1, x2, s1, s2
def f_2d(x1, x2):
return 0.1*x1**2+2*x2**2
eta,gamma = 0.4, 0.9
d2l.show_trace_2d(f_2d, d2l.train_2d(rmsprop_2d))
epoch 20, x1 -0.010599, x2 0.000000
3.2 从零开始实现
按照RMSProp算法中的公式实现该算法
features, labels = d2l.get_data_ch7()
def init_rmsprop_states():
s_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
s_b = torch.zeros(1, dtype = torch.float32)
return (s_w, s_b)
def rmeprop(params, states, hyperparams):
gamma, eps = hyperparams['gamma'], 1e-6
for p, s in zip(params, states):
s.data = gamma * s.data + (1-gamma) * (p.grad.data**2)
p.data -= hyperparams['lr'] * p.grad.data / torch.sqrt(s + eps)
d2l.train_ch7(rmeprop, init_rmsprop_states(), {'lr':0.01, 'gamma':0.9}, features, labels)
loss: 0.242842, 0.127647 sec per epoch
3.3 RMSprop简洁实现
# pytorch中提供名为RMSprop的优化器
d2l.train_pytorch_ch7(torch.optim.RMSprop, {'lr': 0.01, 'alpha':0.9}, features, labels)
loss: 0.243309, 0.129653 sec per epoch
4 AdaDelta优化算法
4.1 算法简介
AdaDelta算法没有学习率这一超参数;
AdaDelta算法也像RMSProp算法一样,使用了 小批量随机梯度gt按元素平方的指数加权移动平均变量st。在时间步t=0,它的所有元素被初始化为0。给定超参数0≤ρ<1(对应RMSProp算法中的γ),在时间步t>0,同RMSProp算法一样计算
与RMSProp算法不同的是,AdaDelta算法还维护一个额外的状态变量Δxt,其元素同样在时间步0时被初始化为0。我们使用Δxt−1来计算自变量的变化量:
其中ϵ是为了维持数值稳定性而添加的常数,如10−5。接着更新自变量:
最后,我们使用Δxt来记录自变量变化量g′t按元素平方的指数加权移动平均:
可以看到,如不考虑ϵ的影响,AdaDelta算法跟RMSProp算法的不同之处在于 使用√ Δx t−1来替代学习率η。
4.2 从零开始实现
# 按AdaDelta算法中的公式实现该算法。
features, labels = d2l.get_data_ch7()
def init_adadelta_states():
s_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
s_b = torch.zeros(1, dtype = torch.float32)
delta_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
delta_b = torch.zeros(1, dtype = torch.float32)
return ((s_w, delta_w),(s_b, delta_b))
def adadelta(params, states, hyperparams):
rho, eps = hyperparams['rho'], 1e-5
for p, (s, delta) in zip(params, states):
s[:] = rho * s.data + (1-rho) * (p.grad.data**2)
g = p.grad.data * torch.sqrt((delta+eps)/(s+eps))
p.data -= g
delta[:] = rho*delta + (1-rho) * g**2
d2l.train_ch7(adadelta, init_adadelta_states(), {'rho':0.9}, features, labels)
loss: 0.242433, 0.185503 sec per epoch
4.3 AdaDelta的简洁实现
d2l.train_pytorch_ch7(torch.optim.Adadelta, { 'rho':0.9}, features, labels)
loss: 0.270904, 0.110704 sec per epoch
5 ADAM优化算法
5.1 算法简介
Adam(Adaptive Moment Estimation)是另一种自适应学习率的方法,它在RMSProp算法基础上对小批量随机梯度也做了指数加权移动平均,所以Adam算法可以看做是RMSProp算法与动量法的结合。
Adam算法还使用了偏差修正。
5.2 从零开始实现
# 按照Adam算法中的公式实现该算法。其中时间步t通过hyperparams参数传入adam函数
features, labels = d2l.get_data_ch7()
def init_adam_states():
v_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
v_b = torch.zeros(1, dtype = torch.float32)
s_w = torch.zeros((features.shape[1], 1), dtype = torch.float32)
s_b = torch.zeros(1, dtype = torch.float32)
return ((v_w, s_w),(v_b, s_b))
def adam(params, states, hyperparams):
beta1, beta2, eps = 0.9, 0.999, 1e-6
for p, (v, s) in zip(params, states):
v[:] = beta1 * v + (1-beta1) * p.grad.data
s[:] = beta2 * s + (1-beta2) * (p.grad.data**2)
v_bias_corr = v / (1-beta1**hyperparams['t'])
s_bias_corr = s / (1-beta2**hyperparams['t'])
g = hyperparams['lr'] * v_bias_corr / (torch.sqrt(s_bias_corr) + eps)
p.data -= g
hyperparams['t'] += 1
d2l.train_ch7(adam, init_adam_states(), {'lr':0.01, 't':1}, features, labels)
loss: 0.244003, 0.170542 sec per epoch
5.3 Adam简洁实现
d2l.train_pytorch_ch7(torch.optim.Adam, {'lr':0.01}, features, labels)
loss: 0.244895, 0.135288 sec per epoch
最后通过两张动图从直观上展现算法的优化过程:
1、下图为不同算法在损失平面等高线上随时间的变化情况:
2、下图为不同算法在鞍点处的行为比较:
动图参考来源点击这里
参考原文:《动手学深度学习(pyTorch)》
欢迎关注【OAOA】