太原做网站软件,辽宁教育网站建设费用,中通建设计院网站,济南天桥区网站建设公司深度学习#xff08;鱼书#xff09;day08–误差反向传播#xff08;后三节#xff09;一、激活函数层的实现
这里#xff0c;我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。ReLU层
激活函数ReLU#xff08;Rectified Linear Unit#xff…深度学习鱼书day08–误差反向传播后三节一、激活函数层的实现
这里我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。ReLU层
激活函数ReLURectified Linear Unit由下式表示。
y{x(x0)0(x≤0)
y
\begin{cases}
x (x 0) \\
0 (x \leq 0)
\end{cases}
y{x0(x0)(x≤0)
y关于x的导数
∂y∂x{1(x0)0(x≤0)
\frac{\partial y}{\partial x}
\begin{cases}
1 (x 0) \\
0 (x \leq 0)
\end{cases}
∂x∂y{10(x0)(x≤0)class ReLU:def __init__(self):self.mask Nonedef forward(self, x):self.mask (x 0)out x.copy()out[self.mask] 0def backward(self, dout):dout[self.mask] 0dx doutreturn dx这个变量mask是由True/False构成的NumPy数组它会把正向传播时的输入x的元素中小于等于0的地方保存为True其他地方大于0的元素保存为False。
如果正向传播时的输入值小于等于0则反向传播的值为0。因此反向传播中会使用正向传播时保存的mask将从上游传来的dout的mask中的元素为True的地方设为0。
ReLU层的作用就像电路中的开关一样。正向传播时有电流通过的话就将开关设为 ON没有电流通过的话就将开关设为 OFF。反向传播时开关为ON的话电流会直接通过开关为OFF的话则不会有电流通过。Sigmoid层正向传播反向传播
步骤1
“/”节点表示
它的导数可以解析性地表示为下式。反向传播时会将上游的值乘以-y 2 正向传播的输出的平方乘以−1后的值后再传给下游。计算图如下所示。步骤2
“”节点将上游的值原封不动地传给下游。计算图如下所示。步骤3
“exp”节点表示y exp(x)它的导数由下式表示。上游的值乘以正向传播时的输出这个例子中是exp(−x)后再传给下游。步骤4
“×”节点将正向传播时的值翻转后做乘法运算。因此这里要乘以−1。集约化的“sigmoid”节点。可以不用在意Sigmoid层中琐碎的细节而只需要专注它的输入和输出这一点也很重要。进一步整理如下因此Sigmoid层的反向传播只根据正向传播的输出就能计算出来。代码实现class Sigmoid:def __init__(self):self.out Nonedef forward(self, x):out 1 / (1 np.exp(-x))self.out outreturn outdef backward(self, dout):dx dout * (1.0 - self.out) * self.outreturn dx二、Affine/Softmax层的实现Affine层
神经网络的正向传播中进行的矩阵乘积运算在几何学领域被称为“仿射变换”。因此这里将进行仿射变换的处理实现为“Affine层”。
现在将这里进行的求矩阵的乘积与偏置的和的运算用计算图表示出来。将乘积运算用“dot”节点表示的话则np.dot(X, W) B的运算可用下图所示的计算图表示出来。在各个变量的上方标记了它们的形状计算图上显示了X的形状为(2,)X·W的形状为(3,)。现在我们来考虑图5-24的计算图的反向传播。以矩阵为对象的反向传播按矩阵的各个元素进行计算时步骤和以标量为对象的计算图相同。实际写一下的话可以得到下式尝试写出计算图的反向传播如图所示批版本的Affine层
前面介绍的Affi ne层的输入X是以单个数据为对象的。现在我们考虑N个数据一起进行正向传播的情况也就是批版本的Affine层。先给出批版本的Affine层的计算图如图所示。正向传播时偏置会被加到每一个数据第1个、第2个……上。因此反向传播时各个数据的反向传播的值需要汇总为偏置的元素。用代码表示的话如下所示。这里使用了np.sum()对第0轴以数据为单位的轴axis0方向上的元素进行求和。
class Affine:def __init__(self, W, b):self.W Wself.b bself.x Noneself.dW Noneself.db Nonedef forward(self, x):self.x xout np.dot(x, self.W) self.breturn outdef backward(self, dout):dx np.dot(dout, self.W.T)self.dW np.dot(self.x.T, dout)self.db np.sum(dout, axis0)return dx输入数据为张量四维数据的情况:
class Affine:def __init__(self, W, b):self.W Wself.b bself.x Noneself.original_x_shape None# 权重和偏置参数的导数self.dW Noneself.db Nonedef forward(self, x):# 对应张量self.original_x_shape x.shapex x.reshape(x.shape[0], -1)self.x xout np.dot(self.x, self.W) self.breturn outdef backward(self, dout):dx np.dot(dout, self.W.T)self.dW np.dot(self.x.T, dout)self.db np.sum(dout, axis0)dx dx.reshape(*self.original_x_shape) # 还原输入数据的形状对应张量return dxSoftmax-with-Loss 层
softmax函数会将输入值正规化之后再输出。比如手写数字识别时Softmax层的输出如图所示。神经网络中进行的处理有推理inference和学习两个阶段。神经网络的推理通常不使用 Softmax层。比如用图 5-28的网络进行推理时会将最后一个 Affine层的输出作为识别结果。神经网络中未被正规化的输出结果有时被称为“得分”。也就是说当神经网络的推理只需要给出一个答案的情况下因为此时只对得分最大值感兴趣所以不需要 Softmax层。不过神经网络的学习阶段则需要 Softmax层。
这里也包含作为损失函数的交叉熵误差cross entropy error所以称为“Softmax-with-Loss层”。Softmax-with-Loss层Softmax函数和交叉熵误差的计算图如图所示。这里假设要进行3类分类从前面的层接收3个输入得分。Softmax层将输入a1, a2, a3正规化输出y1, y2, y3。Cross Entropy Error层接收Softmax的输出y1, y2, y3和教师标签t1, t2, t3从这些数据中输出损失L。注意反向传播的结果Softmax层的反向传播得到了y1 − t1, y2 − t2, y3 − t3这样“漂亮”的结果。由于y1, y2, y3是Softmax层的输出t1, t2, t3是监督数据所以y1 − t1, y2 − t2, y3 − t3是Softmax层的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层这是神经网络学习中的重要性质。考虑一个具体的例子比如教师标签是 0, 1, 0Softmax层的输出是 (0.3, 0.2, 0.5)的情形。因为正确解标签处的概率是0.220%这个时候的神经网络未能进行正确的识别。此时Softmax层的反向传播传递的是(0.3, −0.8, 0.5)这样一个大的误差。因为这个大的误差会向前面的层传播所以Softmax层前面的层会从这个大的误差中学习到“大”的内容。回归问题中输出层使用“恒等函数”损失函数使用“平方和误差”也是出于同样的理由。也就是说使用“平方和误差”作为“恒等函数”的损失函数反向传播才能得到y1 −t1, y2 − t2, y3 − t3这样“漂亮”的结果。再举一个例子比如思考教师标签是 (0, 1, 0)Softmax层的输出是 (0.01, 0.99, 0)的情形这个神经网络识别得相当准确。此时Softmax层的反向传播传递的是 (0.01, −0.01, 0)这样一个小的误差。这个小的误差也会向前面的层传播因为误差很小所以Softmax层前面的层学到的内容也很“小”。Softmax-with-Loss层的实现
class SoftmaxWithLoss:def __init__(self):self.loss Noneself.y Noneself.t Nonedef forward(self, x, t):self.t tself.y softmax(x)self.loss cross_entropy_error(self.y, self.t)return self.lossdef backward(self, dout1):batch_size self.t.shape[0]if self.t.size self.y.size: # 监督数据是one-hot-vector的情况dx (self.y - self.t) / batch_sizeelse:dx self.y.copy()dx[np.arange(batch_size), self.t] - 1dx dx / batch_sizereturn dx注意反向传播时将要传播的值除以批的大小batch_size后传递给前面的层的是单个数据的误差。
三、误差反向传播法的实现
通过像组装乐高积木一样组装上一节中实现的层可以构建神经网络。本节我们将通过组装已经实现的层来构建神经网络。
神经网络学习的全貌图步骤2中之前我们利用数值微分求得了这个梯度。数值微分虽然实现简单但是计算要耗费较多的时间。和需要花费较多时间的数值微分不同误差反向传播法可以快速高效地计算梯度。对应误差反向传播法的神经网络的实现
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 OrderedDictclass 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()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, axis1)if t.ndim ! 1 : t np.argmax(t, axis1)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 gradsdef gradient(self, x, t):# forwardself.loss(x, t)# backwarddout 1dout 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].dbgrads[W2], grads[b2] self.layers[Affine2].dW, self.layers[Affine2].dbreturn gradsOrderedDict是有序字典“有序”是指它可以记住向字典里添加元素的顺序。因此神经网络的正向传播只需按照添加元素的顺序调用各层的forward()方法就可以完成处理而反向传播只需要按照相反的顺序调用各层即可。因为Affine层和ReLU层的内部会正确处理正向传播和反向传播所以这里要做的事情仅仅是以正确的顺序连接各层再按顺序或者逆序调用各层。误差反向传播法的梯度确认
在确认误差反向传播法的实现是否正确时是需要用到数值微分的。数值微分的优点是实现简单一般情况下不太容易出错。而误差反向传播法的实现很复杂容易出错。所以经常会比较数值微分的结果和误差反向传播法的结果以确认误差反向传播法的实现是否正确。确认数值微分求出的梯度结果和误差反向传播法求出的结果是否一致严格地讲是非常相近的操作称为梯度确认gradient check。
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(normalizeTrue, one_hot_labelTrue)network TwoLayerNet(input_size784, hidden_size50, output_size10)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(normalizeTrue, one_hot_labelTrue)network TwoLayerNet(input_size784, hidden_size50, output_size10)iters_num 10000
train_size x_train.shape[0]
batch_size 100
learning_rate 0.1train_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)