从零设计一个神经网络:实现手写数字识别

news2024/9/22 3:37:10

前言

为了能够更好的理解神经网络,从手写数字识别这个小任务来逐层弄清楚神经网络的工作原理以及一般流程是非常合适的。

这篇文章就来手写完成一个数字识别的任务,来说明如何设计、实现并训练一个标准的前馈神经网络,以期对神经网络有一个更加具体感性的认识。

概述

具体来说,我们要设计并训练一个3层的神经网络,这个神经网络会以数字图像作为输入,经过神经网络的计算,就会识别出图像中的数字是几,从而实现数字图像的分类:

在这里插入图片描述

在这个过程中,主要讲解三个方面:神经网络的设计和实现、训练数据的准备和处理、模型的训练和测试流程。

在这里插入图片描述

神经网络的设计和实现

为了设计一个处理图像数据的神经网络,需要首先明确输入的图像数据的大小和格式。

在这里插入图片描述

我们要处理的图片,尺寸是 28 × 28 像素的灰色通道图像(MNIST 数据集本身的格式)。

这样的灰色图像包括了 2828 = 784 个数据点,我们要先将它展平成 1784 大小的向量:

在这里插入图片描述

然后再将这个向量输入到神经网络中,我们会使用一个三层神经网络来处理图片对应的向量 x,输入层需要接收 784 维的图片向量 x,x 中的每个维度的数据都有一个神经元来接收,因此输入层要包含 784 个神经元:

在这里插入图片描述

隐藏层用于特征提取,将输入的特征向量处理为更高级的特征向量。

由于手写数字图像并不复杂,这里就将隐藏层的神经元个数设置为 256,这样输入层与隐藏层之间就会有一个 784*256 大小的线性层:

在这里插入图片描述

它可以将一个 784 维的输入向量转换维 256 维的输出向量,该输出向量会继续向前传播到达输出层。

由于最终要将数字图像识别为 0 到 9 十种可能的数字,因此输出层需要定义 10 个神经元来对应这十种数字:

在这里插入图片描述

256 维的向量在经过隐藏层和输出层之间的线性层计算后,就得到了 10 维的输出结果,这个 10 维的向量就代表了 10 个数字的预测得分:

在这里插入图片描述

为了继续得到 10 个数字的预测概率,我们还要将输出层的输出输入到 softmax 层,softmax 层会将 10 维的向量转换为 10 个概率值 P0 到 P9,每个概率值都对应一个数字,也就是输入图片是某一个数字的可能性,另外 P0 到 P9 这 10 个概率值相加到一起的总和是 1,这是由 softmax 函数的性质决定的:

在这里插入图片描述

以上就是神经网络的设计思路,接下来我们使用 PyTorch 框架来实现一下。

首先实现一下我们的神经网络:

在这里插入图片描述

代码如下:

import torch
from torch import nn


# 定义神经网络
class NetWork(nn.Module):
    def __init__(self):
        super().__init__()
        # 线性层1,输入层和隐藏层之间的线性层
        self.layer1 = nn.Linear(784, 256)
        # 线性层2,隐藏层和输出层之间的线性层
        self.layer2 = nn.Linear(256, 10)
        # 这里没有直接定义 softmax 层
        # 这是因为后面会使用 CrossEntropyLoss 损失函数
        # 在这个损失函数中会实现 softmax 的计算

    def forward(self, x):
        x = x.view(-1, 28 * 28)  # 使用view 函数将 x 展平
        x = self.layer1(x)  # 将 x 输入至 layer1
        x = torch.relu(x)  # 使用 ReLu 函数激活
        return self.layer2(x)  # 输入至 layer2 计算结果

训练数据的准备和处理

接下来准备数据集:

在这里插入图片描述

对于数据集的获取,还可以使用下面这种方式从 PyTorch 官网上进行下载:

# 准备数据集
train_data = torchvision.datasets.MNIST(root='data', train=True, download=True)
test_data = torchvision.datasets.MNIST(root='data', train=False, download=True)

通过这种方式下载下来的数据会自动存储在 train_data 以及 test_data 中,相对应的会有一个工具包被下载在我们在代码中所指定的目录下:

