《Neural Network and Deep Learning(神经网络与深度学习)》练习及问题详解

Auther:金双平

Lastest Update:2019年01月02日

仅供参考,谢绝转载,欢迎讨论。

原书PDF: 网盘链接
提取码: ihnr

1.2 练习

  • 第一部分

我们先从一个感知器入手,设一个感知器具有 n n 个输入 x i x_i ,每个输入的权重分别为 w i w_i ,偏置为 b b ,由此可以得到的计算结果为 i = 1 n x i w i + b \sum_{i=1}^{n} x_iw_i + b ,而在输出层所要做的是将该结果与0进行大小比较,若 i = 1 n x i w i + b 0 \sum_{i=1}^{n} x_iw_i + b\geqslant0 ,则输出 1 1 ,否则输出 0 0 。现在,根据题目的意思,将感知器的所有权重与偏置乘以一个正的常数 c c ,此时计算结果为 i = 1 n x i c w i + c b \sum_{i=1}^{n} x_icw_i + cb ,该计算结果同样是与 0 0 进行大小比较,根据基本数学不等式运算关系可知,由于 c > 0 c>0 ,因此,不等号方向不会发生变化,感知器的行为并没有改变。题目问的是一个感知器网络,可以想象,在一个网络中,由于每一个感知器的行为均未发生改变,因此整个网络的行为也没有发生改变。

  • 第二部分

同样地,我们先从一个传感器入手。对于一个感知器,不考虑 w x + b = 0 wx+b=0 的情况,若 w x + b > 0 wx+b>0 ,则输出为 1 1 ,否则输出 0 0 。现在对于S型神经元,其计算结果为 1 1 + e ( w x + b ) \frac{1}{1+e^{-(wx+b)}} ,由于题目要求将权重与偏置乘以一个正的常数 c c ,其中, c c \rightarrow \infty ,因此实际计算结果为 1 1 + e c ( w x + b ) \frac{1}{1+e^{-c(wx+b)}} 。我们来比较单个感知器与单个S型神经元之间在 w x + b wx+b 大于 0 0 以及小于 0 0 的情况下,输出的结果是否相同。当 w x + b > 0 wx+b>0 时,由于 c c \rightarrow \infty ,因此 1 1 + e c ( w x + b ) \frac{1}{1+e^{-c(wx+b)}} 的计算结果趋近于 1 1 ,当 w x + b < 0 wx+b<0 时,由于 c c \rightarrow \infty ,因此 1 1 + e c ( w x + b ) \frac{1}{1+e^{-c(wx+b)}} 的计算结果趋近于 0 0 。因此,在 w x + b wx+b 不等于 0 0 ,且 c c \rightarrow \infty 的情况下,可以保证S型神经元和感知器的行为完全一致。而由完全一致的器件构成的网络,其行为也必然一致。当 w x + b = 0 wx+b=0 时,则会导致S型神经元的输出结果为 0.5 0.5 ,而这是不符合感知器的输出的。


1.4 练习

在第三层得到正确输出的前提下,我们所要做的仅仅是将正确的输出结果映射到第四层的二进制输出层。这里我将二进制表示与十进制表示列写如下:

0000 0001 0010 0011 0100 0101 0110 0111 1000 1001
0 1 2 3 4 5 6 7 8 9

将二进制从低到高的位数依次称为第一位至第四位,由此我们可以观察得到,第一位对1,3,5,7,9敏感,第二位对2,3,6,7敏感,第三位对4,5,6,7敏感,第四位对8,9敏感。因此,我们可以将各位对应于其敏感的数字权重设为1,不敏感的数字权重设为0,偏置设为-0.5,使用step function,从而可以将原先的3层神经网络转化为二进制输出。参见下图:

在这里插入图片描述

1.4自主拓展

在观察《Neural Networks and Deep Learning》中的神经元多层连接图后,我可以确认,书中的图是由软件绘制的,且质量很高(矢量图),因此,在手绘题1.4的配图后,我萌生了使用Python来绘制神经元连接图的想法,由于比较长时间没有使用Python了,从回顾语法到接触matplotlib库以及networkx库,直至最终完成绘图,花了两三天时间,期间查阅并参考了大量资料,包括各类博客、库的reference books以及官网的众多examples。我不仅打算用这个小项目来完成神经元连接图的绘制,也是为了唤醒Python的记忆。最终,我分别用matplotlib库与networkx库完成了两种绘图方案。具体代码与效果图如下:

#! python2
# 方法1
import sys
import networkx as nx
import matplotlib.pyplot as plt
left, right, bottom, top, layer_sizes = .1, .9, .1, .9, [4, 7, 7, 2]
G = nx.DiGraph()
v_spacing = (top - bottom) / float(max(layer_sizes))
h_spacing = (right - left) / float(len(layer_sizes) - 1)
node_count = 0
for i, v in enumerate(layer_sizes):
    layer_top = v_spacing * (v - 1) / 2. + (top + bottom) / 2.
    for j in range(v):
        G.add_node(node_count, pos=(left + i * h_spacing, layer_top - j * v_spacing))
        node_count += 1
for x, (left_nodes, right_nodes) in enumerate(zip(layer_sizes[:-1], layer_sizes[1:])):
    for i in range(left_nodes):
        for j in range(right_nodes):
            G.add_edge(i + sum(layer_sizes[:x]), j + sum(layer_sizes[:x + 1]))
pos = nx.get_node_attributes(G, 'pos')
nx.draw(G, pos,
        node_color='w',
        with_labels=False,
        node_size=200,
        edge_color='k',
        width=1.0
        )
try:
    plt.show()
except:
    sys.exit()
#! python2
# 方法2
import sys
import matplotlib.pyplot as plt
from matplotlib.patches import Wedge, FancyArrowPatch
left, right, bottom, top, layer_sizes = .1, .9, .1, .9, [4, 7, 7, 2]
v_spacing = (top - bottom) / float(max(layer_sizes))
h_spacing = (right - left) / float(len(layer_sizes) - 1)
node_count = 0
fig = plt.figure(figsize = (8, 8))
ax = fig.add_subplot(111)
circlex = []
circley = []
for i, v in enumerate(layer_sizes):
    layer_top = v_spacing * (v - 1) / 2. + (top + bottom) / 2.
    for j in range(v):
        circlex.append(float(left + i * h_spacing))
        circley.append(float(layer_top - j * v_spacing))
        tempPatch = Wedge((circlex[node_count], circley[node_count]), .03, 0, 360, \
                          width = 0.001, ec = 'k', fc = 'k')
        ax.add_patch(tempPatch)
        node_count += 1
