神经网络模型优化 - 梯度
在之前的模型中我们实现了一个二层神经网络,准确度还行,但是训练的时间极长,我们在调参数之前先看看能不能对它的训练速度进行一个提升。
在这个模型中我们使用了梯度下降法,并使用数值方法近似求损失函数关于各个参数的偏导数,一个很直接的想法就是将数值方法换成 基于计算图理论的前后传播方法。这个方法可以有效提高求导数的效率,目前很多成熟的模型求导都是用的这种方法。
因为我们的模型比较简单,所以我们就不怎么改动之前的代码的结构直接修改 gradient 函数。(其实在使用计算图的时候更适合将每一个运算拆开,分别组成模块,使用的时候只需要像积木一样组装就好了,这是由计算图的性质决定的)
先上源码:
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
使用计算图的话就需要一个向前传播,然后保存好相关计算数据,接着向后传播计算梯度。
向前传播很好办,就是之前的预测函数,因为我们在向后传播的时候要用到向前传播的数据,所以不要直接用 predict 函数,还是要在这里面写一遍,
向后传播的时候需要知道一些运算的向后传播导数计算方法,具体的推导过程在这里就不写了,直接给出结果:可以参考https://blog.csdn.net/oBrightLamp/article/details/84333111
对于 softmax with loss 层反向传播的是 softmax 层的输出和监督标签的差分,也就是 。
对于 Affine 层(批处理版本)可以参考下图:
其实 Affine 的意思是仿射变换,是指在几何中,一个向量空间进行一次线性变换并接上一个平移,变换为另一个向量空间。
具体来说,一个集合 的放射变换为:
直接看反向传播,首先最右端传过来一个 例如大小为 , 表示批的大小。这里面的表示方法其实是 numpy 的表示法,但是它的意义和矩阵理论中的一样,但是需要注意的是,numpy 里面的矩阵每一行作为一条信息。例如 np.array([ [1,2,3], [1,2,3] ])
的大小为 。
继续向前传播,遇到了一个加法运算,对于加法运算,直接把上层结果 传递过去,但是还有一个问题,在正向传播的时候,偏置被加到了每一个数据上面,所以各个数据反向传播的值需要汇总一下作为偏置的梯度。
再看另一个方向,另一个方向是一个点乘,这个没什么问题就是乘法的运算图。交换后相乘向下传播。
最后是 sigmoid 层的反向传播,还记得当时在刚接触深度学习的时候老师跟我们讲 sigmoid 说为什么使用这个函数,有一个很重要的原因就是它好求导,当时没什么感觉,直到了解了它的反向传播才理解了这句话,
直接,上层下来一个 输出
为了代码的简洁,我单独写了一个函数,其实在这个地方不单独写也没事
def sigmoid_grad(x):
return (1.0 - sigmoid(x)) * sigmoid(x)
调用的时候传入 a1 然后乘上 da1 就可以。
再看代码就会感觉代码的含义还是很明确的。这样这个求梯度的函数就写完啦,
从时间上来看明显是误差反向传播的方法更快,但是实现起来比较复杂容易出错,我们可以比较一下数值微分和误差反向传播计算的微分,看看是否一致,因为计算机计算精度的原因,一般来说是不一致的,但是不会相差太多,
这种操作被称为梯度确认
我们来测试一下我们写的代码
# coding: utf-8
import numpy as np
from mnist import load_mnist
from two_layer_net import TwoLayerNet
# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
x_batch = x_train[:3]
t_batch = t_train[:3]
grad_numerical = network.numerical_gradient(x_batch, t_batch)
grad_backprop = network.gradient(x_batch, t_batch)
for key in grad_numerical.keys():
diff = np.average( np.abs(grad_backprop[key] - grad_numerical[key]) )
print(key + ":" + str(diff))
从结果上来看确实差的不多:基本上都是在 以下的数量级。