C5 误差反向传播
计算图
构建计算图,从左向右进行计算。(正向传播)
局部计算:无论全局发生了什么,都只能根据与自己相关的信息输出接下来的结果
计算图优点:可以将中间的计算结果全部保存起来。只有这些无法令人信服,可以通过反向传播高效计算倒数。
计算图的反向传播:沿着与正方向相反的方向,乘上局部导数。
简单层实现:
1.简单层 乘法节点、加法节点
根据误差反向传播原则计算出反向传播的表达式,编写代码如下
#乘法层的实现 #有两个共通的方法 forward()和backward(),一个对应正向传播,一个对应反向传播 class MulLayer: def __init__(self): #初始化实例变量x和y,用于保存正向传播时的输入值。 self.x = None self.y = None def forward(self, x, y):#用于接收x和y两个参数,使他们相乘后输出。 self.x = x self.y = y out = x * y return out def backward(self, dout):#将上游传来的导数dout乘以正向传播的翻转值,然后传给下游。 dx = dout * self.y #翻转 x与y dy = dout * self.x return dx, dy class AddLayer: def __init__(self): pass def forward(self, x, y): out = x + y return out def backward(self, dout): dx = dout * 1 dy = dout * 1 return dx, dy
把构建神经网络的“层”实现为一个类
如:负责sigmoid函数的sigmoid,负责矩阵乘积的Affine,以层为单位进行实现。
激活函数层的实现:
RelU
y={x,(x>0);0,(x<=0)}
如果正向传播时输入x大于0,反向传播会将上游的值原封不动传给下游。如果正向传播x的值小于0,反向传播中传给下游的信号将停留在此处。
在神经网络的实现中,一般假定forward()和backward()的参数是Numpy数组
Relu类有实例变量mask,该变量由True/False构成的Numpy数组,会把正向传播时的输入x的元素中小于等于0的地方保存为True,其他地方保存为False。mask变量保存了由True/False构成的Numpy数组
如果正向传播时输入值小于等于0,那么反向传播的值为0
其作用像电路开关一样,正向传播时,有电的话就把开关设置为on,没电流通过就把开关设置为off
开关为on的时候,电流直接通过,开关为off的时候没有电流通过。
class Relu: def __init__(self): self.mask = None def forward(self, x): self.mask = (x <= 0) out = x.copy() out[self.mask] = 0 return out def backward(self, dout): dout[self.mask] = 0 dx = dout return dx
sigmoid层:
正向传播时将输出保存在实例变量out中,反向传播时使用该变量out进行计算
class Sigmoid: def __init__(self): self.out = None def forward(self, x): out = sigmoid(x) # 1/(1+np.exp(-x)) self.out = out return out def backward(self, dout): dx = dout * (1.0 - self.out) * self.out return dx
affine/softmax层实现:
为了计算加权信号总和使用矩阵乘积运算
神经元的加权和:Y=np.dot(X,W)+B
Y经过激活函数转换后传递给下一层。这就是神经网络的正向传播流程。
矩阵乘积运算要求对应维度元素个数一致。
其中,神经网络的正向传播中进行的矩阵乘积运算在几何学领域中被称为“仿射变换”,这里将进行仿射变换的处理实现为Affine层。
各个数据反转的值需要汇总为偏置的元素
偏置的反向传播会对这两个数据的导数进行求和,使用np.sum对第零轴方向上的元素进行求和
class Affine: def __init__(self, W, b): self.W =W self.b = b self.x = None self.original_x_shape = None # 权重和偏置参数的导数 self.dW = None self.db = None def forward(self, x): # 对应张量 self.original_x_shape = x.shape x = x.reshape(x.shape[0], -1) self.x = x out = np.dot(self.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
Softmax-with-loss层:
把输入值正则化后输出
神经网络的学习阶段需要softmax层
通过调整权重参数,使神经网络的输出接近教师标签。因此必须将神经网络的输出与教师标签的误差高效地传递给前面的层
反向传播时,要将传播的值除以批的大小后传递给前面的层是单个数据的误差。
class SoftmaxWithLoss: def __init__(self): self.loss = None #损失 self.y = None # softmax的输出 self.t = None # 监督数据 def forward(self, x, t): self.t = t self.y = softmax(x) self.loss = cross_entropy_error(self.y, self.t) return self.loss def backward(self, dout=1): batch_size = self.t.shape[0] if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况 dx = (self.y - self.t) / batch_size else: dx = self.y.copy() dx[np.arange(batch_size), self.t] -= 1 dx = dx / batch_size return dx
神经网络的处理有推理和学习两个阶段,神经网络的推理通常不使用softmax层,一般会将最后一个Affine层的输出作为识别结果。
神经网络中未被正则化的输出结果有时候会被称为得分,当神经网络的推理只需要给出一个答案的情况下,此时只对得分最大值感兴趣,不需要softmax层。
但是神经网络的学习阶段需要softmax层。
神经网络学习的目的:通过调整权重参数,是的神经网络的输出接近教师标签,必须把神经网络的输出与教师标签的误差高效地传递给前面的层。
反向传播是,要将传播的值除以批的大小,传递给前面的层是单个数据的误差。
误差反向传播的实现
神经学习网络step
1、从训练数据中随机选择一部分数据
2、计算梯度,计算损失函数中关于各个权重参数的梯度
3、更新参数:将权重参数沿着梯度方向进行微小的更新
4、重复123
其中,误差反向传播法会在2中出现。误差反向传播法可以快速、高效地计算梯度
神经网络的实现
实例变量与说明:
1params:保存神经网络参数的字典型变量,params【w1】是第一层的权重,b1是第一层的偏置;params【w2】是第二层的权重,params【b2】是第二层的偏置
2layers:保存神经网络的层的有序字典型变量。通过layers【affine1】、layers【'Relu1'】、layers【'Affine2'】的形式,有序字典保存各个层
3lastLayer:神经网络的最后一层,本层为SoftmaxWithLoss
TwoLayerNet方法
通过使用层,获得识别结果的处理predict()和计算梯度的处理gradient()只需要通过层之间的传递就可以完成。
# coding: utf-8 import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np from common.layers import * from common.gradient import numerical_gradient from collections import OrderedDict class TwoLayerNet: # 初始化,从头开始依次是输入层的神经元数、隐藏层的神经元树、输出层的神经元数 def __init__(self, input_size, hidden_size, output_size, weight_init_std = 0.01): # 初始化权重 self.params = {} self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) self.params['b1'] = np.zeros(hidden_size) self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size) self.params['b2'] = np.zeros(output_size) # 生成层 self.layers = OrderedDict() self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1']) self.layers['Relu1'] = Relu() self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2']) self.lastLayer = SoftmaxWithLoss() # 进行识别推理,参数x是图像数据 def predict(self, x): for layer in self.layers.values(): x = layer.forward(x) return x # x:输入数据, t:监督数据 计算损失函数的值 def loss(self, x, t): y = self.predict(x) return self.lastLayer.forward(y, t) # 计算识别精度 def accuracy(self, x, t): y = self.predict(x) y = np.argmax(y, axis=1) if t.ndim != 1 : t = np.argmax(t, axis=1) 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 = {} 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 = {} grads['W1'], grads['b1'] = self.layers['Affine1'].dW, self.layers['Affine1'].db grads['W2'], grads['b2'] = self.layers['Affine2'].dW, self.layers['Affine2'].db return grads
梯度确认:通过比较数值微分的结果和误差反向传播法的结果,以确认误差反向传播法的实现是否正确。
# coding: utf-8 import sys, os sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定 import numpy as np from dataset.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))
通过使用误差反向传播法学习:
# coding: utf-8 import sys, os sys.path.append(os.pardir) import numpy as np from dataset.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) iters_num = 10000 train_size = x_train.shape[0] batch_size = 100 learning_rate = 0.1 train_loss_list = [] train_acc_list = [] test_acc_list = [] iter_per_epoch = max(train_size / batch_size, 1) for i in range(iters_num): batch_mask = np.random.choice(train_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) 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)