for x, (left_nodes, right_nodes) in enumerate(zip(layer_sizes[:-1], layer_sizes[1:])):
    for i in range(left_nodes):
        for j in range(right_nodes):
            tempPatch = FancyArrowPatch(posA = (circlex[i + sum(layer_sizes[:x])], \
                        circley[i + sum(layer_sizes[:x])]), posB = (circlex[j + \
                        sum(layer_sizes[:x + 1])], circley[j + sum(layer_sizes[:x \
                        + 1])]), arrowstyle = '->', shrinkA=17, shrinkB=17, \
                        mutation_scale=20, fc = 'w')
            ax.add_patch(tempPatch)
            ax.spines['top'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.get_yaxis().set_visible(False)
ax.get_xaxis().set_visible(False)
try:
    plt.show()
except:
    sys.exit()

在这里插入图片描述


1.5 练习

  • 第一部分

首先我们了解一下柯西-施瓦茨不等式: ( k = 1 n a k b k ) 2 ( k = 1 n a k 2 ) ( k = 1 n b k 2 ) \left(\sum_{k=1}^{n}a_kb_k\right)^2 \geqslant \left( \sum_{k=1}^{n}a_k^2\right) \left( \sum_{k=1}^{n}b_k^2\right)

因为 Δ C = C v 1 Δ v 1 + C v 2 Δ v 2 + + C v n Δ v n \Delta C = \frac{\partial C}{\partial v_1} \Delta v_1 + \frac{ \partial C }{ \partial v_2} \Delta v_2 + \dots + \frac{\partial C}{\partial v_n} \Delta v_n C = ( C v 1 , C v 2 , , C v n ) T \nabla C =\left( \frac{\partial C}{\partial v_1} , \frac{\partial C}{\partial v_2} , \dots , \frac{\partial C}{\partial v_n} \right) ^ T Δ v = ( Δ v 1 , Δ v 2 , , Δ v n ) T \Delta v =\left( \Delta v_1 , \Delta v_2 , \dots , \Delta v_n \right) ^ T

根据柯西-施瓦茨不等式可得:

( Δ C ) 2 = ( C v 1 Δ v 1 + C v 2 Δ v 2 + + C v n Δ v n ) 2 ( ( C v 1 ) 2 + ( C v 2 ) 2 + + ( C v n ) 2 ) ( ( Δ v 1 ) 2 + ( Δ v 2 ) 2 + + ( Δ v n ) 2 ) \left( \Delta C \right)^2 = \left( \frac{\partial C}{\partial v_1} \Delta v_1 + \frac{\partial C}{\partial v_2} \Delta v_2 + \dots + \frac{\partial C}{\partial v_n} \Delta v_n \right)^2 \leqslant \left( (\frac{\partial C}{\partial v_1})^2 + (\frac{\partial C}{\partial v_2})^2 + \dots + (\frac{\partial C}{\partial v_n})^2 \right) \left( (\Delta v_1)^2 + (\Delta v_2)^2 + \dots + (\Delta v_n)^2 \right)

( Δ C ) 2 C 2 ϵ 2 \left( \Delta C \right)^2 \leqslant \parallel \nabla C \parallel ^ 2 \epsilon ^2 ,由于 Δ C \Delta C 必然取负数数值,因此可得,负数绝对值最大的 Δ C \Delta C 的绝对值为 C ϵ \parallel \nabla C\parallel \epsilon ,这个数值也就是梯度下降最快方向上下降的距离大小。其中 ϵ \epsilon 为步长,亦即 Δ v \parallel \Delta v \parallel 因此,为了取此最大值,要使得 Δ v \Delta v 的方向与 C \nabla C 的方向一致,方可使得 Δ C = C Δ v = C Δ v \Delta C = \nabla C \Delta v = \parallel \nabla C \parallel \parallel \Delta v \parallel ,因此,即需要使得 Δ v = η C \Delta v = - \eta \nabla C ,其中负号是为了保证梯度下降。

  • 第二部分

C C 是二元以及多元函数的情况已经被介绍了,很容易推广至一元函数,可以想象,一元函数下, C C 是一条平面中的曲线, C \nabla C 退化为曲线中某一点的斜率(斜率可正可负), Δ v \Delta v 是曲线上一点的变化趋势(向横轴负方向移动为负,向横轴正方向移动为正),保证 Δ v = η C \Delta v = -\eta \nabla C 。分两种情况即可大致讨论完毕,若 C < 0 \nabla C<0 Δ v \Delta v 的公式保证了 Δ v > 0 \Delta v > 0 ,因此,最终使得 Δ C \Delta C 为负值,梯度下降,若 C > 0 \nabla C>0 Δ v \Delta v 的公式保证了 Δ v < 0 \Delta v < 0 ,因此,最终也使得 Δ C \Delta C 为负值,梯度依旧下降。因此,在二元函数中,梯度下降的方向有某点空间范围的周身一圈,但是在一元函数中,方向退化为两个,要么正要么负。

  • 第三部分

优点:计算量小,速度快;

缺点:由于相当于MGD中取 m = 1 m=1 ,因此会造成获得的输出仅匹配最新得到的输入,而对全局数据的计算结果则无法得到满意的结果。


1.6 练习

  • 第一部分

设第二层有 n n 个神经元,其激活量分别为 a 1 a_1 a 2 a_2 \dots a n a_n ,设第三层有 m m 个神经元,其激活量分别为 a 1 a_1^\prime a 2 a_2^\prime \dots a m a_m^\prime ,设第三层各个神经元的偏置值分别为 b 1 b_1 b 2 b_2 \dots b m b_m

由此可得:

a 1 = σ ( w 11 a 1 + w 12 a 2 + + w 1 n a n + b 1 ) a 2 = σ ( w 21 a 1 + w 22 a 2 + + w 2 n a n + b 2 ) a m = σ ( w m 1 a 1 + w m 2 a 2 + + w m n a n + b m ) a_1^\prime = \sigma (w_{11} a_1 + w_{12} a_2 + \dots + w_{1n} a_n + b_1)\\ a_2^\prime = \sigma (w_{21} a_1 + w_{22} a_2 + \dots + w_{2n} a_n + b_2)\\ \qquad \qquad \qquad \qquad \vdots \\ a_m^\prime = \sigma (w_{m1} a_1 + w_{m2} a_2 + \dots + w_{mn} a_n + b_m)

对于第三层中某一个神经元而言,其表达式完全符合S型神经元的输出规则。

  • 第二部分

代码如下:

#! python2
import sys
sys.path.append("C:/neural-networks-and-deep-learning-master/src")
sys.path.append("C:/neural-networks-and-deep-learning-master/data")
sys.path.append("C:/neural-networks-and-deep-learning-master/fig")
import mnist_loader
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
import network
net = network.Network([784, 10]) # 仅有输入层与输出层
net.SGD(training_data, 60, 10, 3.0, test_data=test_data)

输入结果如下:

//使用语句net.SGD(training_data, 30, 10, 3.0, test_data=test_data)得到的训练结果
Epoch 0: 5698 / 10000
Epoch 1: 7433 / 10000
Epoch 2: 7495 / 10000
Epoch 3: 7514 / 10000
Epoch 4: 7554 / 10000
Epoch 5: 7537 / 10000
Epoch 6: 7556 / 10000
Epoch 7: 7565 / 10000
Epoch 8: 7530 / 10000
Epoch 9: 7558 / 10000
Epoch 10: 7582 / 10000    //由Epoch 9至Epoch 10并没有较大变化
Epoch 11: 7579 / 10000
Epoch 12: 7566 / 10000
Epoch 13: 7580 / 10000
Epoch 14: 7563 / 10000
Epoch 15: 7584 / 10000
Epoch 16: 7594 / 10000
Epoch 17: 7597 / 10000
Epoch 18: 7571 / 10000
Epoch 19: 7596 / 10000
Epoch 20: 8388 / 10000
Epoch 21: 8397 / 10000
Epoch 22: 8391 / 10000
Epoch 23: 8388 / 10000
Epoch 24: 8391 / 10000
Epoch 25: 8402 / 10000
Epoch 26: 8409 / 10000
Epoch 27: 8408 / 10000
Epoch 28: 8360 / 10000
Epoch 29: 8401 / 10000
//使用语句net.SGD(training_data, 60, 10, 3.0, test_data=test_data)得到的训练结果
Epoch 0: 5594 / 10000
Epoch 1: 5710 / 10000
Epoch 2: 6717 / 10000
Epoch 3: 7430 / 10000
Epoch 4: 7391 / 10000
Epoch 5: 7431 / 10000
Epoch 6: 7450 / 10000
Epoch 7: 7452 / 10000
Epoch 8: 7487 / 10000
Epoch 9: 7581 / 10000
Epoch 10: 8970 / 10000    //由Epoch 9至Epoch 10产生很大变化
Epoch 11: 9121 / 10000
Epoch 12: 9122 / 10000
Epoch 13: 9167 / 10000
Epoch 14: 9163 / 10000
Epoch 15: 9146 / 10000
Epoch 16: 9135 / 10000
Epoch 17: 9139 / 10000
Epoch 18: 9165 / 10000
Epoch 19: 9102 / 10000
Epoch 20: 9157 / 10000
Epoch 21: 9171 / 10000
Epoch 22: 9153 / 10000
Epoch 23: 9168 / 10000
Epoch 24: 9179 / 10000
Epoch 25: 9122 / 10000
Epoch 26: 9168 / 10000
Epoch 27: 9162 / 10000
Epoch 28: 9186 / 10000
Epoch 29: 9142 / 10000
Epoch 30: 9166 / 10000
Epoch 31: 9131 / 10000
Epoch 32: 9171 / 10000
Epoch 33: 9167 / 10000
Epoch 34: 9158 / 10000
Epoch 35: 9159 / 10000
Epoch 36: 9191 / 10000
Epoch 37: 9169 / 10000
Epoch 38: 9179 / 10000
Epoch 39: 9188 / 10000
Epoch 40: 9178 / 10000
Epoch 41: 9179 / 10000
Epoch 42: 9167 / 10000
Epoch 43: 9185 / 10000
Epoch 44: 9177 / 10000
Epoch 45: 9181 / 10000
Epoch 46: 9182 / 10000
Epoch 47: 9163 / 10000
Epoch 48: 9181 / 10000
Epoch 49: 9149 / 10000
Epoch 50: 9158 / 10000
Epoch 51: 9155 / 10000
Epoch 52: 9158 / 10000
Epoch 53: 9185 / 10000
Epoch 54: 9176 / 10000
Epoch 55: 9160 / 10000
Epoch 56: 9173 / 10000
Epoch 57: 9177 / 10000
Epoch 58: 9171 / 10000
Epoch 59: 9177 / 10000

可以看到,在迭代期为60的情况下,能够获得比30次迭代更好的识别率,但是同时值得注意的是,在两次实验中,第9至第10次迭代形成的结果大不相同,第一次实验中,此时识别率仍旧在75%附近,而在第二次实验中,迭代率突然由75%跳变为接近90%。


2.4 练习

(1)

由题意可得:

( z L ) = [ σ ( z 1 L ) 0 0 0 σ ( z 2 L ) 0 0 0 σ ( z j L ) ] ( j × j ) a C = [ C a 0 L C a 1 L C a j L ] ( j × 1 ) \sum^{\prime}(z^L) = \begin{bmatrix} \sigma^\prime (z_1^L) & 0& \ldots & 0\\0 & \sigma^\prime (z_2^L) & \ldots & 0\\\vdots & \vdots & \ddots & \vdots\\0 & 0& \ldots & \sigma^\prime (z_{j}^L)\\\end{bmatrix}_{(j\times j)} \qquad \nabla_{a}C = \begin{bmatrix} \frac{\partial C}{\partial a_0^L} \\ \frac{\partial C}{\partial a_1^L} \\ \vdots \\ \frac{\partial C}{\partial a_j^L} \end{bmatrix}_{(j \times 1)}

这里我假设 L L 层有 j j 个神经元。

因此可得:

( z L ) a C = [ σ ( z 1 L ) 0 0 0 σ ( z 2 L ) 0 0 0 σ ( z j L ) ] ( j × j ) [ C a 1 L C a 2 L C a j L ] ( j × 1 ) = [ C a 1 L σ ( z 1 L ) C a 2 L σ ( z 2 L ) C a j L σ ( z j L ) ] ( j × 1 ) = [ δ 1 L δ 2 L δ j L ] ( j × 1 ) \sum^{\prime}(z^L) \cdot \nabla_{a}C = \begin{bmatrix} \sigma^\prime (z_1^L) & 0& \ldots & 0\\0 & \sigma^\prime (z_2^L) & \ldots & 0\\\vdots & \vdots & \ddots & \vdots\\0 & 0& \ldots & \sigma^\prime (z_{j}^L)\\\end{bmatrix}_{(j\times j)} \cdot\begin{bmatrix} \frac{\partial C}{\partial a_1^L} \\ \frac{\partial C}{\partial a_2^L} \\ \vdots \\ \frac{\partial C}{\partial a_j^L} \end{bmatrix}_{(j \times 1)} = \begin{bmatrix} \frac{\partial C}{\partial a_1^L} \cdot \sigma^\prime (z_1^L) \\ \frac{\partial C}{\partial a_2^L} \cdot \sigma^\prime (z_2^L) \\ \vdots \\ \frac{\partial C}{\partial a_j^L} \cdot \sigma^\prime (z_j^L) \end{bmatrix}_{(j \times 1)} = \begin{bmatrix} \delta_1^L \\ \delta_2^L \\ \vdots \\ \delta_j^L \\\end{bmatrix}_{(j \times 1)}

即可证得 δ L = Σ ( z L ) a C \delta^L = \Sigma'(z^L) \nabla_a C

(2)

假设 l l 层有 j j 个神经元, l + 1 l+1 层有 k k 个神经元,已知:

( z l ) = [ σ ( z 1 l ) 0 0 0 σ ( z 2 l ) 0 0 0 σ ( z j l ) ] ( j × j ) ( w l + 1 ) T = [ w 11 l + 1 w 21 l + 1 w k 1 l + 1 w 12 l + 1 w 22 l + 1 w k 2 l + 1 w 1 j l + 1 w 2 j l + 1 w k j l + 1 ] ( j × k ) δ l + 1 = [ δ 1 l + 1 δ 2 l + 1 δ j l + 1 ] ( k × 1 ) \sum^{\prime}(z^l) = \begin{bmatrix} \sigma^\prime (z_1^l) & 0& \ldots & 0\\0 & \sigma^\prime (z_2^l) & \ldots & 0\\\vdots & \vdots & \ddots & \vdots\\0 & 0& \ldots & \sigma^\prime (z_{j}^l)\\\end{bmatrix}_{(j\times j)} \quad (w^{l+1})^T = \begin{bmatrix} w_{11}^{l+1} & w_{21}^{l+1} & \cdots & w_{k1}^{l+1} \\ w_{12}^{l+1} & w_{22}^{l+1} & \cdots & w_{k2}^{l+1} \\ \vdots & \vdots & \ddots & \vdots \\ w_{1j}^{l+1} & w_{2j}^{l+1} & \cdots & w_{kj}^{l+1} \\ \end{bmatrix}_{(j \times k)} \quad \delta^{l+1} = \begin{bmatrix} \delta_1^{l+1} \\ \delta_2^{l+1} \\ \vdots \\ \delta_j^{l+1} \\\end{bmatrix}_{(k \times 1)}

所以,

( z l ) ( w l + 1 ) T δ l + 1 = [ σ ( z 1 l ) w 11 l + 1 σ ( z 1 l ) w 21 l + 1 σ ( z 1 l ) w k 1 l + 1 σ ( z 2 l ) w 12 l + 1 σ ( z 2 l ) w 22 l + 1 σ ( z 2 l ) w k 2 l + 1 σ ( z j l ) w 1 j l + 1 σ ( z j l ) w 2 j l + 1 σ ( z j l ) w k j l + 1 ] ( j × k ) [ δ 1 l + 1 δ 2 l + 1 δ j l + 1 ] ( k × 1 ) = [ k σ ( z 1 l ) w k 1 l + 1 δ k l + 1 k σ ( z 2 l ) w k 2 l + 1 δ k l + 1 k σ ( z j l ) w k j l + 1 δ k l + 1 ] ( j × 1 ) \sum^{\prime}(z^l) \cdot (w^{l+1})^T \cdot \delta^{l+1} = \begin{bmatrix} \sigma^\prime (z_1^l)w_{11}^{l+1} & \sigma^\prime (z_1^l)w_{21}^{l+1} & \cdots & \sigma^\prime (z_1^l)w_{k1}^{l+1} \\ \sigma^\prime (z_2^l)w_{12}^{l+1} & \sigma^\prime (z_2^l)w_{22}^{l+1} & \cdots & \sigma^\prime (z_2^l)w_{k2}^{l+1} \\ \vdots & \vdots & \ddots & \vdots \\ \sigma^\prime (z_j^l)w_{1j}^{l+1} & \sigma^\prime (z_j^l)w_{2j}^{l+1} & \cdots & \sigma^\prime (z_j^l)w_{kj}^{l+1} \\ \end{bmatrix}_{(j \times k)} \cdot \begin{bmatrix} \delta_1^{l+1} \\ \delta_2^{l+1} \\ \vdots \\ \delta_j^{l+1} \\\end{bmatrix}_{(k \times 1)} = \begin{bmatrix} \sum_{k}\sigma^\prime (z_1^l) w_{k1}^{l+1}\delta_k^{l+1} \\ \sum_{k}\sigma^\prime (z_2^l) w_{k2}^{l+1}\delta_k^{l+1} \\ \vdots \\ \sum_{k}\sigma^\prime (z_j^l) w_{kj}^{l+1}\delta_k^{l+1} \end{bmatrix}_{(j \times 1)}

根据(BP2)的证明可知, k σ ( z j l ) w k j l + 1 δ k l + 1 = δ j l \sum_{k}\sigma^\prime (z_j^l) w_{kj}^{l+1}\delta_k^{l+1} = \delta_j^l ,因此:

[ k σ ( z 1 l ) w k 1 l + 1 δ k l + 1 k σ ( z 2 l ) w k 2 l + 1 δ k l + 1 k σ ( z j l ) w k j l + 1 δ k l + 1 ] ( j × 1 ) = [ δ 1 l δ 2 l δ j l ] ( j × 1 ) \begin{bmatrix} \sum_{k}\sigma^\prime (z_1^l) w_{k1}^{l+1}\delta_k^{l+1} \\ \sum_{k}\sigma^\prime (z_2^l) w_{k2}^{l+1}\delta_k^{l+1} \\ \vdots \\ \sum_{k}\sigma^\prime (z_j^l) w_{kj}^{l+1}\delta_k^{l+1} \end{bmatrix}_{(j \times 1)} = \begin{bmatrix} \delta_1^l \\ \delta_2^l \\ \vdots \\ \delta_j^l \end{bmatrix}_{(j \times 1)}

从而可得 ( z l ) ( w l + 1 ) T δ l + 1 = δ l \sum^{\prime}(z^l) \cdot (w^{l+1})^T \cdot \delta^{l+1} = \delta^l

(3)

此小问的证明属于递推证明,可以将书本(34)式看作递推公式,而 l + 1 = L l+1 = L 为递推式的最后一项,再由(33)式写出最后一项,轻松可证得结果,这里就不再做证明了。


2.5 练习

(BP3)

这里我使用反推法进行证明,要证明 δ j l = C b j l \delta_j^l = \frac{\partial C}{\partial b_j^l} ,而 δ j l = C z j l \delta_j^l = \frac{\partial C}{\partial z_j^l} ,相当于需要证明 C z j l = C b j l \frac{\partial C}{\partial z_j^l} = \frac{\partial C}{\partial b_j^l}

而为 C b j l = C z j l z j l b j l \frac{\partial C}{\partial b_j^l} = \frac{\partial C}{\partial z_j^l}\frac{\partial z_j^l}{\partial b_j^l} ,可见我们需要证明 z j l b j l = 1 \frac{\partial z_j^l}{\partial b_j^l} = 1

z j l = k w j k l a k l 1 + b j l z_j^l = \sum_kw_{jk}^la_k^{l-1}+b_j^l ,因此可得 z j l b j l = 1 \frac{\partial z_j^l}{\partial b_j^l} = 1 ,故 δ j l = C b j l \delta_j^l = \frac{\partial C}{\partial b_j^l} 成立,(BP3)证毕。

(BP4)

由于 δ j l = C z j l \delta_j^l = \frac{\partial C}{\partial z_j^l} ,所以 a k l 1 δ j l = a k l 1 C z j l a_k^{l-1} \delta_j^l = a_k^{l-1}\frac{\partial C}{\partial z_j^l} ,再由链式法则扩写为 a k l 1 δ j l = a k l 1 C z j l = a k l 1 C w j k l w j k l z j l a_k^{l-1} \delta_j^l = a_k^{l-1}\frac{\partial C}{\partial z_j^l} = a_k^{l-1}\frac{\partial C}{\partial w_{jk}^l}\frac{\partial w_{jk}^l}{\partial z_{j}^l}

因为 z j l = k w j k l a k l 1 + b j l z_j^l = \sum_kw_{jk}^la_k^{l-1}+b_j^l ,所以 z j l w j k l = a k l 1 \frac{\partial z_j^l}{\partial w_{jk}^l} = a_k^{l-1} ,因此 w j k l z j l = 1 a k l 1 \frac{\partial w_{jk}^l}{\partial z_{j}^l} = \frac{1}{a_k^{l-1}}

所以 a k l 1 δ j l = a k l 1 C z j l = a k l 1 C w j k l w j k l z j l = a k l 1 C w j k l 1 a k l 1 = C w j k l a_k^{l-1} \delta_j^l = a_k^{l-1}\frac{\partial C}{\partial z_j^l} = a_k^{l-1}\frac{\partial C}{\partial w_{jk}^l}\frac{\partial w_{jk}^l}{\partial z_{j}^l} = a_k^{l-1}\frac{\partial C}{\partial w_{jk}^l}\frac{1}{a_k^{l-1}} = \frac{\partial C}{\partial w_{jk}^l} ,证毕。


2.6 练习

  • 第一部分

假设改变输出函数的神经元为第 l l 层的第 j j 个神经元,则需要修正反向传播方程式BP1与BP2。

其中,对于BP1,需要在计算 δ j L = C a j L σ ( z j L ) \delta^L_j = \frac{\partial C}{\partial a^L_j} \sigma'(z^L_j) 时修正为 δ j L = C a j L f ( z j L ) \delta^L_j = \frac{\partial C}{\partial a^L_j} f'(z^L_j)

对于BP2,需要在计算 δ j l = k w k j l + 1 δ k l + 1 σ ( z j l ) \delta^l_j = \sum_k w^{l+1}_{kj} \delta^{l+1}_k \sigma'(z^l_j) 时修正为 δ j l = k w k j l + 1 δ k l + 1 f ( z j l ) \delta^l_j = \sum_k w^{l+1}_{kj} \delta^{l+1}_k f'(z^l_j)

  • 第二部分

由于 σ ( z ) = z \sigma(z) = z ,因此 σ ( z ) = 1 \sigma'(z) = 1 。故应将反向传播基本方程式改写为:

δ j L = C a j L \delta^L_j = \frac{\partial C}{\partial a^L_j}
δ l = ( ( w l + 1 ) T δ l + 1 ) \delta^l = ((w^{l+1})^T \delta^{l+1})
C b j l = δ j l \frac{\partial C}{\partial b^l_j} = \delta^l_j
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


2.7 练习

根据题目的要求,我们所要做的就是将原代码中的语句:

for x, y in mini_batch:
    delta_nabla_b, delta_nabla_w = self.backprop(x, y)
    nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
    nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]

