当前位置: 首页 > news >正文

深度学习-MNIST手写数字识别(MLP)

前言:我是一名刚开始学习深度学习的小白,可能是我不会找资源吧(官方是有一些示例的,但都学我没那么多时间),也没找到什么可以跟练的项目。下面是我学习到的内容(来自up主DT算法工程师前钰,好多人搬视频,认准正版),做一个分享。如果大家有什么学习建议,十分乐意倾听您的指教@~@

torchvision.datasets里面包含了一些常用的图像数据集(比如 MNIST 手写数字数据集),直接调用即可下载使用。所以我选择复现里面的MNIST手写数字识别项目(属于图像分类)做个小的入门吧,没有卷积层,是一个纯全连接神经网络,属于深度学习中的 “多层感知机(MLP)” 范畴。

神经网络算法

输入层是展平的神经元,前向传播,通过线性变换,相加求和得到隐藏层。再通过ReLu增加非线性,隐藏层神经元可自定义,层数也自定义。输出层通过softmax计算概率,再与真实值比较,计算损失Loss,再反向传播,计算梯度,跟新线性变换的参数。

数据处理及加载

train.py文件

from torchvision import datasets,transforms   # torchvision.datasets包含常用的图像数据集(比如MNIST手写数字数据集);torchvision.transforms包含图像预处理工具,比如缩放、转张量、归一化等
from torch.utils.data import DataLoader       # 批量加载数据的工具
import matplotlib.pyplot as plt               # 可视化绘图工具transform = transforms.Compose([                    # 图像预处理的组合操作transforms.Grayscale(num_output_channels=1),    # 转为灰度图,通道数为1transforms.ToTensor(),                          # 将图像转换为张量,像素值变为0-1(原始图像像素值为0-255)transforms.Normalize([0.5], [0.5])              # 归一化,标准化到[-1, 1],公式:(x - 均值) / 标准差,前一个[0.5] 是均值,后一个[0.5] 是标准差
])
# 加载MNIST数据集
train_dataset = datasets.MNIST(          # 加载MNIST训练数据集root='./data',                       # 数据集存放路径train=True,                          # 加载训练集transform=transform,                 # 应用预处理操作download=True                        # 如果数据集不存在则下载
)
# 加载MNIST测试数据集
test_dataset = datasets.MNIST(            # 加载MNIST测试数据集root='./data',                        # 数据集存放路径train=False,                          # 加载测试集transform=transform                   # 应用预处理操作
)
# 创建数据加载器
train_loader = DataLoader(               # 创建训练数据加载器dataset=train_dataset,               # 传入训练数据集batch_size=64,                       # 每个批次64张图像shuffle=True                         # 每个epoch打乱数据(epoch指数据集被完整遍历一次的过程)
)
# 创建测试数据加载器
test_loader = DataLoader(                 # 创建测试数据加载器dataset=test_dataset,                 # 传入测试数据集batch_size=64,                        # 每个批次64张图像shuffle=False                         # 不打乱数据
)
# 获取一张训练集的图像和对应的标签
image,label = train_dataset[0] # 可视化图像
plt.imshow(image.squeeze(),cmap='gray')   # 灰度图是(1,H,W),用squeeze()去掉多余的通道维度,cmap='gray'指定为灰度图
plt.title(f'Label: {label}')              # 设置图像标题为标签
plt.axis('off')                           # 关闭坐标轴显示
plt.show()                                # 显示图像

模型构建

新建一个model.py文件

