【DL学习笔记04】深度学习入门——基于Python的理论与实现(ch05: 误差反向传播法)

 

目录

1. 计算图

用计算图求解

局部计算

为何用计算图解体(优点)

2. 链式法则

3. 反向传播

4.简单层的实现

乘法层(MulLayer)

加法层(AddLayer)

5. 激活函数层的实现

6. Affine/Softmax层的实现

Affine层

批版本的Affine层

Softmax-with-Loss层

7. 误差反向传播法的实现

梯度确认

使用误差反向传播法的学习


本章将基于计算图(computational graph)学习一个能够高效计算权重参数的梯度的方法——误差反向传播法

1. 计算图

计算图将计算过程用图形表示出来,图形指数据结构图,通过多个节点和边表示

用计算图求解

  • 示例:

 

  • 流程:

    1. 构建计算图
    2. 在计算图上,从左向右进行计算
  • 正向传播(forward propagation):从左向右进行计算,从计算图出发点到结束点的传播

  • 相应的反向的传播称为反向传播(backward propagation)

局部计算

  • 计算图的特征是可以通过传递”局部计算“获得最终结果
  • 各个节点处的计算都是局部计算,只需要进行与自己有关的计算,不用考虑全局
  • 因此,计算图可以集中精力于局部计算
  • 例子:组装汽车是一个复杂的工作,但流水线上每个工人的工作都是简单的

为何用计算图解体(优点)

  • 局部计算→简化问题
  • 中间的计算结果可被全部保存
  • 最大原因:可以通过反向传播高效计算导数

2. 链式法则

传递局部导数的原理基于链式法则(chain rule)

  • 计算图的反向传播:

    • 沿着与正方向相反的方向,乘上局部导数
    • 正向:x\rightarrow y
    • 反向:E\rightarrow E\frac{\partial y}{\partial x}
  • 链式法则:

    • 如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示
  • 链式法则和计算图:

     

3. 反向传播

  • 加法节点的反向传播:将上游的值原封不动输出到下游
    • 因为导数为1
  • 乘法节点的反向传播:将上游的值乘以正向传播时输入信号的”翻转值“再传递给下游
    • 因此需要保存正向传播的输入信号

4.简单层的实现

  • “层”是神经网络中功能的单位
  • 层的实现有两个共通的方法(接口):forward() 和 backward()

乘法层(MulLayer)

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

加法层(AddLayer)

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

5. 激活函数层的实现

  • RELU
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
class Sigmoid:
    def __init__(self):
        self.out = None

    def forward(self, x):
        out = sigmoid(x)
        self.out = out
        return out

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

        return dx

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

Softmax-with-Loss层

  • softmax函数会将输入值正规化之后再输出,通常在学习阶段使用,不在推理阶段使用

  • 同时考虑作为损失函数的交叉熵误差(cross entropy error)

  • 简易版计算图

  • 反向传播得到的结果时输出和监督标签的差分,直接表示了误差
  • 神经网络的方向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质
  • 实现

    class SoftmaxWithLoss:
        def __init__(self):
            self.loss = None  # 损失
            self.y = None  # softmax的输出
            self.t = None  # 监督数据(one-hot向量)
    
        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
    

7. 误差反向传播法的实现

from collections import OrderedDict

import numpy as np

from ch4_trainNeuralnet.gradient_method import numerical_gradient
from ch5_back_propagation.common_layers import Affine, Relu, SoftmaxWithLoss

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # params 保存神经网络的参数
        # 权重:通过randn(i, h),得到一组i*h的符合标准正态分布的随机数
        # 偏置:通过zeros(h),得到一个全为0的矩阵
        self.params = {'W1': weight_init_std * np.random.randn(input_size, hidden_size),
                       'b1': np.zeros(hidden_size),
                       'W2': weight_init_std * np.random.randn(hidden_size, output_size),
                       'b2': np.zeros(output_size)}

        # 生成层
        # 用有序字典OrderedDict保存神经网络的层
        # 因此正向传播只需按顺序调用,反向传播用reverse()按相反的顺序调用即可
        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:输入数据
    # 直接按顺序调用字典中各层的forward()
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x

    # 计算损失值,x:输入数据,t:监督数据
    # 调用SoftmaxWithLoss层
    def loss(self, x, t):
        y = self.predict(x)
        return self.lastLyer.forward(y, t)

    # 计算识别精度, x:输入数据,t:监督数据
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)  # 返回行最大值的索引的数组,长度为列数
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy

    # 数值微分计算权重参数的梯度
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)  # def loss_W(x):...
        # grads 保存梯度
        grads = {'W1': numerical_gradient(loss_W, self.params['W1']),
                 'b1': numerical_gradient(loss_W, self.params['b1']),
                 'W2': numerical_gradient(loss_W, self.params['W2']),
                 '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)
        # 先转换成list,然后用reverse()反转
        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 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))

使用误差反向传播法的学习

和上一章的实现相比,就是计算梯度时使用了误差反向传播法

import numpy as np
from matplotlib import pyplot as plt

from ch4_trainNeuralnet.two_layer_net import TwoLayerNet
from mnist import load_mnist

# 载入数据集,并将图像的像素值正规化为0.0~1.0,标签采用one-hot表示
# (训练图象,训练标签),(测试图象,测试标签)
(x_train, t_train), (x_test, t_test) = \\
    load_mnist(normalize=True, one_hot_label=True)

# 超参数
iters_num = 10000  # 迭代次数
train_size = x_train.shape[0]  # 训练数据条数
batch_size = 100  # 批数量
learing_rate = 0.1  # 学习率
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

# 记录学习过程中的损失值、训练数据的识别精度、测试数据的识别精度
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 平均每个epoch的重复次数
iter_per_epoch = max(train_size / batch_size, 1)

for i in range(iters_num):
    print("第", i, "次迭代")
    # 获取mini-batch
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    **# 计算梯度
    # grad = network.numerical_grandient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)**
    # 更新参数,正方向更新梯度为负的权重,反方向更新梯度为正的权重
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learing_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) + ", " + str(test_acc))

# 绘制损失函数值的图像
print("绘制损失函数值的图像")
x = np.arange(iters_num)
plt.plot(x, train_loss_list)
plt.xlabel("iters_num")  # x轴标签
plt.ylabel("loss")  # y轴标签
plt.show()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值