深度学习(二)——浅层神经网络

浅层神经网络

神经网络概览

最为常见的由输入层、隐藏层和输出层三层构成的神经网络通常被称为双层神经网络,其中输入层为第零层(因为并不把输入层看作是一个标准的层),隐藏层和输出层分别为第一、二层。
隐藏层和输出层是带有参数的,隐藏层有两个相关参数w[1]和b[1],上标1表示是和第一层隐藏层有关。
接下来的例子里W[1]是一个4x3的矩阵(其中4表示有4个节点,或四个隐藏单元。3表示有三个输入特征),b[1]是一个4x1d的向量。
类似的第二层输出层也有相关参数W[2](1x4,表示又四个隐藏单元)和b[2](1x1,一个隐藏单元)。

计算神经网络的输出

之前讲到的逻辑回归模型,模型内的圆圈代表了逻辑回归计算的两个步骤。先计算出z,之后计算激活函数。
在这里插入图片描述
神经网络实际上就是重复计算这些步骤很多次。
在这里插入图片描述
这里就是对隐藏层的每一个节点(圆圈)进行计算:
例如第一节点:
z [ 1 ] = w T x + b z^{[1]}=w^Tx+b z[1]=wTx+b
a 1 [ 1 ] = σ ( z 1 [ 1 ] ) a_1^{[1]}=\sigma(z_1^{[1]}) a1[1]=σ(z1[1])
所以对于z和a按符号约定写成 a i [ l ] a^{[l]}_i ai[l] l表示层数,i表示层中的第几个节点
在这里插入图片描述
将w堆起来构成一个(4x3)的矩阵
[ . . . w 1 [ 1 ] T . . . . . . w 2 [ 1 ] T . . . . . . w 3 [ 1 ] T . . . . . . w 4 [ 1 ] T . . . ] \begin{bmatrix} ...&w_1^{[1]T} &... \\ ...&w_2^{[1]T} &... \\ ...&w_3^{[1]T} &... \\ ...&w_4^{[1]T} &... \\ \end{bmatrix} ............w1[1]Tw2[1]Tw3[1]Tw4[1]T............
也可以看做是我们拥有4个逻辑回归单元,并且每一个逻辑回归单元都有对应的参数向量w

W[1]的维度是(4,3),b[1]的维度是(4,1)
在这里插入图片描述
a[1]的维度是(4,1)
也可以说x等于a[0],就像 y ^ {\hat y} y^等于a[2]
输出层的参数W[2]的维度是(1,4),b[2]的维度是(1,1),也就得到z[2]是一个实数
z[1] = W[1]a[0] + b[1]
a[1] = σ(z[1])
z[2] = W[2]a[1] + b[2]
a[2] = σ(z[2])
最后的输出单元就像是逻辑回归模型的执行过程
当有一个单隐层神经网络,在代码中实现的过程就是计算以上四个等式,前两个式子是计算隐藏层的四个逻辑回归单元,后两个式子是计算输出层的逻辑回归单元。

多样本向量化实现

上一节说的对于输入的特征向量x单个训练样本,可以用它们生成一个 a [ 2 ] = y ^ a^{[2]}=\hat y a[2]=y^
现在如果有m个训练样本,就需要重复以上计算
例:用第一个训练样本x(1)来计算 y ^ ( 1 ) \hat y^{(1)} y^(1),即对第一训练样本的预测
同理,用x(2)来计算 y ^ ( 2 ) \hat y^{(2)} y^(2),x(m)来计算 y ^ ( m ) \hat y^{(m)} y^(m)
用激活函数来表示这些式子
a [ 2 ] ( 1 ) = y ^ ( 1 ) a^{[2](1)}=\hat y^{(1)} a[2](1)=y^(1)
a [ 2 ] ( 2 ) = y ^ ( 2 ) a^{[2](2)}=\hat y^{(2)} a[2](2)=y^(2)
a [ 2 ] ( m ) = y ^ ( m ) a^{[2](m)}=\hat y^{(m)} a[2](m)=y^(m)
这里a[2](i),i指训练样本i,2指第二层
代码表示
for i = 1 to m,
  z[1](i) = W[1]x(1) + b[1]
  a[1](i) = σ(z[1](i))
  z[2](i) = W[2]a[1](i) + b[2]
  a[2](i) = σ(z[2](i))
将训练样本x堆叠到矩阵各列,构成( n x n_x nx,m)维X矩阵
在这里插入图片描述
得到计算式子
Z[1] = W[1]X + b[1]
A[1] = σ(Z[1])
Z[2] = W[2]A[1] + b[2]
A[2] = σ(Z[2])
同理将向量z和a横向堆叠出矩阵Z和A
在这里插入图片描述
横向即训练集中的所有训练样本,竖向表示对应神经网络中的不同节点,例如矩阵A[1]中的左上角就表示第一个训练样本的第一个隐藏单元的激活函数;矩阵X的竖向对应不同的输入特征,即神经网络输入层的不同节点。

激活函数

