Deep Learning Note: 2-3 优化问题

1. 标准化输入

  对输入数据进行标准化(Normalizing)有利于提高学习速度。

图 1

图 1

  例如对于如图 1 所示的数据,其标准化过程分为两步,首先让数据减去其均值(如图 2 中左图),然后再除以方差(如图 2 中右图):

图 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 所示。

图 3

图 3

  进行正则化之后,各输入特征都具有 0 均值和单位方差,代价函数的形状会更对称,可以使用较大的学习率,梯度下降可以快速抵达最佳点,如图 4 所示。

图 4

图 4

2. 梯度消失、梯度爆炸

  梯度消失(Gradient Vanishing)和梯度爆炸(Gradient Exploding)指的是在训练非常深的网路时,参数的偏导变得非常大或者非常小的现象,这使得训练变得难以进行下去。

  例如训练一个如图 5 所示的很深的 L 层网络:

图 5

图 5

  为方便计算,使用线性的激活函数 $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 所示的某个节点,

图 6

图 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 移除的节点,在这个固定的网络上进行训练和梯度检查。

  另外,可以在网络初始化参数后和训练一段时间后分别进行梯度检查,因为网络的参数通常会随机初始化为较小的数值,实现中有一些小问题不容易暴露出来,虽然此时梯度下降检查无误,但随着训练的进行,参数逐渐变大,再次进行梯度检查就会发现问题。