深度学习入门:基于Python的理论与实现②

第四章神经网络的学习

本章的主题是神经网络的学习。这里所说的“学习”是指从训练数据中自动获取最优权重参数的过程。本章中,为了使神经网络能进行学习,将导入损失函数这一指标。而学习的目的就是以该损失函数为基准,找出能使它 的值达到最小的权重参数。

1.从数据中学习

神经网络的特征就是可以从数据中学习。所谓“从数据中学习”,是指可以由数据自动决定权重参数的值。

1.1数据驱动

数据是机器学习的核心,从数据中寻找答案、从数据中发现模式、根据数据讲故事……这些机器学习所做的事情。机器学习的方法极力避免人为介入,尝试从收集到的数据中发现答案(模式)。神经网络或深度学习则比以往的机器学习方法更能避免人为介入。
识别手写数字5
先从图像中提取特征量,再用机器学习技术学习这些特征量的模式。图像的特征量通常表示为向量的形式。在计算机视觉领域,常用的特征量包括SIFT、SURF和HOG等。使用这些特征量将图像数据转换为向量,然后对转换后的向量使用机器学习中的SVM、KNN等分类器进行学习。对于不同的问题,必须使用合适的特征量(必须设计专门的特征量),才能得到好的结果。

如图所示,神经网络直接学习图像本身。在第2个方法,即利用特征量和机器学习的方法中,特征量仍是由人工设计的,而在神经网络中,连图像中包含的重要特征量也都是由机器来学习的。
深度学习入门:基于Python的理论与实现②_第1张图片

1.2训练数据和测试数据

机器学习中,一般将数据分为训练数据(监督数据)和测试数据两部分来进行学习和实验等。首先,使用训练数据进行学习,寻找最优的参数;然后,使用测试数据评价训练得到的模型的实际能力。
为了正确评价模型的泛化能力,就必须划分训练数据和测试数据。泛化能力是指处理未被观察过的数据(不包含在训练数据中的数据)的能力。获得泛化能力是机器学习的最终目标。
仅仅用一个数据集去学习和评价参数,是无法进行正确评价的。只对某个数据集过度拟合的状态称为过拟合(over fitting)。

2.损失函数

神经网络以某个指标为线索寻找最优权重参数,神经网络的学习中所用的指标称为损失函数(loss function)。这个损失函数可以使用任意函数,但一般用均方误差交叉熵误差等。
损失函数是表示神经网络性能的“恶劣程度”的指标,即当前的神经网络对监督数据在多大程度上不拟合,在多大程度上不一致。

2.1均方误差

深度学习入门:基于Python的理论与实现②_第2张图片
这里,yk是表示神经网络的输出,tk表示监督数据,k表示数据的维数。

用python实现:

def mean_squared_error(y, t):
    return 0.5 * np.sum((y-t)**2)

举例:
深度学习入门:基于Python的理论与实现②_第3张图片

2.2交叉熵误差

Alt
这里,log表示以e为底数的自然对数(log e)。yk是神经网络的输出,tk是正确解标签。并且,tk中只有正确解标签的索引为1,其他均为0(one-hot表示)

def cross_entropy_error(y,t):
    delta = 1e-7
    return -np.sum(t*np.log(y + delta))

这里,参数y和t是NumPy数组。函数内部在计算np.log时,加上了一个微小值delta。这是因为,当出现np.log(0)时,np.log(0)会变为负无限大的-inf,这样一来就会导致后续计算无法进行。作为保护性对策,添加一个微小值可以防止负无限大的发生。

举例:
深度学习入门:基于Python的理论与实现②_第4张图片

2.3mini-batch学习

机器学习使用训练数据进行学习。使用训练数据进行学习,严格来说,就是针对训练数据计算损失函数的值,找出使该值尽可能小的参数。
前面介绍的损失函数的例子中考虑的都是针对单个数据的损失函数。如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可以写成下面的式

