深度学习11. CNN经典网络 LeNet-5实现CIFAR-10

在这里插入图片描述

本文在前节程序基础上,实现对CIFAR-10的训练与测试,以加深对LeNet-5网络的理解 。

首先,要了解LeNet-5并不适合训练 CIFAR-10 , 最后的正确率不会太理想 。

一、CIFAR-10介绍

CIFAR-10是一个常用的图像分类数据集,由10类共计60,000张32x32大小的彩色图像组成,每类包含6,000张图像。这些图像被平均分为了5个训练批次和1个测试批次,每个批次包含10,000张图像。CIFAR-10数据集中的10个类别分别为:飞机、汽车、鸟类、猫、鹿、狗、青蛙、马、船和卡车。

相比之下,MNIST是一个手写数字分类数据集,由10个数字(0-9)共计60,000个训练样本和10,000个测试样本组成,每个样本是一个28x28的灰度图像。

与MNIST相比,CIFAR-10更具挑战性,因为它是一个彩色图像数据集,每张图像包含更多的信息和细节,难度更高。此外,CIFAR-10的类别也更加多样化,更加贴近实际应用场景。因此,CIFAR-10更适合用于测试和评估具有更高难度的图像分类模型,而MNIST则更适合用于介绍和入门级别的模型训练和测试。

二、PyTorch的 transforms 介绍

PyTorch中的transforms是用于对数据进行预处理和增强的工具,主要用于图像数据的处理,它可以方便地对数据进行转换,使其符合神经网络的输入要求。

transforms的方法:

  • ToTensor : 将数据转换为PyTorch中的张量格式。
  • Normalize:对数据进行标准化,使其均值为0,方差为1,以便网络更容易训练。
  • Resize:调整图像大小。
  • RandomCrop:随机裁剪图像的一部分。
  • CenterCrop:从图像的中心裁剪出一部分。
  • RandomHorizontalFlip :以一定的概率随机水平翻转图像,以增加训练集的多样性。
  • RandomVerticalFlip:以一定的概率随机垂直翻转图像,以增加训练集的多样性。
  • RandomRotation:以一定的概率随机旋转图像。
  • ColorJitter:随机调整图像的亮度、对比度、饱和度和色调。
  • RandomErasing:随机擦除图像中的一部分区域,以增加训练集的多样性。

使用transforms可以方便地进行数据预处理和增强,提高模型的鲁棒性和泛化能力。在实际应用中,可以根据具体问题和需求进行选择和组合。

三、实现步骤

1. 准备数据

下面定义加载CIFAR-10数据集,首先会对图片进行一些处理:

  • transforms.RandomHorizontalFlip():随机水平翻转图像
  • transforms.RandomCrop(32, padding=4):随机裁剪图像,大小为32x32,边缘填充4个像素
  • transforms.ToTensor():将图像转换为张量,并归一化到[0,1]范围内
  • transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)):将张量标准化,使每个通道的均值为0.5,标准差为0.5

对数据的处理可以增加数据的多样性和丰富性,以提高神经网络的泛化能力和准确率。

