[图灵程序设计丛书].深度学习入门:基于Python的理论与实现

深度学习入门

    • 第3章 神经网络
      • 3.2 激活函数
        • 3.2.6 非线性函数
        • 3.2.7 ReLU函数
      • 3.3 多维数组的运算
        • 3.3.3 神经网络的内积
      • 3.4 3层神经网络的实现
        • 3.4.2 各层间信号传递的实现
      • 3.5 输出层的设计
        • 3.5.1 恒等函数和 softmax函数
        • 3.5.2 实现 softmax函数时的注意事项
    • 第4章 神经网络的学习
      • 4.1 从数据中学习
      • 4.2 损失函数
          • 4.2.1 均方误差
          • 4.2.2 交叉熵误差
          • 4.2.3 mini-batch学习
          • 4.2.4 mini-batch版交叉熵误差的实现
          • 4.2.5 为何要设定损失函数
      • 4.3 数值微分
      • 4.4 梯度
        • 4.4.1 梯度法
        • 4.4.2 神经网络的梯度
      • 4.5 学习算法的实现神经网络的学习
        • 4.5.1 2层神经网络的类
        • 4.5.2 mini-batch的实现
        • 4.5.3 基于测试数据的评价
    • 第5章 误差反向传播法
      • 5.1 计算图
        • 5.1.1 用计算图求解
        • 5.1.2 局部计算
        • 5.1.3 为何用计算图解题
      • 5.2 链式法则
        • 5.2.3 链式法则和计算图
      • 5.3 反向传播
        • 5.3.1 加法节点的反向传播
        • 5.3.2 乘法节点的反向传播
        • 5.3.3 苹果的例子
      • 5.4 简单层的实现
        • 乘法层的实现
        • 5.4.2 加法层的实现
      • 5.5 激活函数层的实现
        • 5.5.1 ReLU层
        • 5.5.2 Simoid层
      • 5.6 Affine/Softmax层的实现
        • 5.6.1 Affine层
        • 5.6.3 Softmax-with-Loss 层
      • 5.7 误差反向传播法的实现
        • 5.7.2 对应误差反向传播法的神经网络的实现
        • 5.7.4 使用误差反向传播法的学习

第3章 神经网络

3.2 激活函数

3.2.6 非线性函数

  • 神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。
  • 线性函数的问题在于,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。

3.2.7 ReLU函数

ReLU函数在输入大于0时,直接输出该值;在输入小于等于0时,输出0

def relu(x):
	return np.maximum(0,x)

3.3 多维数组的运算

3.3.3 神经网络的内积