在这里插入图片描述

但是这并非是通用的方式,在以后的工作学习中有很多数据集需要我们自己进行操作处理并不会像上面这样方便,因此这里介绍更为通用的一种方式,也就是去官网下载原生的数据集,没错,就是一大堆图片!

我们将下载下来的数据保存为下面两个文件夹:

在这里插入图片描述

我们将数据分别保存到 train 和 test 两个目录中,其中 train 有 60000 个数据,test 有 10000 个数据,它们分别用来模型的训练和测试。

在 train 和 test 这两个目录中,都包括了十个子目录,子目录的名字就对应了图像中的数字。例如,在名为 3 的文件夹中就保存了数字 3 的图像:

在这里插入图片描述

其中图像的名称是随机的字符串签名。

完成数据的准备后,实现数据的读取功能,初学者在学习这一部分时只要知道大致的数据处理流程就可以了。

代码实现如下:

# 首先实现图像的预处理pipeline
transform = torchvision.transforms.Compose([
    torchvision.transforms.Grayscale(num_output_channels=1),  # 转换为单通道灰度图像
    torchvision.transforms.ToTensor()  # 转换为PyTorch支持的张量
])

# 这是方式一准备数据集的方式嗷
train_data = torchvision.datasets.MNIST(root='data', train=True, download=True, transform=transform)
test_data = torchvision.datasets.MNIST(root='data', train=False, download=True, transform=transform)

# 下面这是方式二准备数据集的方式
# 使用 ImageFolder 函数读取数据文件夹,构建数据集 dataset
# 这个函数会将保存数据的文件夹的名字,作为数据的标签,组织数据
# 例如,对于名字为 3 的文件夹
# 就会将 3 作为文件夹中图像数据的标签,和图像配对用于后续的训练,使用起来非常方便
train_dataset = torchvision.datasets.ImageFolder(root='./mnist/train', transform=transform)
test_dataset = torchvision.datasets.ImageFolder(root='./mnist/test', transform=transform)

# 不管使用哪种准备数据集的方式,最后的效果都是一样的
# 打印它们的长度看一下
print(len(train_data))
print(len(test_data))

运行结果看一下:

在这里插入图片描述

可以看到训练数据和测试数据都拿到了,和我们之前说的也完全相符。

然后我们使用 train_loader 来实现小批量的数据读取:

# 使用 train_loader 实现小批量的数据读取
# 这里设置小批量的大小,batch_size = 64。
# 也就是每个批次包括 64 个数据
train_loader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
# 打印 train_loader 的长度
print(" train loader length: ", len(train_loader))
# 60000 个训练数据,如果每个小批量读入 64 个样本,那么 60000 个数据会被分为 938 组
# 计算 938 * 64 = 60032,这说明最后一组会不够 64 个数据

运行结果如下:

在这里插入图片描述

然后我们可以循环遍历 train_loader 来获取每个小批量数据:

# 循环遍历 train_loader
# 每一次循环,都会取出 64 个图像数据,作为一个小批量 batch
for batch_idx, (data, label) in enumerate(train_loader):
    if batch_idx == 3:  # 打印前三个 batch 观察
        break
    print("batch_idx: ", batch_idx)
    print("data.shape: ", data.shape)  # 数据的尺寸
    print("label: ", label.shape)  # 图像中的数字
    print(label)

这里对于上述循环语句做一个简单的解释会更清晰:

enumerate(): 这是Python的内置函数,用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,即同时得到索引和值。在这里,它被用来遍历train_loader中的每个批次(batch),其中batch_idx是当前批次的索引(从0开始),(data, label)是当前批次的数据和标签。

for batch_idx, (data, label) in enumerate(train_loader):: 这个循环语句的意思是,对于train_loader中的每一个批次,都执行循环体内的代码。在每个循环迭代中,batch_idx是当前批次的索引,(data, label)是当前批次的数据和标签。data通常是一个包含多个数据点的张量(Tensor),每个数据点都是一个样本;label是与这些数据点相对应的标签张量,用于监督学习中的目标值。

