##回顾
上一节完成了BP神经网络的Python实现,并稍微改进了分类效果。下面是三层网络 [784,30,50,10],mini_batch_size = 10, eta = 3.0 的结果,
识别率基本保持在96.5%-97.0%,多次实验识别率也不能在提高了。下面继续进行学习,学习其他的优化方法。
##问题
我们通常是在犯比较严重的错误时学习的较快。但是人工神经元在其犯错较大的情况很难学习。为何学习如此缓慢?我们能够找到避免这种情况的方法吗?
首先回顾下BP算法中的四个基本公式
(BP1)
δ
L
=
∇
a
C
.
∗
σ
′
(
z
L
)
{\bf{\delta }}^L = \nabla_a C .* \sigma'({\bf{z}}^L)\tag{BP1}
δL=∇aC.∗σ′(zL)(BP1)
(BP2)
δ
l
=
(
(
w
l
+
1
)
T
δ
l
+
1
)
.
∗
σ
′
(
z
l
)
\delta^l = (({\bf{w}}^{l+1})^T \delta^{l+1}) .* \sigma'({\bf{z}}^l)\tag{BP2}
δl=((wl+1)Tδl+1).∗σ′(zl)(BP2)
(BP3)
∂
C
∂
b
j
l
=
δ
j
l
\frac{\partial C}{\partial b^l_j} = \delta^l_j\tag{BP3}
∂bjl∂C=δjl(BP3)
(BP4)
∂
C
∂
w
j
k
l
=
a
k
l
−
1
δ
j
l
\frac{\partial C}{\partial w^l_{jk}} = a^{l-1}_k \delta^l_j\tag{BP4}
∂wjkl∂C=akl−1δjl(BP4)
上面公式显示,代价函数的偏导数(
∂
C
/
∂
w
\partial C/\partial w
∂C/∂w 和
∂
C
/
∂
b
\partial C/\partial b
∂C/∂b)决定了速度学习,额
∂
C
/
∂
w
\partial C/\partial w
∂C/∂w 和
∂
C
/
∂
b
\partial C/\partial b
∂C/∂b 的计算依赖于激活函数的导数。如果我们利用sigmoid激活函数(如下图),当神经元的输出接近
1
1
1 的时候,曲线变得相当平,所以
σ
′
(
z
)
\sigma'(z)
σ′(z) 就很小了。这其实就是学习缓慢的原因所在。
针对这个问题,使用其他性质更好的激活函数(比如ReLU激活函数)是一种解决方法。当然,我们也可以构造出一种新的代价函数,这个代价函数可以消除掉激活函数导数的影响。
##交叉熵代价函数
下面直接给出交叉熵代价函数的定义
(1)
C
=
−
1
n
∑
x
[
y
ln
a
+
(
1
−
y
)
ln
(
1
−
a
)
]
C = -\frac{1}{n} \sum_x \left[y \ln a + (1-y ) \ln (1-a) \right]\tag{1}
C=−n1x∑[ylna+(1−y)ln(1−a)](1)其中
n
n
n 是训练数据的总数,求和是在所有的训练输入
x
x
x 上进行的,
y
y
y 是对应的目标输出。由上面的交叉熵函数的定义,我们看看
δ
\delta
δ 的形式
(2)
δ
L
=
∇
a
C
⊙
σ
′
(
z
L
)
=
1
n
∑
x
σ
′
(
z
)
σ
(
z
)
(
1
−
σ
(
z
)
)
(
σ
(
z
)
−
y
)
{\bf{\delta }}^L = \nabla_a C \odot \sigma'({\bf{z}}^L) = \frac{1}{n} \sum_x \frac{\sigma'(z)}{\sigma(z) (1-\sigma(z))} (\sigma(z)-y)\tag{2}
δL=∇aC⊙σ′(zL)=n1x∑σ(z)(1−σ(z))σ′(z)(σ(z)−y)(2)根据
σ
(
z
)
=
1
/
(
1
+
e
−
z
)
\sigma(z) = 1/(1+e^{-z})
σ(z)=1/(1+e−z) 的定义,和一些运算,我们可以得到
σ
′
(
z
)
=
σ
(
z
)
(
1
−
σ
(
z
)
)
\sigma'(z) = \sigma(z)(1-\sigma(z))
σ′(z)=σ(z)(1−σ(z))。我们看到
σ
′
(
z
)
\sigma'(z)
σ′(z) 和
σ
(
z
)
(
1
−
σ
(
z
)
)
\sigma(z)(1-\sigma(z))
σ(z)(1−σ(z)) 这两项在方程中直接约去了,所以最终形式就是:
(3)
δ
L
=
1
n
∑
x
(
σ
(
z
)
−
y
)
{\bf{\delta }}^L = \frac{1}{n} \sum_x (\sigma(z)-y)\tag{3}
δL=n1x∑(σ(z)−y)(3)从公式(BP2)看,即使最后一层的
σ
′
(
z
L
)
\sigma'(z^L)
σ′(zL) 消除了,但是从最后一层向前一层传递误差时,我们仍旧需要计算
σ
′
(
z
j
L
)
\sigma'(z^L_j)
σ′(zjL) 的。这一点没有理解透彻。
我们应该在什么时候⽤交叉熵来替换⼆次代价函数?实际上,如果在输出神经元是 sigmoid神经元时,交叉熵⼀般都是更好的选择。为什么?考虑⼀下我们初始化⽹络的权重和偏置时通常使⽤某种随机⽅法。可能会发⽣这样的情况,这些初始选择会对某些训练输⼊误差相当明显 —— ⽐如说,⽬标输出是 1,⽽实际值是 0,或者完全反过来。如果我们使⽤⼆次代价函数,那么这就会导致学习速度的下降。它并不会完全终⽌学习的过程,因为这些权重会持续从其他的样本中进⾏学习,但是显然这不是我们想要的效果。
##测试
由于单独修改一个因素,很难提升神经网络的总体性能,我们做的测试仅仅是感性地认识下这个优化方法的性能。程序修改十分简单,只要讲gnetwork.py
中update_mini_batch()
函数中的 *delta = (a[-1]-y_train)*sigmoid_prime(z[-1])修改为delta = (a[-1]-y_train)*即可。下面是运行结果
网络结构 [784,30,10],mini_batch_size = 50, learning_rate = 1
相比于上一节中使用sigmoid激活函数的结果相比,确实提高了识别率。下面是网络结构为[784,30,50,10],mini_batch_size = 100, learning_rate = 0.5 的结果
遗憾的是,就自己编写的这个浅层BP神经网络,将ReLU激活函数和交叉熵代价函数结合起来,识别率反而没有单一方法识别率高。
##softmax和交叉熵
倘若,我们采用如下的sofmax分类器
(4)
a
i
=
e
z
i
∑
k
e
z
k
a_i = \frac{e^{z_i}}{\sum_k e^{z_k}}\tag{4}
ai=∑kezkezi(4)softmax 函数能够把一组向量转换成相对应的概率向量。如果我们使用均方误差代价函数或者上面的交叉熵函数(2),我们均需要计算 softmax 函数的导数。一般与 softmax 函数配合使用的交叉熵代价为
(5)
C
=
−
∑
i
y
i
ln
(
a
i
L
)
C = - {\sum}_i y_i\text{ln}(a_i^L)\tag{5}
C=−∑iyiln(aiL)(5)在多分类问题中,标签
y
y
y 一般采用 one-hot 编码,即只有对应类别的位置为 1,其他位置为 0,那么交叉熵代价函数简化为:
(6)
C
=
−
ln
(
a
i
L
)
C = - \text{ln}(a_i^L)\tag{6}
C=−ln(aiL)(6)下面我们看看
δ
L
\delta^L
δL 的形式
(7)
δ
i
L
=
−
∇
a
i
L
C
⋅
σ
′
(
z
i
L
)
=
−
y
i
a
i
L
(
e
z
i
(
∑
k
e
z
k
)
2
−
e
z
i
∑
k
e
z
k
)
=
y
i
(
a
i
L
−
1
)
\delta^L_i = -\nabla_{a^L _i}C\cdot \sigma'({\bf{z}}_i^L) \\=- \frac{y_i}{a_i^L}\left( \frac{e^{z_i}}{\left( {\sum_k e^{z_k}} \right)^2}-\frac{e^{z_i}}{\sum_k e^{z_k}} \right) \\ = y_i \left(a_i^L-1\right)\tag{7}
δiL=−∇aiLC⋅σ′(ziL)=−aiLyi((∑kezk)2ezi−∑kezkezi)=yi(aiL−1)(7)Michael Nielsen 给出的形式是
(8)
δ
i
L
=
a
i
L
−
y
i
\delta^L_i = a_i^L-y_i\tag{8}
δiL=aiL−yi(8)与上面我自己推导的公式稍有不同。假如对一个样本
y
i
=
0
y_i = 0
yi=0,按照公式(7),则
δ
i
L
=
0
\delta^L_i = 0
δiL=0,而公式(8)得到是
δ
i
L
=
a
i
L
\delta^L_i = a_i^L
δiL=aiL,显然公式(8)训练的速度更快(因为它的每个输出神经元都参与了误差传递,而公式(7)仅仅只有 1 个输出神经元都参与了误差传递)。但是公式(7)应该没有推导错误,不知道哪个地方出了理解偏差。
(2018-3-22补充)公式(7)的推导确实是错误的。公式(5)可以分为 i = j i=j i=j 和 i ≠ j i\neq j i̸=j部分,即 (9) C = − ∑ j ≠ i y j ln ( a j L ) + y i ln ( a i L ) C = - {\sum_ {j \neq i}} y_j\text{ln}(a_j^L)+y_i\text{ln}(a_i^L)\tag{9} C=−j̸=i∑yjln(ajL)+yiln(aiL)(9)那么 δ i L = ∑ j ∂ C ∂ a j L ⋅ ∂ a j L ∂ z j L = ∑ j ≠ i ∂ C ∂ a j L ⋅ ∂ a j L ∂ z j L + ∂ C ∂ a i L ⋅ ∂ a i L ∂ z i L = ∑ j ≠ i − y j a j L ⋅ − e z j ⋅ e z i ( ∑ k e z k ) 2 + ( − y i a i L e z i ∑ k e z k − ( e z i ) 2 ( ∑ k e z k ) 2 ) = ∑ j ≠ i y j a j L ⋅ a j L ⋅ a i L + ( − y i a i L ) a i L ( 1 − a i L ) = ∑ j ≠ i y j ⋅ a i L + y i a i L − y i = a i L − y i \begin{array}{l} \delta _i^L = \sum\limits_j {\frac{{\partial C}}{{\partial a_j^L}}} \cdot \frac{{\partial a_j^L}}{{\partial z_j^L}}\\ {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} = \sum\limits_{j \ne i} {\frac{{\partial C}}{{\partial a_j^L}}} \cdot \frac{{\partial a_j^L}}{{\partial z_j^L}} + \frac{{\partial C}}{{\partial a_i^L}} \cdot \frac{{\partial a_i^L}}{{\partial z_i^L}}\\ {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} = \sum\limits_{j \ne i} { - \frac{{{y_j}}}{{a_j^L}}} \cdot \frac{{ - {e^{{z_j}}} \cdot {e^{{z_i}}}}}{{{{\left( {\sum\nolimits_k {{e^{{z_k}}}} } \right)}^2}}} + \left( { - \frac{{{y_i}}}{{a_i^L}}\frac{{{e^{{z_i}}}\sum\nolimits_k {{e^{{z_k}}}} - {{\left( {{e^{{z_i}}}} \right)}^2}}}{{{{\left( {\sum\nolimits_k {{e^{{z_k}}}} } \right)}^2}}}} \right)\\ {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} = \sum\limits_{j \ne i} {\frac{{{y_j}}}{{a_j^L}}} \cdot a_j^L \cdot a_i^L + \left( { - \frac{{{y_i}}}{{a_i^L}}} \right)a_i^L\left( {1 - a_i^L} \right)\\ {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} = \sum\limits_{j \ne i} {{y_j}} \cdot a_i^L + {y_i}a_i^L - {y_i}\\ {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} {\kern 1pt} = a_i^L - {y_i} \end{array} δiL=j∑∂ajL∂C⋅∂zjL∂ajL=j̸=i∑∂ajL∂C⋅∂zjL∂ajL+∂aiL∂C⋅∂ziL∂aiL=j̸=i∑−ajLyj⋅(∑kezk)2−ezj⋅ezi+(−aiLyi(∑kezk)2ezi∑kezk−(ezi)2)=j̸=i∑ajLyj⋅ajL⋅aiL+(−aiLyi)aiL(1−aiL)=j̸=i∑yj⋅aiL+yiaiL−yi=aiL−yi公式(7)实际上丢掉了 j ≠ i j \neq i j̸=i的那一部分了,所以是错误的。