深度学习入门:基于Python的理论与实现②_第5张图片
这里,假设数据有N个,tnk表示第n个数据的第k个元素的值(ynk是神经网络的输出,tnk是监督数据)。最后还要除以N进行正规化。通过除以N,可以求单个数据的“平均损失函数”。
我们从全部数据中选出一部分,作为全部数据的“近似”。神经网络的学习也是从训练数据中选出一批数据(称为mini-batch,小批量),然后对每个mini-batch进行学习。比如,从60000个训练数据中随机选择100笔,再用这100笔数据进行学习。这种学习方式称为mini-batch学习

2.4mini-batch版交叉熵误差的实现

def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

这里,y是神经网络的输出,t是监督数据。

3.数值微分

3.1导数

数值微分含有误差。为了减小这个误差,我们可以计算函数f在(x + h)和(x − h)之间的差分。因为这种计算方法以x为中心,计算它左右两边的差分,所以也称为中心差分(而(x + h)和x之间的差分称为前向差分)。

def numerical_diff(f, x):
 h = 1e-4 # 0.0001
 return (f(x+h) - f(x-h)) / (2*h)

3.2偏导数

Alt

def function_2(x):
	return x[0]**2 + x[1]**2

4.梯度

def numerical_gradient(f, x):
	h = 1e-4 # 0.0001
	grad = np.zeros_like(x) # 生成和x形状相同的数组
	
	for idx in range(x.size):
		tmp_val = x[idx]
		# f(x+h)的计算
		x[idx] = tmp_val + h
		fxh1 = f(x)
		# f(x-h)的计算
		x[idx] = tmp_val - h
		fxh2 = f(x)
		grad[idx] = (fxh1 - fxh2) / (2*h)
		x[idx] = tmp_val # 还原值
	return grad

实际上,梯度会指向各点处的函数值降低的方向。更严格地讲,梯度指示的方向是各点处的函数值减小最多的方向。

4.1梯度法

机器学习的主要任务是在学习时寻找最优参数。同样地,神经网络也必须在学习时找到最优参数(权重和偏置)。这里所说的最优参数是指损失函数取最小值时的参数。但是,一般而言,损失函数很复杂,参数空间庞大,我们不知道它在何处能取得最小值。而通过巧妙地使用梯度来寻找函数最小值
(或者尽可能小的值)的方法就是梯度法。
【注】梯度表示的是各点处的函数值减小最多的方向。
在梯度法中,函数的取值从当前位置沿着梯度方向前进一定距离,然后在新的地方重新求梯度,再沿着新梯度方向前进,如此反复,不断地沿梯度方向前进。像这样,通过不断地沿梯度方向前进,逐渐减小函数值的过程就是梯度法(gradient method)。梯度法是解决机器学习中最优化问题的常用方法,特别是在神经网络的学习中经常被使用。
数学公式表示梯度法:
深度学习入门:基于Python的理论与实现②_第6张图片
η表示更新量,在神经网络的学习中,称为学习率(learning rate)。
学习率需要事先确定为某个值,比如0.01或0.001。一般而言,这个值过大或过小,都无法抵达一个“好的位置”。在神经网络的学习中,一般会一边改变学习率的值,一边确认学习是否正确进行了。
用python实现梯度下降法:

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    
    for i in range(step_num):
        grad = numerical_gradient(f,x)
        x -= lr * grad
        
    return x

参数f是要进行最优化的函数,init_x是初始值,lr是学习率learning rate,step_num是梯度法的重复次数。numerical_gradient(f,x)会求函数的梯度,用该梯度乘以学习率得到的值进行更新操作,由step_num指定重复的次数。
像学习率这样的参数称为超参数。学习率这样的超参数则是人工设定的,一般来说,超参数需要尝试多个值,以便找到一种可以使学习顺利进行的设定。

5.学习算法的实现

前提
神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的过程称为“学习”。神经网络的学习分成下面4个步骤。