运行效果如下:

在这里插入图片描述

从运行结果可以看出:

1、batch_idx = 0 表示是第一批数据

2、data.shape 表示数据的尺寸为 [64, 1, 28, 28]

上述尺寸表示每批数据包括 64 个图像,每个图像有 1 个灰色通道,图像的尺寸是 28*28 。

3、label.shape 表示该批次中共有 64 个数字对应的 label 数量总数为 64 个,每个数字都有一个 label 嘛

注意区别,真实的 label 类别肯定只有 9 个,因为数字就只有 1 到 9,而这里是指 label 值的数量有 64 个。

4、tensor 数组则表示的是这 64 张数字图片各自所对应的 label 标签值

模型的训练与测试流程

有了前面的准备之后,我们就可以开始模型的训练和测试了。

下面是训练代码:

# 在使用 PyTorch 训练模型时,需要创建三个对象
model = NetWork()  # 1、模型本身,它就是我们设计的神经网络
optimizer = torch.optim.Adam(model.parameters())  # 2、优化器,优化模型中的参数
criterion = nn.CrossEntropyLoss()  # 3、损失函数,分类问题使用交叉熵损失误差

# 开始训练模型
for epoch in range(10):  # 外层循环,代表了整个训练数据集的遍历次数
    # 整个训练集要循环多少轮,是10次还是20次还是100次都是有可能的
    # 内层循环使用train_loader 进行小批量的数据读取
    for batch_idx, (data, label) in enumerate(train_loader):
        # 内层每循环一次,就会进行一次梯度下降算法
        # 包括五个步骤:
        output = model(data)  # 1、计算神经网络的前向传播结果
        loss = criterion(output, label)  # 2、计算 output 和标签 label 之间的误差损失 loss
        loss.backward()  # 3、使用 backward 计算梯度
        optimizer.step()  # 4、使用 optimizer.step 更新参数
        optimizer.zero_grad()  # 5、将梯度清零
        # 这五个步骤是使用 PyTorch 框架训练模型的定式,初学的时候记住就可以了
        # 每迭代 100 个小批量,就打印一次模型的损失,观察训练的过程
        if batch_idx % 100 == 0:
            print(f"Epoch {epoch + 1} / 10 "
                  f"| Batch {batch_idx} / {len(train_loader)}"
                  f"| Loss: {loss.item():.4f}")

torch.save(model.state_dict(), 'mnist.pth')  # 最后保存训练好的模型

运行效果如下:

在这里插入图片描述
中间省略…
在这里插入图片描述

可以看到最后的损失值已经非常非常小了,为 0.0239 。

最后是测试,测试的流程和训练基本差不多,代码如下:

model = NetWork()  # 定义神经网络模型
model.load_state_dict(torch.load('mnist.pth'))  # 加载刚刚训练好的模型文件

right = 0  # 保存正确识别的数量
for i, (x, y) in enumerate(test_data):
    output = model(x)  # 将其中的数据 x 输入到模型中
    predict = output.argmax(1).item()  # 选择概率最大的标签作为预测结果
    # 对比预测值 predict 和真实标签 y
    if predict == y:
        right += 1
    else:
        # 将识别错误的样例打印出来
        print(f"wrong case: predict = {predict}, but y = {y}")

# 计算出测试结果
sample_num = len(test_data)
accuracy = right * 1.0 / sample_num
print("test accuracy = %d / %d = %.3lf" % (right, sample_num, accuracy))

运行结果如下:

在这里插入图片描述

可以看到测试的准确率为 98%,还是很高的。

以上就是从零设计并训练神经网络的过程。

总结与代码封装

上面都是按照各个功能部分进行讲解和描述的,可能会有点混乱,我们将上面的代码封装如下。

训练代码

import torch
import torchvision.datasets
from torch import nn


