1 神经网络的表示
在计算神经网络的层数时,一般不把输入层计算在内。如一个由输入层、单隐藏层、输出层组成的神经网络,一般叫做是两层的神经网络。
上述单隐藏神经元的神经网络,神经元要做两件事情,一是计算
z
=
w
T
x
+
b
z = w^Tx+b
z=wTx+b,即计算输入和权重的内积;二是应用非线性激活函数,计算
a
=
σ
(
z
)
a = \sigma(z)
a=σ(z),增强神经网络的表达能力。
对第一个隐藏层神经元,用矩阵进行计算表示:
z
[
1
]
z^{[1]}
z[1]=
[
−
−
w
1
[
1
]
T
−
−
−
−
w
2
[
1
]
T
−
−
−
−
w
3
[
1
]
T
−
−
−
−
w
4
[
1
]
T
−
−
]
\left[ \begin{matrix}-- & {w^{[1]}_1}^T & -- \\ -- & {w^{[1]}_2}^T & --\\ -- & {w^{[1]}_3}^T & --\\ -- & {w^{[1]}_4}^T & --\end{matrix} \right]
⎣⎢⎢⎢⎢⎢⎡−−−−−−−−w1[1]Tw2[1]Tw3[1]Tw4[1]T−−−−−−−−⎦⎥⎥⎥⎥⎥⎤
[
x
1
x
2
x
3
]
\left[ \begin{matrix}x_1 \\ x_2 \\x_3\end{matrix} \right]
⎣⎡x1x2x3⎦⎤ +
[
b
1
b
2
b
3
b
4
]
\left[ \begin{matrix}b_1 \\ b_2 \\b_3 \\b_4\end{matrix} \right]
⎣⎢⎢⎡b1b2b3b4⎦⎥⎥⎤ =
[
w
1
[
1
]
T
x
+
b
1
w
2
[
1
]
T
x
+
b
2
w
3
[
1
]
T
x
+
b
3
w
4
[
1
]
T
x
+
b
4
]
\left[ \begin{matrix} {w^{[1]}_1}^Tx + b_1\\ {w^{[1]}_2}^Tx + b_2\\ {w^{[1]}_3}^Tx + b_3\\ {w^{[1]}_4}^Tx + b_4\end{matrix} \right]
⎣⎢⎢⎢⎢⎢⎡w1[1]Tx+b1w2[1]Tx+b2w3[1]Tx+b3w4[1]Tx+b4⎦⎥⎥⎥⎥⎥⎤ =
[
z
1
[
1
]
T
z
2
[
1
]
T
z
3
[
1
]
T
z
4
[
1
]
T
]
\left[ \begin{matrix} {z^{[1]}_1}^T\\ {z^{[1]}_2}^T \\ {z^{[1]}_3}^T \\ {z^{[1]}_4}^T \end{matrix} \right]
⎣⎢⎢⎢⎢⎢⎡z1[1]Tz2[1]Tz3[1]Tz4[1]T⎦⎥⎥⎥⎥⎥⎤
w
[
1
]
,
(
4
,
3
)
的
矩
阵
(
4
,
1
)
的
向
量
\\ ~\\ \\~~~~~~~~~~w^{[1]},(4,3)的矩阵 ~~~~~~~~~~~~(4,1)的向量
w[1],(4,3)的矩阵 (4,1)的向量
a [ 1 ] = σ ( z [ 1 ] ) \\a^{[1]} = \sigma(z^{[1]}) a[1]=σ(z[1])
上述神经网络的计算过程用矩阵表示为:
多个样本的向量化计算:
m个样本,显式的for循环进行前向计算过程
使用向量化消除for循环,即将输入样本组成一个
(
n
x
,
m
)
(n_x,m)
(nx,m)的矩阵X,
n
x
n_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
]
)
z^{[1]} = w^{[1]}X + b^{[1]} \\a^{[1]} = \sigma(z^{[1]}) \\z^{[2]} = w^{[2]}z^{[1]} + b^{[2]} \\a^{[2]} = \sigma(z^{[2]})
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,z^{[1]},a^{[1]},z^{[2]},a^{[2]}
X,z[1],a[1],z[2],a[2]中,不同的列表示不同的样本;同一列的不同行对应不同的隐藏神经元。
把X表示成
A
[
0
]
A^{[0]}
A[0],可以看到神经网络就是重复进行下述两个计算过程:
Z
[
i
]
=
W
[
i
]
A
[
i
−
1
]
+
b
[
i
]
Z^{[i]} = W^{[i]}A^{[i-1]}+b^{[i]}
Z[i]=W[i]A[i−1]+b[i]
A
[
i
]
=
σ
(
Z
[
i
]
)
A^{[i]} = \sigma(Z^{[i]})
A[i]=σ(Z[i])
2 激活函数
s
i
g
m
o
i
d
(
x
)
=
1
1
+
e
(
−
x
)
sigmoid(x) = \frac{1}{1+e^{(-x)}}
sigmoid(x)=1+e(−x)1,输出值范围(0,1);
~
t
a
n
h
(
x
)
=
e
z
−
e
−
z
e
z
+
e
−
z
tanh(x) = \frac{e^z - e^{-z}}{e^z + e^{-z}}
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 − ( t a n h ( x ) ) 2 1 - (tanh(x))^2 1−(tanh(x))2,那么当输入值很大或很小时,sigmoid函数和tanh函数的导数都接近于0,如果神经网络的隐藏层使用了这样的激活函数,那么在梯度反向传播时, ∂ x ∂ z [ i ] = ∂ x ∂ a [ i ] ∗ \frac{\partial x}{\partial z^{[i]}} = \frac{\partial x}{\partial a^{[i]}} * ∂z[i]∂x=∂a[i]∂x∗激活函数对 z [ i ] z^{[i]} z[i]的导数,如果此时 z [ i ] z^{[i]} z[i]的值很大或很小,那么激活函数对 z [ i ] z^{[i]} z[i]的导数就接近于0,所以 ∂ x ∂ z [ i ] \frac{\partial x}{\partial z^{[i]}} ∂z[i]∂x也接近于0,因此就阻碍了梯度往底层的传播,前面的层也就几乎停止了训练过程,这种现象称之为“梯度弥散”。这就是为什么目前主流神经网络都不再使用sigmoid和tanh作为激活函数的原因。
而sigmoid函数最“陡峭”的地方,即x=0处,梯度也只有0.25,也会减小反向传播的梯度值。而tanh在x=0处,梯度为1,对梯度的反向传播不会造成影响。
~
r
e
l
u
(
x
)
=
m
a
x
(
0
,
x
)
=
{
x
,
x
≥
0
0
,
x
<
0
relu(x) = max(0,x) = \begin{cases} x,x\geq 0 \\ 0,x < 0 \end{cases}
relu(x)=max(0,x)={x,x≥00,x<0
relu目前是神经网络默认的隐藏层激活函数。
两个优点:
- 输入值大于0时,激活函数的输出对输入的导数为1,不会造成梯度弥散,影响梯度的反向传播。实践证明,使用relu做激活函数,神经网络收敛速度比较快;
- relu有一半区域为0,这样也增加了数据的稀疏性,减少了计算量。
一个缺点:
如果输入值为负值,则输出为0,这样对负的输入值无法进行梯度反向传播。
一个要解释的点:
严格来说Relu函数并非全部可导,
x
=
0
x = 0
x=0处左右导数不相等。但是因为我们输入值无限接近于0的可能性很小,因此这个问题一般都忽略不计。
~
l
e
a
k
y
r
e
l
u
(
x
)
=
{
x
,
x
≥
0
0.01
x
,
x
<
0
leaky~relu(x) = \begin{cases} x , x \geq 0 \\ 0.01x,x < 0 \end{cases}
leaky relu(x)={x,x≥00.01x,x<0
leaky relu为解决relu的缺点而提出的。输入为负时,也可以进行梯度的前传,只是相对正值输入的梯度要小一些。
~
p
r
e
l
u
(
x
)
=
{
x
,
x
≥
0
α
x
,
x
<
0
prelu(x) = \begin{cases} x , x \geq 0 \\ \alpha x,x < 0 \end{cases}
prelu(x)={x,x≥0αx,x<0
α
\alpha
α可以作为一个参数进行学习。prelu是leaky relu的泛化版本,增加了一个参数,但相比leaky relu提升有限。
为什么要使用非线性激活函数?
如果使用线性函数作为激活函数,因为线性函数的乘积任然是线性函数,那么无论神经网络有多深,输出都是输入的线性变换,这样和单层的线性激活函数是没有区别的,这样是没有任何意义的。只有使用非线性激活函数,才可以使神经网络学到有趣的变换,也才能完成一些有趣的应用。
唯一有可能需要线性激活函数的地方是,对于回归问题,输出是倒数第二层的输出的线性变换,此时用线性激活函数是合理的。
3 神经网络的梯度下降
二分类任务,假设输入样本为
n
x
n_x
nx个单元,隐藏层有
n
1
n_1
n1个单元,输出层有
n
2
n_2
n2个单元。因此对单个样本而言,
x
∈
R
n
x
×
1
,
W
[
1
]
∈
R
n
1
×
n
x
,
b
[
1
]
∈
R
n
1
×
1
,
z
[
1
]
∈
R
n
1
×
1
,
a
[
1
]
∈
R
n
1
×
1
,
W
[
2
]
∈
R
n
2
×
n
1
,
b
[
2
]
∈
R
n
2
×
1
,
z
[
2
]
∈
R
n
2
×
1
,
a
[
2
]
∈
R
n
2
×
1
,
y
∈
R
n
2
×
1
x \in R^{n_x \times1},W^{[1]} \in R^{n_1 \times n_x},b^{[1]} \in R^{n_1 \times 1},z^{[1]} \in R^{n_1 \times 1},a^{[1]} \in R^{n_1 \times 1},W^{[2]} \in R^{n_2 \times n_1},b^{[2]} \in R^{n_2 \times 1},z^{[2]} \in R^{n_2 \times 1},a^{[2]} \in R^{n_2 \times 1},y \in R^{n_2 \times 1}
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。
对于单个样本而言,其反向传播的梯度为:
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
dW^{[2]} = dz^{[2]}{a^{[1]}}^T
dW[2]=dz[2]a[1]T(之所以转置是因为
d
W
[
2
]
∈
R
n
2
×
n
1
dW^{[2]} \in R^{n_2 \times n_1}
dW[2]∈Rn2×n1,而这里
z
[
2
]
z^{[2]}
z[2]和
a
[
1
]
a^{[1]}
a[1]都是列向量,需要将
a
[
1
]
a^{[1]}
a[1]转置才可以计算外积得到矩阵)
d
b
[
2
]
=
d
z
[
2
]
db^{[2]} = dz^{[2]}
db[2]=dz[2]
d
a
[
1
]
=
W
[
2
]
T
d
z
[
2
]
da^{[1]} = {W^{[2]}}^Tdz^{[2]}
da[1]=W[2]Tdz[2]
d
z
[
1
]
=
d
a
[
1
]
∗
g
[
1
]
′
(
z
[
1
]
)
dz^{[1]} = da^{[1]} * {g^{[1]}}^{'}(z^{[1]})
dz[1]=da[1]∗g[1]′(z[1])(这里的“*”表示逐元素相乘)
d
W
[
1
]
=
d
z
[
1
]
x
T
dW^{[1]} = dz^{[1]}x^T
dW[1]=dz[1]xT
d
b
[
1
]
=
d
z
[
1
]
db^{[1]} = dz^{[1]}
db[1]=dz[1]
m个样本,向量化之后为:
X
∈
R
n
x
×
m
,
W
[
1
]
∈
R
n
1
×
n
x
,
b
[
1
]
∈
R
n
1
×
1
,
Z
[
1
]
∈
R
n
1
×
m
,
A
[
1
]
∈
R
n
1
×
m
,
W
[
2
]
∈
R
n
2
×
n
1
,
b
[
2
]
∈
R
n
2
×
1
,
Z
[
2
]
∈
R
n
2
×
m
,
A
[
2
]
∈
R
n
2
×
m
,
Y
∈
R
n
2
×
m
X \in R^{n_x \times m},W^{[1]} \in R^{n_1 \times n_x},b^{[1]} \in R^{n_1 \times 1},Z^{[1]} \in R^{n_1 \times m},A^{[1]} \in R^{n_1 \times m},W^{[2]} \in R^{n_2 \times n_1},b^{[2]} \in R^{n_2 \times 1},Z^{[2]} \in R^{n_2 \times m},A^{[2]} \in R^{n_2 \times m},Y \in R^{n_2 \times 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。
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(之所以有
1
m
\frac{1}{m}
m1是因为计算损失函数时包含了
1
m
\frac{1}{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
)
db^{[2]} = \frac{1}{m}np.sum(dZ^{[2]},axis=1,keepdims = True)
db[2]=m1np.sum(dZ[2],axis=1,keepdims=True)(使用keepdims = True是为了保障输出形状为(
n
2
n_2
n2,1),而不是(
n
2
n_2
n2,),更容易计算)
d
A
[
1
]
=
W
[
2
]
T
d
Z
[
2
]
dA^{[1]} = {W^{[2]}}^TdZ^{[2]}
dA[1]=W[2]TdZ[2]
d
Z
[
1
]
=
d
A
[
1
]
∗
g
[
1
]
′
(
Z
[
1
]
)
dZ^{[1]} = dA^{[1]} * {g^{[1]}}^{'}(Z^{[1]})
dZ[1]=dA[1]∗g[1]′(Z[1])(这里的“*”表示逐元素相乘)
$dW^{[1]} =
1
m
d
Z
[
1
]
X
T
\frac{1}{m}dZ^{[1]}X^T
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
)
db^{[1]} = \frac{1}{m}np.sum(dZ^{[1]},axis=1,keepdims = True)
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.