【卷积神经网络】基于CIFAR10数据集实现图像分类【构建、训练、预测】

news2024/9/22 11:32:19

文章目录

  • 1、内容简介
  • 2、CIFAR10 数据集
    • 2.1、数据集概述
    • 2.2、代码使用
      • 2.2.1、查看数据集基本信息
      • 2.2.2、数据加载器
      • 2.2.3、完整代码
  • 3、搭建图像分类网络🔺
    • 3.1、网络结构⭐
    • 3.2、代码构建网络⭐
  • 4、编写训练函数
    • 4.1、多分类交叉熵损失函数🔺
    • 4.2、Adam🔺
    • 4.3、训练函数代码
      • 4.3.1、代码
      • 4.3.2、训练过程说明⭐
      • 4.3.3、重要代码解读
  • 5、预测函数
    • 5.1、代码
    • 5.2、model.eval()⭐
  • 6、完整代码
    • 6.1、CPU
    • 6.2、GPU
  • 7、改进之处🔺
  • 8、小结

🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎人工智能和前端开发。
🦅个人主页:@逐梦苍穹
📕所属专栏:人工智能
🌻gitee地址:xzl的人工智能代码仓库
✈ 您的一键三连,是我创作的最大动力🌹

1、内容简介

本章学习目标:

了解CIFAR10数据集
掌握分类网络搭建
掌握模型构建流程


本文用前面的学习到的知识来构建一个卷积神经网络,并 训练该网络实现图像分类
要完成这个案例,需要学习的内容如下:

  1. 了解 CIFAR10 数据集
  2. 搭建卷积神经网络
  3. 编写训练函数
  4. 编写预测函数

2、CIFAR10 数据集

CIFAR-10 数据集是计算机视觉领域非常著名的一个数据集,常用于图像分类和机器学习算法的训练和评估。
以下是一些关于 CIFAR-10 数据集的详细信息:

2.1、数据集概述

数据集内容:CIFAR-10 数据集包含 10 个类别的 60000 张 32x32 像素的彩色图像;
类别:飞机(airplane)、汽车(automobile)、鸟(bird)、猫(cat)、鹿(deer)、狗(dog)、青蛙(frog)、马(horse)、船(ship)、卡车(truck);
训练和测试集:50000 张训练图像和 10000 张测试图像。

总结起来一句话:
CIFAR-10数据集5万张训练图像、1万张测试图像、10个类别、每个类别有6k个图像,图像大小32×32×3。
下图列举了10个类,每一类随机展示了10张图片:
image.png
官网显示如下:
Snipaste_2024-08-0211111111111111_23-00-08.png

2.2、代码使用

不用自己去下载了,在PyTorch 中的 torchvision.datasets 计算机视觉模块封装了 CIFAR10 数据集,方便我们使用。
重中之重,先导包:
image.png

2.2.1、查看数据集基本信息

image.png
输出如下:
image.png
这里如果没有下载过数据集的,需要在代码里面指定’download=True’:
image.png
下载过后再运行代码,就不会重复运行了:
image.png

显示文件夹已下载

2.2.2、数据加载器

调用DataLoader:from torch.utils.data import DataLoader
image.png
输出如下:
image.png

2.2.3、完整代码

# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/2 23:10
from torchvision.datasets import CIFAR10  # 导入 CIFAR10 数据集类
from torchvision.transforms import Compose  # 导入 Compose 用于组合多个数据变换
from torchvision.transforms import ToTensor  # 导入 ToTensor 用于将图像转换为张量
from torch.utils.data import DataLoader  # 导入 DataLoader 用于批量加载数据


# 1. 数据集基本信息
def test01():
    # TODO 加载训练集,使用 ToTensor 将图像转换为张量
    train = CIFAR10(root='data', train=True, download=True, transform=Compose([ToTensor()]))
    # TODO 加载测试集,使用 ToTensor 将图像转换为张量
    valid = CIFAR10(root='data', train=False, download=True, transform=Compose([ToTensor()]))
    # 输出训练集的数量
    print('训练集数量:', len(train.targets))
    # 输出测试集的数量
    print('测试集数量:', len(valid.targets))
    # 输出训练集中第一张图像的形状
    print("数据集形状:", train[0][0].shape)
    # 输出数据集类别及其对应的索引
    print("数据集类别:", train.class_to_idx)


# 2. 数据加载器
def test02():
    # 加载训练集,使用 ToTensor 将图像转换为张量
    train = CIFAR10(root='data', train=True, transform=Compose([ToTensor()]))
    # 创建数据加载器,每批加载 8 张图像,并打乱顺序
    dataloader = DataLoader(train, batch_size=8, shuffle=True)
    # 遍历数据加载器,取出一批数据
    for x, y in dataloader:
        # 输出一批数据的图像张量形状
        print(x.shape)
        # 输出一批数据的标签
        print(y)
        # 只取一批数据就跳出循环
        break