改写为矩阵运算的形式。

在处理数据时,有一个主要问题就是如何构造矩阵 X = [ x 1 , x 2 , , x m ] X = [x_1,x_2,\dots,x_m]​ ,刚开始,我使用语句

inputDatas = [mini_batch[t][0] for t in range(mini_batch_size)]

但是这样构造的问题在于,如此构造的inputDatas并非一个 784 × m i n i _ b a t c h _ s i z e 784 \times mini\_batch\_size 大小的矩阵,而是一个含有 m i n i _ b a t c h _ s i z e mini\_batch\_size 784 × 1 784 \times 1 的array的列表,并不能进行矩阵运算。

因此,在这里我使用numpy的c_函数进行构造:

for t in xrange(1, mini_batch_size):
    inputDatas = np.c_[inputDatas, mini_batch[t][0]]
for t in xrange(1, mini_batch_size):
    resultDatas = np.c_[resultDatas, mini_batch[t][1]]

之后在处理时的另一个难点是计算nabla_b与nabla_w。

分析原代码可知,原代码遍历一个 m i n i _ b a t c h mini\_batch 时,将每一次计算得到的nabla_b与nabla_w进行叠加,因此 ,在进行矩阵运算时,需要解决的问题就是在一次矩阵运算后,直接将所得结果求和,代码如下:

for k in xrange(mini_batch_size):
    nabla_b[-l] = np.add(nabla_b[-l], delta[:, k].reshape(self.sizes[-l], 1))

通过这样的运算,我们就能够省去使用backprop函数,而是直接在update_mini_batch函数中进行运算。

全部代码如下:

"""
myNetwork.py
​~~~~~~~~~~

A module to implement the stochastic gradient descent learning
algorithm for a feedforward neural network.  Gradients are calculated
using backpropagation.  Note that I have focused on making the code
simple, easily readable, and easily modifiable.  It is not optimized,
and omits many desirable features.
"""

#### Libraries
# Standard library
import random

# Third-party libraries
import numpy as np


