【深度学习】手写数字识别任务

news/2025/2/24 17:43:01

数字识别是计算机从纸质文档、照片或其他来源接收、理解并识别可读的数字的能力,目前比较受关注的是手写数字识别。手写数字识别是一个典型的图像分类问题,已经被广泛应用于汇款单号识别、手写邮政编码识别等领域,大大缩短了业务处理时间,提升了工作效率和质量。

学习总结

本次学习先写总结,因为paddle教程和网络上的同学们都得到了不错的效果,而我按照paddle或者网上的步骤得不到预期的结果。希望有大神可以指教一下,问题到底出在哪里。
运行环境:
在这里插入图片描述

1、paddle的代码运行不过

paddle的代码:
在这里插入图片描述
我的代码:
在这里插入图片描述

2.运行效果不一致

数据处理用的是这种方式

from data_process import get_MNIST_dataloader

train_loader, test_loader = get_MNIST_dataloader()

paddle的效果:
1、paddle一共有800批次,所以不知道paddle用的batch_size是不是和代码保持了一致。
2、paddle的损失差不多是0.1,而我的损失是2.
在这里插入图片描述
在这里插入图片描述

3.预测效果不一致

从paddle的最终结果看,是预测正确了的。我自己训练的结果没有一个是正确的。
在这里插入图片描述
在这里插入图片描述

深度学习步骤

再次回顾深度学习的步骤
1、数据处理
2、模型设计
3、训练配置
4、训练过程
5、模型保存
6、模型推理

代码目录结构,经过前面的学习,目录也进行了调整
image:准备用实际的jpg图片来预测
work: 保存了训练数据集和训练后的模型
在这里插入图片描述

1.数据处理

数据处理用两种形式,因为两种形式带来的效果差别有些大。
方式一:

# 数据处理部分之前的代码,加入部分数据处理的库
import gzip
import json

class MNISTDataset(Dataset):
    """
    步骤一:继承paddle.io.Dataset类
    """

    def __init__(self, datafile, mode='train', transform=None):
        """
        步骤二:实现构造函数
        """
        super().__init__()

        self.mode = mode
        self.transform = transform

        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':
            # 获得训练数据集
            self.imgs, self.labels = train_set[0], train_set[1]
        elif mode == 'valid':
            # 获得验证数据集
            self.imgs, self.labels = val_set[0], val_set[1]
        elif mode == 'test':
            # 获得测试数据集
            self.imgs, self.labels = eval_set[0], eval_set[1]
        else:
            raise Exception("mode can only be one of ['train', 'valid', 'test']")

    def __getitem__(self, index):
        """
        步骤三:实现__getitem__方法,定义指定index时如何获取数据
        """
        data = self.imgs[index]
        label = self.labels[index]

        return self.transform(data), label

    def __len__(self):
        """
        步骤四:实现__len__方法,返回数据集总数目
        """
        return len(self.imgs)

def get_MNIST_dataloader():
    datafile = './work/mnist.json.gz'
    # 下载数据集并初始化 DataSet
    train_dataset = MNISTDataset(datafile, mode='train', transform=transform)
    test_dataset = MNISTDataset(datafile, mode='test', transform=transform)
    print('train images: ', train_dataset.__len__(), ', test images: ', test_dataset.__len__())
    # 定义并初始化数据读取器
    train_loader = paddle.io.DataLoader(
        train_dataset, batch_size=64, shuffle=True, num_workers=1, drop_last=True)
    test_loader = paddle.io.DataLoader(
        test_dataset, batch_size=64, shuffle=False, num_workers=1, drop_last=True)
    print('step num:',len(train_loader))
    return train_loader, test_loader