# ----------------1、定义神经网络-------------------
class NetWork(nn.Module):
    def __init__(self):
        super().__init__()
        # 线性层1,输入层和隐藏层之间的线性层
        self.layer1 = nn.Linear(784, 256)
        # 线性层2,隐藏层和输出层之间的线性层
        self.layer2 = nn.Linear(256, 10)
        # 这里没有直接定义 softmax 层
        # 这是因为后面会使用 CrossEntropyLoss 损失函数
        # 在这个损失函数中会实现 softmax 的计算

    def forward(self, x):
        x = x.view(-1, 28 * 28)  # 使用view 函数将 x 展平
        x = self.layer1(x)  # 将 x 输入至 layer1
        x = torch.relu(x)  # 使用 ReLu 函数激活
        return self.layer2(x)  # 输入至 layer2 计算结果


# ----------------2、图像预处理-------------------
# 实现图像的预处理的pipeline
transform = torchvision.transforms.Compose([
    torchvision.transforms.Grayscale(num_output_channels=1),  # 转换为单通道灰度图像
    torchvision.transforms.ToTensor()  # 转换为PyTorch支持的张量
])

# ----------------3、数据集准备-------------------
# 准备训练数据集
train_data = torchvision.datasets.MNIST(root='data', train=True, download=True, transform=transform)

# ----------------4、数据集加载-------------------
# 使用 train_loader 实现小批量的数据读取
# 这里设置小批量的大小,batch_size = 64。
# 也就是每个批次包括 64 个数据
train_loader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)

# ----------------5、训练神经网络-------------------
# 在使用 PyTorch 训练模型时,需要创建三个对象
model = NetWork()  # 1、模型本身,它就是我们设计的神经网络
optimizer = torch.optim.Adam(model.parameters())  # 2、优化器,优化模型中的参数
criterion = nn.CrossEntropyLoss()  # 3、损失函数,分类问题使用交叉熵损失误差

# 开始训练模型
for epoch in range(10):  # 外层循环,代表了整个训练数据集的遍历次数
    # 整个训练集要循环多少轮,是10次还是20次还是100次都是有可能的
    # 内层循环使用train_loader 进行小批量的数据读取
    for batch_idx, (data, label) in enumerate(train_loader):
        # 内层每循环一次,就会进行一次梯度下降算法
        # 包括五个步骤:
        output = model(data)  # 1、计算神经网络的前向传播结果
        loss = criterion(output, label)  # 2、计算 output 和标签 label 之间的误差损失 loss
        loss.backward()  # 3、使用 backward 计算梯度
        optimizer.step()  # 4、使用 optimizer.step 更新参数
        optimizer.zero_grad()  # 5、将梯度清零
        # 这五个步骤是使用 PyTorch 框架训练模型的定式,初学的时候记住就可以了
        # 每迭代 100 个小批量,就打印一次模型的损失,观察训练的过程
        if batch_idx % 100 == 0:
            print(f"Epoch {epoch + 1} / 10 "
                  f"| Batch {batch_idx} / {len(train_loader)}"
                  f"| Loss: {loss.item():.4f}")

torch.save(model.state_dict(), 'mnist.pth')  # 最后保存训练好的模型

测试代码

import torch
import torchvision.datasets
from torch import nn


# ----------------1、定义神经网络-------------------
class NetWork(nn.Module):
    def __init__(self):
        super().__init__()
        # 线性层1,输入层和隐藏层之间的线性层
        self.layer1 = nn.Linear(784, 256)
        # 线性层2,隐藏层和输出层之间的线性层
        self.layer2 = nn.Linear(256, 10)
        # 这里没有直接定义 softmax 层
        # 这是因为后面会使用 CrossEntropyLoss 损失函数
        # 在这个损失函数中会实现 softmax 的计算

    def forward(self, x):
        x = x.view(-1, 28 * 28)  # 使用view 函数将 x 展平
        x = self.layer1(x)  # 将 x 输入至 layer1
        x = torch.relu(x)  # 使用 ReLu 函数激活
        return self.layer2(x)  # 输入至 layer2 计算结果


# ----------------2、图像预处理-------------------
# 实现图像的预处理的pipeline
transform = torchvision.transforms.Compose([
    torchvision.transforms.Grayscale(num_output_channels=1),  # 转换为单通道灰度图像
    torchvision.transforms.ToTensor()  # 转换为PyTorch支持的张量
])