# 主函数
if __name__ == '__main__':
    # 运行数据集基本信息函数
    test01()
    # 运行数据加载器函数
    test02()

3、搭建图像分类网络🔺

3.1、网络结构⭐

我们要搭建的网络结构如下:
24.png
下面是这个图的详细解释:

  1. 输入形状:32x32
  2. 第一个卷积层
  • 输入 Channel:3(因为输入是一个 32×32 的彩色图像,有 RGB 三个通道)
  • 输出 Channel:6(图中显示第一个卷积层有 6 个输出特征图,说明使用了 6 个卷积核)
  • Kernel Size:3×33 \times 33×3(每个卷积核的大小)
  1. 第一个池化层输入 30x30, 输出 15x15, Kernel Size 为: 2x2, Stride 为: 2
  2. 第二个卷积层 输入 6 个 Channel, 输出 16 个 Channel, Kernel Size 为 3x3
  3. 第二个池化层 输入 13x13, 输出 6x6, Kernel Size 为: 2x2, Stride 为: 2
  4. 第一个全连接层输入 576 维, 输出 120 维
  5. 第二个全连接层 输入 120 维, 输出 84 维
  6. 最后的输出层输入 84 维, 输出 10 维

3.2、代码构建网络⭐

我们在每个卷积计算之后应用 relu 激活函数来给网络增加非线性因素。
relu函数公式: ReLU ( x ) = max ⁡ ( 0 , x ) \text{ReLU}(x) = \max(0, x) ReLU(x)=max(0,x)
网络代码实现——定义一个图像分类的神经网络类,继承自 nn.Module。
image.png
类里面实现两个方法:init(self)和forward(self, x):
init(self)初始化:
image.png
forward(self, x)前向传播:
image.png

4、编写训练函数

我们的训练时,使用多分类交叉熵损失函数Adam 优化器
下面先简单复习一下这两个概念。

4.1、多分类交叉熵损失函数🔺

多分类交叉熵损失函数(Multiclass Cross-Entropy Loss)是深度学习中常用的损失函数,特别适用于多分类任务。
它计算模型 输出的概率分布真实类别分布之间的差异
对于一个具有 N N N 个样本和 C C C 类别的多分类问题,多分类交叉熵损失函数定义为:
Loss = − 1 N ∑ i = 1 N ∑ c = 1 C y i , c log ⁡ ( y ^ i , c ) \text{Loss} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{i,c} \log(\hat{y}_{i,c}) Loss=N1i=1Nc=1Cyi,clog(y^i,c)
其中:

  • N N N 是样本数量。
  • C C C 是类别数量。
  • y i , c y_{i,c} yi,c 是样本 i i i 的真实标签,如果样本 i i i 的真实类别是 c c c,则 y i , c = 1 y_{i,c} = 1 yi,c=1,否则 y i , c = 0 y_{i,c} = 0 yi,c=0
  • y ^ i , c \hat{y}_{i,c} y^i,c 是模型对样本 i i i 的类别 c c c 的预测概率。

实现:

  1. 在PyTorch中,多分类交叉熵损失函数可以使用 torch.nn.CrossEntropyLoss 类来实现。
  2. 该类结合了 nn.LogSoftmaxnn.NLLLoss,因此无需手动应用 softmax 函数到模型的输出。
  3. CrossEntropyLoss 会自动计算 softmax,然后再计算交叉熵损失。

4.2、Adam🔺

Adam简介:
Adam(Adaptive Moment Estimation)优化器是 深度学习中常用的一种优化算法。
它结合了AdaGrad和RMSProp的优点,既能够适应稀疏梯度,又能够处理非平稳目标。
Adam通过计算梯度的
一阶和二阶矩估计动态调整每个参数
学习率

一阶动量是梯度的指数加权移动平均。它可以看作是梯度的平均值,表示了梯度的方向和大小。
二阶动量是梯度平方的指数加权移动平均。它可以看作是梯度的方差,表示了梯度的变化范围。

Adam 优化器的关键特点:

  1. 自适应学习率:每个参数都有独立的学习率,可以根据一阶和二阶梯度估计动态调整。
  2. 计算效率相对于简单的随机梯度下降 (SGD)Adam计算效率高,存储需求小
  3. 适用于大规模数据集:在处理大规模数据集和高维参数空间时表现优越。
  4. 默认超参数效果好:在许多情况下,Adam的默认超参数配置能取得很不错的效果。