class Network(object):

    def __init__(self, sizes):
        """The list ``sizes`` contains the number of neurons in the
        respective layers of the network.  For example, if the list
        was [2, 3, 1] then it would be a three-layer network, with the
        first layer containing 2 neurons, the second layer 3 neurons,
        and the third layer 1 neuron.  The biases and weights for the
        network are initialized randomly, using a Gaussian
        distribution with mean 0, and variance 1.  Note that the first
        layer is assumed to be an input layer, and by convention we
        won't set any biases for those neurons, since biases are only
        ever used in computing the outputs from later layers."""
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        self.weights = [np.random.randn(y, x)
                        for x, y in zip(sizes[:-1], sizes[1:])]

    def feedforward(self, a):
        """Return the output of the network if ``a`` is input."""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a) + b)
        return a

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):
        """Train the neural network using mini-batch stochastic
        gradient descent.  The ``training_data`` is a list of tuples
        ``(x, y)`` representing the training inputs and the desired
        outputs.  The other non-optional parameters are
        self-explanatory.  If ``test_data`` is provided then the
        network will be evaluated against the test data after each
        epoch, and partial progress printed out.  This is useful for
        tracking progress, but slows things down substantially."""
        if test_data: n_test = len(test_data)
        n = len(training_data)
        for j in xrange(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k + mini_batch_size]
                for k in xrange(0, n, mini_batch_size)]  # mini_batch_size determines the number
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, mini_batch_size, eta)
            if test_data:
                print "Epoch {0}: {1} / {2}".format(
                    j, self.evaluate(test_data), n_test)
            else:
                print "Epoch {0} complete".format(j)

    def update_mini_batch(self, mini_batch, mini_batch_size, eta):
        """Update the network's weights and biases by applying
        gradient descent using backpropagation to a single mini batch.
        The ``mini_batch`` is a list of tuples ``(x, y)``, and ``eta``
        is the learning rate."""

        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        inputDatas = mini_batch[0][0]
        resultDatas = mini_batch[0][1]
        for t in xrange(1, mini_batch_size):
            inputDatas = np.c_[inputDatas, mini_batch[t][0]]
        for t in xrange(1, mini_batch_size):
            resultDatas = np.c_[resultDatas, mini_batch[t][1]]

        activation = inputDatas
        activations = [inputDatas]
        zs = []

        for b, w in zip(self.biases, self.weights):
            z = np.add(np.dot(w, activation), b)
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        delta = self.cost_derivative(activations[-1], resultDatas) * sigmoid_prime(zs[-1])
        
        for k in xrange(mini_batch_size):
            nabla_b[-1] = np.add(nabla_b[-1], delta[:, k].reshape(self.sizes[-1], 1))
        
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        
        for l in xrange(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l + 1].transpose(), delta) * sp
            for k in xrange(mini_batch_size):
                nabla_b[-l] = np.add(nabla_b[-l], delta[:, k].reshape(self.sizes[-l], 1))
            nabla_w[-l] = np.dot(delta, activations[-l - 1].transpose())
        
        self.weights = [w - (eta / len(mini_batch)) * nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b - (eta / len(mini_batch)) * nb
                       for b, nb in zip(self.biases, nabla_b)]

    def evaluate(self, test_data):
        """Return the number of test inputs for which the neural
        network outputs the correct result. Note that the neural
        network's output is assumed to be the index of whichever
        neuron in the final layer has the highest activation."""
        test_results = [(np.argmax(self.feedforward(x)), y)
                        for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    def cost_derivative(self, output_activations, y):
        """Return the vector of partial derivatives \partial C_x /
        \partial a for the output activations."""
        return (output_activations-y)


#### Miscellaneous functions
def sigmoid(z):
    """The sigmoid function."""
    return 1.0 / (1.0 + np.exp(-z))


def sigmoid_prime(z):
    """Derivative of the sigmoid function."""
    return sigmoid(z) * (1 - sigmoid(z))

使用以下语句分别执行原有版本与修正版本:

# 原有版本
import sys
sys.path.append("C:/neural-networks-and-deep-learning-master/src")
sys.path.append("C:/neural-networks-and-deep-learning-master/data")
sys.path.append("C:/neural-networks-and-deep-learning-master/fig")
import mnist_loader
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
import network    # 使用原有的network模块
net = network.Network([784, 30, 10])
import time
t = time.time()
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
print "Use time: " + str(time.time()-t) + "s"

# 修正版本
import sys
sys.path.append("C:/neural-networks-and-deep-learning-master/src")
sys.path.append("C:/neural-networks-and-deep-learning-master/data")
sys.path.append("C:/neural-networks-and-deep-learning-master/fig")
import mnist_loader
training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
import myNetwork    # 使用自行编写的myNetwork模块
net = network.Network([784, 30, 10])
import time
t = time.time()
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
print "Use time: " + str(time.time()-t) + "s"

得到的计算结果如下所示:

# 原有版本
======== RESTART: C:\neural-networks-and-deep-learning-master\EXE.py ========
Epoch 0: 9069 / 10000
Epoch 1: 9125 / 10000
Epoch 2: 9279 / 10000
Epoch 3: 9288 / 10000
Epoch 4: 9298 / 10000
Epoch 5: 9359 / 10000
Epoch 6: 9410 / 10000
Epoch 7: 9429 / 10000
Epoch 8: 9442 / 10000
Epoch 9: 9465 / 10000
Epoch 10: 9448 / 10000
Epoch 11: 9481 / 10000
Epoch 12: 9447 / 10000
Epoch 13: 9507 / 10000
Epoch 14: 9474 / 10000
Epoch 15: 9501 / 10000
Epoch 16: 9507 / 10000
Epoch 17: 9466 / 10000
Epoch 18: 9494 / 10000
Epoch 19: 9494 / 10000
Epoch 20: 9491 / 10000
Epoch 21: 9481 / 10000
Epoch 22: 9520 / 10000
Epoch 23: 9501 / 10000
Epoch 24: 9516 / 10000
Epoch 25: 9522 / 10000
Epoch 26: 9517 / 10000
Epoch 27: 9536 / 10000
Epoch 28: 9507 / 10000
Epoch 29: 9512 / 10000
Use time: 397.203999996s   # 耗时397.203999996s
    
# 修正版本
======== RESTART: C:\neural-networks-and-deep-learning-master\EXE.py ========
Epoch 0: 9083 / 10000
Epoch 1: 9273 / 10000
Epoch 2: 9324 / 10000
Epoch 3: 9369 / 10000
Epoch 4: 9393 / 10000
Epoch 5: 9368 / 10000
Epoch 6: 9418 / 10000
Epoch 7: 9429 / 10000
Epoch 8: 9454 / 10000
Epoch 9: 9475 / 10000
Epoch 10: 9444 / 10000
Epoch 11: 9449 / 10000
Epoch 12: 9469 / 10000
Epoch 13: 9478 / 10000
Epoch 14: 9466 / 10000
Epoch 15: 9445 / 10000
Epoch 16: 9467 / 10000
Epoch 17: 9475 / 10000
Epoch 18: 9479 / 10000
Epoch 19: 9457 / 10000
Epoch 20: 9447 / 10000
Epoch 21: 9484 / 10000
Epoch 22: 9474 / 10000
Epoch 23: 9490 / 10000
Epoch 24: 9479 / 10000
Epoch 25: 9489 / 10000
Epoch 26: 9484 / 10000
Epoch 27: 9503 / 10000
Epoch 28: 9496 / 10000
Epoch 29: 9471 / 10000
Use time: 156.733999968s   #耗时156.733999968s

可以看到,使用修正后的代码进行运算,其运算速度提高了大于2.5倍。


3.1 练习

  • 第一部分

我们已知 σ ( z ) = 1 / ( 1 + e z ) \sigma(z) = 1/(1+e^{-z}) ,因此,根据除法算式的求导原则,可得 σ ( z ) = e z / ( 1 + e z ) 2 \sigma^{\prime}(z) = e^{-z}/(1+e^{-z})^2

σ ( z ) ( 1 σ ( z ) ) = 1 1 + e z ( 1 1 1 + e z ) = 1 1 + e z e z 1 + e z = e z ( 1 + e z ) 2 \sigma(z)(1-\sigma(z)) = \frac{1}{1+e^{-z}} (1-\frac{1}{1+e^{-z}}) = \frac{1}{1+e^{-z}} \frac{e^{-z}}{1+e^{-z}} = \frac{e^{-z}}{(1+e^{-z})^2}

因此 σ ( z ) = σ ( z ) ( 1 σ ( z ) ) \sigma^{\prime}(z) = \sigma(z)(1-\sigma(z))

  • 第二部分

(1)

y = 0 y=0 1 1 时,第二个表达式会出现计算 l n 0 ln0 的情况,这个计算在数学上不成立。但是对于第一个表达式并不会存在这个问题,因为对数运算式子为 a a 1 a 1-a ,而激活值 a a 本身不可能完全等于0或者等于1,只会无限逼近,因此不会出现计算式不成立的情况。

(2)

p = y ln a + ( 1 y ) ln ( 1 a ) p=y\ln{a}+(1-y)\ln{(1-a)} ,要证明, a = y a=y 时, p p 最小,其中, y y 为定值。

可以计算导数 p = y / a ( 1 y ) / ( 1 a ) = ( y a ) / ( a ( 1 a ) ) p^{\prime} = y/a-(1-y)/(1-a)=(y-a)/(a(1-a)) ,其中, 0 < a < 1 0<a<1 0 y 1 0 \le y \le 1 ,所以, a ( 1 a ) > 0 a(1-a)>0

因此,当 y < a y<a 时, p < 0 p^{\prime}<0 ,当 y = a y=a 时, p = 0 p^{\prime}=0 ,当 y > a y>a 时, p > 0 p^{\prime}>0 ,根据高数知识可知,在这种情况下,取 a = y a=y ,可以得到算式 p p 在定义域下的全局最小值。

  • 第三部分

(1)

已知 C = 1 2 n x ( y a L ) 2 C = \frac{1}{2n} \sum_x(y-a^L)^2 ,即 C = 1 2 n x j ( y j a j L ) 2 C = \frac{1}{2n} \sum_x \sum_j(y_j-a_j^L)^2 ,所以

C w j k L = 1 n x ( a j L y j ) a j L w j k L = 1 n x ( a j L y j ) σ ( z j L ) a k L 1 \frac{\partial C}{\partial w_{jk}^L} = \frac{1}{n} \sum_x(a_j^L-y_j)\frac{\partial a_j^L}{\partial w_{jk}^L} = \frac{1}{n} \sum_x(a_j^L-y_j)\sigma^\prime(z_j^L)a_k^{L-1} ,其中, a j L = σ ( k w j k L a k L 1 + b j L ) a_j^L = \sigma(\sum_kw_{jk}^La_k^{L-1}+b_j^L)

已知交叉熵函数 C = 1 n x j [ y j ln a j L + ( 1 y j ) ln ( 1 a j L ) ] C = \frac{-1}{n}\sum_x\sum_j[y_j\ln a_j^L+(1-y_j)\ln (1-a_j^L)] ,输出误差 δ j L = C z j L \delta_j^L = \frac{\partial C}{\partial z_j^L} ,所以

δ j L = 1 n x [ ( y j a j L 1 y j 1 a j L ) δ ( z j L ) ] = 1 n x ( a j L y j a j L ( 1 a j L ) σ ( z j L ) ) \delta_j^L = -\frac{1}{n}\sum_x[(\frac{y_j}{a_j^L}-\frac{1-y_j}{1-a_j^L})\delta^\prime(z_j^L)] = \frac{1}{n}\sum_x(\frac{a_j^L-y_j}{a_j^L(1-a_j^L)}\sigma^\prime(z_j^L)) ,又因 σ ( z j L ) = a j L ( 1 a j L ) \sigma^\prime(z_j^L) = a_j^L(1-a_j^L) ,所以 δ j L = 1 n x ( a j L y j ) \delta_j^L = \frac{1}{n}\sum_x(a_j^L-y_j) ,同时由于训练样本数量为1,即 n = 1 n=1 ,再将计算式写为向量形式,得 δ L = a L y \delta^L = a^L-y

已知 C w j k l = a k l 1 δ j l \frac{\partial C}{\partial w_{jk}^{l}}=a_k^{l-1}\delta_j^l ,将上面得到的计算式带入可得, C w j k L = 1 n x a k L 1 ( a j L y j ) \frac{\partial C}{\partial w_{jk}^{L}}=\frac{1}{n}\sum_xa_k^{L-1}(a_j^L-y_j)

(2)

输出误差 δ j L = C z j L = C a j L a j L z j L \delta_j^L=\frac{\partial C}{\partial z_j^L}=\frac{\partial C}{\partial a_j^L}\frac{\partial a_j^L}{\partial z_j^L} ,由于 a j L = z j L a_j^L = z_j^L ,所以 a j L z j L = 1 \frac{\partial a_j^L}{\partial z_j^L} = 1 ,所以, δ j L = C a j L \delta_j^L=\frac{\partial C}{\partial a_j^L} ,已知 C = 1 2 n x ( y a L ) 2 C = \frac{1}{2n} \sum_x(y-a^L)^2 ,所以 δ j L = C a j L = 1 n x ( a j L y j ) \delta_j^L=\frac{\partial C}{\partial a_j^L} = \frac{1}{n}\sum_x(a_j^L-y_j) ,同上,由于训练样本数量为1,再将结果写为向量形式,因此可得 δ L = a L y \delta^L=a^L-y

已知 C w j k l = a k l 1 δ j l \frac{\partial C}{\partial w_{jk}^{l}}=a_k^{l-1}\delta_j^l C b j l = δ j l \frac{\partial C}{\partial b_{j}^{l}}=\delta_j^l ,带入上面的计算结果可得 C w j k L = 1 n x a k L 1 ( a j L y j ) \frac{\partial C}{\partial w_{jk}^{L}}=\frac{1}{n}\sum_xa_k^{L-1}(a_j^L-y_j) C b j l = 1 n x ( a j L y j ) \frac{\partial C}{\partial b_{j}^{l}}=\frac{1}{n}\sum_x(a_j^L-y_j)

  • 第四部分

我们使用3.1.1中的单神经元作为样例进行分析,由于我们要使用反向传播算法,因此代价函数必须满足的条件二要求:代价要写成神经⽹络输出的函数,即 C = f ( a ) C = f(a) ,其中, a a 为神经网络的输出。

因此, C = f ( a ) = f ( σ ( z ) ) C = f(a)=f(\sigma(z)) ,所以 C w j = f ( a ) σ ( z ) z w j \frac{\partial C}{\partial w_j} = f^\prime(a) \sigma^\prime(z)\frac{\partial z}{\partial w_j} ,因为 z = j w j x j + b z = \sum_j w_jx_j+b ,所以 C w j = f ( a ) σ ( z ) x j \frac{\partial C}{\partial w_j} = f^\prime(a) \sigma^\prime(z)x_j ,可以看到,我们只能通过构造函数 f f 使得其导数 f ( a ) f^\prime(a) 的一部分与 σ ( z ) \sigma^\prime(z) 相消,且这种相消本质上只是数值相等造成的消除。而要消除 x j x_j 的话,则需要构造出的函数 f f 能够对每一个特定的输入 x x 形成消除,这就会导致构造出的代价函数不具有一般性,本身也就不再成为代价函数。

  • 第五部分

这题比较简单,直接使用已有的数字识别network2即可,稍稍添加几行代码即可查看网络输出激活值的和:

for x, y in data:
    print(sum(self.feedforward(x)))

这里我仅对网络做一次训练,语句如下:

net.SGD(training_data, 1, 10, 0.5, evaluation_data=test_data, monitor_evaluation_accuracy=True)

得到输出如下所示:

Epoch 0 training complete
[1.01071867]
[1.02141826]
[1.00266542]
[1.01484663]
[1.06155141]
[1.03858622]
[0.88712522]
[0.95423446]
[1.09079256]
[0.82836036]
[1.03176008]
[1.25510091]
[1.0202282]
[1.01834994]
[1.0296834]
''''''  #此处仅截取部分输出数据,因为原代码输出数据过多
[0.82316285]
[1.03860165]
[0.83621146]
[1.0497341]
[1.04812499]
[1.07704423]
[1.01602758]
[0.98729342]
[1.06504799]
[0.9413053]
[1.01584016]
[1.02875694]
[0.57143772]
[1.21617521]
[1.02335674]
[1.29757417]
[1.04701417]
[0.98423752]
[1.26214428]
[0.68121561]
[1.02639369]
[1.01915821]
[1.02542507]
[1.00972677]
[1.00107948]
[1.11790148]
[1.02599891]
Accuracy on evaluation data: 9134 / 10000

Use time: 27.3789999485s

可以很明显看到,网络输出激活值的和并不能确保为1。

  • 第六部分

(1)

已知 a j L = e z j L k e z k L a_j^L=\frac{e^{z_j^L}}{\sum_ke^{z_k^L}} ,若 j = k j=k ,则 a j L z k L = e z j L ( k e z k L e z k L ) ( k e z k L ) 2 \frac{\partial a_j^L}{\partial z_k^L} = \frac{e^{z_j^L}(\sum_ke^{z_k^L}-e^{z_k^L})}{(\sum_ke^{z_k^L})^2} ,显然,该式子的值大于0,若 j k j \ne k ,则 a j L z k L = e z j L e z k L ( k e z k L ) 2 \frac{\partial a_j^L}{\partial z_k^L} = \frac{-e^{z_j^L}e^{z_k^L}}{(\sum_ke^{z_k^L})^2} ,显然,该式子的值小于0。

(2)

这也是显而易见的,因为 a j L = e z j L k e z k L a_j^L=\frac{e^{z_j^L}}{\sum_ke^{z_k^L}} ,根据其分母项可以分析得出,输出激活值依赖于输出层所有神经元的带权输入。

  • 第七部分

已知 a j L = e z j L k e z k L a_j^L=\frac{e^{z_j^L}}{\sum_ke^{z_k^L}} ,因此 a j L k e z k L = e z j L a_j^L {\sum_ke^{z_k^L}}={e^{z_j^L}} ,所以, z j L = ln ( a j L k e z k L ) = ln a j L + ln k e z k L z_j^L = \ln (a_j^L \sum_ke^{z_k^L}) = \ln a_j^L + \ln \sum_ke^{z_k^L} ,题目中 z j L = ln a j L + C z_j^L = \ln a_j^L +C ,对比分析可知, C = ln k e z k L C = \ln \sum_ke^{z_k^L} ,其值独立于变量 j j

  • 第八部分

(1)

已知 C = k y k ln a k L C = -\sum_k y_k \ln a_k^L ,(书上公式82不是对数似然代价函数严格意义上的代数表达式),在求解 C b j L \frac{\partial C}{\partial b_j^L} 以及 C w j k L \frac{\partial C}{\partial w_{jk}^L} 之前,我们先计算 a j L z i L \frac{\partial a_j^L}{\partial z_i^L}

根据本章第六部分第一小问的计算结果我们可以知道,若 j = i j=i ,则 a j L z i L = e z j L ( k e z k L e z j L ) ( k e z k L ) 2 = a j L ( 1 a j L ) \frac{\partial a_j^L}{\partial z_i^L} = \frac{e^{z_j^L}(\sum_ke^{z_k^L}-e^{z_j^L})}{(\sum_ke^{z_k^L})^2}=a_j^L(1-a_j^L) ,若 j i j\ne i ,则 a j L z i L = e z j L e z i L ( k e z k L ) 2 = a j L a i L \frac{\partial a_j^L}{\partial z_i^L} = \frac{-e^{z_j^L}e^{z_i^L}}{(\sum_ke^{z_k^L})^2}=-a_j^La_i^L

所以 C b j L = ( y j 1 a j L a j L z j L k ( k j ) y k 1 a k L a j L z k L ) z j L b j L = y j ( 1 a j L ) + k ( k j ) y k a j L = y j + a j L k y k = a j L y j \frac{\partial C}{\partial b_j^L} = (-y_j\frac{1}{a_j^L}\frac{\partial a_j^L}{\partial z_j^L}-\sum_{k(k \ne j)}y_k\frac{1}{a_k^L}\frac{\partial a_j^L}{\partial z_k^L})\frac{\partial z_j^L}{\partial b_j^L}=-y_j(1-a_j^L)+\sum_{k(k \ne j)}y_ka_j^L=-y_j+a_j^L\sum_ky_k=a_j^L-y_j

同理, C w j k L = ( y j 1 a j L a j L z j L k ( k j ) y k 1 a k L a j L z k L ) z j L w j k L = a k L 1 ( a j L y j ) \frac{\partial C}{\partial w_{jk}^L} = (-y_j\frac{1}{a_j^L}\frac{\partial a_j^L}{\partial z_j^L}-\sum_{k(k \ne j)}y_k\frac{1}{a_k^L}\frac{\partial a_j^L}{\partial z_k^L})\frac{\partial z_j^L}{\partial w_{jk}^L}=a_k^{L-1}(a_j^L-y_j)

(2)

首先,该式子保证了输出值均为正数,其次,该式子保证了输出激活值的和为1,因此构成了概率分布。

C C \rightarrow \infty ,则 a j L 1 k a_j^L \to \frac{1}{k}

(3)

C b j L = y j 1 a j L a j L z j L k ( k j ) y k 1 a k L a j L z k L = y j ( 1 a j L ) + k ( k j ) y k a j L = y j + a j L k y k = a j L y j \frac{\partial C}{\partial b_j^L} = -y_j\frac{1}{a_j^L}\frac{\partial a_j^L}{\partial z_j^L}-\sum_{k(k \ne j)}y_k\frac{1}{a_k^L}\frac{\partial a_j^L}{\partial z_k^L}=-y_j(1-a_j^L)+\sum_{k(k \ne j)}y_ka_j^L=-y_j+a_j^L\sum_ky_k=a_j^L-y_j


3.2 练习

  • 第一部分

如果允许过大的旋转,比如说90°的旋转角度,则实际上相当于使用了错误的数据来训练神经网络,最终可能会导致神经网络训练失效。


3.3 练习

  • 第一部分

z z 的方差为 1 + 1 / n i n = 1 + 1000 / 500 = 3 / 2 1+1/n_{in}=1+1000/500=3/2 ,所以标准差为 3 / 2 \sqrt{3/2}


3.4 练习

(1)

在L1规范化中,我们需要修改的仅仅是权重的更新规则,根据公式(97)可知,新的权重更新规则为:

w w η λ n s g n ( w ) η C 0 w w \rightarrow w-\frac{\eta \lambda}{n}sgn(w)-\eta\frac{\partial C_0}{\partial w}

因此在代码中需要修改的仅仅是update_mini_batch函数中self.weights的计算公式,将其修改为:

self.weights = [w-eta*(lmbda/n)*sgn(w)-(eta/len(mini_batch))*nw 
                for w, nw in zip(self.weights, nabla_w)]

其中,sgn函数为符号函数,该函数传入参数为array,处理时需要注意,定义如下:

def sgn(w):
    [rows, cols] = w.shape
    p = np.zeros((rows, cols))
    for i in xrange(rows):
        for j in xrange(cols):
           if w[i, j] > 0:
               p[i, j] = 1
           elif w[i, j] < 0:
               p[i, j] = -1
    return p

修改为L1规范化后,使用以下语句执行:

net.SGD(training_data, 30, 10, 0.1, lmbda = 5.0, evaluation_data=test_data, monitor_evaluation_accuracy=True)

得到的结果如下:

Epoch 0 training complete
Accuracy on evaluation data: 8705 / 10000

Epoch 1 training complete
Accuracy on evaluation data: 9013 / 10000

Epoch 2 training complete
Accuracy on evaluation data: 9124 / 10000

Epoch 3 training complete
Accuracy on evaluation data: 9211 / 10000

Epoch 4 training complete
Accuracy on evaluation data: 9262 / 10000

Epoch 5 training complete
Accuracy on evaluation data: 9313 / 10000

Epoch 6 training complete
Accuracy on evaluation data: 9349 / 10000

Epoch 7 training complete
Accuracy on evaluation data: 9384 / 10000

Epoch 8 training complete
Accuracy on evaluation data: 9396 / 10000

Epoch 9 training complete
Accuracy on evaluation data: 9427 / 10000

Epoch 10 training complete
Accuracy on evaluation data: 9453 / 10000

Epoch 11 training complete
Accuracy on evaluation data: 9449 / 10000

Epoch 12 training complete
Accuracy on evaluation data: 9488 / 10000

Epoch 13 training complete
Accuracy on evaluation data: 9489 / 10000

Epoch 14 training complete
Accuracy on evaluation data: 9494 / 10000

Epoch 15 training complete
Accuracy on evaluation data: 9511 / 10000

Epoch 16 training complete
Accuracy on evaluation data: 9525 / 10000

Epoch 17 training complete
Accuracy on evaluation data: 9552 / 10000

Epoch 18 training complete
Accuracy on evaluation data: 9557 / 10000

Epoch 19 training complete
Accuracy on evaluation data: 9563 / 10000

Epoch 20 training complete
Accuracy on evaluation data: 9557 / 10000

Epoch 21 training complete
Accuracy on evaluation data: 9546 / 10000

Epoch 22 training complete
Accuracy on evaluation data: 9584 / 10000

Epoch 23 training complete
Accuracy on evaluation data: 9613 / 10000

Epoch 24 training complete
Accuracy on evaluation data: 9621 / 10000

Epoch 25 training complete
Accuracy on evaluation data: 9605 / 10000

Epoch 26 training complete
Accuracy on evaluation data: 9624 / 10000

Epoch 27 training complete
Accuracy on evaluation data: 9593 / 10000

Epoch 28 training complete
Accuracy on evaluation data: 9623 / 10000

Epoch 29 training complete
Accuracy on evaluation data: 9631 / 10000

在同样的参数下,若无规范化,使用以下语句执行:

net.SGD(training_data, 30, 10, 0.1, test_data=test_data)

得到的运行结果如下:

Epoch 0: 2907 / 10000
Epoch 1: 5010 / 10000
Epoch 2: 6011 / 10000
Epoch 3: 6362 / 10000
Epoch 4: 6575 / 10000
Epoch 5: 6750 / 10000
Epoch 6: 6850 / 10000
Epoch 7: 6939 / 10000
Epoch 8: 6976 / 10000
Epoch 9: 7014 / 10000
Epoch 10: 7065 / 10000
Epoch 11: 7091 / 10000
Epoch 12: 7113 / 10000
Epoch 13: 7124 / 10000
Epoch 14: 7144 / 10000
Epoch 15: 7159 / 10000
Epoch 16: 7170 / 10000
Epoch 17: 7183 / 10000
Epoch 18: 7180 / 10000
Epoch 19: 7196 / 10000
Epoch 20: 7207 / 10000
Epoch 21: 7215 / 10000
Epoch 22: 7223 / 10000
Epoch 23: 7230 / 10000
Epoch 24: 7225 / 10000
Epoch 25: 7243 / 10000
Epoch 26: 7256 / 10000
Epoch 27: 8282 / 10000
Epoch 28: 8330 / 10000
Epoch 29: 8340 / 10000

可以看到,相较于L1规范化的结果,无规范化得到的结果比较差,30个迭代期后仅仅能够达到83.4%的识别率,注意,这里我将学习速率 η \eta 设为了0.1,可以很明显对比出L1规范化的效果更好。

注:后来我发现实际上numpy库中含有符号函数sign,直接调用的话可以用以下代码实现,传入参数x为array:

numpy.sign(x)

(2)

根据公式(39)与公式(66)可知,对于二次代价函数,输出误差 δ j L = C a j L σ ( z j L ) \delta_j^L=\frac{\partial C}{\partial a_j^L}\sigma^ \prime(z_j^L) ,对于交叉熵代价函数,输出误差 δ L = a L y \delta^L = a^L-y 。如果要修改Network.cost_derivative并将其应用于交叉熵代价函数,则需要将Network.cost_derivative修改为:

def cost_derivative(self, z, output_activations, y):
    return (output_activations-y)/sigmoid_prime(z)

可以看到,由于network.py中代码为:

delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])