之前式子里面一直提到的σ(·)就是所谓的激活函数
在这里插入图片描述
在更一般的情况下,可以使用不同的函数g(·),其中g可以是非线性函数,不一定是σ函数。
σ函数是介于0和1之间的,然而tanh函数(双曲正切函数)较σ表现更好
在这里插入图片描述
tanh函数介于-1和1之间
tanh ⁡ ( z ) = e z − e − z e z + e − z \tanh (z) = \frac{{{e^z} - {e^{ - z}}}}{{{e^z} + {e^{ - z}}}} tanh(z)=ez+ezezez
从数学角度讲tanh函数实际上是sigmoid函数变换得到的,即
将sigmoid函数向下平移1/2个单位长度
1 1 + e − z − 1 2 = 1 2 ⋅ 1 − e − z 1 + e − z \frac{1}{{1 + {e^{ - z}}}} - \frac{1}{2} = \frac{1}{2} \cdot \frac{{1 - {e^{ - z}}}}{{1 + {e^{ - z}}}} 1+ez121=211+ez1ez
再将变换后的函数横纵向拉伸2倍
( 1 2 ⋅ 1 − e − z × 2 1 + e − z × 2 ) × 2 = 1 − e − 2 z 1 + e − 2 z = e z − e − z e z + e − z (\frac{1}{2} \cdot \frac{{1 - {e^{ - z \times 2}}}}{{1 + {e^{ - z \times 2}}}}) \times 2 = \frac{{1 - {e^{ - 2z}}}}{{1 + {e^{ - 2z}}}} = \frac{{{e^z} - {e^{ - z}}}}{{{e^z} + {e^{ - z}}}} (211+ez×21ez×2)×2=1+e2z1e2z=ez+ezezez
如果使用tanh当激活函数,通常总是比σ函数效果更好,因为这时函数的输出介于-1和1之间,激活函数的平均值就更接近0。使用tanh函数代替sigmoid函数可以中心化你的数据,使得是数据的平均值接近0,而不是0.5。这样让下一层的学习更加方便,tanh函数几乎在所有场合都更优越。
不过有一个例外,那就是输出层, 因为如果y是0或1,那么得到的 y ^ \hat y y^介于0到1之间更合理,而不是-1和1之间。
使用σ激活函数的一个例外场合就是使用二元分类的时候,这种情况下,可以使用σ激活函数作为输出层。
在之前介绍的神经网络例子中,可以在隐藏层中使用tanh函数,在输出层使用σ函数。即不同层的激活函数可以不一样。
为了方便表示不同的激活函数g(·)可以使用上标进行区分,例如g[1]和g[2],数字表示第几层。
σ函数和tanh函数有一个共同的缺点,对于激活函数g(z),如果z非常大或非常小,那么导数的梯度,或者说这个函数的斜率可能就很小,接近于0,从而拖慢梯度下降算法。
在机器学习中,最受欢迎的一个选择是ReLU函数(修正线性单元)
在这里插入图片描述
只要z为正,导数就是1;当z为负时,导数或斜率为0。如果你实际使用这个函数,导数在z刚好为0的时候不是良定义的(not well-defined即有歧义的),但如果你编程实现,那么你得到的z恰好等于000000…的概率很低,所以实际中不用担心这点。你可以在z=0时,给导数赋值成1或0,尽管事实上这个函数不可微。

【选择激活函数的经验法则】
如果你的输出值是0和1,如果你在做二分类
那么sigmoid函数适合作为输出层的激活函数,然后其他所有单元都用ReLU。ReLU是激活函数的默认选择, 如果不确定隐藏层该用哪个,那么就用ReLU。
ReLU还有另一个版本leaky ReLU,这个函数在z为负时,不再为0。它通常比ReLU激活函数更好,不过实际使用的频率没那么高。
在这里插入图片描述
ReLU和leaky ReLU的优点在于对于大量z空间,激活函数的导数、激活函数的斜率和0差的很远。所以在实践中使用ReLU激活函数,通常可以使神经网络的学习速度加快很多,比使用tanh或σ激活函数快得多。
主要原因在于ReLU在函数斜率接近0时,几乎不出现减慢学习速度的效应。
对于z的另一半范围来说,ReLU的斜率为0,但在实践中,有足够多的的隐藏单元令z大于0,所以对大多数训练样本来说还是很快的。

【为什么神经网络需要非线性激活函数】
之前提到的神经网络计算公式,用g(z)代指激活函数
z[1] = W[1]x + b[1]
a[1] = g[1](z[1])
z[2] = W[2]a[1] + b[2]
a[2] = g[2](z[2])
为什么不能让g(z)=z,这时g(z)叫做线性激活函数或恒等激活函数,这是激活直接将输入值输出
为了说明问题,假设让a[2]=z[2],事实证明如果这样做,那么这个模型的输出y或 y ^ \hat y y^不过是你输入特征x的线性组合
如果 a[1] = z[1]
也就是 a[1] = W[1]x + b[1]
a[2] = z[2]
也就是 a[2] = W[2]a[1] + b[2]

a[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] = W’,(W[2]b[1] + b[2]) = b’
即上式为 W’x + b’
那么神经网络只是把输入线性组合再输出

如果你的神经网络有很多隐藏层,并且你使用线性激活函数或是没有激活函数。那么无论你的神经网络有多少层,只是一直在计算线性激活函数,那还不如直接去掉全部隐藏层。
对于一个双层神经网络,如果在第一层使用线性激活函数,在第二层使用sigmoid激活函数,那么这个模型的复杂度和没有任何隐藏层的标准逻辑回归是一样的。线性隐藏层一点用都没有,因为两个线性函数的组合本身就是线性函数,所以除非引入非线性,再多的网络层数也计算不出有趣的函数。
只有一个地方可以使用线性激活函数g(z)=z,也就是机器学习的回归问题。除了一些与压缩有关的非常特殊的情况,会在隐藏层用线性激活函数。除此之外使用线性激活函数非常少见。

