前面总结了利用单神经元和Logistic回归解决简单的二分类问题,接下来就是建立一个简单的BP神经网络模型了,也就是简单的输入层-隐藏层-输出层网络模型,相比起之前的模型仅仅多了一个隐藏层。
先给出直观的网络示意图(注:图来源于吴恩达老师深度学习课程JupterNotebook):
模型解释
输入层
x
⃗
=
[
x
1
,
x
2
,
.
.
.
,
x
n
]
T
\vec{x}=[x_1, x_2,...,x_n]^T
x=[x1,x2,...,xn]T 对应样本的
n
n
n 个特征。输入层到隐层的权重矩阵用
W
[
1
]
\bold{W}^{[1]}
W[1] 表示,隐层的输入用
z
⃗
[
1
]
\vec{z}^{[1]}
z[1] 表示,隐层的输出用
a
⃗
[
1
]
\vec{a}^{[1]}
a[1] 表示。隐层到输出层的权重矩阵用
W
[
2
]
\bold{W}^{[2]}
W[2] 表示,输出层的输入用
z
[
2
]
z^{[2]}
z[2] 表示,网络的输出用
a
[
2
]
a^{[2]}
a[2] 表示。
从上图可以看出,对于一个二分类网络来说,输出层只需要一个神经元,用来输出一个位于
[
0
,
1
]
[0,1]
[0,1] 区间的概率值。这样对于一个输入样本
x
⃗
\vec{x}
x ,一个网络的正向传播机制可以用数学公式描述为
(1)
z
⃗
[
1
]
=
W
[
1
]
x
⃗
+
b
⃗
[
1
]
\vec{z}^{[1]}=\bold{W}^{[1]}\vec{x}+\vec{b}^{[1]}\tag{1}
z[1]=W[1]x+b[1](1)
(2)
a
⃗
[
1
]
=
t
a
n
h
(
z
⃗
[
1
]
)
\vec{a}^{[1]}=tanh(\vec{z}^{[1]})\tag{2}
a[1]=tanh(z[1])(2)
(3)
z
[
2
]
=
W
[
2
]
a
⃗
[
1
]
+
b
[
2
]
z^{[2]}=\bold{W}^{[2]}\vec{a}^{[1]}+b^{[2]}\tag{3}
z[2]=W[2]a[1]+b[2](3)
(4)
y
^
=
a
[
2
]
=
s
i
g
m
o
i
d
(
z
[
2
]
)
\hat{y}=a^{[2]}=sigmoid(z^{[2]})\tag{4}
y^=a[2]=sigmoid(z[2])(4)其中
W
[
1
]
⊆
R
n
h
×
n
x
\bold{W}^{[1]}\subseteq \bold{R}^{n_h\times{n_x}}
W[1]⊆Rnh×nx ,
W
[
2
]
⊆
R
n
y
×
n
h
\bold{W}^{[2]}\subseteq \bold{R}^{n_y\times{n_h}}
W[2]⊆Rny×nh,
n
x
、
n
h
、
n
y
n_x、n_h、n_y
nx、nh、ny 分别为输入层、隐层和输出层节点数量,
b
⃗
[
1
]
\vec{b}^{[1]}
b[1] 和
b
[
2
]
b^{[2]}
b[2] 分别表示隐层和输出层(更确切地应该说输出神经元)的阈值,从式子里可以看出隐层用了
t
a
n
h
tanh
tanh 作为激活函数,输出层毫无疑问还是 sigmoid 函数。
这样输入样本
x
⃗
\vec{x}
x 的预测值即为
(5)
y
p
r
e
d
i
c
t
i
o
n
=
{
1
,
a
[
2
]
>
0.5
0
,
otherwise
y_{prediction}= \begin{cases}1, & a^{[2]} > 0.5 \\ 0, & \text{otherwise } \end{cases}\tag{5}
yprediction={1,0,a[2]>0.5otherwise (5)
多个输入样本的向量化实现
上面用单个模型解释了一个单隐层神经网络的正向传播机制,实际中对于一个包含很多训练样本的数据集,如果通过遍历的方式一个一个样本输入神经网络进行运算是很耗时的,这时候可以尝试使用Python强大的 numpy 库进行向量化编程,numpy库中定义了各种矩阵和向量运算的函数,可以有效地提高效率。(实质上式 ( 1 ) (1) (1) 和 ( 3 ) (3) (3) 就是向量化的实现。)
对于一个包含
m
m
m 样本的数据集,我们可以将其表示为
(6)
X
=
[
x
⃗
(
1
)
,
x
⃗
(
2
)
,
.
.
.
,
x
⃗
(
m
)
]
\bold{X}=[\vec{x}^{(1)},\vec{x}^{(2)},...,\vec{x}^{(m)}] \tag{6}
X=[x(1),x(2),...,x(m)](6)其中
x
⃗
(
i
)
=
[
x
1
(
i
)
,
x
2
(
i
)
,
.
.
.
,
x
n
(
i
)
]
T
\vec{x}^{(i)}=[x^{(i)}_1, x^{(i)}_2,...,x^{(i)}_n]^T
x(i)=[x1(i),x2(i),...,xn(i)]T 为第
i
i
i 个样本的特征向量,将式
(
6
)
(6)
(6) 展开为矩阵形式即为
X
=
[
x
1
(
1
)
x
1
(
2
)
⋯
x
1
(
m
)
x
2
(
1
)
x
2
(
2
)
⋯
x
2
(
m
)
⋮
⋮
⋱
⋮
x
n
(
1
)
x
n
(
2
)
⋯
x
n
(
m
)
]
\bold{X}= \begin{bmatrix} x^{(1)}_1 & x^{(2)}_1 & \cdots & x^{(m)}_1 \\ x^{(1)}_2 & x^{(2)}_2 & \cdots & x^{(m)}_2 \\ \vdots & \vdots & \ddots & \vdots \\ x^{(1)}_n & x^{(2)}_n & \cdots & x^{(m)}_n \end{bmatrix}
X=⎣⎢⎢⎢⎢⎡x1(1)x2(1)⋮xn(1)x1(2)x2(2)⋮xn(2)⋯⋯⋱⋯x1(m)x2(m)⋮xn(m)⎦⎥⎥⎥⎥⎤显然
X
⊆
R
n
×
m
\bold{X} \subseteq \bold{R}^{n\times{m}}
X⊆Rn×m,
n
n
n为样本特征数量,同样也是网络输入层节点数量,因此
n
=
n
x
n=n_x
n=nx。
这样就可以通过向量化编程同时将所有样本输入到网络,此时式
(
1
)
(1)
(1)-
(
4
)
(4)
(4)就可以向量化表示为以下形式:
(7)
Z
[
1
]
=
W
[
1
]
X
+
b
[
1
]
\bold{Z}^{[1]}=\bold{W}^{[1]}\bold{X}+\bold{b}^{[1]}\tag{7}
Z[1]=W[1]X+b[1](7)
(8)
A
[
1
]
=
t
a
n
h
(
Z
[
1
]
)
\bold{A}^{[1]}=tanh(\bold{Z}^{[1]})\tag{8}
A[1]=tanh(Z[1])(8)
(9)
Z
[
2
]
=
W
[
2
]
A
[
1
]
+
b
[
2
]
\bold{Z}^{[2]}=\bold{W}^{[2]}\bold{A}^{[1]}+\bold{b}^{[2]}\tag{9}
Z[2]=W[2]A[1]+b[2](9)
(10)
Y
^
=
A
[
2
]
=
s
i
g
m
o
i
d
(
Z
[
2
]
)
\hat{\bold{Y}}=\bold{A}^{[2]}=sigmoid(\bold{Z}^{[2]})\tag{10}
Y^=A[2]=sigmoid(Z[2])(10)
对应输出了
m
m
m个样本的预测值
Y
^
=
[
y
^
(
1
)
,
y
^
(
2
)
,
.
.
.
,
y
^
(
m
)
]
\hat{\bold{Y}}=[\hat{y}^{(1)},\hat{y}^{(2)},...,\hat{y}^{(m)}]
Y^=[y^(1),y^(2),...,y^(m)]。
损失函数仍旧使用交叉熵函数,向量化表示为:
(11)
J
=
−
1
m
(
Y
log
(
Y
^
T
)
+
(
1
−
Y
)
log
(
1
−
Y
^
)
T
)
J=-\frac1m(\bold{Y}\log(\hat{\bold{Y}}^T)+(1-\bold{Y})\log(1-\hat{\bold{Y}})^T)\tag{11}
J=−m1(Ylog(Y^T)+(1−Y)log(1−Y^)T)(11)
反向传播的过程主要就是逐层对误差函数求偏导,过程和单神经元Logistic分类器类似,首先对输出层求导,接下来对隐层求导。每一步的求导结果如下:
(12)
d
Z
[
2
]
=
A
[
2
]
−
Y
d\bold{Z}^{[2]}=\bold{A}^{[2]}-\bold{Y}\tag{12}
dZ[2]=A[2]−Y(12)
(13)
d
W
[
2
]
=
1
m
d
Z
[
2
]
A
[
1
]
T
d\bold{W}^{[2]}=\frac1md\bold{Z}^{[2]}\bold{A}^{[1]T}\tag{13}
dW[2]=m1dZ[2]A[1]T(13)
(14) d b [ 2 ] = 1 m ∑ d Z [ 2 ] d\bold{b}^{[2]}=\frac{1}{m}\sum d\bold{Z}^{[2]} \tag{14} db[2]=m1∑dZ[2](14)
(15) d Z [ 1 ] = W [ 2 ] T d Z [ 2 ] ∗ g ′ ( Z [ 1 ] ) d\bold{Z}^{[1]}=\bold{W}^{[2]T}d\bold{Z}^{[2]}*g'(\bold{Z}^{[1]})\tag{15} dZ[1]=W[2]TdZ[2]∗g′(Z[1])(15) (16) d W [ 1 ] = 1 m d Z [ 1 ] X T d\bold{W}^{[1]}=\frac1md\bold{Z}^{[1]}\bold{X}^T\tag{16} dW[1]=m1dZ[1]XT(16)
(17)
d
b
[
1
]
=
1
m
∑
d
Z
[
1
]
d\bold{b}^{[1]}=\frac{1}{m}\sum d\bold{Z}^{[1]} \tag{17}
db[1]=m1∑dZ[1](17)
式
(
15
)
(15)
(15) 中
g
′
(
⋅
)
g'(·)
g′(⋅) 表示隐层激活函数(即
t
a
n
h
tanh
tanh )的导函数,在激活函数总结中已经提到
t
a
n
h
tanh
tanh 函数的导函数性质:
(18)
g
′
(
z
)
=
1
−
g
2
(
z
)
g'(z)=1-g^2(z)\tag{18}
g′(z)=1−g2(z)(18)这样就可以很容易得到
(19)
d
Z
[
1
]
=
W
[
2
]
T
d
Z
[
2
]
∗
g
′
(
Z
[
1
]
)
=
W
[
2
]
T
d
Z
[
2
]
∗
(
1
−
(
A
[
1
]
)
2
)
d\bold{Z}^{[1]}=\bold{W}^{[2]T}d\bold{Z}^{[2]}*g'(\bold{Z}^{[1]})=\bold{W}^{[2]T}d\bold{Z}^{[2]}*(1-(\bold{A}^{[1]})^2)\tag{19}
dZ[1]=W[2]TdZ[2]∗g′(Z[1])=W[2]TdZ[2]∗(1−(A[1])2)(19)
到这里基本已经完成了大部分的计算任务,最后就是利用计算得到的梯度信息更新网络的参数,即两个权重矩阵和两个阈值向量:
(20) d W [ 1 ] = W [ 1 ] − α d W [ 1 ] d\bold{W}^{[1]}=\bold{W}^{[1]}-\alpha d\bold{W}^{[1]}\tag{20} dW[1]=W[1]−αdW[1](20)
(21) d b [ 1 ] = b [ 1 ] − α d b [ 1 ] d\bold{b}^{[1]}=\bold{b}^{[1]}-\alpha d\bold{b}^{[1]}\tag{21} db[1]=b[1]−αdb[1](21)
(22) d W [ 2 ] = W [ 2 ] − α d W [ 2 ] d\bold{W}^{[2]}=\bold{W}^{[2]}-\alpha d\bold{W}^{[2]}\tag{22} dW[2]=W[2]−αdW[2](22)
(23)
d
b
[
2
]
=
b
[
2
]
−
α
d
b
[
2
]
d\bold{b}^{[2]}=\bold{b}^{[2]}-\alpha d\bold{b}^{[2]}\tag{23}
db[2]=b[2]−αdb[2](23)
这样在迭代次数内不断重复上面的更新过程从而不断降低损失函数的值,最终就可以得到一个较好的二分类神经网络模型。
总结
包含单隐层的神经网络只是一个很简单很基础的模型,实现过程中发现当隐层节点数为5的时候,最后的在测试集上的准确率甚至不如单神经元Logistic分类器;增加网络节点数会提高性能,但是计算量也明显上升。
Python实现的代码可以在我的GitHub上下载。