Adam通过以下公式来更新参数:

  1. 计算梯度的移动平均
    • m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt1+(1β1)gt
    • v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt1+(1β2)gt2
    • 其中, g t g_t gt 是当前时间步的梯度, m t m_t mt v t v_t vt 分别是梯度的一阶和二阶矩的移动平均, β 1 \beta_1 β1 β 2 \beta_2 β2 是衰减率(通常取 β 1 = 0.9 \beta_1 = 0.9 β1=0.9 β 2 = 0.999 \beta_2 = 0.999 β2=0.999)。
  2. 偏差修正
    • m t ^ = m t 1 − β 1 t \hat{m_t} = \frac{m_t}{1 - \beta_1^t} mt^=1β1tmt
    • v t ^ = v t 1 − β 2 t \hat{v_t} = \frac{v_t}{1 - \beta_2^t} vt^=1β2tvt
  3. 参数更新
    • θ t = θ t − 1 − α m t ^ v t ^ + ϵ \theta_t = \theta_{t-1} - \alpha \frac{\hat{m_t}}{\sqrt{\hat{v_t}} + \epsilon} θt=θt1αvt^ +ϵmt^
    • 其中, α \alpha α 是学习率, ϵ \epsilon ϵ 是一个小常数(防止分母为零,通常取 1 0 − 8 10^{-8} 108)。

4.3、训练函数代码

4.3.1、代码

# 训练模型
def train(BATCH_SIZE, epoch) -> None:
    """
    Args:
        BATCH_SIZE (int): 批量大小。
        epoch (int): 训练轮数。
    """
    # 定义图像转换操作,将图像转换为张量
    transgform = Compose([ToTensor()])
    # 加载 CIFAR10 训练集,并应用定义的图像转换操作
    cifar10 = torchvision.datasets.CIFAR10(root='data', train=True, download=True, transform=transgform)
    # TODO 构建图像分类模型, 此时完成卷积神经网络初始化
    model = ImageClassification()
    # 定义损失函数为交叉熵损失
    criterion = nn.CrossEntropyLoss()
    # 定义优化器为 Adam,学习率为 1e-3
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    for epoch_idx in range(epoch):
        # 构建数据加载器,批次大小为 BATCH_SIZE,打乱数据顺序
        dataloader = DataLoader(cifar10, batch_size=BATCH_SIZE, shuffle=True)
        # 初始化样本数量
        sam_num = 0
        # 初始化损失总和
        total_loss = 0.0
        # 记录开始时间
        start = time.time()
        # 初始化正确分类样本数量
        correct = 0
        for x, y in dataloader:
            # TODO 开始训练
            # 将输入数据送入模型,得到输出
            output = model(x)
            # 计算损失
            loss = criterion(output, y)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播,计算梯度
            loss.backward()
            # 更新模型参数
            optimizer.step()
            # 计算正确分类的样本数量
            correct += (torch.argmax(output, dim=-1) == y).sum().item()
            # 累加损失
            total_loss += (loss.item() * len(y))
            # 累加样本数量
            sam_num += len(y)
        # 打印每个 epoch 的损失、准确率和训练时间
        print('epoch:%2s loss:%.5f acc:%.2f time:%.2fs' %
              (epoch_idx + 1,
               total_loss / sam_num,
               correct / sam_num,
               time.time() - start))
    # 序列化模型,将模型参数保存到文件
    torch.save(model.state_dict(), 'model/image_classification.bin')

4.3.2、训练过程说明⭐

  1. 定义图像转换操作
  2. 加载 CIFAR10 训练集
  3. 构建图像分类模型
  4. 定义损失函数
  5. 定义优化器
  6. 进行多个 epoch 的训练
    • 6.1 构建数据加载器
      • 使用 DataLoader 构建数据加载器,设置批次大小为 BATCH_SIZE,并打乱数据顺序。
    • 6.2 初始化样本数量计数器
      • sam_num 初始化为 0,用于记录样本数量。
    • 6.3 初始化损失总和计数器
      • total_loss 初始化为 0.0,用于累加每个批次的损失。
    • 6.4 初始化正确分类样本数量计数器
    • 6.5 进行一个批次的训练
      • 6.5.1 将输入数据送入模型
      • 6.5.2 计算损失——计算预测输出与真实标签之间的损失
      • 6.5.3 梯度清零
      • 6.5.4 反向传播,计算梯度loss.backward()
      • 6.5.5 更新模型参数optimizer.step()
      • 6.5.6 计算正确分类的样本数量
      • 6.5.7 累加当前批次的损失
      • 6.5.8 累加当前批次的样本数量

4.3.3、重要代码解读

image.png
分步解释:

  1. torch.argmax(output, dim=-1):
    • torch.argmax 函数返回指定维度上最大值的索引。
    • output 是模型的输出,通常是一个包含预测分数的张量。
    • dim=-1 指定在最后一个维度上寻找最大值的索引,通常这个维度对应于类别的维度。
    • 结果是一个张量,其中包含每个样本的预测类别索引。
  2. (torch.argmax(output, dim=-1) == y):
    • y 是实际的标签张量。
    • torch.argmax(output, dim=-1) 计算得到的是模型的预测类别索引。
  3. sum():
    • 对布尔张量中的元素求和。
    • 在 PyTorch 中,布尔值 True 被视为 1False 被视为 0

