目录
1. 被优化函数 ![x^{2}](https://latex.csdn.net/eq?x%5E%7B2%7D)
能自适应调整学习率:AdaGrade、RMSprop
能进行梯度估计修正:动量法、Nesterov加速梯度法
综合学习率调整和梯度估计修正:Adam算法
# 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 f(x,_):
return x**2
# def df(x, y):
# return x / 10.0, 2.0 * y
def df(x,_):
return 2*x,0
# init_pos = (-7.0, 2.0)
init_pos=(-7.0,)
params = {}
# params['x'], params['y'] = init_pos[0], init_pos[1]
params['x']=init_pos[0]
grads = {}
# grads['x'], grads['y'] = 0, 0
grads['x']=0
learningrate = [0.9, 0.3, 0.3, 0.6, 0.3, 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]
params['x']=init_pos[0]
for i in range(30):
x_history.append(params['x'])
# y_history.append(params['y'])
y_history.append(0)
# grads['x'], grads['y'] = df(params['x'], params['y'])
grads['x'],_=df(params['x'],0)
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, 3, idx)
idx += 1
plt.plot(x_history, y_history, 'o-', color="r")
# plt.contour(X, Y, Z) # 绘制等高线
plt.contour(X, Y, Z, cmap='gray') # 颜色填充
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=20, color="b",
verticalalignment='top', horizontalalignment='center', fontstyle='italic')
plt.xlabel("x")
plt.ylabel("y")
plt.subplots_adjust(wspace=0, hspace=0) # 调整子图间距
plt.show()
结果:
2. 被优化函数
# 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.9):
self.lr = lr
self.decay_rate = decay_rate #平滑指数加权移动平均的超参数 常取0.9
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)#更新。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 f(x,_):
# return x**2
def df(x, y):
return x / 10.0, 2.0 * y
# def df(x,_):
# return 2*x,0
init_pos = (-7.0, 2.0)
# init_pos=(-7.0,)
params = {}
params['x'], params['y'] = init_pos[0], init_pos[1]
# params['x']=init_pos[0]
grads = {}
grads['x'], grads['y'] = 0, 0
# grads['x']=0
learningrate = [0.9, 0.1, 0.2, 0.6, 0.6, 0.3, 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]
# params['x']=init_pos[0]
for i in range(30):
x_history.append(params['x'])
y_history.append(params['y'])
# y_history.append(0)
grads['x'], grads['y'] = df(params['x'], params['y'])
# grads['x'],_=df(params['x'],0)
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, 3, idx)
idx += 1
plt.plot(x_history, y_history, 'o-', color="r")
# plt.contour(X, Y, Z) # 绘制等高线
plt.contour(X, Y, Z, cmap='gray') # 颜色填充
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=20, color="b",
verticalalignment='top', horizontalalignment='center', fontstyle='italic')
plt.xlabel("x")
plt.ylabel("y")
plt.subplots_adjust(wspace=0, hspace=0) # 调整子图间距
plt.show()
结果:
3. 解释不同轨迹的形成原因
算法介绍
SGD:每次迭代并不是沿着梯度最大的方向下降的,而是根据每个样本梯度进行更新,因为每个样本的梯度的差异,就会导致轨迹出现波动。另外,如果学习率设置过大,也会导致参数在范围内波动,形成这种轨迹。
Momentum:在更新参数时,不仅考虑当前梯度信息,还考虑了之前的更新方向和大小。给梯度一动量的概念,使得参数更新更加平滑,并且具有方向性。当函数沿着一个方向,连续下降,这种算法还能够加速收敛。(通过累积之前的速度信息,使得参数更新具有惯性,可以跳出局部最优解,并且在梯度方向上形成一种平滑的轨迹。这种轨迹有助于加速收敛。)
Nestrov:是上一种算法的改进。先根据当前位置加上动量项的方向进行预测,然后计算该位置的梯度并使用这个梯度来调整预测位置的方向。
Adagrad:其学习率会根据参数的历史梯度平方和进行调整。随着梯度更新,累积梯度的平方和将不断增加,导致学习率逐渐减小。这意味着在训练早期,学习率较大,可以更快地更新参数;而随着训练进行,由于梯度平方和的累积,学习率逐渐减小,参数更新的步长也会减小,导致算法在接近最优解时更为稳定。但是很显然,梯度累积,导致学习率过小,最后还没到最优值,参数更新就几乎停滞了,导致无法到最优点。
RMSprop:对AdaGrad算法的改进,解决了AdaGrad算法学习率过早衰减的问题。RMSprop使用梯度平方的指数加权移动平均来计算历史梯度的平方和,而不是简单地累加所有历史梯度的平方。使得学习率不会急剧衰减严重。
Adam:结合了动量法和自适应学习率的优化算法,常用于训练神经网络等深度学习模型。
分析各个算法的优缺点
-
随机梯度下降法(SGD):
- 优点:
- 实现简单,易于理解和实施。
- 可用于大规模数据集和高维度模型。
- 缺点:
- 可能陷入局部最优点,尤其在目标函数非凸的情况下。
- 学习率的设置,过大的学习率可能导致不稳定或跳出最优点。
- 越到最后,算法的收敛速度就越慢。
- 优点:
-
Momentum SGD:
- 优点:
- 增加了动量项,有助于加速收敛。
- 能够跳出局部极小值。
- 缺点:
- 需要调整额外的超参数(动量参数)。
- 可能导致参数震荡。
- 优点:
-
Nesterov加速梯度:
- 优点:
- 对于动量法,有更好的收敛性和参数更新。
- 缺点:
- 对于高维度问题,需要更多的计算资源。
- 需要调整额外的超参数。
- 优点:
-
AdaGrad:
- 优点:
- 自适应学习率,每个参数的学习率会随时间减小。
- 缺点:
- 学习率衰减过快,可能在训练后期导致学习速度过慢。
- 累积梯度的平方可能导致学习率过小,难以收敛。
- 优点:
-
RMSprop:
- 优点:
- 解决了 AdaGrad 学习率下降过快的问题,引入了衰减系数,调整学习率。
- 缺点:
- 仍可能需要手动调整超参数(衰减系数,常取0.9)。
- 有时可能收敛速度较慢。
- 优点:
-
Adam:
- 优点:
- 结合了动量法和 RMSprop 的优点,具有较好的性能。
- 自适应调整学习率和动量。
- 缺点:
- 需要调整额外的超参数如学习率、动量系数。
- 优点:
链接:
【NNDL 作业】优化算法比较 增加 RMSprop、Nesterov