1. 首页 > 电脑知识

深度 进修:PyTorch实现CNN手写字识别 深造 进修

作者:admin 更新时间:2025-07-17
摘要:一、项目介绍 1、概述 手写数字识别是模式识别领域的经典问题,旨在让计算机自动识别手写的数字字符。这个问题看似简单,但对于计算机来说却具有挑战性,因为不同人的书写风格、字体大小和形状差异很大。MNIST 数据集是手写数字识别领域的标准基准数据集,包含 60,000 张训练图像和 10,000 张测试图,深度 进修:PyTorch实现CNN手写字识别 深造 进修

 

一、项目介绍

1、概述

     

        手写数字识别是模式识别领域的经典 难题,旨在让计算机自动识别手写的数字字符。这个 难题看似简单,但对于计算机来说却具有挑战性, 由于不同人的书写风格、字体 大致和形状差异很大。MNIST 数据集是手写数字识别领域的标准基准数据集,包含 60,000 张训练图像和 10,000 张测试图像,每张图像都是 28×28 像素的灰度图,涵盖了 0-9 共 10 个数字。

         卷积神经网络 (CNN) 是一种专门为处理具有网格结构数据(如图像)而设计的深度 进修模型。CNN 通过卷积层自动提取图像特征,减少了对人工特征工程的依赖, 并且在图像识别任务中取得了巨大成功。PyTorch 一个开源的深度 进修框架,提供了动态计算图、自动微分等功能,使得构建和训练神经网络变得更加简单和直观。

         MNIST手写字识别 一个经典的机器 进修任务,旨在通过卷积神经网络(CNN)对28×28像素的灰度手写数字图像进行分类。该数据集包含70,000张图片,分为60,000张训练图片和10,000张测试图片,每张图片代表一个0-9的手写数字。

        本项目的目标是构建一个高精度的卷积神经网络模型,能够准确地识别手写数字, 并且在测试集上达到较高的准确率。 除了这些之后,项目还将探索 怎样优化模型结构、调整超参数以 进步性能。

2、项目意义

【1】教育 价格

MNIST 手写数字识别是深度 进修入门的经典项目,适合初学者 进修和 领会卷积神经网络的基本原理和实现 技巧。通过这个项目,可以掌握 PyTorch 的基本使用、数据处理、模型构建、训练和评估等技能。    

【2】技术验证

CNN 在 MNIST 数据集上的优异表现验证了深度 进修在图像识别领域的有效性,为更复杂的图像识别任务提供了 学说和 操作基础。    

【3】实际应用

手写数字识别技术在现实生活中有广泛的应用,如邮政编码识别、银行支票处理、表单数字识别等。虽然 MNIST 一个相对简单的数据集,但它为开发更复杂的手写文字识别 体系提供了基础。    

【4】模型评估

MNIST 数据集 小编认为 一个标准基准,可以用于评估不同模型架构和训练 技巧的性能,比较它们的优缺点。

3、项目描述

这个项目使用 PyTorch 实现了一个卷积神经网络,用于识别 MNIST 数据集中的手写数字。项目主要包括 下面内容 几许部分:

【1】输入层

         手写字MNIST图片

【2】卷积块1 

        卷积层1: 个3×3卷积核,步长1,padding=1

        ReLU激活函数

        最大池化:2×2窗口,步长2

【3】卷积块2 

        卷积层2:128个3×3卷积核,步长1,padding=1

        ReLU激活函数

        最大池化:2×2窗口,步长2

【4】 展平层:将三维特征图展平为一维向量

【5】全连接层1:1024个神经元

【6】Dropout层:防止过拟合(通常p=0.5)

【7】全连接层2:输出层(示例为0-9数字的10分类) 【8】 结局可视化

4、MNIST 数据集介绍

MNIST(Modified National Institute of Standards and Technology)数据集是机器 进修领域最经典的图像分类数据集 其中一个,专门用于手写数字识别任务。MNIST数据集因其简单性和规范性,至今仍是教学和研究的重要资源, 虽然其技术挑战性已不如前,但作为:

【1】 模型调试的”试金石”

【2】 算法教学的直观案例

【3】 预处理流程的示范样本

仍然具有不可替代的 价格。当需要更具挑战性的基准时,研究者通常会转向CIFAR-10/100或I geNet等更复杂的数据集。该数据集包含0-9共10个 数字的手写灰度图像;训练集60,000张图像;测试集10,000张图像,该数据集的特点:

 MNIST数据集的优点:

【1】 数据干净、标注准确

【2】 尺寸小(约12MB),易于快速实验

【3】 良好的类别平衡(每个数字约6,000训练样本)

 

MNIST数据集的局限性:

【1】 过于简单,现代算法易达到饱和性能

【2】 缺乏 诚恳场景的复杂性(如背景噪声、倾斜变形等)

5、用到的技术和工具

开发工具:Anaconda Jupyter Notebook

深度 进修框架:PyTorch

Python工具包:Numpy、 tplotlib

二、PyTorch实现手写字识别

1、环境准备和数据处理

【1】导入了所有必要的PyTorch模块

【2】设置了随机种子以确保 结局可复现

【3】检查并设置使用GPU或CPU  

# 导入必要的库 import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader from torchvision import datasets, transforms import tplotlib.pyplot as plt import numpy as np import warnings

# 忽略警告信息 warnings.filterwarnings(“ignore”)

# 设置随机种子保证 结局可复现 torch. nual_seed(42)

# 检查是否可以使用GPU device = torch.device(“cuda” if torch.cuda.is_available() else “cpu”) print(f”Using device: {device}”)

2、数据加载和预处理

【1】 定义了数据转换,包括将图像转换为Tensor和归一化

【2】 载并加载MNIST数据集

【3】 创建了训练和测试的数据加载器

【4】 可视化了一些训练样本以检查数据是否正确加载

【5】输出:显示15张MNIST手写数字图像,每张图像上方有对应的标签

# 定义数据转换 transform = transforms.Compose([     transforms.ToTensor(),  # 将PIL图像转换为Tensor     transforms.Nor lize((0.1307,), (0.3081,))  # MNIST数据集的均值和标准差 ])

# 下载并加载训练数据集 train_dataset = datasets.MNIST(     root='./data',  # 数据存储路径     train=True,  # 加载训练集     download=True,  # 如果不存在则下载     transform=transform  # 应用定义的数据转换 )

# 下载并加载测试数据集 test_dataset = datasets.MNIST(     root='./data',  # 数据存储路径     train=False,  # 加载测试集     download=True,  # 如果不存在则下载     transform=transform  # 应用定义的数据转换 )

# 创建数据加载器 batch_size =   # 每批加载的图像数量 train_loader = DataLoader(     dataset=train_dataset,     batch_size=batch_size,     shuffle=True  # 打乱训练数据 )

