Paddle_手写数字识别

        本文采用Paddle深度学习框架实现手写数字识别,用python语言实现,包含一些其他第三方库。代码和数据均来源于百度AIstudio。


目录

前言

一、数据处理

数据集介绍

 数据集导入

  效验数据有效性

二、网络结构

三、训练过程

调用GPU进行训练

 损失函数

反向传播

 学习率设置

训练总代码

四、模型的保存和加载

效果展示

 五、模型测试


前言

        如果说房价预测是机器学习的“Hello world”,那么手写数字识别就是深度学习的“Hello world”。采用MNIST数据集包括50 000条训练样本、10 000条验证样本、10 000条测试样本。每个样本包含手写数字图片和对应的标签。本文将重点从数据处理,网络结构,训练过程,模型的保存和加载等几个方面讲解。


一、数据处理

数据集介绍

        data包含三个元素的列表:train_set、val_set、 test_set,包括50 000条训练样本、10 000条验证样本、10 000条测试样本。每个样本包含手写数字图片和对应的标签。其中训练集样本用于确定模型参数;验证集样本用于调节模型超参数;测试机样本用于评估模型的质量。训练集train_set包含两个元素的列表:train_images、train_labels。其中train_images是[50 000, 784]的二维列表,包含50 000张图片。每张图片用一个长度为784的向量表示,内容是28*28尺寸的像素灰度值(黑白图片)。train_labels是[50 000, ]的列表,表示这些图片对应的分类标签,即0~9之间的一个数字。

如图所示:

Paddle_手写数字识别_第1张图片

 数据集导入

# 定义数据集读取器
def load_data(mode='train'):
    # 读取数据文件
    datafile = 'mnist.json.gz'
    print('loading mnist dataset from {} ......'.format(datafile))
    data = json.load(gzip.open(datafile))
    # 读取数据集中的训练集,验证集和测试集
    train_set, val_set, eval_set = data

    # 数据集相关参数,图片高度IMG_ROWS, 图片宽度IMG_COLS
    IMG_ROWS = 28
    IMG_COLS = 28
    # 根据输入mode参数决定使用训练集,验证集还是测试
    if mode == 'train':
        imgs = train_set[0]
        labels = train_set[1]
    elif mode == 'valid':
        imgs = val_set[0]
        labels = val_set[1]
    elif mode == 'eval':
        imgs = eval_set[0]
        labels = eval_set[1]

        datafile为文件的路径名。gzip.open表示打开此压缩文件,因为文件是json格式,所以用json.load加载数据并赋值给data。此时的data为一个列表,内包含三个元素分别是训练集数据,验证集数据和测试集数据。分别用train_set,val_set和eval_set表示这三个数据样本。因为本次使用的数据集的图片是28*28的灰度图。所以设置图片的长IMG_ROWS,IMG_COLS分别为28。再根据模型是训练模式,验证模式还是测试模式分别读取对应的数据。


    # 获得所有图像的数量
    imgs_length = len(imgs)
    # 验证图像数量和标签数量是否一致
    assert len(imgs) == len(labels), \
        "length of train_imgs({}) should be the same as train_labels({})".format(
            len(imgs), len(labels))

  效验数据有效性

        在实际的生产中,我们所获得的数据可能会存在一些问题 如数据标注不正确,数据格式不统一等。因此,我们需要增加一些操作来验证数据是否正确。

    # 校验数据
    imgs_length = len(imgs)

    assert len(imgs) == len(labels), \
        "length of train_imgs({}) should be the same as train_labels({})".format(len(imgs), len(labels))

        imgs是若干行,784列的矩阵。也可以理解为二维数组或者是一个具有若干个元素的列表。使用len()来求出元素的个数即样本的数量,与其标签的数量进行对比,若相等,则表明数据格式没有问题,若不相等,则表明数据格式存在问题。assert是python语言里的关键字,相当于if,如果assert后面的语句为真,则跳过;若果为假,则执行后面的语句。

    # 定义数据集每个数据的序号,根据序号读取数据
    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:
            # 将数据处理成希望的类型
            img = np.array(imgs[i]).astype('float32')
            label = np.array(labels[i]).astype('float32')
            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)

       这里batchsize定义为100,表示每次向网络里喂入100组数据,即100个样本。用list()定义一个列表index_list为从0到样本数量的列表。之后定义一个数据生成器。本文不重点去介绍数据生成器,读者只需知道它可以提高训练速度即可。数据读取和模型训练并行。读取到的数据不断的放入缓存区,无需等待模型训练就可以启动下一轮数据读取。当模型训练完一个批次后,不用等待数据读取过程,直接从缓存区获得下一批次数据进行训练,从而加快了数据读取速度。用random.shuffle()将index_list列表乱序。之后重新定义2组空的列表。

        之后依次遍历index_list列表。此时的列表是乱序以后的对应的序号,从imgs取出对应序号的元素,即784个元素的列表并将其转化为ndarray格式类型为“float32”,标签同理;并添加到对应元素的列表中去。当样本数够一个batchsize时,返回一组数据。注意这里也可以先转化格式,之后在进行乱序。因为shuffle只会对最外层的元素进行乱序。

        最后的if语句是说,当数据不够以一个batch的时候,将剩余的数据返回。

