【深度学习】Python实现基于数值微分的神经网络的学习

回顾

\quad\quad 在之前的神经网络的学习过程一篇中,我们介绍了如何获取批量数据、损失函数、梯度以及梯度下降法,本篇将通过这些来实现神经网络的学习。

神经网络的学习步骤

  • 1、mini-batch

从训练数据中随机选出一部分数据,这部分数据称为mini-batch。学习的目标就是减小mini-batch的损失函数的值

  • 2、计算梯度

减小mini-batch的损失函数的值,需要求出各个权重参数的梯度。梯度表示损失函数的值减小最多的方向

  • 3、更新参数

将权重参数沿梯度方向进行微小更新

  • 4、重复

重复以上三个步骤

构建手写数字识别的神经网络

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

# coding: utf-8
import numpy as np

# sigmoid函数
def sigmoid(x):
    return 1 / (1 + np.exp(-x)) 

# softmax函数
def softmax(x):
    if x.ndim == 2:
        x = x.T
        x = x - np.max(x, axis=0)
        y = np.exp(x) / np.sum(np.exp(x), axis=0)
        return y.T 

    x = x - np.max(x) # 溢出对策
    return np.exp(x) / np.sum(np.exp(x))

# 交叉熵误差
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
        
    # 监督数据是one-hot-vector的情况下,转换为正确解标签的索引
    if t.size == y.size:
        t = t.argmax(axis=1)
             
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size

# 基于数值微分的梯度求解
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


# 2层神经网络的实现类
class TwoLayerNet:
    # input_size输入层的神经元数、hidden_size隐藏层神经元数、output_size输出层神经元数
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 初始化权重
        self.params = {}  # 参数字典
        # 初始化输入层到隐藏层的权重、偏置
        # 随机生成形状为(input_size, hidden_size)的二维数组
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) 
        # 生成和隐藏层神经元数相同的一维0数组
        self.params['b1'] = np.zeros(hidden_size)
        # 初始化隐藏层到输出层的权重、偏置
        # 随机生成形状为(hidden_size, output_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  # 返回各个参数的梯度
    
# 测试
net = TwoLayerNet(input_size=784, hidden_size=100, output_size=10)
print(net.params['W1'].shape)
print(net.params['b1'].shape)
print(net.params['W2'].shape)
print(net.params['b1'].shape)
输出为:
(784, 100)
(100,)
(100, 10)
(100,)

类前面的函数,在之前都介绍过,我们可以将函数打包,通过导入模块方式调用

使用MNIST数据集进行学习(使用mini-batch方式)

import mnist
import time
# 开始时间
start = time.time()

# 读入数据
(x_train, t_train), (x_test, t_test) = mnist.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 = [] # 批次训练损失列表

# 遍历循环次数
for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size) # 从训练数据集中随机选取batch_size作为小批量数据
    x_batch = x_train[batch_mask] # 批量训练数据
    t_batch = t_train[batch_mask] # 批量监督数据
    
    # 计算梯度
    grad = network.numerical_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)
    
# 结束时间        
end = time.time()
print("耗时:", (end-start))

此程序mini-batch为100,需要每次从60000个训练数据集中随机选取出100个数据,然后对这100个数据的mini-batch求梯度,使用SGD(随机梯度下降法)更新参数
梯度法更新次数为10000,每更新一次,都对训练数据计算损失函数的值,并把该值添加到数组中

基于测试数据集的评价

\quad\quad 通过上面的学习过程后,我们发现,损失函数的值在逐渐减小,但是仅依据这个并不能说明神经网络在其他数据集上也一定能有同样的效果,我们还需要评价它的泛化能力。因此,我们需要在测试数据集下,计算正确率,如果在测试数据集中,正确率也比较高,那么说明这个神经网络的泛化能力比较好。

如果训练正确率比较高,测试正确率比较低,可能发生了过拟合

import mnist
import time

# 开始时间
start = time.time()

# 读入数据
(x_train, t_train), (x_test, t_test) = mnist.load_mnist(normalize=True, one_hot_label=True)
# 构建神经网络
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
# 设置超参数
iters_num = 10  # 适当设定循环的次数(这里设为10是为了程序早点结束,自然结果也不是很好)可以设为10000,但是运行时间很长
train_size = x_train.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):
    batch_mask = np.random.choice(train_size, batch_size) # 从训练数据集中随机选取batch_size作为小批量数据
    x_batch = x_train[batch_mask] # 批量训练数据
    t_batch = t_train[batch_mask] # 批量监督数据
    
    # 计算梯度
    grad = network.numerical_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))
# 结束时间        
end = time.time()
print("耗时:", (end-start))
输出为:
train acc, test acc | 0.10441666666666667, 0.1028
耗时: 294.10724544525146
# 绘制图形
import matplotlib.pyplot as plt

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

上面程序中,每经过一个epoch,就会对所有的训练数据和测试数据计算识别精度,并记录结果,因为,如果每次循环都计算识别精度,会花费太多时间,而且,一般我们也没必要频繁记录

总结

  • 以上程序运行时间比较长,感兴趣的可以运行下
  • 此程序是基于数值微分来计算梯度的
  • 为了让程序运行快一点,我们可以使用反向传播算法来计算梯度,大大减少运行时间,这在以后的博客中具体实现

你可能感兴趣的:(深度学习)