|
SGD
输入, 为样本个数 为特征数,对于一个样本 分类标签为,模型的输出:
损失函数为:
SGD缺点:
一个可塑性很好的容器,俯视图是一个圆形,当把它在x轴向进行拉伸,y方向不变,就会呈现x方向的z的变化很平滑的情况,也就是z在x方向的梯度变得很小,由图2也可以看出x方向的优化速度明显不如y方向的快
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
def fun(x, y):
return x ** 2 / 10 + y ** 2
fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-10, 10, 0.1)
Y = np.arange(-10, 10, 0.1)
X, Y = np.meshgrid(X, Y)
Z = fun(X, Y)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow')
ax.contourf(X, Y, Z, zdir='z', offset=-2, cmap='rainbow')
ax.set_xlabel('x', color='r')
ax.set_ylabel('y', color='g')
ax.set_zlabel('z', color='b')
plt.show()
X = np.arange(-10, 10, 1)
Y = np.arange(-10, 10, 2)
U, V = np.meshgrid(-X/5, -2*Y) # x的负梯度,y的负梯度,指向最优点,所以是负的
fig, ax = plt.subplots()
f = ax.quiver(X, Y, U, V)
ax.quiverkey(f, X=0.1, Y=1, U=10, label="key")
plt.xlabel("x")
plt.ylabel("y")
plt.show()
将x和y优化的进程可视化出来的效果如下:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
def fun(x, y):
return x ** 2 + 10 * y ** 2
def g(x, y):
return 2 * x , 20 * y
x, y = -200, 120
x_sgd, y_sgd = [x], [y]
a = 0.9
n_step = 100
lr = 0.08
x, y = -200, 120
for _ in range(n_step):
gx, g_y = g(x, y)
x -= gx * lr
y -= g_y * lr
x_sgd.append(x)
y_sgd.append(y)
fig = plt.figure()
X = np.arange(-240, 240, 0.1)
Y = np.arange(-130, 130, 0.1)
X, Y = np.meshgrid(X, Y)
Z = fun(X, Y)
plt.plot(x_sgd, y_sgd, color='red', marker='o', label='sgd')
plt.contourf(X, Y, Z, cmap='rainbow')
plt.legend(loc='best')
plt.show()
SGD可以看成是向着容器底部滑落,一开始梯度大,速度很快,随着逐渐接近最低点,速度最大,加速度最小,因此SGD理解为一种加速度逐渐减小的加速运动。很容易冲出最优点,因此效率低下。
Momentum
关于momentum的对于梯度更新的公式,网上有好多个版本,不同版本也只是两个正负号不同,并不是说他们写的不对。这里直接找了原论文的版本,只不过我这里换了符号名。实际上不管什么版本,最终得到的结果都是一样的。目的就是要综合考虑当前的梯度和历史的梯度情况,用历史的梯度对当前的梯度做修正。
SGD的问题是,更新方向完全依赖于当前的样本,梯度方向也完全依赖当前样本及其得到的误差,因此更新不稳定。引入momentum动量,就是在对当前样本进行梯度更新时,同时考虑历史的更新方向。就是指数平均的作用,将过去的更新方向和当前的梯度方向进行加权,Momentum梯度下降呈指数衰减。
直观点,就是原始SGD在梯度方向上直接做下降,现在把历史的梯度信息也考虑进来,因此优化时会有两个矢量相加的效果。下降方向变成了两个矢量方向的中间方向,万物皆可融合呗。
优点:
- 效率比SGD高
- 波动比SGD小
- 有摆脱局部最优的能力
x, y = -200, 120
x_momentum, y_momentum = [x], [y]
a = 0.9
v_x, v_y = 0, 0
n_step = 10
lr = 0.012
for _ in range(n_step):
g_x, g_y = g(x, y)
v_x = a * v_x - lr * g_x
v_y = a * v_y - lr * g_y
x += v_x
y += v_y
x_momentum.append(x)
y_momentum.append(y)
由图可见,Momentum比SGD的优化速度快了很多,同样是迭代10次,Momentum已经达到了最优附近,而SGD离得还很远。
Nesterov
由于momentum考虑了历史的梯度信息,可以加速优化的进程,但如果参数已经处于最优附近,很有可能会因为累积的梯度导致过大的动量,再一次远离最优。
因此Nesterov期望参数在快到最优解时,适当调整当前的优化距离。
更新方式:
首先按照上一步的梯度和参数,对进行一次预更新,预测当前时刻 如果按照之前的梯度会更新到哪里,参数是更加接近最优 还是会越过最优。并且求预更新之后的参数梯度,令其为. 如果预更新的参数越过了最优,那么参数梯度的方向是指向最优的,而且方向与是相反的,因此利用矢量相加,可以实现用对的大小方向做矫正的目的。
然后进行标准的梯度下降,完成本次迭代真正的梯度更新。
从下图中可以看出,NAG相比momentum先做了路径的调整,不过在当前实验条件下优化相同次数的效果 相差不大,这和损失函数的复杂程度有关。不过看其他的关于分类任务的实验报告以及论文来看,NAG也并不会比momentum有非常明显的提高。
x_nag, y_nag = [x], [y]
a = 0.9
nv_x, nv_y = 0, 0
n_step = 10
lr = 0.01
x, y = -200, 120
for _ in range(n_step):
x_tmp = x + a * nv_x
y_tmp = y + a * nv_y
g_x, g_y = g(x_tmp, y_tmp)
nv_x = a * nv_x - lr * g_x
nv_y = a * nv_y - lr * g_y
x += nv_x
y += nv_y
x_nag.append(x)
y_nag.append(y)Adagrad
从上图的优化路径可以看出SGD的一个问题,纵轴方向的优化速度较快,横轴方向的速度很慢,因为纵轴方向的梯度相对横轴的梯度大很多。Adagrad期望利用每个维度的梯度值大小动态调整每个维度的更新步长,就是让纵轴方向的更新慢一点或者不变,让横轴的更新快一点。Adagrad的更新方式:
Adagrad一直在累积梯度的平方和,然后将当作全局学习率的分母,从而实现动态调整学习率。这样做,如果优化前期的梯度很大,那么累积的梯度平方和会很大,放在分母上得到的更新步长就会很小,优化后期的更新步长就会变小;如果优化前期的梯度很小,那么累积的梯度平方和也会很小,更新步长就很大。
那么问题就来了,Adagrad一直在累积梯度的平方和,更新步长就会一直减小。如果早期梯度很大,导致更新步长迅速减小,很容易出现后期优化不动的情况,因为更新步长会逐渐趋向于0.如下图所示,Adagrad的优化是真的慢,我有点怀疑是不是我代码有问题。
x, y = -2, 1
x_adagrad, y_adagrad = [x], [y]
h_adagrad_x, h_adagrad_y = 0, 0
for _ in range(n_step):
g_x, g_y = g(x, y)
h_adagrad_x += g_x * g_x
h_adagrad_y += g_y * g_y
x -= lr / (math.sqrt(h_adagrad_x) + 1e-6) * g_x
y -= lr / (math.sqrt(h_adagrad_y) + 1e-6) * g_y
x_adagrad.append(x)
y_adagrad.append(y)Adadelta
Adadelta是对Adagrad的一个改进,没有选择将梯度的平方和全部累加,而是使用指数加权的形式对梯度进行累积。此外,为了实现加速更新进程的效果,Adadelta还维护了一个更新步长平方的指数加权。
可以理解为SGD的更新步长,而且可以看出这里不需要指定学习率超参数,避免对学习率敏感的问题。
相当于是可以自适应的学习率,而且带有Adagrad的特点,对于历史平均较大的梯度方向给予较小的更新步长,反之则给予较大的更新步长,加快更新速度。
但我做出的图,效果和Adagrad一样拉胯
x, y = -200, 120
x_adadelta, y_adadelta = [x], [y]
e_g_x, e_g_y = 0, 0
e_x, e_y = 0, 0
dx, dy = 0, 0
r = 0.9
eps = 1e-6
for _ in range(n_step):
g_x, g_y = g(x, y)
e_g_x = r * e_g_x + (1 - r) * g_x ** 2
e_g_y = r * e_g_y + (1 - r) * g_y ** 2
dx = -math.sqrt(e_x + eps) / math.sqrt(e_g_x + eps) * g_x
dy = -math.sqrt(e_y + eps) / math.sqrt(e_g_y + eps) * g_y
e_x = r * e_x + (1 - r) * dx ** 2
e_y = r * e_y + (1 - r) * dy ** 2
x += dx
y += dy
x_adadelta.append(x)
y_adadelta.append(y)RMSProp
RMSProp同样是对Adagrad的改进,相比Adadelta更加直观。只维护一个历史的梯度平方的指数加权,然后用其来影响当前的梯度
从图中看上去效果还不错,实际上我的迭代次数调整到了200次,不过发现一个有趣的事情,即使迭代次数到了400次,RMSProp也不会离开最优点。说明其学习率自动调整的效果非常不错的。
x, y = -200, 120
x_rmsprop, y_rmsprop = [x], [y]
e_g_x, e_g_y = 0, 0
beta = 0.9
eps = 1e-6
for _ in range(n_step):
g_x, g_y = g(x, y)
e_g_x = beta * e_g_x + (1 - beta) * g_x ** 2
e_g_y = beta * e_g_y + (1 - beta) * g_y ** 2
x += -g_x / math.sqrt(e_g_x + eps)
y += -g_y / math.sqrt(e_g_y + eps)
x_rmsprop.append(x)
y_rmsprop.append(y)Adam
Adam 相当于是RMSProp的动量版,既要RMSProp自适应学习率,又要加入动量加快优化。更新方式如下:
Adam认为在训练初期和初始为0,这样更新会存在偏差,因此采取矫正方法.令初始学习率为, 更新次数为次
x, y = -7.5, 2.5
x_adam, y_adam = [x], [y]
v_x, v_y = 0, 0
h_x, h_y = 0, 0
beta_1, beta_2 = 0.9, 0.999
lr_base = 0.9
eps = 1e-6
for n in range(1, n_step+1):
g_x, g_y = g(x, y)
v_x = beta_1 * v_x + (1 - beta_1) * g_x
v_y = beta_1 * v_y + (1 - beta_1) * g_y
h_x = beta_2 * h_x + (1 - beta_2) * g_x ** 2
h_y = beta_2 * h_y + (1 - beta_2) * g_y ** 2
lr = lr_base * math.sqrt((1 - beta_2 ** n) / (1 - beta_1 ** n))
x -= lr * v_x / math.sqrt(h_x + eps)
y -= lr * v_y / math.sqrt(h_y + eps)
x_adam.append(x)
y_adam.append(y)放上所有优化器的对比图
在此实验设置下Adam正常收敛,但是如果把初始位置放在离最优非常远的位置,效果就很拉跨,需要增加迭代次数到300次才能基本到最优
那么问题来了,一般情况Adam的效果比SGD + momentum效果更好,因为其学习率可以自适应,总结的结论一般就是adam设置的初始学习率并不敏感,可以在很广泛的区间内优化到一个较好的参数,而SGD+momentum则是在很窄的学习率区间内可以得到非常优秀的结果,在这个区间外的结果可能就比较差。
大致的分析一下Adam的初始参数为(-200, 120)的时候效果这么差,和通常的结论相差甚远。
首先来看下离最优较近的情况,这里做了300次迭代的和更新有关的9个参数的曲线,
其中,同理
可以发现一阶参数如在大约100次收敛至0,而二阶参数(开平方)呈现先急速上升后缓慢下降的趋势。
对比一下初始值离最优很远的情况,
可以观察到现象,两个的初始位置虽然差了大约两个数量级,导致对应的一阶、二阶参数也差了两个数量级,但是实际更新步长的处于同样的数量级,即学习率、和都没太大变化,所以要增大迭代次数才能有收敛的迹象。
另外,这个现象对这个问题给出一个可能的比较合理的解释:为什么在一些情况下,Adam总也收敛不了,而SGD+momentum总是能收敛。
这也可以看出随机初始化方法选择的重要性。
- [1] On the importance of initialization and momentum in deep learning
- [2] 比Momentum更快:揭开Nesterov Accelerated Gradient的真面目 - 知乎 (zhihu.com)
- [3] Deep Learning 最优化方法之AdaGrad - 知乎 (zhihu.com)
- [4] 如何理解Adam算法(Adaptive Momentum Estimation)? - 知乎 (zhihu.com)
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|