之前学习机器学习的时候已经写了几篇有关神经网络的博客,最近看吴恩达深度学习的视频,其中的神经网络跟之前又有所不同,所以记个笔记。
基本结构与符号约定
基本结构还是输入层、隐藏层,中间层,激励用字母
a
a
a表示,单元的层标号放在方括号中,圆括号中为样本标号,所以有输入层
x
x
x(
a
[
0
]
a^{[0]}
a[0])、隐藏层
a
[
1
]
a^{[1]}
a[1]和输出层
a
[
2
]
a^{[2]}
a[2]。运算过程中需要用到权重
w
w
w和偏置
b
b
b,单元内的函数仍然是逻辑函数
σ
(
z
)
=
1
1
+
e
−
z
\sigma(z)=\frac{1}{1+e^{-z}}
σ(z)=1+e−z1。
声明数据矩阵
X
(
n
×
m
)
X(n\times m)
X(n×m),权重矩阵
W
[
l
]
(
s
l
×
s
l
−
1
)
W^{[l]}(s_l\times s_{l-1})
W[l](sl×sl−1)和偏置矩阵
b
[
l
]
(
s
l
×
1
)
b^{[l]}(s_l\times 1)
b[l](sl×1),激励矩阵
A
[
l
]
(
s
l
×
m
)
A^{[l]}(s_l\times m)
A[l](sl×m),
n
[
l
]
n^{[l]}
n[l]表示第
l
l
l层的单元个数,令
A
[
0
]
=
X
=
[
∣
∣
∣
x
(
1
)
x
(
2
)
⋯
x
(
m
)
∣
∣
∣
]
,
W
[
l
]
=
[
−
w
1
[
l
]
T
−
−
w
2
[
l
]
T
−
⋯
−
w
n
[
l
]
[
l
]
T
−
]
,
b
[
l
]
=
[
b
1
[
l
]
b
2
[
l
]
⋮
b
n
[
l
]
[
l
]
]
A^{[0]}=X=\left[\begin{matrix} |&|& &|\\ x^{(1)}&x^{(2)}&\cdots&x^{(m)}\\ |&|& &|\\ \end{matrix}\right], W^{[l]}=\left[\begin{matrix} -&w_1^{[l]T}&-\\ -&w_2^{[l]T}&-\\ &\cdots&\\ -&w_{n^{[l]}}^{[l]T}&-\\ \end{matrix}\right], b^{[l]}=\left[\begin{matrix} b^{[l]}_1\\ b^{[l]}_2\\ \vdots\\ b^{[l]}_{n^{[l]}}\\ \end{matrix}\right]
A[0]=X=⎣⎡∣x(1)∣∣x(2)∣⋯∣x(m)∣⎦⎤,W[l]=⎣⎢⎢⎢⎡−−−w1[l]Tw2[l]T⋯wn[l][l]T−−−⎦⎥⎥⎥⎤,b[l]=⎣⎢⎢⎢⎢⎡b1[l]b2[l]⋮bn[l][l]⎦⎥⎥⎥⎥⎤
还有一些补充可见机器学习中的神经网络。
向前传播
有机器学习中的神经网络向量化推导打底,再加上向前传播比较简单,我们就直接上多组数据+多隐藏层的情况吧。
中间向量
z
[
l
]
z^{[l]}
z[l]为
z
[
l
]
=
[
z
1
[
l
]
z
2
[
l
]
⋮
z
n
[
l
]
[
l
]
]
=
[
w
1
[
l
]
T
a
[
l
−
1
]
+
b
1
[
l
]
w
2
[
l
]
T
a
[
l
−
1
]
+
b
2
[
l
]
⋮
w
n
[
l
]
[
l
]
T
a
[
l
−
1
]
+
b
n
[
l
]
[
l
]
]
=
W
[
l
]
a
[
l
−
1
]
+
b
[
l
]
z^{[l]}=\left[\begin{matrix} z^{[l]}_1\\ z^{[l]}_2\\ \vdots\\ z^{[l]}_{n^{[l]}}\\ \end{matrix}\right]= \left[\begin{matrix} w^{[l]T}_1a^{[l-1]}+b_1^{[l]}\\ w^{[l]T}_2a^{[l-1]}+b_2^{[l]}\\ \vdots\\ w^{[l]T}_{n^{[l]}}a^{[l-1]}+b_{n^{[l]}}^{[l]}\\ \end{matrix}\right]= W^{[l]}a^{[l-1]}+b^{[l]}
z[l]=⎣⎢⎢⎢⎢⎡z1[l]z2[l]⋮zn[l][l]⎦⎥⎥⎥⎥⎤=⎣⎢⎢⎢⎢⎡w1[l]Ta[l−1]+b1[l]w2[l]Ta[l−1]+b2[l]⋮wn[l][l]Ta[l−1]+bn[l][l]⎦⎥⎥⎥⎥⎤=W[l]a[l−1]+b[l]
所以由
z
[
l
]
(
i
)
z^{[l](i)}
z[l](i)构成的矩阵
Z
[
l
]
Z^{[l]}
Z[l]为
Z
[
l
]
=
[
∣
∣
∣
z
[
l
]
(
1
)
z
[
l
]
(
2
)
⋯
z
[
l
]
(
m
)
∣
∣
∣
]
=
W
[
l
]
A
[
l
−
1
]
+
b
[
l
]
Z^{[l]}=\left[\begin{matrix} |&|& &|\\ z^{[l](1)}&z^{[l](2)}&\cdots&z^{[l](m)}\\ |&|& &|\\ \end{matrix}\right]= W^{[l]}A^{[l-1]}+b^{[l]}
Z[l]=⎣⎡∣z[l](1)∣∣z[l](2)∣⋯∣z[l](m)∣⎦⎤=W[l]A[l−1]+b[l]
而隐藏层的激励矩阵
A
[
l
]
A^{[l]}
A[l]就是
A
[
l
]
=
[
∣
∣
∣
a
[
l
]
(
1
)
a
[
l
]
(
2
)
⋯
a
[
l
]
(
m
)
∣
∣
∣
]
=
σ
(
Z
[
l
]
)
=
σ
(
W
[
l
]
A
[
l
−
1
]
+
b
[
l
]
)
A^{[l]}=\left[\begin{matrix} |&|& &|\\ a^{[l](1)}&a^{[l](2)}&\cdots&a^{[l](m)}\\ |&|& &|\\ \end{matrix}\right]=\sigma(Z^{[l]}) =\sigma(W^{[l]}A^{[l-1]}+b^{[l]})
A[l]=⎣⎡∣a[l](1)∣∣a[l](2)∣⋯∣a[l](m)∣⎦⎤=σ(Z[l])=σ(W[l]A[l−1]+b[l])
其他激活函数
之前我们的神经网络都是沿用的逻辑回归使用的逻辑函数,但事实上在神经网络中有许多更好的选择。
tanh函数
tanh
(
z
)
=
e
z
−
e
−
z
e
z
+
e
−
z
\tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}
tanh(z)=ez+e−zez−e−z
图像如下:
tanh
\tanh
tanh函数几乎是严格优越于逻辑函数,因为
tanh
\tanh
tanh函数使得激励的平均值在0左右,这能让下一层的计算更轻松。除了在输出层,我们期望的是
0
∼
1
0\sim 1
0∼1之间的输出,这时我们可以在输出层使用逻辑函数。
该函数的导数为
tanh
′
(
z
)
=
1
−
(
tanh
(
z
)
)
2
\tanh'(z)=1-(\tanh(z))^2
tanh′(z)=1−(tanh(z))2
ReLU函数
不过逻辑函数和
tanh
\tanh
tanh函数都有一个毛病,那就是在坐标的绝对值很大的时候,函数的梯度会变得非常小,这样我们在运行类似梯度下降的算法时收敛的速度就会变得很慢。而ReLU函数就可以解决这个问题,其表达式为
ReLU
(
z
)
=
max
(
0
,
z
)
\text{ReLU}(z)=\max(0,z)
ReLU(z)=max(0,z)
图像为:
这样,只要
z
>
0
z>0
z>0,导数就是1,而
z
<
0
z<0
z<0时导数则是0。虽然从数学上
z
=
0
z=0
z=0处并没有导数,但是
z
z
z值刚好为0的概率非常小,而且我们可以人为定义其为1或0,这在实际应用中无伤大雅。
一般来讲,如果要做一个二元分类问题,我们可能会使用 tanh \tanh tanh函数,输出层再加一个逻辑函数,而在其他时候一般默认ReLU函数。
Leaky ReLU
在实际运用中,ReLU函数的表现通常很不错,但是因为它负数部分的导数为0,所以对于这部分它的梯度下降速率会很慢,尽管在一个网络中,我们会有很多正的部分,使参数整体仍以一个较快的速度学习。如果不放心,可以给负的部分也设置一个较小的斜率,比如
0.01
0.01
0.01,这样激活函数就表示为
Leaky ReLU
(
z
)
=
max
(
0.01
z
,
z
)
\text{Leaky ReLU}(z)=\max(0.01z,z)
Leaky ReLU(z)=max(0.01z,z)
向后传播
浅层神经网络
向后传播较复杂,我们先推一个浅层神经网络:
首先,对于最后一步代价函数,其值为
L
(
a
[
2
]
,
y
)
=
−
y
log
a
[
2
]
−
(
1
−
y
)
log
(
1
−
a
[
2
]
)
\mathcal{L}(a^{[2]},y)=-y\log a^{[2]}-(1-y)\log(1-a^{[2]})
L(a[2],y)=−yloga[2]−(1−y)log(1−a[2])
对
a
[
2
]
a^{[2]}
a[2]求微分,可得
d
L
d
a
[
2
]
=
−
y
a
[
2
]
+
1
−
y
1
−
a
[
2
]
\frac{d\mathcal{L}}{da^{[2]}}=-\frac{y}{a^{[2]}}+\frac{1-y}{1-a^{[2]}}
da[2]dL=−a[2]y+1−a[2]1−y
则
z
[
2
]
z^{[2]}
z[2]对代价函数的微分为
d
L
d
z
[
2
]
=
d
L
d
a
[
2
]
d
a
[
2
]
d
z
[
2
]
=
(
−
y
a
[
2
]
+
1
−
y
1
−
a
[
2
]
)
a
[
2
]
(
1
−
a
[
2
]
)
=
a
[
2
]
−
y
\frac{d\mathcal{L}}{dz^{[2]}}=\frac{d\mathcal{L}}{da^{[2]}}\frac{d a^{[2]}}{dz^{[2]}}=(-\frac{y}{a^{[2]}}+\frac{1-y}{1-a^{[2]}})a^{[2]}(1-a^{[2]})=a^{[2]}-y
dz[2]dL=da[2]dLdz[2]da[2]=(−a[2]y+1−a[2]1−y)a[2](1−a[2])=a[2]−y
之后计算
d
L
d
W
[
2
]
\frac{d\mathcal{L}}{dW^{[2]}}
dW[2]dL和
d
L
d
b
[
2
]
\frac{d\mathcal{L}}{db^{[2]}}
db[2]dL为
d
L
d
W
[
2
]
=
d
L
d
z
[
2
]
a
[
1
]
T
d
L
d
b
[
2
]
=
d
L
d
z
[
2
]
\frac{d\mathcal{L}}{dW^{[2]}}=\frac{d\mathcal{L}}{dz^{[2]}}a^{[1]T}\\ \frac{d\mathcal{L}}{db^{[2]}}=\frac{d\mathcal{L}}{dz^{[2]}}
dW[2]dL=dz[2]dLa[1]Tdb[2]dL=dz[2]dL
现在推导已经完成了一半了,我们再计算
a
[
1
]
a^{[1]}
a[1]的导数为
d
L
d
a
[
1
]
=
W
[
2
]
T
d
L
d
z
[
2
]
\frac{d\mathcal{L}}{da^{[1]}}=W^{[2]T}\frac{d\mathcal{L}}{dz^{[2]}}
da[1]dL=W[2]Tdz[2]dL
因为
z
[
2
]
z^{[2]}
z[2]是
n
[
2
]
×
1
n^{[2]}\times 1
n[2]×1的,
W
[
2
]
W^{[2]}
W[2]是
n
[
2
]
×
n
[
1
]
n^{[2]}\times n^{[1]}
n[2]×n[1]的,所以这里需要转置一下。之后,再求对于
z
[
1
]
z^{[1]}
z[1]的导数,只需要在此基础之上乘上
d
a
[
1
]
d
z
[
1
]
\frac{d a^{[1]}}{dz^{[1]}}
dz[1]da[1](
∗
*
∗表示按位相乘):
d
L
d
z
[
1
]
=
W
[
2
]
T
d
L
d
z
[
2
]
∗
g
[
1
]
′
(
z
[
1
]
)
\frac{d\mathcal{L}}{dz^{[1]}}=W^{[2]T}\frac{d\mathcal{L}}{dz^{[2]}}*g^{[1]'}(z^{[1]})
dz[1]dL=W[2]Tdz[2]dL∗g[1]′(z[1])
而计算
d
L
d
W
[
1
]
\frac{d\mathcal{L}}{dW^{[1]}}
dW[1]dL和
d
L
d
b
[
1
]
\frac{d\mathcal{L}}{db^{[1]}}
db[1]dL的过程与第2层几乎一模一样:
d
L
d
W
[
1
]
=
d
L
d
z
[
1
]
a
[
0
]
T
d
L
d
b
[
1
]
=
d
L
d
z
[
1
]
\frac{d\mathcal{L}}{dW^{[1]}}=\frac{d\mathcal{L}}{dz^{[1]}}a^{[0]T}\\ \frac{d\mathcal{L}}{db^{[1]}}=\frac{d\mathcal{L}}{dz^{[1]}}
dW[1]dL=dz[1]dLa[0]Tdb[1]dL=dz[1]dL
以上推导都是针对单个样本的,要对多个样本进行向后传播,将样本列向量按列堆叠在一起,就可以套用上面推导的结果,其实就是所有
n
[
l
]
×
1
n^{[l]}\times1
n[l]×1的矩阵变成了
n
[
l
]
×
m
n^{[l]}\times m
n[l]×m,然后
b
b
b向量需要在水平方向求和一次(为了简化表达,我们用
d
Z
[
2
]
dZ^{[2]}
dZ[2]表示矩阵
Z
[
2
]
Z^{[2]}
Z[2]对代价函数求导的结果,其他矩阵同理):
d
Z
[
2
]
=
(
A
[
2
]
−
Y
)
d
W
[
2
]
=
1
m
d
Z
[
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
Z
[
1
]
=
W
[
2
]
T
d
Z
[
2
]
∗
g
[
1
]
′
(
Z
[
1
]
)
d
W
[
1
]
=
1
m
d
Z
[
1
]
X
T
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
)
\begin{aligned} &dZ^{[2]}=(A^{[2]}-Y)\\ &dW^{[2]}=\frac{1}{m}dZ^{[2]}A^{[1]T}\\ &db^{[2]}=\frac{1}{m}np.sum(dZ^{[2]},axis=1,keepdims=True)\\ &dZ^{[1]}=W^{[2]T}dZ^{[2]}*g^{[1]'}(Z^{[1]})\\ &dW^{[1]}=\frac{1}{m}dZ^{[1]}X^T\\ &db^{[1]}=\frac{1}{m}np.sum(dZ^{[1]},axis=1,keepdims=True)\\ \end{aligned}
dZ[2]=(A[2]−Y)dW[2]=m1dZ[2]A[1]Tdb[2]=m1np.sum(dZ[2],axis=1,keepdims=True)dZ[1]=W[2]TdZ[2]∗g[1]′(Z[1])dW[1]=m1dZ[1]XTdb[1]=m1np.sum(dZ[1],axis=1,keepdims=True)keepdims
的作用是让Python不要将我们的列向量(n,1)变成秩为1的矩阵(n,),那可能导致难以发现的bug。
深度神经网络
上面的浅层网络只有两层,而对于复杂的问题,增加网络的层数(深度)比强制在一个隐藏层中添加节点要有效得多,所以就需要将上面的推导化为更一般的形式,即下面这四个公式:
d
Z
[
l
]
=
d
A
[
l
]
∗
g
[
l
]
′
(
Z
[
l
]
)
d
W
[
l
]
=
1
m
d
Z
[
l
]
A
[
l
−
1
]
T
d
b
[
l
]
=
1
m
n
p
.
s
u
m
(
d
Z
[
l
]
,
a
x
i
s
=
1
,
k
e
e
p
d
i
m
s
=
T
r
u
e
)
d
A
[
l
−
1
]
=
W
[
l
]
T
d
Z
[
l
]
\begin{aligned} &dZ^{[l]}=dA^{[l]}*g^{[l]'}(Z^{[l]})\\ &dW^{[l]}=\frac{1}{m}dZ^{[l]}A^{[l-1]T}\\ &db^{[l]}=\frac{1}{m}np.sum(dZ^{[l]},axis=1,keepdims=True)\\ &dA^{[l-1]}=W^{[l]T}dZ^{[l]} \end{aligned}
dZ[l]=dA[l]∗g[l]′(Z[l])dW[l]=m1dZ[l]A[l−1]Tdb[l]=m1np.sum(dZ[l],axis=1,keepdims=True)dA[l−1]=W[l]TdZ[l]
初始的输入
d
A
[
L
]
dA^{[L]}
dA[L]由输出节点的激励函数决定,对于
m
m
m组样本,其值为
d
A
[
L
]
=
[
d
L
d
a
[
L
]
(
1
)
d
L
d
a
[
L
]
(
2
)
⋯
d
L
d
a
[
L
]
(
m
)
]
dA^{[L]}=\left[\begin{matrix} \frac{d\mathcal{L}}{da^{[L](1)}}&\frac{d\mathcal{L}}{da^{[L](2)}}&\cdots&\frac{d\mathcal{L}}{da^{[L](m)}} \end{matrix}\right]
dA[L]=[da[L](1)dLda[L](2)dL⋯da[L](m)dL]
由上面的公式,我们就可以做到输入 d A [ l ] dA^{[l]} dA[l],输出 d A [ l − 1 ] dA^{[l-1]} dA[l−1],同时计算每一层的权值和偏置的梯度。