Paddle:手写字符识别模型(2)

数据的预处理

  • 读入数据
  • 划分数据集
  • 生成批次数据
  • 训练样本集乱序
  • 校验数据有效性

实验开始之前 ,让我们先来导入相应的库:

import os
import random
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image

import gzip
import json

数据的读入与划分

这里我们的数据集是 Json 形式的数据集合,可以利用 json.load() 加载 Json 文件。

# 声明数据集文件位置
datafile = './work/mnist.json.gz'
print('loading mnist dataset from {} ......'.format(datafile))
# 加载json数据文件
data = json.load(gzip.open(datafile))
print('mnist dataset load done')
# 读取到的数据区分训练集,验证集,测试集
train_set, val_set, eval_set = data

# 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
IMG_ROWS = 28
IMG_COLS = 28

# 打印数据信息
imgs, labels = train_set[0], train_set[1]
print("训练数据集数量: ", len(imgs))

# 观察验证集数量
imgs, labels = val_set[0], val_set[1]
print("验证数据集数量: ", len(imgs))

# 观察测试集数量
imgs, labels = val= eval_set[0], eval_set[1]
print("测试数据集数量: ", len(imgs))

结果:
Paddle:手写字符识别模型(2)_第1张图片

数据打乱与批次的生成

数据打乱的代码如下:

imgs,labels = train_set[0],train_set[1]
print("训练数据大小:",len(imgs))
imgs_len = len(imgs)
# 数据打乱
## 给每条数据定义一个序号,然后将序号打乱,并且根据需要读取数据
index_list = list(range(imgs_len))
Batch_size = 100
# 将index_list 打乱
random.shuffle(index_list)
index_list

结果:
Paddle:手写字符识别模型(2)_第2张图片
批次的生成函数,这里我们采用 Python 中的 yield 机制,生成一个数据迭代器:

 # 定义生成器,返回批次数据,迭代器模式,返回批次生成器
def data_generator():
    imgs_list = []
    label_list =[]
    for i in index_list:
        # 处理数据的格式
        # 将img 处理成 (C,W,H) 的形式
        img = np.reshape(imgs[i],[1,IMG_ROWS,IMG_COLS]).astype('float32')
        label = np.reshape(labels[i],[1]).astype('float32')
        # 将数据加入该批次
        imgs_list.append(img)
        label_list.append(label)
        if len(imgs_list) == Batch_size:
            # yield 机制,即迭代器机制。每次data_generator.next 就会返回yield 后面的值
            # 下一次,将从上以次结束的地方继续开始执行,i 值不会清零
            yield np.array(imgs_list),np.array(label_list)
            imgs_list = []
            label_list =[]
    #剩余的,不足 batch_size 的数据
    if len(imgs_list) > 0:
        yield np.array(imgs_list),np.array(label_list)  
    return data_generator
   

测试代码,我们可以通过 for 语句遍历整个迭代器,每次迭代返回一个批次:

## 测试代码:以迭代的形式读取数据
train_loader = data_generator
for batch_id, data in enumerate(train_loader()):
    image_data, label_data = data
    if batch_id == 0:
        # 打印数据shape和类型
        print("打印第一个batch数据的维度:")
        print("图像维度: {}, 标签维度: {}".format(image_data.shape, label_data.shape))
    break

结果:
在这里插入图片描述

封装数据读取与处理函数

上文,我们从读取数据,划分数据集,到打乱训练数据,构建数据读取器以及数据数据校验,完成了一整套一般性的数据处理流程,下面将这些步骤放在一个函数中实现,方便在神经网络训练时直接调用。
下面代码块可以根据传入的 mode 不同,生成不同批次的训练集或检验集合或测试集合:


def load_data(mode='train'):
    datafile = './work/mnist.json.gz'
    print('loading mnist dataset from {} ......'.format(datafile))
    # 加载json数据文件
    data = json.load(gzip.open(datafile))
    print('mnist dataset load done')
   
    # 读取到的数据区分训练集,验证集,测试集
    train_set, val_set, eval_set = data
    if mode=='train':
        # 获得训练数据集
        imgs, labels = train_set[0], train_set[1]
    elif mode=='valid':
        # 获得验证数据集
        imgs, labels = val_set[0], val_set[1]
    elif mode=='eval':
        # 获得测试数据集
        imgs, labels = eval_set[0], eval_set[1]
    else:
        raise Exception("mode can only be one of ['train', 'valid', 'eval']")
    print("训练数据集数量: ", len(imgs))
    
    # 校验数据
    imgs_length = len(imgs)

    assert len(imgs) == len(labels), \
          "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(label))
    
    # 获得数据集长度
    imgs_length = len(imgs)
    
    # 定义数据集每个数据的序号,根据序号读取数据
    index_list = list(range(imgs_length))
    # 读入数据时用到的批次大小
    BATCHSIZE = 100
    
    # 定义数据生成器
    def data_generator():
        if mode == 'train':
            # 训练模式下打乱数据
            random.shuffle(index_list)
        imgs_list = []
        labels_list = []
        for i in index_list:
            # 将数据处理成希望的格式,比如类型为float32,shape为[1, 28, 28]
            img = np.reshape(imgs[i], [1, IMG_ROWS, IMG_COLS]).astype('float32')
            label = np.reshape(labels[i], [1]).astype('int64')
            imgs_list.append(img) 
            labels_list.append(label)
            if len(imgs_list) == BATCHSIZE:
                # 获得一个batchsize的数据,并返回
                yield np.array(imgs_list), np.array(labels_list)
                # 清空数据读取列表
                imgs_list = []
                labels_list = []
    
        # 如果剩余数据的数目小于BATCHSIZE,
        # 则剩余数据一起构成一个大小为len(imgs_list)的mini-batch
        if len(imgs_list) > 0:
            yield np.array(imgs_list), np.array(labels_list)
    return data_generator

