NNDL 作业11:优化算法比较

目录

编程实现图6-1,并观察特征

观察梯度方向

编写代码实现算法,并可视化轨迹

分析上图,说明原理(选做)

1. 为什么SGD会走“之字形”?其它算法为什么会比较平滑?

2. Momentum、AdaGrad对SGD的改进体现在哪里?速度?方向?在图上有哪些体现?

3. 仅从轨迹来看,Adam似乎不如AdaGrad效果好,是这样么?

4. 四种方法分别用了多长时间?是否符合预期?

5. 调整学习率、动量等超参数,轨迹有哪些变化?

总结SGD、Momentum、AdaGrad、Adam的优缺点(选做)

Adam这么好,SGD是不是就用不到了?

增加RMSprop、Nesterov算法。(选做)

参考:


编程实现图6-1,并观察特征

 代码实现:

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

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()

 

通过解析求解,显然当x=0,y=0时,Loss取得最小值。但这里我们通过神经网络反向传播求导的的方式,一步步优化参数,让Loss变小。通过这个过程,可以看出RMSProp算法的作用

观察梯度方向

编写代码实现算法,并可视化轨迹

 代码实现:

# 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()

分析上图,说明原理(选做)

1. 为什么SGD会走“之字形”?其它算法为什么会比较平滑?

因为图像的变化并不均匀,所以y方向变化很大时,x方向变化很小,只能迂回往复地寻找,效率很低。

2. Momentum、AdaGrad对SGD的改进体现在哪里?速度?方向?在图上有哪些体现?

SGD方法的一个缺点是,其更新方向完全依赖于当前的batch,因而其更新十分不稳定。解决这一问题的一个简单的做法便是引入momentum。

同一个更新速率不一定适合所有参数。比如有的参数可能已经到了仅需要微调的阶段,但又有些参数由于对应样本少等原因,还需要较大幅度的调动,Adagrad就是针对这一问题提出的,自适应地为各个参数分配不同学习率的算法。

3. 仅从轨迹来看,Adam似乎不如AdaGrad效果好,是这样么?

Adagrad采用的是学习率递减的办法,而Adam的学习采用的是一定范围内学习率的办法,两种存在差异,但是Adagrad要优于Adam下题6是解释在同种情况下的办法。

4. 四种方法分别用了多长时间?是否符合预期?

SGD:0.21247896345845154
Momentum:0.16324158694584524
AdaGrad:0.03421365268985212
Adam:0.03965424756389542
可以看出SGD确实比较慢,而AdaGrad运行时间短于Adam所以AdaGrad的性能更好一些。

5. 调整学习率、动量等超参数,轨迹有哪些变化?

学习率扩大一倍:

总结SGD、Momentum、AdaGrad、Adam的优缺点(选做)

SGD:

优点:训练收敛速度快,可在线更新模型,有几率跳出局部最优达到更好的局部最优或全局最优。
缺点:不稳定,容易陷入局部最优,容易困在鞍点。

Momentum:

动量优化法,相比于SGD仅仅关注当前的梯度,该方法引入了动量向量的概念,参数沿着动量向量进行更新,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。

Adagrad:

该方法使梯度在各个维度上按比例地缩小,也就是降低学习率,随着迭代次数的增加,学习率会越来越小,并且在某个维度上越陡峭,学习率降低得就越快,在这个维度上越平缓,学习率降低得就越慢。所以,该方法非常适合处理稀疏数据。随着训练迭代轮数的增加,学习率会越来越小,后期可能学不到任何东西,导致训练提前结束,总的来说,该方法是从学习率的角度进行了优化。

Adam:

Adam的优点主要在于经过偏差修正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。

Adam这么好,SGD是不是就用不到了?

SGD除了算得快,还具有许多优良性质。它能够自动逃离鞍点,自动逃离比较差的局部最优点,但他也存在着一些不足之处。

在理论上和实践上,Adam家族里那些用了自适应学习率的优化器都不善于寻找flat minima。而flat minima对于generalization是很重要的。所以Adam训练得到的training loss可能会更低,但test performance常常却更差。这是很多任务里避免用自适应学习率的最主要的原因。