假设有一个批次的输出 output 和真实标签 y,形状分别为 [4, 3][4]
image.png
输出结果为:
image.png

5、预测函数

5.1、代码

我们加载训练好的模型,对测试集中的 1 万条样本进行预测,查看模型在测试集上的准确率。
代码总览:
image.png
程序输出结果(这是读取训练10轮的模型):
image.png
训练过程:
image.png
我独显的算力不行,凑合看吧😭

5.2、model.eval()⭐

在深度学习中,模型在训练和评估(推理)阶段的行为可能会有所不同。
PyTorch 提供了 model.train()model.eval() 两种模式,用于切换模型在训练和评估时的行为。
model.train() 与 model.eval():

  • model.train()
    • 用于设置模型为训练模式。
    • 这会启用训练时特有的操作,例如 Dropout 和 Batch Normalization。
    • Dropout 在训练时会随机地丢弃部分神经元,以防止过拟合;Batch Normalization 在训练时会根据当前批次的数据动态调整均值和方差。
  • model.eval()
    • 用于设置模型为评估模式。
    • 这会关闭训练时特有的操作,例如 Dropout 和 Batch Normalization 的动态调整。
    • Dropout 在评估时不丢弃任何神经元;Batch Normalization 在评估时使用训练时计算的全局均值和方差,而不是当前批次的数据。

6、完整代码

6.1、CPU

# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/3 2:21
import torch  # 导入 PyTorch 主库
import torch.nn.functional as F  # 导入 PyTorch 的神经网络功能模块
import torch.nn as nn  # 导入 PyTorch 的神经网络模块
import torch.optim as optim  # 导入 PyTorch 的优化器模块
import torchvision  # 导入 PyTorch 的计算机视觉工具包
from torchvision.transforms import Compose  # 导入 Compose 用于组合多个数据变换
from torchvision.transforms import ToTensor  # 导入 ToTensor 用于将图像转换为张量
from torch.utils.data import DataLoader  # 导入数据加载器模块
import time  # 导入时间模块


# 定义一个图像分类的神经网络类,继承自 nn.Module
class ImageClassification(nn.Module):
    # 初始化方法,定义网络的层
    def __init__(self):
        super(ImageClassification, self).__init__()  # 调用父类的初始化方法
        # 定义第一个卷积层:输入通道数 3,输出通道数 6,卷积核大小 3x3
        self.conv1 = nn.Conv2d(3, 6, stride=1, kernel_size=3)
        # 定义第一个池化层:池化核大小 2x2,步幅 2
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 定义第二个卷积层:输入通道数 6,输出通道数 16,卷积核大小 3x3
        self.conv2 = nn.Conv2d(6, 16, stride=1, kernel_size=3)
        # 定义第二个池化层:池化核大小 2x2,步幅 2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 定义第一个全连接层:输入节点数 576,输出节点数 120
        self.linear1 = nn.Linear(576, 120)
        # 定义第二个全连接层:输入节点数 120,输出节点数 84
        self.linear2 = nn.Linear(120, 84)
        # 定义输出层:输入节点数 84,输出节点数 10(对应 10 个分类)
        self.out = nn.Linear(84, 10)

    # 前向传播方法,定义数据如何通过网络层
    def forward(self, x):
        # TODO 应用第一个卷积层和 ReLU 激活函数
        x = F.relu(self.conv1(x))
        # 应用第一个池化层
        x = self.pool1(x)
        # TODO 应用第二个卷积层和 ReLU 激活函数
        x = F.relu(self.conv2(x))
        # 应用第二个池化层
        x = self.pool2(x)
        # TODO 展平操作,将多维特征图展平为一维向量
        # TODO x.size(0)获取第一个维度的大小, 即 batch_size; -1表示自动计算
        # TODO 如果批次大小为 1, 特征为16x6x6, 则展平后的输入形状为[1,576]
        # TODO 由于最后一个批次可能不够 32,所以需要根据批次数量来 flatten
        x = x.reshape(x.size(0), -1)
        # 应用第一个全连接层和 ReLU 激活函数
        x = F.relu(self.linear1(x))
        # 应用第二个全连接层和 ReLU 激活函数
        x = F.relu(self.linear2(x))
        # TODO 返回输出层的结果
        return self.out(x)


