minst 手写数字识别实战

综述

手写数字识别是每个学习神经网络的人上手操作的必由之路,此次实验基于paddlepaddle框架在百度AI Studio上进行实战,在fork的学习项目基础上做了数据集的修改以及网络结构的改良,最终可正确识别黑底白字及白底黑字的手写数字,在20k张的测试集上呈现99.313%的准确率,基本达到实验目的。

 

网络结构

本次实验原有基础网络结构为两层全连接层+一层全连接输出层,经过多次调参优化,实验发现在测试集上的准确率始终无法超过98%,实验效果并不理想。

最终,在Lenet-5结构的启发下,实验在原有基础上对图片做了卷积-池化-卷积-池化的处理,即两层卷积-池化层+两层全连接层+全连接输出层。经过多次调参,测试集上表现效果优秀,准确率最终可达 99.313%

其中部分超参数设置如下:

1. 卷积-池化层:滤波器大小5,滤波器数量20,池化层kernel大小2*2,池化层步长2,激活函数“relu”

2. 卷积-池化层:滤波器大小5,滤波器数量20,池化层kernel大小2*2,池化层步长2,激活函数“relu”

3. 全连接层: 神经元数量100,激活函数“relu”

4. 全连接层: 神经元数量100,激活函数“relu”

5. 全连接输出层: 输出神经元数量10,激活函数“softmax”

 

数据集

本次实验数据集来源有二,其一为黑底白字的minst开源数据集,已由paddlepaddle封装提供,其二为自制白底黑字的压缩包文件(含标签)

数据集大小为,训练集(60k黑底白字+60k白底黑字)+测试集(10k黑底白字+10k白底黑字)

 

实验结果

共进行五轮训练,每轮训练都会在测试集上测试。

此处只展示第五轮训练结束后最终网络表现: 损失:0.01924  准确率:0.99313

minst 手写数字识别实战_第1张图片

 

代码简述

 

1.导入需要的包

import numpy as np
import paddle as paddle
import paddle.fluid as fluid
from PIL import Image
import matplotlib.pyplot as plt
import os
import zipfile
import struct
import re

2.处理数据集zip文件

因本实验采用两个数据集来源,其一是被制作为压缩包的白底黑字数据集,其二是由paddlepaddle官方提供的封装好的paddle.dataset.mnist。因此次步骤目的为处理白底黑字数据集的压缩包,解压至"data/"下以便读取 

is_extract_zip = True

if is_extract_zip:
    
    train_zip = zipfile.ZipFile('/home/aistudio/data/data7475/train_examples_labels.zip') 
    test_zip = zipfile.ZipFile('/home/aistudio/data/data7475/test_examples_labels.zip')
    
    train_zip.extractall('data/')
    test_zip.extractall('data/')

3.解析白底黑字数据集的label,并加载

标注训练集与测试集路径

# 训练集标签文件
train_labels_idx1_ubyte_file = 'data/train_examples_labels/train-labels.idx1-ubyte'

# 测试集标签文件
test_labels_idx1_ubyte_file = 'data/test_examples_labels/t10k-labels.idx1-ubyte'

定义解析minist数据集idx1文件的通用函数及加载训练集和测试集的函数

