浅层神经网络
本篇从一个具有一个隐藏层的神经网络开始,初步了解神经网络的基本概念与工作机制,如图1所示。
下面,我们将对神经网络的各个部分做以说明。
输入层:对于每一个输入样本,它的维度决定了输入层有几个神经元,使用下标
i
i
i表示维度值,如
x
1
,
x
2
,
x
3
,
…
x_1,x_2,x_3,\dots
x1,x2,x3,…。同时,由于具有多个样本,我们使用上标
(
i
)
(i)
(i)表示它是第
i
i
i个样本。所有的
m
m
m个特征样本可以记作一个矩阵
X
X
X,
X
=
{
x
(
1
)
,
x
(
2
)
,
…
,
x
(
m
)
}
X=\{x^{(1)},x^{(2)},\dots,x^{(m)}\}
X={x(1),x(2),…,x(m)}。
隐藏层:隐藏层包含着神经网络所需要训练的参数
W
W
W和
b
b
b,以及主要的神经网络的结构。每一层神经元的个数以及隐藏层的层数要根据具体的问题进行设计。使用上标
[
i
]
[i]
[i]表示这是第
i
i
i个隐藏层,下标
j
j
j表示是该层的第
j
j
j个神经元。
输出层:对于分类问题来说,输出集
y
y
y是一个由有限的自然数组成的集合,
y
=
{
0
,
1
,
2
,
…
,
k
−
1
}
y=\{0,1,2,\dots,k-1\}
y={0,1,2,…,k−1}。例如对于垃圾邮件识别,就是一个典型的二元分类问题,可以用0表示垃圾邮件,1表示非垃圾邮件。
神经网络分为两个过程:前向传播和后向传播。我们以分类问题为例,初步描述神经网络的工作机制。
前向传播过程
对于一个已经训练好参数的神经网络,我们需要根据我们已知的特征集,来对它的类别进行预测。对于每一个单独的神经元与每个样本,它的前向传播过程与逻辑回归算法类似:
z [ 1 ] = W [ 1 ] x + b [ 1 ] z^{[1]}=W^{[1]}x+b^{[1]} z[1]=W[1]x+b[1]
a [ 1 ] = σ ( z [ 1 ] ) a^{[1]}=\sigma{(z^{[1]})} a[1]=σ(z[1])
z [ 2 ] = W [ 2 ] a [ 1 ] + b [ 2 ] z^{[2]}=W^{[2]}a^{[1]}+b^{[2]} z[2]=W[2]a[1]+b[2]
y ^ = a [ 2 ] = σ ( z [ 2 ] ) \hat{y}=a^{[2]}=\sigma(z^{[2]}) y^=a[2]=σ(z[2])
以上过程是在一个训练样本上进行的一次训练。为了尽量加快运算的速度,Python采用了SIMD(单指令多数据)技术,利用向量化方法,将所有的数据一次并行处理。
Z [ 1 ] = W [ 1 ] X + b [ 1 ] Z^{[1]}=W^{[1]}X+b^{[1]} Z[1]=W[1]X+b[1]
A [ 1 ] = σ ( Z [ 1 ] ) A^{[1]}=\sigma(Z^{[1]}) A[1]=σ(Z[1])
Z [ 2 ] = W [ 2 ] A [ 1 ] + b [ 2 ] Z^{[2]}=W^{[2]}A^{[1]}+b^{[2]} Z[2]=W[2]A[1]+b[2]
A [ 2 ] = σ ( Z [ 2 ] ) = Y ^ A^{[2]}=\sigma(Z^{[2]})=\hat{Y} A[2]=σ(Z[2])=Y^
注意,这里的偏置 b [ 1 ] b^{[1]} b[1]和 b [ 2 ] b^{[2]} b[2]与单样本不同,是一个向量。
后向传播过程
对于初始建立的神经网络,我们使用后向传播过程进行参数的调优。后向传播基于梯度下降法,计算代价函数关于参数的(偏)导数。
d Z [ 2 ] = A [ 2 ] − Y dZ^{[2]}=A^{[2]}-Y dZ[2]=A[2]−Y
d W [ 2 ] = 1 m d Z [ 2 ] ( A [ 1 ] ) T dW^{[2]}=\frac{1}{m}dZ^{[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 = T r u e ) db^{[2]}=\frac{1}{m}np.sum(dZ^{[2]},axis=1,keepdim=True) db[2]=m1np.sum(dZ[2],axis=1,keepdim=True)
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]=(W[2])TdZ[2]∗g′[1](Z[1])
d W [ 1 ] = 1 m d Z [ 1 ] X T dW^{[1]}=\frac{1}{m}dZ^{[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 = T r u e ) db^{[1]}=\frac{1}{m}np.sum(dZ^{[1]},axis=1,keepdim=True) db[1]=m1np.sum(dZ[1],axis=1,keepdim=True)
W [ 1 ] = W [ 1 ] − α 1 × d W [ 1 ] W^{[1]}=W^{[1]}-\alpha_1\times dW^{[1]} W[1]=W[1]−α1×dW[1]
b [ 1 ] = b [ 1 ] − α 1 × d b [ 1 ] b^{[1]}=b^{[1]}-\alpha_1\times db^{[1]} b[1]=b[1]−α1×db[1]
W [ 2 ] = W [ 2 ] − α 2 × d W [ 2 ] W^{[2]}=W^{[2]}-\alpha_2\times dW^{[2]} W[2]=W[2]−α2×dW[2]
b [ 2 ] = b [ 2 ] − α 2 × d b [ 2 ] b^{[2]}=b^{[2]}-\alpha_2\times db^{[2]} b[2]=b[2]−α2×db[2]
常用激活函数及其选择
- sigmoid函数
a = σ ( z ) = 1 1 + e − z , z ∈ R a = \sigma(z)=\frac{1}{1+e^{-z}},z\in\mathbb{R} a=σ(z)=1+e−z1,z∈R,值域是 ( 0 , 1 ) (0,1) (0,1)。它与它的导数对应的图像如图2:
其中,蓝色曲线为sigmoid函数,橙色曲线为它的导数。该激活函数主要在早期用于神经网络,现在一般仅用于二分问题的最后一层。它的缺点在于当 z z z的值过大或过小时,导数趋于0,使得梯度下降的速度太慢。 - tanh函数
a = tanh ( z ) = e z − e − z e z + e − z , z ∈ R a=\tanh(z)=\frac{e^z-e^{-z}}{e^z+e^{-z}},z\in\mathbb{R} a=tanh(z)=ez+e−zez−e−z,z∈R,值域是 ( − 1 , 1 ) (-1,1) (−1,1)。它的图像与sigmoid函数图像类似,但是它的值域中心点位于0处,这使得它的性能总是比sigmoid函数要好。但是,它的问题与sigmoid函数相同,在于当 z z z的值过大或过小时,导数趋于0,使得梯度下降的速度太慢,如图3所示。
- relu与带泄露的relu函数
a = r e l u ( z ) = max { 0 , z } a = relu(z)=\max\{0,z\} a=relu(z)=max{0,z},这是目前神经网络中最常使用的激活函数。它既引入了一定的非线性因素,也避免了 z z z过大或过小时出现的饱和现象,如图4所示。
relu函数还有一些变种。例如,考虑在 z < 0 z<0 z<0的区域里加入少许的"泄露",使得它不完全等于0。这时 a = r e l u ( z ) = max { α z , z } a = relu(z)=\max\{\alpha z,z\} a=relu(z)=max{αz,z},如图5所示。
随机初始化参数
在逻辑回归中,我们也许不需要对初始化值做出特定的约束。而在神经网络中,我们必须随即地初始化这些参数,特别是权重矩阵
W
W
W,否则,每一个神经元都将会计算出同样的结果。我们以一个简单的例子说明一下,神经网络如下图所示。
现在初始化
W
[
1
]
=
[
0
0
0
0
]
W^{[1]}=\left[\begin{matrix}0 & 0\\ 0 & 0\end{matrix}\right]
W[1]=[0000],
b
[
1
]
=
0
b^{[1]}=0
b[1]=0。那么:
z [ 1 ] = [ 0 0 ] z^{[1]}=\left[\begin{matrix}0\\0\end{matrix}\right] z[1]=[00] a [ 1 ] = σ ( a [ 1 ] ) = [ 1 2 1 2 ] a^{[1]}=\sigma(a^{[1]})=\left[\begin{matrix}\frac{1}{2}\\\frac{1}{2}\end{matrix}\right] a[1]=σ(a[1])=[2121]
同理,得到的其他对应参数也均相同,即所有的神经元都是对称的,没有起到神经网络的作用。因此,必须对神经网络参数进行随即=机初始化,例如在python中使用高斯随机数:
W [ 1 ] = n p . r a n d o m . r a n d n ( ( 2 , 2 ) ) ∗ 0.01 W^{[1]}=np.random.randn((2,2))*0.01 W[1]=np.random.randn((2,2))∗0.01
b [ 1 ] = n p . z e r o s ( ( 2 , 1 ) ) b^{[1]}=np.zeros((2,1)) b[1]=np.zeros((2,1))
由于b不会影响对称性,因此可以将b初始化为0。同时,我们希望参数值较小,所以在生成的随机数上缩放一个因子0.01(当然,其他值也可以)。
这样我们就完成了一个双层神经网络的建立与训练。