为了保证计算结果的正确性,导致我们必须在cost_derivative函数中多除以一个sigmoid_prime(z),因此函数本身的计算算式又不成其为代价函数的导数,这会导致一些理解上的问题。

而在network2.py中,通过将代价函数写为一个类,我们实现了,既封装了代价函数的计算公式,也可以将与之相关的输出误差计算公式封装进去。可以看到,为了保证代码封装的一致性,在使用交叉熵函数计算输出误差时,即使没有使用到输出层的权重输入,依然在其输入参数中包含了参数z,完全解决了仅仅修改cost_derivative带来的问题。


3.5 练习

  • 第一部分

(1)

在Network类中,添加函数如下:

def check_fit(self, accuracy, j, n_epoch):
    if j >= n_epoch:
        if accuracy[j] <= accuracy[j - n_epoch]:
            print
            print "The network has been overfitted in epoch " + str(j)
            return 1
        else:
            return 0
    else:
        return 0

在SGD函数的循环中调用该函数:

if self.check_fit(evaluation_accuracy, j, n_epoch):
    break

函数中的n_epoch为可调参数,表示如果n_epoch个迭代期准确度不上升就不再进行训练,防止过度拟合,将该参数设为SGD函数的一个传入参数,默认传入值为10,如下所示:

def SGD(self, training_data, epochs, mini_batch_size, eta,
    lmbda = 0.0,
    evaluation_data=None,
    monitor_evaluation_cost=False,
    monitor_evaluation_accuracy=False,
    monitor_training_cost=False,
    monitor_training_accuracy=False,
    n_epoch = 10):   # 该处传入参数n_epoch