同时,我们对SGD的理论算是比较了解,而以Adam代表的自适应优化器是一种很heuristic、理论机制也很不清晰的方法。

参考

增加RMSprop、Nesterov算法。(选做)

Nesterov代码实现:

#nesterov momentum
def update_parameters_with_nesterov_momentum(parameters, grads, v, beta, learning_rate):
	"""
	Update parameters using Momentum
	Arguments:
	parameters -- python dictionary containing your parameters:
					parameters['W' + str(l)] = Wl
					parameters['b' + str(l)] = bl
	grads -- python dictionary containing your gradients for each parameters:
					grads['dW' + str(l)] = dWl
					grads['db' + str(l)] = dbl
	v -- python dictionary containing the current velocity:
					v['dW' + str(l)] = ...
					v['db' + str(l)] = ...
	beta -- the momentum hyperparameter, scalar
	learning_rate -- the learning rate, scalar
	Returns:
	parameters -- python dictionary containing your updated parameters
	v -- python dictionary containing your updated velocities
	'''
	VdW = beta * VdW - learning_rate * dW
	Vdb = beta * Vdb - learning_rate * db
	W = W + beta * VdW - learning_rate * dW
	b = b + beta * Vdb - learning_rate * db
	'''
	"""

	L = len(parameters) // 2  # number of layers in the neural networks

	# Momentum update for each parameter
	for l in range(L):
		# compute velocities
		v["dW" + str(l + 1)] = beta * v["dW" + str(l + 1)] - learning_rate * grads['dW' + str(l + 1)]
		v["db" + str(l + 1)] = beta * v["db" + str(l + 1)] - learning_rate * grads['db' + str(l + 1)]
		# update parameters
		parameters["W" + str(l + 1)] += beta * v["dW" + str(l + 1)]- learning_rate * grads['dW' + str(l + 1)]
		parameters["b" + str(l + 1)] += beta * v["db" + str(l + 1)] - learning_rate * grads["db" + str(l + 1)]

	return parameters

RMSprop:

#RMSprop
def update_parameters_with_rmsprop(parameters, grads, s, beta = 0.9, learning_rate = 0.01, epsilon = 1e-6):
	"""
	Update parameters using Momentum
	Arguments:
	parameters -- python dictionary containing your parameters:
					parameters['W' + str(l)] = Wl
					parameters['b' + str(l)] = bl
	grads -- python dictionary containing your gradients for each parameters:
					grads['dW' + str(l)] = dWl
					grads['db' + str(l)] = dbl
	s -- python dictionary containing the current velocity:
					v['dW' + str(l)] = ...
					v['db' + str(l)] = ...
	beta -- the momentum hyperparameter, scalar
	learning_rate -- the learning rate, scalar
	Returns:
	parameters -- python dictionary containing your updated parameters
	'''
	SdW = beta * SdW + (1-beta) * (dW)^2
	sdb = beta * Sdb + (1-beta) * (db)^2
	W = W - learning_rate * dW/sqrt(SdW + epsilon)
	b = b - learning_rate * db/sqrt(Sdb + epsilon)
	'''
	"""
	L = len(parameters) // 2  # number of layers in the neural networks
	# rmsprop update for each parameter
	for l in range(L):
		# compute velocities
		s["dW" + str(l + 1)] = beta * s["dW" + str(l + 1)] + (1 - beta) * grads['dW' + str(l + 1)]**2
		s["db" + str(l + 1)] = beta * s["db" + str(l + 1)] + (1 - beta) * grads['db' + str(l + 1)]**2
		# update parameters
		parameters["W" + str(l + 1)] = parameters["W" + str(l + 1)] - learning_rate * grads['dW' + str(l + 1)] / np.sqrt(s["dW" + str(l + 1)] + epsilon)
		parameters["b" + str(l + 1)] = parameters["b" + str(l + 1)] - learning_rate * grads['db' + str(l + 1)] / np.sqrt(s["db" + str(l + 1)] + epsilon)

	return parameters

 

参考:

NNDL 作业11:优化算法比较

深度学习入门:基于Python的理论与实现 (ituring.com.cn)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值