深度学习——CNN实例手写数字

在深度学习领域,卷积神经网络(Convolutional Neural Networks,简称CNN)已成为图像识别任务的强大工具。本文将通过一个具体的实例------使用 PyTorch 构建一个简单的 CNN 模型来识别 MNIST 数据集中的手写数字,详细讲解 CNN 的构建过程、训练和测试方法。MNIST 数据集是一个包含 60,000 张训练图像和 10,000 张测试图像的手写数字数据集,每张图像均为 28x28 像素的灰度图,目标是识别图像中的数字(0 到 9)。

一、环境准备与数据加载

1. 导入必要的库

首先,我们需要导入 PyTorch 及其相关模块,以及用于数据处理的 torchvision 库。

python 复制代码
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

2. 下载并加载 MNIST 数据集

MNIST 数据集可以通过 torchvision.datasets 轻松下载和加载。我们将数据集分为训练集和测试集,并将图像转换为 PyTorch 张量格式,以便输入到神经网络中。

python 复制代码
# 下载并加载训练数据集
training_data = datasets.MNIST(
    root='./data',
    train=True,
    download=True,
    transform=ToTensor()  # 将图像转换为张量
)

# 下载并加载测试数据集
test_data = datasets.MNIST(
    root='./data',
    train=False,
    download=True,
    transform=ToTensor()  # 将图像转换为张量
)

# 查看训练数据集的大小(可选)
# print(len(training_data))

​注意​​:为了便于调试和可视化,您可以取消注释以下代码以查看部分训练图像。

python 复制代码
# from matplotlib import pyplot as plt
# figure = plt.figure()
# for i in range(9):
#     img, label = training_data[i+59000]
#     figure.add_subplot(3, 3, i+1)
#     plt.title(label)
#     plt.axis('off')
#     plt.imshow(img.squeeze(), cmap='gray')
#     a = img.squeeze()  # squeeze()将张量格式
# plt.show()

3. 创建 DataLoader

DataLoader 是 PyTorch 提供的一个实用工具,用于将数据集分成多个批次(batches),便于批量训练和测试。

python 复制代码
# 创建训练和测试的 DataLoader,每批包含 64 张图像
training_dataloader = DataLoader(dataset=training_data, batch_size=64)
test_dataloader = DataLoader(dataset=test_data, batch_size=64)

4. 设备配置

为了加速训练过程,我们优先使用 GPU(如 CUDA 或 MPS),如果不可用,则使用 CPU。

python 复制代码
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
# print(f'Using {device} device')

二、构建卷积神经网络模型

我们将构建一个包含多个卷积层和全连接层的 CNN 模型。以下是模型的详细构建过程。

1. 定义 CNN 类

python 复制代码
class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        # 第一个卷积块
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=1,      # 输入通道数,1 表示灰度图像
                      out_channels=8,     # 输出通道数,即卷积核的数量
                      kernel_size=5,      # 卷积核大小为 5x5
                      stride=1,           # 步长为 1
                      padding=2           # 填充为 2,以保持特征图尺寸
                      ),
            nn.ReLU(),                  # 激活函数 ReLU
            nn.MaxPool2d(2)             # 最大池化,窗口大小为 2x2
        )
        # 第二个卷积块
        self.conv2 = nn.Sequential(
            nn.Conv2d(8, 16, 5, 1, 2),  # 输入通道 8,输出通道 16,卷积核 5x5,步长 1,填充 2
            nn.ReLU(),
            nn.Conv2d(16, 32, 5, 1, 2), # 输入通道 16,输出通道 32,卷积核 5x5,步长 1,填充 2
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2), # 输入通道 32,输出通道 32,卷积核 5x5,步长 1,填充 2
            nn.ReLU(),
            nn.MaxPool2d(2)             # 最大池化,窗口大小为 2x2
        )
        # 第三个卷积块
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2), # 输入通道 32,输出通道 64,卷积核 5x5,步长 1,填充 2
            nn.ReLU()
        )
        # 全连接层,将特征展平后输入
        self.out = nn.Linear(64 * 7 * 7, 10)  # 输入特征数为 64 * 7 * 7,输出为 10 个类别

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)  # 将特征图展平为一维向量
        output = self.out(x)
        return output