# 训练模型
def train(BATCH_SIZE, epoch) -> None:
    """
    Args:
        BATCH_SIZE (int): 批量大小。
        epoch (int): 训练轮数。
    """
    # 定义图像转换操作,将图像转换为张量
    transgform = Compose([ToTensor()])
    # 加载 CIFAR10 训练集,并应用定义的图像转换操作
    cifar10 = torchvision.datasets.CIFAR10(root='data', train=True, download=True, transform=transgform)  # TODO train=False测试集
    # TODO 构建图像分类模型, 此时完成卷积神经网络初始化
    model = ImageClassification()
    # 定义损失函数为交叉熵损失
    criterion = nn.CrossEntropyLoss()
    # 定义优化器为 Adam,学习率为 1e-3
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    for epoch_idx in range(epoch):
        # 构建数据加载器,批次大小为 BATCH_SIZE,打乱数据顺序
        dataloader = DataLoader(cifar10, batch_size=BATCH_SIZE, shuffle=True)
        # 初始化样本数量
        sam_num = 0
        # 初始化损失总和
        total_loss = 0.0
        # 记录开始时间
        start = time.time()
        # 初始化正确分类样本数量
        correct = 0
        for x, y in dataloader:
            # TODO 开始训练
            # 将输入数据送入模型,得到输出
            output = model(x)
            # 计算损失
            loss = criterion(output, y)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播,计算梯度
            loss.backward()
            # 更新模型参数
            optimizer.step()
            # 计算正确分类的样本数量
            correct += (torch.argmax(output, dim=-1) == y).sum().item()
            # 累加损失
            total_loss += (loss.item() * len(y))
            # 累加样本数量
            sam_num += len(y)
        # 打印每个 epoch 的损失、准确率和训练时间
        print('epoch:%2s loss:%.5f acc:%.2f time:%.2fs' %
              (epoch_idx + 1,
               total_loss / sam_num,
               correct / sam_num,
               time.time() - start))
    # 序列化模型,将模型参数保存到文件
    torch.save(model.state_dict(), 'model/image_classification.bin')


# 预测函数
def predict(BATCH_SIZE):
    # TODO 加载 CIFAR10 测试集,并将其转换为张量
    # 使用 Compose 将多个转换操作组合在一起,这里只使用 ToTensor 将图像转换为张量
    transform = Compose([ToTensor()])
    # 使用 torchvision.datasets.CIFAR10 加载 CIFAR10 测试集,设置 train=False 表示加载测试集,transform=transform 表示应用上述的图像转换操作
    cifar10 = torchvision.datasets.CIFAR10(root='data', train=False, download=True, transform=transform)  # TODO train=False测试集
    # TODO 构建数据加载器,用于批量加载测试数据
    dataloader = DataLoader(cifar10, batch_size=BATCH_SIZE, shuffle=True)  # shuffle=True 表示打乱数据顺序
    # TODO 创建卷积神经网络
    model = ImageClassification()
    # 加载训练好的模型参数
    model.load_state_dict(torch.load('model/image_classification.bin'))
    model.eval()  # TODO 设置模型为评估模式
    total_correct = 0  # 初始化正确分类的样本数量为 0
    total_samples = 0  # 初始化总样本数量为 0
    # 遍历数据加载器,按批次处理数据
    for x, y in dataloader:
        # 将输入数据送入模型,得到输出
        output = model(x)
        # 计算正确分类的样本数量
        total_correct += (torch.argmax(output, dim=-1) == y).sum()
        # 累加当前批次的样本数量
        total_samples += len(y)
    print('Acc: %.2f' % (total_correct / total_samples))


if __name__ == '__main__':
    train(BATCH_SIZE=32, epoch=10)  # 训练模型, 批次大小为 32, 训练轮数为 10
    predict(BATCH_SIZE=32)  # 测试模型, 批次大小为 32

6.2、GPU

# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/3 2:29
# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/3 2:21
import torch  # 导入 PyTorch 主库
import torch.nn.functional as F  # 导入 PyTorch 的神经网络功能模块
import torch.nn as nn  # 导入 PyTorch 的神经网络模块
import torch.optim as optim  # 导入 PyTorch 的优化器模块
import torchvision  # 导入 PyTorch 的计算机视觉工具包
from torchvision.transforms import Compose  # 导入 Compose 用于组合多个数据变换
from torchvision.transforms import ToTensor  # 导入 ToTensor 用于将图像转换为张量
from torch.utils.data import DataLoader  # 导入数据加载器模块
import time  # 导入时间模块


