1. 引言
深度学习因其较高的复杂性和众多的参数,我们很不容易训练出好的模型,模型也缺乏可解释性,因此深度学习从业者一般也自嘲自己是“炼丹师”。
为了快速训练出好的模型,过去的学者也总结出来很多技巧进行辅助训练。在前人的基础上,我们可以学习、理解各种影响训练过程和结果的问题及其对应的解决方案。
需要注意的是,根据可用数据的规模和质量、模型的类型和复杂度、任务的类型和应用要求、可用设备的性能等,我们在具体训练时需要解决的问题也不同,应当因地制宜,活学活用。
2. 当梯度≈0时
2.1 判断critical point的类型
2.1.1 理论分析
梯度近似为0的时候,训练损失几乎不再改变,此时损失可能达到了全局最小值,也有可能停在了局部最小值(Local Minima)和马鞍点(Saddle Point)。
我们需要判断梯度近似为0的原因,才能针对性地进行处理,而判断的前提是知道 loss function 的形状。然而,深度学习的网络非常复杂,我们无法得知完整的 loss function,但可以使用泰勒公式近似局部的 loss function来帮助我们稳定点(critical point)的位置。
如上图所示,可以使用点 θ ′ \theta' θ′的值来近似 θ \theta θ的值,其中绿线是一阶导数项的贡献,红线是二阶导数项的贡献。
当处于critical point时,一阶导数为0,此时需要通过二阶导数项来判断具体类型。
我们可以发现,不同critical point周边点的大小是不同的,可以直观判断出所处的类型,但问题在于我们实际上无法绘制出类似的图形来进行判断。正确方式是基于 Hessian 矩阵的奇异值来判断。
2.1.2 举例说明
为方便理解,我们构建了一个最简单的神经网络,而且仅用一个样本训练,然后计算损失分布。
在上图中,颜色越亮,说明损失值就越大。critical point有七个,分为 Minima 和 saddle 两种。
在已知损失函数的情况下,我们可以直接计算出 critical point 的一阶和二阶微分(以(0,0)点为例),然后根据二阶微分的值判断出该点的类型为 saddle point。
类似地,我们也可以判断其他6个 critical point 的类型。
2.2 应对方案
2.2.1 Saddle Point
Hessian
如果遇到了 saddle point,不用担心,可以通过 Hessian 计算出可行的方向进行参数更新。
按照之前的示例,我们可以计算得出更新的方向可以为(1,1)。
但实际上,这种寻找参数更新方向的方法计算量天大,实际还有其他高效的计算方法。
Momentum
Momentum 将现实世界中的惯性概念引入到了模型训练中,是一项对抗 saddle point 和 local Minima 的技术。它的主要思想是,让每次的更新步伐在本次梯度的基础上加上之前梯度的值,这样可以避免本次梯度为0时模型不再更新的问题。
更具体的运算过程如下图所示:
要注意的是,Momentum 不仅考虑了过去的梯度的值,还包括梯度的方向。
2.2.2 Local Minima
另外要注意的是,在高维空间中,Local Minima几乎不会出现,因为不大可能所有的维度的梯度都近似为0。低维空间中找不到的路,在高维空间可能很容易找到。
实证研究也证实了这一点,如下图所示。因此,我们也不必担心 Local Minima 的问题。
3. 当梯度≠0但训练损失不下降
3.1 原因分析
除了 critical point 的问题外,我们经常遇到的问题是损失函数不变,梯度却来回振荡。
我们在实际训练的时候,即使是一个很简单的深度学习模型,我们都可能 train 不起来。
背后的原因在于,不同的参数使用的学习率的相同的,A参数合适的学习率对于B参数可能就会过小,也可能过大导致无法收敛。因此,我们需要为不同的参数设置不同的学习率。
3.2 应对方案
根据调整学习率的不同方式,有不同的方法可以使用来解决梯度振荡的问题。
3.2.1 Adagrad
Adagrad 使用 Root Mean Square 作为参数依赖的
σ
\sigma
σ,考虑过去梯度调整当前学习率。
3.2.2 RMSProp
即使是同一个参数,在更新过程中可能会经历梯度逐渐下降和上升的不同过程,需要快速反应。
以下是具体的算法过程,其中我们需要设置权重
α
\alpha
α 来表示过去梯度的重要性。
下图可以形象表达 RMSProp 的工作特点,相比 Adagrad,它给予不同时期梯度以不同的权重。
3.2.3 Adam
Adam 是目前最常用的模型训练方法,需要设置超参数。但是默认的超参数设置已经很好了。
3.2.4 Learning Rate Scheduling
当使用 Adagrad 训练模型时,若某个维度一直未能更新,则
σ
\sigma
σ 会变得很小导致很大的学习率,该维度会经历一次很大的剧变,然后逐渐收敛回来,周而复始。
解决这个问题的方法是动态调整 learning Rate 的数值,大方向是随着时间而降低。
经过实验发现,Warm up在训练 Bert 模型时很好用,一种解释是刚开始的时候因为数据量不足、统计值不具备代表性所以先降低学习率避免风险。
3.2.5 总结
4. 权衡模型训练的速度和质量
4.1 Batch 的一般理解
Batch 是深度学习模型训练时经常使用的方法,它不使用所有样本的损失总和去做微分,而是使用部分(batch size)样本的损失做微分。一般认为,small Batch 的使用会加快参数更新的速度,但会降低看完整个数据集(epoch)的速度,以及每次参数更新的质量不够高。
然而,实际上我们可以使用GPU平行计算加快大batch的运算速度(非倍增),但存在极限。
另一方面,当batch较小时,运算完一个epoch所需要的时间会很大。
也就是说,当使用了GPU后,Full batch 的参数更新速度快且稳定,优于 batch,是这样吗?
4.2 Small Batch 的出乎意料
反直觉的是,实验发现 smaller batch 反而增强了模型训练后的效果。noisy 反而是有价值的?
不稳定有助于跳出 critical point
不稳定有助于收敛到平稳 Minima
当处于平稳 Minima 时,测试集与训练集的细微变化对结果的影响不会很大,避免overfiting。
4.3 Batch 使用总结
目前也有很多学者探讨如何结合 small batch 和 large batch的优势,达到鱼与熊掌兼得的目的。
5. 重置模型的损失函数及影响
损失函数会影响我们训练模型的难度以及最终模型的性能。因此,我们可以尝试修改损失函数。
5.1 更改损失函数
回归和分类是机器学习中常见的两个任务,对于任务完成的好坏也有不同的损失函数,如果错用可能很难训练出满意的模型。例如,如果将一个分类任务当做回归任务来处理,可能会出现问题。
与回归不同的是,在处理分类任务时,我们需要使用 one-hot vector 来处理类别变量。即,在神经网络模型中,回归任务的输出只有一项,而分类任务会有很多项。
为了更方便的进行分类,一般会使用softmax函数将连续值映射为0-1的值。
注意:Softmax和 Sigmoid 本质上是一样的,后者用于二分类任务中。
分类任务的损失函数一般使用 cross-entropy,要比使用 MSE 要好得多。
从下图中可以直观发现使用 MSE 训练分类模型的不足:很容易陷入 critical point。
5.2 更改输入变量
除了更改损失函数外,我们还可以更改输入的变量尺度,来间接降低模型训练的难度。
从上图可知,改变两个参数相同大小,参数对应的变量范围大小决定了影响模型性能的程度。反之,如果将变量的范围归一化,那么不同参数的敏感性就相同,可以使用相同的学习率。
在实际操作中,可以对
z
z
z 或
a
a
a 做标准化,结果差异并不大,任意选择一个即可。
要注意在每一层网络中都要进行标准化的操作,而不仅仅是一开始做一次而已。另外,均值和方法的计算需要考虑所有样本,但这样计算效率就会很低,可以考虑使用Batch Normalization技术。
Batch 需要设置足够大,以便计算有效的均值和方差(保证代表性)。
β
\beta
β 和
γ
\gamma
γ 是引入是为了防止均值为0带来的负面影响,具体值由学习得到。
再具体实施时,我们不能等到样本达到一个 Batch 才开始计算。当达不到时,我们使用移动平均来考虑之前的数据进行均值和方差的计算。
实验表明,Batch normalization 可加快收敛速度,但未必提高收敛质量。目前没有很好的解释。