transform = transforms.Compose(
    [transforms.RandomHorizontalFlip(),
     transforms.RandomCrop(32, padding=4),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=32,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(testset, batch_size=32,
                                         shuffle=False, num_workers=2)

2. 模型定义

class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        # 定义卷积层C1,输入通道数为1,输出通道数为6,卷积核大小为5x5
        self.conv1 = nn.Conv2d(3, 6, kernel_size=5, stride=1)
        # 定义池化层S2,池化核大小为2x2,步长为2
        self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 定义卷积层C3,输入通道数为6,输出通道数为16,卷积核大小为5x5
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1)
        # 定义池化层S4,池化核大小为2x2,步长为2
        self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 定义全连接层F5,输入节点数为16x4x4=256,输出节点数为120
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        # 定义全连接层F6,输入节点数为120,输出节点数为84
        self.fc2 = nn.Linear(120, 84)
        # 定义输出层,输入节点数为84,输出节点数为10
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 卷积层C1
        x = self.conv1(x)
        # 池化层S2
        x = self.pool1(torch.relu(x))
        # 卷积层C3
        x = self.conv2(x)
        # 池化层S4
        x = self.pool2(torch.relu(x))
        # 全连接层F5
        x = x.view(-1, 16 * 5 * 5)
        x = self.fc1(x)
        x = torch.relu(x)
        # 全连接层F6
        x = self.fc2(x)
        x = torch.relu(x)
        # 输出层
        x = self.fc3(x)
        return x

和上节类似 , 这个模型定义了经典的 LeNet-5。它由两个卷积层、两个池化层和三个全连接层组成,层间通过一定的非线性激活函数进行连接。

  • 模型中的第一个卷积层(C1)的输入通道数是3,即输入的是3通道的图像数据;输出通道数为6,表示该层有6个卷积核,每个卷积核可以提取出一种特征,卷积核大小为5x5。
  • 第一个池化层(S2)的池化核大小为2x2,步长为2,可以将特征图的大小降低一半。
  • 卷积层(C3),输入通道数为6,输出通道数为16,卷积核大小为5x5。然后再经过一个池化层(S4),池化核大小为2x2,步长为2,同样可以将特征图的大小降低一半。
  • 接下来是三个全连接层(F5、F6、F7),其中 F5 的输入节点数为16x5x5=400,输出节点数为120;F6 的输入节点数为120,输出节点数为84;输出层的输入节点数为84,输出节点数为10,表示对10个类别进行分类。

最后,网络输出了分类结果。在前向传播过程中,经过卷积、池化、全连接等操作,每层的输出都要经过一定的非线性激活函数,这里使用的是 ReLU 函数(即 Rectified Linear Unit)。

3. 训练与测试

model = LeNet5()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
criterion = nn.CrossEntropyLoss()
if __name__ == '__main__':
    # 定义模型保存路径和文件名
    model_path = 'model.pth'
    if os.path.exists(model_path):
        # 存在,直接加载模型
        model.load_state_dict(torch.load(model_path))
        print('Loaded model from', model_path)
    else:
        # 训练模型
        for epoch in range(epochs):
            model.train()
            for images, labels in train_loader:
                # 将数据放入模型
                optimizer.zero_grad()
                outputs = model(images)
                loss = criterion(outputs, labels)
                loss.backward()
                optimizer.step()

            # 在测试集上测试模型
            model.eval()
            correct = 0
            with torch.no_grad():
                for images, labels in test_loader:
                    # 将数据放入模型
                    outputs = model(images)
                    _, predicted = torch.max(outputs, 1)
                    correct += (predicted == labels).sum().item()

            accuracy = 100 * correct / len(testset)
            print('Epoch [{}/{}], Loss: {:.4f}, Accuracy: {:.2f}%'.format(epoch + 1, epochs, loss.item(), accuracy))

        torch.save(model.state_dict(), 'model.pth')

    for i in range(10):
        img, label = next(iter(test_loader))
        img = img[i].unsqueeze(0)

        # 使用模型进行预测
        model.eval()
        with torch.no_grad():
            output = model(img)

        # 解码预测结果
        pred = output.argmax(dim=1).item()
        print(f'Predicted class: {
      
      pred}, actual value: {
      
      label[i]}')

四、完整代码

import os

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
from torchvision import datasets, transforms
from torch.utils.data import DataLoader


# 定义 LeNet-5 模型
class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        # 定义卷积层C1,输入通道数为1,输出通道数为6,卷积核大小为5x5
        self.conv1 = nn.Conv2d(3, 6, kernel_size=5, stride=1)
        # 定义池化层S2,池化核大小为2x2,步长为2
        self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 定义卷积层C3,输入通道数为6,输出通道数为16,卷积核大小为5x5
        self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1)
        # 定义池化层S4,池化核大小为2x2,步长为2
        self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)
        # 定义全连接层F5,输入节点数为16x4x4=256,输出节点数为120
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        # 定义全连接层F6,输入节点数为120,输出节点数为84
        self.fc2 = nn.Linear(120, 84)
        # 定义输出层,输入节点数为84,输出节点数为10
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 卷积层C1
        x = self.conv1(x)
        # print('卷积层C1后的形状:', x.shape)
        # 池化层S2
        x = self.pool1(torch.relu(x))
        # print('池化层S2后的形状:', x.shape)
        # 卷积层C3
        x = self.conv2(x)
        # print('卷积层C3后的形状:', x.shape)
        # 池化层S4
        x = self.pool2(torch.relu(x))
        # print('池化层S4后的形状:', x.shape)
        # 全连接层F5
        x = x.view(-1, 16 * 5 * 5)
        x = self.fc1(x)
        # print('全连接层F5后的形状:', x.shape)
        x = torch.relu(x)
        # 全连接层F6
        x = self.fc2(x)
        # print('全连接层F6后的形状:', x.shape)
        x = torch.relu(x)
        # 输出层
        x = self.fc3(x)
        # print('输出层后的形状:', x.shape)
        return x

# 设置超参数
batch_size = 128
learning_rate = 0.01
epochs = 10

# CIFAR-10
# 准备数据
transform = transforms.Compose(
    [transforms.RandomHorizontalFlip(),
     transforms.RandomCrop(32, padding=4),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(trainset, batch_size=32,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(testset, batch_size=32,
                                         shuffle=False, num_workers=2)

# 实例化模型和优化器
model = LeNet5()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)
criterion = nn.CrossEntropyLoss()
if __name__ == '__main__':
    # 定义模型保存路径和文件名
    model_path = 'model.pth'
    if os.path.exists(model_path):
        # 存在,直接加载模型
        model.load_state_dict(torch.load(model_path))
        print('Loaded model from', model_path)
    else:
        # 训练模型
        for epoch in range(epochs):
            model.train()
            for images, labels in train_loader:
                # 将数据放入模型
                optimizer.zero_grad()
                outputs = model(images)
                loss = criterion(outputs, labels)
                loss.backward()
                optimizer.step()

            # 在测试集上测试模型
            model.eval()
            correct = 0
            with torch.no_grad():
                for images, labels in test_loader:
                    # 将数据放入模型
                    outputs = model(images)
                    _, predicted = torch.max(outputs, 1)
                    correct += (predicted == labels).sum().item()

            accuracy = 100 * correct / len(testset)
            print('Epoch [{}/{}], Loss: {:.4f}, Accuracy: {:.2f}%'.format(epoch + 1, epochs, loss.item(), accuracy))

        torch.save(model.state_dict(), 'model.pth')

    for i in range(10):
        img, label = next(iter(test_loader))
        img = img[i].unsqueeze(0)

        # 使用模型进行预测
        model.eval()
        with torch.no_grad():
            output = model(img)

        # 解码预测结果
        pred = output.argmax(dim=1).item()
        print(f'Predicted class: {
      
      pred}, actual value: {
      
      label[i]}')

最后训练准确率仅有47.13%。

猜你喜欢

转载自blog.csdn.net/xundh/article/details/129635574