def decode_idx1_ubyte(idx1_ubyte_file):
    """
    解析idx1文件的通用函数
    :param idx1_ubyte_file: idx1文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx1_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数和标签数
    offset = 0
    fmt_header = '>ii'
    magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
    print ('魔数:%d, 图片数量: %d张' % (magic_number, num_images))

    # 解析数据集
    offset += struct.calcsize(fmt_header)
    fmt_image = '>B'
    labels = np.empty(num_images)
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print ('已解析 %d' % (i + 1) + '张')
        labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
        offset += struct.calcsize(fmt_image)
    return labels


def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
 
    return decode_idx1_ubyte(idx_ubyte_file)


def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):

    return decode_idx1_ubyte(idx_ubyte_file)

标注图片路径,加载label

# 图片路径
train_img = 'data/train_examples_labels/train_turn/'
test_img = 'data/test_examples_labels/test_new/'

# 加载label
train_labels = load_train_labels()
test_labels = load_test_labels()

4.数据预处理

定义load_image函数用于对图片进行预处理,将RGB转化为灰度图像,L代表灰度图像,灰度图像的像素值在0~255之间。图像大小为28*28,返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式,之后归一化到【-1~1】之间。

def load_image(file):
    im = Image.open(file).convert('L')                        #将RGB转化为灰度图像,L代表灰度图像,灰度图像的像素值在0~255之间
    im = im.resize((28, 28), Image.ANTIALIAS)                 #resize image with high-quality 图像大小为28*28
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)#返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式。
    im = im / 255.0 * 2.0 - 1.0                               #归一化到【-1~1】之间
    im = im.reshape((28*28))
    # print(im.size)
    return im

加载训练集与测试集入list以便创建reader。因白底黑字数据集中label会出现图片与label对应不一致情况,所以这里使用文件名中的图片序号大小为key进行排序。

# 加载训练data
train_data = []
train_file_names = []
list = os.listdir(train_img)
for i in range(0,len(list)):
    file = os.path.join(train_img,list[i])
    train_file_names.append(file)
train_file_names.sort(key = lambda x: int(x[38:-4]))    #按照index排序
for file in train_file_names:
    train_data.append(load_image(file))

# 加载测试data
test_data = []
test_file_names = []
list = os.listdir(test_img)
for i in range(0,len(list)):
    file = os.path.join(test_img,list[i])
    test_file_names.append(file)
test_file_names.sort(key = lambda x: int(x[35:-4]))    #按照index排序
for file in test_file_names:
    test_data.append(load_image(file))

以迭代循环创建reader  

def reader_createor(data, label):
    def reader():
        for i in  range(len(data)):
            yield data[i], int(label[i])
    return reader

调用paddle.batch合并黑底白字reader及白底黑字reader,其中黑底白字reader为paddlepaddle直接提供已封装好的

train_reader = paddle.batch(paddle.reader.shuffle(paddle.reader.chain(reader_createor(train_data, train_labels),paddle.dataset.mnist.train()),
                                                  buf_size=512),
                    batch_size=128)
test_reader = paddle.batch(paddle.reader.chain(reader_createor(test_data, test_labels),paddle.dataset.mnist.test()),
                          batch_size=128)

5.定义多层感知机

在此网络结构采用两个卷积-池化层+两个全连接层+一个全连接输出层

def multilayer_perceptron(input):
    
    conv_pool_1 = fluid.nets.simple_img_conv_pool(

        input=input,         # 输入图像

        filter_size=5,     # 滤波器的大小

        num_filters=20,    # filter 的数量。它与输出的通道相同

        pool_size=2,       # 池化层大小2*2

        pool_stride=2,     # 池化层步长

        act="relu")        # 激活类型
    
    conv_pool_2 = fluid.nets.simple_img_conv_pool(

        input=conv_pool_1,         # 输入图像

        filter_size=5,     # 滤波器的大小

        num_filters=20,    # filter 的数量。它与输出的通道相同

        pool_size=2,       # 池化层大小2*2

        pool_stride=2,     # 池化层步长

        act="relu")        # 激活类型
        
    # 第一个全连接层,激活函数为ReLU
    hidden1 = fluid.layers.fc(input=conv_pool_2, size=100, act='relu')
    
    # 第二个全连接层,激活函数为ReLU
    hidden2 = fluid.layers.fc(input=hidden1, size=100, act='relu')
    
    # 以softmax为激活函数的全连接输出层,大小为10
    prediction = fluid.layers.fc(input=hidden2, size=10, act='softmax')
    
    return prediction

6.定义输入输出层

image = fluid.layers.data(name='image', shape=[1, 28, 28], dtype='float32')  #单通道,28*28像素值
label = fluid.layers.data(name='label', shape=[1], dtype='int64')            #图片标签

7.获取分类器

model = multilayer_perceptron(image)

8.获取损失函数和准确率函数

在此使用交叉熵损失函数,描述真实样本标签和预测概率之间的差值  

cost = fluid.layers.cross_entropy(input=model, label=label)  #使用交叉熵损失函数,描述真实样本标签和预测概率之间的差值
avg_cost = fluid.layers.mean(cost)
acc = fluid.layers.accuracy(input=model, label=label)

9.定义优化方法

使用Adam算法进行优化

optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.001)   #使用Adam算法进行优化
opts = optimizer.minimize(avg_cost)

10.定义一个使用CPU的解析器并进行参数初始化

# 定义一个使用CPU的解析器
place = fluid.CPUPlace()
exe = fluid.Executor(place)
# 进行参数初始化
exe.run(fluid.default_startup_program())

11.定义输入数据维度

feeder = fluid.DataFeeder(place=place, feed_list=[image, label])

12.开始训练和测试

共进行五轮训练,每训练一轮,进行一次测试

# 开始训练和测试
for pass_id in range(5):
    # 进行训练
    for batch_id, data in enumerate(train_reader()):                        #遍历train_reader
        train_cost, train_acc = exe.run(program=fluid.default_main_program(),#运行主程序
                                        feed=feeder.feed(data),             #给模型喂入数据
                                        fetch_list=[avg_cost, acc])         #fetch 误差、准确率
        # 每100个batch打印一次信息  误差、准确率
        if batch_id % 100 == 0:
            print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
                  (pass_id, batch_id, train_cost[0], train_acc[0]))

    # 进行测试
    test_accs = []
    test_costs = []
    #每训练一轮 进行一次测试
    for batch_id, data in enumerate(test_reader()):                         #遍历test_reader
        test_cost, test_acc = exe.run(program=fluid.default_main_program(), #执行训练程序
                                      feed=feeder.feed(data),               #喂入数据
                                      fetch_list=[avg_cost, acc])           #fetch 误差、准确率
        test_accs.append(test_acc[0])                                       #每个batch的准确率
        test_costs.append(test_cost[0])                                     #每个batch的误差
    # 求测试结果的平均值
    test_cost = (sum(test_costs) / len(test_costs))                         #每轮的平均误差
    test_acc = (sum(test_accs) / len(test_accs))                            #每轮的平均准确率
    print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' % (pass_id, test_cost, test_acc))
    
    #保存模型
    model_save_dir = "/home/aistudio/data/hand.inference.model"
    # 如果保存路径不存在就创建
    if not os.path.exists(model_save_dir):
        os.makedirs(model_save_dir)
    print ('save models to %s' % (model_save_dir))
    fluid.io.save_inference_model(model_save_dir,  #保存推理model的路径
                                  ['image'],      #推理(inference)需要 feed 的数据
                                  [model],        #保存推理(inference)结果的 Variables
                                  exe)            #executor 保存 inference model

 

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