Deep Learning Note: 2-3 优化问题
Contents [show]
1. 标准化输入
对输入数据进行标准化(Normalizing)有利于提高学习速度。
例如对于如图 1 所示的数据,其标准化过程分为两步,首先让数据减去其均值(如图 2 中左图),然后再除以方差(如图 2 中右图):
计算过程为:
\begin{equation} x := \frac{x – \mu}{\sigma} \tag{1} \end{equation}
其中 \mu 和 \sigma^2 分别为数据的均值和方差,即:
\begin{equation} \mu = \frac{1}{m} \sum_{i=1}^m x^{(i)} \tag{2} \end{equation}
\begin{equation} \sigma^2 = \frac{1}{m} \sum_{i=1}^m (x^{(i)} – \mu)^2 \tag{3} \end{equation}
如图 2 中右图所示,通过式 (1) 的计算,x_1 和 x2 都具有 0 均值和单位方差。
需要注意的是,如果使用了上述的标准化方法处理了训练数据,那么就要使用训练数据的 \mu 和 \sigma^2 来处理测试数据,即使用相同的方式来缩放训练集和测试集。
如果输入特征的范围差距很大,比如 x_1 的取值范围是 1 到 1000, x_2 的取值范围是 0 到 1,则相应代价函数的形状也会非常不对称,需要使用较小的学习率,梯度下降要经过反复的震荡才能到达最佳点,如图 3 所示。
进行正则化之后,各输入特征都具有 0 均值和单位方差,代价函数的形状会更对称,可以使用较大的学习率,梯度下降可以快速抵达最佳点,如图 4 所示。
2. 梯度消失、梯度爆炸
梯度消失(Gradient Vanishing)和梯度爆炸(Gradient Exploding)指的是在训练非常深的网路时,参数的偏导变得非常大或者非常小的现象,这使得训练变得难以进行下去。
例如训练一个如图 5 所示的很深的 L 层网络:
为方便计算,使用线性的激活函数 g(z) = z,并令隔层的偏置 b^{[l]} 均为 0,此时的网络输出为:
\begin{equation} \hat{y} = W^{[L]}W^{[L-1]}W^{[L-2]}…W^{[2]}W^{[1]}x \end{equation}
第 1 到第 L-1 层权重均为 2 \times 2 的矩阵,假设将它们初始化为大于单位矩阵的值,如 1.5,即:
\begin{equation} W^{[l]} = \begin{bmatrix}1.5 & 0 \\0 & 1.5 \end{bmatrix} \end{equation}
则网络的输出可以表示为:
\begin{equation} \hat{y} = W^{[L]} \begin{bmatrix}1.5 & 0 \\0 & 1.5 \end{bmatrix}^{L-1} x \end{equation}
如果网络非常深,即 L 很大,则由上式得到的 \hat{y} 也非常大。各层的激活值和最终的输出 \hat{y} 会随层数 L 的增加以指数级增长。
反之,若初始化第 1 到第 L-1 层权重为小于单位矩阵的值,如 0.5,即:
\begin{equation} W^{[l]} = \begin{bmatrix}0.5 & 0 \\0 & 0.5 \end{bmatrix} \end{equation}
则有:
\begin{equation} \hat{y} = W^{[L]} \begin{bmatrix}0.5 & 0 \\0 & 0.5 \end{bmatrix}^{L-1} x \end{equation}
如果网络非常深,即 L 很大,则由上式得到的 \hat{y} 会非常小,各层的激活值和最终的输出 \hat{y} 会随层数 L 的增加以指数级缩小。
综上所述,当网络层数很深时,如果每层的权重 W^{[l]} 大于单位矩阵,即便只是大一点点,激活值就会随层数急剧增长,发生爆炸;而如果每层权重 W^{[l]} 小于单位矩阵,激活值就会随层数急剧缩小,逐渐消失。对于参数偏导或梯度,也存在同样的现象。如果梯度非常小,则每一次迭代的步进都会很小,导致学习速度很慢,
3. 深度网络的权重初始化
通过合理地选择网络权重的随机初始化,可以部分解决前面提到的梯度消失和梯度爆炸问题。
例如对于如图 6 所示的某个节点,
假设它有 n 个输入 x_1、x_2、…x_n,则 z 的计算为:
\begin{equation} z = w_1 x_1 + w_2 x_2 + … + w_n x_n + b \end{equation}
当输入的维度 n 非常大时,上式等号右边的项数越多,如果想要保持 z 不要太大,则相应的 w_i 各值需要比较小,即使得上式等号右边每一项的值变得较小。
如果使用 Tanh 激活函数,通常令 w_i 的方差为 \frac{1}{n},即第 l 层权重 W^{[l]} 的方差为其输入维度 n^{[l-1]} 的导数:
\begin{equation} Var(W^{[l]}) = \frac{1}{n^{[l-1]}} \tag{4} \end{equation}
在实现中,可以初始化高斯分布的随机数,然后乘以 \sqrt{\frac{1}{n^{[l-1]}}},得到一组方差为 \frac{1}{n^{[l-1]}} 的随机数。在 Python 和 NumPy 中的实现方法为:
w[l] = np.random.randn(n[l-1], n[l]) * np.sqrt(1/n[l-1])
这种初始化方法称为 Xavier 初始化。
如果使用 RuLU 作为激活函数,则使用下面的方式进行权重的初始化效果较好:
\begin{equation} Var(W^{[l]}) = \frac{2}{n^{[l-1]}} \tag{5} \end{equation}
此外的初始化方式还有:
\begin{equation} Var(W^{[l]}) = \frac{2}{n^{[l-1]} + n^{[l]}} \tag{6} \end{equation}
对权重的初始化方式也可以作为一个超参数进行调优,比如调整分母上的系数等。有时候调整这个权重初始化的超参数会带来一定的效果。
通过使用上述方式初始化各层的权重,可以保证在训练初期各层的输出都比较稳定,在一定程度上解决了梯度爆炸或消失的问题。
4. 梯度检查
通过梯度检查(Gradient Checking)可以检验梯度下降实现的正确性,在反向传播中应用梯度检查,有利于尽早发现实现中的错误。
实现梯度检查,首先要将所有参数 W^{[1]}、b^{[1]}、…W^{[L]}、b^{[L]} 分别重组为向量的形式,然后拼接在一起,形成一个包含所有参数的向量 \theta。此时代价函数可以表示为 \theta 的函数,即:
\begin{equation} J(W^{[1]}, b^{[1]}, …, W^{[L]}, b^{[L]}) = J(\theta) \end{equation}
然后对所有偏导 dW^{[1]}、db^{[1]}、…dW^{[L]}、db^{[L]} 应用同样的处理,重组为向量并拼接在一起,形成一个包含所有参数偏导的向量 d\theta。
现在要验证的问题是,d\theta 是否为 J(\theta) 的梯度。首先使用 J(\theta) 估计其梯度 ,得到 d\theta_{approx}:
\begin{align} for \; & \; each \; i: \\ & d\theta_{approx[i]} = \frac{J(\theta_1,\theta_2,…\theta_i + \epsilon,…) – J(\theta_1,\theta_2,…\theta_i – \epsilon,…)}{2 \epsilon} \end{align}
上式中 \epsilon 是一个很小的值,d\theta_{approx[i]} 是对第 i 个参数的偏导估计,循环估计所有参数偏导,得到 d\theta_{approx} 作为 J(\theta) 的梯度的估计。
然后验证 d\theta_{approx} \approx d\theta,即验证 d\theta 确实是 J(\theta) 的梯度。通过计算式 (7)的结果,
\begin{align} \frac{\Vert d\theta_{approx} – d\theta \Vert_2}{\Vert d\theta_{approx} \Vert_2 + \Vert d\theta \Vert_2 } \tag{7} \end{align}
其中 \Vert x \Vert_2 表示计算向量 x 的欧几里得范数,如果式 (7) 的值与 \epsilon 相差不大,则可以认为梯度下降的实现是正确的。当使用 \epsilon = 10^{-7} 时,一般来说如果式 (7) 的值在 10^{-7} 左右,则可以非常确认 d\theta 确实是 J(\theta) 的梯度;如果式 (7) 的值在 10^{-5} 左右,则还可以接受,但需要检查一下梯度下降的实现;如果式 (7) 的值在 10^{-3} 左右,则梯度下降的实现可能存在问题,需要仔细检查一下,比如 d\theta_{approx} 中是否有某个值与 d\theta 相差很大,以此为线索进行排查。
梯度检查是一个 Debug 工具,它的计算量很大,不要在训练中使用梯度检查。如果通过梯度检查发现了问题,可以通过比较找出 d\theta_{approx} 中与 d\theta 相差很大的值,并进一步找到这些值对应那一层的什么参数,以此定位问题,缩小排查的范围。
如果使用了正则化,则在进行梯度检查时,J(\theta) 中也要包含正则化项。如果使用了 Dropout,则难以通过代价函数评估整个网络的性能,不能使用梯度检查。但可以先禁用 Dropout,通过梯度检查检验实现的正确性,再启用 Dropout;或者固定 Dropout 移除的节点,在这个固定的网络上进行训练和梯度检查。
另外,可以在网络初始化参数后和训练一段时间后分别进行梯度检查,因为网络的参数通常会随机初始化为较小的数值,实现中有一些小问题不容易暴露出来,虽然此时梯度下降检查无误,但随着训练的进行,参数逐渐变大,再次进行梯度检查就会发现问题。