主要对较为典型的全连接神经网络的算法原理进行描述,多层的神经网络结构如下,由三部分组成:输入层(input layer),隐藏层(Hidden layer),输出层(output layer)。
特点如下:
其实之前学过的逻辑回归可以看成一种简单的浅层神经网络,只不过没有隐藏层且输出层只有一个输出单元。逻辑回归的结构如下:
神经网络的可看成由很多个逻辑回归组成,神经网络的每层结构和上述类似,也包括输入输出、权重w、偏置b(阈值)以及激活函数。
逻辑回归在不引入其他方法的情况下解决线性可分的问题,上述第四个的结果无法使用一条直线划分,为了解决线性不可分的问题,就需要引入多层神经网络。为了更直观得比较多层神经网络和逻辑回归(单层),利用TensorFlow PlayGround演示平台来演示一下。(http://playground.tensorflow.org/)
上述测试数据可看作线性可分的二分类问题,可见对于此测试集的数据,逻辑回归和两层的神经网络在此类问题的处理结果上没有太大差距。
以上四种情况比较了逻辑回归、两层神经网络和三层神经网络在线性不可分的数据下的输出结果, 逻辑回归在不引入其他方法的情况下,只能处理线性可分的数据,或者进一步说,处理二分类的问题 。 可见神经网络在一定情况下需要适当引入隐藏层或者增加隐藏层的单元个数来使推测结果更准确。
神经网络算法原理和逻辑回归原理部分相同,主要介绍运用监督学习的神经网络算法的原理。在实际应用神经网络情况中,总体分两部分:输入训练数据通过训练构建神经网络(主要任务是确定最终的权重w和偏置项b)和输入测试数据得出预测结果。其中第一部分为主要部分,构建神经网络的一般方法是:
定义神经网络结构(输入、输出单元的数量,隐藏单元的数量等)。
初始化模型的参数(对权重w和偏置b进行初始化:向量化矩阵的形状和设置w、b的值)
循环:
以下一个两层单输出的神经网络详细说明一下具体构建神经网络的流程,神经网络模型及常用的符号规定如下:
定义神经网络结构:神经网络的结构即神经网络的层数、隐藏层的单元个数等,此处神经网络的结构已经由上图给定,一般情况下输入层个数看需输入的特征参数的个数,隐藏层需要自己进行设定。
初始化模型的参数:先考虑W[1]、W[2]矩阵的形状(W[1]为(4,2)矩阵,W[2]为(1,4)矩阵),再进行初始值的设置,在设置w的初始值时见下面的注释;b[1]和b[2]的矩阵形状为(4,1)和(1,1),b的初始值可全设置为0。
python代码如下:
W1 = np.random.randn(4,2) * 0.01
#通常会在生成的矩阵后面乘以小数,比如0.01,目的是为了提高梯度下降算法的收敛速度。
b1 = np.zeros(shape=(4, 1))
W2 = np.random.randn(1,4) * 0.01
b2 = np.zeros(shape=(1, 1))
注1:只有在逻辑回归模型(一层神经网络)时才设置所有w的初始值为0;在多层神经网络中w的初始不能设定全为0,若全设置为相同的0会导致下一隐藏层(若下一隐藏层单元数大于1)开始输出的a以及梯度下降时的dw、db相同,造成对称的权重问题,最终得到权重与偏置(阈值)都相同。详细解释:https://www.zhihu.com/question/36068411?sort=created。
注2:同时在初始化权值的时候,常用的一个函数是 np.random.randn() 函数。这个函数会产生一个均值是0,方差是1的的分布,以上 w = np.random.randn() * 0.01 把w设置较小的随机数,我的理解是因为sigmoid函数和tan函数在0附近的斜率大,可以提高梯度下降的收敛速度。除了本例中w初始值的设置,很多情况并不是只乘0.01,还有Xavier初始值(sigmod、tanh等S型曲线的权重初始值)和He初始值(ReLU的权重初始值)这两种初始权重w的设置方式,python实现代码如下:
w = np.random.randn(node_num, node_num) * np.sqrt(2/node_num) #He
w = np.random.randn(node_num, node_num) * np.sqrt(1/node_num) #Xavier
更直观得理解w的初始值设置,可以看一下https://blog.csdn.net/Answer3664/article/details/96108837。
实际的He初始值设置代码如下:
for l in range(1,L):
parameters["W" + str(l)] = np.random.randn(layers_dims[l], layers_dims[l - 1]) / np.sqrt(layers_dims[l - 1])
parameters["b" + str(l)] = np.zeros((layers_dims[l], 1))
#确保我要的数据的格式是正确的
assert(parameters["W" + str(l)].shape == (layers_dims[l], layers_dims[l-1]))
assert(parameters["b" + str(l)].shape == (layers_dims[l], 1))
循环
前向传播
Z [ 1 ] = W [ 1 ] X + b [ 1 ] A [ 1 ] = g [ 1 ] ( Z [ 1 ] ) = t a n h Z [ 1 ] Z [ 2 ] = W [ 2 ] A [ 1 ] + b [ 2 ] A [ 2 ] = σ [ 1 ] ( Z [ 2 ] ) \begin{aligned} &Z^{[1]}=W^{[1]}X+b^{[1]}\\ &A^{[1]}=g^{[1]}(Z^{[1]})=tanhZ^{[1]}\\ &Z^{[2]}=W^{[2]}A^{[1]}+b^{[2]}\\ &A^{[2]}=σ^{[1]}(Z^{[2]})\\ \end{aligned} Z[1]=W[1]X+b[1]A[1]=g[1](Z[1])=tanhZ[1]Z[2]=W[2]A[1]+b[2]A[2]=σ[1](Z[2])
其中把各个参数进行ed了向量化,可以省去用循环语句并减少程序运行损耗的时间,提高了程序执行的效率。W[1]、X等的具体组成如下,其中训练样本有m个,X的上标表示样本的序号及代表层数(不带[]的上标表示第几个样本),其他向量组成类似。
W [ 1 ] = [ W 11 [ 1 ] W 12 [ 1 ] W 21 [ 1 ] W 22 [ 1 ] W 31 [ 1 ] W 32 [ 1 ] W 41 [ 1 ] W 42 [ 1 ] ] X = [ X 1 1 X 1 2 ⋯ ⋯ X 1 m X 2 1 X 2 2 ⋯ ⋯ X 2 m ] W^{[1]}=\left[ \begin{matrix} W^{[1]}_{11} & W^{[1]}_{12} \\ W^{[1]}_{21} & W^{[1]}_{22}\\ W^{[1]}_{31} & W^{[1]}_{32} \\ W^{[1]}_{41} & W^{[1]}_{42} \end{matrix} \right] X=\left[ \begin{matrix} X^1_{1} & X^2_{1}&\cdots ⋯ X^m_{1} \\ X^1_{2} & X^2_{2}&\cdots ⋯ X^m_{2}\\ \end{matrix} \right] W[1]=⎣⎢⎢⎢⎡W11[1]W21[1]W31[1]W41[1]W12[1]W22[1]W32[1]W42[1]⎦⎥⎥⎥⎤X=[X11X21X12X22⋯⋯X1m⋯⋯X2m]
b [ 1 ] = [ b 1 [ 1 ] b 2 [ 1 ] b 3 [ 1 ] b 4 [ 1 ] ] Z [ 1 ] = [ Z 1 [ 1 ] 1 Z 1 [ 1 ] 2 ⋯ ⋯ Z 1 [ 1 ] m Z 2 [ 1 ] 1 Z 2 [ 1 ] 2 ⋯ ⋯ Z 2 [ 1 ] m Z 3 [ 1 ] 1 Z 3 [ 1 ] 2 ⋯ ⋯ Z 3 [ 1 ] m Z 4 [ 1 ] 1 Z 4 [ 1 ] 2 ⋯ ⋯ Z 4 [ 1 ] m ] b^{[1]}=\left[ \begin{matrix} b^{[1]}_{1} \\ b^{[1]}_{2} \\ b^{[1]}_{3} \\ b^{[1]}_{4} \end{matrix} \right]Z^{[1]}=\left[ \begin{matrix} Z^{[1]1}_{1} & Z^{[1]2}_{1}&\cdots ⋯ Z^{[1]m}_{1}\\ Z^{[1]1}_{2} &Z^{[1]2}_{2}&\cdots ⋯ Z^{[1]m}_{2}\\ Z^{[1]1}_{3} & Z^{[1]2}_{3}&\cdots ⋯ Z^{[1]m}_{3} \\ Z^{[1]1}_{4} & Z^{[1]2}_{4} &\cdots ⋯ Z^{[1]m}_{4} \end{matrix} \right] b[1]=⎣⎢⎢⎢⎡b1[1]b2[1]b3[1]b4[1]⎦⎥⎥⎥⎤Z[1]=⎣⎢⎢⎢⎡Z1[1]1Z2[1]1Z3[1]1Z4[1]1Z1[1]2Z2[1]2Z3[1]2Z4[1]2⋯⋯Z1[1]m⋯⋯Z2[1]m⋯⋯Z3[1]m⋯⋯Z4[1]m⎦⎥⎥⎥⎤
计算损失
以上神经网络的损失函数如下:
J = − 1 m ∑ i = 1 m ( y ( i ) l o g ( a [ 2 ] i ) + ( 1 − y ( i ) ) l o g ( 1 − a [ 2 ] i ) ) J=-{1\over m}\sum_{i=1}^m(y^{(i)}log(a^{[2]i})+(1-y^{(i)})log(1-a^{[2]i})) J=−m1i=1∑m(y(i)log(a[2]i)+(1−y(i))log(1−a[2]i))
以上给只是两层神经网络损失函数的计算,类推可知当为神经网络为L层时且只有一个输出时计算损失:
J = − 1 m ∑ i = 1 m ( y ( i ) l o g ( a [ L ] i ) + ( 1 − y ( i ) ) l o g ( 1 − a [ L ] i ) ) J=-{1\over m}\sum_{i=1}^m(y^{(i)}log(a^{[L]i})+(1-y^{(i)})log(1-a^{[L]i})) J=−m1i=1∑m(y(i)log(a[L]i)+(1−y(i))log(1−a[L]i))
python实现代码:
logprobs = np.multiply(np.log(A2), Y) + np.multiply((1 - Y), np.log(1 - A2))
cost = - np.sum(logprobs) / m
cost = float(np.squeeze(cost))
cost = -np.sum(np.multiply(np.log(AL),Y) + np.multiply(np.log(1 - AL), 1 - Y)) / m
cost = np.squeeze(cost)
assert(cost.shape == ())
反向传播
所谓的反向传播其实就是应用梯段下降法,计算出各个W和b对损失函数的偏导,用到的公式如下:
此部分最终是为了求各个层间的dW和db,dW和db表示损失函数对此参数的偏导数,用到链式求导法则,上面的dZ[2]的形式只对应激活函数为sigmoid函数情况,其他情况需要另外求导推导。
更新参数
我们需要使用(dW[1], db[1], dW[2], db[2])来更新(W1, b1, W2, b2)。更新算法如下:
KaTeX parse error: No such environment: align at position 8: \begin{̲a̲l̲i̲g̲n̲}̲ &W^{[1]}=W^…
其中 α 为学习率。(学习率的选择与调整暂时还没学习)
通过循环前向传播、反向传播和更新参数,最终可以通过训练得出较为合适的权重W和阈值b。
为什么要用激活函数?
在逻辑回归二分类中我们用sigmoid作为激活函数,其中选择sigmoid函数的一个目的可以使输出结果在0与1之间,但激活函数的目的不能简单地理解为是为了输出限定范围, 神经网络的激活函数其实是将线性转化为非线性的一个函数 ,从而使神经网络可以逼近任何函数而不只是线性函数。激活函数是用来加入非线性因素的,只有线性模型的表达能力不够,而且如果只有线性函数部分增加神经网络的深度也是无意义的,只有线性函数部分就算增加再多层隐藏层,最后的结果还是线性的(可以只用一层的神经网络实现)。
激活函数需要满足的条件?(激活函数是怎么来的?)
除了sigmoid函数,常用的激活函数还有tanh函数、Relu函数以及其变体( LReLU、PReLU、RReLU 等)。常用的激活函数有以下:
1.sigmoid函数
激活函数是为了加入非线性因素,所以激活函数应满足以下条件:
非线性
可导(易于使用梯度下降法) 以上两个条件可总结为导数不为常数
计算简单:在深层神经网络中前向传播要多次用到激活函数, 因此简单的非线性函数自然更适合用作激活函数 。
单调性:这个条件大部分激活函数满足,即使导数的符号不变,使训练时易于收敛。
输出范围最好有限,但不是必须条件(如Relu函数): 有限的输出范围使得网络对于一些比较大的输入也会比较稳定 ,如sigmoid函数和tanh函数, 但这导致了前面提到的梯度消失问题,而且强行让每一层的输出限制到固定范围会限制其表达能力 。
zero-centered:Sigmoid函数的输出值恒大于0,这会导致模型训练的收敛速度变慢。(还没弄明白为啥)
参考:https://blog.csdn.net/junjun150013652/article/details/81487059
激活函数怎么选择的?除了sigmoid为什么要选择其他激活函数?
对于非线性激活来说,中间的隐藏层普遍使用ReLU及其变体(如LReLU、PReLU、RReLU等),输出层激活函数则是要根据任务配合损失函数来选择。sigmoid函数在隐藏层中很少应用,常用在二分类问题的输出层作为激活函数, 一般二分类问题中,隐藏层可以用tanh函数,输出层用sigmod函数 (多分类问题还会用到softmax,详细看https://blog.csdn.net/qq_39226755/article/details/89355974)。sigmoid函数不作为深层神经网络隐藏层的激活函数的原因如下:
第一,采用sigmoid等函数,算激活函数时(指数运算),计算量大,反向传播求误差梯度时,求导涉及除法,计算量相对大,而采用Relu激活函数,整个过程的计算量节省很多。
第二,对于深层网络,sigmoid函数反向传播时,很容易就会出现梯度消失的情况(在sigmoid接近饱和区时,变换太缓慢,导数趋于0,这种情况会造成信息丢失而无法完成训练。
第三, Sigmoid函数并不是以(0,0)为中心点 。(原因还带进一步理解,先放一张图)
Relu函数的优势:
第一,在输入为正数的时候,不存在梯度饱和问题。
第二,计算速度要快很多。ReLU函数只有线性关系,不管是前向传播还是反向传播,都比sigmod和tanh要快很多。(sigmod和tanh要计算指数,计算速度会比较慢)
都有哪些损失函数?损失函数该怎么选择?
https://blog.csdn.net/u010976453/article/details/78488279
http://www.csuldw.com/2016/03/26/2016-03-26-loss-function/
https://blog.csdn.net/qq_39226755/article/details/89355974
神经网络层数怎么选择?以及隐藏层神经单元个数怎么选择?
学习率怎么选择?
在实现神经网络时,一般按以下前两个步骤进行:
优化costfunction(减小偏差)
具体方法有Momentum算法、RMSprop(均方根传播)、Adam优化算法等(以上三种算法通过加速学习,使收敛速度快,在面对局部最优点及鞍点时能更快得收敛。),也可能网络的深度不够,如增加隐藏层的数量或隐藏层单元的数量。
避免过拟合(减小方差)
在无法增加数据的情况下,具体方法有正则化(如L2正则化)、数据扩增(在无法增加额外训练数据的情况下,可以对已有数据进行翻转、放大,扭曲等处理得到新的数据来扩充原数据集,从而达到正则化的目的)、dropout等。
加速训练
除了1中提到的Momentum、Adam算法等,还有正则化输入、Batch Norm(隐藏层数据Z或A归一化,Batch Norm可以使神经网络对超参数的选择更稳定,有一定的正则化的效果。)
在损失函数后加一个L2正则项,以逻辑回归为例,加入L2正则项的损失函数如下:
们首先做一个极端的假设,假设正则项系数 λ 非常大,那么为了满足minJ(w,b)这个运算使损失函数较小,则必须使得w[l]≈0,但是这样会极大的简化神经网络的结构,简单地说可能就是从非常复杂的非线性结构转变成了线性结构,L2正则化就是找到一个λ中间值,既能减小过拟合模型的非线性程度又不至于导致欠拟合。
1.每层每个节点以某一概率(这里以50%为例)被选中为需要删除的节点(如下图中标上X的节点)
**2.**被选中为删除的节点,不仅要删除节点,与之相连的线段也要删除,如下图示
**3.**使用反向传播算法对精简后的神经网络进行权重更新计算
**4.**恢复被删节点,然后循环往复上面的步骤,直到得到我们想要的结果。每轮循环删除的节点都是随机的,不一定删除哪个。
keepProb = 0.8 #保留节点的概率keepProb=0.8
# d3元素值为True或False
d3 = np.random.rand(a3.shape[0], a3.shape[1]) < keepProb
# 在相乘运算时,python会自动将True转化为1,False转化为0
# 所以可以选出概率大于keepProb的节点继续留下来进行计算
a3 = np.multiply(a3, d3)
a3 /= keepProb
其中a3 /= keepProb 是为了防止下一层的Z的期望值发生改变。
另外需要注意的是在测试阶段,我们不需要再使用dropout,而是像之前一样直接将各层的权重,偏差带入计算出预测值即可。
一神带九坑:https://www.cnblogs.com/unclelin/p/6423608.html
在上面归一化数据过程中需要减去均值,所以b这一项可以省略掉,然后归一化求得Znormal。接下来如下图:
注:
其中的gamma和beta是不需要人为设置,它是自动学习的。
训练段和测试段中BN的运用方式有所不同,训练过程中均值和方差所求的是每一个mini batch的均值方差,而在测试过程中,并不是求整个测试样本的均值方差。(训练时使用一个minibatch的训练数据计算均值和方差然后标准化数据,在test的时候我们也希望作相同的处理,但是test的速度越快越好)测试、预测时,直接使用在训练结束时保存下来的running_mean(moving average),计算公式如下:
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_var = momentum * running_var + (1 - momentum) * sample_var
#可以理解为每次更新running mean相当于把之前的值衰减一些(* momentum),然后把当前的minibatch sample mean加进去一部分(* (1-momentum))。其实也就是一阶指数平滑平均。
上述基于一个momentum参数(跟优化方法的momentum没什么关系)的指数衰减的running mean。
BN层常被用在conv卷积层与relu激活层之间,用来调整正向传播过程中卷积后特征层值的分布,可防止梯度消失或爆炸以减轻过拟合的情况。
超参数:(按调整重要性从高到低排列)
非常深的网络训练时存在梯度消失和梯度爆炸现象,残差网络解决了极深网络的梯度问题,让极深网络成为了可能。
计算公式:
N = (W − F + 2P )/S+1
参数:
卷积层输出的深度==卷积核的个数
池化层:
池化层(Pooling Layer)的理解则简单得多,其可以理解为对图像进行降采样的过程,对于每一次滑动窗口中的所有值,输出其中的最大值(MaxPooling)、均值或其他方法产生的值。例如,对于一个三通道的 16×16 图像(即一个 16*16*3
的张量),经过感受野为 2×2,滑动步长为 2 的池化层,则得到一个 8*8*3
的张量。
参考:
https://blog.csdn.net/fendouaini/article/details/83626441.
https://www.cnblogs.com/marsggbo/p/7501553.html#autoid-13-0-0