【激活函数的导数】
当对神经网络使用反向传播的时候,需要计算激活函数的斜率或导数。
(1)sigmoid
对σ激活函数求导
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)) dzdg(z)=1+ez1(11+ez1)=g(z)(1g(z))
在这个式子中,如果z非常大,假设z=10
那么g(z)就接近1
d d z g ( z ) \frac{d}{{dz}}g(z) dzdg(z)就接近1·(1-1)=0
这实际上是对的,因为当z很大的时候,斜率接近0。
相反如果很小,例如z等于-10
那么g(z)接近0
d d z g ( z ) \frac{d}{{dz}}g(z) dzdg(z)就接近0·(1-0)=0
也是很接近于0,所以也是正确的。
如果z=0,那么g(z)=1/2
这就是sigmoid函数
这时导数 d d z g ( z ) = 1 2 ⋅ ( 1 − 1 2 ) = 1 4 \frac{d}{{dz}}g(z) = \frac{1}{2} \cdot (1 - \frac{1}{2})=\frac{1}{4} dzdg(z)=21(121)=41
可以证明这是正确的导数值,或者说是z=0时正确的函数斜率。

(2)tanh
可以得到导数
g ′ ( z ) = d d z g ( z ) = 1 − ( tanh ⁡ ( z ) ) 2 g'(z)=\frac{d}{{dz}}g(z) = 1 - {(\tanh (z))^2} g(z)=dzdg(z)=1(tanh(z))2
当z=10时,tanh(z)≈1,g’(z)≈0
所以当z很大的时候,斜率接近0
当z=-10,tanh(z)≈-1,g’(z)≈0
所以当z很小的时候,斜率接近0
当z=0,g’(z)=1

(3)ReLU、leaky ReLU
RELU:
g(z)=max(0,z)
当z<0,g’(z)=0
当z>0,g’(z)=1
当z=0,g’(z)没有定义
但是在软件中实现这个算法时,并不能百分百保证在数学上z为0,但是也有可能出现。如果z为0那么可以操作令导数为1或是0。
在优化技术上,g’变成了所谓的激活函数g(z)的次梯度,这就是梯度下降依然有效的原因。
但是z精确为0的概率非常小,所以将z=0处的导数设置成哪个值实际上无关紧要。所以在实践中,人们一般都这么做。
leaky ReLU:
g(z)=max(0.01z,z)
当z<0,g’(z)= 0.01
当z>0,g’(z)=1
z=0的梯度严格意义上是没有定义的,但是可以写一段代码来定义这个梯度,使z=0处g’(z)为0.01或1。

神经网络的梯度下降算法

【单层神经网络的参数(括号内为参数对应的维度)】
W[1] (n[1], n[0]),b[1] (n[1], 1),W[2] (n[2], n[1]),b[2] (n[2], 1)
输入特征数: n x = n [ 0 ] n_x=n^{[0]} nx=n[0]
隐藏单元数: n [ 1 ] n^{[1]} n[1]
输出单元数: n [ 2 ] n^{[2]} n[2](在之前的例子中,值介绍过 n [ 2 ] = 1 n^{[2]}=1 n[2]=1的情况)

【神经网络的成本函数】
假设研究是二分类问题,此时成本函数的参数为
J ( W [ 1 ] , b [ 1 ] , W [ 2 ] , b [ 2 ] ) = 1 m ∑ i = 1 n L ( y ^ , y ) J({W^{[1]}},{b^{[1]}},{W^{[2]}},{b^{[2]}}) = \frac{1}{m}\sum\limits_{i = 1}^n {L(\hat y,y)} J(W[1],b[1],W[2],b[2])=m1i=1nL(y^,y)
L表示当神经网络预测出 y ^ \hat y y^时的损失函数
y ^ \hat y y^相当于之前算式中的a[2]
正确的标注等于y(ground truth labels
如果做的是二分类,那么损失函数就和之前做逻辑回归完全一样,所以需要使用梯度下降法为算法训练参数。
在训练神经网络时,随机初始化参数很重要,不能将参数全都初始化为0。
当把参数初始化成某些值后,每个梯度下降循环都会计算预测值,所以需要计算预测值 y ^ ( i ) , i = 1 , . . . , m \hat y^{(i)},i=1,...,m y^(i),i=1,...,m,之后需要计算导数
d W [ 1 ] = ∂ J ∂ W [ 1 ] , d b [ 1 ] = ∂ J ∂ b [ 1 ] , . . . d{W^{[1]}} = \frac{{\partial J}}{{\partial {W^{[1]}}}},d{b^{[1]}} = \frac{{\partial J}}{{\partial {b^{[1]}}}},... dW[1]=W[1]J,db[1]=b[1]J,...
最终梯度下降更新会更新W[1],b[1],W[2],b[2]
W[1] := W[1] - α·dW[1]
b[1] := b[1] - α·db[1]
dW[1],db[1]为学习率,W[2]和b[2]同理
这就表示了梯度下降的一次迭代循环,训练过程中需要多次循环,直到得到的参数看起来收敛。

【计算导数方程】
正向传播:
Z[1] = W[1]X + b[1]
A[1] = g[1](Z[1])
Z[2] = W[2]A[1] + b[2]
A[2] = g[2](Z[2])=σ(Z[2])

反向传播(计算导数,针对所有样本的向量化):
dZ[2] = A[2] - Y
矩阵Y是(1×m)维矩阵
d W [ 2 ] = 1 m d Z [ 2 ] A [ 1 ] T d{W^{[2]}} = \frac{1}{m}d{Z^{[2]}}{A^{[1]T}} dW[2]=m1dZ[2]A[1]T
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 ) d{b^{[2]}} = \frac{1}{m}np.sum(d{Z^{[2]}},axis = 1,keepdims = True) db[2]=m1np.sum(dZ[2],axis=1,keepdims=True)
这三个式子跟逻辑的梯度下降非常相似
np.sum是Python的numpy命令,用于对矩阵的一个维度求和,水平相加求和;keepdims为了防止Python直接输出这些古怪的秩为1的数组,其维度为(n,…)。加上keepdims=True来确保Python输出的是矩阵,使db[2]这个向量的输出维度为(n,1),准确的来讲应该是(n[2],1)
到这位置,所做的和逻辑回归非常相似,但是当开始计算反向传播时,需要计算
d Z [ 1 ] = W [ 2 ] T d Z [ 2 ] ∗ g [ 1 ] ′ ( Z [ 1 ] ) d{Z^{[1]}} = {W^{[2]T}}d{Z^{[2]}} * {g^{[1]}}'({Z^{[1]}}) dZ[1]=W[2]TdZ[2]g[1](Z[1])
g[1]'是在隐藏层使用的激活函数的导数
反向传播中的第一个式子是基于使用σ函数进行二分类为前提的,所以已经包含在dz[2]的式子里了
*指逐个元素乘积,所以(W[2]TdZ[2])是一(n[1],m)维矩阵,后半部分就是一个(n[1],m)维矩阵
d W [ 1 ] = 1 m d Z [ 1 ] X T d{W^{[1]}} = \frac{1}{m}d{Z^{[1]}}{X^T} dW[1]=m1dZ[1]XT
d b [ 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 ) d{b^{[1]}} = \frac{1}{m}np.sum(d{Z^{[1]}},axis = 1,keepdims = True) db[1]=m1np.sum(dZ[1],axis=1,keepdims=True)
如果前面的n[2]=1,那么keepdims可能没那么重要,然而这里的db[1]维度为(n[1],1),为了不输出难以进行运算的古怪数组,除了使用keepdims参数外,还有另一种方法。但是要显式的调动reshape把np.sum输出结果写成矩阵形式,这样就得到了希望出现的矩阵形式。

