上一回我们聊完了算法,这回我们正式开始写代码。上回在做公式推导的时候,我们实际上只是针对一个数据样本进行推导,而实际中,计算和训练都是一批一批完成的。大多数机器学习训练都有batch的概念,而训练中batch的计算不是一个一个地算,而是一批数据集中算,那么就需要用上矩阵了。 首先给出Loss的代码,这里y和t都是按列存储的,每一列都是一个样本: class SquareLoss: 为了代码的简洁,我们在前向运算的时候就把一些后向计算的信息都保存起来,这样在后向计算的时候就能简单点。这样这个类就不能具备多线程的特性了,不过想支持多线程的功能还有别的办法。后面的全连接层也会采用同样的思路——前向为后向准备运算数据。 上一节我们讲了1个例子,输入有2个元素,第一层有4个输出,第2层有1个输出。我们假设训练数据有N个,我们对所有相关的训练数据和参数做以下的约定:
基于上面的规则,我们把上一节的例子以批量数据的形式画成了下面一张图: 这张图从左往右有三个部分:
对于上图右边的部分,需要认真地看几遍,最好能仔细地推导一遍,才能更好地掌握这个推导的过程,尤其是为了维度对矩阵做转置这部分。 看懂了上面的图,接下来要做的就是对上面的内容进行总结,写出最终的矩阵版后向传播算法: class FC: 好了,现在我们有了Loss类和全连接类,我们还需要一个类把上面两个类串联起来,这里为了后面的内容我们定义了许多默认变量: class Net: 代码是写完了,可是我们还需要验证一下自己的代码是不是正确的。一般来说我们会采用一些近似方法计算验证梯度是否正确,而现在,有一个博客为我们做了这件事情: A Step by Step Backpropagation Example http://link.zhihu.com/?target=https%3A//mattmazur.com/2015/03/17/a-step-by-step-backpropagation-example/ 把我们的代码用博客上数据和结果做一下验证,就可以帮助我们修正代码做好debug。其实上面的代码本来也不多,可能犯错的地方也不多。 一些具体的例子 一个经典的例子就是用神经网络做逻辑运算。我们可以用一个两层神经网络来模拟模拟与运算。下面就是具体的代码: # and operation X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T y = np.array([[0],[0],[0],[1]]).T net = Net(2,4,1,0.1) net.train(X,y) 以下是调用代码给出的结果,可以看出最终的结果效果还不错,经过10000轮的迭代,最终模型给出的结果和我们的期望结果十分相近,实际上如果我们继续进行迭代,这个算法的精度还可以进一步地提高,Loss可以进一步地减少: iter = 0, loss =0.105256639066 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.40930536 0.4617139 0.36923076 0.4299025 ]] iter = 1000, loss =0.0229368486589 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.04445123 0.22684496 0.17747671 0.68605373]] iter = 2000, loss =0.00657594469044 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.01057127 0.11332809 0.11016211 0.83411794]] iter = 3000, loss =0.00322081318498 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00517544 0.07831654 0.07871461 0.88419737]] iter = 4000, loss =0.00201059297485 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00336374 0.06171018 0.0624756 0.90855558]] iter = 5000, loss =0.00142205310651 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00249895 0.05189239 0.05257126 0.92309992]] iter = 6000, loss =0.00108341055769 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00200067 0.04532728 0.04585262 0.93287134]] iter = 7000, loss =0.000866734887908 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00167856 0.04058314 0.04096262 0.9399489 ]] iter = 8000, loss =0.000717647908313 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00145369 0.03696819 0.0372232 0.94534786]] iter = 9000, loss =0.000609513241467 === Label vs Prediction === t=[[0 0 0 1]] y=[[ 0.00128784 0.03410575 0.03425751 0.94962473]] === Final === X=[[0 0 1 1] [0 1 0 1]] t=[[0 0 0 1]] y=[[ 0.00116042 0.03177232 0.03183889 0.95311123]] 记得初始化 初始化是神经网络一个十分重要的事情,我就不说三遍了,来个实验,如果我们把所有的参数初始化成0,会发生一个可怕的事情: X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T y = np.array([[0],[0],[0],[1]]).T net = Net(2,4,1,0.1) net.fc1.w.fill(0) net.fc2.w.fill(0) net.train(X,y) print "=== w1 ===" print net.fc1.w print "=== w2 ===" print net.fc2.w 直接看结果: === Final ===X=[[0 0 1 1][0 1 0 1]]t=[[0 0 0 1]]y=[[ 3.22480024e-04 2.22335711e-02 2.22335711e-02 9.57711099e-01]]=== w1 ===[[-2.49072772 -2.49072772 -2.49072772 -2.49072772][-2.49072772 -2.49072772 -2.49072772 -2.49072772]]=== w2 ===[[-3.373125][-3.373125][-3.373125][-3.373125]] 不但没有训练出合理的结果,而且每一层的参数还都是一样的。 但是如果把每层参数设为不同的固定值呢? X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]]).T y = np.array([[0],[0],[0],[1]]).T net = Net(2,4,1,0.1) net.fc1.w.fill(1) net.fc2.w.fill(0) net.train(X,y) print "=== w1 ===" print net.fc1.w print "=== w2 ===" print net.fc2.w 结果竟然也不错: === Final === X=[[0 0 1 1] [0 1 0 1]] t=[[0 0 0 1]] y=[[ 0.00399349 0.02830098 0.02830098 0.96924181]] === w1 === [[ 2.48265841 2.48265841 2.48265841 2.48265841] [ 2.48265841 2.48265841 2.48265841 2.48265841]] === w2 === [[ 3.231811] [ 3.231811] [ 3.231811] [ 3.231811]] 虽然每层的参数依然相同,但是训练得到了收敛。这又说明了什么呢?关于这个问题有机会再说。 全连接层就这样聊了三期,下回可以换个口味了。 |
|