浅层神经网络(One Hidden Layer Neural Network)
在之前课程中,我们实现了逻辑回归函数,它是一个不包含隐藏层的网络结构。
本节课中,我们将继续神经网络的学习,本文主要来讲解包含一个隐藏层的神经网络结构及其原理。
本文内容概述
之前的文章中,我们讲述了一个逻辑回归的模型如下:
其中,输入参数包含w, x和b,具体计算流程如下:
而对应一个包含隐藏的神经网络而言,其网络结构如下:
其中,每个隐藏层的计算逻辑与之前的输出层计算逻辑一致。
此处,我们用[1]表示第1层,用[2]表示第二层,则上述模型的计算逻辑如下:
此外,在反向计算中,也会有与之前类似的逻辑来计算,我们将在接下来的内容详细讲解。
神经网络的表示
接下来,我们首先讨论的是神经网络图的含义:
对于下图而言:
其中,x1,x2,x3表示其输入层,中间的四个圆圈则表示隐藏层,最后的一个节点是其输出层。
通常,对于一个神经网络训练任务而言,训练集中会包含着输入层数据和期望输出层数据。
而隐藏层数据是我们无法知道而需要进行训练学习的。
在该模型中,我们通常会把输入数据X标记为a[0]。
而对于第一个隐藏层而言,我们会把输出数据标记为a[1]。
此外,由于该隐藏层中,有多个神经元,因此,从上到下我们一次标记为a1[1],a2[1]等。
在输出层,我们将其输出记作a[2]。具体表示见下图:
此外,对于上述网络结构而言,我们称其为一个两层的神经网络。(输入层不包含在内)
神经网络的前向传播计算
回顾一下之前的逻辑回归,其前向传播的计算模型如下:
转换为公式表示如下:
而对于一个包含隐层的神经网络而言呢?则是对每个神经元进行一次类似的操作:
假设一个神经网络的结构如下:
我们首先针对其第一个隐藏层神经元来看看其计算逻辑:
可以看出,其计算逻辑与之前逻辑回归的神经元相同,只是针对特定层及特定节点的w和b。
其中,对于ai[l]而言,l表示它是在第l层,而i则表示它是该层的第i个节点,对于w和b而言,符号表示的含义相同。
同理,对于第二个隐藏层神经元而言,计算逻辑相同:
以此类推,我们可以分别计算出第1层隐藏神经元的值a[1]。
将其进行向量化表示后,结果如下:
最终,得到的矢量化计算公式如下:
训练样本的矢量化
在上一个小节中,我们将每层中不同的节点进行了矢量化,而本节中,我们希望对不同的训练样本进行矢量化。
在之前的符号描述中,我们使用(i)表示第i个样本点。
那么,一个普通的计算逻辑如下:
然而,对于上式的循环任务中,前后没有依赖关系,因此,我们可以对其进行矢量化。
接下来,我们定义一个变量X,它是将每个训练样本作为变量矩阵X中的一列。
其中,X的维度为nx*m。nx表示训练样本的维度,m表示训练样本数量。
此时,计算公式则可以整理如下:
其中,Z[l]和A[l]都是有每个样本作为其中一列组成的。
**函数
对于隐藏层和输出层而言,我们通常会选择一个**函数用于对其输出进行处理。
接下来,我们将对**函数的相关内容进行讲解。
目前为止,我们主要讲解的**函数都是sigmod函数,但实际上,还有一些可能更好用的**函数。
例如tanh函数通常会比sigmod函数用更好的性能。
sigmod函数简图如下所示,其y的取值范围在0到1之间:
而tanh函数简图如下所示,其y的取值范围在-1到1之间:
如果我们选择tanh作为**函数,由于其平均值更接近于0,可以起到中心化的作用,通常更有利用算法优化。
这儿有一个特殊情况,对于一个二分类问题的输出层的**函数,由于我们仅希望对其进行是否判断,其实更类似于0,1判断,因此,此时选择sigmod函数是一个更加合理的选择。
但是,对于sigmod函数和tanh函数,都有一个共同的问题,即当输入变量特别大或者特别小时,斜率非常小,此时,并不利于梯度下降法进行学习。
在机器学习中, 一个非常常用的**函数叫做ReLU函数(修正线性单元函数)。
该函数简图如下:
ReLU有一个缺点就是在输入变量小于0时,其输出始终为0。(实际在应用中没有太大问题)
有一个改进版本为leakly ReLU函数,其在输入变量小于0时,斜率为一个很小的正数。(有略微优点,但使用场景并不多)
总结一下:
sigmod函数,除二分类输出层外,几乎不使用:
tanh函数:
ReLU函数(强烈推荐):
leakly ReLU函数:
**函数不选择线性函数的原因
对于一个前向传播计算而言,计算公式如下:
此时,我们假设g是一个线性函数,那么会有什么影响呢?
从上述的公式推导中,我们可以看出,无论最终有多少隐藏层,得到的最终输出结果仍然是输入的线性组合。
**函数求导
在进行反向传播计算时,需要对**函数进行求导。
例如,对于sigmod函数而言,其函数表单式及函数曲线如下:
而在某一点的导数,则需要计算的函数。
如果了解微积分的话,我们可以证明sigmod函数的导数公式如下:
此处,我们不再讲解基本的微积分知识。
但是我们可以从直观的角度来看一下这个公式的计算结果是否符合我们的期望:
例如,当Z=10时,g(z)约等于1。此时,其导数约等于0。
而当Z=-10时,g(z)约等于0。此时,其导数约等于0。
当Z=0时,g(z)约等于0.5。此时,其导数约等于0.25。此时,斜率最大。
对比图像,我们可以发现计算得到的斜率与sigmod函数图像是相互匹配的。
Ps:在后续的文章中,我们会使用g'(z)来表示dg(z)/dz。
同样,对于tanh函数而言,其函数表单式及函数曲线如下:
当Z=10时,g(z)约等于1。此时,其导数约等于0。
而当Z=-10时,g(z)约等于-1。此时,其导数约等于0。
当Z=0时,g(z)约等于0。此时,其导数约等于1。此时,斜率最大。
最后,我们来看一下ReLU函数和Leaky ReLU函数:
对于ReLU函数而言:
可以发现,当z=0时,ReLU函数的导数不存在,不过这个并不重要,我们可以假设其导数也是1即可。
同理,对于Leaky ReLU函数:
梯度下降法
梯度下降法是神经网络优化算法的根基,接下来,我们将主要了解一下神经网络中梯度下降算法的原理。
以一个两层的神经网络为例:
我们需要计算如下一些参数:
w[1], b[1], w[2], b[2]共四个参数。
其中,四个参数对应的维度分别是(n[1], n[0]), (n[1], 1),
(n[2], n[1]), (n[2], 1)。
此外,我们还知道了代价函数的计算公式:
J(w[1], b[1], w[2], b[2])
= 1/m * sum(L(a[2], y))
而梯度下降法就是将代价函数J对四个参数求偏导数,分别计算出dw[1], db[1], dw[2],
db[2]。
然后将其沿导数方向以一补偿进行迭代即可。
根据一些数学推导后,最终可以得到每次迭代的公式如下:
接下来,我们来了解一下这个迭代公式是怎么来的吧:
首先,以Logistic为例:
再反向传播计算中,我们首先需要计算da,接着可以计算dz,最后来计算dw和db。
在之前的学习中,我们可以知道L计算公式如下:
则da的计算公式为:
而接下来,dz的计算如下:
Ps:其中,g(z)是sigmod函数,sigmod函数的导数在上文中已有提及。
而最终,dw和db的计算则比较简单了:
接下来,就让我们继续来看一个两层的神经网络吧:
Ps:上图中,有一个笔误:
相比之前的Logistic,在两层神经网络中,多了w[2]和b[2]两个参数。
同样,按照之前的逻辑,我们首先需要计算da[2]。
从而,dz[2]可以得到:
根据dz[2],此时,可以计算得到dw[2]和wb[2]:
接下来,da[1]的计算如下:
从而,dz[1]的计算如下:
最终,可以计算得到
最终,我们将其整合并矢量化,可以得到如下结果:
Ps:其中,左侧为总结结果,右侧为矢量化后得到的结果。
参数为什么要随机初始化
在使用神经网络时,参数的初始化也是一个重要的工作。
一个常用的原则是:使用较小的随机数来对参数进行初始化。
里面涉及到两个KeyWords:随机数、较小的数。
接下来,我们来依次解释这两点要求的必要性:
首先,我们来分析一下为什么不能使用一个固定参数(例如0)来对神经网络进行初始化。
假设有两个输入特征x1和x2:
此时,W[1]是一个2*2的矩阵。
假设我们令其为一个2*2的全零矩阵,同时令b[1]也是一个全零的列向量。
那么,此时无论输入的x是什么,经过w[1]和b[1]后,得到的结果是相同的,全部是0。
而此时,由于对称性,最终得到的dz结果如下:
此时,会导致每次训练过程,得到的W矩阵中,第一行与第二行始终相等,即每层中的多个神经元无效,对称性没有被打破。
实际初始化过程通常如下:
- w_1 = np.random.randn((2,2))*0.01
- b_1 = np.zeros((2,1))
- w_2 = np.random.randn((1,2))*0.01
- b_2 =0
Ps:可以注意到两点
1. w进行了随机初始化,b则进行了全0初始化。主要原因是由于b的全0初始化不会导致始终对称。
2. w在初始化时,乘以了一个较小的系数,例如0.01。
下面,我们来分析一下为什么要乘以一个小系数呢?假设我们使用一个较大的参数进行初始化又会导致什么结果呢?
因为我们的**函数中,采用了Sigmod函数或tanh函数,其函数图如下:
当你在计算**函数的值时,如果W很大,会导致sigmod或tanh函数的输入值很大或很小。
此时,则位于函数的平缓部分,导致其计算到的梯度下降非常缓慢。
而越接近于0时,其梯度越明显,收敛速度也相对更快。
因此,我们通常会用一个较小的系数来乘以我们的随机数。