声明
1)该文章整理自网上的大牛和机器学习专家无私奉献的资料,具体引用的资料请看参考文献。
2)本文仅供学术交流,非商用。因此每一部分具体的参考资料并无详细对应。若是某部分不当心侵犯了你们的利益,还望海涵,并联系博主删除。
3)博主才疏学浅,文中若有不当之处,请各位指出,共同进步,谢谢。
4)此属于初版本,如有错误,还需继续修正与增删。还望你们多多指点。你们都共享一点点,一块儿为祖国科研的推动添砖加瓦。html
深度学习入门笔记(六):浅层神经网络
1 、神经网络概述
关于神经网络的概述,具体的能够看这个博客——大话卷积神经网络CNN(干货满满),我在其中详细的介绍了 人类视觉原理、神经网络、卷积神经网络的定义和相关网络结构基础,还有 应用 和 关于深度学习的本质的探讨,若是你须要 学习资源 也能够在博客中找一下,这里就不详细介绍了。python
什么是浅层神经网络呢?web
看了上面提到的博客,你应该知道相关概念了,浅层神经网络其实就是一个单隐层神经网络!!!算法
二、激活函数和激活函数的导数
使用一个神经网络时,须要决定使用哪一种激活函数用隐藏层上?哪一种用在输出节点上?不一样的激活函数的效果是不同的。下面将介绍一下经常使用的激活函数:编程
函数图像和导数图像以下:
公式以下:网络
a=σ(z)=1+e−z1app
导数公式以下:框架
dzdg(z)=1+e−z1(1−1+e−z1)=g(z)(1−g(z))dom
若是没有非线性的激活函数,再多的神经网络只是计算线性函数,或者叫恒等激励函数。sigmoid 函数是使用比较多的一个激活函数。机器学习
函数图像和导数图像以下:
公式以下:
a=tanh(z)=ez+e−zez−e−z
导数公式以下:
dzdg(z)=1−(tanh(z))2
事实上,tanh 是 sigmoid 的向下平移和伸缩后的结果。对它进行了变形后,穿过了
(0,0)点,而且值域介于 +1 和 -1 之间。
因此效果老是优于 sigmoid 函数。由于函数值域在 -1 和 +1 的激活函数,其均值是更接近零均值的。在训练一个算法模型时,若是使用 tanh 函数代替 sigmoid 函数中心化数据,使得数据的平均值更接近0而不是0.5。可是也有例外的状况,有时对隐藏层使用 tanh 激活函数,而输出层使用 sigmoid 函数,效果会更好。
小结:
sigmoid 函数和 tanh 函数二者共同的缺点是,在未通过激活函数的输出特别大或者特别小的状况下,会致使导数的梯度或者函数的斜率变得特别小,最后就会接近于0,致使下降梯度降低的速度。
在机器学习另外一个很流行的函数是:修正线性单元的函数(ReLu)。
函数图像和导数图像以下:
公式以下:
a=max(0,z)
导数公式以下:
g(z)′=⎩⎪⎨⎪⎧01undefinedif z < 0if z > 0if z = 0
当
z 是正值的状况下,导数恒等于1,当
z 是负值的时候,导数恒等于0。Relu 的一个优势是当
z 是负值的时候,导数等于0,当
z 是正值的时候,导数等于1。这样在梯度降低时就不会受 梯度爆炸或者梯度消失 的影响了。
详见博客——深度学习100问之深刻理解Vanishing/Exploding Gradient(梯度消失/爆炸)
一些选择激活函数的经验法则:
若是输出是0、1值(二分类问题),则输出层选择 sigmoid 函数,而后其它的全部单元都选择 Relu 函数。这是不少激活函数的默认选择,若是在隐藏层上不肯定使用哪一个激活函数,那么一般会使用 Relu 激活函数。有时,也会使用 tanh 激活函数。
这里也有另外一个版本的 Relu 被称为 Leaky Relu。
函数图像和导数图像以下:
与 ReLU 相似,公式以下:
g(z)=max(0.01z,z)
导数公式以下:
g(z)′=⎩⎪⎨⎪⎧0.011undefinedif z < 0if z > 0if z = 0
当
z 是负值时,这个函数的值不是等于0,而是轻微的倾斜。这个函数一般比 Relu 激活函数效果要好,尽管在实际中 Leaky ReLu 使用的并很少。
RELU 系列的两个激活函数的优势是:
-
第一,在未通过激活函数的输出的区间变更很大的状况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个 if-else 语句,而 sigmoid 函数须要进行浮点四则运算,在实践中,使用 ReLu 激活函数神经网络一般会比使用 sigmoid 或者 tanh 激活函数学习的更快。
-
第二,sigmoid 和 tanh 函数的导数在正负饱和区的梯度都会接近于0,这会形成梯度弥散,而 Relu 和 Leaky ReLu 函数大于0部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu 进入负半区的时候,梯度为0,神经元此时不会训练,产生所谓的 稀疏性,而 Leaky ReLu 不会有这问题。但 ReLu 的梯度一半都是0,有足够的隐藏层使得未通过激活函数的输出值大于0,因此对大多数的训练数据来讲学习过程仍然能够很快。)
最后简单介绍完了经常使用的激活函数以后,来快速归纳一下。
-
sigmoid 激活函数:除了输出层是一个二分类问题基本上不会用 sigmoid。
-
tanh 激活函数:tanh 是很是优秀的,几乎适合全部场合。
-
ReLu 激活函数:最经常使用的默认激活函数。若是不肯定用哪一个激活函数,就先使用 ReLu。
不少人在编写神经网络的时候,常常遇到一个问题是,有不少个选择:隐藏层单元的个数、激活函数的种类、初始化权值的方式、等等……这些选择想获得一个比较好的指导原则是挺困难的,因此其实更多的是经验,这也是深度学习被人称为经验主义学科和被人诟病的地方,更像是一种炼丹术,是否是?
你可能会看到好多博客,文章,或者哪个工业界大佬或者学术界大佬说过,哪种用的多,哪种更好用。可是,你的神经网络的结构,以及须要解决问题的特殊性,是很难提早知道选择哪些效果更好的,或者没办法肯定别人的经验和结论是否是对你一样有效。
因此一般的建议是:若是不肯定哪个激活函数效果更好,能够把它们都试试,而后在验证集或者测试集上进行评价,这样若是看到哪种的表现明显更好一些,就在你的网络中使用它!!!
三、为何须要非线性激活函数?
为何神经网络须要非线性激活函数?
首先是事实证实了,要让神经网络可以计算出有趣的函数,必须使用非线性激活函数。可是这么说太不科学了,如今来证实一下,证实过程以下:
a[1]=g(z[1]),这是神经网络正向传播的方程,以前咱们学过的,你还记得不?不记得去翻翻 深度学习入门笔记(二):神经网络基础。
如今去掉函数
g,也就是去掉激活函数,而后令
a[1]=z[1],或者也能够直接令
g(z[1])=z[1],这个有时被叫作 线性激活函数(更学术点的名字是 恒等激励函数,由于它们就是把输入值直接输出)。
由于:
(1)
a[1]=z[1]=W[1]x+b[1]
(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])
而后简化多项式,你能够发现两个括号里的式子均可以简化,可得:
(4)
a[2]=z[2]=W′x+b′
小结:若是使用 线性激活函数 或者叫 恒等激励函数,那么神经网络只是把输入线性组合再输出。
以后咱们会学到 深度网络,什么是 深度网络?顾名思义,就是有不少层(不少隐藏层)的神经网络。然而,上面的证实告诉咱们,若是使用线性激活函数或者不使用激活函数,那么不管你的神经网络有多少层,一直在作的也只是计算线性函数,均可以用
a[2]=z[2]=W′x+b′ 表示,还不如直接去掉所有隐藏层,反正也没啥用。。。
总之,不能在隐藏层用线性激活函数,相反你能够用 ReLU 或者 tanh 或者 leaky ReLU 或者其余的非线性激活函数,惟一能够用线性激活函数的一般就是输出层。
四、神经网络的梯度降低
咱们这一次讲的浅层神经网络——单隐层神经网络,会有
W[1],
b[1],
W[2],
b[2] 这些个参数,还有个
nx 表示输入特征的个数,
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] 的维度和
b[1] 同样,就是写成
(n[2],1)。
此外,还有一个神经网络的 代价函数(Cost function),在以前的博客——深度学习入门笔记(二):神经网络基础 中讲过,不过是基于逻辑回归的。假设如今是在作二分类任务,那么代价函数等于:
J(W[1],b[1],W[2],b[2])=m1i=1∑mL(y^,y)
训练参数以后,须要作梯度降低,而后进行参数更新,进而网络优化。因此,每次梯度降低都会循环,而且计算如下的值,也就是网络的输出:
y^(i)(i=1,2,…,m)
- 前向传播(forward propagation) 方程以下(以前讲过):
(1)
z[1]=W[1]x+b[1]
(2)
a[1]=σ(z[1])
(3)
z[2]=W[2]a[1]+b[2]
(4)
a[2]=g[2](z[z])=σ(z[2])
- 反向传播(back propagation) 方程以下:
(1)
dz[2]=A[2]−Y,Y=[y[1]y[2]⋯y[m]]
(2)
dW[2]=m1dz[2]A[1]T
(3)
db[2]=m1np.sum(dz[2],axis=1,keepdims=True)
(4)
dz[1]=(n[1],m)
W[2]Tdz[2]∗activationfunctionofhiddenlayer
g[1]′∗(n[1],m)
(z[1])
(5)
dW[1]=m1dz[1]xT
(6)
(n[1],1)
db[1]=m1np.sum(dz[1],axis=1,keepdims=True)
注:反向传播的这些公式都是针对全部样本,进行过向量化的(深度学习入门笔记(四):向量化)。
其中,
Y 是
1×m 的矩阵;这里 np.sum
是 python 的 numpy 命令,axis=1
表示水平相加求和,keepdims
是防止 python 输出那些古怪的秩数
(n,),加上这个确保阵矩阵
db[2] 这个向量的输出的维度为
(n,1) 这样标准的形式。
编程操做看这个博客——深度学习入门笔记(五):神经网络的编程基础。
(1)
dW[1]=dW[1]dJ,db[1]=db[1]dJ
(1)
dW[2]=dW[2]dJ,db[2]=db[2]dJ
其中
W[1]⟹W[1]−adW[1],b[1]⟹b[1]−adb[1],
W[2]⟹W[2]−αdW[2],b[2]⟹b[2]−αdb[2]。
若是你跟着我们系列下来的话(深度学习入门笔记),应该发现了到目前为止,计算的都和 Logistic 回归(深度学习入门笔记(二):神经网络基础)十分的类似,但当你开始 计算 反向传播的时候,你会发现,是须要计算隐藏层和输出层激活函数的导数的,在这里(二元分类)使用的是 sigmoid 函数。
若是你想认真的推导一遍反向传播,深刻理解反向传播的话,欢迎看一下这个博客——深度学习100问之深刻理解Back Propagation(反向传播),只要你跟着推导一遍,反向传播基本没什么大问题了。或者若是你以为本身数学不太好的话,也能够和许多成功的深度学习从业者同样直接实现这个算法,不去了解其中的知识,这就是深度学习相较于机器学习最大的优点,我猜是的。
五、随机初始化
当训练神经网络时,权重随机初始化 是很重要的,简单来讲,参数初始化 就是 决定梯度降低中的起始点。对于逻辑回归,把权重初始化为0固然也是能够的,可是对于一个神经网络,若是权重或者参数都初始化为0,那么梯度降低将不会起做用。你必定想问为何?
慢慢来看,假设如今有两个输入特征,即
n[0]=2,2个隐藏层单元,即
n[1] 等于2,所以与一个隐藏层相关的矩阵,或者说
W[1] 就是一个 2*2 的矩阵。咱们再假设,在权重随机初始化的时候,把它初始化为 0 的 2*2 矩阵,
b[1] 也等于
[00]T(把偏置项
b 初始化为0是合理的),可是把
w 初始化为 0 就有问题了。你会发现,若是按照这样进行参数初始化的话,老是发现
a1[1] 和
a2[1] 相等,这两个激活单元就会同样了!!!为何会这样呢?
由于在反向传播时,两个隐含单元计算的是相同的函数,都是来自
a1[2] 的梯度变化,也就是
dz1[1] 和
dz2[1] 是同样的,由
W[1]=W[1]−adW 可得
W[1]=adW,学习率
a 同样,梯度变化
dW 同样,这样更新后的输出权值也会如出一辙,由此
W[2] 也等于
[00]。
你可能以为这也没啥啊,大惊小怪的,可是若是这样初始化这整个神经网络的话,那么这两个隐含单元就彻底同样了,所以它们两个彻底对称,也就意味着计算一样的函数,而且确定的是最终通过每次训练的迭代,这两个隐含单元仍然是同一个函数,使人困惑。
由此能够推导,因为隐含单元计算的是同一个函数,全部的隐含单元对输出单元有一样的影响。一次迭代后,一样的表达式结果仍然是相同的,即 隐含单元还是对称的。那么两次、三次、不管多少次迭代,无论网络训练多长时间,隐含单元仍然计算的是一样的函数。所以这种状况下超过1个隐含单元也没什么意义,由于计算的是一样的东西。固然不管是多大的网络,好比有3个特征,仍是有至关多的隐含单元。
那么这个问题的解决方法是什么?其实很简单,就是 随机初始化参数。
你应该这么作:把
W[1] 设为 np.random.randn(2,2)
(生成标准正态分布),一般再乘上一个较小的数,好比 0.01
,这样就把它初始化为一个很小的随机数。而后
b 原本就没有这个对称的问题(叫作symmetry breaking problem),因此能够把
b 初始化为0,由于只要随机初始化
W,就有不一样的隐含单元计算不一样的东西,就不会有 symmetry breaking 问题了。类似地,对于
W[2] 也随机初始化,
b[2] 能够初始化为0。
举一个随机初始化的例子,好比:
W[1]=np.random.randn(2,2)∗0.01,b[1]=np.zeros((2,1))
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]),若是
W 很大,
z 就会很大或者很小,这种状况下极可能停在 tanh / sigmoid 函数的平坦的地方(甚至在训练刚刚开始的时候),而这些平坦的地方对应导数函数图像中梯度很小的地方,也就意味着梯度降低会很慢(由于梯度小),所以学习也就很慢,这显然是很差的。
sigmoid 函数图像和导数函数图像:
tanh 函数图像和导数函数图像:
若是你没有使用 sigmoid / tanh 激活函数在整个的神经网络里,就不成问题。但若是作二分类而且输出单元是 Sigmoid 函数,那么你必定不会想让你的初始参数太大,所以这就是为何乘上 0.01
或者其余一些小数是合理的尝试,对
w[2] 也是同样。
关于浅层神经网络的代码,能够手撕一下,欢迎看一下这个博客——深度学习之手撕神经网络代码(基于numpy)。
推荐阅读
参考文章