test_loader = DataLoader(     dataset=test_dataset,     batch_size=batch_size,     shuffle=False  # 测试数据不需要打乱 )

# 可视化一些训练样本 def plot_i ges(i ges, labels, nrows=3, ncols=5):     plt.figure(figsize=(10, 6))     for i in range(nrows * ncols):         plt.subplot(nrows, ncols, i + 1)         plt.imshow(i ges[i].squeeze(), c p='gray')  # 显示灰度图像         plt.title(f”Label: {labels[i]}”)         plt.axis('off')     plt.tight_layout()     plt.show()

# 获取一批训练数据 data_iter = iter(train_loader) i ges, labels = next(data_iter)

# 显示图像 plot_i ges(i ges, labels)

3、构建CNN模型

【1】定义了一个CNN类,继承自nn.Module

【2】模型包含两个卷积块,每个块包含卷积层、ReLU激活和最大池化

【3】 接着 一个展平层将三维特征图转换为一维向量

【4】 接着是两个全连接层,中间有Dropout层防止过拟合

【5】 最后打印出模型结构,可以看到各层的参数设置

# 定义CNN模型 class CNN(nn.Module):     def __init__(self):         super(CNN, self).__init__()                  # 卷积块1         self.conv_block1 = nn.Sequential(             # 卷积层1: 输入通道1(灰度图), 输出通道 , 3×3卷积核, padding=1             nn.Conv2d(1, , kernel_size=3, stride=1, padding=1),             nn.ReLU(),  # ReLU激活函数             # 最大池化: 2×2窗口, 步长2             nn.MaxPool2d(kernel_size=2, stride=2)         )                  # 卷积块2         self.conv_block2 = nn.Sequential(             # 卷积层2: 输入通道 , 输出通道128, 3×3卷积核, padding=1             nn.Conv2d( , 128, kernel_size=3, stride=1, padding=1),             nn.ReLU(),  # ReLU激活函数             # 最大池化: 2×2窗口, 步长2             nn.MaxPool2d(kernel_size=2, stride=2)         )                  # 展平层         self.flatten = nn.Flatten()                  # 全连接层1         self.fc1 = nn.Linear(128 * 7 * 7, 1024)  # 输入尺寸计算: 28×28经过两次池化变为7×7         self.relu = nn.ReLU()                  # Dropout层         self.dropout = nn.Dropout(0.5)                  # 全连接层2 (输出层)         self.fc2 = nn.Linear(1024, 10)  # 10个输出对应10个数字类别          def forward(self, x):         # 前向传播         x = self.conv_block1(x)  # 通过第一个卷积块         x = self.conv_block2(x)  # 通过第二个卷积块         x = self.flatten(x)  # 展平特征图         x = self.fc1(x)  # 第一个全连接层         x = self.relu(x)  # ReLU激活         x = self.dropout(x)  # Dropout         x = self.fc2(x)  # 输出层         return x

# 创建模型实例并移动到设备(GPU或CPU) model = CNN().to(device) print(model)

输出 结局:

CNN( (conv_block1): Sequential( (0): Conv2d(1, , kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (conv_block2): Sequential( (0): Conv2d( , 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)) (1): ReLU() (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False) ) (flatten): Flatten(start_dim=1, end_dim=-1) (fc1): Linear(in_features=6272, out_features=1024, bias=True) (relu): ReLU() (dropout): Dropout(p=0.5, inplace=False) (fc2): Linear(in_features=1024, out_features=10, bias=True) )

4、定义损失函数和优化器

【1】使用交叉熵损失函数,适合多分类 难题

【2】使用Adam优化器,它是一种自适应 进修率的优化算法

【3】添加了 进修率调度器,当验证损失不再下降时自动降低 进修率

# 定义损失函数 – 交叉熵损失 criterion = nn.CrossEntropyLoss()

# 定义优化器 – Adam优化器 optimizer = optim.Adam(model.parameters(), lr=0.001)  # 进修率设为0.001

# 进修率调度器 – 在验证损失不再下降时降低 进修率 scheduler = optim.lr_scheduler.ReduceLROnPlateau(     optimizer,      mode='min',  # 监控验证损失的最小值     factor=0.1,  # 进修率衰减因子     patience=5,  # 耐心值     verbose=True  # 打印 进修率更新信息 )

5、训练模型

     【1】 我们设置了15个训练周期(epoch)

【2】 每个epoch中,模型在训练集上训练, 接着在测试集上验证

【3】 记录并打印每个epoch的训练和验证损失及准确率

【4】 使用 进修率调度器根据验证损失调整 进修率

# 训练参数 num_epochs = 15  # 训练轮数 train_losses = []  # 记录训练损失 train_accuracies = []  # 记录训练准确率 val_losses = []  # 记录验证损失 val_accuracies = []  # 记录验证准确率

# 训练循环 for epoch in range(num_epochs):     model.train()  # 设置模型为训练模式     running_loss = 0.0     correct = 0     total = 0          # 遍历训练数据加载器     for i ges, labels in train_loader:         # 将数据移动到设备(GPU或CPU)         i ges = i ges.to(device)         labels = labels.to(device)                  # 前向传播         outputs = model(i ges)         loss = criterion(outputs, labels)                  # 反向传播和优化         optimizer.zero_grad()  # 清空梯度         loss.backward()  # 反向传播计算梯度         optimizer.step()  # 更新参数                  # 统计训练损失和准确率         running_loss += loss.item()         _, predicted = torch. x(outputs.data, 1)         total += labels.size(0)         correct += (predicted == labels).sum().item()          # 计算平均训练损失和准确率     train_loss = running_loss / len(train_loader)     train_accuracy = 100 * correct / total     train_losses.append(train_loss)     train_accuracies.append(train_accuracy)          # 验证阶段     model.eval()  # 设置模型为评估模式     val_loss = 0.0     correct = 0     total = 0          with torch.no_grad():  # 不计算梯度         for i ges, labels in test_loader:             i ges = i ges.to(device)             labels = labels.to(device)                          outputs = model(i ges)             loss = criterion(outputs, labels)                          val_loss += loss.item()             _, predicted = torch. x(outputs.data, 1)             total += labels.size(0)             correct += (predicted == labels).sum().item()          # 计算平均验证损失和准确率     val_loss = val_loss / len(test_loader)     val_accuracy = 100 * correct / total     val_losses.append(val_loss)     val_accuracies.append(val_accuracy)          # 更新 进修率     scheduler.step(val_loss)          # 打印训练和验证信息     print(f”Epoch [{epoch + 1}/{num_epochs}], “           f”Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, “           f”Val Loss: {val_loss:.4f}, Val Acc: {val_accuracy:.2f}%”)

输出 结局:

Epoch [1/15], Train Loss: 0.1201, Train Acc: 96.24%, Val Loss: 0.0327, Val Acc: 98.81% Epoch [2/15], Train Loss: 0.0463, Train Acc: 98.55%, Val Loss: 0.0297, Val Acc: 99.01% Epoch [3/15], Train Loss: 0.0320, Train Acc: 98.99%, Val Loss: 0.0228, Val Acc: 99.30% Epoch [4/15], Train Loss: 0.0257, Train Acc: 99.19%, Val Loss: 0.0237, Val Acc: 99.21% Epoch [5/15], Train Loss: 0.0207, Train Acc: 99.36%, Val Loss: 0.0410, Val Acc: 98.83% Epoch [6/15], Train Loss: 0.0171, Train Acc: 99.48%, Val Loss: 0.0300, Val Acc: 99.19% Epoch [7/15], Train Loss: 0.0148, Train Acc: 99.53%, Val Loss: 0.0253, Val Acc: 99.29% Epoch [8/15], Train Loss: 0.0128, Train Acc: 99.56%, Val Loss: 0.0319, Val Acc: 99.20% Epoch [9/15], Train Loss: 0.0107, Train Acc: 99.66%, Val Loss: 0.0339, Val Acc: 99.22% Epoch [10/15], Train Loss: 0.0046, Train Acc: 99.84%, Val Loss: 0.0247, Val Acc: 99.44% Epoch [11/15], Train Loss: 0.0022, Train Acc: 99.93%, Val Loss: 0.0239, Val Acc: 99.42% Epoch [12/15], Train Loss: 0.0016, Train Acc: 99.95%, Val Loss: 0.0235, Val Acc: 99.42% Epoch [13/15], Train Loss: 0.0015, Train Acc: 99.95%, Val Loss: 0.0250, Val Acc: 99.39% Epoch [14/15], Train Loss: 0.0008, Train Acc: 99.97%, Val Loss: 0.0245, Val Acc: 99.44% Epoch [15/15], Train Loss: 0.0009, Train Acc: 99.97%, Val Loss: 0.0262, Val Acc: 99.41%

6、可视化训练 经过

【1】通过可视化可以直观地看到模型的 进修 经过

【2】理想情况下,训练和验证损失都应该下降,准确率都应该上升

【3】如果验证指标开始变差而训练指标继续改善,可能出现过拟合

【4】输出:显示两张图表:一张是训练和验证损失随epoch的变化,另一张是训练和验证准确率随epoch的变化

# 绘制训练和验证损失曲线 plt.figure(figsize=(12, 5)) plt.subplot(1, 2, 1) plt.plot(train_losses, label='Train Loss') plt.plot(val_losses, label='Validation Loss') plt.title('Training and Validation Loss') plt.xlabel('Epoch') plt.ylabel('Loss') plt.legend()

# 绘制训练和验证准确率曲线 plt.subplot(1, 2, 2) plt.plot(train_accuracies, label='Train Accuracy') plt.plot(val_accuracies, label='Validation Accuracy') plt.title('Training and Validation Accuracy') plt.xlabel('Epoch') plt.ylabel('Accuracy (%)') plt.legend()

plt.tight_layout() plt.show()

7、模型评估

【1】在测试集上评估模型的最终性能

【2】计算并显示混淆矩阵,可以查看模型在哪些数字上容易混淆

【3】高准确率表明模型性能良好

# 在测试集上评估模型 model.eval()  # 设置模型为评估模式 test_loss = 0.0 correct = 0 total = 0 all_preds = [] all_labels = []

with torch.no_grad():  # 不计算梯度     for i ges, labels in test_loader:         i ges = i ges.to(device)         labels = labels.to(device)                  outputs = model(i ges)         loss = criterion(outputs, labels)                  test_loss += loss.item()         _, predicted = torch. x(outputs.data, 1)         total += labels.size(0)         correct += (predicted == labels).sum().item()                  # 保存预测 结局和 诚恳标签用于后续分析         all_preds.extend(predicted.cpu().numpy())         all_labels.extend(labels.cpu().numpy())

# 计算测试集上的准确率 test_accuracy = 100 * correct / total print(f”Test Accuracy: {test_accuracy:.2f}%”)

# 计算混淆矩阵 from sklearn.metrics import confusion_ trix import seaborn as sns

cm = confusion_ trix(all_labels, all_preds)

# 绘制混淆矩阵 plt.figure(figsize=(10, 8)) sns.heat p(cm, annot=True, fmt='d', c p='Blues',              xticklabels=range(10), yticklabels=range(10)) plt.title('Confusion Matrix') plt.xlabel('Predicted Label') plt.ylabel('True Label') plt.show()

8、可视化预测 结局

输出:

【1】 显示15张测试图像,每张图像上方显示预测标签和 诚恳标签

【2】 正确预测用绿色显示,错误预测用红色显示

说明:

【1】 可视化一些测试样本的预测 结局

【2】 可以直观地看到模型在哪些图像上表现良好,在哪些图像上出现错误

【3】 有助于 领会模型的优势和局限性

# 获取一批测试数据 data_iter = iter(test_loader) i ges, labels = next(data_iter) i ges = i ges.to(device) labels = labels.to(device)

# 进行预测 outputs = model(i ges) _, predicted = torch. x(outputs, 1)

# 将数据移回CPU以便可视化 i ges = i ges.cpu() labels = labels.cpu() predicted = predicted.cpu()

# 可视化预测 结局 def plot_predictions(i ges, labels, predicted, nrows=3, ncols=5):     plt.figure(figsize=(12, 8))     for i in range(nrows * ncols):         plt.subplot(nrows, ncols, i + 1)         plt.imshow(i ges[i].squeeze(), c p='gray')                  # 用绿色表示正确预测,红色表示错误预测         color = 'green' if labels[i] == predicted[i] else 'red'         plt.title(f”Pred: {predicted[i]} True: {labels[i]}”, color=color)         plt.axis('off')     plt.tight_layout()     plt.show()

plot_predictions(i ges, labels, predicted)