步骤1(mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们的目标是减小mini-batch的损失函数的值。
步骤2(计算梯度)
为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向。
步骤3(更新参数)
将权重参数沿梯度方向进行微小更新
步骤4(重复)
重复步骤1、2、3

因为这里使用的数据是随机选择的mini batch数据,所以又称为随机梯度下降法(stochastic gradient descent)。深度学习的很多框架中,随机梯度下降法一般由一个名为SGD的函数来实现。SGD来源于随机梯度下降法的英文名称的首字母。

5.1 2层神经网络的类

这里以2层神经网络(隐藏层为1层的网络)为对象,使用MNIST数据集进行学习。

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
from common.functions import *
from common.gradient import numerical_gradient


class TwoLayerNet:

    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 初始化权重
        self.params = {}	#params神经网络参数的字典型量,W偏置,b权重
        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)

    def predict(self, x):		#识别推理
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
    
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        return y
        
    # x:输入数据, t:监督数据
    def loss(self, x, t):		#计算损失函数的值
        y = self.predict(x)
        
        return cross_entropy_error(y, 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
        
    # 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):	#计算权重参数的梯度,numerical_gradient()的高速版
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}
        
        batch_num = x.shape[0]
        
        # forward
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        
        # backward
        dy = (y - t) / batch_num
        grads['W2'] = np.dot(z1.T, dy)
        grads['b2'] = np.sum(dy, axis=0)
        
        da1 = np.dot(dy, W2.T)
        dz1 = sigmoid_grad(a1) * da1
        grads['W1'] = np.dot(x.T, dz1)
        grads['b1'] = np.sum(dz1, axis=0)

        return grads

TwoLayerNet类有params和grads两个字典型实例变量。params变量中保存了权重参数,比如params[‘W1’]以NumPy数组的形式保存了第1层的权重参数。此外,第1层的偏置可以通过param[‘b1’]进行访问。
深度学习入门:基于Python的理论与实现②_第7张图片
进行手写数字识别时,输入图像的大小是784(28 × 28),输出为10个别,所以指定参数input_size=784、output_size=10,将隐藏层的个hidden_size设置为一个合适的值即可。
如何设置权重参数的初始值这个问题是关系到神经网络能否成功学习的重要问题。这里只需要知道,权重使用符合高斯分布的随机数进行初始化,偏置使用0进行初始化。另外,loss(self, x, t)是计算损失函数值的方法。这个方法会基于predict()的结果和正确解标签,计算交叉熵误差。
剩下的numerical_gradient(self, x, t)方法会计算各个参数的梯度。根据数值微分,计算各个参数相对于损失函数的梯度。另外,gradient(self, x,t)是下一章要实现的方法,该方法使用误差反向传播法高效地计算梯度。

5.2 mini-batch的实现

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
# pardir代表parent directory
#sys.path.append(os.pardir)语句实际上是把父目录deep learning加入到sys.path(Python 的搜索目录模块的路径集中)
#从而可以导入deep learning下的任何目录(包括dataset目录)中的任何文件。

import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist ##load_mnist为dataset文件下的mnist.py中的函数
from two_layer_net import TwoLayerNet

# 读入数据
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
#normalize设置是否将输入图像正规化为0.0~1.0的值。
#如果将该参数设置为False,则输入图像的像素会保持原来的0~255。
#当one_hot_label为True时,标签则保存为one-hot表示
#one-hot表示是仅正确解标签为1,其余皆为0的数组
#就像[0,0,1,0,0,0,0,0,0,0]这样

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000  # 适当设定循环的次数
#梯度法更新次数,每更新一次,都对训练数据计算损失函数的值,并把该值添加到数组中
train_size = x_train.shape[0]      #shape[0]表示矩阵的行数
batch_size = 100                   #批的大小
learning_rate = 0.1

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):
    #获取mini-batch
    batch_mask = np.random.choice(train_size, batch_size)   #变量mask是由True/False构成的NumPy数组
    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)
    
    #计算每个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))

# 绘制图形
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

