优化器方法总结

日期:2024-05-13 09:10 | 人气:

梯度优化算法,简而言之就是利用目标函数的梯度进行优化。利用一阶梯度的优化算法,也就是常说的梯度下降优化算法以及基于此的一些优化,二阶梯度优化算法称为牛顿算法。本文主要介绍第一种即一阶梯度优化算法。

一阶梯度的优化公式为:

w=w - \\alpha \\frac{\\partial L}{\\partial w}

其中 \\alpha 为学习率, \\frac{\\partial L}{\\partial w} 为梯度计算。

从上式可以看出,针对梯度优化的算法,主要包括两方面:一个是梯度的计算,一个是学习率的设置。本文综合一些参考文献 Juliuszh:一个框架看懂优化算法之异同 SGD/AdaGrad/Adam 提到的统一框架,对各种优化算法(梯度的计算)进行统一描述,对学习率的设置做简要介绍。

加速训练、收敛

找到最优解

  • 优化训练样本的大小:mini-batch,计算mini-batch大小的样本的loss和梯度,更新参数,不需要计算所有样本的loss和梯度,速度加快
  • 优化梯度计算:动量梯度下降优化算法,RMSprop, Adam。
  • 优化学习率:学习率衰减 \\alpha

待优化参数: w

目标函数(损失函数): f(w)

初始学习率: \\alpha


通过一个统一的优化器框架,方便比较掌握各种优化器的不同与优缺点。

训练过程中进行迭代优化。在每个batch:

1. 计算目标函数对于当前参数的梯度: g_t=\
abla f(w_t)

2. 根据历史梯度计算一阶动量和二阶动量: m_t=\\phi(g_1, g_2, ..., g_t); V_t=\\psi(g_1, g_2, ..., g_t)

3. 计算当前时刻的下降梯度: \\eta_t=\\alpha \\cdot m_t/\\sqrt{V_t}

4.根据下降梯度进行更新: w_{t+1}=w_t - \\eta_t

各种优化器的主要差别在前两个步骤,步骤3和4是一致的。


优化器方法,主要对应优化梯度计算的方法

SGD为随机梯度下降

SGD没有动量概念,即没有考虑历史梯度:

m_t=g_t; V_t=I^2

即梯度下降为:

\\eta_t=\\alpha \\cdot g_t

梯度更新为:

w_{t+1}=w_t - \\alpha \\cdot g_t

随机梯度下降经常代指mini-batch梯度下降,即对mini-batch size (n)的样本计算目标函数 f(w_t; x^{i:i+n}; y^{i:i+n})。

mini-batch梯度下降,介于BGD(batch gradient desent)和完全的随机梯度下降方法之间。前者需要对所有样本进行计算然后更新梯度,后者只需要对一个样本进行计算就更新梯度。

  • BGD:更新速度慢,不能在线更新,对于凸损失函数,收敛于全局最小值。对于非凸损失函数,收敛于局部最小值。

* 完全随机梯度下降:更新速度快,可用于在线更新,但是会发生震荡,噪声大。当缓慢降低学习率 \\alpha 时,收敛行为类似于BGD。

  • MBSGD:相比完全随机梯度下降,参数更新会更加稳定。


存在问题:

  • 学习率选择困难
  • 对所有参数采用相同的学习率
  • 可能困在局部最小值

Nesterov momentum is a different version of the momentum method which has stronger theoretical converge guarantees for convex functions.


考虑历史梯度计算一阶动量(类似惯性),减少了随机梯度的震荡,并加速了训练。

m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1) \\cdot g_t

梯度更新为:

w_{t+1}=w_t - \\alpha \\cdot m_t

通常设置 \\beta_1 的值为0.9,即时刻t的主要下降方向由此前累积的下降方向决定,并略微偏向当前时刻的梯度方向。


SGD可能困在局部沟壑里,相比SGDM,NAG中的梯度会计算下一步位置的梯度。即 g_t=\
abla f(w_t - \\beta_1 \\cdot m_{t-1}) 。通过计算下一步位置的梯度,实现向前多看一步,减少困在局部沟壑里的可能性。

在RNN的模型任务中,NAG的效果较好。

对不同的参数采用不同的学习率,对于更新频繁的参数,采用较小的学习率;对于更新不频繁的参数,采用较大的学习率。

通过计算历史二阶动量来衡量历史更新频率:

V_{t,i}=\\sum_{\	au=1}^t g_{\	au, i}^2

其中i为参数的索引.

目标函数当前参数i的梯度为:

g_{t,i}=\
abla f(w_{t, i})

下降梯度为:

\\eta_{t,i}=\\alpha \\cdot g_{t,i}/ \\sqrt{V_{t,i}+\\epsilon}

其中 \\epsilon 为平滑参数。

这一方法在稀疏数据场景下表现很好,如在Glove训练word embedding的训练中。学习率由 \\alpha 变为了 \\alpha/\\sqrt{V_{t,i}} ,学习率会自动更新,但是随着二阶动量的积累,学习率会逐渐变小。


