Deep Learning Note: 1-9 深度神经网络
前面以一个简单的 2 层神经网络为例,介绍了前向传播和反向传播的计算过程。下面扩展到 L 层神经网络的情况。
以图 1 所示的网络为例,记网络的层数为 L,此时 L=4。使用 n[l] 表示第 l 层中节点的个数,记输入层为第 0 层,输入有三个特征,故 n[0]=nx=3;第一个隐藏层有 5 个节点,故 n[1]=5,以此类推,n[2]=5,n[3]=3,n[4]=n[L]=1。使用 a[l] 表示第 l 层的激活值,则有 a[l]=g[l](z[l]),其中 g[l] 为第 l 层的激活函数。使用 W[l]、b[l] 分别表示第 l 层的权重和偏置。
Contents [show]
1. 计算网络输出
对于图 1 所示的网络,单个样本前向传播的计算步骤为:
- 第一层:
z[1]=W[1]x+b[1]=W[1]a[0]+b[1]
a[1]=g[1](z[1])
- 第二层:
z[2]=W[2]a[1]+b[2]
a[2]=g[2](z[2])
- 第三层:
z[3]=W[3]a[2]+b[3]
a[3]=g[3](z[3])
- 第四层(输出层):
z[4]=W[4]a[3]+b[4]
a[4]=g[4](z[4])
更一般地,可以将单个样本前向传播第 l 层的计算写为如下的形式:
z[l]=W[l]a[l−1]+b[l]
a[l]=g[l](z[l])
m 个样本前向传播的向量化实现形式为:
Z[l]=W[l]A[l−1]+b[l]
A[l]=g[l](Z[l])
式 (3)、(4) 向量化地计算了 m 个样本在第 l 层上的前向传播,但无法向量化整个 L 层网络的计算,仍需要逐层计算各层的激活值,作为下一层的输入,逐层向前传播到输出层。
2. 检查矩阵的维数
如前所示的计算涉及大量的矩阵运算,通过检查各矩阵的维数是否匹配,可以快速地对计算的合法性和正确性进行初步校验。
以图 2 所示的网络为例,首先看第 1 层,有:
z[1]=W[1]x+b[1]
上式中 z[1] 的大小与第一层节点数相同,是一个 n[1]×1 即 3×1 的向量;x 是一个 n[0]×1 即 2×1 的向量;W[1] 是一个 n[1]×n[0] 即 3×2 的矩阵,它与 2×1 的 x 相乘得到一个 3×1 的向量,与 z[1] 的大小相符。b[1] 的大小与 W[1]x 和 z[1] 相同,是一个 n[1]×1 即 3×1 的向量。
一般的,对于第 l 层,z[l] 和 a[l] 都是 n[l]×1 的向量,权重 W[l] 是一个 n[l]×n[l−1] 的矩阵,偏置 b[l] 是一个 n[l]×1 的向量。
由此可以快速得到各层参数的大小为:
- W[2]:5×3,b[2]:5×1
- W[3]:4×5,b[3]:4×1
- W[4]:2×4,b[4]:2×1
- W[5]:1×2,b[5]:1×1
在进行反向传播时,dW[l] 和 db[l] 的大小与 W[l] 和 b[l] 相同,分别为 n[l]×n[l−1] 和 n[l]×1。
下面考虑向量化计算 m 个样本的情况,对第一层,有:
Z[1]=W[1]X+b[1]
上式中 Z[1] 的大小为 n[1]×m;W[1] 的大小不变,仍为 n[1]×n[0];X[1] 的大小为 n[0]×m;对于 b[1],仍可以将它看成是 n[1]×1 的向量,但在实际运算中,要把它复制并水平叠加成 n[1]×m 的形式,才能与 W[1]X 相加,在 Python 中可以利用其广播机制自动完成这一操作。
在向量化计算 m 个样本的时候,对于第 l 层,W[l] 和 b[l] 的大小与计算单个样本的情况一致,分别为 n[l]×n[l−1] 和 n[l]×1。而对于 Z[l] 和 A[l],以及反向传播时的偏导 dZ[l] 和 dA[l],都是 n[l]×m 的矩阵。
3. 深度神经网络的构造块
对于网络的第 l 层,参数为权重 W[l] 和偏置 b[l]。前向传播时,输入为 a[l−1],输出为 a[l],计算过程为:
Z[l]=W[l]a[l−1]+b[l]
a[l]=g[l](Z[l])
在计算式 (5) 时,通常会缓存 Z[l] 的结果,直接用于后续的反向传播。在进行反向传播时,输入为 da[l],以及前面缓存的 Z[l]。输出为 da[l−1],以及参数的梯度 dW[l]、db[l]。
由此得到神经网络的基本结构如图 3 所示。
图 3 中,对于神经网络的第 l 层,首先通过前向传播,由第 l−1 层的激活值 a[l−1] 和第 l 层的参数 W[l]、b[l],计算得到第 l 层的激活值 a[l],并缓存中间结果 Z[l]。然后通过反向传播,由第 l 层的激活值的导数 da[l] 和参数 W[l]、b[l],计算得到第 l 层各参数的梯度 dW[l]、db[l],以及第 l−1 层的激活值的导数 da[l−1]。使用梯度 dW[l]、db[l] 更新该层的参数 W[l]、b[l]:
\begin{equation} W^{[l]} = W^{[l]} – \alpha dW^{[l]} \tag{7} \end{equation}
\begin{equation} b^{[l]} = b^{[l]} – \alpha db^{[l]} \tag{8} \end{equation}
以图 3 所示的结构为基础,可以将神经网络抽象为如图 4 所示的形式。
4. 前向传播
结合前面计算网络输出的过程和网络的基本结构,可以得到第 l 层前向传播的计算过程为:
- 输入:a^{[l-1]}
- 输出:a^{[l]},缓存 z^{[l]}
- 单个样本的计算:
\begin{equation} z^{[l]} = W^{[l]}a^{[l-1]} + b^{[l]} \tag{9} \end{equation}
\begin{equation} a^{[l]} = g^{[l]}(z^{[l]}) \tag{10} \end{equation}
- 向量化计算 m 个样本:
\begin{equation} Z^{[l]} = W^{[l]}A^{[l-1]} + b^{[l]} \tag{11} \end{equation}
\begin{equation} A^{[l]} = g^{[l]}(Z^{[l]}) \tag{12} \end{equation}
5. 反向传播
结合前文反向传播的推导和网络的基本结构,可以得到第 l 层前向传播的计算过程为:
- 输入:da^{[l]}
- 输出:da^{[l-1]},dW^{[l]},db^{[l]}
- 单个样本的计算:
\begin{equation} dz^{[l]} = da^{[l]} * g^{[l]\prime}(z^{[l]}) \tag{13} \end{equation}
\begin{equation} dW^{[l]} = dz^{[l]} a^{[l-1]} \tag{14} \end{equation}
\begin{equation} db^{[l]} = dz^{[l]} \tag{15} \end{equation}
\begin{equation} da^{[l-1]} = W^{[l]T} dz^{[l]} \tag{16} \end{equation}
- 向量化计算 m 个样本:
\begin{equation} dZ^{[l]} = dA^{[l]} * g^{[l]\prime}(Z^{[l]}) \tag{17} \end{equation}
\begin{equation} dW^{[l]} = \frac{1}{m} dZ^{[l]} A^{[l-1]T} \tag{18} \end{equation}
\begin{equation} db^{[l]} = \frac{1}{m} np.sum(dZ^{[l]}, axis = 1, keepdims = True) \tag{19} \end{equation}
\begin{equation} dA^{[l-1]} = W^{[l]T} dZ^{[l]} \tag{20} \end{equation}
如果使用式 (21) 作为损失函数,其中 \hat{y} 为第 L 层的输出,即 a^{[L]},则 da^{[L]} 如式 (22) 所示。
\begin{equation} L(\hat{y}, y) = -y\log{\hat{y}} – (1 – y)log(1- \hat{y}) \tag{21} \end{equation}
\begin{equation} da^{[L]} = d\hat{y} = -\frac{y}{\hat{y}} + \frac{1-y}{1-\hat{y}} \tag{22} \end{equation}
6. 参数和超参数
训练神经网络时涉及到的参数除了模型本身的参数 W 和 b,还有其他一些参数,如学习率 \alpha、迭代次数、隐藏层数量 L、隐藏单元数量 n^{[l]}、激活函数等,这些参数并不直接作用于预测,但它们会在一定程度上控制或影响 W 和 b 的学习过程和结果,这些参数称为超参数(Hyperparameter)。
除了上面提到的,在训练神经网络的过程中涉及的超参数还有很多,比如动量(Momentum)项、最小批大小(Minibatch Size)、正则化(Regularization)形式等等。
由于涉及到众多的超参数,通常很难在一开始就找到最佳的选择,往往需要尝试各种选择和取值,进行比较。应用机器学习是一个非常依赖经验的过程,比如对某个超参数的取值有了一个猜想,那么接下来就要把它实现出来,进行实验,根据实验结果对超参数进行必要的调整,再进行实现···如此循环。
另一方面,即便找到了较好的超参数,随着外界条件的变化,比如计算环境的变化,原有的超参数不再适用于新的环境,无法达到原来的性能。所以通常每隔一段时间,比如几个月或几年,需要重新尝试各种不同的超参数,检查是否有新的更好的参数选择。