二、网络结构

        对于之前的房价预测问题。我们可以简单地去定义一个全连接神经网络,因为是一组数据可以线性输入,线性输出;但对于手写数字识别来说,如果简单的使用全连接神经网络,不会达到很好的预测效果。这里采用卷积层,池化层和全连接层来搭建神经网络。

代码如下:

# 多层卷积神经网络实现
class MNIST(paddle.nn.Layer):
    def __init__(self):
        super(MNIST, self).__init__()

        # 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
        self.conv1 = Conv2D(in_channels=1, out_channels=20, kernel_size=5, stride=1, padding=2)
        # 定义池化层,池化核的大小kernel_size为2,池化步长为2
        self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        # 定义卷积层,输出特征通道out_channels设置为20,卷积核的大小kernel_size为5,卷积步长stride=1,padding=2
        self.conv2 = Conv2D(in_channels=20, out_channels=20, kernel_size=5, stride=1, padding=2)
        # 定义池化层,池化核的大小kernel_size为2,池化步长为2
        self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        # 定义一层全连接层,输出维度是10
        self.fc = Linear(in_features=980, out_features=10)

    # 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
    # 卷积层激活函数使用Relu,全连接层激活函数使用softmax
    def forward(self, inputs):
        x = self.conv1(inputs)
        x = F.relu(x)
        x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.max_pool2(x)
        x = paddle.reshape(x, [x.shape[0], 980])
        x = self.fc(x)
        return x

首先定义一个MNIST类,并继承paddle.nn.Layer这个类。 supper()._init_()表示初始化父类的一些参数。Con2D表示卷积层,MaxPool2D表示池化层,Linear表示全连接层,他们全都是类。在MNIST类里定义一个forward方法表示前向传播,relu为激活函数。因为最后一层是全连接层,维度只能是1,所以之前要reshape一下。

三、训练过程

调用GPU进行训练

        在计算机视觉这一领域,使用GPU来训练显然会获得比CPU训练更快的训练速度。paddle也提供了一些使用GPU的接口。

    # 开启GPU
    use_gpu = True
    paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')

        一台计算机或者服务器可以有多个GPU,每个GPU都有与之对应的编号gpu:0表示使用第0号GPU进行训练。

 损失函数

            # 计算损失,取一个批次样本损失的平均值
            loss = F.cross_entropy(predicts, labels)
            avg_loss = paddle.mean(loss)

这里使用的是cross_entropy这一损失函数,并计算平均值。

反向传播

            # 后向传播,更新参数的过程
            avg_loss.backward()
            opt.step()
            opt.clear_grad()

 学习率设置

    # 设置不同初始学习率
    opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters())

这里的学习率设置为0.01,parameters = model.parameters()表示对所有参数进行梯度下降。

训练总代码

def train(model):
    # 开启GPU
    use_gpu = True
    paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
    model.train()
    # 调用加载数据的函数
    train_loader = load_data('train')

    # 设置不同初始学习率
    opt = paddle.optimizer.Adam(learning_rate=0.01, parameters=model.parameters())

    EPOCH_NUM = 5
    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            # 准备数据,变得更加简洁
            images, labels = data
            images = paddle.to_tensor(images)
            labels = paddle.to_tensor(labels)

            # 前向计算的过程
            predicts = model(images)

            # 计算损失,取一个批次样本损失的平均值
            loss = F.cross_entropy(predicts, labels)
            avg_loss = paddle.mean(loss)

            # 每训练了100批次的数据,打印下当前Loss的情况
            if batch_id % 200 == 0:
                print("epoch: {}, batch: {}, loss is: {}".format(epoch_id, batch_id, avg_loss.numpy()))

            # 后向传播,更新参数的过程
            avg_loss.backward()
            opt.step()
            opt.clear_grad()

         训练迭代5轮,每轮当读取200份样本时打印loss

四、模型的保存和加载

    # 保存模型参数
    paddle.save(model.state_dict(), 'mnist.pdparams')

model.state_dict表示读取模型的参数,后面的字符串为模型保存的地址

效果展示

Paddle_手写数字识别_第2张图片

 五、模型测试

import numpy as np
import paddle
from PIL import Image
from Number import MNIST

def load_image(img_path):
    # 从img_path中读取图像,并转为灰度图
    im = Image.open(img_path).convert('L')
    im = im.resize((28, 28), Image.ANTIALIAS)
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
    # 图像归一化
    im = 1.0 - im / 255.
    return im

# 定义预测过程
model = MNIST()
params_file_path = 'mnist.pdparams'
img_path = 'example_6.jpg'
# 加载模型参数
param_dict = paddle.load(params_file_path)
model.load_dict(param_dict)
# 灌入数据
model.eval()
tensor_img = load_image(img_path)
#模型反馈10个分类标签的对应概率
results = model(paddle.to_tensor(tensor_img))
#取概率最大的标签作为预测输出
lab = np.argsort(results.numpy())
print("本次预测的数字是: ", lab[0][-1])

Paddle_手写数字识别_第3张图片

你可能感兴趣的:(Paddlepaddle,python,深度学习,计算机视觉)