这种方式paddle官方说用transform进行归一化处理,但是通过查看mnist.json.gz里面的数据可以看出,数据集已经是[-1,1]之间的数据了。
方式二:

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

    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)

    # 定义数据集每个数据的序号,根据序号读取数据
    index_list = list(range(imgs_length))
    # 读入数据时用到的批次大小
    BATCHSIZE = 100

    # 定义数据生成器
    def data_generator():
        if mode == 'train':
            # 训练模式下打乱数据
            random.shuffle(index_list)
        imgs_list = []
        labels_list = []
        print_0 = True
        for i in index_list:
            # 将数据处理成希望的类型
            img = np.array(imgs[i]).astype('float32')
            label = np.array(labels[i]).astype('float32')
            if print_0:
                print("图像数据形状和对应数据为:", img)
                print("图像标签形状和对应数据为:", label.shape, label)
                print_0 = False
            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

2.模型设计

使用与房价预测相同的简单神经网络解决手写数字识别问题,但是效果并不理想。原因是手写数字识别的输入是28×28的像素值,输出是0~9的数字标签,而线性回归模型无法捕捉二维图像数据中蕴含的复杂信息。

2.1.经典的全连接神经网络(MLP)

在这里插入图片描述
在这里插入图片描述
这里的代码是paddle官方的最终的代码
在paddle_model.py中实现模型设计

# 数据处理部分之后的代码,数据读取的部分调用Load_data函数

# 加载飞桨、NumPy和相关类库
import paddle
import paddle.nn.functional as F
from paddle.nn import Linear
from tools import plot

# from data_process import get_MNIST_dataloader
# train_loader,_ = get_MNIST_dataloader()


# 数据处理部分之后的代码,数据读取的部分调用Load_data函数
# 定义网络结构,同上一节所使用的网络结构
class MNIST(paddle.nn.Layer):
    def __init__(self):
        super(MNIST, self).__init__()
        # 定义两层全连接隐含层,输出维度是10,当前设定隐含节点数为10,可根据任务调整
        self.fc1 = Linear(in_features=784, out_features=10)
        self.fc2 = Linear(in_features=10, out_features=10)
        # 定义一层全连接输出层,输出维度是1
        self.fc3 = Linear(in_features=10, out_features=1)

    # 定义网络的前向计算,隐含层激活函数为sigmoid,输出层不使用激活函数
    def forward(self, inputs):
        inputs = paddle.reshape(inputs, [inputs.shape[0], 784])
        outputs1 = self.fc1(inputs)
        outputs1 = F.sigmoid(outputs1)
        outputs2 = self.fc2(outputs1)
        outputs2 = F.sigmoid(outputs2)
        outputs_final = self.fc3(outputs2)
        return outputs_final


# 网络结构部分之后的代码,保持不变
def train(model):
    model.train()
    from data_process import load_data
    train_loader = load_data(mode='train')
    # 使用SGD优化器,learning_rate设置为0.01
    opt = paddle.optimizer.SGD(learning_rate=0.01, parameters=model.parameters())
    # 训练5轮
    EPOCH_NUM = 10

    loss_list = []

    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            # 准备数据
            images, labels = data
            images = paddle.to_tensor(images, dtype="float32")
            labels = paddle.to_tensor(labels, dtype="float32")
            # 前向计算的过程
            predicts = model(images)

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

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

            # 后向传播,更新参数的过程
            avg_loss.backward()
            # 最小化loss,更新参数
            opt.step()
            # 清除梯度
            opt.clear_grad()

    # 保存模型参数
    paddle.save(model.state_dict(), './work/mnist_mlp.pdparams')
    return loss_list


model = MNIST()
params_info = paddle.summary(model, (1, 1, 28, 28))
loss_list = train(model)
plot(loss_list)

2.2.卷积神经网络(CNN)

虽然使用经典的全连接神经网络可以提升一定的准确率,但其输入数据的形式导致丢失了图像像素间的空间信息,这影响了网络对图像内容的理解。对于计算机视觉问题,效果最好的模型仍然是卷积神经网络。卷积神经网络针对视觉问题的特点进行了网络结构优化,可以直接处理原始形式的图像数据,保留像素间的空间信息,因此更适合处理视觉问题。
卷积神经网络由多个卷积层和池化层组成,如图所示。卷积层负责对输入进行扫描以生成更抽象的特征表示,池化层对这些特征表示进行过滤,保留最关键的特征信息。
在这里插入图片描述

