Python数据科学程序员

深度学习入门--误差反向传播法

2019-03-31  本文已影响5人  爱吃西瓜的番茄酱

上一节我们学习的是通过数值微分来计算损失函数关于权重参数的梯度,这种方法也称为:随机梯度下降法(SGD)

随机梯度下降法的优点是比较简单,容易理解,缺点是计算比较耗费时间。还有一种计算损失函数关于权重参数的梯度的方法:误差反向传播法

误差反向传播法的优点是计算速度快,能够高效地计算权重参数的梯度;缺点是不太容易理解。

关于误差反向传播法的理解,大概有两种方式:

基于数学式的方式过于复杂,一大堆数学式看着头晕。而计算图则十分便于理解。

计算图

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

一个简单的计算图的示例:

上面这个计算图表示的是:购买2个10元的苹果,消费税是10%,计算其总金额。

用计算图求解问题的一般步骤如下:

这种计算方法称为正向传播:即是从计算图出发点到结束点的传播。

既然有正向传播,那就有反向传播。计算图的反向传播就是理解误差反向传播法的关键。

计算图的特征是:可以通过传递“局部计算”来获得最终结果

计算图解题的优点:

链式法则

计算图的反向传播可以高效计算局部导数的原理是基于链式法则的。链式法则的具体内容是大学本科【高等数学】里面的知识,这里不再赘述。

链式法则和计算图的反向传播有着密切的关系,因为,反向传播的计算顺序是:先将节点的输入信号乘以节点的偏导数,然后再传递给下一个节点。

计算图的反向传播的一个简单的示例:

反向传播

反向传播有两种类型:

加法节点的反向传播:

加法节点的反向传播是将上游的值原封不动的输出到下游。

乘法节点的反向传播

乘法的反向传播会将上游的值乘以正向传播时的输入信号的“互换值”。

互换值表示一种互换关系,比如正向传播时,输入信号为x和y。则输入信号x的互换值为y;输入信号y的互换值为x。

如上所示,乘法节点的反向传播需要正向传播时的输入信号值,因此,实现乘法节点的反向传播时,要保存正向的输入信号。

加法层的Python实现

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

乘法层的Python实现

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

激活函数ReLU的计算图的Python实现

在神经网络的层的实现中,一般假定forward()backward()的参数是numpy数组

class ReLU:
    def __init__(self):
        self.mask = None

    def forward(self, x):
        self.mask = (x<=0)  # 当x小于0时,mask为true
        out = x.copy()
        out[self.mask] = 0  # 将mask中为true的地方设为0
        return out

    def backward(self, dout):
        dout[self.mask] = 0
        dx = dout
        return dx

激活函数Sigmoid的计算图的Python实现

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):
        dx = dout * (1.0 - self.out) * self.out
        return dx

Affine层的计算图的Python实现

在神经网络的正向传播中,为了计算加权信号的总和,我们使用了矩阵的乘积运算:np.dot()。这样一来,神经元的加权和可以用Y = np.dot(X, W) + B计算出来。这种计算所在的层称为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.w.T表示矩阵W的转置
        self.dw = np.dot(self.x.T, dout)
        self.db = np.dot(dout, axis=0)  # 列方向
        return dx

Softmax-with-Loss层的计算图的Python实现

损失函数有两种:

输出层的激活函数也有两种:

一般情况下,使用交叉熵误差作为softmax函数的损失函数;使用平方和误差作为恒等函数的损失函数。

Softmax-with-Loss层的实现:

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

神经网络学习的全貌

神经网络中有合适的权重和偏置,调整权重和前置以便拟合训练数据的过程称为学习,神经网络的学习主要是以下四大步骤:

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

import sys, os
sys.path.append(os.pardir)

import numpy as np
import matplotlib.pyplot as plt
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("训练精度:"+ str(train_acc)+ " | " + "测试精度: " +str(test_acc))

训练结果如下:

训练精度:0.16325 | 测试精度: 0.1616
训练精度:0.9031833333333333 | 测试精度: 0.9074
训练精度:0.9242833333333333 | 测试精度: 0.9269
训练精度:0.9374833333333333 | 测试精度: 0.9369
训练精度:0.9443166666666667 | 测试精度: 0.9408
训练精度:0.9516166666666667 | 测试精度: 0.9474
训练精度:0.95615 | 测试精度: 0.9524
训练精度:0.9610666666666666 | 测试精度: 0.9581
训练精度:0.9646333333333333 | 测试精度: 0.9589
训练精度:0.9675333333333334 | 测试精度: 0.9623
训练精度:0.9706333333333333 | 测试精度: 0.9644
训练精度:0.9712666666666666 | 测试精度: 0.9638
训练精度:0.9729666666666666 | 测试精度: 0.9664
训练精度:0.9754 | 测试精度: 0.9679
训练精度:0.9768666666666667 | 测试精度: 0.9682
训练精度:0.9772666666666666 | 测试精度: 0.9697
训练精度:0.9784333333333334 | 测试精度: 0.9688

可以看出,训练数据的识别精度和测试数据的识别精度在不断提高,这说明神经网络在不断地学习。

其次,训练数据的识别精度和测试数据的识别精度基本吻合,说明这次的学习没有发生过拟合的现象。

每天学习一点点,每天进步一点点。

上一篇下一篇

猜你喜欢

热点阅读