优化器算法简述
首先来看一下梯度下降最常见的三种变形:
批量梯度下降(BGD)
随机梯度下降(SGD)
小批量随机梯度下降(MSGD)
这三种形式的区别就是取决于我们用多少数据来计算目标函数的梯度, 涉及参数更新的准确率和运行时间。
- 批量梯度下降(BGD)
梯度更新规则:
BGD 采用整个训练集的数据来计算 cost function 对参数的梯度:
我们会事先定义一个迭代次数 epoch,首先计算梯度向量 params_grad,然后沿着梯度的方向更新参数 params,
learning rate 决定了我们每一步迈多大。
Batch gradient descent 对于凸函数可以收敛到全局极小值,对于非凸函数可以收敛到局部极小值。
- 随机梯度下降(SGD)
![](https://i-blog.csdnimg.cn/blog_migrate/68baf5f7e4f7618222624ce93f704590.png)
梯度更新规则:
和批量梯度下降(BGD) 的一次用所有数据计算梯度相比,随机梯度下降(SGD)每次更新时对每个样本进行梯度更新,
对于很大的数据集来说,可能会有相似的样本,这样 BGD 在计算梯度时会出现冗余, 而 SGD 一次只进行一次更新,
就没有冗余,而且比较快,并且可以新增样本。
缺点:
随机梯度下降(SGD)因为更新比较频繁,会造成 cost function 有严重的震荡,此外随机梯度下降(SGD)对噪声比较敏感。
批量梯度下降(BGD)可以收敛到局部极小值,当然 随机梯度下降(SGD)的震荡可能会跳到更好的局部极小值处。
当我们稍微减小 learning rate,随机梯度下降(SGD)和批量梯度下降(BGD)的收敛性是一样的。
- 小批量随机梯度下降(MSGD)
![](https://i-blog.csdnimg.cn/blog_migrate/72ae25f15b442f54431a86e9b10f9847.png)
梯度更新规则:
MBGD 每一次利用一小批样本,即 n 个样本进行计算, 这样它可以降低参数更新时的方差,收敛更稳定,
另一方面可以充分地利用深度学习库中高度优化的矩阵操作来进行更有效的梯度计算。
和 SGD 的区别是每一次循环不是作用于每个样本,而是具有 n 个样本的Batch。
超参数设定值:
n 一般取值在 50~200
缺点:
Mini-batch gradient descent 不能保证很好的收敛性,
①learning rate 如果选择的太小,收敛速度会很慢,如果太大,loss function 就会在极小值处不停地震荡甚至偏离。
②有一种措施是先设定大一点的学习率,当两次迭代之间的变化低于某个阈值后,就减小 learning rate,
不过这个阈值的设定需要提前写好,这样的话就不能够适应数据集的特点。
此外,这种方法是对所有参数更新时应用同样的 learning rate,
如果我们的数据是稀疏的,我们更希望对出现频率低的特征进行大一点的更新。
③另外,对于非凸函数,还要避免陷于局部极小值处,或者鞍点处,因为鞍点周围的error 是一样的,所有维度的梯度都接近于0,SGD 很容易被困在这里。
鞍点:一个光滑函数的鞍点邻域的曲线,曲面,或超曲面,都位于这点的切线的不同边。
例如这个二维图形,像个马鞍:在x-轴方向往上曲,在y-轴方向往下曲,鞍点就是(0,0)
为了应对上面的三点挑战就有了下面这些算法:
- Momentum
SGD 在 ravines 的情况下容易被困住, ravines 就是曲面的一个方向比另一个方向更陡,
这时 SGD 会发生震荡而迟迟不能接近极小值:
当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。
加入的这一项,可以使得梯度方向不变的维度上速度变快,梯度方向有所改变的维度上的更新速度变慢,
这样就可以加快收敛并减小震荡。
梯度更新规则:
![](https://i-blog.csdnimg.cn/blog_migrate/19322cd88da9940f09448832aa17c324.png)
超参数设定值:
一般 γ取值 0.9 左右。
缺点:
这种情况相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,
就知道需要减速了的话,适应性会更好。
- Nesterov accelerated gradient
梯度更新规则:
超参数设定值:
γ仍然取值 0.9 左右。效果比较:
蓝色是 Momentum 的过程,会先计算当前的梯度,然后在更新后的累积梯度后会有一个大的跳跃。
而 NAG 会先在前一步的累积梯度上(brown vector)有一个大的跳跃,然后衡量一下梯度做一下修正(red vector),这种预期的更新可以避免我们走的太快。
NAG 可以使 RNN 在很多任务上有更好的表现。
目前为止,我们可以做到,在更新梯度时顺应 loss function 的梯度来调整速度,并且对 SGD 进行加速。
我们还希望可以根据参数的重要性而对不同的参数进行不同程度的更新。
NAG在TF中跟Momentum合并在同一个函数tf.train.MomentumOptimizer中,可以通过参数配置启用。在Momentum中,我们知道小球会盲目的跟从下坡的梯度,容易发生错误,这样我们需要一个更聪明的小球,
- Adagrad
![](https://i-blog.csdnimg.cn/blog_migrate/30898867c76e2504c6bc938240a985ce.png)
它是基于SGD的一种算法,他的核心思想是对比较常见的数据给予比较小的学习率去调整参数,对于比较罕见的数据给以比较大的学习率去调整参数。
他比较适合应用于数据稀疏的数据集(比如一个图片数据集,有10000张狗的图片,10000张猫的图片,只有100张大象的照片)
Adagrad主要的优势在于不需要人为的调解学习率,他可以自动调解。缺点在于,随着迭代次数的增多,学习率会越来越低,最终会趋向于0。
- RMSProp:基于adagrad的缺点提出了这个,改进版adagrad
1.AdaGrad算法的改进。鉴于神经网络都是非凸条件下的,RMSProp在非凸条件下结果更好,改变梯度累积为指数衰减的移动平均以丢弃遥远的过去历史。
2.经验上,RMSProp被证明有效且实用的深度学习网络优化算法。
相比于AdaGrad的历史梯度:
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/6a44c05c3e2f388fde0b337034925036.png)
![这里写图片描述](https://i-blog.csdnimg.cn/blog_migrate/a7cf95e8ff47be6bf04218257e55e442.png)
![](https://i-blog.csdnimg.cn/blog_migrate/4748099b01b164dacd2bf7c61c760a85.jpeg)
- Adadlta:
批量梯度下降(BGD)、随机梯度下降(SGD)、小批量随机梯度下降(MSGD)实现过程
设我们提供了这样的数据样本(样本值取自于y=3*x1+4*x2):
x1 x2 y 1 4 19 2 5 26 5 1 19 4 2 29
x1和x2是样本值,y是预测目标,我们需要以一条直线来拟合上面的数据,待拟合的函数如下:
——1式
我们的目的就是要求出θ1和θ2的值,让h(θ)尽量逼近目标值y。
这是一个线性回归问题,若对线性回归有所了解的同学就知道:利用最小二乘法则和梯度下降法可以求出两个参数,而深度学习也同样可以利用这两种方法求得所有的网络参数,因此,在这里用这个数学模型来解释BGD、SGD、MSGD这几个概念。
先熟悉一下梯度下降法:
我们首先确定损失函数:
——2式
其中,J(θ)是损失函数,m代表每次取多少样本进行训练,如果采用SGD进行训练,那每次随机取一组样本,m=1;如果是批处理,则m等于每次抽取作为训练样本的数量。θ是参数,对应(1式)的θ1和θ2。求出了θ1和θ2,h(x)的表达式就出来了:
——3式
我们的目标是让损失函数J(θ)的值最小,根据梯度下降法,首先要用J(θ)对θ求偏导:
——4式
由于是要最小化损失函数(2式),所以参数θ按其负梯度方向来更新:
——5式
BGD(Batch gradient descent)批量梯度下降法实现:每次迭代使用所有的样本
每次迭代都需要把所有样本都送入,这样的好处是每次迭代都顾及了全部的样本,做的是全局最优化。
# -*- coding: utf-8 -*-
import random
# 用y = Θ1*x1 + Θ2*x2来拟合下面的输入和输出
# input1 1 2 5 4
# input2 4 5 1 2
# output 19 26 19 20
input_x = [[1, 4], [2, 5], [5, 1], [4, 2]] # 输入
y = [19, 26, 19, 20] # 输出
theta = [1, 1] # θ参数初始化
loss = 10 # loss先定义一个数,为了进入循环迭代
step_size = 0.01 # 学习率步长
eps = 0.0001 # 精度要求
max_iters = 10000 # 最大迭代次数
error = 0 # 损失值
iter_count = 0 # 当前迭代次数
err1 = [0, 0, 0, 0] # 求Θ1梯度的中间变量1
err2 = [0, 0, 0, 0] # 求Θ2梯度的中间变量2
while (loss > eps and iter_count < max_iters): # 迭代条件
loss = 0
err1sum = 0
err2sum = 0
for i in range(4): # 每次迭代所有的样本都进行训练
pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 预测值
err1[i] = (pred_y - y[i]) * input_x[i][0]
err1sum = err1sum + err1[i]
err2[i] = (pred_y - y[i]) * input_x[i][1]
err2sum = err2sum + err2[i]
theta[0] = theta[0] - step_size * err1sum / 4 # 对应5式
theta[1] = theta[1] - step_size * err2sum / 4 # 对应5式
for i in range(4):
pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 预测值
error = (1 / (2 * 4)) * (pred_y - y[i]) ** 2 # 损失值
loss = loss + error # 总损失值
iter_count += 1
print("iters_count", iter_count)
print('theta: ', theta)
print('final loss: ', loss)
print('iters: ', iter_count)
theta: [3.0044552563214433, 3.9955447274498894]
final loss: 9.428456066652548e-05
iters: 97
这里我们只有4个样本,所以训练的时间不长。但是,如果面对数量巨大的样本量(如40万个),采取这种训练方式,所耗费的时间会非常长。
SGD(Stochastic gradientdescent)随机梯度下降法:每次迭代使用一组样本
针对BGD算法训练速度过慢的缺点,提出了SGD算法,普通的BGD算法是每次迭代把所有样本都过一遍,每训练一组样本就把梯度更新一次。而SGD算法是从样本中随机抽出一组,训练后按梯度更新一次,然后再抽取一组,再更新一次,在样本量及其大的情况下,可能不用训练完所有的样本就可以获得一个损失值在可接受范围之内的模型了。
# -*- coding: utf-8 -*-
import random
# 用y = Θ1*x1 + Θ2*x2来拟合下面的输入和输出
# input1 1 2 5 4
# input2 4 5 1 2
# output 19 26 19 20
input_x = [[1, 4], [2, 5], [5, 1], [4, 2]] # 输入
y = [19, 26, 19, 20] # 输出
theta = [1, 1] # θ参数初始化
loss = 10 # loss先定义一个数,为了进入循环迭代
step_size = 0.01 # 步长
eps = 0.0001 # 精度要求
max_iters = 10000 # 最大迭代次数
error = 0 # 损失值
iter_count = 0 # 当前迭代次数
while (loss > eps and iter_count < max_iters): # 迭代条件
loss = 0
i = random.randint(0, 3) # 每次迭代在input_x中随机选取一组样本进行权重的更新
pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 预测值
theta[0] = theta[0] - step_size * (pred_y - y[i]) * input_x[i][0]
theta[1] = theta[1] - step_size * (pred_y - y[i]) * input_x[i][1]
for i in range(3):
pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 预测值
error = 0.5 * (pred_y - y[i]) ** 2
loss = loss + error
iter_count += 1
print('iters_count', iter_count)
print('theta: ', theta)
print('final loss: ', loss)
print('iters: ', iter_count)
theta: [3.0023801858476675, 3.9979937500751443]
final loss: 7.877537813973126e-05
iters: 37
MBGD(Mini-batch gradient descent)小批量梯度下降:每次迭代使用b组样本
SGD相对来说要快很多,但是也有存在问题,由于单个样本的训练可能会带来很多噪声,使得SGD并不是每次迭代都向着整体最优化方向,因此在刚开始训练时可能收敛得很快,但是训练一段时间后就会变得很慢。在此基础上又提出了小批量梯度下降法,它是每次从样本中随机抽取一小批进行训练,而不是一组。
# -*- coding: utf-8 -*-
import random
# 用y = Θ1*x1 + Θ2*x2来拟合下面的输入和输出
# input1 1 2 5 4
# input2 4 5 1 2
# output 19 26 19 20
input_x = [[1, 4], [2, 5], [5, 1], [4, 2]] # 输入
y = [19, 26, 19, 20] # 输出
theta = [1, 1] # θ参数初始化
loss = 10 # loss先定义一个数,为了进入循环迭代
step_size = 0.01 # 学习率步长
eps = 0.0001 # 精度要求
max_iters = 10000 # 最大迭代次数
error = 0 # 损失值
iter_count = 0 # 当前迭代次数
while (loss > eps and iter_count < max_iters): # 迭代条件
loss = 0
# 这里每次批量选取的是2组样本进行更新,另一个点是随机点+1的相邻点
i = random.randint(0, 3) # 随机抽取一组样本
j = (i + 1) % 4 # 抽取另一组样本,j=i+1
pred_y0 = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 预测值1
pred_y1 = theta[0] * input_x[j][0] + theta[1] * input_x[j][1] # 预测值2
theta[0] = theta[0] - step_size * (1 / 2) * (
(pred_y0 - y[i]) * input_x[i][0] + (pred_y1 - y[j]) * input_x[j][0]) # 对应5式
theta[1] = theta[1] - step_size * (1 / 2) * (
(pred_y0 - y[i]) * input_x[i][1] + (pred_y1 - y[j]) * input_x[j][1]) # 对应5式
for i in range(3):
pred_y = theta[0] * input_x[i][0] + theta[1] * input_x[i][1] # 总预测值
error = (1 / (2 * 2)) * (pred_y - y[i]) ** 2 # 损失值
loss = loss + error # 总损失值
iter_count += 1
print('iters_count', iter_count)
print('theta: ', theta)
print('final loss: ', loss)
theta: [3.0036670206779137, 3.996960701618425]
final loss: 9.196570363036153e-05
iters: 102