# 定义一个图像分类的神经网络类,继承自 nn.Module
class ImageClassification(nn.Module):
    # 初始化方法,定义网络的层
    def __init__(self):
        super(ImageClassification, self).__init__()  # 调用父类的初始化方法
        # 定义第一个卷积层:输入通道数 3,输出通道数 6,卷积核大小 3x3
        self.conv1 = nn.Conv2d(3, 6, stride=1, kernel_size=3)
        # 定义第一个池化层:池化核大小 2x2,步幅 2
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 定义第二个卷积层:输入通道数 6,输出通道数 16,卷积核大小 3x3
        self.conv2 = nn.Conv2d(6, 16, stride=1, kernel_size=3)
        # 定义第二个池化层:池化核大小 2x2,步幅 2
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        # 定义第一个全连接层:输入节点数 576,输出节点数 120
        self.linear1 = nn.Linear(576, 120)
        # 定义第二个全连接层:输入节点数 120,输出节点数 84
        self.linear2 = nn.Linear(120, 84)
        # 定义输出层:输入节点数 84,输出节点数 10(对应 10 个分类)
        self.out = nn.Linear(84, 10)

    # 前向传播方法,定义数据如何通过网络层
    def forward(self, x):
        # TODO 应用第一个卷积层和 ReLU 激活函数
        x = F.relu(self.conv1(x))
        # 应用第一个池化层
        x = self.pool1(x)
        # TODO 应用第二个卷积层和 ReLU 激活函数
        x = F.relu(self.conv2(x))
        # 应用第二个池化层
        x = self.pool2(x)
        # TODO 展平操作,将多维特征图展平为一维向量
        # TODO x.size(0)获取第一个维度的大小, 即 batch_size; -1表示自动计算
        # TODO 如果批次大小为 1, 特征为16x6x6, 则展平后的输入形状为[1,576]
        # TODO 由于最后一个批次可能不够 32,所以需要根据批次数量来 flatten
        x = x.reshape(x.size(0), -1)
        # 应用第一个全连接层和 ReLU 激活函数
        x = F.relu(self.linear1(x))
        # 应用第二个全连接层和 ReLU 激活函数
        x = F.relu(self.linear2(x))
        # TODO 返回输出层的结果
        return self.out(x)


# 训练模型
def train(BATCH_SIZE, epoch) -> None:
    """
    Args:
        BATCH_SIZE (int): 批量大小。
        epoch (int): 训练轮数。
    """
    # 定义图像转换操作,将图像转换为张量
    transform = Compose([ToTensor()])
    # 加载 CIFAR10 训练集,并应用定义的图像转换操作
    cifar10 = torchvision.datasets.CIFAR10(root='data', train=True, download=True, transform=transform)
    # TODO 构建图像分类模型, 并将模型移动到设备上
    model = ImageClassification().to(device)
    # 定义损失函数为交叉熵损失
    criterion = nn.CrossEntropyLoss()
    # 定义优化器为 Adam,学习率为 1e-3
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    for epoch_idx in range(epoch):
        # 构建数据加载器,批次大小为 BATCH_SIZE,打乱数据顺序
        dataloader = DataLoader(cifar10, batch_size=BATCH_SIZE, shuffle=True)
        # 初始化样本数量
        sam_num = 0
        # 初始化损失总和
        total_loss = 0.0
        # 记录开始时间
        start = time.time()
        # 初始化正确分类样本数量
        correct = 0
        for x, y in dataloader:
            # TODO 将输入数据移动到设备上
            x, y = x.to(device), y.to(device)
            # 将输入数据送入模型,得到输出
            output = model(x)
            # 计算损失
            loss = criterion(output, y)
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播,计算梯度
            loss.backward()
            # 更新模型参数
            optimizer.step()
            # 计算正确分类的样本数量
            correct += (torch.argmax(output, dim=-1) == y).sum().item()
            # 累加损失
            total_loss += (loss.item() * len(y))
            # 累加样本数量
            sam_num += len(y)
        # 打印每个 epoch 的损失、准确率和训练时间
        print('epoch:%2s loss:%.5f acc:%.2f time:%.2fs' %
              (epoch_idx + 1,
               total_loss / sam_num,
               correct / sam_num,
               time.time() - start))
    # 序列化模型,将模型参数保存到文件
    torch.save(model.state_dict(), 'model-gpu/image_classification.bin')


# 预测函数
def predict(BATCH_SIZE):
    print('device:', device)
    # TODO 加载 CIFAR10 测试集,并将其转换为张量
    # 使用 Compose 将多个转换操作组合在一起,这里只使用 ToTensor 将图像转换为张量
    transform = Compose([ToTensor()])
    # 使用 torchvision.datasets.CIFAR10 加载 CIFAR10 测试集,设置 train=False 表示加载测试集,transform=transform 表示应用上述的图像转换操作
    cifar10 = torchvision.datasets.CIFAR10(root='data', train=False, download=True, transform=transform)
    # TODO 构建数据加载器,用于批量加载测试数据
    dataloader = DataLoader(cifar10, batch_size=BATCH_SIZE, shuffle=True)  # shuffle=True 表示打乱数据顺序
    # TODO 创建卷积神经网络
    model = ImageClassification()
    # 加载训练好的模型参数
    model.load_state_dict(torch.load('model-gpu/image_classification.bin'))
    model.to(device)  # TODO 将模型移动到设备上
    model.eval()  # TODO 设置模型为评估模式
    total_correct = 0  # 初始化正确分类的样本数量为 0
    total_samples = 0  # 初始化总样本数量为 0
    # 遍历数据加载器,按批次处理数据
    for x, y in dataloader:
        # TODO 将输入数据移动到设备上
        x, y = x.to(device), y.to(device)
        # 将输入数据送入模型,得到输出
        output = model(x)
        # 计算正确分类的样本数量
        total_correct += (torch.argmax(output, dim=-1) == y).sum().item()
        # 累加当前批次的样本数量
        total_samples += len(y)
    print('Acc: %.2f' % (total_correct / total_samples))


