【深度学习】手写数字识别任务
- 软件开发
- 2025-08-25 19:27:02

数字识别是计算机从纸质文档、照片或其他来源接收、理解并识别可读的数字的能力,目前比较受关注的是手写数字识别。手写数字识别是一个典型的图像分类问题,已经被广泛应用于汇款单号识别、手写邮政编码识别等领域,大大缩短了业务处理时间,提升了工作效率和质量。
学习总结本次学习先写总结,因为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.卷积神经网络【深度学习】手写数字识别任务由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【深度学习】手写数字识别任务”