使用PyTorch构建全连接神经网络实现MNIST手写数字分类

发布于:2025-09-04 ⋅ 阅读:(25) ⋅ 点赞:(0)

在深度学习领域,图像分类是最基础也是最经典的任务之一。MNIST手写数字数据集作为计算机视觉领域的"Hello World",包含了60,000个训练样本和10,000个测试样本,每个样本都是28×28像素的灰度图像,对应0-9十个数字类别。本文将详细介绍如何使用PyTorch框架构建全连接神经网络(FCN)来完成MNIST手写数字分类任务。

一、全连接神经网络基础

1.1 什么是全连接神经网络

全连接神经网络(Fully Connected Neural Network),也称为多层感知机(Multi-Layer Perceptron, MLP),是最基础的深度学习模型之一。在这种网络中,每一层的每个神经元都与下一层的所有神经元相连接,因此称为"全连接"。

1.2 全连接网络的结构

一个典型的三层全连接网络包括:

  • 输入层:接收原始数据

  • 隐藏层:进行特征提取和转换

  • 输出层:产生最终预测结果

对于MNIST分类任务,输入层需要处理28×28=784个像素值,输出层需要产生10个类别的概率分布。

1.3 全连接网络的优缺点

优点

  • 结构简单,易于理解和实现

  • 对数据预处理要求相对较低

  • 在小规模数据集上表现良好

缺点

  • 参数量大,容易过拟合

  • 忽略了图像的空间局部性

  • 对于大规模图像数据表现不如卷积神经网络

二、PyTorch框架简介

PyTorch是由Facebook开发的开源深度学习框架,以其动态计算图和简洁的API设计受到研究人员和开发者的广泛欢迎。PyTorch的核心组件包括:

  1. torch.Tensor:支持自动微分的多维数组

  2. torch.nn:神经网络层和损失函数的集合

  3. torch.optim:各种优化算法的实现

  4. torch.utils.data:数据加载和预处理的工具

三、项目实现详解

3.1 环境准备

首先需要安装必要的Python库:

pip install torch torchvision matplotlib

3.2 数据加载与预处理

MNIST数据集可以通过torchvision方便地获取:

from torchvision import datasets, transforms

# 定义数据转换
transform = transforms.Compose([
    transforms.ToTensor(),  # 转换为Tensor
    transforms.Normalize((0.1307,), (0.3081,))  # 标准化
])

# 加载数据集
train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, download=True, transform=transform)

数据标准化使用MNIST数据集的全局均值(0.1307)和标准差(0.3081),这有助于模型更快收敛。

3.3 构建全连接网络

我们实现一个三层的全连接网络:

import torch.nn as nn
import torch.nn.functional as F

class FCNet(nn.Module):
    def __init__(self):
        super(FCNet, self).__init__()
        self.fc1 = nn.Linear(28*28, 512)  # 输入层到隐藏层1
        self.fc2 = nn.Linear(512, 256)    # 隐藏层1到隐藏层2
        self.fc3 = nn.Linear(256, 10)     # 隐藏层2到输出层
        self.dropout = nn.Dropout(0.2)    # Dropout层
    
    def forward(self, x):
        x = x.view(-1, 28*28)            # 展平输入
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = F.relu(self.fc2(x))
        x = self.dropout(x)
        x = self.fc3(x)                  # 输出层不使用激活函数
        return x

网络结构说明:

  1. 输入层:784个神经元(对应28×28图像)

  2. 第一个隐藏层:512个神经元,使用ReLU激活

  3. 第二个隐藏层:256个神经元,使用ReLU激活

  4. 输出层:10个神经元(对应10个数字类别)

  5. 在两个隐藏层后添加Dropout层,丢弃概率为20%,防止过拟合

3.4 训练过程实现

训练过程包括前向传播、损失计算、反向传播和参数更新:

def train(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)
        optimizer.zero_grad()  # 梯度清零
        output = model(data)  # 前向传播
        loss = F.cross_entropy(output, target)  # 计算损失
        loss.backward()        # 反向传播
        optimizer.step()       # 参数更新
        
        # 打印训练进度
        if batch_idx % 100 == 0:
            print(f'Epoch: {epoch} [{batch_idx*len(data)}/{len(train_loader.dataset)} '
                  f'({100.*batch_idx/len(train_loader):.0f}%)]\tLoss: {loss.item():.6f}')

3.5 测试过程实现

测试阶段不计算梯度,只评估模型性能:

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)
            test_loss += F.cross_entropy(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(f'Test set: Average loss: {test_loss:.4f}, '
          f'Accuracy: {correct}/{len(test_loader.dataset)} '
          f'({100.*correct/len(test_loader.dataset):.1f}%)')

3.6 主训练循环

def main():
    # 设置设备
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    
    # 初始化模型
    model = FCNet().to(device)
    
    # 定义优化器
    optimizer = optim.Adam(model.parameters(), lr=0.001)
    
    # 创建数据加载器
    train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
    test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
    
    # 训练和测试
    epochs = 10
    for epoch in range(1, epochs + 1):
        train(model, device, train_loader, optimizer, epoch)
        test(model, device, test_loader)
    
    # 保存模型
    torch.save(model.state_dict(), "mnist_fc.pth")

四、模型性能分析

运行上述代码,模型在测试集上的准确率通常可以达到97-98%。以下是几个关键点的分析:

4.1 激活函数选择

我们使用ReLU(Rectified Linear Unit)作为隐藏层的激活函数,相比传统的Sigmoid或Tanh函数,ReLU具有以下优势:

  • 计算简单,加速训练

  • 缓解梯度消失问题

  • 产生稀疏激活,有助于模型泛化

4.2 Dropout正则化

Dropout通过在训练过程中随机"关闭"一部分神经元,防止神经元之间形成复杂的共适应关系,从而有效减轻过拟合。我们设置dropout率为0.2,即在训练时每个神经元有20%的概率被暂时丢弃。

4.3 优化器选择

使用Adam优化器结合了动量法和RMSProp的优点,具有以下特性:

  • 自适应学习率

  • 动量项加速收敛

  • 对初始学习率不太敏感

五、模型改进方向

虽然我们的基础模型已经取得了不错的效果,但仍有多方面可以改进:

5.1 网络结构调整

  • 增加网络深度

  • 调整每层神经元数量

  • 尝试不同的激活函数(如LeakyReLU, ELU)

5.2 训练策略优化

  • 使用学习率调度器

  • 增加早停(Early Stopping)机制

  • 尝试不同的批量大小

5.3 数据增强

  • 随机旋转

  • 轻微平移

  • 添加噪声

六、总结

本文详细介绍了使用PyTorch构建全连接神经网络进行MNIST手写数字分类的完整流程。通过这个项目,我们学习了:

  1. PyTorch的基本使用和神经网络构建方法

  2. 全连接网络的结构设计和实现

  3. 深度学习模型的训练和评估流程

  4. 常见的优化和正则化技术

全连接网络虽然简单,但包含了深度学习中最核心的概念和技术,是学习神经网络的重要起点。掌握了这些基础知识后,可以进一步学习更复杂的网络结构,如卷积神经网络(CNN)、循环神经网络(RNN)等。

完整的项目代码已在上文中给出,读者可以复制运行,也可以尝试调整网络结构和超参数,观察模型性能的变化,加深对神经网络的理解。


网站公告

今日签到

点亮在社区的每一天
去签到