# 定义 SimpleNet 网络结构
import paddle
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F
from data_process import get_MNIST_dataloader
from tools import plot


# 多层卷积神经网络实现
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)
        # 定义一层全连接层,输出维度是1
        self.fc = Linear(in_features=980, out_features=10)

    # 定义网络前向计算过程,卷积后紧接着使用池化层,最后使用全连接层计算最终输出
    # 卷积层激活函数使用Relu,全连接层不使用激活函数
    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], -1])
        x = self.fc(x)
        return x

# 网络结构部分之后的代码,保持不变
def train(model):
    # 在使用GPU机器时,可以将use_gpu变量设置成True
    use_gpu = True
    from data_process import load_data
    train_loader = load_data(mode='train')
    # train_loader, test_loader = get_MNIST_dataloader()
    paddle.set_device('gpu:0') if use_gpu else paddle.set_device('cpu')
    model.train()

    learning_rate = 0.001

    # 四种优化算法的设置方案,可以逐一尝试效果
    opt = paddle.optimizer.SGD(learning_rate=learning_rate, parameters=model.parameters())
    # opt = paddle.optimizer.Momentum(learning_rate=learning_rate, momentum=0.9, parameters=model.parameters())
    # opt = paddle.optimizer.Adagrad(learning_rate=learning_rate, parameters=model.parameters())
    # opt = paddle.optimizer.Adam(learning_rate=learning_rate, parameters=model.parameters())
    # 训练5轮
    EPOCH_NUM = 10
    # MNIST图像高和宽
    IMG_ROWS, IMG_COLS = 28, 28

    loss_list = []

    for epoch_id in range(EPOCH_NUM):
        for batch_id, data in enumerate(train_loader()):
            # 准备数据
            images, labels = data
            images = paddle.to_tensor(images, dtype="float32")
            images = paddle.reshape(images, [images.shape[0], 1, IMG_ROWS, IMG_COLS])
            labels = paddle.to_tensor(labels, dtype="int64")
            # 前向计算的过程
            predicts = model(images)  # [batch_size, 1]

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

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

            # 后向传播,更新参数的过程
            avg_loss.backward()
            # 最小化loss,更新参数
            opt.step()
            # 清除梯度
            opt.clear_grad()

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

    return loss_list

if __name__ == '__main__':
    model = MNIST()
    params_info = paddle.summary(model, (1, 1, 28, 28))
    print(params_info)
    loss_list_conv = train(model)
    plot(loss_list_conv)

3.训练配置

4.训练过程

训练配置和训练过程的代码都在模型设计中一并给出了,这里只给出最终的训练效果。

4.1经典的全连接神经网络

在这里插入图片描述
在这里插入图片描述

4.2.卷积神经网络

在这里插入图片描述
在这里插入图片描述

5.保存模型

6.模型推理

经典的全连接神经网络与卷积神经网络除了模型地址不一样,其他都是一样的代码。

#加载飞桨、NumPy和相关类库
import paddle
from paddle_model_cnn import MNIST
import numpy as np
from PIL import Image
import matplotlib

matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

# 读取一张本地的样例图片,转变成模型输入的格式
def load_image(img_path):
    # 从img_path中读取图像,并转为灰度图
    im = Image.open(img_path).convert('L')
    print(np.array(im))
    im = im.resize((28, 28), Image.LANCZOS)
    im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
    # 图像归一化,保持和数据集的数据范围一致
    im = 1 - im / 255
    return im