模型的训练

异步读取数据

模型的定义不变的情况下,让我们使用异步读取数据的方式读取数据

#### 指定异步读取
# 定义数据读取后存放的位置,CPU或者GPU,这里使用CPU
# place = fluid.CUDAPlace(0) 时,数据读取到GPU上
place = fluid.CPUPlace()
with fluid.dygraph.guard(place):
 
    # 声明数据加载函数,使用训练模式
    train_loader = load_data(mode='train')
    # 定义DataLoader对象用于加载Python生成器产生的数据
    data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True)
    # 设置数据生成器
    data_loader.set_batch_generator(train_loader, places=place)


    # 迭代的读取数据并打印数据的形状
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.001, parameter_list=model.parameters())
    EPOCH_NUM = 10
    for epoch_id in range(EPOCH_NUM):
      	...................

上面的模型训练代码和上一节代码相比,多了三句话,用于指定数据的异步读取:
与同步数据读取相比,异步数据读取仅增加了三行代码,如下所示。

	place = fluid.CPUPlace() 或者 place = fluid.CUDAPlace(0)
	# 设置读取的数据是放在CPU还是GPU上。
	
	data_loader = fluid.io.DataLoader.from_generator(capacity=5, return_list=True) 	
	# 创建一个DataLoader对象用于加载Python生成器产生的数据。
	# 数据会由Python线程预先读取,并异步送入一个队列中。
	# capacity : 表示在DataLoader中维护的队列容量,如果读取数据的速度很快,建议设置为更大的值。
	# return_list : 在动态图模式下需要设置为True,静态图模式下设置为False。
	
	data_loader.set_batch_generator(train_loader, place) 
	# 用创建的DataLoader对象设置一个数据生成器set_batch_generator
	# 输入的参数是一个Python数据生成器train_loader和服务器资源类型place(标明CPU还是GPU)

从异步数据读取的训练结果来看,损失函数下降与同步数据读取训练结果一致。注意,异步读取数据只在数据量规模巨大时会带来显著的性能提升,对于多数场景采用同步数据读取的方式已经足够。

模型的建立

全连接网络

这里定义用的网络为 78410,1010,10*1

# 多层全连接神经网络实现
class MNIST(fluid.dygraph.Layer):
    def __init__(self):
        super(MNIST, self).__init__()
        # 定义两层全连接隐含层,输出维度是 10,激活函数为 sigmoid
        self.fc1 = Linear(input_dim=784, output_dim=10, act='sigmoid') # 隐含层节点为10,可根据任务调整
        self.fc2 = Linear(input_dim=10, output_dim=10, act='sigmoid')
        # 定义一层全连接输出层,输出维度是1,不使用激活函数
        self.fc3 = Linear(input_dim=10, output_dim=1, act=None)
    
    # 定义网络的前向计算
    def forward(self, inputs, label=None):
        inputs = fluid.layers.reshape(inputs, [inputs.shape[0], 784])
        outputs1 = self.fc1(inputs)
        outputs2 = self.fc2(outputs1)
        outputs_final = self.fc3(outputs2)
        return outputs_final
model = MNIST()
model

结果:
在这里插入图片描述
计算上面模型的准确率:

# 通过with语句创建一个dygraph运行的context,
# 动态图下的一些操作需要在guard下进行
correct = 0
count = 0
with fluid.dygraph.guard():
    model = MNIST()
   # 加载模型参数
    model_dict, _ = fluid.load_dygraph("mnist")
    model.load_dict(model_dict)
    test_loader = load_data('eval')
    for batch_id, data in enumerate(test_loader()):
        #准备数据,格式需要转换成符合框架要求的
        image_data, label_data = data
        # 将数据转为飞桨动态图格式
        image = fluid.dygraph.to_variable(image_data)
        label = fluid.dygraph.to_variable(label_data)
        model.eval()#启动模型评价
        #前向计算的过程
        predict = model(image)
        pre = predict.numpy().astype('int32')    
        correct=correct+np.sum(pre==label_data)
        count = count+len(image_data)