2. 模型结构解析

  • ​卷积层(Conv2d)​ :每个卷积层通过卷积核在输入图像上滑动,提取局部特征。卷积核的大小、数量和步长等参数决定了提取特征的方式和数量。
  • ​激活函数(ReLU)​ :ReLU(Rectified Linear Unit)是一个常用的非线性激活函数,能够引入非线性特性,增强模型的表达能力。
  • 池化层(MaxPool2d)​ :最大池化层通过选取每个池化窗口中的最大值,减小特征图的空间尺寸,从而减少计算量和参数数量,同时有助于防止过拟合。
  • 全连接层(Linear)​ :全连接层将前面卷积层提取到的高维特征进行整合,输出最终的分类结果。在本例中,输出为 10 个类别,对应数字 0 到 9。

3. 模型实例化与设备分配

python 复制代码
model = CNN().to(device)
print(model)

通过 to(device) 方法,将模型移动到指定的设备(GPU 或 CPU)上,以利用硬件加速。

三、定义损失函数与优化器

1. 损失函数

我们使用交叉熵损失函数(CrossEntropyLoss),这是分类任务中常用的损失函数,能够衡量模型输出的概率分布与真实标签之间的差异。

python 复制代码
loss_fn = nn.CrossEntropyLoss()

2. 优化器

优化器用于更新模型的参数,以最小化损失函数。在本例中,我们选择 Adam 优化器,它是一种自适应学习率的优化算法,通常能够更快地收敛。

python 复制代码
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

​备注​​:您也可以选择随机梯度下降(SGD)优化器,只需将上述代码替换为:

python 复制代码
# optimizer = torch.optim.SGD(model.parameters(), lr=0.01)

四、训练与测试函数

1. 训练函数

训练函数用于在每个 epoch 中遍历训练数据,计算损失,进行反向传播和参数更新。

python 复制代码
def train(train_dataloader, model, loss_fn, optimizer):
    model.train()  # 设置模型为训练模式
    batch_size_num = 1
    for X, y in train_dataloader:
        X, y = X.to(device), y.to(device)  # 将数据移动到指定设备
        pred = model(X)                    # 前向传播,获取预测结果
        loss = loss_fn(pred, y)            # 计算损失
        optimizer.zero_grad()              # 清空梯度
        loss.backward()                    # 反向传播,计算梯度
        optimizer.step()                   # 更新参数

        loss_val = loss.item()             # 获取损失值
        if batch_size_num % 100 == 0:
            print(f'Number: {batch_size_num}, Train Loss: {loss_val:>7f}')
        batch_size_num += 1

2. 测试函数

测试函数用于在测试数据集上评估模型的性能,计算准确率和平均损失。

python 复制代码
def test(test_dataloader, model, loss_fn):
    size = len(test_dataloader.dataset)  # 测试数据集的总样本数
    num_batches = len(test_dataloader)   # 测试数据集的批次数量
    model.eval()                         # 设置模型为评估模式
    test_loss, correct = 0, 0
    with torch.no_grad():                # 不计算梯度
        for X, y in test_dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)              # 前向传播,获取预测结果
            test_loss += loss_fn(pred, y).item()  # 累加损失
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()  # 累加正确预测的数量

    test_loss /= num_batches             # 计算平均损失
    correct /= size                      # 计算准确率
    print(f'Test Result:\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f}')

五、模型训练与评估

1. 实例化模型

为了确保模型在训练和测试时的一致性,我们重新实例化模型并将其移动到指定设备。

python 复制代码
model = CNN().to(device)

2. 训练模型

我们设定训练的 epoch 数为 10,即模型将在整个训练数据集上迭代 10 次。

python 复制代码
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

epochs = 10
for epoch in range(epochs):
    print(f'Epoch {epoch + 1}')
    train(training_dataloader, model, loss_fn, optimizer)
print("Finished Training")

​说明​​:

  • 每个 epoch 开始时,打印当前的 epoch 编号。
  • 调用 train 函数进行训练,期间会每 100 个批次打印一次训练损失,便于监控训练过程。
  • 所有 epoch 完成后,打印 "Finished Training" 表示训练结束。

3. 测试模型

训练完成后,我们在测试数据集上评估模型的性能,查看其准确率和平均损失。