[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第1张图片
这个神经网络省略了偏置和激活函数,只有权重。

>>> X = np.array([1, 2])
>>> X.shape
(2,)
>>> W = np.array([[1, 3, 5], [2, 4, 6]])
>>> print(W)
[[1 3 5]
 [2 4 6]]
>>> W.shape
(2, 3)
>>> Y = np.dot(X, W)
>>> print(Y)
[ 5 11 17]

3.4 3层神经网络的实现

3.4.2 各层间信号传递的实现

[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第2张图片

  • 如果使用矩阵的乘法运算,则可以将第1层的加权和表示成下面的式
    A ( 1 ) = X W ( 1 ) + B ( 1 ) \bold A^{(1)}=\bold X\bold W^{(1)}+\bold B^{(1)} A(1)=XW(1)+B(1)
    [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第3张图片
  • 用NumPy多维数组来实现上式
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) # (2, 3)
print(X.shape) # (2,)
print(B1.shape) # (3,)
A1 = np.dot(X, W1) + B1
  • 隐藏层的加权和(加权信号和偏置的总和)用a表示,被激活函数转换后的信号用z表示。此外,图中h()表示激活函数,这里我们使用的是sigmoid函数。
Z1 = sigmoid(A1)

3.5 输出层的设计

3.5.1 恒等函数和 softmax函数

分类问题中使用的softmax函数可以用下面的式(3.10)表示
y k = e x p ( a k ) ∑ i = 1 n exp ⁡ ( a i ) y_k=\frac{exp(a_k)}{\sum_{i=1}^n\exp(a_i)} yk=i=1nexp(ai)exp(ak)

3.5.2 实现 softmax函数时的注意事项

softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。有可能出现溢出问题。
softmax函数实现这样进行改进
y k = exp ⁡ ( a k ) ∑ i = 1 n exp ⁡ ( a i ) = C exp ⁡ ( a k ) C ∑ i = 1 n exp ⁡ ( a i ) = exp ⁡ ( a k + l o g C ) ∑ i = 1 n exp ⁡ ( a i + l o g C ) = exp ⁡ ( a k + C ˊ ) ∑ i = 1 n exp ⁡ ( a i + C ˊ ) y_k=\frac{\exp(a_k)}{\sum_{i=1}^n\exp(a_i)}=\frac{C\exp(a_k)}{C\sum_{i=1}^n\exp(a_i)} =\frac{\exp(a_k+logC)}{\sum_{i=1}^n\exp(a_i+logC)} =\frac{\exp(a_k+\acute C)}{\sum_{i=1}^n\exp(a_i+\acute C)} yk=i=1nexp(ai)exp(ak)=Ci=1nexp(ai)Cexp(ak)=i=1nexp(ai+logC)exp(ak+logC)=i=1nexp(ai+Cˊ)exp(ak+Cˊ)

def softmax(a):
	c = np.max(a)
	exp_a = np.exp(a - c) # 溢出对策
	sum_exp_a = np.sum(exp_a)
	y = exp_a / sum_exp_a
	return y
  • softmax函数的输出是0.0到1.0之间的实数。
  • 并且,softmax函数的输出值的总和是1。输出总和为1是softmax函数的一个重要性质。正
    因为有了这个性质,我们才可以把softmax函数的输出解释为“概率”。

第4章 神经网络的学习

4.1 从数据中学习

4.2 损失函数

4.2.1 均方误差
4.2.2 交叉熵误差

除了均方误差之外,交叉熵误差(cross entropy error)也经常被用作损失函数。交叉熵误差如下式所示: E = − ∑ k t k log ⁡ y k E=-\sum_kt_k\log y_k E=ktklogyk

  • yk是神经网络的输出,tk是正确解标签。

比如,假设正确解标签的索引是“2”,与之对应的神经网络的输出是0.6,则交叉熵误差是−log 0.6 = 0.51;若“2”对应的输出是0.1,则交叉熵误差为−log 0.1 = 2.30。

  • 也就是说,交叉熵误差的值是由正确解标签所对应的输出结果决定的。
def cross_entropy_error(y, t):
    delta = 1e-7
    return -np.sum(t * np.log(y + delta)) # 加上了一个微小值delta可以防止负无限大的发生
4.2.3 mini-batch学习
  • 前面介绍的损失函数的例子中考虑的都是针对单个数据的损失函数。
  • 如果要求所有训练数据的损失函数的总和,以交叉熵误差为例,可以写成下面
    的式: E = − 1 N ∑ n ∑ k t k log ⁡ y k E=-\frac{1}{N}\sum_n\sum_kt_k\log y_k E=N1nktklogyk
  • 这里,假设数据有N个,tnk表示第n个数据的第k个元素的值(ynk是神经网络的输出,tnk是监督数据)。
  • 通过除以N,可以求单个数据的“平均损失函数”。通过这样的平均化,可以获得和训练数据的数量无关的统一指标。
4.2.4 mini-batch版交叉熵误差的实现
  • 这里,我们来实现一个可以同时处理单个数据和批量数据(数据作为batch集中输入)两种情况的函数。
def cross_entropy_error(y, t):
	if y.ndim == 1:
		'''y的维度为1(y是一个数)时,即求单个数据的交叉熵误差时,需要改变数据的形状。'''
		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 # np.sum 没有axis参数,表示对全部元素求和
  • 此外,当监督数据是标签形式(非one-hot表示,而是像“2”“7”这样的标签)时,交叉熵误差可通过如下代码实现。
def cross_entropy_error(y, t):
	if y.ndim == 1:
		'''y的维度为1(y是一个数)时,即求单个数据的交叉熵误差时,需要改变数据的形状。'''
		t = t.reshape(1, t.size)
		y = y.reshape(1, y.size)
	batch_size = y.shape[0]
	'''t是非one-hot表示,而是像“2”“7”这样的标签,
	y[np.arange(batch_size), t] 取出了每一行对应位置的预测值'''
	return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
4.2.5 为何要设定损失函数
  • 对该权重参数的损失函数求导,表示的是“如果稍微改变这个权重参数的值,损失函数的值会如何变化”。
  • 如果导数的值为负,通过使该权重参数向正方向改变,可以减小损失函数的值;反过来,如果导数的值为正,则通过使该权重参数向负方向改变,可以减小损失函数的值。
  • 当导数的值为0时,无论权重参数向哪个方向变化,损失函数的值都不会改变,此时该权重参数的更新会停在此处。
  • 在进行神经网络的学习时,不能将识别精度作为指标。因为如果以识别精度为指标,则参数的导数在绝大多数地方都会变为0。

假设某个神经网络正确识别出了100笔训练数据中的32笔,此时识别精度为32 %。如果以识别精度为指标,即使稍微改变权重参数的值,识别精度也仍将保持在32 %,不会出现变化。也就是说,仅仅微调参数,是无法改善识别精度的。即便识别精度有所改善,它的值也不会像32.0123 … %这样连续变化,而是变为33 %、34 %这样的不连续的、离散的值。而如果把损失函数作为指标,则当前损失函数的值可以表示为0.92543 … 这样的值。并且,如果稍微改变一下参数的值,对应的损失函数也会像0.93432 … 这样发生连续性的变化。

4.3 数值微分

4.4 梯度

4.4.1 梯度法

def numerical_gradient_1d(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

def numerical_gradient(f, x):
    h = 1e-4 # 0.0001
    grad = np.zeros_like(x)
    
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x) # f(x+h)
        
        x[idx] = tmp_val - h 
        fxh2 = f(x) # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)
        
        x[idx] = tmp_val # 还原值
        it.iternext()   
        
    return grad

用Python来实现梯度下降法:

def gradient_descent(f, init_x, lr=0.01, step_num=100):
	x = init_x # init_x是初始值
	for i in range(step_num):
		grad = numerical_gradient(f, x)
		x -= lr * grad # lr是学习率learning rate
	return x

4.4.2 神经网络的梯度

神经网络的梯度是指损失函数关于权重参数的梯度
下面,我们以一个简单的神经网络为例,来实现求梯度的代码。为此,我们要实现一个名为simpleNet的类。

import sys, os
sys.path.append(os.pardir)
import numpy as np
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient
class simpleNet:
	def __init__(self):
		self.W = np.random.randn(2,3) # 用高斯分布进行初始化
	def predict(self, x):
		return np.dot(x, self.W)
	def loss(self, x, t):
		z = self.predict(x)
		y = softmax(z)
		loss = cross_entropy_error(y, t)
		return loss

这里参数x接收输入数据,t接收正确解标签。现在我们来试着用一下这个simpleNet。

>>> net = simpleNet()
>>> print(net.W) # 权重参数
[[ 0.47355232 0.9977393 0.84668094],
 [ 0.85557411 0.03563661 0.69422093]])
>>>
>>> x = np.array([0.6, 0.9])
>>> p = net.predict(x)
>>> print(p)
[ 1.05414809 0.63071653 1.1328074]
>>> np.argmax(p) # 最大值的索引
2
>>>
>>> t = np.array([0, 0, 1]) # 正确解标签
>>> net.loss(x, t)
0.92806853663411326

接下来求梯度


>>> f = lambda w: net.loss(x, t)
>>> dW = numerical_gradient(f, net.W)
>>> print(dW)
[[ 0.21924763 0.14356247 -0.36281009]
 [ 0.32887144 0.2153437 -0.54421514]]

会发现 中的 的值大约是0.2,这表示如果将w11增加h,那么损失函数的值会增加0.2h。
再如, 对应的值大约是−0.5,这表示如果将w23增加h,损失函数的值将减小0.5h。
因此,从减小损失函数值的观点来看,w23应向正方向更新,w11应向负方向更新。至于更新的程度,w23比w11的贡献要大。

4.5 学习算法的实现神经网络的学习

步骤如下所示

前提
神经网络存在合适的权重和偏置,调整权重和偏置以便拟合训练数据的
过程称为“学习”。神经网络的学习分成下面4个步骤。
步骤1(mini-batch)
从训练数据中随机选出一部分数据,这部分数据称为mini-batch。我们
的目标是减小mini-batch的损失函数的值。
步骤2(计算梯度)
为了减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。
梯度表示损失函数的值减小最多的方向。
步骤3(更新参数)
将权重参数沿梯度方向进行微小更新。
步骤4(重复)
重复步骤1、步骤2、步骤3。

  • 不过因为这里使用的数据是随机选择的mini batch数据,所以又称为随机梯度下降法(stochastic gradient descent)。“随机”指的是“随机选择的”的意思,因此,随机梯度下降法是“对随机选择的数据进行的梯度下降法”。

4.5.1 2层神经网络的类

# 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 = {}
        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)
        # y有形状 (batch_size,10),cross_entropy_error返回的是一个负数
        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):
        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

