转载请注明出处: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 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)] 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)
这段代码输出的结果是:
0123
所以,如果我们把epoch定义成4,那么循环就会进行4次,也就是说训练会进行4轮。
文章来源:https://www.codelast.com/
显然,for j in xrange(epochs) 下面的循环体里的代码,就是每一轮训练要执行的代码。
首先,random.shuffle(training_data)这一句的作用是什么呢?答:随机打乱training_data这个list。
为了说明它,我们打开ipython,来做一个相当简单的试验:
import numpy as np import random a = [(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,它的输出结果是:
0369
所以,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
进行计算的呢?且听下回分解。