Adam
Adam[1]的出发点是希望优化高维参数空间下的目标函数,它结合了AdaGrad适合于稀疏梯度的优点和RMSProp适合于在线与非平稳情况下的优点,核心的更新公式如下:
$$
\begin{align}
& m_{t,i} = \gamma_1 m_{t-1,i} + (1 - \gamma_1)g_{t,i} \\
& v_{t,i} = \gamma_2 v_{t-1,i} + (1 - \gamma_2)g^2_{t,i} \\
& \hat m_{t,i} = {m_{t,i} \over 1-\gamma^t_1} \\
& \hat v_{t,i} = {v_{t,i} \over 1-\gamma^t_2} \\
& \theta_{t+1,i} = \theta_{t,i} - {\eta \over {\sqrt{v_{t,i}} + \epsilon}} \hat m_{t,i}
\end{align}
$$
因为$\epsilon$非常小($10^{-8}$级别),因此上述更新步骤在实际代码实现[2]时可以按照下面的方式写:
$$
\begin{align}
& m_{t,i} = \gamma_1 m_{t-1,i} + (1 - \gamma_1)g_{t,i} \\
& v_{t,i} = \gamma_2 v_{t-1,i} + (1 - \gamma_2)g^2_{t,i} \\
& \eta' = {{\sqrt{1-\gamma_2^t}} \over {1- \gamma_1^t}} \eta \\
& \theta_{t+1,i} = \theta_{t,i} - {\eta' \over {\sqrt{v_{t,i}} + \epsilon}} m_{t,i}
\end{align}
$$
完整的Adam算法流程为:

Adam算法具备的优点包括:
- 适用于稀疏梯度的情况;
- 适合于在线和非平稳学习的情况;
- 适合于高维参数空间的优化;
- 也能够应用于部分的非凸优化问题
Nadam
Nadam的出发点是进一步提升Adam算法的性能,它将Adam算法和Nestorev加速算法结合在了一起,下面是算法流程[10]:

其中用于计算$\hat m_t$的动态参数$u_t$由一个momentum schedule给出:
一般而言,在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果[10].
算法选择
- 如果训练数据是稀疏的,则一般用自适应学习率方法(AdaGrad, RMSProp, AdaDelta, Adam, Nadam)的效果会比较好;
- 如果是训练一个比较深层的或复杂的神经网络,并且希望收敛速度比较快,选择自适应学习率方法;
二阶方法
上面提到的所有优化算法均只用到目标函数的一阶梯度信息,而牛顿法则还考虑了目标函数的二阶信息(海塞矩阵)作为优化的方向,但是在神经网络等参数较多的模型中,因为计算和存储Hessian矩阵带来的开销很大,因此牛顿法很少用到参数较多的模型优化中。为了减少Hessian矩阵的计算量,拟牛顿法通过寻找另外一个矩阵来近似原始的Hessian矩阵,比如用的比较多的L-BFGS[17],但是原始的L-BFGS算法同样在神经网络等模型的训练中很少用到,原因[15]是:
…, even after we eliminate the memory concerns, a large downside of a naive application of L-BFGS is that it must be computed over the entire training set, which could contain millions of examples. Unlike mini-batch SGD, getting L-BFGS to work on mini-batches is more tricky …
另外一种利用目标函数的二阶梯度信息并且不直接计算Hessian矩阵的方法是共轭梯度法[18]或者Hessian-Free类算法,下图[19]是部分二阶优化方法在速度和内存方面的对比:

根据上图,如果模型需要训练的参数量非常大,则选择左下角位置的算法;如果需要优化的参数量比较少,则考虑用右上角位置的优化算法。
SGD优化策略
Shuffling and d Curriculum Learning
如果不希望模型学习到训练数据顺序中所隐含的信息,需要在训练时每个epoch结束后打乱(shuffling)训练数据;而如果输入的训练数据的顺序信息对于提高模型的性能有帮助,或者希望模型学习到隐藏在训练数据顺序中的信息,则不应该进行 shuffling 操作,而应该将数据按照某种特定的顺序输入到模型训练过程中,这种方法称为 curriculum learning。
Batch normalization
Batch Normalization要解决的问题[12]是:
…, the distribution of each layer’s inputs changes during training, as the parameters of the previous layers change.
也就是说每一层参数的变动会影响该层输出的分布,即会影响后一层输入数据的分布,这个问题在神经网络中带来的影响是:
… requiring lower learning rates and careful parameter initialization, and makes it notoriously hard to train models with saturating nonlinearities. We refer to this phenomenon as internal covariate shift.
可见,它会限制学习率、参数初始化和激活函数的选择,否则当网络层数比较多时容易出现梯度消失或爆炸问题,为此,Sergey Ioffe 提出Batch Normalization的方法,它通过对每一层 mini-batch 个输入数据进行 normalization 处理,使得输入的 mini-batch 个数据的均值为0,方差为1,具体的计算公式为:

但是,单纯的对输入数据进行 Batch Normalization 操作会限制激活函数的表达能力:
… simply normalizing each input of a layer may change what the layer can represent. For instance, normalizing the inputs of a sigmoid would constrain them to the linear regime of the nonlinearity.
为此,为每一层的增加一组可以学习的参数$\gamma, \beta$,用于保存每一层的表达能力。将Batch Normalization 应用到神经网络中,对应的算法流程如下:

Batch Normalization 带来的好处包括:
- 能够选择较大的学习率而不会造成梯度消失或爆炸的问题;
- 能够规格化(regularize)模型,避免过拟合问题,具有跟dropout相同的作用;
- makes training more resilient to the parameter scale,也就是说各层权重的大小不会影响反向梯度值,带来的另一个好处是初始权重的选择对于模型的影响会比较小。
SELUs
SELUS[13]是一种改进的具有 self-normalizing 性质的激活函数,它的出发点是让神经网络自动的收敛到均值为0、方差为1的分布,因此不需要像 Batch Normalization 一样进行手动的 Normalization 转换。SELUs的表达式为:
$$
selu(x) = \lambda
\begin{cases}
x, \; &\text{if x > 0} \\
\alpha (e^x - 1), \; &\text{else}
\end{cases}
$$
其中的两个常数是固定的(如下代码中所示),作者证明通过使用SELUs激活函数后激活值方差的上下界来说明梯度的消失或爆炸不可能发生。因此使用SELUs构建的神经网络(SNN)的层数能够更深,对输入数据中的噪声也更具有鲁棒性。下面的代码是作者对SELUs的实现[14],非常的简单,其中的tf.nn.elu(x)表示$e^x - 1$:
early stopping
early stopping用来解决模型的过拟合问题,它的思路是:跟踪模型在验证集上的性能指数值$\tau$(正确率、MAE等),如果$\tau$的值连续没有提升的次数超过某一个预设的阈值时,我们便停止训练,避免模型过拟合。
Gradient noise
Gradient noise 解决的问题是深层网络的过拟合问题以及差初始化权重(poor initialization)的鲁棒性问题,它的方法是向每一次迭代更新后的参数中加入服从某一个随时间变化的高斯噪声,如下:
其中噪声的方差所遵循的退火规律(anneal schedule)为:
$$
\sigma_t^2 = { \eta \over (1+t)^{\gamma}}
$$
Annealing the learning rate
除了上面提到的自适应优化算法,对于一般的优化算法,往往在开始时设定一个比较大的学习率,然后在训练时再慢慢减小学习率,这样带来的效果是“ quickly learning good weights early and fine tuning them later[16] ”,下面是三种常见的学习率decay的方法[15]:
- step decay:每训练一定量的epoch就将学习率减小一定的比例,一般可以通过观察固定学习率下模型在验证集上的误差率等曲线的变化来确定大概需要多少epoch进行一次学习率decay;
- Exponential decay:$\alpha = \alpha_0 e^{-k t}$,其中$\alpha_0, k$是超参数,$t$是迭代次数或者epoch次数;
- 1/t decay:类似于上一种方法,具体形式不同$\alpha = \alpha_0 / (1 + k t )$,参数意义与上一种类似。
参考文献
- ADAM: A METHOD FOR STOCHASTIC OPTIMIZATION;
- Adam implement from Keras;
- Geoffrey Hinton. Neural networks for machine learning;
- Adaptive Subgradient Methods for Online Learning and Stochastic Optimization;
- A post about comparison of optimization techniques;
- Matthew D. Zeiler. ADADELTA: AN ADAPTIVE LEARNING RATE METHOD;
- Alexander Ororbia’s answer from Quora about non-stationary setting;
- Matthew Lai’s answer from Quora about non-stationary setting;
- Taesup Moon. optimization ;
- Timothy Dozat. Incorporating Nesterov Momentum into Adam;
- Sebastian Ruder. An overview of gradient descent optimization algorithms;
- Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift;
- Self-Normalizing Neural Networks ;
- Implementation of SELUs by author;
- CS231n Convolutional Neural Networks for Visual Recognition;
- Using Learning Rate Schedules for Deep Learning Models in Python with Keras;
- Numerical Optimization: Understanding L-BFGS;
- Hessian Free Optimization;
- 5 algorithms to train a neural network;