if __name__ == '__main__':
    # 选择运行设备
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    print("PyTorch版本: ", torch.__version__)  # 打印PyTorch版本
    print("torchvision版本 ", torchvision.__version__)  # 打印torchvision版本
    print("CUDA是否可用: ", torch.cuda.is_available())  # 检查CUDA是否可用
    print("GPU设备数量: ", torch.cuda.device_count())
    print("GPU设备名称: ", torch.cuda.get_device_name(0))
    print("当前设备: ", device)

    # train(BATCH_SIZE=32, epoch=100)  # 训练模型, 批次大小为 32, 训练轮数为 10
    predict(BATCH_SIZE=32)  # 测试模型, 批次大小为 32

7、改进之处🔺

网络模型还可以以下几个方面来调整:

  1. 增加卷积核输出通道数
  2. 增加全连接层的参数量
  3. 调整学习率
  4. 调整优化方法
  5. 修改激活函数
  6. 等等…

比如,对网络参数微调,让网络参数量增加,然后再调整一下学习率,由 1e-3 修改为 1e-4等等操作。
image.png
网络模型修改:
image.png
可以看到,就是增加了整个卷积神经网络的复杂性。
训练十次差不多提高准确率0.1,训练100次肯定更明显!
100次的你们去跑吧…这网络复杂度上来了,我这设备干不动了……😭
image.png

8、小结

本文主要讲解了如何使用卷积层和池化层来设计、构建一个卷积神经网络。
回顾一下训练过程:

  1. 定义损失函数
  2. 定义优化器
  3. 进行多个 epoch 的训练:
    • 3.1 构建数据加载器
    • 3.2 初始化样本数量计数器
    • 3.3 初始化损失总和计数器
    • 3.4 初始化正确分类样本数量计数器
    • 3.5 进行一个批次的训练
      • 3.5.1 将输入数据送入模型
      • 3.5.2 计算损失
      • 3.5.3 梯度清零
      • 3.5.4 反向传播,计算梯度
      • 3.5.5 更新模型参数
      • 3.5.6 计算正确分类的样本数量
      • 3.5.7 累加当前批次的损失
      • 3.5.8 累加当前批次的样本数量

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1974063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

泛微开发修炼之旅--41Ecology基于触发器实现增量数据同步(人员、部门、岗位、人员关系表、人岗关系表)

一、需求背景 我们在项目上遇到一个需求,需要将组织机构数据(包含人员信息、部门信息、分部信息、人岗关系)生成的增量数据,实时同步到三方的系统中,三方要求,只需要增量数据即可。 那么基于ecology系统&a…

【C++高阶】:C++11的深度解析上

✨ 心似白云常自在,意如流水任东西 🌏 📃个人主页:island1314 🔥个人专栏:C学习 🚀 欢迎关注:👍点赞 &#x1f4…

数说故事|引爆社媒的森贝儿IP,品牌如何实现流量变现?

以可爱、雅痞、贱萌......的外表加魔性舞姿出圈的可爱小狗——森贝儿贵宾犬Milo,用“可爱微怒”的表情演绎着当代打工人的“疯态”,并迅速晋升成不少打工人高频使用的表情包。 最近几年,“萌系”爆款IP频出,用小动物的形象、可爱…

一键生成视频并批量上传视频抖音、bilibili、腾讯(已打包)

GenerateAndAutoupload Github地址:https://github.com/cmdch2017/GenerateAndAutoupload 如何下载(找到最新的release) https://github.com/cmdch2017/GenerateAndAutoupload/releases/download/v1.0.1/v1.0.1.zip 启动必知道 conf.py …

Redis学习[5] ——Redis过期删除和内存淘汰

