优化器 示意图 神经网络_神经网络 优化器

本文介绍了神经网络学习过程中优化器的重要性,特别是SGD、Momentum、AdaGrad和Adam优化器的工作原理和优缺点。通过动态调整学习率,这些优化器旨在提高收敛速度和稳定性,降低震荡。实验结果显示,Momentum和Adam在减小震荡和加速收敛方面表现出色。
摘要由CSDN通过智能技术生成

我们知道,神经网络的学习的目的就是寻找合适的参数,使得损失函数的值尽可能小。解决这个问题的过程为称为最优化。解决这个问题使用的算法叫做优化器。

1. SGD

在前面我们实现的神经网络中所使用的优化方法是随机梯度下降法(Stachastic gradient desent 简称 SGD)。SGD 的想法就是沿着梯度的方向前进一定距离。用数学的语言来描述的话可以写成下式:

这里面,

表示需要更新的权重,

表示损失函数关于

的梯度(准确点来说这是一个 Jacobian 矩阵),

表示学习率,

表示使用右边的值更新左边的值。下面我们先给出一个 python 实现:

class SGD:

def __init__(self, lr=0.01):

self.lr = lr

def update(self, params, grades):

for key in params.keys():

params[key] -= self.lr * grades[key]

将优化器实现为一个类是一个很好的做法, python 是动态语言的原因,我们在实现的时候只要类里面都有 update 方法,解释器就会正常执行。例如我们可以将之前的学习过程写成这样:重点在于加了 # 号的那几行。

# 导入数据

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

# 全局变量

train_loss_list = []

train_acc_list = []

test_acc_list = []

# 超参数

iter_num = 10000

train_size = x_train.shape[0]

batch_size = 100

learning_rate = 0.1

iter_per_epoch = max(train_size / batch_size, 1)

# 生成模型

net_work = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

optimizer = SGD(learning_rate) ########

# 模型的训练

for i in range(iter_num):

# 获取 mini_batch

batch_mask = np.random.choice(train_size, batch_size)

x_batch = x_train[batch_mask]

t_batch = t_train[batch_mask]

# 计算梯度

grads = net_work.gradient(x_batch, t_batch)

# 更新参数

params = net_work.params

optimizer.update(params, grads) ##########

# 记录学习过程

if i % iter_per_epoch == 0:

loss = net_work.loss(x_train, t_train)

train_loss_list.append(loss)

train_acc = net_work.accuracy(x_train, t_train)

train_acc_list.append(train_acc)

test_acc = net_work.accuracy(x_test, t_test)

test_acc_list.append(test_acc)

print('运行中... 损失:{} 训练集准确度:{} 测试集准确度:{}'.format(loss, train_acc, test_acc))

SGD 的优点就是简单,容易实现。但是其缺点就是低效,因为有的时候梯度的方向并没有指向最小值的方向。低效的原因有两大方面:

函数呈延伸状,梯度指向了’谷底‘。(文章的最后有一个呈延伸状函数的图片)

这使得损失函数值不停的在震荡。

梯度方向指向了极小值,或者鞍点方向,

因为所有维度的梯度在这附近都接近于 0,这使得损失函数在这里变化的很慢。

下面的方法就全是针对这两大方面对 SGD 进行改进。

2. Momentum

这种方法主要是为了解决第一种情况,当函数呈延伸状的情况下,梯度指向了谷底,而不是直接指向了最低点,函数值在学习过程中会来回震荡,但是向最低点移动的却很小。

上面的叙述中提到了两个方向,一个是纵向的震动,一个是横向的向最低点移动,如下图,如果我们可以避免或者减少震荡,加快横向的向最低点移动,那么就加快了学习。

实际上震荡是不可避免的,所以我们只能考虑减轻震荡。

我们还是先看一下数学描述:

Momentum 在 SGD 的基础上引入了一个变量 - 速度

和一个超参数 - 指数衰减平均

我们分别来看一下他们的含义。为了便于理解,我们先将

去掉,或者说是设为 1。首先将

初始化为零矩阵,然后进行第一次迭代,

保存的就是上一次的梯度,如果方向没改变,再一次迭代的时候梯度会被累加,加快学习。如果方向改变了(符号发生了变化),那么就减少了这次的学习,这样其实就实现了我们的目的:使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,这样就可以加快收敛并减小震荡。

接下来我们再来看一下超参数

,它是描述之前梯度对现在影响的参数。

越大表示之前梯度对现在的影响越大。

一般被设定为 0.9。

说了这么多理论,我们还是要实现一下这个优化器:

class Momentum:

def __init__(self, lr=0.01, alpha=0.9):

self.lr = lr

self.alpha = alpha

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.alpha*self.v[key] - self.lr*grads[key]

params[key] += self.v[key]

如果想使用 Momentum ,只需要将上面代码中的 optimizer = SGD(learning_rate) 修改为 optimizer = Momentum(learning_rate) 即可。

保持其他参数不变,训练的结果是:

SGD: 损失:0.5450568343946544 训练集准确度:0.8639 测试集准确度:0.8672

Momentum: 损失:0.1830601846034292 训练集准确度:0.94755 测试集准确度:0.9444

所以总结一句,优点:使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,可以加快收敛并减小震荡。

但是也有缺点:这种方法相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,就知道需要减速了的话,适应性会更好。根据这个改进的优化器叫做:NAG(Nesterov Accelerated Gradient)

3. AdaGrad

这种方法主要是为了解决 SGD 遇到鞍点或者极小值点后学习变慢的问题。我们知道超参数学习率是一个很重要的参数,不同的参数对学习结果的影响很大,如果设置的值较小,会导致学习花费较多的时间,学习率大了就会导致学习发散而不能正常的进行。所以我们可以考虑避免人为的介入,根据需要让程序自己动态地设置学习率。例如对于遇到鞍点的情况,参数变化很小,基本不会改变,那么这个方法就会设置一个较大的学习率,跨过鞍点。

在神经网络中有一种方法经常被使用:学习率衰减方法(learning rate decay),也就是说随着学习的进行,使学习率逐渐减少。AdaGrade 进一步发展了这个想法,它会为参数的每一个元素设当的调整学习率。

我们还是看一下数学的描述:

这里新出现了一个变量

,它保存了之前所有梯度的平方和,在更新参数的时候通过乘以

就可以调整学习的尺度。

我们还是尝试实现它:

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

结果为:

AdaGrad: 损失:0.20811779864341037 训练集准确度:0.9411 测试集准确度:0.936

AdaGrad 的优点是可以动态的调整学习率,

缺点是 AdaGrad 会记录过去所有的梯度平方和,最后有可能不再更新,

针对这个问题有一个方法叫做 RMSProp 进行了优化。

4. Adam

Adam 直观的来讲就是融合了 Momentum 和 AdaGrad 方法,详细可以参考原版论文

详细回头再更,,

class Adam:

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] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]

#self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)

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)

#unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias

#unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias

#params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

最后看两个动态的优化过程,图片来自网络,侵删。

第一个是存在鞍点和局部极小值的情况。

第二个是损失函数呈延伸状的情况。

图片的绘制可以参考https://github.com/dream-catcher/learning_blogs

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值