直观理解反向传播

在这里插入图片描述
前一篇文章中讨论的逻辑回归中
正向传播先是得到z,之后是a,最后计算损失函数
之后反向传播,先计算da,然后是dz,最后计算dw和db
损失函数的定义是
L ( a , y ) = − y log ⁡ a − ( 1 − y ) log ⁡ ( 1 − a ) L(a,y) = - y\log a - (1 - y)\log (1 - a) L(a,y)=yloga(1y)log(1a)
对L求a的导得到了da
d a = − y a + 1 − y 1 − a da = - \frac{y}{a} + \frac{{1 - y}}{{1 - a}} da=ay+1a1y
再往反向走一步,计算dz时
之前计算出了
d z = a − y dz = a - y dz=ay
事实上,使用微积分的链式法则
d z = d a ⋅ g ′ ( z ) dz = da \cdot g'(z) dz=dag(z)
其中 g ( z ) = σ ( z ) g(z) = \sigma (z) g(z)=σ(z)
即对L求z的导数
d L d z = d L d a ⋅ d a d z \frac{{dL}}{{dz}} = \frac{{dL}}{{da}} \cdot \frac{{da}}{{dz}} dzdL=dadLdzda
这里仍然是逻辑回归,有 x 1 , x 2 , x 3 x_1, x_2, x_3 x1,x2,x3,并且只有一个sigmoid单元可以得到 y ^ \hat y y^和a,所以这里的激活函数是个sigmoid函数
当只有一个训练样本的时候,计算出dW和db
d w = d z ⋅ x d b = d z \begin{array}{l} dw = dz \cdot x\\ db = dz \end{array} dw=dzxdb=dz
这就是逻辑回归的反向传播。