六、Redis过期键值删除 6.1 Redis的过期键值删除策略 6.1.1 什么是过期键值删除? Redis中是可以对key设置过期时间的,所以需要有相应的机制将已过期的键值对删除,也就是**过期键值删除策略。Redis会用一个过期字典(expires dic…

如何改网络的ip地址:实用方法与步骤解析

在数字化时代,网络IP地址作为设备在互联网上的唯一标识,其重要性不言而喻。然而,在某些特定场景下,如网络测试、隐私保护或突破地域限制等,我们可能需要更改网络IP地址。那么,如何安全、有效地实现这一操作…

学习日志:update 没加索引会锁全表

文章目录 前言一、为什么会发生这种的事故如何避免这种事故的发生?总结 前言 在线上执行一条 update 语句修改数据库数据的时候,where 条件没有带上索引,导致业务直接崩了 为什么会发生这种的事故? 又该如何避免这种事故的发生&a…

html+css練習:iconfont使用

1.網址地址:https://www.iconfont.cn/search/index 2.註冊登錄,將需要的圖標添加到購物車 3.下載代碼 4.下載后的代碼有一個html頁面,裡面有詳細的使用方式

Linux进程间通信学习2

文章目录 共享内存信号信号概述以及种类信号的处理信号相关函数(简单)运用小demo实现ctrlc无法终止进程使用kill函数在程序内部实现一个进程杀死另外一个进程 信号相关函数高级版运用函数小demo 信号量信号量相关函数运用小demo: 共享内存 相比于前三个…

基于微信小程序的宠物服务平台(系统源码+lw+部署文档+讲解等)

文章目录 目录 详细视频演示 系统详细设计截图 微信小程序系统的实现 1.1系统前台功能的实现 2.1微信小程序开发环境搭建 2.2微信开发者工具 2.3程序应用相关技术和知识 2.3.1小程序目录结构以及框架介绍 2.3.2 Java技术 2.3.3 MySQL数据库 2.3.4 SSM框架 源码获…

构建铁路安全防线:EasyCVR视频+AI智能分析赋能铁路上道作业高效监管

一、方案背景 随着我国铁路特别是高速铁路的快速发展,铁路运营里程不断增加,铁路沿线的安全环境对保障铁路运输的安全畅通及人民群众的生命财产安全具有至关重要的作用。铁路沿线安全环境复杂多变,涉及多种风险因素,如人员入侵、…

函数递归超详解!

目录 1.什么是递归调用? 直接调用 间接调用 2.什么是递归? 3.递归举例 3.1求n!的阶乘 3.1.1.非递归法 3.1.2.递归法 3.1.2.1分析和代码实现 3.2顺序打印一个整数的每一位 3.2.1分析和代码实现 4.递归与迭代 4.1举例:斐波那契数列 …

开放式耳机更适合运动的时候使用?开放式耳机推荐指南

开放式耳机确实非常适合运动时使用,原因主要有以下几点。 首先,保持对外界的感知是很重要的一点。在运动的时候,我们需要听到周围的环境声音,比如车辆的行驶声、行人的呼喊等,以便及时做出反应,保证自身安全…

【MySQL】索引概念解析

1.什么是索引? MySQL中的索引是一种数据结构,用于帮助MySQL数据库管理系统快速查询数据。索引的主要目的是提高数据检索的速度,减少数据库系统需要扫描的数据量。 优点: 索引可以极大的提高数据检索效率,降低数据库…

【Nuxt】配置

Nuxt 配置 nuxt.config.ts 里面可以添加相关配置: runtimeConfig 运行时配置。 // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({compatibilityDate: 2024-04-03,devtools: {enabled: true},runtimeConfig: {appKey: …

手拉手模型笔记and一线三角笔记

手拉手模型 基本需要:两个 顶角相等 的 等腰 三角形 共 顶点 反手拉手: 等边 等腰 R t △ 等边\\等腰Rt△ 等边等腰Rt△ 左手拉左手,右手拉右手( − 红线 − \textcolor{red}{-红线-} −红线−): △ A B D ≅ △ A C E ( S A S ) △ABD \cong △ACE(S…

一次多波束和浅地层处理的经历—信标机出问题?

最近处理多波束和浅地层时,一个从来没有过的问题出现了。 多波束数据(.pds)是由PDS2000采集的,使用设备型号为T50P。浅地层数据(.raw)是有SESWIN采集的,使用设备型号为SES2000 Standard。 1、多波束处理 多波束数据采用CARIS11.3处理的。船…

返校季热度持续发酵,赛盈分销浅谈下半年选品趋势!

小孩学习用的东西,那可真是省不了一点,该买的,家长还是会买。 特别是在每年的7-9月份正是海外返校季高消费的时候,这也是卖家少有的能在淡季里血赚的机会了。 都说早起的鸟儿有虫吃,一些朋友提前行动的,已经…

day17 Java流程控制——用户交互Scanner

day17 Java流程控制——用户交互Scanner 目录 day17 Java流程控制——用户交互Scanner1. 什么是Scanner对象?2. 实操 1. 什么是Scanner对象? Scanner对象是Java编程语言中的一个类,存在于java.util包中。它用于获取输入,可以是各…

数据库的安装初始化及管理

1. 官网下载或者 wget [rootmysql ~] # ls anaconda-ks.cfg initserver.sh mysql-8.0.33-1.el7.x86_64.rpm-bundle.tar mysql-community-client-8.0.33-1.el7.x86_64.rpm mysql-community-client-plugins-8.0.33-1.el7.x86_64.rpm mysql-community-common-8.0.33-1.el7.…