🥙 浅层神经网络 Shallow neural networks
🔈 本节我们将从浅层神经网络入手,开始真正的神经网络模型的学习。
一、神经网络概述 Overview
首先,我们从整体结构上来大致看一下神经网络模型。
前面的课程中,我们已经使用计算图的方式介绍了逻辑回归梯度下降算法的正向传播和反向传播两个过程。如下图所示:
神经网络的结构与逻辑回归类似,只是神经网络的层数比逻辑回归多一层,多出来的中间那层称为隐藏层或中间层。这样从计算上来说,神经网络的正向传播和反向传播过程只是比逻辑回归多了一次重复的计算:
正向传播过程分成两层,第一层是输入层到隐藏层,用上标 [1] 来表示:
$z^{[1]}=W^{[1]}x+b^{[1]}$
$a^{[1]}=\sigma(z^{[1]})$
第二层是隐藏层到输出层,用上标 [2] 来表示:
$z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}$
$a^{[2]}=\sigma(z^{[2]})$
在写法上值得注意的是,方括号上标 [i]
表示当前所处的层数;圆括号上标 (i)
表示第 i 个样本。
同样,反向传播过程也分成两层。第一层是输出层到隐藏层,第二层是隐藏层到输入层。其细节部分我们之后再来讨论。
二、神经网络的表示 Representation
下面系统的总结一下神经网络的表示以及相关术语。
如下所示,单隐藏层神经网络就是典型的浅层(shallow)神经网络。
我们有 3 个输入特征,它们被竖直地堆叠起来,这叫做神经网络的输入层。它包含了神经网络的输入;
中间层我们称之为隐藏层:在一个神经网络中,当你使用监督学习训练它的时候,训练集包含了输入也包含了目标输出,神经网络的神奇之处就在于我们不必去关心中间层(隐藏层)的取值,只需要输入 x,就能得到输出 y。所以人们常说神经网络就像一个黑盒子
在本例中最后一层只由一个结点构成,而这个只有一个结点的层被称为输出层,它负责产生预测值 y。
📐 现在我们再引入几个符号:
这里有个可代替的记号 $a^{[0]}$ 可以用来表示输入特征。$a$ 表示激活 active 的意思,它意味着网络中不同层的值会传递到它们后面的层中,一般来说,我们将输入层称为第 0 层,所以将输入层的值称为 $a^{[0]}$;
🚨 注意:在这里你所看到的这个例子,只能叫做一个两层的神经网络。原因是当我们计算网络的层数时,输入层是不算入总层数内,所以隐藏层是第一层,输出层是第二层。
我们将输入层称为第零层,所以在技术上,这仍然是一个三层的神经网络,因为这里有输入层、隐藏层,还有输出层。但是在传统的符号使用中,如果你阅读研究论文,你会看到人们将这个神经网络称为一个两层的神经网络。
输入层的数据经过激活函数产生激活值进入隐藏层,将其记作 $a^{[1]}$。所以具体地:
这里的第一个单元或结点产生的激活值表示为$a^{[1]}_{1}$,
第二个结点的激活值我们记为$a^{[1]}_{2}$,以此类推。
所以这里的是一个四维的向量,如果写成 Python 代码,那么它是一个规模为4x1的矩阵或一个大小为4的列向量,如下公式,它是四维的,因为在本例中,我们有四个结点或者单元,或者称为四个隐藏层单元:
最后输出层将产生某个数值 $a$,它只是一个单独的实数并处于第 2 层,所以的$\hat{y}$值将取为 $a^{[2]}$。
💡 这与逻辑回归很相似,在逻辑回归中,我们有 $\hat{y}$ 直接等于 $a$,在逻辑回归中我们只有一个输出层,所以我们没有用带方括号的上标。但是在神经网络中,我们将使用这种带上标的形式来明确地指出这些值来自于哪一层。
三、浅层神经网络的正向传播过程
😶 在上一节,我们介绍只有一个隐藏层的神经网络的结构与符号表示。在本节我们将了解神经网络的输出究竟是如何计算出来的。
逻辑回归部分我们提到过,从输入计算一个神经网络的输出过程就是正向传播过程。
① 单个样本的神经网络正向传播过程
如下图所示,用圆圈表示神经网络的计算单元,逻辑回归的计算有两个步骤,首先你按步骤计算出 z,然后在第二步中你以 sigmoid 函数为激活函数计算 z,得出 a(sigmoid(z) = a
),一个神经网络只是这样子做了好多次重复计算。
回到两层的神经网络,我们从隐藏层的第一个神经元开始计算,如下图第一个最上面的箭头所指。从下图可以看出,输入与逻辑回归相似,这个神经元的计算与逻辑回归一样分为两步,小圆圈代表了计算的两个步骤。
第一步,计算$z^{[1]}_1,z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1$。
第二步,通过激活函数计算 $a^{[1]}_1,a^{[1]}_1 = \sigma(z^{[1]}_1)$。
隐藏层的第二个以及后面两个神经元的计算过程一样,只是注意符号表示不同,最终分别得到$a^{[1]}_2、a^{[1]}_3、a^{[1]}_4$,详细结果见下:
$z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1, a^{[1]}_1 = \sigma(z^{[1]}_1)$
$z^{[1]}_2 = w^{[1]T}_2x + b^{[1]}_2, a^{[1]}_2 = \sigma(z^{[1]}_2)$
$z^{[1]}_3 = w^{[1]T}_3x + b^{[1]}_3, a^{[1]}_3 = \sigma(z^{[1]}_3)$
$z^{[1]}_4 = w^{[1]T}_4x + b^{[1]}_4, a^{[1]}_4 = \sigma(z^{[1]}_4)$
向量化计算:如果你执行神经网络的程序,用 for 循环来做这些看起来真的很低效。所以接下来我们要做的就是把这四个等式向量化。向量化的过程是将神经网络中的一层神经元参数纵向堆积起来,例如隐藏层中的 $w$ 纵向堆积起来变成一个$(4,3)$的矩阵,用符号$W^{[1]}$表示。因此: $z^{[n]} = w^{[n]}x + b^{[n]}$,$a^{[n]}=\sigma(z^{[n]})$
详细过程见下:
对于神经网络的第一层,给予一个输入$x$,得到$a^{[1]}$,$x$可以表示为$a^{[0]}$。通过相似的衍生你会发现,后一层的表示同样可以写成类似的形式,得到$a^{[2]}$,$\hat{y} = a^{[2]}$
② 多个样本的神经网络正向传播过程
上一部分我们只是介绍了单个样本的神经网络正向传播矩阵运算过程。而对于 m 个训练样本,我们也可以使用矩阵相乘的形式来提高计算效率。而且它的形式与上一部分单个样本的矩阵运算十分相似,比较简单。
之前我们也介绍过,在书写标记上用上标(i)表示第i个样本,例如$x^{(i)}$,$z^{(i)}$,$a^{[2](i)}$。对于每个样本 i,可以使用 for循环来求解其正向输出:
for i = 1 to m:
$\ \ \ \ z^{[1](i)}=W^{[1]}x^{(i)}+b^{[1]}$
$\ \ \ \ a^{[1](i)}=\sigma(z^{[1](i)})$
$\ \ \ \ z^{[2](i)}=W^{[2]}a^{[1](i)}+b^{[2]}$
$\ \ \ \ a^{[2](i)}=\sigma(z^{[2](i)})$
不使用for循环,利用矩阵运算的思想,输入矩阵X的维度为($n_x$,m)。这样,我们可以把上面的for循环写成矩阵运算的形式:
$Z^{[1]}=W^{[1]}X+b^{[1]}$
$A^{[1]}=\sigma(Z^{[1]})$
$Z^{[2]}=W^{[2]}A^{[1]}+b^{[2]}$
$A^{[2]}=\sigma(Z^{[2]})$
其中,$Z^{[1]}$ 的维度是(4,m),4是隐藏层神经元的个数;$A^{[1]}$ 的维度与$Z^{[1]}$相同;$Z^{[2]}$和 $A^{[2]}$ 的维度均为(1,m)。👍 对上面这四个矩阵来说,均可以这样来理解:行表示神经元个数,列表示样本数目m。
四、浅层神经网络的反向传播过程
逻辑回归部分我们提到过,反向传播过程的目的就是快速计算各个参数的偏导数
浅层神经网络(包含一个隐藏层),单个训练样本(左)和 m 个训练样本(右)的反向传播过程包含了6个表达式,其向量化矩阵形式如下图所示:
五、激活函数 Activation functions
使用一个神经网络时,需要决定使用哪种激活函数用隐藏层上,哪种用在输出节点上。到目前为止,我们只用过sigmoid激活函数,但是,有时其他的激活函数效果会更好。
下面系统的总结一下常见的激活函数:
sigmoid 函数
📈 导数: $\frac{d}{dz}g(z) = {\frac{1}{1 + e^{-z}} (1-\frac{1}{1 + e^{-z}})}=g(z)(1-g(z))$
注:
当$z$ = 10 或 $z= -10$ ; $\frac{d}{dz}g(z)\approx0$
当$z $= 0 , $\frac{d}{dz}g(z)\text{=g(z)(1-g(z))=}{1}/{4}$
在神经网络中 $a= g(z)$; $g{{(z)}^{'}}=\frac{d}{dz}g(z)=a(1-a)$
tanh 函数
📈 导数:$\frac{d}{{d}z}g(z) = 1 - (tanh(z))^{2}$
注:
当$z$ = 10或$z= -10$ $\frac{d}{dz}g(z)\approx0$
当$z$ = 0, $\frac{d}{dz}g(z)\text{=1-(0)=}1$
ReLU 函数
📈 导数:
注:通常在$z$= 0的时候给定其导数1,0;当然$z$=0的情况很少
Leaky ReLU 函数
📈 导数:与 ReLU 类似
注:通常在$z = 0$的时候给定其导数1,0.01;当然$z=0$的情况很少。
如上图所示,不同激活函数形状不同,a 的取值范围也有差异。❓ 如何选择合适的激活函数呢?
首先我们来比较sigmoid函数和tanh函数。对于隐藏层的激活函数,一般来说,tanh函数要比sigmoid函数表现更好一些。因为tanh函数的取值范围在[-1,+1]之间,隐藏层的输出被限定在[-1,+1]之间,可以看成是在0值附近分布,均值为0。这样从隐藏层到输出层,数据起到了归一化(均值为0)的效果。因此,隐藏层的激活函数,tanh比sigmoid更好一些。而对于输出层的激活函数,因为二分类问题的输出取值为{0,+1},所以一般会选择sigmoid作为激活函数。
观察sigmoid函数和tanh函数,我们发现有这样一个问题,就是当 |z| 很大的时候,激活函数的斜率(梯度)很小。因此,在这个区域内,梯度下降算法会运行得比较慢。在实际应用中,应尽量避免使z落在这个区域,使|z|尽可能限定在零值附近,从而提高梯度下降算法运算速度。
为了弥补sigmoid函数和tanh函数的这个缺陷,就出现了 ReLU 激活函数。ReLU 激活函数在 z 大于零时梯度始终为1;在z小于零时梯度始终为0;z等于零时的梯度可以当成1也可以当成0,实际应用中并不影响。对于隐藏层,选择ReLU作为激活函数能够保证z大于零时梯度始终为1,从而提高神经网络梯度下降算法运算速度。但当z小于零时,存在梯度为0的缺点,实际应用中,这个缺点影响不是很大。为了弥补这个缺点,出现了Leaky ReLU激活函数,能够保证 z 小于零时梯度不为0。
⭐ 最后总结一下,如果是分类问题,输出层的激活函数一般会选择sigmoid函数。但是隐藏层的激活函数通常不会选择sigmoid函数,tanh函数的表现会比sigmoid函数好一些。实际应用中,通常会会选择使用ReLU或者Leaky ReLU函数,保证梯度下降速度不会太小。其实,具体选择哪个函数作为激活函数没有一个固定的准确的答案,应该要根据具体实际问题进行验证(validation)。
六、为什么需要非线性激活函数 Nonlinear activation function
我们知道上一部分讲的四种激活函数都是非线性(non-linear)的。那是否可以使用线性激活函数呢?答案是不行!下面我们就来进行简要的解释和说明。
假设所有的激活函数都是线性的,为了简化计算,我们直接令激活函数 $g(z)=z$,即 $a = z$。那么,浅层神经网络的各层输出为:
$z^{[1]}=W^{[1]}x+b^{[1]}$
$a^{[1]}=z^{[1]}$
$z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}$
$a^{[2]}=z^{[2]}$
我们对上式中$a^{[2]}$进行化简计算:
$a^{[2]}=z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}=W^{[2]}(W^{[1]}x+b^{[1]})+b^{[2]}=(W^{[2]}W^{[1]})x+(W^{[2]}b^{[1]}+b^{[2]})=W'x+b'$
经过推导我们发现$a^{[2]}$仍是输入变量x的线性组合。这表明,使用神经网络与直接使用线性模型的效果并没有什么两样。即便是包含多层隐藏层的神经网络,🚨 如果使用线性函数作为激活函数,最终的输出仍然是输入 x 的线性模型。这样的话神经网络就没有任何作用了。因此,隐藏层的激活函数必须要是非线性的。
另外,如果所有的隐藏层全部使用线性激活函数,只有输出层使用非线性激活函数,那么整个神经网络的结构就类似于一个简单的逻辑回归模型,而失去了神经网络模型本身的优势和价值。
值得一提的是,如果是预测问题而不是分类问题,输出 y 是连续的情况下,输出层的激活函数可以使用线性函数。如果输出 y 恒为正值,则也可以使用 ReLU 激活函数,具体情况,具体分析。
七、神经网络的梯度下降 Gradient descent for neural networks
单隐藏层神经网络会有$W^{[1]}$,$b^{[1]}$,$W^{[2]}$,$b^{[2]}$这些参数,还有个$n_x$表示输入特征的个数,$n^{[1]}$表示隐藏单元个数,$n^{[2]}$表示输出单元个数。
矩阵$W^{[1]}$的维度就是($n^{[1]}, n^{[0]}$),$b^{[1]}$就是$n^{[1]}$维向量,可以写成$(n^{[1]}, 1)$,就是一个的列向量。 矩阵$W^{[2]}$的维度就是($n^{[2]}, n^{[1]}$),$b^{[2]}$的维度就是$(n^{[2]},1)$维度。
你还有一个神经网络的代价函数,假设你在做二分类任务,那么你的代价函数等于:
Cost function 公式: $J(W^{[1]},b^{[1]},W^{[2]},b^{[2]}) = {\frac{1}{m}}\sum_{i=1}^mL(\hat{y}, y)$
(loss function 和之前做 logistic回归完全一样:$L(\hat y,y)=-(ylog\ \hat y+(1-y)log\ (1-\hat y))$)
训练参数需要做梯度下降,在训练神经网络的时候,随机初始化参数很重要,而不是初始化成全零。当你参数初始化成某些值后,每次梯度下降都会循环计算以下预测值:$\hat{y}^{(i)},(i=1,2,…,m)$$dW^{[1]} = \frac{dJ}{dW^{[1]}},db^{[1]} = \frac{dJ}{db^{[1]}}$${d}W^{[2]} = \frac{{dJ}}{dW^{[2]}},{d}b^{[2]} = \frac{dJ}{db^{[2]}}$
其中
$W^{[1]}\implies{W^{[1]} - adW^{[1]}},b^{[1]}\implies{b^{[1]} -adb^{[1]}}$
$W^{[2]}\implies{W^{[2]} - \alpha{\rm d}W^{[2]}},b^{[2]}\implies{b^{[2]} - \alpha{\rm d}b^{[2]}}$
八、随机初始化 Random Initialization
神经网络模型中的参数权重 W 是不能全部初始化为零的,接下来我们分析一下原因。
举个简单的例子,一个浅层神经网络包含两个输入,隐藏层包含两个神经元。如果权重$W^{[1]}$和$W^{[2]}$都初始化为零,即:
$W^{[1]}$=
$W^{[2]}$=
这样使得隐藏层第一个神经元的输出等于第二个神经元的输出,即$a_2^{[1]}$。经过推导得到$dz_1^{[1]}=dz_2^{[1]}$,以及 $dW_2^{[1]}$。因此,这样的结果是隐藏层两个神经元对应的权重行向量$W_1^{[1]}$和$W_2^{[1]}$每次迭代更新都会得到完全相同的结果,$W_1^{[1]}$始终等于$W_2^{[1]}$,完全对称。这样隐藏层设置多个神经元就没有任何意义了。
值得一提的是,参数 b 可以全部初始化为零,并不会影响神经网络训练效果。
我们把这种权重 W 全部初始化为零带来的问题称为 symmetry breaking problem。解决方法也很简单,就是将W进行随机初始化(b可初始化为零)。python里可以使用如下语句进行W和b的初始化:
W_1 = np.random.randn((2,2)) * 0.01
b_1 = np.zero((2,1))
W_2 = np.random.randn((1,2)) * 0.01
b_2 = 0
🚩 这里我们将$W_1^{[1]}$和$W_2^{[1]}$乘以0.01的目的是尽量使得权重W初始化比较小的值。之所以让W比较小,是因为如果使用sigmoid函数或者tanh函数作为激活函数的话,W比较小,得到的 |z| 也比较小(靠近零点),而零点区域的梯度比较大,这样能大大提高梯度下降算法的更新速度,尽快找到全局最优解。如果W较大,得到的|z|也比较大,附近曲线平缓,梯度较小,训练过程会慢很多。
当然,如果激活函数是ReLU或者Leaky ReLU函数,则不需要考虑这个问题。但是,如果输出层是sigmoid函数,则对应的权重W最好初始化到比较小的值。