import torch                   # PyTorch深度学习框架核心库
import torch.nn as nn          # PyTorch的神经网络模块# 自定义神经网络模型
class SimpleNN(nn.Module):     # 自定义模型继承自nn.Module(PyTorch所有模型的基类)def __init__(self):        # 初始化函数,定义网络层super().__init__()     # 继承父类的初始化# 定义全连接层(也叫线性层,nn.Linear),是最基础的神经网络层,作用是对输入数据做线性变换(类似 y = wx + b,w 是权重,b 是偏置)self.fc1 = nn.Linear(28*28, 256) # 全连接层1,输入大小为28*28,输出大小为256( MNIST数据集的图片是28x28像素的灰度图,展平后就是一个长度为28*28的一维向量)self.fc2 = nn.Linear(256, 128)   # 全连接层2,输入大小为256,输出大小为128(输入必须和上一层输出一致)self.fc3 = nn.Linear(128, 64)    # 全连接层3,输入大小为128,输出大小为64      (中间层的256、128等是隐藏层的神经元熟练,是认为设定的,通过多层变换,学习到更复杂的特征)self.fc4 = nn.Linear(64, 10)     # 全连接层4,输入大小为64,输出大小为10(对应10个类别)def forward(self, x):                  # 前向传播函数,定义输入数据x如何流过网络x = torch.flatten(x,start_dim=1)   # 展平输入,维度start_dim=1表示从第1维开始展平,保持第0维(批次大小)不变,输入的图片格式是 (批量大小, 通道数, 高度, 宽度) x = torch.relu(self.fc1(x))        # 通过全连接层1,再应用ReLU激活函数(把负数变成 0,正数不变,给网络增加 “非线性”,让它能学习更复杂的规律)x = torch.relu(self.fc2(x))        # 通过全连接层2,再应用ReLU激活函数x = torch.relu(self.fc3(x))        # 通过全连接层3,再应用ReLU激活函数x = self.fc4(x)                    # 通过全连接层4(输出10个数字,不需要激活函数,后续计算损失时内部包含了softmax操作,进行概率计算)return x

模型训练

train.py文件(从第38行开始)