这里我使用overfitting.py函数运行,并将其中绘图的函数注释了,设定的参数如下:

Enter a file name: MNISTTest180919_2
Enter the number of epochs to run for: 100
training_cost_xmin (suggest 200): 200
test_accuracy_xmin (suggest 200): 200
test_cost_xmin (suggest 0): 0
training_accuracy_xmin (suggest 0): 0
Training set size (suggest 1000): 1000
Enter the regularization parameter, lambda (suggest: 5.0): 5.0

得到的输出结果为:

......  # 该处省略了前17个迭代期的输出结果
Epoch 17 training complete
Cost on training data: 1.33789987018
Accuracy on training data: 939 / 1000
Cost on evaluation data: 1.0821807555

The network has been overfitted in epoch 17

(2)

我这里有三个思路,分别是区间中值增幅判定,区间均值增幅判定以及区间最大值增幅判定,这三个方法仍需设定区间大小参数n_epoch。

区间中值增幅判定

代码:

def check_fit(self, accuracy, j, n_epoch):
    if j > n_epoch:
        temp = sorted(accuracy[j-n_epoch:j])
        lengthpp = len(temp)
        length = lengthpp - 1
        mid_now = (temp[lengthpp/2] + temp[length/2]) / 2.0
        temp = sorted(accuracy[j-1-n_epoch:j-1])
        lengthpp = len(temp)
        length = lengthpp - 1
        mid_last = (temp[lengthpp/2] + temp[length/2]) / 2.0
        if mid_now <= mid_last:
            print
            print "The network has been overfitted in epoch " + str(j)
            return 1
        else:
            return 0
    else:
        return 0

得到的输出结果如下:

...... # 前16个结果省略
Epoch 16 training complete
Cost on training data: 1.32334422395
Accuracy on training data: 947 / 1000
Cost on evaluation data: 1.07454114819

The network has been overfitted in epoch 16

区间均值增幅判定

代码:

 def check_fit(self, accuracy, j, n_epoch):
     if j > n_epoch:
         mean_now = np.mean(accuracy[j-n_epoch:j])
         mean_last = np.mean(accuracy[j-1-n_epoch:j-1])
         if mean_now <= mean_last:
             print
             print "The network has been overfitted in epoch " + str(j)
             return 1
         else:
             return 0
     else:
         return 0

输出结果如下:

 ...... # 前17个迭代期输出数据省略
 Epoch 18 training complete
 Cost on training data: 1.28025099479
 Accuracy on training data: 955 / 1000
 Cost on evaluation data: 1.03996906298
 
 The network has been overfitted in epoch 18

区间最大值增幅判定

代码:

 def check_fit(self, accuracy, j, n_epoch):
     if j > n_epoch:
         max_now = max(accuracy[j-n_epoch:j])
         max_last = max(accuracy[j-1-n_epoch:j-1])
         if max_now <= max_last:
             print
             print "The network has been overfitted in epoch " +  str(j)
             return 1
         else:
             return 0
     else:
         return 0

输出结果如下:

 Epoch 11 training complete
 Cost on training data: 1.43547611121
 Accuracy on training data: 940 / 1000
 Cost on evaluation data: 1.04705574375
 
 The network has been overfitted in epoch 11

综合对比下来,通过区间的准确度均值是否增长来判定获得的最终准确率是最高的。

  • 第二部分

10回合不提升终止策略代码如上一部分,在SGD需要做如下修改,在SGD函数运行之初,记录 η \eta 的1/128:

 etaZero = eta / 128

在检测到10回合不提升之后需要做的代码如下:

 if self.check_fit(evaluation_accuracy, j, n_epoch):
     eta = eta / 2
     if eta < etaZero:
         break

得到运行结果如下:

 ...... # 前49个迭代期的运行输出省略
 Epoch 49 training complete
 Cost on training data: 1.20477393934
 Accuracy on training data: 969 / 1000
 Cost on evaluation data: 0.982837436038
 Accuracy on evaluation data: 8715 / 10000
  • 第三部分

对于梯度下降算法,若参数 λ \lambda 过小,则会导致规范化本身的作用过小,导致过度拟合,若 λ \lambda 过大,又会导致代价函数过于偏向权重的minimize,使得很难达到梯度下降应该有的效果。

确定参数 η \eta 的问题主要在于,其决定了步长与精度,在复杂的神经网络中,权重偏置空间中很可能含有多个局部最小值,因此步长决定了能否找到全局最小值以及寻找最小值的速度。 η \eta 过大,则很有可能在梯度下降的过程中反复错过最小值并且达不到跟高精度下的最小值, η \eta 过小,则又会导致梯度下降的速度缓慢,效率低下。


3.6 练习

  • 第一部分

(1)

μ &gt; 1 \mu &gt; 1 ,相当于速度本身在被不断放大,导致震荡、甚至发散,代码上可能导致计算出错(如出现无穷大的速度值)。

(2)

μ &lt; 1 \mu &lt; 1 ,相当于速度被不断转换方向(翻转),梯度始终被拉向反方向,导致永远不能找到“低谷”。

  • 第二部分

首先需要初始化“速度”,这部分代码与偏置和权重的初始化放在一起:

 self.biases = [np.random.randn(y, 1) for y in self.sizes[1:]]
 self.weights = [np.random.randn(y, x)
                 for x, y in zip(self.sizes[:-1], self.sizes[1:])]
 self.v_b = [np.random.randn(y, 1) for y in self.sizes[1:]]
 self.v_w = [np.random.randn(y, x)
             for x, y in zip(self.sizes[:-1], self.sizes[1:])]

接着要在SGD函数以及update_mini_batch函数中传入参数 μ \mu ,这部分无需赘述;

最后就是在更新self.weights与self.biases时改变并添加一部分代码:

 self.v_w = [mu * v - (eta/len(mini_batch))*nw
             for v, nw in zip(self.v_w, nabla_w)]
 self.weights = [w + v for w, v in zip(self.weights, self.v_w)]
 self.v_b = [mu * b-(eta/len(mini_batch))*nb
             for b, nb in zip(self.v_b, nabla_b)]
 self.biases = [b + v for b, v in zip(self.biases, self.v_b)]
  • 第三部分

因为 tanh ( z ) = e z e z e z + e z \tanh (z) = \frac{e^z - e^{-z}}{e^z + e^{-z}} ,所以
tanh ( z / 2 ) = e z / 2 e z / 2 e z / 2 + e z / 2 \tanh (z/2) = \frac{e^{z/2} - e^{-z/2}}{e^{z/2} + e^{-z/2}}
所以
1 + tanh ( z / 2 ) 2 = ( 1 + e z / 2 e z / 2 e z / 2 + e z / 2 ) / 2 = e z / 2 e z / 2 + e z / 2 = 1 1 + e z \frac{1 + \tanh (z/2)}{2} = (1+\frac{e^{z/2} - e^{-z/2}}{e^{z/2} + e^{-z/2}})/2 = \frac{e^{z/2}}{e^{z/2} + e^{-z/2}} = \frac{1}{1+e^{-z}}
所以
σ ( z ) = 1 + tanh ( z / 2 ) 2 \sigma (z) = \frac{1 + \tanh (z/2)}{2}


Chapter 4

4.3 问题
(a)
在之前书中给出的例子中,我们可以看到,在构建的简单神经网络中,第一隐藏层中的神经元,对于输入层的输入 x x y y ,其中一个输入的权重总是被设置为0,这就相当于使得最终在空间中的 z z 值大小始终与 x x y y 其中一个参数无关,也就形成了仅在 x x y y 方向上的阶跃函数。
当我们设置权重大小均不为0时即可得到任意方向上的阶跃函数,其中阶跃函数的方向由权重 w 1 w_1 w 2 w_2 决定,偏置参数 b b 则决定了阶跃点的位置。
这里我使用Matlab绘制出了两种不同参数下的阶跃函数图形:

 x = -50 : 0.1 : 50;
 y = x;
 [X, Y] = meshgrid(x, y);
 
 % 第一种阶跃函数,权重参数很大且均为正数
 w1_1L2 = 100;
 w1_2L2 = 100;
 b_1L2 = 0;
 a_1L2 = 1 ./ (1 + exp(-(w1_1L2*X + w1_2L2*Y + b_1L2)));
 mesh(X, Y, a_1L2), xlabel('x'), ylabel('y')

 % 第二种阶跃函数,权重参数很大且均为负数
 w2_1L2 = -100;
 w2_2L2 = -100;
 b_2L2 = 0;
 a_2L2 = 1 ./ (1 + exp(-(w2_1L2*X + w2_2L2*Y + b_2L2)));
 mesh(X, Y, a_2L2), xlabel('x'), ylabel('y')

得到的输出图形如下图所示:

在这里插入图片描述
在这里插入图片描述

可以看到,由于权重参数绝对值相同,所以阶跃分界面交于 x y x y 平面中直线 y = x y = -x 处,且由于权重参数的设置互为相反数,因此得到的阶跃函数恰好是互补的。
通过调整权重参数,即可轻易调整分界面的位置,实现任意方向上的阶跃函数。

