使用卷积神经网络训练手写数字识别模型(CNN)
效果展示
目录结构
README.md
# Basic MNIST Example
pip install -r requirements.txt
python main.py
# CUDA_VISIBLE_DEVICES=2 python main.py # to specify GPU id to ex. 2
requirements.txt
torch
torchvision
main.py
from __future__ import print_function # 导入Python 2和Python 3兼容性的print函数
import argparse # 导入命令行参数解析库
import torch # 导入PyTorch深度学习框架
import torch.nn as nn # 导入PyTorch神经网络模块
import torch.nn.functional as F # 导入PyTorch的函数模块
import torch.optim as optim # 导入PyTorch的优化器
from torchvision import datasets, transforms # 导入PyTorch视觉库中的数据集和数据预处理模块
from torch.optim.lr_scheduler import StepLR # 导入学习率衰减模块
import numpy as np # 导入NumPy库
import matplotlib.pyplot as plt # 导入Matplotlib库用于可视化
# 定义神经网络模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3, 1) # 32 3*3*1 kernel 输入图像: 28*28*1 feature map 26*26*32
# 第一个卷积层,输入通道1,输出通道32,卷积核大小3x3,步长1
self.conv2 = nn.Conv2d(32, 64, 3, 1) # 64 3*3*32 输入图像: 26*26*32 feature map 24*24*64
# 第二个卷积层,输入通道32,输出通道64,卷积核大小3x3,步长1
self.dropout1 = nn.Dropout(0.25) # 第一个Dropout层,防止过拟合
self.dropout2 = nn.Dropout(0.5) # 第二个Dropout层,防止过拟合
self.fc1 = nn.Linear(9216, 128) # 全连接层1,输入维度9216,输出维度128
self.fc2 = nn.Linear(128, 10) # 全连接层2,输入维度128,输出维度10(用于分类0-9数字)
# 前向传播函数
def forward(self, x): # batchsize,h,w,channel
x = self.conv1(x) # 第一个卷积层
x = F.relu(x) # 使用ReLU激活函数
x = self.conv2(x) # 第二个卷积层
x = F.relu(x) # 使用ReLU激活函数
x = F.max_pool2d(x, 2) # feature map h * w 24*24*64 -> 12 * 12 *64
# 最大池化,将特征图大小缩小一半
x = self.dropout1(x) # 应用第一个Dropout层,防止过拟合
x = torch.flatten(x, 1) # dim0 batch size
# 将特征图展平成一维,dim0为批次大小
x = self.fc1(x) # 第一个全连接层
x = F.relu(x) # 使用ReLU激活函数
x = self.dropout2(x) # 应用第二个Dropout层,防止过拟合
x = self.fc2(x) # 第二个全连接层
output = F.log_softmax(x, dim=1) # 使用log_softmax作为输出,用于多类别分类
return output
# 训练函数
def train(args, model, device, train_loader, optimizer, epoch):
model.train()
for batch_idx, (data, target) in enumerate(train_loader): # 遍历训练数据集
data, target = data.to(device), target.to(device) # 将数据和目标移到指定设备(GPU或CPU)
optimizer.zero_grad() # 梯度清零,准备计算新一批数据的梯度
output = model(data) # 使用模型前向传播得到预测结果
loss = F.nll_loss(output, target) # 计算交叉熵损失
loss.backward() # 反向传播,计算梯度
optimizer.step() # 根据梯度更新模型参数
if batch_idx % args.log_interval == 0: # 每隔一定批次数输出训练状态
print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
epoch, batch_idx * len(data), len(train_loader.dataset),
100. * batch_idx / len(train_loader), loss.item()))
if args.dry_run: # 如果开启了快速检查模式,提前结束训练
break
# 测试函数
def test(model, device, test_loader):
model.eval() # 设置模型为评估模式
test_loss = 0 # 初始化测试损失
correct = 0 # 初始化正确分类的样本数
with torch.no_grad(): # 禁用梯度计算
for data, target in test_loader: # 遍历测试数据集
data, target = data.to(device), target.to(device) # 将数据和目标移到指定设备(GPU或CPU)
output = model(data) # 使用模型前向传播得到预测结果
test_loss += F.nll_loss(output, target, reduction='sum').item() # # 累积损失
pred = output.argmax(dim=1, keepdim=True) # 找到预测的类别
correct += pred.eq(target.view_as(pred)).sum().item() # 统计正确分类的样本数
test_loss /= len(test_loader.dataset) # 计算平均测试损失
print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
test_loss, correct, len(test_loader.dataset),
100. * correct / len(test_loader.dataset))) # 打印测试结果,包括平均损失和准确率
# 可视化结果函数
def visualize_results(data, predictions, targets):
image_index = np.random.randint(0, len(data) - 1) # 随机选择一个图像索引
plt.subplot(111) # 创建一个子图
image = data[image_index].cpu().numpy().squeeze() # 获取图像数据并将其转换为NumPy数组
prediction = predictions[image_index].item() # 获取预测的类别
target = targets[image_index].item() # 获取真实标签
plt.imshow(image, cmap='gray') # 显示图像,使用灰度颜色映射
title = f"真值标签为:[{target}],预测标签为:{prediction}" # 创建图像标题
print(title)
plt.title(title, fontproperties='SimHei') # 设置标题并指定字体
plt.show() # 显示图像
def visualize(model, device, test_loader):
model.eval() # 设置模型为评估模式
test_loss = 0 # 初始化测试损失
correct = 0 # 初始化正确分类的样本数
test_data, test_predictions, test_targets = [], [], [] # 初始化用于保存数据、预测和目标的列表
with torch.no_grad(): # 禁用梯度计算
for data, target in test_loader: # 遍历测试数据集
data, target = data.to(device), target.to(device) # 将数据和目标移到指定设备(GPU或CPU)
output = model(data) # 使用模型前向传播得到预测结果
test_loss += F.nll_loss(output, target, reduction='sum').item() # 累积损失
pred = output.argmax(dim=1, keepdim=True) # 找到预测的类别
correct += pred.eq(target.view_as(pred)).sum().item() # 统计正确分类的样本数
# 收集数据、预测和目标
test_data.append(data)
test_predictions.append(pred)
test_targets.append(target)
test_loss /= len(test_loader.dataset) # 计算平均测试损失
# V可视化结果
test_data = torch.cat(test_data, dim=0) # 拼接所有数据
test_predictions = torch.cat(test_predictions, dim=0) # 拼接所有预测
test_targets = torch.cat(test_targets, dim=0) # 拼接所有目标
visualize_results(test_data, test_predictions, test_targets) # 调用可视化函数
def visual():
use_cuda = torch.cuda.is_available() # 检查是否可用CUDA
use_mps = False # 暂时禁用 macOS GPU 支持
# 根据CUDA和 macOS GPU 支持选择设备
if use_cuda:
device = torch.device("cuda")
elif use_mps:
device = torch.device("mps")
else:
device = torch.device("cpu")
# 设置测试数据加载参数
test_kwargs = {'batch_size': 64}
# 如果使用CUDA,设置CUDA加载参数
cuda_kwargs = {'num_workers': 1,
'pin_memory': True,
'shuffle': True}
# 更新测试数据加载参数以包括CUDA参数
test_kwargs.update(cuda_kwargs)
# 数据预处理:将图像数据转换为张量并进行标准化
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 创建测试数据集
dataset = datasets.MNIST('../data', train=False,
transform=transform) # list
# 创建测试数据加载器
test_loader = torch.utils.data.DataLoader(dataset, **test_kwargs)
# 定义模型文件路径
model_path = "mnist_cnn.pt"
# 加载模型
model = Net()
model.load_state_dict(torch.load(model_path))
# 将模型移动到选择的设备上
model.to(device)
# 调用可视化函数
visualize(model, device, test_loader)
# 主函数
def main():
# 训练参数设置
parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
# 输入训练批次大小(默认:64)
parser.add_argument('--batch-size', type=int, default=64, metavar='N',
help='input batch size for training (default: 64)')
# 输入测试批次大小(默认:1000)
parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
help='input batch size for testing (default: 1000)')
# 训练的时期数(默认:20)
parser.add_argument('--epochs', type=int, default=20, metavar='N',
help='number of epochs to train (default: 14)')
# 学习率(默认:1.0)
parser.add_argument('--lr', type=float, default=1.0, metavar='LR',
help='learning rate (default: 1.0)')
# 学习率步长衰减因子(默认:0.7)
parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
help='Learning rate step gamma (default: 0.7)')
# 禁用CUDA训练
parser.add_argument('--no-cuda', action='store_true', default=False,
help='disables CUDA training')
# 禁用macOS GPU训练
parser.add_argument('--no-mps', action='store_true', default=False,
help='disables macOS GPU training')
# 快速检查一次
parser.add_argument('--dry-run', action='store_true', default=False,
help='quickly check a single pass')
# 随机种子(默认:1)
parser.add_argument('--seed', type=int, default=1, metavar='S',
help='random seed (default: 1)')
# 设置多少批次后记录训练状态
parser.add_argument('--log-interval', type=int, default=10, metavar='N',
help='how many batches to wait before logging training status')
# 设置是否保存训练模型
parser.add_argument('--save-model', action='store_true', default=True,
help='For Saving the current Model')
args = parser.parse_args() # 解析命令行参数
# 检查是否使用CUDA(GPU),以及是否可用
use_cuda = not args.no_cuda and torch.cuda.is_available()
# 暂时不使用 macOS GPU
use_mps = False # not args.no_mps and torch.backends.mps.is_available()
# 设置随机种子
torch.manual_seed(args.seed)
# 根据CUDA和MPS的可用性选择设备
if use_cuda:
device = torch.device("cuda")
elif use_mps:
device = torch.device("mps")
else:
device = torch.device("cpu")
# 定义训练和测试数据集的批处理参数
train_kwargs = {'batch_size': args.batch_size}
test_kwargs = {'batch_size': args.test_batch_size}
# 如果使用CUDA,配置相应的参数
if use_cuda:
cuda_kwargs = {'num_workers': 1,
'pin_memory': True,
'shuffle': True}
train_kwargs.update(cuda_kwargs)
test_kwargs.update(cuda_kwargs)
# 数据预处理和加载
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 训练数据集
dataset1 = datasets.MNIST('../data', train=True, download=True,
transform=transform)
# 测试数据集
dataset2 = datasets.MNIST('../data', train=False,
transform=transform) # list
# 创建训练和测试数据加载器
train_loader = torch.utils.data.DataLoader(dataset1, **train_kwargs)
test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)
# 创建神经网络模型并将其移到所选设备
model = Net().to(device)
# 配置优化器
optimizer = optim.Adadelta(model.parameters(), lr=args.lr)
# 设置学习率衰减策略
scheduler = StepLR(optimizer, step_size=1, gamma=args.gamma)
# 开始训练循环
for epoch in range(1, args.epochs + 1):
train(args, model, device, train_loader, optimizer, epoch)
test(model, device, test_loader)
scheduler.step()
# 保存训练好的模型
if args.save_model:
torch.save(model.state_dict(), "mnist_cnn.pt")
if __name__ == '__main__':
main() # 训练、测试模型函数
visual() # 可视化函数
使用卷积神经网络训练手写数字识别模型(CNN)的更多相关文章
- TensorFlow卷积神经网络实现手写数字识别以及可视化
边学习边笔记 https://www.cnblogs.com/felixwang2/p/9190602.html # https://www.cnblogs.com/felixwang2/p/9190 ...
- 基于卷积神经网络的手写数字识别分类(Tensorflow)
import numpy as np import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_dat ...
- TensorFlow(十):卷积神经网络实现手写数字识别以及可视化
上代码: import tensorflow as tf from tensorflow.examples.tutorials.mnist import input_data mnist = inpu ...
- 莫烦pytorch学习笔记(八)——卷积神经网络(手写数字识别实现)
莫烦视频网址 这个代码实现了预测和可视化 import os # third-party library import torch import torch.nn as nn import torch ...
- BP神经网络的手写数字识别
BP神经网络的手写数字识别 ANN 人工神经网络算法在实践中往往给人难以琢磨的印象,有句老话叫“出来混总是要还的”,大概是由于具有很强的非线性模拟和处理能力,因此作为代价上帝让它“黑盒”化了.作为一种 ...
- 利用c++编写bp神经网络实现手写数字识别详解
利用c++编写bp神经网络实现手写数字识别 写在前面 从大一入学开始,本菜菜就一直想学习一下神经网络算法,但由于时间和资源所限,一直未展开比较透彻的学习.大二下人工智能课的修习,给了我一个学习的契机. ...
- Java实现BP神经网络MNIST手写数字识别
Java实现BP神经网络MNIST手写数字识别 如果需要源码,请在下方评论区留下邮箱,我看到就会发过去 一.神经网络的构建 (1):构建神经网络层次结构 由训练集数据可知,手写输入的数据维数为784维 ...
- 使用L2正则化和平均滑动模型的LeNet-5MNIST手写数字识别模型
使用L2正则化和平均滑动模型的LeNet-5MNIST手写数字识别模型 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献Tensorflow实战Google深度学习框架 实验平台: T ...
- 持久化的基于L2正则化和平均滑动模型的MNIST手写数字识别模型
持久化的基于L2正则化和平均滑动模型的MNIST手写数字识别模型 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献Tensorflow实战Google深度学习框架 实验平台: Tens ...
- 第二节,TensorFlow 使用前馈神经网络实现手写数字识别
一 感知器 感知器学习笔记:https://blog.csdn.net/liyuanbhu/article/details/51622695 感知器(Perceptron)是二分类的线性分类模型,其输 ...
随机推荐
- 2023-07-02:给定一个1~N的排列,每次将相邻两数相加,可以得到新的序列,长度是N-1 再对新的序列,每次将相邻两数相加,可以得到新的序列,长度是N-2 这样下去可以最终只剩一个数字 比如 :
2023-07-02:给定一个1~N的排列,每次将相邻两数相加,可以得到新的序列,长度是N-1 再对新的序列,每次将相邻两数相加,可以得到新的序列,长度是N-2 这样下去可以最终只剩一个数字 比如 : ...
- 《逆向工程核心原理》之DLL注入
DLL注入 DLL注入指的是向运行中的其他进程强制插入特定的DLL文件.从技术细节来说,DLL注入命令其他进程自行调用LoadLibrary() API,加载(Loading)用户指定的DLL文件.D ...
- 伸展树(Splay)详解
引入 在一条链中,二叉查找树的时间复杂度就会退化成 \(O(n)\),这时我们就需要平衡树来解决这个问题. \(Splay\)(伸展树)是平衡树的一种,它的每一步插入.查找和删除的平摊时间都是 \(O ...
- MAUI+MASA Blazor 兼容性测试报告及分析
目录 1. 背景 2. 目的 3. 测试目标 4. 预期结果 5. 测试策略及范围 6. 测试结果与分析 7. 附加内容 8. 结尾 1. 背景 MASA Blazor组件是一款基于Material ...
- fread()模板
char buf[1<<20],*p1,*p2;#define GC (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin ...
- Linux 软件包:lvm
lvm LVM是 Logical Volume Manager(逻辑卷管理)的简写,它是Linux环境下对磁盘分区进行管理的一种机制. Linux用户安装Linux操作系统时遇到的一个常见的难以决定的 ...
- ChatGPT 1.0.0安卓分析,仅限国内分享
ChatGPT 1.0.0安卓分析,仅限国内分享 博客园首发,本文将对ChatGpt Android版本1.0.0 APK进行静态解包分析和抓包分析,从ChatGpt Android APK功能的设计 ...
- @ControllerAdvice 注解使用及原理探究
最近在新项目的开发过程中,遇到了个问题,需要将一些异常的业务流程返回给前端,需要提供给前端不同的响应码,前端再在次基础上做提示语言的国际化适配.这些异常流程涉及业务层和控制层的各个地方,如果每个地方都 ...
- Promise的理解和使用(二)
一.Promise的常用APIpromise的这些方法执行完成都会返回一个新的promise,promise的状态由执行的结果决定. (1) new Promise()中的excutor函数 new ...
- 如何选择最适合您的Excel处理库?
摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 引言 GcExcel和POI是两个应用于处理Excel文件的技 ...