1. 编程实现图6-1,并观察特征
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# https://blog.csdn.net/weixin_39228381/article/details/108511882
def func(x, y):
return x * x / 20 + y * y
def paint_loss_func():
x = np.linspace(-50, 50, 100) # x的绘制范围是-50到50,从改区间均匀取100个数
y = np.linspace(-50, 50, 100) # y的绘制范围是-50到50,从改区间均匀取100个数
X, Y = np.meshgrid(x, y)
Z = func(X, Y)
fig = plt.figure() # figsize=(10, 10))
ax = Axes3D(fig)
plt.xlabel('x')
plt.ylabel('y')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow')
plt.show()
paint_loss_func()
特征:两端高,中间低。
2. 观察梯度方向
梯度特征:y轴方向大,x轴方向小,好多地方没有指向(0,0)
3. 编写代码实现算法,并可视化轨迹
# coding: utf-8
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
optimizers = OrderedDict()
optimizers["SGD"] = SGD(lr=0.95)
optimizers["Momentum"] = Momentum(lr=0.1)
optimizers["AdaGrad"] = AdaGrad(lr=1.5)
optimizers["Adam"] = Adam(lr=0.3)
idx = 1
for key in optimizers:
optimizer = optimizers[key]
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
# plot
plt.subplot(2, 2, idx)
idx += 1
plt.plot(x_history, y_history, 'o-', color="red")
plt.contour(X, Y, Z) # 绘制等高线
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.plot(0, 0, '+')
plt.title(key)
plt.xlabel("x")
plt.ylabel("y")
plt.subplots_adjust(wspace=0, hspace=0) # 调整子图间距
plt.show()
4. 分析上图,说明原理(选做)
1、为什么SGD会走“之字形”?其它算法为什么会比较平滑?
曲面的一个方向比另一个方向陡,往复查找,因此SGD会Z字型走位震荡严重,因为其他算法在SGD的基础上进行改进了,比如Momentum,引入了动量这一概念来减弱Z字型走位。第二问有提及。
2、Momentum、AdaGrad对SGD的改进体现在哪里?速度?方向?在图上有哪些体现?
动量是衡量让运动物体停下难度的物理量。当动量momentum越大时,其转换为势能的能量也就越大,越易摆动,难以停下来。
动量法几乎总是比标准的梯度下降法速度更快,算法的主要思想是计算梯度的指数加权平均,然后使用这个梯度来更新权重(上图中,你希望纵轴可以学习慢一点,不希望出现这些震荡,横轴上,你希望加快学习速度)
可以理解为,在到达新的一点时,SGD会直接按照该点的负梯度方向去更新,而Momentum会考虑之前的梯度及方向,即动量。更新的时候考虑梯度均值(指数加权平均),指数衰减理解为摩擦力造成的损失。
AdaGrad 会为参数的每个元素适当地调整学习率,与此同时进行学习(AdaGrad 的 Ada 来自英文单词 Adaptive,即“适当的”的意思)。逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。
和 SGD (随机梯度下降算法)一样,
W
W
W 表示要更新的权重参数,表示损失函数关于
∂
L
∂
W
\frac{\partial L}{\partial W}
∂W∂L 的梯度,η 表示学习率。这里新出现了变量
h
h
h ,如式 (6.5) 所示,它保存了以前的所有梯度值的平方和。然后,在更新参数时,通过乘以
1
h
\frac{1}{\sqrt{h}}
h1 ,就可以调整学习的尺度。这意味着,参数的元素中变动较大(被大幅更新)的元素的学习率将变小。也就是说,可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。
AdaGrad 会记录过去所有梯度的平方和。因此,学习越深入,更新的幅度就越小。实际上,如果无止境地学习,更新量就会变为 0,完全不再更新。
四种方法分别用了多长时间?是否符合预期?
符合预期。因为不同的优化算法,里面的内置参数不同,所以最后的时间会有相应的差异。但是根据上面的寻找路径来看,还是越厉害的算法跑的快一点。
调整学习率、动量等超参数,轨迹有哪些变化?
学习率设置
当学习率过大的时候,我们发现,四种算法均很容易超过范围,大幅度震荡,
当学习率过小的时候,我们发现点比较密集,寻找最优点的时间比较长。
经过实验我们发现:当四种算法的学习率分别为0.95,0.1, 1.5,0.3左右的时候,算法的速率较快。
5. 总结SGD、Momentum、AdaGrad、Adam的优缺点(选做)
SGD、Momentum、AdaGrad上面已经总结好了,这里仅仅说Adam的优缺点。
Adam优点:
1、参数更新的大小不随着梯度大小的缩放而变化;
2、更新参数时的步长的边界受限于超参的步长的设定;
3、不需要固定的目标函数;支持稀疏梯度;它能够自然的执行一种步长的退火。
Adam缺点:
1、可能不收敛参考On the Convergence of Adam and Beyond,
2、可能错过全区最优解
改正方法
其收敛性问题,adam可以结合SGD同时使用,即先进行Adam训练,再进行SGD。
6.Adam这么好,SGD是不是就用不到了?
我的看法,存在即合理,归根到底是Adam好还是SGD好?我认为可能要因材施教了,这可能是很难一句话说清楚的事情。好多大牛在文章中指出,用SGD的很多,Adam的也不少,还有很多偏爱AdaGrad或者AdaDelta。可能研究员把每个算法都试了一遍,哪个出来的效果好就用哪个了。而有几篇怒怼Adam的大牛,多数都构造了一些比较极端的例子来演示了Adam失效的可能性。这些例子一般过于极端,实际情况中可能未必会这样,但这提醒了我们,理解数据对于设计算法的必要性。优化算法的演变历史,都是基于对数据的某种假设而进行的优化,那么某种算法是否有效,就要看你的数据是否符合该算法的胃口了。算法固然美好,数据才是根本。另一方面,Adam之流虽然说已经简化了调参,但是并没有一劳永逸地解决问题,默认参数虽然好,但也不是放之四海而皆准。因此,在充分理解数据的基础上,依然需要根据数据特性、算法特性进行充分的