AdaGrad单调递减的学习率源于历史梯度的积累,AdaDelta中不累积全部历史梯度,而只关注过去一段时间窗口的下降梯度。这就是Delta的由来。

指数移动平均值大约就是过去一段时间的平均值,因此通过这一方法计算二阶累积动量:

V_{t,i}=\\beta_2 * V_{t-1,i}+ (1-\\beta_2)g_{t,i}^2

这样避免了二阶动量持续累积,导致学习率逐渐变小为零导致训练过程提前结束的问题。

SGD-M是在SGD基础上增加了一阶动量,AdaGrad和AdaDelta是在SGD的基础上增加了二阶动量。Adam是将一阶动量与二阶动量都结合了起来,Adam=Adaptive+Momentum

一阶动量:

m_t=\\beta_1 \\cdot m_{t-1}+ (1-\\beta_1) \\cdot g_t

二阶动量:

V_t=\\beta_2 * V_{t-1}+ (1-\\beta_2) g_t^2

超参 \\beta_1 控制一阶动量, \\beta_2 控制二阶动量

实际使用过程中,参数的经验值是 \\beta_1=0.9, \\beta_2=0.999

初始化时 m_0=0, V_0=0,m_t, V_t 会接近于0,尤其是在迭代初期和 \\beta_1, \\beta_2 接近于1的情况下。这种估计会有问题,需要修正,修正如下:

m_t=m_t / (1-\\beta_1^t)

V_t=V_t / (1-\\beta_2^t)


最终的梯度更新为:

w_{t+1}=w_t - \\alpha \\cdot m_t/\\sqrt{V_t+\\epsilon}


SGD利用了目标函数的一阶导数,为一阶优化方法。牛顿算法利用了目标函数的二阶导数信息,为二阶优化算法。但由于二阶导数导致参数量过大,计算复杂,导致牛顿算法并不流行。

Momentum、AdaGrad、Adam等方法,利用了一阶导数的变化信息(如历史一阶导数、下一时刻的导数),与牛顿算法二阶导数本质上是利用了一阶导数的梯度信息一致,所以Momentum、AdaGrad、Adam等算法,也可以看做是近似二阶优化方法。



ref:

Intro to optimization in deep learning: Momentum, RMSProp and Adam

medium.com/@vinodhb95/m


学习率的变化有多种方法,包括学习率衰减、warmup等,如一般在模型训练的初始阶段通过warmup的方式,逐渐增大学习率,在后期阶段通过学习率衰减的方式组件逼近最优值(bert的训练中也采用了这种方式)。这里以学习率衰减为例,介绍在keras中的实现。

keras中设置学习率衰减的方法

  • 创建衰减函数
  • LearningRateScheduler(decay_method) , LearningRateScheduler为callback的子类
  • model.fit(callbacks=[LearningRateScheduler])
def step_decay(epoch):
   initial_lrate = 0.1
   drop = 0.5
   epochs_drop = 10.0
   lrate = initial_lrate * math.pow(drop,  
           math.floor((1+epoch)/epochs_drop))
   return lrate
lrate = LearningRateScheduler(step_decay)   

class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
       self.losses = []
       self.lr = []
 
    def on_epoch_end(self, batch, logs={}):
       self.losses.append(logs.get(loss))
       self.lr.append(step_decay(len(self.losses)))
       
loss_history = LossHistory()
lrate = LearningRateScheduler(step_decay)
callbacks_list = [loss_history, lrate]
history = model.fit(X_train, y_train, 
   validation_data=(X_test, y_test), 
   epochs=epochs, 
   batch_size=batch_size, 
   callbacks=callbacks_list, 
   verbose=2)

As a digression, a callback is a set of functions to be applied at given stages of the training procedure

ref:

towardsdatascience.com/


Ref:

Juliuszh:一个框架看懂优化算法之异同 SGD/AdaGrad/Adam

Juliuszh:Adam那么棒,为什么还对SGD念念不忘 (2)—— Adam的两宗罪

骆梁宸:从 SGD 到 Adam —— 深度学习优化算法概览(一)

深度学习--优化器算法Optimizer详解(BGD、SGD、MBGD、Momentum、NAG、Adagrad、Adadelta、RMSprop、Adam) - 云+社区 - 腾讯云

大力再出奇迹,1024 张TPU,65536 batch size,仅76分钟训练完BERT!

titu1994/keras-LAMB-Optimizer

An overview of gradient descent optimization algorithms

“让Keras更酷一些!”:小众的自定义优化器 - 科学空间|Scientific Spaces

towardsdatascience.com/

medium.com/datadrivenin

Reducing BERT Pre-Training Time from 3 Days to 76 Minutes

Scaling SGD Batch Size to 32K for ImageNet Training

LARGE BATCH TRAINING OF CONVOLUTIONAL NETWORKS

github.com/tensorflow/t

旋转小火锅定制流程

免费咨询

提供图纸

免费设计

免费报价

无忧安装

终身维护

平台注册入口