深度学习入门笔记(六):浅层神经网络

专栏——深度学习入门笔记

声明

1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献。
2)本文仅供学术交流,非商用。因此每一部分具体的参考资料并无详细对应。若是某部分不当心侵犯了你们的利益,还望海涵,并联系博主删除。
3)博主才疏学浅,文中若有不当之处,请各位指出,共同进步,谢谢。
4)此属于初版本,如有错误,还需继续修正与增删。还望你们多多指点。你们都共享一点点,一块儿为祖国科研的推动添砖加瓦。html

深度学习入门笔记(六):浅层神经网络

1 、神经网络概述

关于神经网络的概述,具体的能够看这个博客——大话卷积神经网络CNN(干货满满),我在其中详细的介绍了 人类视觉原理神经网络卷积神经网络的定义和相关网络结构基础,还有 应用关于深度学习的本质的探讨,若是你须要 学习资源 也能够在博客中找一下,这里就不详细介绍了。python

什么是浅层神经网络呢?web

看了上面提到的博客,你应该知道相关概念了,浅层神经网络其实就是一个单隐层神经网络!!!算法

二、激活函数和激活函数的导数

使用一个神经网络时,须要决定使用哪一种激活函数用隐藏层上?哪一种用在输出节点上?不一样的激活函数的效果是不同的。下面将介绍一下经常使用的激活函数:编程

  • sigmoid 函数

函数图像和导数图像以下:
在这里插入图片描述
公式以下:网络

a = σ ( z ) = 1 1 + e z a = \sigma(z) = \frac{1}{{1 + e}^{- z}} app

导数公式以下:框架

d d z g ( z ) = 1 1 + e z ( 1 1 1 + e z ) = g ( z ) ( 1 g ( z ) ) \frac{d}{dz}g(z) = {\frac{1}{1 + e^{-z}} (1-\frac{1}{1 + e^{-z}})}=g(z)(1-g(z)) dom

若是没有非线性的激活函数,再多的神经网络只是计算线性函数,或者叫恒等激励函数。sigmoid 函数是使用比较多的一个激活函数。机器学习

  • tanh 函数

函数图像和导数图像以下:
在这里插入图片描述
公式以下:

a = t a n h ( z ) = e z e z e z + e z a= tanh(z) = \frac{e^{z} - e^{- z}}{e^{z} + e^{- z}}

导数公式以下:

d d z g ( z ) = 1 ( t a n h ( z ) ) 2 \frac{d}{{d}z}g(z) = 1 - (tanh(z))^{2}

事实上,tanhsigmoid 的向下平移和伸缩后的结果。对它进行了变形后,穿过了 ( 0 , 0 ) (0,0) 点,而且值域介于 +1 和 -1 之间。

因此效果老是优于 sigmoid 函数。由于函数值域在 -1 和 +1 的激活函数,其均值是更接近零均值的。在训练一个算法模型时,若是使用 tanh 函数代替 sigmoid 函数中心化数据,使得数据的平均值更接近0而不是0.5。可是也有例外的状况,有时对隐藏层使用 tanh 激活函数,而输出层使用 sigmoid 函数,效果会更好。

小结:

sigmoid 函数和 tanh 函数二者共同的缺点是,在未通过激活函数的输出特别大或者特别小的状况下,会致使导数的梯度或者函数的斜率变得特别小,最后就会接近于0,致使下降梯度降低的速度。

  • ReLu 函数

在机器学习另外一个很流行的函数是:修正线性单元的函数(ReLu)

函数图像和导数图像以下:
在这里插入图片描述
公式以下:

a = m a x ( 0 , z ) a =max( 0,z)

导数公式以下:

g ( z ) = { 0 if z < 0 1 if z > 0 u n d e f i n e d if z = 0 g(z)^{'}= \begin{cases} 0& \text{if z < 0}\\ 1& \text{if z > 0}\\ undefined& \text{if z = 0} \end{cases}

z z 是正值的状况下,导数恒等于1,当 z z 是负值的时候,导数恒等于0。Relu 的一个优势是当 z z 是负值的时候,导数等于0,当 z z 是正值的时候,导数等于1。这样在梯度降低时就不会受 梯度爆炸或者梯度消失 的影响了。

详见博客——深度学习100问之深刻理解Vanishing/Exploding Gradient(梯度消失/爆炸)

一些选择激活函数的经验法则:
若是输出是0、1值(二分类问题),则输出层选择 sigmoid 函数,而后其它的全部单元都选择 Relu 函数。这是不少激活函数的默认选择,若是在隐藏层上不肯定使用哪一个激活函数,那么一般会使用 Relu 激活函数。有时,也会使用 tanh 激活函数。

  • Leaky Relu 函数

这里也有另外一个版本的 Relu 被称为 Leaky Relu

函数图像和导数图像以下:
在这里插入图片描述
ReLU 相似,公式以下:

g ( z ) = max ( 0.01 z , z ) g(z)=\max(0.01z,z)

导数公式以下:

g ( z ) = { 0.01 if z < 0 1 if z > 0 u n d e f i n e d if z = 0 g(z)^{'}= \begin{cases} 0.01& \text{if z < 0}\\ 1& \text{if z > 0}\\ undefined& \text{if z = 0} \end{cases}

z z 是负值时,这个函数的值不是等于0,而是轻微的倾斜。这个函数一般比 Relu 激活函数效果要好,尽管在实际中 Leaky ReLu 使用的并很少。

RELU 系列的两个激活函数的优势是:

  • 第一,在未通过激活函数的输出的区间变更很大的状况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个 if-else 语句,而 sigmoid 函数须要进行浮点四则运算,在实践中,使用 ReLu 激活函数神经网络一般会比使用 sigmoid 或者 tanh 激活函数学习的更快。

  • 第二,sigmoidtanh 函数的导数在正负饱和区的梯度都会接近于0,这会形成梯度弥散,而 ReluLeaky ReLu 函数大于0部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu 进入负半区的时候,梯度为0,神经元此时不会训练,产生所谓的 稀疏性,而 Leaky ReLu 不会有这问题。但 ReLu 的梯度一半都是0,有足够的隐藏层使得未通过激活函数的输出值大于0,因此对大多数的训练数据来讲学习过程仍然能够很快。)

最后简单介绍完了经常使用的激活函数以后,来快速归纳一下。

  • sigmoid 激活函数:除了输出层是一个二分类问题基本上不会用 sigmoid

  • tanh 激活函数:tanh 是很是优秀的,几乎适合全部场合。

  • ReLu 激活函数:最经常使用的默认激活函数。若是不肯定用哪一个激活函数,就先使用 ReLu

不少人在编写神经网络的时候,常常遇到一个问题是,有不少个选择:隐藏层单元的个数激活函数的种类初始化权值的方式、等等……这些选择想获得一个比较好的指导原则是挺困难的,因此其实更多的是经验,这也是深度学习被人称为经验主义学科和被人诟病的地方,更像是一种炼丹术,是否是?

你可能会看到好多博客,文章,或者哪个工业界大佬或者学术界大佬说过,哪种用的多,哪种更好用。可是,你的神经网络的结构,以及须要解决问题的特殊性,是很难提早知道选择哪些效果更好的,或者没办法肯定别人的经验和结论是否是对你一样有效。

因此一般的建议是:若是不肯定哪个激活函数效果更好,能够把它们都试试,而后在验证集或者测试集上进行评价,这样若是看到哪种的表现明显更好一些,就在你的网络中使用它!!!

三、为何须要非线性激活函数?

为何神经网络须要非线性激活函数?

首先是事实证实了,要让神经网络可以计算出有趣的函数,必须使用非线性激活函数。可是这么说太不科学了,如今来证实一下,证实过程以下:

a [ 1 ] = g ( z [ 1 ] ) a^{[1]} = g(z^{[1]}) ,这是神经网络正向传播的方程,以前咱们学过的,你还记得不?不记得去翻翻 深度学习入门笔记(二):神经网络基础

如今去掉函数 g g ,也就是去掉激活函数,而后令 a [ 1 ] = z [ 1 ] a^{[1]} = z^{[1]} ,或者也能够直接令 g ( z [ 1 ] ) = z [ 1 ] g(z^{[1]})=z^{[1]} ,这个有时被叫作 线性激活函数(更学术点的名字是 恒等激励函数,由于它们就是把输入值直接输出)。

由于:

(1) a [ 1 ] = z [ 1 ] = W [ 1 ] x + b [ 1 ] a^{[1]} = z^{[1]} = W^{[1]}x + b^{[1]}

(2) a [ 2 ] = z [ 2 ] = W [ 2 ] a [ 1 ] + b [ 2 ] a^{[2]} = z^{[2]} = W^{[2]}a^{[1]}+ b^{[2]}

将式子(1)代入式子(2)中,则获得:

(3) a [ 2 ] = z [ 2 ] = W [ 2 ] ( W [ 1 ] x + b [ 1 ] ) + b [ 2 ] = W [ 2 ] W [ 1 ] x + W [ 2 ] b [ 1 ] + b [ 2 ] = ( W [ 2 ] W [ 1 ] ) x + ( W [ 2 ] b [ 1 ] + b [ 2 ] ) a^{[2]} = z^{[2]} = W^{[2]}(W^{[1]}x + b^{[1]}) + b^{[2]} = W^{[2]}W^{[1]}x + W^{[2]}b^{[1]} + b^{[2]} = (W^{[2]}W^{[1]})x + (W^{[2]}b^{[1]} + b^{[2]})

而后简化多项式,你能够发现两个括号里的式子均可以简化,可得:

(4) a [ 2 ] = z [ 2 ] = W x + b a^{[2]} = z^{[2]} = W^{'}x + b^{'}

小结:若是使用 线性激活函数 或者叫 恒等激励函数,那么神经网络只是把输入线性组合再输出。

以后咱们会学到 深度网络,什么是 深度网络?顾名思义,就是有不少层(不少隐藏层)的神经网络。然而,上面的证实告诉咱们,若是使用线性激活函数或者不使用激活函数,那么不管你的神经网络有多少层,一直在作的也只是计算线性函数,均可以用 a [ 2 ] = z [ 2 ] = W x + b a^{[2]} = z^{[2]} = W^{'}x + b^{'} 表示,还不如直接去掉所有隐藏层,反正也没啥用。。。

总之,不能在隐藏层用线性激活函数,相反你能够用 ReLU 或者 tanh 或者 leaky ReLU 或者其余的非线性激活函数,惟一能够用线性激活函数的一般就是输出层。

四、神经网络的梯度降低

咱们这一次讲的浅层神经网络——单隐层神经网络,会有 W [ 1 ] W^{[1]} b [ 1 ] b^{[1]} W [ 2 ] W^{[2]} b [ 2 ] b^{[2]} 这些个参数,还有个 n x n_x 表示输入特征的个数, n [ 1 ] n^{[1]} 表示隐藏单元个数, n [ 2 ] n^{[2]} 表示输出单元个数。

好了,这就是所有的符号参数了。那么具体参数的维度以下:

  • 矩阵 W [ 1 ] W^{[1]} 的维度就是( n [ 1 ] , n [ 0 ] n^{[1]}, n^{[0]} ), b [ 1 ] b^{[1]} 就是 n [ 1 ] n^{[1]} 维向量,能够写成 ( n [ 1 ] , 1 ) (n^{[1]}, 1) ,就是一个的列向量。

  • 矩阵 W [ 2 ] W^{[2]} 的维度就是( n [ 2 ] , n [ 1 ] n^{[2]}, n^{[1]} ), b [ 2 ] b^{[2]} 的维度和 b [ 1 ] b^{[1]} 同样,就是写成 ( n [ 2 ] , 1 ) (n^{[2]},1)

此外,还有一个神经网络的 代价函数(Cost function),在以前的博客——深度学习入门笔记(二):神经网络基础 中讲过,不过是基于逻辑回归的。假设如今是在作二分类任务,那么代价函数等于:

J ( W [ 1 ] , b [ 1 ] , W [ 2 ] , b [ 2 ] ) = 1 m i = 1 m L ( y ^ , y ) J(W^{[1]},b^{[1]},W^{[2]},b^{[2]}) = {\frac{1}{m}}\sum_{i=1}^mL(\hat{y}, y)

训练参数以后,须要作梯度降低,而后进行参数更新,进而网络优化。因此,每次梯度降低都会循环,而且计算如下的值,也就是网络的输出:

y ^ ( i ) ( i = 1 , 2 , , m ) \hat{y}^{(i)} (i=1,2,…,m)

  • 前向传播(forward propagation) 方程以下(以前讲过):

(1) z [ 1 ] = W [ 1 ] x + b [ 1 ] z^{[1]} = W^{[1]}x + b^{[1]}

(2) a [ 1 ] = σ ( z [ 1 ] ) a^{[1]} = \sigma(z^{[1]})

(3) z [ 2 ] = W [ 2 ] a [ 1 ] + b [ 2 ] z^{[2]} = W^{[2]}a^{[1]} + b^{[2]}

(4) a [ 2 ] = g [ 2 ] ( z [ z ] ) = σ ( z [ 2 ] ) a^{[2]} = g^{[2]}(z^{[z]}) = \sigma(z^{[2]})

  • 反向传播(back propagation) 方程以下:

(1) d z [ 2 ] = A [ 2 ] Y , Y = [ y [ 1 ] y [ 2 ] y [ m ] ] dz^{[2]} = A^{[2]} - Y , Y = \begin{bmatrix}y^{[1]} & y^{[2]} & \cdots & y^{[m]}\\ \end{bmatrix}

(2) d W [ 2 ] = 1 m d z [ 2 ] A [ 1 ] T dW^{[2]} = {\frac{1}{m}}dz^{[2]}A^{[1]T}

(3) d b [ 2 ] = 1 m n p . s u m ( d z [ 2 ] , a x i s = 1 , k e e p d i m s = T r u e ) {\rm d}b^{[2]} = {\frac{1}{m}}np.sum({d}z^{[2]},axis=1,keepdims=True)

(4) d z [ 1 ] = W [ 2 ] T d z [ 2 ] ( n [ 1 ] , m ) g [ 1 ] a c t i v a t i o n    f u n c t i o n    o f    h i d d e n    l a y e r ( z [ 1 ] ) ( n [ 1 ] , m ) dz^{[1]} = \underbrace{W^{[2]T}{\rm d}z^{[2]}}_{(n^{[1]},m)}\quad*\underbrace{{g^{[1]}}^{'}}_{activation \; function \; of \; hidden \; layer}*\quad\underbrace{(z^{[1]})}_{(n^{[1]},m)}

(5) d W [ 1 ] = 1 m d z [ 1 ] x T dW^{[1]} = {\frac{1}{m}}dz^{[1]}x^{T}

(6) d b [ 1 ] ( n [ 1 ] , 1 ) = 1 m n p . s u m ( d z [ 1 ] , a x i s = 1 , k e e p d i m s = T r u e ) {\underbrace{db^{[1]}}_{(n^{[1]},1)}} = {\frac{1}{m}}np.sum(dz^{[1]},axis=1,keepdims=True)

:反向传播的这些公式都是针对全部样本,进行过向量化的(深度学习入门笔记(四):向量化)。

其中, Y Y 1 × m 1×m 的矩阵;这里 np.sumpythonnumpy 命令,axis=1 表示水平相加求和,keepdims 是防止 python 输出那些古怪的秩数 ( n , ) (n,) ,加上这个确保阵矩阵 d b [ 2 ] db^{[2]} 这个向量的输出的维度为 ( n , 1 ) (n,1) 这样标准的形式。

编程操做看这个博客——深度学习入门笔记(五):神经网络的编程基础

  • 参数更新 方程以下:

(1) d W [ 1 ] = d J d W [ 1 ] , d b [ 1 ] = d J d b [ 1 ] dW^{[1]} = \frac{dJ}{dW^{[1]}},db^{[1]} = \frac{dJ}{db^{[1]}}

(1) d W [ 2 ] = d J d W [ 2 ] , d b [ 2 ] = d J d b [ 2 ] {d}W^{[2]} = \frac{{dJ}}{dW^{[2]}},{d}b^{[2]} = \frac{dJ}{db^{[2]}}

其中 W [ 1 ]       W [ 1 ] a d W [ 1 ] b [ 1 ]       b [ 1 ] a d b [ 1 ] W^{[1]}\implies{W^{[1]} - adW^{[1]}},b^{[1]}\implies{b^{[1]} -adb^{[1]}} W [ 2 ]       W [ 2 ] α d W [ 2 ] b [ 2 ]       b [ 2 ] α d b [ 2 ] W^{[2]}\implies{W^{[2]} - \alpha{\rm d}W^{[2]}},b^{[2]}\implies{b^{[2]} - \alpha{\rm d}b^{[2]}}

若是你跟着我们系列下来的话(深度学习入门笔记),应该发现了到目前为止,计算的都和 Logistic 回归(深度学习入门笔记(二):神经网络基础)十分的类似,但当你开始 计算 反向传播的时候,你会发现,是须要计算隐藏层和输出层激活函数的导数的,在这里(二元分类)使用的是 sigmoid 函数。

若是你想认真的推导一遍反向传播,深刻理解反向传播的话,欢迎看一下这个博客——深度学习100问之深刻理解Back Propagation(反向传播),只要你跟着推导一遍,反向传播基本没什么大问题了。或者若是你以为本身数学不太好的话,也能够和许多成功的深度学习从业者同样直接实现这个算法,不去了解其中的知识,这就是深度学习相较于机器学习最大的优点,我猜是的。

五、随机初始化

当训练神经网络时,权重随机初始化 是很重要的,简单来讲,参数初始化 就是 决定梯度降低中的起始点。对于逻辑回归,把权重初始化为0固然也是能够的,可是对于一个神经网络,若是权重或者参数都初始化为0,那么梯度降低将不会起做用。你必定想问为何?

慢慢来看,假设如今有两个输入特征,即 n [ 0 ] = 2 n^{[0]} = 2 ,2个隐藏层单元,即 n [ 1 ] n^{[1]} 等于2,所以与一个隐藏层相关的矩阵,或者说 W [ 1 ] W^{[1]} 就是一个 2*2 的矩阵。咱们再假设,在权重随机初始化的时候,把它初始化为 0 的 2*2 矩阵, b [ 1 ] b^{[1]} 也等于 [ 0    0 ] T [0\;0]^T (把偏置项 b b 初始化为0是合理的),可是把 w w 初始化为 0 就有问题了。你会发现,若是按照这样进行参数初始化的话,老是发现 a 1 [ 1 ] a_{1}^{[1]} a 2 [ 1 ] a_{2}^{[1]} 相等,这两个激活单元就会同样了!!!为何会这样呢?

由于在反向传播时,两个隐含单元计算的是相同的函数,都是来自 a 1 [ 2 ] a_1^{[2]} 的梯度变化,也就是 dz 1 [ 1 ] \text{dz}_{1}^{[1]} dz 2 [ 1 ] \text{dz}_{2}^{[1]} 是同样的,由 W [ 1 ] = W [ 1 ] a d W W^{[1]} = {W^{[1]}-adW} 可得 W [ 1 ] = a d W W^{[1]} = ad{W} ,学习率 a a 同样,梯度变化 d W d{W} 同样,这样更新后的输出权值也会如出一辙,由此 W [ 2 ] W^{[2]} 也等于 [ 0    0 ] [0\;0]
在这里插入图片描述
你可能以为这也没啥啊,大惊小怪的,可是若是这样初始化这整个神经网络的话,那么这两个隐含单元就彻底同样了,所以它们两个彻底对称,也就意味着计算一样的函数,而且确定的是最终通过每次训练的迭代,这两个隐含单元仍然是同一个函数,使人困惑。

由此能够推导,因为隐含单元计算的是同一个函数,全部的隐含单元对输出单元有一样的影响。一次迭代后,一样的表达式结果仍然是相同的,即 隐含单元还是对称的。那么两次、三次、不管多少次迭代,无论网络训练多长时间,隐含单元仍然计算的是一样的函数。所以这种状况下超过1个隐含单元也没什么意义,由于计算的是一样的东西。固然不管是多大的网络,好比有3个特征,仍是有至关多的隐含单元。

那么这个问题的解决方法是什么?其实很简单,就是 随机初始化参数

你应该这么作:把 W [ 1 ] W^{[1]} 设为 np.random.randn(2,2)(生成标准正态分布),一般再乘上一个较小的数,好比 0.01,这样就把它初始化为一个很小的随机数。而后 b b 原本就没有这个对称的问题(叫作symmetry breaking problem),因此能够把 b b 初始化为0,由于只要随机初始化 W W ,就有不一样的隐含单元计算不一样的东西,就不会有 symmetry breaking 问题了。类似地,对于 W [ 2 ] W^{[2]} 也随机初始化, b [ 2 ] b^{[2]} 能够初始化为0。

举一个随机初始化的例子,好比:

W [ 1 ] = n p . r a n d o m . r a n d n ( 2 , 2 )       0.01    ,    b [ 1 ] = n p . z e r o s ( ( 2 , 1 ) ) W^{[1]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[1]} = np.zeros((2,1))

W [ 2 ] = n p . r a n d o m . r a n d n ( 2 , 2 )       0.01    ,    b [ 2 ] = 0 W^{[2]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[2]} = 0

你也许会疑惑,这个常数从哪里来?为何是0.01,而不是100或者1000?

这是由于咱们 一般倾向于初始化为很小的随机数

这么想,若是你用 tanh 或者 sigmoid 激活函数,或者说只在输出层有一个 sigmoid 激活函数,这种状况下,若是(数值)波动太大,在计算激活值时 z [ 1 ] = W [ 1 ] x + b [ 1 ]    ,    a [ 1 ] = σ ( z [ 1 ] ) = g [ 1 ] ( z [ 1 ] ) z^{[1]} = W^{[1]}x + b^{[1]}\;,\;a^{[1]} = \sigma(z^{[1]})=g^{[1]}(z^{[1]}) ,若是 W W 很大, z z 就会很大或者很小,这种状况下极可能停在 tanh / sigmoid 函数的平坦的地方(甚至在训练刚刚开始的时候),而这些平坦的地方对应导数函数图像中梯度很小的地方,也就意味着梯度降低会很慢(由于梯度小),所以学习也就很慢,这显然是很差的。

sigmoid 函数图像和导数函数图像:
在这里插入图片描述
tanh 函数图像和导数函数图像:
在这里插入图片描述
若是你没有使用 sigmoid / tanh 激活函数在整个的神经网络里,就不成问题。但若是作二分类而且输出单元是 Sigmoid 函数,那么你必定不会想让你的初始参数太大,所以这就是为何乘上 0.01 或者其余一些小数是合理的尝试,对 w [ 2 ] w^{[2]} 也是同样。

关于浅层神经网络的代码,能够手撕一下,欢迎看一下这个博客——深度学习之手撕神经网络代码(基于numpy)

推荐阅读

参考文章

  • 吴恩达——《神经网络和深度学习》视频课程