深度学习入门(四):误差反向传播法

前言

上一篇文章深度学习入门(三):神经网络的学习
中,神经网络参数的学习是通过数值微分求梯度实现的,该方法虽然简单,但也有一个明显的问题就是计算费时。本文介绍一种高效计算参数梯度的方法——误差反向传播法。

注意,误差反向传播法本质上是一种梯度计算的技术,而梯度下降法是一种优化算法

链式法则

什么是链式法则

先看以下的复合函数:
z = t 2 t = x + y z=t^2 \\ t=x+y z=t2t=x+y
回忆以前学过的求复合函数的导数的性质:复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。 因此:
∂ z ∂ x = ∂ z ∂ t ∗ ∂ t ∂ x = 2 t ∗ 1 = 2 ( x + y ) \frac{\partial z}{\partial x} = \frac{\partial z}{\partial t}* \frac{\partial t}{\partial x}=2t*1=2(x+y) xz=tzxt=2t1=2(x+y)

基于以上思想,神经网络中的链式法则就是:将复杂函数的导数分解为多个简单函数的导数的乘积,使得神经网络中权重的多层梯度计算成为可能。

链式法则和计算图

上例的过程,在计算图里表现出来即:
在这里插入图片描述
在这里插入图片描述

反向传播

加法节点的反向传播

在这里插入图片描述

乘法节点的反向传播

在这里插入图片描述
可以看到实现乘法节点的反向传播时,要保留正向传播的信号。

苹果的例子

在这里插入图片描述
上图可以理解为:如果苹果的价格和消费税增加相同的值,那么消费税将对最终的价格产生200倍的影响,苹果的个数将对最终的价格产生110倍的影响,苹果的价格将对最终的价格产生2.2倍的影响。

简单层的实现

乘法层的实现

class MulLayer:
	def __init__(self):
		self.x = None
		self.y = None
	
	def forward(self, x, y):
		self.x = x
		self.y = y
		out = x * y
		
		return out

	def backward(self, dout):
		dx = dout * self.y
		dy = dout * self.x
		
		return dx, dy

apple_price = 100
apple_num = 2
tax_rate = 1.1

# layer
mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_total_price = mul_apple_layer.forward(apple_price, apple_num)
final_price = mul_tax_layer.forward(apple_total_price, tax_rate)

print(final_price) # 220

加法层的实现

class AddLayer:
	def __init__(self):
		self.x = None
		self.y = None
	
	def forward(self, x, y):
		self.x = x
		self.y = y
		out = x + y
		
		return out

	def backward(self, dout):
		dx = dout * 1
		dy = dout * 1
		
		return dx, dy

用上面的乘法层和加法层,实现下面的例子:
在这里插入图片描述

# layer
mul_apple_layer = MulLayer()
mul_orange_layer = MulLayer()
add_apple_orange_layer = AddLayer()
mul_tax_layer = MulLayer()

# forward
apple_price_cur = mul_apple_layer.forward(apple_price, apple_num)
orange_price_cur = mul_orange_layer.forward(orange_price, orange_num)
all_price = add_apple_orange_layer.forward(apple_price_cur, orange_price_cur)
price = mul_tax_layer.forward(all_price, tax_rate)

# backward
d_price = 1
d_all_price, d_tax_rate = mul_tax_layer.barkward(d_price)
d_apple_price_cur, d_orange_price_cur = add_apple_orange_layer.backward(d_all_price)
d_apple_price, d_apple_num = mul_apple_layer.backward(d_apple_price_cur)
d_orange_price, d_orange_num = mul_apple_layer.backward(d_orange_price_cur)

激活函数层的实现

ReLu层

Relu函数的数学表达为:
y = { x x > 0 0 x < = 0 y=\begin{cases} x & x>0 \\ 0 & x<=0 \end{cases} y={x0x>0x<=0
其导数为:
∂ y ∂ x = { 1 x > 0 0 x < = 0 \frac{\partial y}{\partial x}=\begin{cases} 1 & x>0\\ 0 & x<=0 \end{cases} xy={10x>0x<=0

计算图表示如下:
在这里插入图片描述
代码实现如下:

class Relu:
	def __init__(self):
		self.mask = None
	
	def forward(self, x):
		# x<=0 的地方保存为True,其它保存为False
		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层

sigmoid函数的数学表达如下:
y = 1 1 − e − x y = \frac{1}{1-e^{-x}} y=1ex1

图像如下:
在这里插入图片描述
导数的计算图为:
在这里插入图片描述
在这里插入图片描述

class Sigmoid:
	def __init__(self):
		self.out = None
	
	def forward(self, x):
		out = 1 / (1 + np.exp(-x))
		
		return out

	def backward(self, dout):
		dx = dout * (1 - self.out) * self.out
		
		return dx

Affine层/SoftMax层的实现

Affine层

所谓Affine层指的是:在神经网络的正向传播中,进行矩阵乘积变换的处理,称为“仿射转换”,也称为“Affine层”。
在这里插入图片描述

class Affine:
	def __init__(self, W, b):
		self.W = W
		self.b = b
		self.x = None
		self.dW = None
		self.db = None
	
	def forward(self, x):
		self.x = x
		out = np.dot(x, self.W) + self.b
		return

	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)
		
		return dx

Softmax层

回顾一下softmax的函数表示:(如果也有softmax和sigmoid记不清的小伙伴,来个口诀吧:sigmoid看自己,softmax看大家,sigmoid是只关于x的计算,而softmax是将最终的输出结果归一化成一个概率值)
σ ( z ) i = e z i ∑ j = 1 K e z j , 其中  i = 1 , 2 , … , K \sigma(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^{K} e^{z_j}}, \quad \text{其中 } i = 1, 2, \dots, K σ(z)i=j=1Kezjezi,其中 i=1,2,,K

如下图所示,可以看到最终的输出是通过softmax归一化。
在这里插入图片描述
一个问题:为什么神经网络的推理不需要softmax,而学习却需要softmax呢?先思考为什么推理不需要softmax,这个比较简单理解,如果是分类任务,直接选择最后一个Affine输出的值的最大值对应的类作为结果即可,回归任务的输出层无需激活函数。然后思考为什么学习需要softmax,回想前面文章提到的损失函数的计算(交叉熵误差、MSE),都要求输入是一个概率值。

下图展示了softmax层的反向传播的结果,很神奇,我们得到了 y 1 − t 1 y_1-t_1 y1t1, y 2 − t 2 y_2-t_2 y2t2, y 3 − t 3 y_3-t_3 y3t3这样漂亮的结果(正好是标签和输出值的误差),这不是巧合,而是为了得到这样漂亮的结果,特地设计了交叉熵误差这样的损失函数。(此处省去了详细的推导过程)
在这里插入图片描述

def 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)

		return self.loss

	def backward(self, dout=1):
		batch_size = self.t.shape[0]
		dx = (self.y - self.t) / batch_size
		
		return dx

误差反向传播的实现

和上一篇文章两层神经网络的实现一样,这里我们的步骤仍然是:

  1. mini-batch: 从训练数据中随机选择一部分样本;
  2. 计算梯度;
  3. 更新参数;
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()

    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'] = self.layers['Affine1'].dW
        grads['b1'] = self.layers['Affine1'].db
        grads['W2'] = self.layers['Affine2'].dW
        grads['b2'] = self.layers['Affine2'].db

        return grads
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.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)

参考资料

[1] 斋藤康毅. (2018). 深度学习入门:基于Python的理论与实践. 人民邮电出版社.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值