这里,mini-batch的大小为100,需要每次从60000个训练数据中随机取出100个数据(图像数据和正确解标签数据)。然后,对这个包含100笔数据的mini-batch求梯度,使用随机梯度下降法(SGD)更新参数。这里,梯度法的更新次数(循环的次数)为10000。每更新一次,都对训练数据计算损失函数的值,并把该值添加到数组中。
随着学习的进行,损失函数的值在不断减小。这是学习正常进行的信号,表示神经网络的权重参数在逐渐拟合数据。也就是说,神经网络的确在学习!通过反复地向它浇灌(输入)数据,神经网络正在逐渐向最优参数靠近。

神经网络的学习中,必须确认是否能够正确识别训练数据以外的其他数据,即确认是否会发生过拟合。过拟合是指,虽然训练数据中的数字图像能被正确辨别,但是不在训练数据中的数字图像却无法被识别的现象

epoch是一个单位。一个 epoch表示学习中所有训练数据均被使用过一次时的更新次数。比如,对于 10000笔训练数据,用大小为 100笔数据的mini-batch进行学习时,重复随机梯度下降法 100次,所有的训练数据就都被“看过”了。此时,100次就是一个 epoch。

实线表示训练数据的识别精度,虚线表示测试数据的识别精度。如图所示,随着epoch的前进(学习的进行),我们发现使用训练数据和测试数据评价的识别精度都提高了,并且,这两个识别精度基本上没有差异(两条线基本重叠在一起)。因此,可以说这次的学习中没有发生过拟合的现象。
深度学习入门:基于Python的理论与实现②_第8张图片

第五章误差反向传播法

一个高效计算权重参数的梯度的方法——误差反向传播法。正确理解误差反向传播法,有两种方法:一种是基于数学式;另一种是基于计算图。本章通过计算图,直观地理解误差反向传播法。然后,再结合实际的代码加深理解。

1.计算图

计算图将计算过程用图形表示出来。这里说的图形是数据结构图,通过多个节点和边表示(连接节点的直线称为“边”)。
计算图可以集中精力于局部计算。例如苹果和其他很多东西的求和运算(4000 + 200 → 4200)并不关心4000这个数字是如何计算而来的,只要把两个数字相加就可以了。换言之,各个节点处只需进行与自己有关的计算(在这个例子中是对输入的两个数字进行加法运算),不用考虑全局。
深度学习入门:基于Python的理论与实现②_第9张图片
计算图的三大优点:
①无论全局是多么复杂的计算,都可以通过局部计算使各个节点致力于简单的计算,从而简化问题。
②利用计算图可以将中间的计算结果全部保存起来。
③使用计算图最大的原因是,可以通过反向传播高效计算导数

“支付金额关于苹果的价格的导数”的值可以通过计算图的反向传播求出来。
深度学习入门:基于Python的理论与实现②_第10张图片
反向传播使用与正方向相反的箭头(粗线)表示。反向传播传递“局部导数”,将导数的值写在箭头的下方。在这个例子中,反向传播从右向左传递导数的值(1 → 1.1 → 2.2)。这意味着,如果苹果的价格上涨1日元,最终的支付金额会增加2.2日元。

2.链式法则

上述传递这个局部导数的原理,是基于链式法则(chain rule)的。
假设存在*y = f(x)*的计算:
深度学习入门:基于Python的理论与实现②_第11张图片
反向传播的计算顺序是,将信号E乘以节点的局部导数,然后将结果传递给下一个节点。

3.反向传播

加法节点的反向传播只乘以1,所以输入的值会原封不动地流向下一个节点。
乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。翻转值表示一种翻转关系,正向传播时信号是x的话,反向传播时则是y;正向传播时信号是y的话,反向传播时则是x。
深度学习入门:基于Python的理论与实现②_第12张图片
深度学习入门:基于Python的理论与实现②_第13张图片

4.简单层的实现

我们把要实现的计算图的乘法节点称为“乘法层”(MulLayer),加法节点称为“加法层”(AddLayer)。

4.1乘法层的实现

层的实现中有两个共通的方法(接口)forward()和backward()。forward()
对应正向传播,backward()对应反向传播。现在来实现乘法层。乘法层作为MulLayer类,其实现过程如下所示:

# coding: utf-8


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

实例
深度学习入门:基于Python的理论与实现②_第14张图片

from layer_naive import *


apple = 100
apple_num = 2
tax = 1.1

mul_apple_layer = MulLayer()
mul_tax_layer = MulLayer()

# forward
apple_price = mul_apple_layer.forward(apple, apple_num)
price = mul_tax_layer.forward(apple_price, tax)

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)

print("price:", int(price))
print("dApple:", dapple)
print("dApple_num:", int(dapple_num))
print("dTax:", dtax)

输出结果:
price: 220
dApple: 2.2
dApple_num: 110
dTax: 200

5.激活函数层的实现

现在,我们将计算图的思路应用到神经网络中。这里,我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。

5.1ReLU层

深度学习入门:基于Python的理论与实现②_第15张图片
如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游。反过来,如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处。
深度学习入门:基于Python的理论与实现②_第16张图片

class ReLU:
    def __init__(self):
        self.mask = None
#mask是由True/False构成的NumPy数组
#它会把正向传播时的输入x的元素中小于等于0的地方保存为True
#其他地方(大于0的元素)保存为False

    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

ReLU层的作用就像电路中的开关一样。正向传播时,有电流通过
的话,就将开关设为 ON;没有电流通过的话,就将开关设为 OFF。
反向传播时,开关为ON的话,电流会直接通过;开关为OFF的话,
则不会有电流通过。

5.2.Sigmoid层

深度学习入门:基于Python的理论与实现②_第17张图片
深度学习入门:基于Python的理论与实现②_第18张图片
整理后:
深度学习入门:基于Python的理论与实现②_第19张图片
深度学习入门:基于Python的理论与实现②_第20张图片
代码实现:

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

6.Affine/Softmax层的实现

6.1.Affine层

神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”A。因此,这里将进行仿射变换的处理实现为“Affine层”。
Affine层的反向传播:
深度学习入门:基于Python的理论与实现②_第21张图片

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

6.2 Softmax-with-Loss层

手写数字识别时,Softmax层的输出如图5-28所示。
深度学习入门:基于Python的理论与实现②_第22张图片
Softmax层将输入值正规化(将输出值的和调整为1)之后再输出。另外,为手写数字识别要进行10类分类,所以向Softmax层的输入也有10个。神经网络中进行的处理有推理(inference)学习两个阶段。神经网络的推理通常不使用 Softmax层。比如,用图 5-28的网络进行推理时,会将最后一个 Affine层的输出作为识别结果。神经网络中未被正规化的输出结果(图 5-28中 Softmax层前面的 Affine层的输出)有时被称为“得分”。也就是说,当神经网络的推理只需要给出一个答案的情况下,因为此时只对得分最大值感兴趣,所以不需要 Softmax层。不过,神经网络的学习阶段则需要Softmax层。
深度学习入门:基于Python的理论与实现②_第23张图片
图5-30中要注意的是反向传播的结果。Softmax层的反向传播得到了(y1 − t1, y2 − t2, y3 − t3)这样“漂亮”的结果。由于(y1, y2, y3)是Softmax层的输出,(t1, t2, t3)是监督数据,所以(y1 − t1, y2 − t2, y3 − t3)是Softmax的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质。
【注意】反向传播时,将要传播的值除以批的大小(batch_size)后,传递给前面的层的是单个数据的误差。

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

【参考】
深度学习入门:基于Python的理论与实现②_第24张图片

深度学习入门:基于Python的理论与实现②_第25张图片

# 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):
        # 初始化权重
        # weight_init_std为初始化权重时的高斯分布的规模
        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()
        #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)
    # self.lastLayer = SoftmaxWithLoss()
    
    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)
        #dim维数
        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)

第四五章引入很多新概念、方法,同时代码量陡然增加,很多地方需要反复理解。好多时候看了后面的忘了前面的,同时python我又掌握得很一般,推进比较困难比较慢。┭┮﹏┭┮

在这里插入图片描述

你可能感兴趣的:(深度学习,python,机器学习)