# ----------------3、数据集准备-------------------
# 准备测试数据集
test_data = torchvision.datasets.MNIST(root='data', train=False, download=True, transform=transform)

# ----------------4、测试神经网络-------------------
model = NetWork()  # 定义神经网络模型
model.load_state_dict(torch.load('mnist.pth'))  # 加载刚刚训练好的模型文件

right = 0  # 保存正确识别的数量
for i, (x, y) in enumerate(test_data):
    output = model(x)  # 将其中的数据 x 输入到模型中
    predict = output.argmax(1).item()  # 选择概率最大的标签作为预测结果
    # 对比预测值 predict 和真实标签 y
    if predict == y:
        right += 1
    else:
        # 将识别错误的样例打印出来
        print(f"wrong case: predict = {predict}, but y = {y}")

# 计算出测试结果
sample_num = len(test_data)
accuracy = right * 1.0 / sample_num
print("test accuracy = %d / %d = %.3lf" % (right, sample_num, accuracy))

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1921726.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI编程助手-Tabnine的使用体验

文章目录 一,安装使用1,VSCode安装Tabnine插件2,使用 三,Tabnine的工作原理1,深度学习的力量2,注意事项:最大化Tabnine的效能 在编程的世界里,每一行代码都承载着创造者的智慧与汗水…

ubuntu安装YOLOV8环境

文章目录 前言 前言 ubuntu20.04 使用vmware虚拟机 1、安装python sudo apt-get install python3 python3-pip2,安装虚拟环境 sudo apt install python3.8-venv3,创建虚拟环境 python3 -m venv yolov8-env4,进入虚拟环境 source yolov8…

测试人必会 K8S 操作之 Dashboard

在云计算和微服务架构的时代,Kubernetes (K8S) 已成为管理容器化应用的标准。然而,对于许多新手来说,K8S 的操作和管理常常显得复杂而神秘。特别是,当你第一次接触 K8S Dashboard 时,你是否也感到有些无所适从&#xf…

十大CRM系统对比:选出最适合你的工具

本文将分享10款优质CRM系统:纷享销客、Zoho CRM、HubSpot、Salesforce、悟空CRM、销售易、Pipedrive、Oracle CRM、Insightly、SugarCRM。 在选择CRM系统时,很多企业主和管理者都面临着一个难题:市面上的品牌众多,到底哪个才是最…

《昇思25天学习打卡营第14天|SSD目标检测》

SSD(Single Shot MultiBox Detector)是一种用于目标检测的深度学习算法。它的设计旨在同时检测多个对象,并确定它们在图像中的位置和类别。与其他目标检测算法相比,SSD具有速度快和精度高的特点,在实时检测应用中非常受…

python 代码设计贪吃蛇

代码: # -*- codeing utf-8 -*- import tkinter as tk import random from tkinter import messageboxclass Snake:def __init__(self, master):self.master masterself.master.title("Snake")# 创建画布self.canvas tk.Canvas(self.master, width400,…

Centos忘记密码,重置root密码

Centos忘记密码,重置root密码 操作环境:Centos7.6 1、选择包含rescue的选项,按e进入编辑模式 首先,我们需要重启系统,进入开机引导菜单界面。在这里,我们可以看到系统的内核版本和启动参数等信息。我们需…

期权专题12:期权保证金和期权盈亏

目录 1. 期权保证金 1.1 计算逻辑 1.2 代码复现 1.3 实际案例 2. 期权盈亏 2.1 价格走势 2.2 计算公式 2.2.1 卖出期权 2.2.2 买入期权 免责声明:本文由作者参考相关资料,并结合自身实践和思考独立完成,对全文内容的准确性、完整性或…

龙迅#LT8644EX适用于HDMI2.0 4进4出矩阵应用,分辨率最高支持4K60HZ!

1. 概述 LT8644EX是一款 1616 数字交叉点开关,具有 16 个差分 CML 兼容输入和 16 个差分 CML 输出。该LT8644EX针对每个端口的数据速率高达 6 Gbps 的不归零 (NRZ) 信令进行了优化。每个端口都提供可编程的输入均衡电平和可编程输出摆幅。…