当计算神经网络反向传播时,会做类似这样的计算。不过并非一次计算,而是进行了两次计算,因为在神经网络中,x并非直接连着输出单元,x首先进入了隐藏层,之后才连接到了输出单元。
所以和逻辑回归的一步计算不同,这里进行两步计算就像是神经网络内的两层。
在这里插入图片描述
双层神经网络中的反向传播做法:
向后推算出da[2],之后是dz[2],dW[2]和db[2],然后反向计算da[1],dz[1],dW[1]、db[1]。并不需要对输入x求导,因为监督学习的输入x是固定的,而我们并不想优化x,所以不用求导(至少在监督学习中,不会对x求导)。
da[2]和dz[2]的运算过程在这里同之前逻辑回归一样,所以可以得到
d z [ 2 ] = a [ 2 ] − y dz^{[2]} = a^{[2]} - y dz[2]=a[2]y
同理求得
d W [ 2 ] = d z [ 2 ] ⋅ a [ 1 ] T d b [ 2 ] = d z [ 2 ] \begin{array}{l} dW^{[2]} = dz^{[2]} \cdot a^{[1]T}\\ db^{[2]} = dz^{[2]} \end{array} dW[2]=dz[2]a[1]Tdb[2]=dz[2]
(在这里a[2]为(n[2],1)维,dz[2]为(n[2],1)维,a[1]为(n[1],1),dW[2]为(n[2],n[1])维,db[2]为(n[2],1)维)
到这里就完成了反向传播的一半,接下来可以继续计算da[1],虽然在实践中,da[1]和dz[1]的计算通常合并成一步,所以实际编程时用的是 d z [ 1 ] = W [ 2 ] T d z [ 2 ] ∗ g [ 1 ] ′ ( z [ 1 ] ) d{z^{[1]}} = {W^{[2]T}}d{z^{[2]}} * {g^{[1]}}'({z^{[1]}}) dz[1]=W[2]Tdz[2]g[1](z[1])
这里由于使用的是编程符号,导致有点乱,过程参考以下公式
在这里插入图片描述
(在这里dz[1]的维度是(n[1],1),dz[2]的维度是(n[2],1),W[2]的维度是(n[2],n[1]),g[1]’(z[1])的维度是(n[1],1))
在这里需要知道任意变量foo和dfoo,它们的维度相同。为了维度匹配,需要使用矩阵的转置。
(探究矩阵求导为什么会出现转置,参考博文
实现反向传播,必须确保矩阵的维度相互匹配。
继续计算得到
d W [ 1 ] = d z [ 1 ] ⋅ x T d b [ 1 ] = d z [ 1 ] \begin{array}{l} d{W^{[1]}} = d{z^{[1]}} \cdot {x^T}\\ d{b^{[1]}} = d{z^{[1]}} \end{array} dW[1]=dz[1]xTdb[1]=dz[1]
可以发现和第二层的反向传播非常相似,因为x扮演了a[0]的角色,实际上x的转置就是a[0]的转置 。
至此对于双层神经网络的反向传播,得到了6个关键式子
在这里插入图片描述
在每次训练单个训练样本时,必须写出反向传播。但是实际上并不会一个个样本进行计算,所以要把所有训练样本向量化。
得到反向传播的向量化方程:
d Z [ 2 ] = A [ 2 ] − Y d{Z^{[2]}} = {A^{[2]}} - Y dZ[2]=A[2]Y
d W [ 2 ] = 1 m d Z [ 2 ] A [ 1 ] T d{W^{[2]}} = \frac{1}{m}d{Z^{[2]}}{A^{[1]T}} dW[2]=m1dZ[2]A[1]T
这里的 1 m \frac{1}{{\rm{m}}} m1是因为成本函数
J ( . . . ) = 1 m ∑ i = 1 n ( y ^ , y ) {\rm{J(}}...{\rm{) = }}\frac{1}{m}\sum\limits_{i = 1}^n {(\hat y,y)} J(...)=m1i=1n(y^,y)
所以求导后会有额外项 1 m \frac{1}{{\rm{m}}} m1
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 ) d{b^{[2]}} = \frac{1}{m}np.sum(d{Z^{[2]}},axis = 1,keepdims = True) db[2]=m1np.sum(dZ[2],axis=1,keepdims=True)
d Z [ 1 ] = W [ 2 ] T d Z [ 2 ] ∗ g [ 1 ] ′ ( Z [ 1 ] ) d{Z^{[1]}} = {W^{[2]T}}d{Z^{[2]}}*{g^{[1]}}'\left( {{Z^{[1]}}} \right) dZ[1]=W[2]TdZ[2]g[1](Z[1])
(在这里dZ[1]的维度是(n[1],m),W[2]TdZ[2]的维度是(n[1],m),g[1]’(Z[1])的维度是(n[1],m))
d W [ 1 ] = 1 m d Z [ 1 ] X T d{W^{[1]}} = \frac{1}{m}d{Z^{[1]}}{X^T} dW[1]=m1dZ[1]XT
d b [ 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 ) d{b^{[1]}} = \frac{1}{m}np.sum(d{Z^{[1]}},axis = 1,keepdims = True) db[1]=m1np.sum(dZ[1],axis=1,keepdims=True)
至此得到了双层神经网络反向传播的向量化公式。
最后要提一点,事实证明在初始化参数的时候,使用随机初始化,不是将参数初始化为全零,将对神经网络的训练有非常重要的影响。

随机初始化

对于逻辑回归,可以将权重初始化为零。但如果将神经网络的个参数数组全部初始化为0,再使用梯度下降算法,那会完全无效。
下面我们来看看如果初始化权重为零,会出现什么情况
在这里插入图片描述
在这里,输入层n[0]=2,隐藏层n[1]=2,那么W[1]是一个2×2的矩阵
如果令
W [ 1 ] = [ 0 0 0 0 ] W^{[1]}=\begin{bmatrix} 0&0 \\ 0& 0 \end{bmatrix} W[1]=[0000] b [ 1 ] = [ 0 0 ] b^{[1]}=\begin{bmatrix} 0 \\ 0 \end{bmatrix} b[1]=[00]
将偏置项b初始化为零实际上是可行的,但是将W初始化为零会出现问题
这种初始化形式的问题在于,在给网络输入任何样本时, a 1 [ 1 ] = a 2 [ 1 ] a^{[1]}_1=a^{[1]}_2 a1[1]=a2[1],它们经过激活函数计算得到的值完全一样,因为两个隐藏单元都在做完全一样的计算(w,b还有激活函数都相同)。
当进行反向传播时,事实证明,出于对称性 d z 1 [ 1 ] = d z 2 [ 1 ] dz^{[1]}_1=dz^{[1]}_2 dz1[1]=dz2[1],两个隐藏单元会以同样的方式初始化。确切的讲,就是假设输出的权重也是一样的,即
W [ 2 ] = [ 0 0 ] W^{[2]}=\begin{bmatrix} 0 & 0\end{bmatrix} W[2]=[00]
如果以这种方式初始化神经网络,那么隐藏单元 a 1 [ 1 ] a^{[1]}_1 a1[1] a 2 [ 1 ] a^{[1]}_2 a2[1]就完全一样了,也就是所谓的完全对称,意味着得出了完全一样的函数。
可以通过归纳法证明,每次进行迭代训练后,两个隐藏单元仍然在计算完全相同的函数,因为dW是这样一个矩阵
d W = [ u v u v ] dW=\begin{bmatrix} u & v\\ u & v\end{bmatrix} dW=[uuvv]
各行相同,然后执行一次权重更新
W [ 1 ] = W [ 1 ] − α ⋅ d W W^{[1]}=W^{[1]}-α·dW W[1]=W[1]αdW
你会发现每次迭代后,W[1]的第一行和第二行都是完全一样的。由此可以通过归纳法来证明,如果将W的所有值都初始化为0,因为两个隐藏单元一开始就是在做同样的计算,所以两个隐藏单元对输出单元的影响也一样大。那么在一次迭代后,同样的对称性依然存在,两个隐藏单元仍然是对称的。之后不论迭代多少层,两个隐藏单元仍然在计算完全一样的函数。所以在这种情况下,多隐藏单元并没有实际意义。
同理隐藏层包含多个隐藏单元,其权重均初始化为零时,所有隐藏单元依然都是对称的。

