minst 手写数字识别实战

通过改进网络结构和调参,利用PaddlePaddle框架在百度AIStudio实现手写数字识别,最终在20k测试集上达到99.313%的准确率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

综述

手写数字识别是每个学习神经网络的人上手操作的必由之路,此次实验基于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

 

代码简述

 

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

 

### 使用Python实现MNIST手写数字识别 以下是基于深度学习框架PyTorch和TensorFlow的两种方法来实现MNIST手写数字识别的任务。 --- #### 方法一:使用PyTorch实现MNIST手写数字识别 以下是一个完整的代码示例,展示了如何利用PyTorch构建神经网络模型并训练它以完成MNIST数据集的手写数字分类任务: ```python import torch import torchvision import torchvision.transforms as transforms import torch.nn as nn import torch.optim as optim import matplotlib.pyplot as plt # 数据预处理 transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,))]) trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transform) trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True) testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transform) testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=False) # 定义简单的全连接神经网络 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.fc1 = nn.Linear(28 * 28, 128) # 输入层到隐藏层 self.fc2 = nn.Linear(128, 64) # 隐藏层到更深层次 self.fc3 = nn.Linear(64, 10) # 输出层 def forward(self, x): x = x.view(-1, 28 * 28) # 展平输入图片 x = torch.relu(self.fc1(x)) x = torch.relu(self.fc2(x)) x = self.fc3(x) return x device = torch.device("cuda" if torch.cuda.is_available() else "cpu") model = Net().to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 训练过程 for epoch in range(5): # 循环遍历整个数据集多次 running_loss = 0.0 for i, data in enumerate(trainloader, 0): inputs, labels = data[0].to(device), data[1].to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: # 每100个小批量打印一次 print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f}") running_loss = 0.0 print("Finished Training") # 测试模型性能 correct = 0 total = 0 with torch.no_grad(): for data in testloader: images, labels = data[0].to(device), data[1].to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f'Accuracy of the network on the 10000 test images: {100 * correct / total}%') ``` 此代码实现了加载MNIST数据集、定义简单神经网络架构以及训练和测试的过程[^2]。 --- #### 方法二:使用TensorFlow/Keras实现MNIST手写数字识别 另一种流行的方法是使用TensorFlow及其高级API Keras。下面是一段简洁而高效的代码示例: ```python import tensorflow as tf from tensorflow.keras import layers, models import matplotlib.pyplot as plt # 加载MNIST数据集 mnist = tf.keras.datasets.mnist (x_train, y_train), (x_test, y_test) = mnist.load_data() # 归一化像素值至 [0, 1] x_train, x_test = x_train / 255.0, x_test / 255.0 # 构建模型 model = models.Sequential([ layers.Flatten(input_shape=(28, 28)), layers.Dense(128, activation='relu'), layers.Dropout(0.2), layers.Dense(10, activation='softmax') ]) model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy']) # 训练模型 history = model.fit(x_train, y_train, epochs=5, validation_split=0.1) # 绘制损失曲线 plt.plot(history.history['loss'], label="Training Loss") plt.plot(history.history['val_loss'], label="Validation Loss") plt.legend() plt.show() # 测试模型 test_loss, test_acc = model.evaluate(x_test, y_test, verbose=2) print(f'\nTest accuracy: {test_acc}') ``` 这段代码同样完成了从数据准备到模型评估的一系列操作,并提供了可视化工具以便观察训练效果[^3]。 --- ### 总结 无论是选择PyTorch还是TensorFlow/Keras,都可以高效地解决MNIST手写数字识别问题。两者各有优劣,具体取决于个人偏好或团队技术栈的选择。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值