10个Python函数参数进阶用法及代码优化

目录 1. 默认参数值:让函数更加灵活 2. 关键字参数:清晰的调用方式 3. *args:拥抱不确定数量的位置参数 4. **kwargs:处理不确定数量的关键字参数 5. 参数解包:简化多参数的传递 6. 命名关键字参数:限…

【第31章】MyBatis-Plus之注解配置

文章目录 前言一、注解介绍二、注解列表总结 前言 本文详细介绍了 MyBatisPlus 注解的用法及属性,提供了源码链接以便深入理解。欢迎通过下方链接查看注解类的源码。 Mybatis-Plus Annotation 源码 一、注解介绍 Mybatis-Plus注解统一存放在com.baomidou.mybatis…

PS 2024【最新】中文白嫖版!,安装教程,图文步骤

文章目录 软件介绍软件下载安装步骤 软件介绍 Photoshop,简称“PS” Adobe Photoshop,简称“PS”,是由Adobe Systems开发和发行的图像处理软件。Photoshop主要处理以像素所构成的数字图像。使用其众多的编修与绘图工具,可以有效地…

python调用阿里云汇率接口

整体请求流程 介绍: 本次解析通过阿里云云市场的云服务来实现程序中对货币汇率实时监控,首先需要准备选择一家可以提供汇率查询的商品。 https://market.aliyun.com/apimarket/detail/cmapi00065831#skuyuncode5983100001 步骤1: 选择商品 如图点击…

洁净室行业“”尘埃粒子计数器原理及品牌选型

尘埃粒子计数器原理 尘埃粒子计数器是一种基于激光粒子检测技术的精密仪器,用于测量洁净环境中单位体积内尘埃粒子的数量和粒径分布。其工作原理基于光散射现象:当空气中的微粒在激光束的照射下,会发生散射,散射光的强度与微粒的…

年薪60w的前端阿里P7专家,顶尖的技术人才,只因做到了这几点

全方位讲解前端主流框架Recat、Vue在项目中的应用、解析核心源码以及内存机制、核心技术点、架构设计思想等。从根源解决开发难题。 移动端App开发专题 了解移动端适配常见难点、学习Dart语法,掌握控件、布局、动画、操作手势、传感器、线程网络以及交互等核心技能。…

服务攻防——中间件Jboss

文章目录 一、Jboss简介二、Jboss渗透2.1 JBoss 5.x/6.x 反序列化漏洞(CVE-2017-12149)2.2 JBoss JMXInvokerServlet 反序列化漏洞(CVE-2015-7501)2.3 JBossMQ JMS 反序列化漏洞(CVE-2017-7504)2.4 Adminis…

elasticsearch源码分析-04集群状态发布

集群状态发布 cluster模块封装了在集群层面执行的任务,如集群健康、集群级元信息管理、分片分配给节点、节点管理等。集群任务执行之后可能会产生新的集群状态,如果产生新的集群状态主节点会将集群状态广播给其他节点。 集群状态封装在clusterState中&…

5款文案生成神器,自动一键生成原创文案

文案在我们的生活中随处可见,好的文案内容不仅可以为企业带来销售转化,而且还能提升品牌的影响力,因此文案的重要性可想而知,对于文案创作者来说,写作好的文案不是轻松容易的事,但如果把这个任务交给文案生…

【低照度图像增强系列(8)】URetinex-Net算法详解与代码实现(2022|CVPR)

前言 ☀️ 在低照度场景下进行目标检测任务,常存在图像RGB特征信息少、提取特征困难、目标识别和定位精度低等问题,给检测带来一定的难度。 🌻使用图像增强模块对原始图像进行画质提升,恢复各类图像信息,再使用目标检…

手机也能玩转AI影像?荣耀200系列带来手机人像摄影新升级

智能手机在影像技术上的革新从未停歇,荣耀200系列系统的全新升级,为用户带来了更加丰富和便捷的拍摄体验。AI技术的融入,让摄影变得更加智能,简化了编辑过程,使得每个用户都能成为专业的摄影师。 一、AI消除功能&#…