一、数据集介绍

MNIST(Mixed National Institute of Standards and Technology)是一个经典的图像分类数据集,包含 70000张 手写数字图像,其中:

        训练集:60000张

        测试集:10000张

        图像尺寸:28×28灰度图

        标签范围:0~9,共10类数字

MNIST 是深度学习入门最常用的数据集,适用于图像分类、特征提取、模型调试等任务。

二、网络结构

        卷积神经网络(CNN)结构: 通过卷积层提取图像局部特征; ReLU 增加非线性; 最大池化(MaxPool) 降低空间维度; Dropout 防止过拟合; 全连接层实现特征整合和分类; 最后使用 LogSoftmax 输出 10 类别的概率(对数形式)。相关概念见【深度学习系列】从模型到训练,逐步掌握深度学习核心术语和原理_深度学习做ai预测和分类小白入门csdn-CSDN博客

        损失函数: 使用 F.nll_loss(负对数似然损失)+ log_softmax。 或者可以直接用 nn.CrossEntropyLoss(),代替两者。

        优化器和调度器: 使用 Adadelta 优化器。 学习率逐轮调整:StepLR 每训练一轮后,学习率乘以 gamma=0.7。

三、代码实现步骤与网络结构

1. 导入库和设置环境

import argparse  # 命令行参数解析模块
import torch  # PyTorch 的核心模块,用于创建张量(tensor)、调用 GPU 运算、自动求导等
import torch.nn as nn  # PyTorch 的 神经网络模块(neural network),包括各种层(如 Linear, Conv2d, ReLU)、损失函数(如 CrossEntropyLoss)等
import torch.nn.functional as F
import torch.optim as optim  # PyTorch 的 优化器模块,用来更新模型参数,比如 SGD、Adam、RMSprop 等
from torchvision import datasets,transforms  # 为图像任务准备的数据和预处理模块,如转张量、归一化、裁剪、旋转等
from torch.optim.lr_scheduler import StepLR  # 导入学习率调度器(Learning Rate Scheduler),用于训练过程中自动调整学习率

2. 构建CNN模型

2.1 初始化与定义模块:

        输入图片尺寸是 28x28,单通道(灰度图),即28*28*1。

        卷积层conv1中的参数:1是输入通道数,32是输出通道数(即卷积核个数,产生32张特征图,决定输出图像的通道数),卷积核大小是 3x3,步长 stride = 1,通过公式:

可以计算出该层的输出图像的尺寸是:output_size = (input_size - kernel_size) / stride + 1 = (28 - 3)/1 + 1 = 26。所以输出尺寸是26*26*32。

        同理卷积层conv2卷积之后输出24*24*62。

        而dropout层作用是随机将神经元失活,防止过拟合。

def __init__(self):
        super(Net,self).__init__()

         #输入形状:(batch_size, 1, 28, 28)   输出形状:(batch_size, 32, 26, 26) 
        self.conv1=nn.Conv2d(1,32,3,1)  
        
        # 输入形状:(batch_size, 32, 26, 26)  输出形状:(batch_size, 62, 24, 24)
        self.conv2=nn.Conv2d(32,62,3,1)  

        self.dropout1=nn.Dropout(0.25)  # 训练时随机将25%的神经元“置零”
        self.dropout2=nn.Dropout(0.5)

        # 输入形状:batch_size x 8928,8928是展开之后的
        self.fc1=nn.Linear(8928,128)
        self.fc2=nn.Linear(128,10)
2.2  前向计算

        代码里我详细注释了每一步的输入和输出变化,可以看看模型是怎么一步步从输入到最终输出的。

对于网络训练过程的输入和输出的尺度变化如下图:

def forward(self,x):
        # 使用 32 个 3x3 卷积核进行卷积,步长 1,padding=0
        # 输入尺寸:(batch_size, 1, 28, 28)    输出尺寸:(batch_size, 32, 26, 26)
        x=self.conv1(x)
        # ReLU 激活函数,把负数变成 0,增加非线性    输入/输出尺寸:不变  
        x=F.relu(x) 

        # 62个 3x3 卷积核,步长为1
        # 输入尺寸:(batch_size, 32, 26, 26)   输出尺寸:(batch_size, 62, 24, 24)
        x=self.conv2(x) 
        # 输入/输出尺寸:不变
        x=F.relu(x)    

        # 2x2 的最大池化操作,步长默认为2
        x=F.max_pool2d(x,2)  # 输入尺寸:(batch_size, 62, 24, 24)   输出尺寸:(batch_size, 62, 12, 12)
        x=self.dropout1(x)
        # 将除了 batch 维度以外的所有维度拉平,准备送入全连接层,1 表示从第1维(也就是跳过 batch)开始展平
        x=torch.flatten(x,1)  #输入尺寸:(batch_size, 62, 12, 12)   输出尺寸:(batch_size, 62*12*12) = (batch_size, 8928)

        # 全连接层,输入8928个特征,输出128个特征
        x=self.fc1(x) #输入尺寸:(batch_size, 8928)  输出尺寸:(batch_size, 128)
        x=F.relu(x)
        x=self.dropout2(x)  # 防止过拟合,随机将一半的神经元输出设为0(训练时)
        x=self.fc2(x)  # 把128个特征变成10个类别的 logits

        # 对每一行(样本)做 softmax 后再取对数,用于配合 nn.NLLLoss() 作为损失函数
        # dim=1 表示在“每一行”上做 softmax 操作,即对每个样本的类别得分归一化成概率
        output=F.log_softmax(x,dim=1) # 输入尺寸:(batch_size, 10) 输出尺寸:(batch_size, 10)(每个样本的10类 log 概率)
        return output

3. 模型训练与测试

def train(args,model,device,train_loader,optimizer,epoch):
        # 是用来将模型设置为“训练模式”(train mode), 让模型中的某些层(比如 Dropout 和 BatchNorm)工作在训练时的行为
        model.train()
        for batch_idx, (data, target) in enumerate(train_loader):
            data,target=data.to(device),target.to(device)
            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)
            output = model(data)
            # .item() 把单个张量转换成 Python float,才能加到 test_loss 里
            test_loss += F.nll_loss(output, target, reduction='sum').item()  # sum up batch loss
            pred = output.argmax(dim=1, keepdim=True)  # get the index of the max log-probability
            # dim=1 表示按行找最大值(即每个样本在 10 个类别中谁最大)
            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)))

4. 数据加载运行与可视化

4.1 数据下载与加载

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)
train_loader = torch.utils.data.DataLoader(dataset1,**train_kwargs)
test_loader = torch.utils.data.DataLoader(dataset2, **test_kwargs)

4.2 使用GPU

train_kwargs = {'batch_size': args.batch_size}
    test_kwargs = {'batch_size': args.test_batch_size}
    if use_cuda:
        cuda_kwargs = {
            'num_workers': 1,
            'pin_memory': True,
            'shuffle': True
        }
        train_kwargs.update(cuda_kwargs)
        test_kwargs.update(cuda_kwargs)
use_cuda = not args.no_accel and torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")

4.3  其他配置设置

def main():
    # Training settings
    parser = argparse.ArgumentParser(description='PyTorch MNIST Example')
    parser.add_argument('--batch-size', type=int, default=64, metavar='N',
                        help='input batch size for training (default: 64)')
    parser.add_argument('--test-batch-size', type=int, default=1000, metavar='N',
                        help='input batch size for testing (default: 1000)')
    parser.add_argument('--epochs', type=int, default=14, metavar='N',
                        help='number of epochs to train (default: 14)')
    parser.add_argument('--lr', type=float, default=1.0, metavar='LR',
                        help='learning rate (default: 1.0)')
    parser.add_argument('--gamma', type=float, default=0.7, metavar='M',
                        help='Learning rate step gamma (default: 0.7)')
    parser.add_argument('--no-accel', action='store_true',
                        help='disables accelerator')
    parser.add_argument('--dry-run', action='store_true',
                        help='quickly check a single pass')
    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', 
                        help='For Saving the current Model')
    args = parser.parse_args()
    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()

四、实验结果

训练10轮后,模型准确率在 99.26% ,对于简单数字识别任务表现优秀。

        训练损失快速下降,模型收敛良好,无明显震荡或过早停止,训练阶段表现优秀。

        如下图,对识别正确的和识别错误的图像打印几个查看,不难发现,识别正确的的数字都具备明显的识别度且数字完整。而识别错误的数字,一类是手写数字模糊性高,迷惑性强,即使是人眼也不轻易判断出来,比如下面的5识别成3;另一类是特征不明显或者部分丢失,但人眼还是可以识别出,比如下图的8,2,但机器识别就错误识别成2和7。针对这个特征模糊/部分缺失的问题,怎么改进呢?希望有大佬评论区指点一二。

本实验完整代码如下:https://github.com/lovesuger/Handwriting-recognition-mnist.git中的mainnew.py文件。

Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