(b)
由于我们获得的阶跃可以是任意方向上的,因此我们可以使用两个方向相反、分界面错开的阶跃函数构成一个凸函数,将这一凸函数沿某一中心旋转一周,每隔一定时间点 Δ t \Delta t 暂停旋转,取出这一时间点的凸函数。在获得了 n n 个这样的凸函数后,将其直接叠加,即可得到一个近似于圆形底面的凸函数,可以想见, Δ t \Delta t 越小,得到的 n n 越大,即获取到的凸函数越多,叠加后会越近似于圆形。

Extension:
在研究这道题的过程中,我绘制了一些图形:

在这里插入图片描述

这里我总结了一些规律,设核心图形为 n n 边形,靠近核心图形的,若是边相邻,则其在空间中的值为 n 1 n-1 ,若是点相邻,则其在空间中的值为 n 2 n-2 ,仅有两边夹的区域,空间值为 f l o o r ( n / 2 ) floor(n/2) ,其余区域则视情况而定。

( c )
通过上面绘制的图形可以推测出,得到的圆形底面塔并非是圆柱体,而是类似于一个梯台。根据修补阶跃函数的思想,我们可以将得到的函数 f ( x , y ) f(x,y) 除以 M M ,并将该函数 M M 次移动 1 / M 1/M 个单位长度,并将其相加,即可得到较好的近似。

4.4 问题
(1)
普遍性条件要求 s ( z ) s(z) z z \to - \infty z z \to \infty 时有明确的定义,而修正线性单元在 z z \to \infty 时的值是不明确的,因此不符合普遍性条件。
在线性修正单元中,体现了权重与偏置参数的作用,且可以通过调节权重与偏置,按照书本证明过程的要求,使得权重足够大,且可以使用偏置来调整位置。另一方面,可以使用 y = w x + b 1 y=wx+b_1 y = w x + b 2 y=-wx+b_2 叠加构成阶跃函数,形成构造函数的基本条件。

(2)
普遍性条件要求 s ( z ) s(z) z z \to - \infty z z \to \infty 时有明确的定义,而 s ( z ) = z s(z) = z z z \to -\infty 以及 z z \to \infty 时的值是不明确的,因此不符合普遍性条件。
线性神经元会导致所有的神经元相当于权重参数均为1,偏置参数均为0,这就无法构成书本中的例子所要求的偏重值大的基本条件。另一方面,无论如何加减处理,获得的仍然是线性函数,无法构成阶跃函数。


Chapter 5

无练习或问题


Chapter 6

6.1 问题

在卷积神经网络中,较之于全连接神经网络中的反向传播,需要做出一定修改的地方就是卷积层与池化层的反向传播公式,这里我参考了@hanbingtao中的推导过程,并发现了其推导公式中的一些问题。我的具体推导过程如下所述。

  • 卷积层的反向传播公式推导

首先,我们考虑输入层深度为1,卷积核个数为1,跨距为1的基本情况。让我们事先规定好一些符号: L L 指神经网络最后一层的层数, l l 指卷积层所在层数, l 1 l-1 为输入层(即卷积层的上一层)层数, z l z^l 指卷积层的带权输入, w l w^l 指卷积核的权重, b l b^l 指卷积核的偏置, a i , j l 1 a_{i,j}^{l-1} 指输入层的激活值, f l 1 f^{l-1} 指卷积层的激活函数。

这里我们假设, l 1 l-1 层的size为1*3*3,卷积核的size为1*2*2,因此 l l 层的size为1*2*2,其中第一个数字指深度。

由此我们可以得到两个基本关系表达式:

z l = c o n v ( w l , a l 1 ) + b l z^l = conv(w^l,a^{l-1})+b^l
a i , j l 1 = f l 1 ( z i , j l 1 ) a_{i,j}^{l-1} = f^{l-1}(z_{i,j}^{l-1})

在推导反向传播公式时,我们需要先假设 δ l \delta^l 已知,这个假设成立的前提是由于卷积神经网络的末端依旧是全连接神经网络,因而必然能够得到 δ L \delta^L ,这是反向传播递推的基础,因此在这里,我们先假设输出层的误差已知。

根据书本2.5节的知识我们知道 δ j l = C z j l \delta^l_j=\frac{\partial C}{\partial z_j^l} ,在卷积层中,我们换一种表达方式,即 δ i , j l = C z i , j l \delta^l_{i,j}=\frac{\partial C}{\partial z_{i,j}^l} ,因此 δ i , j l 1 = C z i , j l 1 \delta^{l-1}_{i,j}=\frac{\partial C}{\partial z_{i,j}^{l-1}}
所以

δ i , j l 1 = C a i , j l 1 a i , j l 1 z i , j l 1 \delta^{l-1}_{i,j}=\frac{\partial C}{\partial a_{i,j}^{l-1}}\frac{\partial a_{i,j}^{l-1}}{\partial z_{i,j}^{l-1}}

注意,我们能够这么写的基础是来源于上述两个基本公式中的第二个,它述明了 a i , j l 1 a_{i,j}^{l-1} z i , j l 1 z_{i,j}^{l-1} 有直接关系。

看来,为了求出 δ i , j l 1 \delta^{l-1}_{i,j} ,我们还需要分别求出 C a i , j l 1 \frac{\partial C}{\partial a_{i,j}^{l-1}} a i , j l 1 z i , j l 1 \frac{\partial a_{i,j}^{l-1}}{\partial z_{i,j}^{l-1}} ,我们一步一步来完成。

  • 求出 C a i , j l 1 \frac{\partial C}{\partial a_{i,j}^{l-1}}

考察三类情况,依然是由简单到复杂。

第一类:求 C a 1 , 1 l 1 \frac{\partial C}{\partial a_{1,1}^{l-1}}

因为 z 1 , 1 l = w 1 , 1 l a 1 , 1 l 1 + w 1 , 2 l a 1 , 2 l 1 + w 2 , 1 l a 2 , 1 l 1 + w 2 , 2 l a 2 , 2 l 1 + b l z_{1,1}^l=w_{1,1}^la_{1,1}^{l-1}+w_{1,2}^la_{1,2}^{l-1}+w_{2,1}^la_{2,1}^{l-1}+w_{2,2}^la_{2,2}^{l-1}+b^l

所以 C a 1 , 1 l 1 = C z 1 , 1 l z 1 , 1 l a 1 , 1 l 1 = δ 1 , 1 l w 1 , 1 l \frac{\partial C}{\partial a_{1,1}^{l-1}}=\frac{\partial C}{\partial z_{1,1}^{l}}\frac{\partial z_{1,1}^{l}}{\partial a_{1,1}^{l-1}}=\delta_{1,1}^lw_{1,1}^l

注意,这里能够这样进行微分拆解的前提是基于前面所说的两个基本公式中的第一个,它述明了 z l z^l a l 1 a^{l-1} 之间的直接关系。

实际上,

C a i , j l 1 = m n C z m , n l z m , n l a i , j l 1 \frac{\partial C}{\partial a_{i,j}^{l-1}}=\sum_m \sum_n \frac{\partial C}{\partial z_{m,n}^l}\frac{\partial z_{m,n}^l}{\partial a_{i,j}^{l-1}}

第二类:求 C a 1 , 2 l 1 \frac{\partial C}{\partial a_{1,2}^{l-1}}

这里需要注意, a 1 , 2 l 1 a_{1,2}^{l-1} 能够影响到的带权输入有两个

z 1 , 1 l = w 1 , 1 l a 1 , 1 l 1 + w 1 , 2 l a 1 , 2 l 1 + w 2 , 1 l a 2 , 1 l 1 + w 2 , 2 l a 2 , 2 l 1 + b l z_{1,1}^l=w_{1,1}^la_{1,1}^{l-1}+w_{1,2}^la_{1,2}^{l-1}+w_{2,1}^la_{2,1}^{l-1}+w_{2,2}^la_{2,2}^{l-1}+b^l

z 1 , 2 l = w 1 , 1 l a 1 , 2 l 1 + w 1 , 2 l a 1 , 3 l 1 + w 2 , 1 l a 2 , 2 l 1 + w 2 , 2 l a 2 , 3 l 1 + b l z_{1,2}^l=w_{1,1}^la_{1,2}^{l-1}+w_{1,2}^la_{1,3}^{l-1}+w_{2,1}^la_{2,2}^{l-1}+w_{2,2}^la_{2,3}^{l-1}+b^l

所以 C a 1 , 2 l 1 = C z 1 , 1 l z 1 , 1 l a 1 , 2 l 1 + C z 1 , 2 l z 1 , 2 l a 1 , 2 l 1 = δ 1 , 1 l w 1 , 2 l + δ 1 , 2 l w 1 , 1 l \frac{\partial C}{\partial a_{1,2}^{l-1}}=\frac{\partial C}{\partial z_{1,1}^{l}}\frac{\partial z_{1,1}^{l}}{\partial a_{1,2}^{l-1}} + \frac{\partial C}{\partial z_{1,2}^{l}}\frac{\partial z_{1,2}^{l}}{\partial a_{1,2}^{l-1}}=\delta_{1,1}^lw_{1,2}^l+\delta_{1,2}^lw_{1,1}^l

第三类:求 C a 2 , 2 l 1 \frac{\partial C}{\partial a_{2,2}^{l-1}}

这里需要注意, a 2 , 2 l 1 a_{2,2}^{l-1} 能够影响到的带权输入有四个

z 1 , 1 l = w 1 , 1 l a 1 , 1 l 1 + w 1 , 2 l a 1 , 2 l 1 + w 2 , 1 l a 2 , 1 l 1 + w 2 , 2 l a 2 , 2 l 1 + b l z_{1,1}^l=w_{1,1}^la_{1,1}^{l-1}+w_{1,2}^la_{1,2}^{l-1}+w_{2,1}^la_{2,1}^{l-1}+w_{2,2}^la_{2,2}^{l-1}+b^l

z 1 , 2 l = w 1 , 1 l a 1 , 2 l 1 + w 1 , 2 l a 1 , 3 l 1 + w 2 , 1 l a 2 , 2 l 1 + w 2 , 2 l a 2 , 3 l 1 + b l z_{1,2}^l=w_{1,1}^la_{1,2}^{l-1}+w_{1,2}^la_{1,3}^{l-1}+w_{2,1}^la_{2,2}^{l-1}+w_{2,2}^la_{2,3}^{l-1}+b^l

z 2 , 1 l = w 1 , 1 l a 2 , 1 l 1 + w 1 , 2 l a 2 , 2 l 1 + w 2 , 1 l a 3 , 1 l 1 + w 2 , 2 l a 3 , 2 l 1 + b l z_{2,1}^l=w_{1,1}^la_{2,1}^{l-1}+w_{1,2}^la_{2,2}^{l-1}+w_{2,1}^la_{3,1}^{l-1}+w_{2,2}^la_{3,2}^{l-1}+b^l

z 2 , 2 l = w 1 , 1 l a 2 , 2 l 1 + w 1 , 2 l a 2 , 3 l 1 + w 2 , 1 l a 3 , 2 l 1 + w 2 , 2 l a 3 , 3 l 1 + b l z_{2,2}^l=w_{1,1}^la_{2,2}^{l-1}+w_{1,2}^la_{2,3}^{l-1}+w_{2,1}^la_{3,2}^{l-1}+w_{2,2}^la_{3,3}^{l-1}+b^l

所以
C a 2 , 2 l 1 = C z 1 , 1 l z 1 , 1 l a 2 , 2 l 1 + C z 1 , 2 l z 1 , 2 l a 2 , 2 l 1 + C z 2 , 1 l z 2 , 1 l a 2 , 2 l 1 + C z 2 , 2 l z 2 , 2 l a 2 , 2 l 1 = δ 1 , 1 l w 2 , 2 l + δ 1 , 2 l w 2 , 1 l + δ 2 , 1 l w 1 , 2 l + δ 2 , 2 l w 1 , 1 l \frac{\partial C}{\partial a_{2,2}^{l-1}}=\frac{\partial C}{\partial z_{1,1}^{l}}\frac{\partial z_{1,1}^{l}}{\partial a_{2,2}^{l-1}} + \frac{\partial C}{\partial z_{1,2}^{l}}\frac{\partial z_{1,2}^{l}}{\partial a_{2,2}^{l-1}} + \frac{\partial C}{\partial z_{2,1}^{l}}\frac{\partial z_{2,1}^{l}}{\partial a_{2,2}^{l-1}} + \frac{\partial C}{\partial z_{2,2}^{l}}\frac{\partial z_{2,2}^{l}}{\partial a_{2,2}^{l-1}} \\=\delta_{1,1}^lw_{2,2}^l+\delta_{1,2}^lw_{2,1}^l+\delta_{2,1}^lw_{1,2}^l+\delta_{2,2}^lw_{1,1}^l