from torchvision import datasets,transforms   # torchvision.datasets包含常用的图像数据集(比如MNIST手写数字数据集);torchvision.transforms是图像预处理工具,比如缩放、转张量、归一化等
from torch.utils.data import DataLoader       # 批量加载数据的工具
import matplotlib.pyplot as plt               # 可视化绘图工具
from models import SimpleNN                    # 导入自定义的神经网络模型
import torchtransform = transforms.Compose([                    # 图像预处理的组合操作transforms.Grayscale(num_output_channels=1),    # 转为灰度图,通道数为1transforms.ToTensor(),                          # 将图像转换为张量,像素值变为0-1(原始图像像素值为0-255)transforms.Normalize([0.5], [0.5])              # 归一化,标准化到[-1, 1],公式:(x - 均值) / 标准差,前一个[0.5] 是均值,后一个[0.5] 是标准差
])
# 加载MNIST数据集
train_dataset = datasets.MNIST(          # 加载MNIST训练数据集root='./data',                       # 数据集存放路径train=True,                          # 加载训练集transform=transform,                 # 应用预处理操作download=True                        # 如果数据集不存在则下载
)
# 加载MNIST测试数据集
test_dataset = datasets.MNIST(            # 加载MNIST测试数据集root='./data',                        # 数据集存放路径train=False,                          # 加载测试集transform=transform                   # 应用预处理操作
)
# 创建数据加载器
train_loader = DataLoader(               # 创建训练数据加载器dataset=train_dataset,               # 传入训练数据集batch_size=64,                       # 每个批次64张图像shuffle=True                         # 每个epoch打乱数据(epoch指数据集被完整遍历一次的过程)
)
# 创建测试数据加载器
test_loader = DataLoader(                 # 创建测试数据加载器dataset=test_dataset,                 # 传入测试数据集batch_size=64,                        # 每个批次64张图像shuffle=False                         # 不打乱数据
)import torch.nn as nn            # PyTorch的神经网络模块
import torch.optim as optim      # 优化器模块,包含各种优化算法
from tqdm import tqdm            # 进度条显示工具#检查是否有可用的GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f'Using device: {device}')#初始化模型
model = SimpleNN().to(device)   # 实例化自定义的神经网络模型,并将其移动到指定设备(GPU或CPU)#定义损失函数和优化器
criterion = nn.CrossEntropyLoss()          # 交叉熵损失函数(错的越离谱值就越大),常用于多分类任务
optimizer = optim.Adam(model.parameters(), lr=0.001)  # Adam优化器(根据损失函数的结果,调整模型线性变换里的权重w和偏置b,学习率0.001是调整幅度)#用于保存训练过程中的损失和准确率
train_losses = []
train_accuracies = []
test_accuracies = []#训练模型
num_epochs = 10                          # 训练轮数
best_accuracy = 0.0                      # 用于保存最佳测试准确率
best_model_path = 'best_model.pth'       # 保存最佳模型的路径for epoch in range(num_epochs):running_loss = 0.0                   # 累计本轮的损失correct_train = 0                    # 训练集上正确预测的样本数total_train = 0                      # 训练集上总样本数model.train()                        # 设置模型为训练模式# 用tqdm显示进度条(desc参数设置进度条前缀),遍历训练集中的每一批数据for images, labels in tqdm(train_loader, desc=f'Epoch {epoch+1}/{num_epochs}'):images, labels = images.to(device), labels.to(device)  # 将数据移动到指定设备optimizer.zero_grad()            # 清零梯度outputs = model(images)          # 前向传播,模型对图片做预测loss = criterion(outputs, labels)# 计算损失loss.backward()                  # 反向传播optimizer.step()                 # 更新参数running_loss += loss.item()      # 累计损失_, predicted = torch.max(outputs, 1)     # 获取预测结果(_为最大值,predicted取最大值索引即预测数字),模型输出的二维张量格式是【batch_size,类别维度】,取1代表类别维度(概率)total_train += labels.size(0)            # 累计样本数correct_train += (predicted == labels).sum().item()  # 累计正确预测数# 计算训练集上的准确率train_accuracy = correct_train / total_traintrain_losses.append(running_loss / len(train_loader))  # 计算并保存本轮的平均损失train_accuracies.append(train_accuracy)print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {running_loss/len(train_loader):.4f}, Train Accuracy: {train_accuracy*100:.2f}%')# 训练完一轮后,在测试集上评估模型model.eval()                     # 设置模型为评估模式correct_test = 0                 # 测试集上正确预测的样本数total_test = 0                   # 测试集上总样本数with torch.no_grad():               # 评估时不计算梯度for images, labels in tqdm(test_loader, desc='Testing'):images, labels = images.to(device), labels.to(device)outputs = model(images)_, predicted = torch.max(outputs, 1)total_test += labels.size(0)correct_test += (predicted == labels).sum().item()test_accuracy = correct_test / total_test       # 计算测试集上的准确率test_accuracies.append(test_accuracy)print(f'Test Accuracy: {test_accuracy*100:.2f}%')# 保存最佳模型if test_accuracy > best_accuracy:best_accuracy = test_accuracytorch.save(model.state_dict(), best_model_path)     # 保存模型参数(权重和偏置)print(f'Best model saved with accuracy: {best_accuracy*100:.2f}%')print(f'Best Test Accuracy over all epochs: {best_accuracy*100:.2f}%')# 绘制损失曲线
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(train_losses, label='Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()
plt.grid(True)# 绘制准确率曲线
plt.subplot(1, 2, 2)
plt.plot(train_accuracies, label='Training Accuracy')
plt.plot(test_accuracies, label='Testing Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.title('Training and Testing Accuracy')
plt.legend()
plt.grid(True)plt.tight_layout()
plt.savefig('training_curves.png')  # 保存图像到文件
plt.show()

训练的轮数更多,Loss还能更少点,准确率还能更高点。

加载模型权重,推理图片

predict.py加载MNIST测试集中的图片进行预测

import cv2           # 用于图像显示和处理
import torch         # PyTorch深度学习框架核心库
from torchvision import datasets,transforms     # 数据集和图像预处理工具
from torch.utils.data import DataLoader         # 批量加载数据的工具
from models import SimpleNN                     # 导入自定义的神经网络模型transform = transforms.Compose([                # 图像预处理的组合操作transforms.Grayscale(num_output_channels=1),    transforms.ToTensor(),                          transforms.Normalize([0.5], [0.5])              
])# 加载MNIST测试集
test_dataset = datasets.MNIST(root='./data',train=False,transform=transform,download=True
)
# 创建测试集的数据加载器
test_loader = DataLoader(dataset=test_dataset,batch_size=64,shuffle=True
)model = SimpleNN()         # 实例化模型结构
model.load_state_dict(torch.load('best_model.pth'))     # 加载训练好的模型参数
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)           # 将模型移动到指定设备(GPU或CPU)model.eval()               # 设置模型为评估模式
with torch.no_grad():      # 关闭梯度计算for images, labels in test_loader:images, labels = images.to(device), labels.to(device)outputs = model(images)                   # 模型推理:输入图像,输出预测结果_, predicted = torch.max(outputs.data, 1) # 从输出概率中取最大值对应的索引,_是最大值(概率),predicted是索引(预测结果)# 遍历当前批次的每张图像,显示预测结果for i in range(images.size(0)):# 1. 处理图像格式:从张量转为OpenCV可显示的格式img = images[i].cpu().numpy().transpose(1, 2, 0)  # 从[C, H, W]转为[H, W, C](OpenCV要求的格式)img = (img * 0.5 + 0.5) * 255  # 反归一化,从[-1,-1]到[0, 255]img = img.astype('uint8')      # 转为整数类型(像素值必须是0-255的整数)# 2. 显示图像,窗口标题为预测结果img_resized = cv2.resize(img, (500, 500), interpolation=cv2.INTER_LINEAR)  # 放大图像以便观察(Inter_LINEAR双线性插值,避免模糊)cv2.imshow(f'Predicted: {predicted[i].item()}', img_resized)# 3. 按任意键显示下一张,按q键直接退出所有窗口key = cv2.waitKey(0)  # 等待按键if key & 0xFF == ord('q'):  # 按q退出cv2.destroyAllWindows() # 关闭所有窗口exit()  # 退出程序else:cv2.destroyWindow(f'Predicted: {predicted[i].item()}') # 关闭当前窗口cv2.destroyAllWindows() # 处理完一个批次后关闭所有窗口break  # 只处理一个批次以示例展示