print(f"正确率为:{correct/count*100:.2f}%") 

结果为:
在这里插入图片描述

卷积神经网络模型

结果:卷积-池化-卷积-池化-全连接(980,1):

# 多层卷积神经网络实现
class MNIST(fluid.dygraph.Layer):
     def __init__(self):
         super(MNIST, self).__init__()
         
         # 定义卷积层,输出特征通道num_filters设置为20,卷积核的大小filter_size为5,卷积步长stride=1,padding=2
         # 激活函数使用relu
         self.conv1 = Conv2D(num_channels=1, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
         # 定义池化层,池化核pool_size=2,池化步长为2,选择最大池化方式
         self.pool1 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
         # 定义卷积层,输出特征通道num_filters设置为20,卷积核的大小filter_size为5,卷积步长stride=1,padding=2
         self.conv2 = Conv2D(num_channels=20, num_filters=20, filter_size=5, stride=1, padding=2, act='relu')
         # 定义池化层,池化核pool_size=2,池化步长为2,选择最大池化方式
         self.pool2 = Pool2D(pool_size=2, pool_stride=2, pool_type='max')
         # 定义一层全连接层,输出维度是1,不使用激活函数
         self.fc = Linear(input_dim=980, output_dim=10, act='softmax')
         
    # 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
     def forward(self, inputs):
         x = self.conv1(inputs)
         x = self.pool1(x)
         x = self.conv2(x)
         x = self.pool2(x)
         x = fluid.layers.reshape(x, [x.shape[0], -1])
         x = self.fc(x)
         return x

神经网络的训练时采用交叉熵损失函数:

#仅修改计算损失的函数,从均方误差(常用于回归问题)到交叉熵误差(常用于分类问题)
with fluid.dygraph.guard():
    model = MNIST()
    model.train()
    #调用加载数据的函数
    train_loader = load_data('train')
    optimizer = fluid.optimizer.SGDOptimizer(learning_rate=0.01, parameter_list=model.parameters())
    #optimizer = fluid.optimizer.MomentumOptimizer(learning_rate=0.01, momentum=0.9, parameter_list=model.parameters())
    #optimizer = fluid.optimizer.AdagradOptimizer(learning_rate=0.01, parameter_list=model.parameters())
    #optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.01, parameter_list=model.parameters())
    EPOCH_NUM = 5
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            #准备数据,变得更加简洁
            image_data, label_data = data
            image = fluid.dygraph.to_variable(image_data)
            label = fluid.dygraph.to_variable(label_data)
            
            #前向计算的过程
            predict = model(image)
            
            #计算损失,使用交叉熵损失函数,取一个批次样本损失的平均值
            loss = fluid.layers.cross_entropy(predict, label)
            avg_loss = fluid.layers.mean(loss)
            
            #每训练了200批次的数据,打印下当前Loss的情况
            if batch_id % 200 == 0:
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))
            
            #后向传播,更新参数的过程
            avg_loss.backward()
            optimizer.minimize(avg_loss)
            model.clear_gradients()

    #保存模型参数
    fluid.save_dygraph(model.state_dict(), 'mnist')

由于这里使用了softmax,输出的是 one-hot 编码,因此测试时需要进行一下转换:

# 通过with语句创建一个dygraph运行的context,
# 动态图下的一些操作需要在guard下进行
correct = 0
count = 0
with fluid.dygraph.guard(place):
    model = MNIST()
   # 加载模型参数
    model_dict, _ = fluid.load_dygraph("mnist")
    model.load_dict(model_dict)
    test_loader = load_data('eval')
    for batch_id, data in enumerate(test_loader()):
        #准备数据,格式需要转换成符合框架要求的
        image_data, label_data = data
        # 将数据转为飞桨动态图格式
        image = fluid.dygraph.to_variable(image_data)
        label = fluid.dygraph.to_variable(label_data)
        model.eval()#启动模型评价
        #前向计算的过程
        predict = model(image)
        pres = predict.numpy().astype('int32')   
        result = [np.argmax(one_hot) for one_hot in pres]
        re = np.array(result).reshape(-1,1)
        correct=correct+np.sum(re==label_data)
        count = count+len(image_data)
print(f"正确率为:{correct/count*100:.2f}%") 

结果:
在这里插入图片描述

GPU训练模型

飞桨动态图通过fluid.dygraph.guard(place=None)里的place参数,设置在GPU上训练还是CPU上训练。

with fluid.dygraph.guard(place=fluid.CPUPlace()) #设置使用CPU资源训神经网络。
with fluid.dygraph.guard(place=fluid.CUDAPlace(0)) #设置使用GPU资源训神经网络,默认使用服务器的第一个GPU卡。"0"是GPU卡的编号,比如一台服务器有的四个GPU卡,编号分别为0、1、2、3。

你可能感兴趣的:(Paddle,神经网络)