5.6 误差反向传播(基于计算图的代码实现)
这一章解的代码实现有两种途径:
- 第一种:直接对求解梯度的子函数进行修改,其他部分与上一章一样。
- 第二种:对两层神经网络的类进行修改,把神经网络分出层次来。
这两种方法均有优缺点:
- 第一种方法中,直接对求解梯度函数进行修改,操作比较简答,但是内容比较复杂,如果网络的层数再加深一下,会比较难写出对应的求解梯度函数。
- 第二种方法改的地方比较多,但是层次结构简单,非常适合于拓展到更加深层次的网络结构中。
5.6.1 方法一的实现
这种方法只是把求解梯度的子函数进行改变,这里还是以一个两层网络为例,列出了修改前后的子函数。
(修改前)数值微分法求梯度:
def numerical_gradient(self,x,t):
loss_W=lambda W:self.loss(x,t) #看不懂,什么意思
grads={} #定义参数的梯度信息,存取权重参数的梯度信息值
#求取四个权重参数的梯度信息值,并且存入grad中
grads['W1']=numerical_gradient(loss_W,self.params['W1'])
grads['b1']=numerical_gradient(loss_W,self.params['b1'])
grads['W2']=numerical_gradient(loss_W,self.params['W2'])
grads['b2']=numerical_gradient(loss_W,self.params['b2'])
return grads
(修改后)误差反向转播求梯度:
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
5.6.2 方法二的实现
对两层神经网络的类,进行了比较大的修改。定义了很多类型的激活函数层,后面的预测用前向传播表示,梯度的求取用误差反向传播结果表示,层与层直接的建构一目了然,方便后面结构的扩增。
针对上一篇博客(数值微分法)minist神经网络学习.中实现神经网络的代码,这里修改的地方有5处:
- 要额外写一个用于调用激活函数层的文件layer,方便神经网络类的调用。
- 在__init__()子函数中,定义了网络中用到的层
- 在子函数predict()中,直接对一次对每一层求取前向传播的值,而不用自己一个一个写结果。
- 在loss()子函数中,损失函数就不再用cross_entropy_error()函数求取了,而是直接用SoftmaxWithLoss层解决。
- 最后一个就是求解梯度函数的改变,这里变化比较大。
5.6.3 方法二的实现代码
实现这个文件有三个主要文件:
- TLN_main.py :主函数与上一个博客一样,没有变化
- TLN_function.py:存放一些激活函数,也没有变化
- Layer.py: 新增文件,用于存放激活函数层(基于计算图)函数
- two_layer_net1.py :变化比较多,刚刚已经叙述了变化的地方(下面代码中也标注出来了)
5.6.3.1 文件一:TLN_main.py
文件一:TLN_main.py
'''#ini-batch 进行算法的实现'''
#相关库的调用
import time
import numpy as np
import matplotlib.pyplot as plt
import sys,os
sys.path.append(os.pardir) #前面这两行代码,纯粹是为了跨文件夹调用文档中的函数
from dataset.mnist import load_mnist #调用加载mnist数据集的函数
from two_layer_net1 import TwoLayerNet #调用编辑的两层神经网络构成的类
#from two_layer_net import TwoLayerNet
#手写数据集mnist的数据获取
(x_train,t_train),(x_test,t_test)=load_mnist(flatten=True,normalize=True,one_hot_label=True)
#load_mnist以(训练图片,训练标签),(测试图片,测试标签)的形式读入数据集
#normalize:是否将输入图片正规化为0.0-1.0的值,这里没有正规化,图片像素仍然为0-255
#flatten:是否展开输入图像(变为一维数组),这里是用一维数组显示的
#one_hot_label:仅正确标签为1,其他均为0的数组,形如[0,0,0,1,0,0],如果为False,则仅保存2,7等正确解的标签
#平均每一个epoch重复的次数
#超参数的定义
iters_num=10000
train_size=x_train.shape[0] #总训练集的大小
batch_size=100 #mini_batch的大小
learning_rate=0.5 #学习率,也相当于是步长
network= TwoLayerNet(input_size=784,hidden_size=100,output_size=10)
#给两层神经网络的基本参数进行定义:
#神经网络:两层,输入为784个,隐藏神经元为50个,输出为10个。
#定义一些计算损失函数和精确度值的矩阵
train_loss_list=[] #训练损失函数
train_acc_list=[] #训练数据的准确率
test_acc_list=[] #测试数据的准确率
iter_per_epoch=max(train_size/batch_size,1)
#主函数
start = time.clock() #计时开始
for i in range(iters_num):
#获取随机mini——batch
batch_mask=np.random.choice(train_size,batch_size) #从总的训练集数中选取batch_size个随机数,不带重复的
x_batch=x_train[batch_mask]
t_batch=t_train[batch_mask]
#计算梯度
#grad=network.numerical_gradient(x_batch,t_batch)
#采用误差反向传播法:
grad=network.gradient(x_batch,t_batch)
#更新参数
for key in ('W1','b1','W2','b2'):
network.params[key] -= learning_rate*grad[key]
#记录学习过程
loss=network.loss(x_batch,t_batch)
train_loss_list.append(loss) #不懂代码意思
#每经过一个epoch(参数更新后),就对所有的训练数据和测试数据计算识别精度
if i % iter_per_epoch==0:
#训练数据和测试数据的记录
train_acc=network.accuracy(x_train,t_train)
test_acc=network.accuracy(x_test,t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
# print('train_acc,test_acc| '+str(train_acc)+' , '+test_acc)
print('train_acc,test_acc|', train_acc,',',test_acc)
#增加一个计时功能
end = time.clock() #计时结束
print ('Running time:',str(end-start)) #显示所耗时间
#绘制图形
x = np.arange(len(train_acc_list)) #绘制图像的最主要的三行,变量与因变量
plt.plot(x, train_acc_list, label='train acc', marker='o')
plt.plot(x, test_acc_list, label='test acc', marker='x',linestyle='--')
plt.xlabel("epochs") #显示横纵坐标的标签
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right') #显示图例,在右下角
plt.savefig('./test2.jpg') #保存显示的图片
plt.show()
5.6.3.2文件二:TLN_function.py
文件二:TLN_function.py
#TwoLayerNet中的子函数
import numpy as np
#求梯度函数
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x)
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi_index
tmp_val = x[idx]
x[idx] = float(tmp_val) + h
fxh1 = f(x) # f(x+h)
x[idx] = tmp_val - h
fxh2 = f(x) # f(x-h)
grad[idx] = (fxh1 - fxh2) / (2*h)
x[idx] = tmp_val # 还原值
it.iternext()
return grad
#激活函数(层与层之间使用)
def sigmoid(x):
return 1 / (1 + np.exp(-x))
#激活函数(输出层使用)
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # 溢出对策
return np.exp(x) / np.sum(np.exp(x))
#损失函数的求取
def cross_entropy_error(y, t):
if y.ndim == 1:
t = t.reshape(1, t.size)
y = y.reshape(1, y.size)
# 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
if t.size == y.size:
t = t.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
#误差反向传播算法要用的子函数
def sigmoid_grad(x):
return (1.0 - sigmoid(x)) * sigmoid(x)
5.6.3.3 文件三:Layer.py
文件三:Layer.py
#激活层的实现,四个(ReLU,Sigmoid,Affine,SoftmaxWithLoss()
import numpy as np
from TLN_function import *
class ReLU:
def __init__(self):
self.mask=None
#前向传播
def forward(self,x):
self.mask=(x<=0) #mask一个逻辑数组,其中x<=的值都是True
out=x.copy() #out与x相等
out[self.mask]=0 #对mask中True中的值置0
#相当于完成了ReLU的功能,小于等于0时为0;大于0时,保持原值
return out
#反向传播
def backward(self,dout):
dout[self.mask]=0
df=dout
return df
class Sigmoid():
def __init__(self):
self.out=None
def forward(self,x):
out=1 / (1 + np.exp(-x))
self.out=out
return out
def backward(self,dout):
return dout*(1.0-self.out)*self.out
class Affine(): #把重要一点的参数全部变为实例变量
def __init__(self,W,b):
self.W=W
self.b=b
self.x=None
self.original_x_shapr=None ####定义x的形状
self.dW=None
self.db=None
def forward(self,x):
self.original_x_shape =x.shape #####保存x的形状
x=x.reshape(x.shape[0],-1) #####恢复x的形状
self.x=x
out=np.dot(x,self.W)+self.b
return out
def backward(self,dout):
dx=np.dot(dout,self.W.T)
self.dW=np.dot(self.x.T,dout)
self.db=np.sum(dout,axis=0)
dx=dx.reshape(*self.original_x_shape) #还原输入数据的形状(对应张量)
return dx
class SoftmaxWithLoss():
def __init__(self):
self.loss=None #损失
self.y=None #softmax的输出
self.t=None #监督数据(one—hot vector)
def forward(self,x,t):
self.t=t
self.y=softmax(x)
self.loss=cross_entropy_error(self.y,self.t) #运用前面定义的softmax和cross_entropy_error求取误差值
return self.loss
def backward(self,dout=1):
batch_size=self.t.shape[0]
if self.t.size ==self.y.size: ####监督数据是one-hot-vector情况(加了一个判断是否为one-hot-vector情况)
dx=(self.y-self.t)/batch_size #梯度 除以批的原因:前面的误差求解的是一批
# 中误差的总和,这里除以batch——size,是
# 为了传递给前面的层的是单个数据的误差
# 这里的梯度公式是推导出来的
else: ###
dx=self.y.copy() ###
dx[np.arange(batch_size),self.t] -= 1
dx= dx/batch_size
return dx
5.6.3.4 文件四:two_layer_net1.py
文件四:two_layer_net1.py
#应用误差反向传播的两层网络结构
#库和函数的导入
import sys,os
import numpy as np
sys.path.append(os.pardir) #前面这两行代码,纯粹是为了跨文件夹调用文档中的函数
from TLN_function import * #调用TLN_function文件中所有子函数
from Layer import * #调用Layer文件中所有子函数 新增#######
from collections import OrderedDict #从内带库中调用OrderdDict这个有序字典 新增#######
class TwoLayerNet:
def __init__(self,input_size,hidden_size,output_size,weight_init_std=0.01):
#初始化权重,并且定义几个实例变量,(也就是在类中的局部变量)
self.params={} #初始化实例变量params,里面有四个变量,分别是W1 ,W2,b1,b2
#W1和W2使用符合高斯分布的随机数进行初始化
self.params['W1']=weight_init_std*np.random.randn(input_size,hidden_size)
self.params['W2']=weight_init_std*np.random.randn(hidden_size, output_size)
#b1和b2使用0进行初始化
self.params['b1']=np.zeros(hidden_size) #初始值全部设为0
self.params['b2']=np.zeros(output_size) #初始值全部设为0
#生成层 新增#######
self.layers=OrderedDict()
self.layers['Affinel'] = Affine(self.params['W1'],self.params['b1'])
self.layers['Relu1'] = ReLU()
self.layers['Affine2'] = Affine(self.params['W2'],self.params['b2'])
self.lastLayer = SoftmaxWithLoss()
def predict(self,x): #修改#######
for layer in self.layers.values(): #value函数是对字典变量进行操作,求取字典变量的值
x=layer.forward(x)
return x
'''
def predict(self,x):
#赋值变量
W1,W2=self.params['W1'],self.params['W2']
b1,b2=self.params['b1'],self.params['b2']
#两层网络的前向传播算法
a1=np.dot(x,W1)+b1
z1=sigmoid(a1)
a2=np.dot(z1,W2)+b2
y=softmax(a2)
return y
'''
#x:图像数据 ;t:正确解标签
#损失函数(交叉熵误差函数u,求出损失值)
def loss(self,x,t): #修改
y=self.predict(x)
#loss_x=cross_entropy_error(y,t) #修改,两个东西是一个意思
loss_x=self.lastLayer.forward(y,t)
return loss_x
#计算准确率函数
def accuracy(self,x,t):
y=self.predict(x)
y=np.argmax(y,axis=1) #求出y中每一列中的最大值
t=np.argmax(t,axis=1) #求出t中每一列中的最大值
accuracy=np.sum(y==t)/float(x.shape[0]) #强制类型转换
return accuracy
#x:图像数据 ;t:正确解标签
#计算权重参数的梯度
#两种求解梯度的算法
#负梯度法求解权重参数梯度值
def numerical_gradient(self,x,t):
loss_W=lambda W:self.loss(x,t) #看不懂,什么意思
grads={} #定义参数的梯度信息,存取权重参数的梯度信息值
#求取四个权重参数的梯度信息值,并且存入grad中
grads['W1']=numerical_gradient(loss_W,self.params['W1'])
grads['b1']=numerical_gradient(loss_W,self.params['b1'])
grads['W2']=numerical_gradient(loss_W,self.params['W2'])
grads['b2']=numerical_gradient(loss_W,self.params['b2'])
return grads
#误差反向传播法求解梯度值
def gradient(self, x, t): #比较大的修改地方
#forward
self.loss(x,t)
#backward
dout=1
dout=self.lastLayer.backward(dout)
layers=list(self.layers.values())
layers.reverse() #对于这个字典向量进行逆转
for layer in layers:
dout=layer.backward(dout)
#设定
grads={} #定义参数的梯度信息,存取权重参数的梯度信息值
#求取四个权重参数的梯度信息值,并且存入grad中
#更新后的值存入一个矩阵
grads['W1']=self.layers['Affinel'].dW
grads['b1']=self.layers['Affinel'].db
grads['W2']=self.layers['Affine2'].dW
grads['b2']=self.layers['Affine2'].db
return grads
5.6.3.5. 运行结果
>>>start = time.clock() #计时开始
>>>train_acc,test_acc| 0.16121666666666667 , 0.1615
>>>train_acc,test_acc| 0.9480833333333333 , 0.9461
>>>train_acc,test_acc| 0.9716 , 0.9669
>>>train_acc,test_acc| 0.9767333333333333 , 0.972
>>>,test_acc| 0.9813333333333333 , 0.9727
>>>train_acc,test_acc| 0.9871333333333333 , 0.973
>>>train_acc,test_acc| 0.9903833333333333 , 0.9748
>>>train_acc,test_acc| 0.9923666666666666 , 0.9782
>>>,test_acc| 0.9934833333333334 , 0.9767
>>>train_acc,test_acc| 0.99525 , 0.9784
>>>train_acc,test_acc| 0.9958666666666667 , 0.978
>>>train_acc,test_acc| 0.9983833333333333 , 0.9794
>>>Running time: 44.66675650000002 #总耗时
图片输出:
![](https://i-blog.csdnimg.cn/blog_migrate/023e7bb6f8a3da25ba826a0b3f270dc6.png)
链接: 代码运行压缩包:.
提取码:nuh9
参考书籍:
1.《深度学习–基于python的理论与实现》