predict1.py加载本地图片进行推理,需注意,图片预处理,以及输入图像的特征必须与训练集一致(黑底白字,像素为28*28),myfigure.png我没有调整像素大小,使用强制压缩图片到28*28,推理结果错误,myfigure2.jpg像素大小我手动裁剪过了,预测正确

import torch
import torch.nn as nn
from torchvision import transforms
from PIL import Image      # 用于读取自定义图片(MNIST数据集用datasets自动读,自定义图需手动读)
from models import SimpleNN
import matplotlib.pyplot as plt     # 用于显示图片# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f'Using device: {device}')# 定义图像预处理(和训练时保持一致)
transform = transforms.Compose([transforms.Grayscale(num_output_channels=1),   # 转为单通道灰度图transforms.Resize((28, 28)),                   # 调整图像大小为28x28像素(MNIST数据集的标准尺寸)transforms.ToTensor(),transforms.Normalize([0.5], [0.5])
])# 加载训练好的模型
model = SimpleNN().to(device)     # 实例化模型并放入设备
model.load_state_dict(torch.load('best_model.pth', map_location=device))  # 加载模型参数(map_location=device 确保参数加载到指定设备)
model.eval()                      # 设置模型为评估模式,关闭训练特有的层# 读取并处理单张图片
img_path = r'./myfigure2.jpg'   # 要推理的图片路径
image = Image.open(img_path)   # 使用PIL打开图片
image = transform(image).unsqueeze(0).to(device)  # 图片预处理+添加批次维度+放入设备(模型中的图片格式为B C H W是一个四维的张量,而图片格式是C H W,Batch size默认为1)# 进行预测
with torch.no_grad():           # 关闭梯度计算,节省内存和计算output = model(image)       # 将处理后的图片输入模型进行推理_, predicted = torch.max(output, 1)  # 获取预测结果print(f'Predicted class: {predicted.item()}')  # 打印预测的类别# 展示图片和预测类别
plt.imshow(Image.open(img_path), cmap='gray')      # 读取原始图片(用于显示)
plt.title(f'Predicted class: {predicted.item()}')  #  给图片加标题,显示预测结果
plt.axis('off')  # 关闭坐标轴
plt.show()  # 展示图片

