简要介绍图中的优化算法,编程实现并2D可视化
1. 被优化函数 x 2 x^2 x2
from nndl7_optimization.op import Op
import torch
import numpy as np
from matplotlib import pyplot as plt
#优化函数
class OptimizedFunction(Op):
def __init__(self, w):
super(OptimizedFunction, self).__init__()
self.w = w
self.params = {'x': 0}
self.grads = {'x': 0}
def forward(self, x):
self.params['x'] = x
return torch.matmul(self.w.T, torch.tensor(torch.square(self.params['x']), dtype=torch.float32))
def backward(self):
self.grads['x'] = 2 * torch.multiply(self.w.T, self.params['x'])
#训练函数,记录梯度下降过程中每轮的参数x和损失
import copy
def train_f(model, optimizer, x_init, epoch):
"""
训练函数
输入:
- model:被优化函数
- optimizer:优化器
- x_init:x初始值
- epoch:训练回合数
"""
x = x_init
all_x = []
losses = []
for i in range(epoch):
all_x.append(copy.copy(x.numpy()))
loss = model(x)
losses.append(loss)
model.backward()
optimizer.step()
x = model.params['x']
print(all_x)
return torch.tensor(all_x), losses
#可视化函数,用于绘制更新轨迹
class Visualization(object):
def __init__(self):
"""
初始化可视化类
"""
# 只画出参数x1和x2在区间[-5, 5]的曲线部分
x1 = np.arange(-5, 5, 0.1)
x2 = np.arange(-5, 5, 0.1)
x1, x2 = np.meshgrid(x1, x2)
self.init_x = torch.tensor([x1, x2])
def plot_2d(self, model, x, fig_name):
"""
可视化参数更新轨迹
"""
fig, ax = plt.subplots(figsize=(10, 6))
cp = ax.contourf(self.init_x[0], self.init_x[1], model(self.init_x.transpose(0, 1)),
colors=['#e4007f', '#f19ec2', '#e86096', '#eb7aaa', '#f6c8dc', '#f5f5f5', '#000000'])
c = ax.contour(self.init_x[0], self.init_x[1], model(self.init_x.transpose(0, 1)), colors='black')
cbar = fig.colorbar(cp)
ax.plot(x[:, 0], x[:, 1], '-o', color='#000000')
ax.plot(0, 'r*', markersize=18, color='#fefefe')
ax.set_xlabel('$x1$')
ax.set_ylabel('$x2$')
ax.set_xlim((-2, 5))
ax.set_ylim((-2, 5))
plt.savefig(fig_name)
#训练模型并可视化参数更新轨迹
import numpy as np
def train_and_plot_f(model, optimizer, epoch, fig_name):
"""
训练模型并可视化参数更新轨迹
"""
# 设置x的初始值
x_init = torch.tensor([3, 4], dtype=torch.float32)
print('x1 initiate: {}, x2 initiate: {}'.format(x_init[0].numpy(), x_init[1].numpy()))
x, losses = train_f(model, optimizer, x_init, epoch)
print(x)
losses = np.array(losses)
# 展示x1、x2的更新轨迹
vis = Visualization()
vis.plot_2d(model, x, fig_name)
1.SGD
#1.SGD
from nndl7_optimization.op import SimpleBatchGD
# 固定随机种子
torch.manual_seed(0)
w = torch.tensor([0.2, 2])
model = OptimizedFunction(w)
opt = SimpleBatchGD(init_lr=0.2, model=model)
train_and_plot_f(model, opt, epoch=20, fig_name='opti-vis-para.pdf')
plt.show()
运行结果:
2.AdaGrad
#2.AdaGrad
from nndl7_optimization.op import Optimizer
class Adagrad(Optimizer):
def __init__(self, init_lr, model, epsilon):
"""
Adagrad 优化器初始化
输入:
- init_lr: 初始学习率
- model:模型,model.params存储模型参数值
- epsilon:保持数值稳定性而设置的非常小的常数
"""
super(Adagrad, self).__init__(init_lr=init_lr, model=model)
self.G = {}
for key in self.model.params.keys():
self.G[key] = 0
self.epsilon = epsilon
def adagrad(self, x, gradient_x, G, init_lr):
"""
adagrad算法更新参数,G为参数梯度平方的累计值。
"""
G += gradient_x ** 2
x -= init_lr / torch.sqrt(G + self.epsilon) * gradient_x
return x, G
def step(self):
"""
参数更新
"""
for key in self.model.params.keys():
self.model.params[key], self.G[key] = self.adagrad(self.model.params[key],
self.model.grads[key],
self.G[key],
self.init_lr)
# 固定随机种子
torch.manual_seed(0)
w = torch.tensor([0.2, 2])
model = OptimizedFunction(w)
opt = Adagrad(init_lr=0.5, model=model, epsilon=1e-7)
train_and_plot_f(model, opt, epoch=50, fig_name='opti-vis-para2.pdf')
plt.show()
运行结果:
3.RMSprop
#3.RMSprop
class RMSprop(Optimizer):
def __init__(self, init_lr, model, beta, epsilon):
"""
RMSprop优化器初始化
输入:
- init_lr:初始学习率
- model:模型,model.params存储模型参数值
- beta:衰减率
- epsilon:保持数值稳定性而设置的常数
"""
super(RMSprop, self).__init__(init_lr=init_lr, model=model)
self.G = {}
for key in self.model.params.keys():
self.G[key] = 0
self.beta = beta
self.epsilon = epsilon
def rmsprop(self, x, gradient_x, G, init_lr):
"""
rmsprop算法更新参数,G为迭代梯度平方的加权移动平均
"""
G = self.beta * G + (1 - self.beta) * gradient_x ** 2
x -= init_lr / torch.sqrt(G + self.epsilon) * gradient_x
return x, G
def step(self):
"""参数更新"""
for key in self.model.params.keys():
self.model.params[key], self.G[key] = self.rmsprop(self.model.params[key],
self.model.grads[key],
self.G[key],
self.init_lr)
# 固定随机种子
torch.manual_seed(0)
w = torch.tensor([0.2, 2])
model = OptimizedFunction(w)
opt = RMSprop(init_lr=0.1, model=model, beta=0.9, epsilon=1e-7)
train_and_plot_f(model, opt, epoch=50, fig_name='opti-vis-para3-RMSprop.pdf')
plt.show()
运行结果:
4.Momentum
#4.Momentum
class Momentum(Optimizer):
def __init__(self, init_lr, model, rho):
"""
Momentum优化器初始化
输入:
- init_lr:初始学习率
- model:模型,model.params存储模型参数值
- rho:动量因子
"""
super(Momentum, self).__init__(init_lr=init_lr, model=model)
self.delta_x = {}
for key in self.model.params.keys():
self.delta_x[key] = 0
self.rho = rho
def momentum(self, x, gradient_x, delta_x, init_lr):
"""
momentum算法更新参数,delta_x为梯度的加权移动平均
"""
delta_x = self.rho * delta_x - init_lr * gradient_x
x += delta_x
return x, delta_x
def step(self):
"""参数更新"""
for key in self.model.params.keys():
self.model.params[key], self.delta_x[key] = self.momentum(self.model.params[key],
self.model.grads[key],
self.delta_x[key],
self.init_lr)
# 固定随机种子
torch.manual_seed(0)
w = torch.tensor([0.2, 2])
model = OptimizedFunction(w)
opt = Momentum(init_lr=0.1, model=model, rho=0.9)
train_and_plot_f(model, opt, epoch=50, fig_name='opti-vis-para4-Momentum.pdf')
plt.show()
运行结果:
5.Adam
#5.Adam
class Adam(Optimizer):
def __init__(self, init_lr, model, beta1, beta2, epsilon):
"""
Adam优化器初始化
输入:
- init_lr:初始学习率
- model:模型,model.params存储模型参数值
- beta1, beta2:移动平均的衰减率
- epsilon:保持数值稳定性而设置的常数
"""
super(Adam, self).__init__(init_lr=init_lr, model=model)
self.beta1 = beta1
self.beta2 = beta2
self.epsilon = epsilon
self.M, self.G = {}, {}
for key in self.model.params.keys():
self.M[key] = 0
self.G[key] = 0
self.t = 1
def adam(self, x, gradient_x, G, M, t, init_lr):
"""
adam算法更新参数
输入:
- x:参数
- G:梯度平方的加权移动平均
- M:梯度的加权移动平均
- t:迭代次数
- init_lr:初始学习率
"""
M = self.beta1 * M + (1 - self.beta1) * gradient_x
G = self.beta2 * G + (1 - self.beta2) * gradient_x ** 2
M_hat = M / (1 - self.beta1 ** t)
G_hat = G / (1 - self.beta2 ** t)
t += 1
x -= init_lr / torch.sqrt(G_hat + self.epsilon) * M_hat
return x, G, M, t
def step(self):
"""参数更新"""
for key in self.model.params.keys():
self.model.params[key], self.G[key], self.M[key], self.t = self.adam(self.model.params[key],
self.model.grads[key],
self.G[key],
self.M[key],
self.t,
self.init_lr)
# 固定随机种子
torch.manual_seed(0)
w = torch.tensor([0.2, 2])
model = OptimizedFunction(w)
opt = Adam(init_lr=0.2, model=model, beta1=0.9, beta2=0.99, epsilon=1e-7)
train_and_plot_f(model, opt, epoch=20, fig_name='opti-vis-para5-Adam.pdf')
plt.show()
运行结果:
2. 被优化函数 1 20 x 2 + y 2 \frac{1}{20}x^2+y^2 201x2+y2
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
class SGD:
"""随机梯度下降法(Stochastic Gradient Descent)"""
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr * grads[key]
class Momentum:
"""Momentum SGD"""
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum * self.v[key] - self.lr * grads[key]
params[key] += self.v[key]
class Nesterov:
"""Nesterov's Accelerated Gradient (http://arxiv.org/abs/1212.0901)"""
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] *= self.momentum
self.v[key] -= self.lr * grads[key]
params[key] += self.momentum * self.momentum * self.v[key]
params[key] -= (1 + self.momentum) * self.lr * grads[key]
class AdaGrad:
"""AdaGrad"""
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
class RMSprop:
"""RMSprop"""
def __init__(self, lr=0.01, decay_rate=0.99):
self.lr = lr
self.decay_rate = decay_rate
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] *= self.decay_rate
self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
class Adam:
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter)
for key in params.keys():
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key] ** 2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
def f(x, y):
return x ** 2 / 20.0 + y ** 2
def df(x, y):
return x / 10.0, 2.0 * y
init_pos = (-7.0, 2.0)
params = {}
params['x'], params['y'] = init_pos[0], init_pos[1]
grads = {}
grads['x'], grads['y'] = 0, 0
learningrate = [0.9, 0.3, 0.3, 0.6, 0.6, 0.6, 0.6]
optimizers = OrderedDict() #一个有序的字典
optimizers["SGD"] = SGD(lr=learningrate[0])
optimizers["Momentum"] = Momentum(lr=learningrate[1])
optimizers["Nesterov"] = Nesterov(lr=learningrate[2])
optimizers["AdaGrad"] = AdaGrad(lr=learningrate[3])
optimizers["RMSprop"] = RMSprop(lr=learningrate[4])
optimizers["Adam"] = Adam(lr=learningrate[5])
idx = 1
id_lr = 0
for key in optimizers:
optimizer = optimizers[key]
lr = learningrate[id_lr]
id_lr = id_lr + 1
x_history = []
y_history = []
params['x'], params['y'] = init_pos[0], init_pos[1]
for i in range(30):
x_history.append(params['x'])
y_history.append(params['y'])
grads['x'], grads['y'] = df(params['x'], params['y'])
optimizer.update(params, grads)
x = np.arange(-10, 10, 0.01)
y = np.arange(-5, 5, 0.01)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
# for simple contour line
mask = Z > 7
Z[mask] = 0 #这个操作可以使高度大于 7 的点在绘制等高线时被忽略掉,只画出高度小于等于 7 的部分,从而得到一个简单的等高线图。
# plot
plt.subplot(2, 3, idx)
idx += 1
plt.plot(x_history, y_history, 'o-',markersize=4, color="r")
# plt.contour(X, Y, Z) # 绘制等高线
plt.contour(X, Y, Z, cmap='rainbow') # 颜色填充
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.plot(0, 0, '+')
# plt.axis('off')
# plt.title(key+'\nlr='+str(lr), fontstyle='italic')
plt.text(0, 10, key + '\nlr=' + str(lr), fontsize=10, color="b",
verticalalignment='top', horizontalalignment='center', fontstyle='italic')
plt.xlabel("x")
plt.ylabel("y")
plt.subplots_adjust(wspace=0, hspace=0) # 调整子图间距
plt.show()
这个实验中RMSprop的效果比较好,迭代了几次就到终点了。
可能在别的模型中别的优化算法表现也会比较好。不同模型需要选择适当的优化算法。
3. 解释不同轨迹的形成原因,分析各个算法的优缺点
1)小批量随机梯度下降
这种算法,每次更新的方向就是在该点的负梯度方向,学习率是人为给定的一个固定值,如果过大就不会收敛,如果过小则收敛速度太慢;而且如果学习率是固定值的话,可能在靠近模型最优点时会发生振荡。公式如下: θ t = θ t − 1 + Δ θ \theta _{t}=\theta _{t-1}+\varDelta \theta θt=θt−1+Δθ Δ θ = − g t \varDelta \theta = -g_t Δθ=−gt 正确的学习率,应该是在开始的时候较大,这样可以加快模型收敛;越往后学习率应该越小,这样方便收敛到极值点。于是出现了AdaGrad算法。
2)AdaGrad(Adaptive Gradient,自适应梯度下降)
看名字就可以知道,自适应梯度下降,这个算法的学习率不是固定的,随着迭代次数的增加,学习率越来越小。那么学习率是按怎样的方式减小呢?公式如下:
θ
t
=
θ
t
−
1
+
Δ
θ
\theta _t=\theta _{t-1}+\varDelta \theta
θt=θt−1+Δθ
Δ
θ
=
−
α
G
t
+
ε
⊙
g
t
\varDelta \theta =-\frac{\alpha}{\sqrt{G_t+\varepsilon}}\odot \boldsymbol{g}_{\boldsymbol{t}}
Δθ=−Gt+εα⊙gt
G
t
=
∑
τ
=
1
t
g
t
⊙
g
t
(
⊙
是逐元素积
)
G_t=\sum_{\tau =1}^t{\boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}}}\ \ \left( \odot \text{是逐元素积} \right)
Gt=τ=1∑tgt⊙gt (⊙是逐元素积) 在第𝑡 次迭代时,先计算每个参数梯度平方的累计值。可以看到,在 AdaGrad 算法中,如果某个参数的偏导数累积比较大,其学习率相对较小;相反,如果其偏导数累积较小,其学习率相对较大.但整体是随着迭代次数的增加,学习率逐渐缩小。
AdaGrad 算法的缺点是在经过一定次数的迭代依然没有找到最优点时,由于这时的学习率已经非常小,很难再继续找到最优点.
这种算法仍然有一个问题,就是历史梯度大小对学习率衰减的贡献率是相同的,但是在现实中,可能很久之前的梯度对本次学习率的影响不是很大,离本次迭代越久远的梯度大小可能对本次学习率的影响越小,我们希望历史梯度大小对本次学习率的影响应该是不一样的。于是出现了RMSprop算法。
3)RMSprop算法
RMSprop算法用到了指数移动平均,是一种自适应学习率的方法,可以在有些情况下避免 AdaGrad 算法中学习率不断单调下降以至于过早衰减的缺点.公式: Δ θ t = − α G t + ε ⊙ g t \varDelta \theta _t=-\frac{\alpha}{\sqrt{G_t+\varepsilon}}\odot \boldsymbol{g}_{\boldsymbol{t}} Δθt=−Gt+εα⊙gt G t = β G t − 1 + ( 1 − β ) g t ⊙ g t , β = 0.9 G_t=\beta G_{t-1}+\left( 1-\beta \right) \boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}}\text{,}\beta =0.9 Gt=βGt−1+(1−β)gt⊙gt,β=0.9 这种算法仍然有缺点,什么缺点呢?就是这个初始学习率是人为指定的,是个固定值,这样的效果并不好,我们希望他是能够动态变化的。于是出现了AdaDelta算法。
4)AdaDelta
AdaDelta 算法通过梯度平方的指数衰减移动平均来调整学习率.此外,AdaDelta算法还引入了每次参数更新差值Δ𝜃的平方的指数衰减权移动平均.
Δ
θ
=
−
Δ
X
t
−
1
2
+
ε
G
t
+
ε
g
t
\varDelta \theta =-\frac{\sqrt{\varDelta X_{t-1}^{2}+\varepsilon}}{\sqrt{G_t+\varepsilon}}g_t
Δθ=−Gt+εΔXt−12+εgt
Δ
X
t
−
1
2
=
β
1
Δ
X
t
−
2
2
+
(
1
−
β
1
)
Δ
θ
t
−
1
Δ
θ
t
−
1
\varDelta X_{t-1}^{2}=\beta _1\varDelta X_{t-2}^{2}+\left( 1-\beta _1 \right) \varDelta \theta _{t-1}\varDelta \theta _{t-1}
ΔXt−12=β1ΔXt−22+(1−β1)Δθt−1Δθt−1
G
t
=
β
G
t
−
1
+
(
1
−
β
)
g
t
⊙
g
t
G_t=\beta G_{t-1}+\left( 1-\beta \right) g_t\odot g_t
Gt=βGt−1+(1−β)gt⊙gtAdaDelta算法将RMSprop算法中的初始学习率𝛼改为动态计算的
Δ
X
t
−
1
2
\sqrt{\varDelta X_{t-1}^{2}}
ΔXt−12 ,在一定程度上平抑了学习率的波动。
上面的算法都是对学习率做了一个改进,还能改进什么呢?当然是梯度啦!
因为当前梯度下降的方向不一定是最优的方向,因此我们可以尝试对梯度的方向做一个修正。
在随机(小批量)梯度下降法中,如果每次选取样本数量比较小,损失会呈现振荡的方式下降.也就是说,随机梯度下降方法中每次迭代的梯度估计和整个训练集上的最优梯度并不一致,具有一定的随机性.一种有效地缓解梯度估计随机性的方式是通过使用最近一段时间内的平均梯度来代替当前时刻的随机梯度来作为参数更新的方向,从而提高优化速度*.
5)动量法(Momentum)
动量法就是计算负梯度的“加权移动平均”作为参数的更新方向,公式如下:
Δ
θ
t
=
ρ
Δ
θ
t
−
1
−
α
g
t
=
−
α
∑
τ
=
1
t
ρ
t
−
τ
g
τ
,
ρ
=
0.9
\varDelta \theta _t=\rho \varDelta \theta _{t-1}-\alpha \boldsymbol{g}_{\boldsymbol{t}}=-\alpha \sum_{\tau =1}^t{\rho ^{t-\tau}\boldsymbol{g}_{\boldsymbol{\tau }}} ,\rho=0.9
Δθt=ρΔθt−1−αgt=−ατ=1∑tρt−τgτ,ρ=0.9 可以看出,真实梯度,是由上一次的梯度更新量(大小和方向)和当前梯度(大小和方向)共同决定的。
这样,每个参数的实际更新差值取决于最近一段时间内梯度的加权平均值.当某个参数在最近一段时间内的梯度方向不一致时,其真实的参数更新幅度变小;相反,当在最近一段时间内的梯度方向都一致时,其真实的参数更新幅度变大,起到加速作用.一般而言,在迭代初期,梯度方向都比较一致,动量法会起到加速作用,可以更快地到达最优点.在迭代后期,梯度方向会不一致,在收敛值附近振荡,动量法会起到减速作用,增加稳定性。
可以看到上面都是参考了历史梯度对参数进行更新的,那思考一下,能不能用参考未来的梯度方向对当前梯度方向进行更新呢?参考未来?现在梯度还没有呢,咋参考未来呢?可能就有人会疑惑了。接下来进入Nesterov算法,看看是怎么参考未来的梯度的。
6)Nesterov算法
公式:
Δ
θ
t
=
ρ
Δ
θ
t
−
1
−
α
g
t
(
θ
t
−
1
+
ρ
Δ
θ
t
−
1
)
\varDelta \theta _t=\rho \varDelta \theta _{t-1}-\alpha \boldsymbol{g}_{\boldsymbol{t}}\left( \boldsymbol{\theta }_{\boldsymbol{t}-1}+\boldsymbol{\rho \Delta \theta }_{\boldsymbol{t}-1} \right)
Δθt=ρΔθt−1−αgt(θt−1+ρΔθt−1) 可以看出,真实梯度,是由上一次的梯度更新量(大小和方向)和下一次梯度(大小和方向)共同决定的。
g
t
(
θ
t
−
1
+
ρ
Δ
θ
t
−
1
)
\boldsymbol{g}_{\boldsymbol{t}}\left( \boldsymbol{\theta }_{\boldsymbol{t}-1}+\boldsymbol{\rho \Delta \theta }_{\boldsymbol{t}-1} \right)
gt(θt−1+ρΔθt−1)就是在
θ
t
−
1
\theta _{t-1}
θt−1 处按照上一次的方向往前走了一步,变成了
θ
t
−
1
+
ρ
Δ
θ
t
−
1
\theta _{t-1}+\rho \varDelta \theta _{t-1}
θt−1+ρΔθt−1 ,我们在上一个点
θ
t
−
1
\theta _{t-1}
θt−1按照上一次的方向
Δ
θ
t
−
1
\varDelta \theta _{t-1}
Δθt−1先算出来一个下一个点
θ
t
−
1
+
ρ
Δ
θ
t
−
1
\theta _{t-1}+\rho \varDelta \theta _{t-1}
θt−1+ρΔθt−1 ,然后求出来在这个点的梯度。用
g
t
(
θ
t
−
1
+
ρ
Δ
θ
t
−
1
)
\boldsymbol{g}_{\boldsymbol{t}}\left( \boldsymbol{\theta }_{\boldsymbol{t}-1}+\boldsymbol{\rho \Delta \theta }_{\boldsymbol{t}-1} \right)
gt(θt−1+ρΔθt−1)代替之前公式中的
g
t
g_t
gt。
优化学习率、优化梯度方向都能使收敛效果变好,那我们尝试把他们结合起来,这就出来了Adam算法。
7)Adam算法
Adam算法可以看作动量法和 RMSprop 算法的结合,不但使用动量作为参数更新方向,而且可以自适应调整学习率。公式:
Δ
θ
=
−
α
G
t
^
+
ε
M
t
^
\varDelta \theta =-\frac{\alpha}{\sqrt{\widehat{G_t}+\varepsilon}}\widehat{M_t}
Δθ=−Gt
+εαMt
M
^
=
M
t
1
−
β
1
t
,
G
t
^
=
G
t
1
−
β
2
t
\widehat{M}=\frac{M_t}{1-\beta _{1}^{t}},\ \ \widehat{G_t}=\frac{G_t}{1-\beta _{2}^{t}}
M
=1−β1tMt, Gt
=1−β2tGt
M
t
=
β
1
M
t
−
1
+
(
1
−
β
1
)
g
t
M_t=\beta _1M_{t-1}+\left( 1-\beta _1 \right) \boldsymbol{g}_{\boldsymbol{t}}
Mt=β1Mt−1+(1−β1)gt
G
t
=
β
2
G
t
−
1
+
(
1
−
β
2
)
g
t
⊙
g
t
G_t=\beta _2G_{t-1}+\left( 1-\beta _2 \right) \boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}}
Gt=β2Gt−1+(1−β2)gt⊙gt 其中通常
β
1
=
0.9
,
β
2
=
0.99
,
α
0.001
\beta _1=0.9\text{,}\beta _2=0.99,\alpha0.001
β1=0.9,β2=0.99,α0.001 。
Adam 算法一方面计算梯度平方的指数加权平均(和 RMSprop 算法类似),另一方面计算梯度g𝑡 的指数加权平均(和动量法类似).
这几种算法,我按照自己的理解给串下来了,我认为这样方便记忆。
最后总结一下:
算法 | 优点 | 缺点 | 适用情况 |
---|---|---|---|
批量梯度下降(BGD) | 目标函数为凸函数时,可以找到全局最优值 | 收敛速度慢,需要用到全部数据,内存消耗大 | 不适用于大数据集,不能在线更新模型 |
随机梯度下降 (SGD) | 收敛速度加快 | 更新值的方差较大,收敛过程会产生波动,可能落入极小值,选择合适的学习率比较困难 | 适用于需要在线更新的模型,适用于大规模训练样本情况 |
小批量梯度下降 (MSGD) | 降低更新值的方差,收敛较为稳定 | 选择合适的学习率比较困难 | |
A d a G r a d AdaGrad AdaGrad Δ θ = − α G t + ε ⊙ g t \varDelta \theta =-\frac{\alpha}{\sqrt{G_t+\varepsilon}}\odot \boldsymbol{g}_{\boldsymbol{t}} Δθ=−Gt+εα⊙gt G t = ∑ τ = 1 t g t ⊙ g t G_t=\sum_{\tau =1}^t{\boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}}} Gt=τ=1∑tgt⊙gt | 不需要对每个学习率手工地调节 | 仍依赖于人工设置一个全局学习率,学习率设置过大,对梯度的调节太大。中后期,学习率接近于0,很难继续寻优 | 稀疏数据;需要快速收敛,训练复杂网络时 |
R M S p r o p RMSprop RMSprop Δ θ = − α G t + ε ⊙ g t \varDelta \theta =-\frac{\alpha}{\sqrt{G_t+\varepsilon}}\odot \boldsymbol{g}_{\boldsymbol{t}} Δθ=−Gt+εα⊙gt G t = β G t − 1 + ( 1 − β ) g t ⊙ g t , G_t=\beta G_{t-1}+\left( 1-\beta \right) \boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}}, Gt=βGt−1+(1−β)gt⊙gt, β = 0.9 \ \beta =0.9 β=0.9 | 在迭代过程中,每个参数的学习率并不是呈衰减趋势,既可以变小也可以变大 | 依然依赖于全局学习率 | 需要快速收敛,训练复杂网络时;适合处理非平稳目标 - 对于RNN效果很好 |
A d a D e l t a AdaDelta AdaDelta Δ θ = − Δ X t − 1 2 + ε G t + ε g t \varDelta \theta =-\frac{\sqrt{\varDelta X_{t-1}^{2}+\varepsilon}}{\sqrt{G_t+\varepsilon}}g_t Δθ=−Gt+εΔXt−12+εgt Δ X t − 1 2 = β 1 Δ X t − 2 2 + ( 1 − β 1 ) Δ θ t − 1 Δ θ t − 1 \varDelta X_{t-1}^{2}=\beta _1\varDelta X_{t-2}^{2}+\left( 1-\beta _1 \right) \varDelta \theta _{t-1}\varDelta \theta _{t-1} ΔXt−12=β1ΔXt−22+(1−β1)Δθt−1Δθt−1 G t = β G t − 1 + ( 1 − β ) g t ⊙ g t G_t=\beta G_{t-1}+\left( 1-\beta \right) g_t\odot g_t Gt=βGt−1+(1−β)gt⊙gt | 不需要预设一个默认学习率,训练初中期,加速效果不错,很快,可以避免参数更新时两边单位不统一的问题。 | 训练后期,反复在局部最小值附近抖动 | 需要快速收敛,训练复杂网络时 |
动量法( M o m e n t u m ) \text{动量法(}Momentum\text{)} 动量法(Momentum) Δ θ t = ρ Δ θ t − 1 − α g t \varDelta \theta _t=\rho \varDelta \theta _{t-1}-\alpha \boldsymbol{g}_{\boldsymbol{t}} Δθt=ρΔθt−1−αgt α = 0.9 \alpha =0.9 α=0.9 | 在迭代初期 起到加速作用;在迭代后期,起到减速作用,增加稳定性 | 需要人工设定学习率 | 适用于有可靠的初始化参数 |
N e s t e r o v Nesterov Nesterov Δ θ = ρ Δ θ − α g t ( θ t − 1 + ρ Δ θ t − 1 ) \varDelta \theta =\rho \varDelta \theta -\alpha g_t\left( \theta _{t-1}+\rho \varDelta \theta _{t-1} \right) Δθ=ρΔθ−αgt(θt−1+ρΔθt−1) | 梯度在大的跳跃后,进行计算对当前梯度进行校正 | 需要人工设定学习率 | |
A d a m Adam Adam Δ θ = − α G t ^ + ε M t ^ \varDelta \theta =-\frac{\alpha}{\sqrt{\widehat{G_t}+\varepsilon}}\widehat{M_t} Δθ=−Gt +εαMt M ^ = M t 1 − β 1 t , G t ^ = G t 1 − β 2 t \widehat{M}=\frac{M_t}{1-\beta _{1}^{t}},\ \ \widehat{G_t}=\frac{G_t}{1-\beta _{2}^{t}} M =1−β1tMt, Gt =1−β2tGt M t = β 1 M t − 1 + ( 1 − β 1 ) g t M_t=\beta _1M_{t-1}+\left( 1-\beta _1 \right) \boldsymbol{g}_{\boldsymbol{t}} Mt=β1Mt−1+(1−β1)gt G t = β 2 G t − 1 + ( 1 − β 2 ) g t ⊙ g t G_t=\beta _2G_{t-1}+\left( 1-\beta _2 \right) \boldsymbol{g}_{\boldsymbol{t}}\odot \boldsymbol{g}_{\boldsymbol{t}} Gt=β2Gt−1+(1−β2)gt⊙gt | 自适应学习率;加速收敛;对稀疏梯度友好;参数具有较好的鲁棒性 | 内存需求较高;对小批量样本敏感;需要调节超参数 | 需要快速收敛,训练复杂网络时;善于处理稀疏梯度和处理非平稳目标的优点,也适用于大多非凸优化 - 适用于大数据集和高维空间 |
参考:
“随机梯度下降、牛顿法、动量法、Nesterov、AdaGrad、RMSprop、Adam”-b站
SGD ,Adam,momentum等优化算法比较-csdn
深度学习优化函数详解(4)-- momentum 动量法
深度学习优化函数详解(5)-- Nesterov accelerated gradient (NAG)