python 复制代码
test(test_dataloader, model, loss_fn)

​输出示例​​:

python 复制代码
Test Result:
 Accuracy: 98.5%, Avg loss: 0.045678

​说明​​:

  • ​Accuracy​:模型在测试集上的分类准确率,例如 98.5% 表示模型在测试集上正确分类了 98.5% 的样本。
  • ​Avg loss​:模型在测试集上的平均损失,数值越低表示模型的预测越准确。

六、完整代码回顾

为了便于理解和复现,以下是完整的代码汇总:

python 复制代码
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

'''下载训练数据集'''
training_data = datasets.MNIST(
    root='./data',
    train=True,
    download=True,
    transform=ToTensor()  # 将图像转换为张量
)
test_data = datasets.MNIST(
    root='./data',
    train=False,
    download=True,
    transform=ToTensor()
)

# 创建DataLoader(数据加载器)
training_dataloader = DataLoader(dataset=training_data, batch_size=64)
test_dataloader = DataLoader(dataset=test_data, batch_size=64)

# 设备配置
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
# print(f'Using {device} device')

class CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Sequential(
            nn.Conv2d(in_channels=1,      # 图像通道数,1表示灰度图像
                      out_channels=8,     # 特征图数
                      kernel_size=5,      # 卷积核大小
                      stride=1,           # 步长
                      padding=2,          # 填充参数
                      ),
            nn.ReLU(),                  # 激活函数 ReLU
            nn.MaxPool2d(2),            # 池化操作
        )
        self.conv2 = nn.Sequential(
            nn.Conv2d(8, 16, 5, 1, 2),
            nn.ReLU(),
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2)
        )
        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.ReLU()
        )
        self.out = nn.Linear(64 * 7 * 7, 10)  # 全连接层,将特征展平后输入

    def forward(self, x):
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)  # 将特征图展平为一维向量
        output = self.out(x)
        return output

model = CNN().to(device)
print(model)

def train(train_dataloader, model, loss_fn, optimizer):
    model.train()
    batch_size_num = 1
    for X, y in train_dataloader:
        X, y = X.to(device), y.to(device)
        pred = model(X)
        loss = loss_fn(pred, y)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        loss_val = loss.item()
        if batch_size_num % 100 == 0:
            print(f'Number: {batch_size_num}, Train Loss: {loss_val:>7f}')
        batch_size_num += 1

def test(test_dataloader, model, loss_fn):
    size = len(test_dataloader.dataset)
    num_batches = len(test_dataloader)
    model.eval()
    test_loss, correct = 0, 0
    with torch.no_grad():
        for X, y in test_dataloader:
            X, y = X.to(device), y.to(device)
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

        test_loss /= num_batches
        correct /= size
    print(f'Test Result:\n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f}')

# 实例化模型
model = CNN().to(device)
# print(model)

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

epochs = 10
for epoch in range(epochs):
    print(f'Epoch {epoch + 1}')
    train(training_dataloader, model, loss_fn, optimizer)
print("Finished Training")
test(test_dataloader, model, loss_fn)
相关推荐
vivo互联网技术3 小时前
微信小程序端智能项目工程化实践
前端·人工智能·微信小程序·端智能
vvilkim3 小时前
PyTorch 损失函数与优化器全面指南:从理论到实践
人工智能·pytorch·python
fsnine3 小时前
深度学习——卷积神经网络
人工智能·深度学习·cnn
三之又三4 小时前
卷积神经网络CNN-part2-简单的CNN
人工智能·神经网络·cnn
机器之心4 小时前
谷歌放出Nano Banana六大正宗Prompt玩法,手残党速来
人工智能·openai
艾醒4 小时前
大模型面试题剖析:大模型微调数据集构建
人工智能·算法·程序员
慧都小项4 小时前
构建安全的自动驾驶:软件测试中的编码规范与AI验证
人工智能·测试工具·安全·自动驾驶·parasoft
GEO_JYB4 小时前
车载卫星通信:让自动驾驶“永不掉线”?
人工智能
CoovallyAIHub4 小时前
YOLO-ELA:用于高性能实时绝缘子缺陷检测的高效局部注意力建模
深度学习·算法·计算机视觉