《Neural Networks and Deep Learning》读书笔记:最简单的识别MNIST的神经网络程序(2)

来源:互联网 发布:linux 搜狗输入法 双拼 编辑:程序博客网 时间:2024/05/16 03:12

转载请注明出处:https://www.codelast.com/

本文是上一篇文章的续文。
《Neural Networks and Deep Learning》一书的中文译名是《神经网络与深度学习》,书如其名,不需要解释也知道它是讲什么的,这是本入门级的好书。
在第一章中,作者展示了如何编写一个简单的、用于识别MNIST数据的Python神经网络程序。
本文接着上一篇文章对程序代码进行解析。

下面来看看 SGD() 方法的实现。先把它的完整代码贴上来:

def SGD(self, training_data, epochs, mini_batch_size, eta,test_data=None):"""Train the neural network using mini-batch stochasticgradient descent.  The ``training_data`` is a list of tuples``(x, y)`` representing the training inputs and the desiredoutputs.  The other non-optional parameters areself-explanatory.  If ``test_data`` is provided then thenetwork will be evaluated against the test data after eachepoch, and partial progress printed out.  This is useful fortracking 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)]for mini_batch in mini_batches:self.update_mini_batch(mini_batch, eta)if test_data:print "Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)else:print "Epoch {0} complete".format(j)

代码自带详细注释,而且很容易看懂。
文章来源:https://www.codelast.com/
for j in xrange(epochs) 这句代码使得训练会进行epoch轮。
xrang()是Python自带函数,随便试验一下就知道它的作用了,例如:

for j in xrange(4):print(j)

这段代码输出的结果是:

0
1
2
3

所以,如果我们把epoch定义成4,那么循环就会进行4次,也就是说训练会进行4轮。
文章来源:https://www.codelast.com/
显然,for j in xrange(epochs) 下面的循环体里的代码,就是每一轮训练要执行的代码。
首先,random.shuffle(training_data)这一句的作用是什么呢?答:随机打乱training_data这个list。
为了说明它,我们打开ipython,来做一个相当简单的试验:

import numpy as npimport randoma = [(1, 2), (3, 4), (5, 6), (7, 8), (0, 9)]random.shuffle(a)print(a)random.shuffle(a)print(a)random.shuffle(a)print(a)

在上面的代码中,打印了3次 a 的内容,在我的PC上,3次print的输出分别如下:

[(7, 8), (0, 9), (1, 2), (3, 4), (5, 6)]
[(3, 4), (5, 6), (0, 9), (7, 8), (1, 2)]
[(1, 2), (0, 9), (5, 6), (3, 4), (7, 8)]

可见,random.shuffle()把list里的元素进行了随机打乱。由于是随机的,所以,你的测试结果可能和我的不一样。
随机打乱数据是为了防止某些pattern相似的输入数据集中在一个batch中,导致对训练结果产生负面影响。SGD不就是“随机梯度下降”嘛。
文章来源:https://www.codelast.com/
在打乱数据之后,程序就把所有输入数据拆分成了若干个批量(mini batch),每一个batch的大小是由mini_batch_size定义的:

mini_batches = [training_data[k:k+mini_batch_size]for k in xrange(0, n, mini_batch_size)]

这里的xrange用法和上面的xrange稍有不同,我们还是用一个实例来表明它的作用:

for k in xrange(0, 10, 3):print(k)

这段代码把mini_batch_size设置成了3,它的输出结果是:

0
3
6
9
可见,它会使k从0开始,按mini_batch_size的大小为步长递增,但最大值不超过第二个参数。
所以,training_data也是按这个套路实现了分割。
文章来源:https://www.codelast.com/
在training_data分割得到了若干个mini batch之后,下面就是对每一个mini batch分别进行训练,从而求得参数向量 w 和 b 的值。但这里是一个串行的计算,也就是说第一个mini batch计算完了,才轮到第二个mini batch计算,依此类推。
对每一个mini batch求解参数向量,其实就是这一句代码调用(eta即 η ,学习率,这是一个人为设定其值的超参数):

self.update_mini_batch(mini_batch, eta)

有人可能会说,这跟待求的参数向量 w 和 b 没什么关系啊?在上一篇文章中我们看到, w 和 b 被定义成了Network类的成员变量,update_mini_batch()其实是在函数体内计算出、并更新了它们的值,所以只是表面上看起来“没关系”,实际上完全有关系。

在循环迭代完所有的mini batch之后, w 和 b 的值也就被更新完了,即“学习”的过程也就结束了。所以,随着迭代的进行, w 和 b 的值越来越接近理想值,所有迭代结束之后,我们就认为 w 和 b 的值已经达到了理想值。
文章来源:https://www.codelast.com/
在每一轮迭代的最后,有下面这段代码:

if test_data:print "Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)else:print "Epoch {0} complete".format(j)

在每一轮迭代更新完 w 和 b 的值之后,如果test_data不为空的话,那么就会用evaluate()方法对本轮的计算结果进行评估。理论上,随着迭代一轮一轮的进行,评估结果应该越来越好。
文章来源:https://www.codelast.com/
所以,现在最关键的代码就封装在了update_mini_batch()方法中。这个方法是怎么对 w 和 b 进行计算的呢?且听下回分解。

阅读全文
0 0