两个预测代码对比

单张自定义图片推理(predict1.py)批量预测 MNIST 测试集(predict.py)
处理对象本地自定义图片(如myfigure.pngMNIST 数据集自带的测试集(datasets.MNIST(train=False)
图片读取方式PIL.Image.open()手动读取datasets.MNIST自动读取(无需手动处理路径)
数据加载方式单张图手动处理(加批次维度)DataLoader批量加载(自动分批次,batch_size=64
显示工具matplotlib.pyplot(单张图 + 标题,简洁)cv2(批量图循环显示,需控制窗口关闭)
核心场景验证自定义图片(如自己写的数字,实际应用场景)验证模型在标准测试集上的泛化能力(评估模型性能)
输入格式处理需手动加unsqueeze(0)补批次维度DataLoader自动生成[B, C, H, W]格式批量数据

整个项目已上传github,可自行copylin00007/MNIST

http://www.dtcms.com/a/528317.html

相关文章:

  • K8s 静态持久化存储详解
  • wordpress seo 能提高网站速度吗
  • GitHub等平台形成的开源文化正在重塑特尔恩恩
  • 追根索源:换不同的词嵌入(词向量生成方式不同,但词与词关系接近),会出现什么结果?
  • 视频与音频碰撞,谷歌 Veo 3.1,生成“有声电影”,人物对话超震撼
  • 【PID】基本PID控制 chaprt1 学习笔记
  • 【大语言模型 103】推理服务监控:性能指标、故障诊断与自动恢复实战
  • 网站广东海外建设集团有限公司做网站工资多钱
  • Julia 字符串处理指南
  • volatile关键词探秘:从咖啡厅的诡异订单到CPU缓存之谜
  • 嵌入式Lua脚本编程核心概念
  • VScode开发环境搭建(本文为个人学习笔记,内容整理自哔哩哔哩UP主【非学者勿扰】的公开课程。 > 所有知识点归属原作者,仅作非商业用途分享)
  • 基于springboot的车辆管理系统设计与实现
  • WPF GroupBox 淡入淡出
  • Dify从入门到精通 第33天 基于GPT-4V构建图片描述生成器与视觉问答机器人
  • 网页制作与网站建设实战教程视频网站一般用什么数据库
  • React 05
  • srpingboot 推rtsp/rtmp等流地址给前端播放flv和ws
  • 游戏任务简单设计
  • 平台网站建设ppt模板下载阿里巴巴的电子商务网站建设
  • GitHub等平台形成的开源文化正在重塑脱离了
  • Linux18--进程间的通信总结
  • 基于脚手架微服务的视频点播系统-脚手架开发部分-FFmpeg,Etcd-SDK的简单使用与二次封装
  • 【教学类-120-01】20251025旋转数字
  • 制作网站多少钱一个有哪些做企业点评的网站
  • 网站会员营销上海注册公司哪家好
  • 【深度学习新浪潮】深入理解Seed3D模型:参数化驱动的下一代3D内容生成技术
  • GitHub等平台形成的开源文化正在重塑和人家
  • 免费网站收录入口有了域名空间服务器怎么做网站
  • 5.go-zero集成gorm 和 go-redis