4.5.2 mini-batch的实现

所谓mini-batch学习,就是从训练数据中随机选择一部分数据(称为mini-batch),再以这些mini-batch为对象,使用梯度法更新参数的过程。
(ps.而不是选择一条数据)

4.5.3 基于测试数据的评价

每经过一个epoch,就对所有的训练数据和测试数据计算识别精度,并记录结果。

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("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()

第5章 误差反向传播法

上一章中,通过数值微分计算了神经网络的损失函数关于权重参数的梯度(。数值微
分虽然简单,也容易实现,但缺点是计算上比较费时间。
本章我们将学习一个能够高效计算权重参数的梯度的方法——误差反向传播法。

5.1 计算图

5.1.1 用计算图求解

5.1.2 局部计算

  • 计算图的特征是可以通过传递“局部计算”获得最终结果。“局部”这个词的意思是“与自己相关的某个小范围”。
  • 无论全局的计算有多么复杂各个步骤所要做的就是对象节点的局部计算。

5.1.3 为何用计算图解题

计算图的优点:

  • 局部计算
  • 利用计算图可以将中间的计算结果全部保存起来(比如,计算进行到2个苹果时的金额是200日元、加上消费税之前的金额650日元等)
  • 实际上,使用计算图最大的原因是,可以通过反向传播高效计算导数。

5.2 链式法则

5.2.3 链式法则和计算图

[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第4张图片
用“**2”节点表示平方运算

计算图的反向传播从右到左传播信号。反向传播的计算顺序是,先将节点的输入信号乘以节点的局部导数(偏导数),然后再传递给下一个节点。
比如,反向传播时,“**2”节点的输入是 ∂ z ∂ z \frac{\partial z}{\partial z} zz,将其乘以局部导数 ∂ z ∂ t \frac{\partial z}{\partial t} tz(因为正向传播时输入是t、输出是z,所以这个节点的局部导数是 ),然后传递给下一个节点。
另外,图5-7中反向传播最开始的信号 在前面的数学式中没有出现,这是因为 ,所以在刚才的式子中被省略了。
图 5-7 中需要注意的是最左边的反向传播的结果。根据链式法则,成立,对应“z关于x的导数”。也就是说,反向传播是基于链式法则的。

  • 反向传播是基于链式法则的。
    [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第5张图片

5.3 反向传播

5.3.1 加法节点的反向传播

  • 因为加法节点的反向传播只乘以1,所以输入的值会原封不动地流向下一个节点。

5.3.2 乘法节点的反向传播

  • 乘法的反向传播会将上游的值乘以正向传播时的输入信号的“翻转值”后传递给下游。
  • 翻转值表示一种翻转关系,如图5-12所示,正向传播时信号是x的话,反向传播时则是y;正向传播时信号是y的话,反向传播时则是x。
  • 另外,加法的反向传播只是将上游的值传给下游,并不需要正向传播的输入信号。
  • 但是,乘法的反向传播需要正向传播时的输入信号值。因此,实现乘法节点的反向传播时,要保存正向传播的输入信号。
    [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第6张图片

5.3.3 苹果的例子

[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第7张图片

  • 这里要解的问题是苹果的价格、苹果的个数、消费税这3个变量各自如何影响最终支付的金额。
  • 这个问题相当于求“支付金额关于苹果的价格的导数”“支付金额关于苹果的个数的导数”“支付金额关于消费税的导数”。
  • 从图5-14的结果可知,苹果的价格的导数是2.2,苹果的个数的导数是110,消费税的导数是200。
  • 这可以解释为,其他不变,消费税每增加一个单位,对支付金额的影响合适使支付金额增加200个单位。

5.4 简单层的实现

乘法层的实现

层的实现中有两个共通的方法(接口)forward()和backward()。forward()对应正向传播,backward()对应反向传播。

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 # 翻转x和y
		dy = dout * self.x
		return dx, dy

使用这个乘法层的话,图5-16的正向传播可以像下面这样实现

apple = 100
apple_num = 2
tax = 1.1
# layer
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)
print(price) # 220

此外,关于各个变量的导数可由backward()求出。

# backward
dprice = 1
dapple_price, dtax = mul_tax_layer.backward(dprice)
dapple, dapple_num = mul_apple_layer.backward(dapple_price)
print(dapple, dapple_num, dtax) # 2.2 110 200

5.4.2 加法层的实现

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.5 激活函数层的实现

我们将计算图的思路应用到神经网络中。这里,我们把构成神经网络的层实现为一个类。

5.5.1 ReLU层

激活函数ReLU(Rectified Linear Unit)由下式(5.7)表示。
f ( n ) = { x , x > 0 0 , x ≤ 0 f(n) = \begin{cases} x, & x>0 \\ 0, & x\le0 \end{cases} f(n)={x,0,x>0x0
通过式(5.7),可以求出y关于x的导数,如式(5.8)所示。
f ( n ) = { 1 , x > 0 0 , x ≤ 0 f(n) = \begin{cases} 1, & x>0 \\ 0, & x\le0 \end{cases} f(n)={1,0,x>0x0

  • 如果正向传播时的输入x大于0,则反向传播会将上游的值原封不动地传给下游。
  • 反过来,如果正向传播时的x小于等于0,则反向传播中传给下游的信号将停在此处
    *[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第8张图片
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

5.5.2 Simoid层

  • 反向传播:[图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第9张图片
  • 反向传播的输出是 ∂ L ∂ y y 2 exp ⁡ ( − x ) \frac{\partial L}{\partial y}y^2\exp(-x) yLy2exp(x),这个值会传给下游的节点, ∂ L ∂ y y 2 exp ⁡ ( − x ) \frac{\partial L}{\partial y}y^2\exp(-x) yLy2exp(x)只根据正向传播时的输入x和输出y就可以算出来
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

5.6 Affine/Softmax层的实现

5.6.1 Affine层

  • 神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”A。因此,这里将进行仿射变换的处理实现为“Affine层”。
  • 几何中,仿射变换包括一次线性变换和一次平移,分别对应神经网络的加权和运算与加偏置运算。
  • 计算图
    [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第10张图片
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) #mini-batch
		return dx

5.6.3 Softmax-with-Loss 层

  • Cross Entropy Error层的反向传播:
    • 反向传播的初始值(下图中最右边的值)是1(因为 ∂ L ∂ L = 1 \frac{\partial L}{\partial L}=1 LL=1)。
  • Softmax层的反向传播:
    • 在除法节点,正向传播时若有分支流出,则反向传播时它们的反向传播的值会相加。
    • 这里分成了三支的反向传播的值(−t1S, −t2S, −t3S)会被求和。然后,
      还要对这个相加后的值进行“/”节点的反向传播,结果为: ( ( − t 1 S ) + ( − t 2 S ) + ( − t 3 S ) ) ( − S − 2 ) = S − 1 ( t 1 + t 2 + t 3 ) ((-t_1S)+(-t_2S)+(-t_3S))(-S^{-2})=S^{-1}(t_1+t_2+t_3) ((t1S)+(t2S)+(t3S))(S2)=S1(t1+t2+t3)
    • (t1, t2, t3)是标签,也是one-hot向量。one-hot向量意味着(t1, t2, t3)
      中只有一个元素是1,其余都是0。因此,(t1, t2, t3)的和为1
      [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第11张图片
    • Softmax-with-Loss层的计算图的全部内容以及其反向传播
      [图灵程序设计丛书].深度学习入门:基于Python的理论与实现_第12张图片

5.7 误差反向传播法的实现

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

  1. (mini-batch)从训练数据中随机选择一部分数据。
  2. (计算梯度)计算损失函数关于各个权重参数的梯度。
  3. (更新参数)将权重参数沿梯度方向进行微小的更新。
  4. (重复)重复步骤1、步骤2、步骤3。

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

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

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的理论与实现)