在这里插入图片描述

如上图所示,注意上图中左侧是卷积层,右侧才是输入层,通过上面的简单推导,最终我们可以得到以下公式:

C a i , j l 1 = m n w m , n l δ i m + 1 , j n + 1 l \frac{\partial C}{\partial a_{i,j}^{l-1}} = \sum_m \sum_n w_{m,n}^l \delta^l_{i-m+1,j-n+1}
其中,如果下标 i m + 1 i-m+1 j n + 1 j-n+1 不在 δ l \delta ^l 范围内,均取0

  • 求出 a i , j l 1 z i , j l 1 \frac{\partial a_{i,j}^{l-1}}{\partial z_{i,j}^{l-1}}

根据两个基本公式中的第二个可以知道, a i , j l 1 z i , j l 1 = f ( z i , j l ) \frac{\partial a_{i,j}^{l-1}}{\partial z_{i,j}^{l-1}} = f^{\prime}(z_{i,j}^l) ,可以轻松求出。

因此,最后,卷积层的误差反向传播递推公式推导可以写成:

δ i , j l 1 = m n w m , n l δ i m + 1 , j n + 1 l f ( z i , j l ) \delta^{l-1}_{i,j}=\sum_m \sum_n w_{m,n}^l \delta^l_{i-m+1,j-n+1} \bigodot f^{\prime}(z_{i,j}^l)

刚才我们讨论的情况是最简单的情况,即输入层深度为1,卷积核个数为1,跨距为1,现在我们再来看看如果这些取值发生了变化应该怎么办。

1. 深度不为1

设深度为D,则对应卷积核的深度也为D,最终的输出是多个卷积核卷积结果叠加形成的。因此,在反向递推误差项时,上述公式的带权输入与权重值带入对应深度的值即可获得输入层对应深度的误差项。即:
δ d , i , j l 1 = m n w d , m , n l δ i m + 1 , j n + 1 l f ( z d , i , j l ) \delta^{l-1}_{d,i,j}=\sum_m \sum_n w_{d,m,n}^l \delta^l_{i-m+1,j-n+1} \bigodot f^{\prime}(z_{d,i,j}^l)

2. 卷积核个数不为1

设卷积核个数为N,则对应输出层的深度为N,输入层的误差项为卷积层各层输出误差项的叠加。即:
δ i , j l 1 = d m n w d , m , n l δ d , i m + 1 , j n + 1 l f ( z d , i , j l ) \delta^{l-1}_{i,j}=\sum_d\sum_m \sum_n w_{d,m,n}^l \delta^l_{d,i-m+1,j-n+1} \bigodot f^{\prime}(z_{d,i,j}^l)

3. 跨距不为1

设跨距为S,对比跨距为1与跨距为2时得到的输出结果可以知道,跨距为2时的结果为跨距为1时结果的聚缩,因此在跨距不为1时,将输出层误差项补为跨距为1时的输出即可实现反向递推。

通过上面的论述,我们解决了卷积层反向传播方程(BP2),但是(BP3)与(BP4)仍旧没有得到解决,我们继续往下走。

(BP3),即 C w i , j l \frac{\partial C}{\partial w_{i,j}^l} 与误差项之间的关系。

首先,我们需要进行链式分解:

C w i , j l = m n C z m , n l z m , n l w i , j l \frac{\partial C}{\partial w_{i,j}^l} = \sum_m\sum_n\frac{\partial C}{\partial z_{m,n}^l}\frac{\partial z_{m,n}^l}{\partial w_{i,j}^l}

根据上面的公式,我们还是从最简单的开始推导,沿用上述最简单的卷积层模型,可以推知:

C w 1 , 1 l = δ 1 , 1 l a 1 , 1 l 1 + δ 1 , 2 l a 1 , 2 l 1 + δ 2 , 1 l a 2 , 1 l 1 + δ 2 , 2 l a 2 , 2 l 1 \frac{\partial C}{\partial w_{1,1}^l}=\delta_{1,1}^la_{1,1}^{l-1}+\delta_{1,2}^la_{1,2}^{l-1}+\delta_{2,1}^la_{2,1}^{l-1}+\delta_{2,2}^la_{2,2}^{l-1}

C w 1 , 2 l = δ 1 , 1 l a 1 , 2 l 1 + δ 1 , 2 l a 1 , 3 l 1 + δ 2 , 1 l a 2 , 2 l 1 + δ 2 , 2 l a 2 , 3 l 1 \frac{\partial C}{\partial w_{1,2}^l}=\delta_{1,1}^la_{1,2}^{l-1}+\delta_{1,2}^la_{1,3}^{l-1}+\delta_{2,1}^la_{2,2}^{l-1}+\delta_{2,2}^la_{2,3}^{l-1}

C w 2 , 1 l = δ 1 , 1 l a 2 , 1 l 1 + δ 1 , 2 l a 2 , 2 l 1 + δ 2 , 1 l a 3 , 1 l 1 + δ 2 , 2 l a 3 , 2 l 1 \frac{\partial C}{\partial w_{2,1}^l}=\delta_{1,1}^la_{2,1}^{l-1}+\delta_{1,2}^la_{2,2}^{l-1}+\delta_{2,1}^la_{3,1}^{l-1}+\delta_{2,2}^la_{3,2}^{l-1}

C w 2 , 2 l = δ 1 , 1 l a 2 , 2 l 1 + δ 1 , 2 l a 2 , 3 l 1 + δ 2 , 1 l a 3 , 2 l 1 + δ 2 , 2 l a 3 , 3 l 1 \frac{\partial C}{\partial w_{2,2}^l}=\delta_{1,1}^la_{2,2}^{l-1}+\delta_{1,2}^la_{2,3}^{l-1}+\delta_{2,1}^la_{3,2}^{l-1}+\delta_{2,2}^la_{3,3}^{l-1}

通过总结,结合上面的链式分解的公式,可以轻易推导得到:

C w i , j l = m n δ m , n l a i + m 1 , j + n 1 l \frac{\partial C}{\partial w_{i,j}^l} = \sum_m\sum_n \delta_{m,n}^l a_{i+m-1,j+n-1}^l

(BP4),即 C b l \frac{\partial C}{\partial b_{}^l} 与误差项之间的关系。

直接使用链式公式得到:

C b l = m n C z m , n l z m , n l b l = m n δ m , n l \frac{\partial C}{\partial b_{}^l} = \sum_m\sum_n \frac{\partial C}{\partial z_{m,n}^l}\frac{\partial z_{m,n}^l}{\partial b_{}^l}=\sum_m\sum_n\delta_{m,n}^l

以上,我们完成了卷积层的反向传播公式推导。

  • 池化层的反向传播公式推导

由于池化层并不会进行激活函数的运算,可以将其看作 f ( z ) = z f(z) = z ,因此其误差项的传递也比较简单,现仅讨论max_pooling对应的误差项传递公式(这里,我们按照2*2的大小为凝缩的特征映射)。

根据max_pooling的运算法则, z i , j l = max ( z 2 i 1 , 2 j 1 l 1 , z 2 i 1 , 2 j l 1 , z 2 i , 2 j 1 l 1 , z 2 i , 2 j l 1 ) z_{i,j}^l=\max(z_{2i-1,2j-1}^{l-1},z_{2i-1,2j}^{l-1},z_{2i,2j-1}^{l-1},z_{2i,2j}^{l-1})

所以

δ i , j l 1 = C z i , j l 1 = C z i , j l z i , j l z i , j l 1 \delta^{l-1}_{i,j}=\frac{\partial{C}}{\partial{z^{l-1}_{i,j}}}=\frac{\partial{C}}{\partial{z^{l}_{\lceil i \rceil ,\lceil j \rceil }}} \frac{\partial{z^{l}_{\lceil i \rceil ,\lceil j \rceil }}}{\partial{z^{l-1}_{i,j}}}

可以看到, C z i , j l = δ i , j l \frac{\partial{C}}{\partial{z^{l}_{i,j}}} = \delta^{l}_{i,j} ,而根据池化层的公式, z i , j l z i , j l 1 = 1 \frac{\partial{z^{l}_{\lceil i \rceil ,\lceil j \rceil }}}{\partial{z^{l-1}_{i,j}}}= 1 ,在 z i , j l = z i , j l 1 z^{l}_{\lceil i \rceil ,\lceil j \rceil }=z^{l-1}_{i,j} 处,对应的 δ i , j l 1 = δ i , j l \delta^{l-1}_{i,j}=\delta^{l}_{i,j} ,其余地方则为0。

其他的池化方法,如L2池化等,都可以使用相同的方法进行推导。

以上就是卷积神经网络的反向传播公式。

6.2 练习 1

我试着用CPU跑过本书的卷积神经网络代码,一度让我以为时间暂停了,所以关于实际测试的题目我都已尽量避开。这里我想主要想要问的是,全连接层在卷积神经网络中究竟扮演了怎样的角色?

我所看到的比较好的解释来源于这篇文章,其主要表达了一个观点,即全连接层起到了忽略特征位置进行特征分类的作用,具体可以参见这篇文章。

6.2 问题 1

根据何恺明目前的研究工作来看,使用ReLU结合He initialization的权重初始化方法是比较好的。无论如何,使用CPU来判断孰优孰劣是不明智的。

6.2 问题 2

通过扩展数据集,更多的训练数据能够减少过拟合,提高模型适应性与预测能力。

顺便谈一下我关于神经网络的一些思考,这里我想到了两点:

第一,噪声和随机性似乎才是大自然发展至今的缘由,神经网络其实有很多地方和大自然不谋而合,比如说dropout技术的使用,根据“组合派”的观点,dropout技术之所以能够减少过拟合正是由于减少了神经元之间的联合依赖性,就像基因一样,不断地吸收新的基因(异性交配)进行糅合才能够更好地适应环境。

第二,目前我们看到了神经网络结构对于神经网络训练的重要性,而且不同的结构会导致不同的学习上限,我不禁联想到,难道说大脑神经元之间的空间结构也会决定一个人的学习能力,甚至性格吗?

6.3 问题

(1)

参考3.5节我所提出的一些方法,这里由于我们的迭代期较短,可以考虑将窗口的size变小。

(2)

将SoftMaxLayer类对应的accuracy方法移过来便是。

(3)

同样可以参考3.5节中的相关习题。

(4)

直接使用expand_mnist生成的扩展数据包即可。

(5)

参考 network2。

(6)

依旧是有关是否过拟合的判断,参考3.5节相关习题。

(7) (8)

ReLU究竟好在哪里?首先我们需要知道为什么会出现sigmoid和tanh这两个非线性激活函数,参考本书Chapter4《神经⽹络可以计算任何函数的可视化证明》可以知道,唯有通过这种非线性函数,才有可能产生无限逼近任何函数的基础条件——凸函数或者塔函数。

但是同时我们也渐渐看到了sigmoid和tanh函数造成梯度消失/爆炸、运算量过大的弊端,而这些毛病ReLU统统没有。最后一点在于,由于ReLU具有0输出,形成的网络具有稀疏性,联合依赖性较低,过拟合情况也就不那么容易发生了。

发布了10 篇原创文章 · 获赞 8 · 访问量 4610

猜你喜欢

转载自blog.csdn.net/ICE_KoKi/article/details/82901108
今日推荐