【解决方案】
这个问题的解决方案就是随机初始化所有参数
可以令 W[1]=np.random.randn((2,2))*0.01
将权重初始化为很小的随机数
b并没有对称性问题,将b初始化为零是可以
b[1]=np.zeros((2,1))
因为只要W随机初始化,那么就可以使用不同的隐藏单元计算不同函数。
同理可以这样初始化W[2]和b[2]
W[2]=np.random.randn((1,2))*0.01
b[2]=0
在这里使用0.01,主要是因为通常偏向将权重矩阵初始化为非常小的随机值。由于如果使用的是tanh或sigmoid激活函数,或者在输出层有一个sigmoid函数,这里权重太大会导致经过tanh和sigmoid函数运算得到的值,掉在了函数的平缓部分,梯度的斜率非常小,最终结果意味着梯度下降法会非常慢,减慢学习速度。
实际上,有比0.01更好用的常数。不过在这里训练的是一个单隐藏层的神经网络,设为0.01还是可以的。当训练一个很的神经网络时,可能就需要尝试0.01之外的常数了。

课后作业

【测验】
存在问题
题(1)
a 4 [ 2 ] a^{[2]}_4 a4[2]指第二层中的第四个结点的激活函数值
a [ 2 ] ( 12 ) a^{[2](12)} a[2](12)指第12个训练样本的第二层得到的激活函数值
题(7)
逻辑回归没有隐藏层。如果将权重初始化为零,那么逻辑回归中第一个样本x将输出零。但是逻辑回归的导数依赖于非零的输入x,所以在第二次迭代中,权重遵循了x的分布并且当x不是常向量时彼此之间分布不同。

【编程作业】
【Logistic回归部分】
(1) numpy.squeeze(a, axis = None)
从数组的shape中,去掉维度为1的条目。
a表示输入的数组
axis:用于删除数组shape中指定的一维项,指定项必须是一维的,否则会报错
举例说明:

# shape 
import numpy as np

a = np.array([1,2,3])
b = np.array([[1],[2],[3]])
c = np.array([[1,2,3],[4,5,6]])
d = np.array([[1],[2,3],[4,5,6]])


print ("a.shape=", a.shape)
print ("b.shape=", b.shape)
print ("c.shape=", c.shape)
print ("c.shape[0]=", c.shape[0])
print ("c.shape[1]=", c.shape[1])
print ("d.shape=", d.shape)
print ("d.shape[0]=", d.shape[0])
# output
a.shape= (3,)
b.shape= (3, 1)
c.shape= (2, 3)
c.shape[0]= 2
c.shape[1]= 3
d.shape= (3,)
d.shape[0]= 3
# squeeze
import numpy as np
a = np.array([[[1,2,3],[4,5,6],[7,8,9]]])
b = np.array([[[1],[2],[3]]])

print("a.shape=", a.shape)
print("a=", a)
print("np.squeeze(a)=", np.squeeze(a))
print("np.squeeze(a,axis=0)=", np.squeeze(a,axis=0))
print("b.shape=", b.shape)
print("b=", b)
print("np.squeeze(b)=", np.squeeze(b))
print("np.squeeze(b,axis=0)=", np.squeeze(b,axis=0))
print("np.squeeze(b,axis=2)=", np.squeeze(b,axis=2))
# output
a.shape= (1, 3, 3)
a= [[[1 2 3]
  [4 5 6]
  [7 8 9]]]
np.squeeze(a)= [[1 2 3]
 [4 5 6]
 [7 8 9]]
np.squeeze(a,axis=0)= [[1 2 3]
 [4 5 6]
 [7 8 9]]
b.shape= (1, 3, 1)
b= [[[1]
  [2]
  [3]]]
np.squeeze(b)= [1 2 3]
np.squeeze(b,axis=0)= [[1]
 [2]
 [3]]
np.squeeze(b,axis=2)= [[1 2 3]]

从得出的结果可以看出,squeeze操作实际上就是将数据中一维的括号去掉了

(2)matplotlib.pyplot.scatter()
plt.scatter(X[0, :], X[1, :], c=np.squeeze(Y), s=40, cmap=plt.cm.Spectral)
X[0, :], X[1, :]:取出数据集X中的两类数据,描点表示
在这里插入图片描述
c:标记颜色
用于将两类数据的颜色区分开
下图为去掉 c=np.squeeze(Y)得到的结果
在这里插入图片描述
s:标记大小
下图是s为20的情况
在这里插入图片描述
cmap:色彩盘,colormap是MATLAB里面用来设定和获取当前色图的函数
(3)DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples, ), for example using ravel().
在预计一维数组的情况下,传入了列向量y。请使用例如ravel()的函数改变y的维度。
(4)numpy.ravel()、numpy.flatten()、numpy.squeeze()
ndarray.flatten(order=‘C’):
返回一份数组拷贝,对拷贝所做的修改不会影响原始数组。
order:‘C’ – 按行,‘F’ – 按列,‘A’ – 原顺序,‘K’ – 元素在内存中的出现顺序。
numpy.ravel(a, order=‘C’):
展平的数组元素,顺序通常是"C风格",返回的是数组视图,修改会影响原始数组。
numpy.squeeze 函数从给定数组的形状中删除一维的条目。
(5)在已给的代码包下,文件planar_utils.py里的plot_decision_boundary函数,修改该函数最后一句plt.scatter(X[0, :], X[1, :], c=y, cmap=plt.cm.Spectral),c=y改为c=np.squeeze(y)或者c=y.ravel()
(6)numpy.meshgrid()从坐标向量中返回坐标矩阵,在实例 np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))中,其中h=0.01。表示将x和y的范围按0.01精度进行分割,并将分割出的横纵坐标分别存在xx和yy矩阵中.
(7)numpy.c_:按行转换成矩阵;numpy.r_:按列转换成矩阵:

import numpy as np

a = np.array([[1,2,3],[4,5,6]])
b = np.array([[7,8,9],[10,11,12]])

c = np.c_[a,b]
r = np.r_[a,b]
print("c=",c)
print("r=",r)
# output
c= [[ 1  2  3  7  8  9]
 [ 4  5  6 10 11 12]]
r= [[ 1  2  3]
 [ 4  5  6]
 [ 7  8  9]
 [10 11 12]]

(8)lambda表达式,用于定义一个匿名函数,匿名函数的内容简单,例如
in: add = lambda x, y : x+y
in: add(1,2)
out: 3
详情及应用

plot_decision_boundary(lambda x: clf.predict(x), X, Y)
def plot_decision_boundary(model, X, y):
Z = model(np.c_[xx.ravel(), yy.ravel()])

这三条语句配合起来就是

model = lambda x: clf.predict(x)
clf.predict(np.c_[xx.ravel(), yy.ravel()])

clf.predict()是用训练得到的模型对按0.01精度分割的坐标点进行预测,返回预测结果。此时得到结果Z,Z.shape= (1038240,)。
对Z进行变形,Z.reshape(xx.shape),xx.shape=(1008, 1030),即将Z变形为1008×1030阶矩阵
(9)matplotlib.pyplot contourf()
用于绘制等高线,contour和contourf都是画三维等高线图的,不同点在于contour() 是绘制轮廓线,contourf()会填充轮廓。
下图分别为使用contour和contourf函数所绘制的图片
在这里插入图片描述
在这里插入图片描述
(10)numpy.dot()返回两数组的点积
处理一维数组,返回两数组的内积,即对应位置相乘再相加。
处理二维数组,返回矩阵积,即完成矩阵乘法。
float((np.dot(Y, LR_predictions) + np.dot(1 - Y,1 - LR_predictions))
在这里是将真正的标记和预测出的标记做内积,可以的到预测准确的数量,前半部分是预测是真实标记均为1的数量,后半部分是预测和真实标记均为0的数量。
(11)使用logistic回归算法建立的模型测试的准确性只有47%,原因是logistic回归的决策边界是线性的,而从图里明显可以看出数据的分布是非线性的。【注意线性回归方程并非线性方程,一个线性回归模型不需要是自变量的线性函数——线性回归方程

【神经网络部分】
(1)numpy.multiply():数组和矩阵对应位置相乘,输出与相乘数组/矩阵的大小一致。
(2)numpy.sum(a, axis=1, keepdims=True):
axis 可选参数,默认None,可以是整数或者整数元组,用于某个轴或多个轴方向上进行求和运算。
keepdims 布尔类型的可选参数(keep dimensions),默认False,被删去的维度在结果矩阵中就被设置为一。函数详解
详解的例子中axis=0的情况下是从第二维层面,对应相加再组合。即
[ 0 + 12 1 + 13 2 + 14 3 + 15 4 + 16 . . . . . . . . . 8 + 20 . . . . . . . . . ] \begin{bmatrix} 0+12&1+13&2+14&3+15 \\ 4+16& ...& ...& ...\\ 8+20&...& ...& ... \end{bmatrix} 0+124+168+201+13......2+14......3+15......
维度:1×3×4(axis=-3结果同axis=0)
axis=1的情况下是从第三维层面,对应位置相加再组合。即
[ 0 + 4 + 8 1 + 5 + 9 2 + 6 + 10 3 + 7 + 11 12 + 16 + 20 . . . . . . . . . ] \begin{bmatrix} 0+4+8&1+5+9&2+6+10&3+7+11 \\ 12+16+20&...&...&... \end{bmatrix} [0+4+812+16+201+5+9...2+6+10...3+7+11...]
维度:2×4(axis=-2结果同axis=1)
axis=2的情况下是从第四维层面,对应位置相加再组合。即
[ 0 + 1 + 2 + 3 4 + 5 + 6 + 7 8 + 9 + 10 + 11 12 + 13 + 14 + 15 . . . . . . ] \begin{bmatrix} 0+1+2+3&4+5+6+7&8+9+10+11 \\ 12+13+14+15&...&... \end{bmatrix} [0+1+2+312+13+14+154+5+6+7...8+9+10+11...]
维度:2×3(axis=-1结果同axis=2)

import numpy as np

a = np.arange(24).reshape(2,3,4)
print("a:\n", a)
print("np.sum(a, axis=0):\n", np.sum(a, axis=0))
print("np.sum(a, axis=1):\n", np.sum(a, axis=1))
print("np.sum(a, axis=2):\n", np.sum(a, axis=2))
print("np.sum(a, axis=-1):\n", np.sum(a, axis=-1))
print("np.sum(a, axis=-2):\n", np.sum(a, axis=-2))
print("np.sum(a, axis=-3):\n", np.sum(a, axis=-3))
# 输出结果
a:
 [[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
np.sum(a, axis=0):
 [[[12 14 16 18]
  [20 22 24 26]
  [28 30 32 34]]]
np.sum(a, axis=1):
 [[12 15 18 21]
 [48 51 54 57]]
np.sum(a, axis=2):
 [[ 6 22 38]
 [54 70 86]]
np.sum(a, axis=-1):
 [[ 6 22 38]
 [54 70 86]]
np.sum(a, axis=-2):
 [[12 15 18 21]
 [48 51 54 57]]
np.sum(a, axis=-3):
 [[12 14 16 18]
 [20 22 24 26]
 [28 30 32 34]]
# keepdims = True 时
# 输出结果
a:
 [[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
np.sum(a, axis=0,keepdims=True):
 [[[12 14 16 18]
  [20 22 24 26]
  [28 30 32 34]]]
np.sum(a, axis=1,keepdims=True):
 [[[12 15 18 21]]

 [[48 51 54 57]]]
np.sum(a, axis=2,keepdims=True):
 [[[ 6]
  [22]
  [38]]

 [[54]
  [70]
  [86]]]
np.sum(a, axis=-1,keepdims=True):
 [[[ 6]
  [22]
  [38]]

 [[54]
  [70]
  [86]]]
np.sum(a, axis=-2,keepdims=True):
 [[[12 15 18 21]]

 [[48 51 54 57]]]
np.sum(a, axis=-3,keepdims=True):
 [[[12 14 16 18]
  [20 22 24 26]
  [28 30 32 34]]]
axis012
维度1×3×42×1×42×3×1

(3)numpy.power(x1, x2):
x1求x2对应位置该值的次方,x2可以是数字或数组,当x2是数组时,要求x1和x2的列数相同

import numpy as np

x1 = np.arange(8).reshape(1,8)
x2 = np.array([[1,2,0,3,2,2,3,0]])
print("x1:\n",x1)
print("x2:\n",x2)
print("np.power(x1,2)\n",np.power(x1,2))
print("np.power(x1,x2)\n",np.power(x1,x2))
# output
x1:
 [[0 1 2 3 4 5 6 7]]
x2:
 [[1 2 0 3 2 2 3 0]]
np.power(x1,2):
 [[ 0  1  4  9 16 25 36 49]]
np.power(x1,x2):
 [[  0   1   1  27  16  25 216   1]]

(4)(tanh(x))’=1-tanh2(x)
(5)nn_model()函数下的parameters = update_parameters(parameters,grads,learning_rate = 0.5)里的学习率应改为learning_rate = 1.2,来同update_parameters()函数相匹配,anaconda3下的Python3.7并不会报错。
(6)plt.figure():在plt中绘制一张图片
plt.subplot:创建单个子图
(7)在隐藏层结点为4的情况下,改变学习率

# ************ grads,learning_rate = 0.1 ************
# ********************** output *********************
第  0  次循环,成本为:0.6930480201239823
第  1000  次循环,成本为:0.6082072899772629
第  2000  次循环,成本为:0.36073810801711903
第  3000  次循环,成本为:0.3290211712344614
第  4000  次循环,成本为:0.3169254361704178
第  5000  次循环,成本为:0.30973342826059314
第  6000  次循环,成本为:0.3046867806714765
第  7000  次循环,成本为:0.30080373411176864
第  8000  次循环,成本为:0.2976318125152893
第  9000  次循环,成本为:0.29493076176193456
准确率: 89%

# ************ grads,learning_rate = 0.5 ************
# ********************** output *********************
第  0  次循环,成本为:0.6930480201239823
第  1000  次循环,成本为:0.3098018601352803
第  2000  次循环,成本为:0.2924326333792646
第  3000  次循环,成本为:0.2833492852647412
第  4000  次循环,成本为:0.27678077562979253
第  5000  次循环,成本为:0.2634715508859308
第  6000  次循环,成本为:0.24204413129940755
第  7000  次循环,成本为:0.23552486626608762
第  8000  次循环,成本为:0.2314096450985427
第  9000  次循环,成本为:0.22846408048352362
准确率: 90%

# ************ grads,learning_rate = 1.0 ************
# ********************** output *********************
第  0  次循环,成本为:0.6930480201239823
第  1000  次循环,成本为:0.29229023562502554
第  2000  次循环,成本为:0.276357149207868
第  3000  次循环,成本为:0.24083501866130597
第  4000  次循环,成本为:0.23106003194293667
第  5000  次循环,成本为:0.2260242509095667
第  6000  次循环,成本为:0.22267730382455533
第  7000  次循环,成本为:0.22018493619014953
第  8000  次循环,成本为:0.21820806978190718
第  9000  次循环,成本为:0.2165878755163115
准确率: 90%

# ************ grads,learning_rate = 10 ************
# ********************** output *********************
第  0  次循环,成本为:0.6930480201239823
第  1000  次循环,成本为:0.2810720970389547
第  2000  次循环,成本为:0.26576363650532914
第  3000  次循环,成本为:0.2580216345974393
第  4000  次循环,成本为:0.2533743499376164
第  5000  次循环,成本为:0.2503057082048681
第  6000  次循环,成本为:0.2481490876972756
第  7000  次循环,成本为:0.24654832535042956
第  8000  次循环,成本为:0.24530008619469737
第  9000  次循环,成本为:0.24434823473941694
准确率: 90%

学习率的大小对模型训练的影响

(8)以下改变第一次激活的激活函数方法仅供参考,由于得出的结果个人认为很奇怪,如果有大佬发现错误请指正,非常感谢。
修改第一次激活的激活函数为sigmoid时,不同学习率下得到的结果

# planar_utils.py中
# 修改函数forward_propagation中
A1 = sigmoid(Z1)

# 修改函数backward_propagation中
dZ1 = np.multiply(np.dot(W2.T, dZ2), dsigmoid(A1))

在这里插入图片描述

修改第一次激活的激活函数为ReLU时,不同学习率下得到的结果

# 修改函数forward_propagation中
A1 = np.maximum(Z1, 0)

# 修改函数backward_propagation中
dZ1 = np.multiply(np.dot(W2.T, dZ2), np.where(A1>0, 1, 0))

在这里插入图片描述
【索引】
深度学习(一)——神经网络基础
深度学习(三)——深层神经网络

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值