版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cdknight_happy/article/details/84997267
1 神经网络的表示
在计算神经网络的层数时,一般不把输入层计算在内。如一个由输入层、单隐藏层、输出层组成的神经网络,一般叫做是两层的神经网络。
上述单隐藏神经元的神经网络,神经元要做两件事情,一是计算
z=wTx+b,即计算输入和权重的内积;二是应用非线性激活函数,计算
a=σ(z),增强神经网络的表达能力。
对第一个隐藏层神经元,用矩阵进行计算表示:
z[1]=
⎣⎢⎢⎢⎢⎢⎡−−−−−−−−w1[1]Tw2[1]Tw3[1]Tw4[1]T−−−−−−−−⎦⎥⎥⎥⎥⎥⎤
⎣⎡x1x2x3⎦⎤ +
⎣⎢⎢⎡b1b2b3b4⎦⎥⎥⎤ =
⎣⎢⎢⎢⎢⎢⎡w1[1]Tx+b1w2[1]Tx+b2w3[1]Tx+b3w4[1]Tx+b4⎦⎥⎥⎥⎥⎥⎤ =
⎣⎢⎢⎢⎢⎢⎡z1[1]Tz2[1]Tz3[1]Tz4[1]T⎦⎥⎥⎥⎥⎥⎤
w[1],(4,3)的矩阵 (4,1)的向量
a[1]=σ(z[1])
上述神经网络的计算过程用矩阵表示为:
多个样本的向量化计算:
m个样本,显式的for循环进行前向计算过程
使用向量化消除for循环,即将输入样本组成一个
(nx,m)的矩阵X,
nx表示各输入样本的特征维度,m表示训练样本的总数目。即每一个样本变成一个列向量依次排列车矩阵。如下图所示。
那么,计算过程就变成了:
z[1]=w[1]X+b[1]a[1]=σ(z[1])z[2]=w[2]z[1]+b[2]a[2]=σ(z[2])
在矩阵
X,z[1],a[1],z[2],a[2]中,不同的列表示不同的样本;同一列的不同行对应不同的隐藏神经元。
把X表示成
A[0],可以看到神经网络就是重复进行下述两个计算过程:
Z[i]=W[i]A[i−1]+b[i]
A[i]=σ(Z[i])
2 激活函数
sigmoid(x)=1+e(−x)1,输出值范围(0,1);
tanh(x)=ez+e−zez−e−z,输出值范围(-1,1);
tanh(x)和sigmoid(x)是一个系列的激活函数,但是由于tanh(x)的中心点为0,对输入数据有类似于中心化的效果,使输入数据变换后的中心点为0,而不是sigmoid的中心点0.5,可以使得下一层的学习过程相对容易些,所以tanh一般要优于sigmoid。
现在几乎任何使用sigmoid函数的地方都可以使用tanh代替,但唯一的例外是二分类任务中,输出层的概率介于(0,1)之间是最好的结果,所以这是目前唯一一个还使用sigmoid函数的地方。除非用在二分类的输出层,否则绝对不要再使用sigmoid激活函数。
现在一般也不再使用sigmoid和tanh做激活函数了,因为,sigmoid(x)的对输入的导数为sigmoid(x) * (1 - sigmoid(x)),tanh(x)的导数为
1−(tanh(x))2,那么当输入值很大或很小时,sigmoid函数和tanh函数的导数都接近于0,如果神经网络的隐藏层使用了这样的激活函数,那么在梯度反向传播时,
∂z[i]∂x=∂a[i]∂x∗激活函数对
z[i]的导数,如果此时
z[i]的值很大或很小,那么激活函数对
z[i]的导数就接近于0,所以
∂z[i]∂x也接近于0,因此就阻碍了梯度往底层的传播,前面的层也就几乎停止了训练过程,这种现象称之为“梯度弥散”。这就是为什么目前主流神经网络都不再使用sigmoid和tanh作为激活函数的原因。
而sigmoid函数最“陡峭”的地方,即x=0处,梯度也只有0.25,也会减小反向传播的梯度值。而tanh在x=0处,梯度为1,对梯度的反向传播不会造成影响。
relu(x)=max(0,x)={x,x≥00,x<0
relu目前是神经网络默认的隐藏层激活函数。
两个优点:
- 输入值大于0时,激活函数的输出对输入的导数为1,不会造成梯度弥散,影响梯度的反向传播。实践证明,使用relu做激活函数,神经网络收敛速度比较快;
- relu有一半区域为0,这样也增加了数据的稀疏性,减少了计算量。
一个缺点:
如果输入值为负值,则输出为0,这样对负的输入值无法进行梯度反向传播。
一个要解释的点:
严格来说Relu函数并非全部可导,
x=0处左右导数不相等。但是因为我们输入值无限接近于0的可能性很小,因此这个问题一般都忽略不计。
leaky relu(x)={x,x≥00.01x,x<0
leaky relu为解决relu的缺点而提出的。输入为负时,也可以进行梯度的前传,只是相对正值输入的梯度要小一些。
prelu(x)={x,x≥0αx,x<0
α可以作为一个参数进行学习。prelu是leaky relu的泛化版本,增加了一个参数,但相比leaky relu提升有限。
为什么要使用非线性激活函数?
如果使用线性函数作为激活函数,因为线性函数的乘积任然是线性函数,那么无论神经网络有多深,输出都是输入的线性变换,这样和单层的线性激活函数是没有区别的,这样是没有任何意义的。只有使用非线性激活函数,才可以使神经网络学到有趣的变换,也才能完成一些有趣的应用。
唯一有可能需要线性激活函数的地方是,对于回归问题,输出是倒数第二层的输出的线性变换,此时用线性激活函数是合理的。
3 神经网络的梯度下降
二分类任务,假设输入样本为
nx个单元,隐藏层有
n1个单元,输出层有
n2个单元。因此对单个样本而言,
x∈Rnx×1,W[1]∈Rn1×nx,b[1]∈Rn1×1,z[1]∈Rn1×1,a[1]∈Rn1×1,W[2]∈Rn2×n1,b[2]∈Rn2×1,z[2]∈Rn2×1,a[2]∈Rn2×1,y∈Rn2×1。
对于单个样本而言,其反向传播的梯度为:
dz[2]=a[2]−y
dW[2]=dz[2]a[1]T(之所以转置是因为
dW[2]∈Rn2×n1,而这里
z[2]和
a[1]都是列向量,需要将
a[1]转置才可以计算外积得到矩阵)
db[2]=dz[2]
da[1]=W[2]Tdz[2]
dz[1]=da[1]∗g[1]′(z[1])(这里的“*”表示逐元素相乘)
dW[1]=dz[1]xT
db[1]=dz[1]
m个样本,向量化之后为:
X∈Rnx×m,W[1]∈Rn1×nx,b[1]∈Rn1×1,Z[1]∈Rn1×m,A[1]∈Rn1×m,W[2]∈Rn2×n1,b[2]∈Rn2×1,Z[2]∈Rn2×m,A[2]∈Rn2×m,Y∈Rn2×m。
dZ[2]=A[2]−Y
dW[2]=m1dz[2]A[1]T(之所以有
m1是因为计算损失函数时包含了
m1)
db[2]=m1np.sum(dZ[2],axis=1,keepdims=True)(使用keepdims = True是为了保障输出形状为(
n2,1),而不是(
n2,),更容易计算)
dA[1]=W[2]TdZ[2]
dZ[1]=dA[1]∗g[1]′(Z[1])(这里的“*”表示逐元素相乘)
$dW^{[1]} =
m1dZ[1]XT
db[1]=m1np.sum(dZ[1],axis=1,keepdims=True)
4 权重随机初始化
如果同一层的不同神经元具有相同的初始化参数,那么前向计算时,该层的不同神经元具有相同的输出。同样,多次梯度下降更新之后不同的神经元依然具有相同的参数,这种情况下,无论某一隐藏层有多少神经元,它们都是在学习相同的参数。这种对称性对神经网络的学习是相当不利的,我们还是希望不同的神经元去学习不同的函数。
因此,正确的权重初始化策略是使用随机初始化以破坏对称性。
w = np.random.randn((n_1,n_x)) * 0.01,b=np.zeros((n_1,1))。
- 一般权重进行随机初始化,偏置b还是初始化为0;
- 权重一般会随机初始化为比较小的值,之所以这样是因为,若使用sigmoid/tanh做激活函数,太大的权重易于使得输出进入函数饱和区,造成梯度弥散阻碍梯度反向传播;即便使用relu系的激活函数,不会出现梯度弥散现象,使用比较大的初始化权重也更容易破坏对称性。但是,比较大的初始化权重也易于造成梯度值比较大,称之为梯度爆炸,当然梯度爆炸可以通过梯度截断来缓解。
总之,一般对权重进行比较小的随机初始化,偏置项初始化为0.