def predict():
    # 将模型参数保存到指定路径中
    # 定义预测过程
    model = MNIST()
    params_file_path = './work/mnist_cnn.pdparams'

    # 加载模型参数
    param_dict = paddle.load(params_file_path)
    model.load_dict(param_dict)
    # 灌入数据
    model.eval()
    for index in range(0, 10):
        img_path = 'image/{}.jpg'.format(index)
        tensor_img = load_image(img_path)
        # 模型反馈10个分类标签的对应概率
        results = model(paddle.to_tensor(tensor_img))
        # 取概率最大的标签作为预测输出
        lab = np.argsort(results.numpy())
        print("本次预测的数字是: ", lab[0][-1])

if __name__ == '__main__':
    predict()

6.1.全连接神经网络

在这里插入图片描述

6.2.卷积神经网络

在这里插入图片描述


http://www.niftyadmin.cn/n/5864661.html

相关文章

Linux 命令大全完整版(11)

5.文件管理命令 diff&#xff08;differential&#xff09; 功能说明&#xff1a;比较文件的差异。语  法&#xff1a;diff [-abBcdefHilnNpPqrstTuvwy][-<行数>][-C <行数>][-D <巨集名称>][-I <字符或字符串>][-S <文件>][-W <宽度>…

DeepSeek本地搭建 和 Android

DeepSeek 搭建和 Android 文章目录 DeepSeek 搭建和 Android一、前言二、DeepSeek 本地环境ollama搭建1、软件下载网址&#xff1a;2、Ollama的安装3、配置算法模型和使用qwen2 模型使用&#xff0c; 三、Android Studio 和 DeepSeek四、其他1、Deepseek 使用小结(1) 网页版本可…

通俗理解Test time Scaling Law、RL Scaling Law和预训练Scaling Law

一、Scaling Law解释 1、预训练阶段的Scaling Law&#xff08;打地基阶段&#xff09; 通俗解释&#xff1a;就像建房子时&#xff0c;地基越大、材料越多、施工时间越长&#xff0c;房子就能盖得越高越稳。 核心&#xff1a;通过堆资源&#xff08;算力、数据、模型参数&am…

Express + MongoDB 实现在筛选时间段中用户名的模糊查询

使用 $gte&#xff08;大于等于&#xff09;和 $lte&#xff08;小于等于&#xff09;操作符构建时间段查询条件。使用 $regex 操作符进行模糊查询&#xff0c;$options: i 表示不区分大小写。使用 $and 操作符将它们组合起来。 // 处理查询的路由app.get("/users",…

[设计模式] Builder 建造者模式

目录 意图 问题 解决 Applying the Builder pattern 主管 结构 伪代码 生成器模式适合应用场景 实现方法 生成器模式优缺点 与其他模式的关系 C代码 main.cc&#xff1a;概念示例 Output.txt&#xff1a;执行结果 意图 Builder 是一种创建性设计模式&#xff0c…

C语言【指针篇】(一)

前言 指针基础概念理解&#xff0c;从底层出发理解指针 C语言【指针篇】&#xff08;一&#xff09; 前言正文1. 内存和地址1.1 内存1.2 究竟该如何理解编址 2. 指针变量和地址2.1 取地址操作符(&)2.2 指针变量和解引用操作符(*)2.3 指针变量的大小 3. 指针变量类型的意义…

Qt 中集成mqtt协议

一&#xff0c;引入qmqtt 库 我是将整个头文件/源文件都添加到了工程中进行编译&#xff0c;这样 跨平台时 方便&#xff0c;直接编译就行了。 原始仓库路径&#xff1a;https://github.com/emqx/qmqtt/tree/master 二&#xff0c;使用 声明一个单例类&#xff0c;将订阅到…

from flask_session import Session 为什么是Session(app)这么用?

在 Flask 中&#xff0c;from flask_session import Session 和 Session(app) 的用法是为了配置和使用 Flask-Session 扩展&#xff0c;将用户的会话&#xff08;Session&#xff09;数据存储到服务器端&#xff08;如 Redis、数据库或文件系统&#xff09;&#xff0c;而不是默…