第P2周:CIFAR10彩色图片识别


FROM


我的环境

  • 语言环境:Python 3.8.10
  • 开发工具:Jupyter Lab
  • 深度学习环境:
    • torch==1.12.1+cu113
    • torchvision==0.13.1+cu113

1. 准备知识

1.1 检查环境

import torch  # 导入PyTorch库,用于构建深度学习模型
import torch.nn as nn  # 导入torch.nn模块,包含构建神经网络所需的类和函数
import matplotlib.pyplot as plt  # 导入matplotlib.pyplot模块,用于数据可视化
import torchvision  # 导入torchvision库,包含处理图像和视频的工具和预训练模型

# 设置硬件设备,如果有GPU则使用,没有则使用cpu
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 检查系统是否有可用的GPU,如果有则使用GPU,否则使用CPU
device  # 打印当前设备,以确认是使用GPU还是CPU

输出:
输出

1.2 数据导入

使用dataset下载CIFAR10数据集,并划分好训练集与测试集
使用dataloader加载数据,并设置好基本的batch_size

参数解释:
data:数据集的本地存储路径。
train=True:表示加载 CIFAR-10 数据集的训练部分。
transform=torchvision.transforms.ToTensor():指定预处理操作,将 PIL 图像或NumPy ndarray转换为 FloatTensor,并缩放像素值到[0.0, 1.0]范围。
download=True:如果数据集已经存在于指定的路径下,则不会重新下载;如果不存在,则会从网上下载数据集。

import torchvision  # 导入torchvision库,它包含了许多用于计算机视觉的预构建数据集和数据转换工具

# 加载CIFAR-10训练数据集
train_ds = torchvision.datasets.CIFAR10(
    'data',  # 指定数据集的存储路径
    train=True,  # 指定加载训练集,如果设置为False,则加载测试集
    transform=torchvision.transforms.ToTensor(),  # 指定预处理操作,将图像转换为Tensor
    download=True  # 如果数据集不存在,则下载数据集
)

# 加载CIFAR-10测试数据集
test_ds = torchvision.datasets.CIFAR10(
    'data',  # 指定数据集的存储路径
    train=False,  # 指定加载测试集
    transform=torchvision.transforms.ToTensor(),  # 指定预处理操作,将图像转换为Tensor
    download=True  # 如果数据集不存在,则下载数据集
)

输出:

参数解释:
train_dstest_ds:分别是训练集和测试集的数据集对象。
batch_size:每个批次包含的样本数量。
shuffle:在训练数据加载器中设置为True,表示在每个epoch开始时将数据随机打乱,这有助于模型学习时的泛化能力。
shuffle 在测试数据加载器中通常设置为 False,因为测试集通常不需要打乱。
DataLoader的作用:
DataLoaderPyTorch 中用于封装数据集并提供批量加载功能的工具。它允许用户指定批次大小、是否打乱数据、多线程加载等。
使用 DataLoader 可以简化数据的批量处理,使得数据能够以批次的形式被神经网络处理,这是训练深度学习模型的标准做法。

batch_size = 32  # 设置每个批次的样本数量为32

# 创建训练数据的DataLoader
train_dl = torch.utils.data.DataLoader(
    train_ds,  # 传入训练数据集
    batch_size=batch_size,  # 设置每个批次的大小为32
    shuffle=True  # 设置为True以在每个epoch开始时随机打乱数据
)

# 创建测试数据的DataLoader
test_dl = torch.utils.data.DataLoader(
    test_ds,  # 传入测试数据集
    batch_size=batch_size  # 设置每个批次的大小为32
)
# 取一个批次查看数据格式
# 数据的shape为:[batch_size, channel, height, width]
# 其中batch_size为自己设定,channel,height和width分别是图片的通道数,高度和宽度。
imgs, labels = next(iter(train_dl))  # 从训练数据加载器中获取一个批次的图像和标签
imgs.shape  # 查看图像数据的形状

输出:
在这里插入图片描述

1.3 数据可视化

transpose((1, 2, 0))详解:

  • 作用是对NumPy数组进行轴变换,transpose函数的参数是一个元组,定义了新轴的顺序。原始PyTorch张量通常是以(C, H, W)的格式存储的,其中:
    • C是通道数(例如,RGB图像有3个通道)。
    • H是图像的高度。
    • W是图像的宽度。
  • transpose((1, 2, 0))将轴的顺序从(C, H, W)转换为(H, W, C),这使得数据格式更适合可视化和处理。
import numpy as np  # 导入NumPy库,用于数值计算

# 指定图片大小,图像大小为20宽、5高的绘图(单位为英寸)
plt.figure(figsize=(20, 5))  # 创建一个大小为20x5英寸的图形窗口

# 遍历前20张图像
for i, imgs in enumerate(imgs[:20]):
    # 将图像数据从PyTorch Tensor转换为NumPy数组,并进行轴变换
    # 因为图像数据的原始格式是[batch_size, channel, height, width],我们需要将其转换为[height, width, channel]
    npimg = imgs.numpy().transpose((1, 2, 0))

    # 将整个figure分成2行10列,绘制第i+1个子图
    plt.subplot(2, 10, i+1)  # 创建子图,2行10列的布局,当前是第i+1个位置

    # 显示图像,cmap=plt.cm.binary表示使用二值颜色映射
    plt.imshow(npimg, cmap=plt.cm.binary)

    # 关闭坐标轴
    plt.axis('off')

# 显示绘制的图像
plt.show()

输出:
在这里插入图片描述


2. 构建简单的CNN网络

卷积神经网络(CNN)是一种深度学习模型,广泛应用于图像识别、分类和分割等任务。下面是一些主要组件的详细说明:

  1. torch.nn.Conv2d()详解
    torch.nn.Conv2d是 PyTorch 中定义二维卷积层的类。以下是它的参数的详细解释:
torch.nn.Conv2d(
   in_channels: int, 输入数据的通道数。例如,对于RGB图像,in_channels 通常是3。
   out_channels: int, 输出数据的通道数。这通常决定了卷积层输出的特征图的数量。
   kernel_size: int or tuple, 卷积核的大小。可以是单个整数(表示卷积核的高度和宽度相同),也可以是元组 (H, W) 指定高度和宽度。
   stride: int or tuple = 1, 卷积的步长。可以是单个整数(表示高度和宽度的步长相同),也可以是元组 (H, W) 指定不同的步长。
   padding: int or tuple or str = 0, 填充的大小。可以是单个整数(表示在高度和宽度方向上添加相同数量的填充),可以是元组 (H, W) 指定不同的填充,也可以是字符串(如 'same' 或 'valid'),这取决于 padding_mode 参数。
   dilation: int or tuple = 1, 卷积的空洞率。可以是单个整数(表示高度和宽度的空洞率相同),也可以是元组 (H, W) 指定不同的空洞率。
   groups: int = 1, 分组卷积的数量。分组卷积可以减少模型的参数数量和计算量。
   bias: bool = True, 布尔值,指示卷积层是否包含偏置项。
   padding_mode: str = 'zeros', 填充模式,可以是 'zeros'、'reflect'、'replicate' 或 'circular'。
   device: torch.device = None, 指定设备对象,卷积层的参数将被初始化在该设备上。
   dtype: torch.dtype = None 指定数据类型对象,卷积层的参数将被初始化为该数据类型。
   )
  • 作用:用于提取图像的特征。通过学习图像中局部区域的特征,卷积层可以捕捉到图像的局部特征。
  1. torch.nn.Linear()详解
    torch.nn.Linear 是 PyTorch 中定义全连接层(也称为线性层或稠密层)的类。以下是它的参数的详细解释:
torch.nn.Linear(
    in_features: int, 输入数据的特征数量。这是全连接层输入的每个样本的特征数。
    out_features: int, 输出数据的特征数量。这是全连接层输出的每个样本的特征数,也称为神经元的数量。
    bias: bool = True, 布尔值,指示全连接层是否包含偏置项。如果设置为 True,则层中将包含偏置项;如果设置为 False,则不包含。
    device: torch.device = None, 指定设备对象,全连接层的参数将被初始化在该设备上。如果为 None,则参数将被初始化在 CPU 上。
    dtype: torch.dtype = None 指定数据类型对象,全连接层的参数将被初始化为该数据类型。如果为 None,则参数将被初始化为默认的数据类型(通常是 torch.float32)。
)
  • 作用:在CNN中,全连接层通常用于最终的分类或回归任务。在特征提取的最后阶段,全连接层将学习到的高级特征映射到最终的输出类别。
  1. torch.nn.MaxPool2d()详解
    torch.nn.MaxPool2d 是 PyTorch 中定义二维最大池化层的类。最大池化层用于对输入特征图进行下采样,通过滑动窗口中的最大值来降低特征图的空间维度,同时保留最重要的信息。以下是它的参数的详细解释:
torch.nn.MaxPool2d(
    kernel_size: int or tuple, 池化窗口的大小。可以是单个整数(表示高度和宽度的窗口大小相同),也可以是元组 (H, W) 指定高度和宽度的窗口大小。
    stride: int or tuple = None, 池化的步长。可以是单个整数(表示高度和宽度的步长相同),也可以是元组 (H, W) 指定不同的步长。如果设置为 None,则默认等于 kernel_size。
    padding: int or tuple = 0, 填充的大小。可以是单个整数(表示在高度和宽度方向上添加相同数量的填充),也可以是元组 (H, W) 指定不同的填充。
    dilation: int = 1, 池化窗口的空洞率。可以是单个整数(表示高度和宽度的空洞率相同),也可以是元组 (H, W) 指定不同的空洞率。空洞池化可以增加池化窗口的有效感受野而不增加计算量。
    return_indices: bool = False, 布尔值,指示是否返回最大值的索引。如果设置为 True,则返回每个输出元素的最大值的索引。
    ceil_mode: bool = False 布尔值,指示是否使用天花板函数来计算输出大小。如果设置为 True,则输出大小将向上取整。
)
  • 作用:最大池化层是卷积神经网络中常用的结构,它有助于减少特征图的维度,从而减少模型的参数数量和计算量,同时能够保持特征的重要信息。
    [图片]
import torch.nn.functional as F  # 导入PyTorch的函数库,提供一些常用的函数,如ReLU和池化操作
import torch.nn as nn  # 导入PyTorch的神经网络模块

num_classes = 10  # 定义图片的类别数,例如在CIFAR-10数据集中有10个类别

class Model(nn.Module):  # 定义一个名为Model的类,继承自nn.Module
    def __init__(self):  # 类的初始化函数
        super().__init__()  # 调用父类的初始化函数
        # 特征提取网络
        self.conv1 = nn.Conv2d(3, 64, kernel_size=3)   # 第一层卷积,输入通道数为3,输出通道数为64,卷积核大小为3x3
        self.pool1 = nn.MaxPool2d(kernel_size=2)       # 第一层池化,池化核大小为2x2
        self.conv2 = nn.Conv2d(64, 64, kernel_size=3)  # 第二层卷积,输入通道数为64,输出通道数为64,卷积核大小为3x3   
        self.pool2 = nn.MaxPool2d(kernel_size=2)       # 第二层池化,池化核大小为2x2
        self.conv3 = nn.Conv2d(64, 128, kernel_size=3) # 第三层卷积,输入通道数为64,输出通道数为128,卷积核大小为3x3   
        self.pool3 = nn.MaxPool2d(kernel_size=2)       # 第三层池化,池化核大小为2x2
        
        # 分类网络
        self.fc1 = nn.Linear(512, 256)          # 第一层全连接层,输入特征数为512,输出特征数为256
        self.fc2 = nn.Linear(256, num_classes)  # 第二层全连接层,输入特征数为256,输出特征数为类别数

    # 前向传播
    def forward(self, x):  # 定义前向传播函数
        x = self.pool1(F.relu(self.conv1(x)))     # 通过第一层卷积层,然后应用ReLU激活函数,最后通过第一层池化层
        x = self.pool2(F.relu(self.conv2(x)))     # 通过第二层卷积层,然后应用ReLU激活函数,最后通过第二层池化层
        x = self.pool3(F.relu(self.conv3(x)))     # 通过第三层卷积层,然后应用ReLU激活函数,最后通过第三层池化层
        
        x = torch.flatten(x, start_dim=1)  # 将特征图展平为一维向量,以便输入到全连接层

        x = F.relu(self.fc1(x))  # 通过第一层全连接层,并应用ReLU激活函数
        x = self.fc2(x)  # 通过第二层全连接层,得到最终的分类结果
        
        return x  # 返回网络的输出

这个模型首先通过三个卷积层和池化层来提取图像的特征,然后将特征图展平,并通过两层全连接层进行分类。F.relu用于应用ReLU激活函数,torch.flatten用于将多维特征图展平为一维向量。
请注意,self.fc1的输入特征数设置为512,这是基于输入特征图大小。特征数需要根据卷积层和池化层之后的输出特征图大小来确定。如果输入图像的尺寸或网络结构发生变化,这个数值可能需要相应地调整。
打印并加载模型:

from torchinfo import summary  # 导入summary函数,用于打印模型的详细信息

# 创建模型实例并将其转移到GPU或CPU
model = Model().to(device)

# 使用summary函数打印模型的详细信息
summary(model)

输出:
在这里插入图片描述


3. 模型训练

3.1 设置超参数

loss_fn    = nn.CrossEntropyLoss() # 创建损失函数
learn_rate = 1e-2 # 学习率
opt        = torch.optim.SGD(model.parameters(),lr=learn_rate)
  • nn.CrossEntropyLoss() 是PyTorch中的交叉熵损失函数,它通常用于多分类问题。这个损失函数结合了nn.LogSoftmax()nn.NLLLoss(),即它首先应用对数softmax函数,然后计算负对数似然损失(Negative Log Likelihood Loss)。
  • 学习率是优化算法中的一个重要参数,它决定了模型在训练过程中参数更新的幅度。如果学习率设置得过大,可能会导致训练过程中的梯度爆炸,从而无法收敛;如果学习率设置得过小,可能会导致训练过程缓慢,甚至陷入局部最优。
  • torch.optim.SGD 是PyTorch中的随机梯度下降(Stochastic Gradient Descent)优化器。
  • model.parameters() 是一个生成器,它返回模型中所有可训练的参数。
  • lr=learn_rate 设置优化器的学习率为之前定义的learn_rate

3.2 训练函数

  1. optimizer.zero_grad()
  • 这个函数用于清零(重置)模型所有参数的梯度。在PyTorch中,梯度是累加的,因此每次进行参数更新前,都需要将梯度清零,以避免将上次迭代的梯度累积到本次迭代中。
  • 这一步通常在每次迭代的开始进行,以确保每次计算的梯度都是当前批次数据的梯度,而不是累积的梯度。
  1. loss.backward()
  • 这个函数用于进行反向传播。在PyTorch中,当您对一个Tensor调用.backward()方法时,会自动计算这个Tensor的梯度,并且沿着计算图反向传播,直到所有的叶子节点(即模型的参数)。
  • loss.backward()会将损失函数关于模型参数的梯度计算出来,并将这些梯度保存到对应参数的.grad属性中。
  • 需要注意的是,只有设置了requires_grad=True的Tensor才会在反向传播中计算梯度。
  1. optimizer.step()
  • 这个函数用于根据计算出的梯度更新模型的参数。在调用optimizer.step()之前,需要确保已经调用了loss.backward()来计算梯度。
  • optimizer.step()会根据优化器的算法(例如SGD、Adam等)和学习率来更新参数的值,实现模型的学习。
    optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。
# 训练循环
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 获取数据加载器中的样本总数,例如MNIST数据集有60000张图片
    num_batches = len(dataloader)   # 计算数据加载器中的批次数量,例如60000张图片,每批次32张,共有1875个批次

    train_loss, train_acc = 0, 0  # 初始化累计训练损失和准确率

    # 遍历数据加载器中的所有批次
    for X, y in dataloader:
        X, y = X.to(device), y.to(device)  # 将特征和标签转移到GPU或CPU

        # 前向传播
        pred = model(X)  # 使用模型进行预测
        loss = loss_fn(pred, y)  # 计算预测和真实标签之间的损失

        # 反向传播和优化
        optimizer.zero_grad()  # 清零模型参数的梯度
        loss.backward()        # 反向传播,计算梯度
        optimizer.step()       # 根据梯度更新模型参数

        # 记录准确率和损失
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()  # 计算预测正确的样本数量
        train_loss += loss.item()  # 累加损失值

    # 计算平均准确率和平均损失
    train_acc /= size  # 将累计准确率除以样本总数,得到平均准确率
    train_loss /= num_batches  # 将累计损失除以批次数量,得到平均损失

    return train_acc, train_loss  # 返回训练过程中的平均准确率和平均损失

3.3 测试函数

  • 这个 test函数接收三个参数:dataloadermodelloss_fn
  • 它遍历 dataloader 中的所有批次,对每个批次执行前向传播和损失计算,但不进行反向传播或参数更新,因为测试阶段的目的是评估模型性能,而不是训练模型。
  • 在每个批次中,它计算模型的预测值 target_pred,然后使用损失函数 loss_fn 计算预测值和真实标签 target 之间的损失 loss
  • 函数还记录了整个测试集上的平均准确率和平均损失。
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 获取测试数据集中的样本总数,例如CIFAR-10数据集有10000张图片
    num_batches = len(dataloader)  # 计算测试数据加载器中的批次数量

    test_loss, test_acc = 0, 0  # 初始化累计测试损失和准确率

    # 使用torch.no_grad()上下文管理器,停止梯度计算,因为在测试阶段不需要更新模型参数
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)  # 将图像和标签转移到GPU或CPU

            # 前向传播,获取模型的预测输出
            target_pred = model(imgs)
            # 计算模型预测输出和真实标签之间的损失
            loss = loss_fn(target_pred, target)

            # 累加损失和准确率
            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()

    # 计算平均准确率和平均损失
    test_acc /= size  # 将累计准确率除以样本总数,得到平均准确率
    test_loss /= num_batches  # 将累计损失除以批次数量,得到平均损失

    return test_acc, test_loss  # 返回测试过程中的平均准确率和平均损失

3.4 训练

  1. model.train()
    model.train()的作用是启用 Batch NormalizationDropout
    model.train()PyTorch 中用于将模型设置为训练模式,这主要影响模型中特定层的行为,如 Batch Normalization(批量归一化)和 Dropout。以下是这些层在训练模式下的行为解释:
  • Batch Normalization(批量归一化):
    在训练模式下,Batch Normalization 层会计算并存储每个批次数据的均值和方差,然后使用这些统计数据来归一化该批次的数据。这样做的目的是为了减少内部协变量偏移(Internal Covariate Shift),即确保网络的每一层输入数据分布保持相对稳定,从而加快训练速度并提高模型性能。
  • Dropout
    Dropout 是一种正则化技术,用于防止模型过拟合。在训练模式下,Dropout 层会随机地将一部分网络连接“丢弃”(即设置为零),这样每次训练时只有一部分网络被更新。这种方法可以减少神经元之间复杂的共适应关系,增强模型的泛化能力。

当调用 model.train() 时,模型中的所有层都会知道模型处于训练模式。对于 Batch Normalization 层,这意味着它们会计算并更新均值和方差的估计值;对于 Dropout 层,这意味着它们会随机丢弃一些神经元。
在测试或验证模型时,通常使用 model.eval() 来将模型设置为评估模式。在评估模式下,Batch Normalization 层会使用训练期间计算的均值和方差来归一化数据,而 Dropout 层则不会丢弃任何神经元,即它们会保持所有连接的激活状态。
2. model.eval()

model.eval()的作用是不启用 Batch NormalizationDropout

  • Batch Normalization(批量归一化):
    在评估模式下,Batch Normalization 层会使用训练期间计算并存储的全局均值和方差来归一化数据,而不是使用当前批次的统计数据。这样做是为了确保模型在训练和评估时表现的一致性,因为训练时使用的是每个批次的动态统计数据,而评估时则使用整个训练集的静态统计数据。
  • Dropout
    在评估模式下,Dropout 层不会随机丢弃任何神经元,即不会应用任何丢弃概率。这意味着在评估或测试时,所有的神经元都会参与前向传播,确保模型使用其全部能力来处理输入数据。

使用 model.eval() 是为了确保在模型评估或测试时,Batch NormalizationDropout 层的行为与训练时不同,以反映模型在实际部署时的预期行为。这对于获得准确的性能指标至关重要。

epochs = 10  # 设置训练周期的数量为10

# 初始化用于存储每个epoch训练和测试损失与准确率的列表
train_loss = []
train_acc = []
test_loss = []
test_acc = []

# 开始训练循环
for epoch in range(epochs):
    model.train()  # 将模型设置为训练模式
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)  # 训练模型并获取当前epoch的训练准确率和损失
    
    model.eval()  # 将模型设置为评估模式
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)  # 测试模型并获取当前epoch的测试准确率和损失
    
    # 将当前epoch的训练和测试结果添加到对应的列表中
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
    
    # 定义输出格式模板
    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}')
    # 打印当前epoch的进度信息
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))
print('Done')  # 训练完成后打印'Done'

输出:
在这里插入图片描述


4. 结果可视化

import matplotlib.pyplot as plt  # 导入matplotlib的pyplot模块,用于数据可视化
import warnings  # 导入警告模块
warnings.filterwarnings("ignore")  # 忽略警告信息,避免绘图时出现警告提示

# 设置matplotlib的配置参数
# plt.rcParams['font.sans-serif'] = ['SimHei']  # 设置中文字体,使得图表可以正常显示中文
plt.rcParams['axes.unicode_minus'] = False  # 设置正常显示负号
plt.rcParams['figure.dpi'] = 100  # 设置图像的分辨率

epochs_range = range(epochs)  # 创建一个从0到epochs-1的范围,用于x轴的刻度

# 设置图像大小
plt.figure(figsize=(12, 3))
# 创建一个1行2列的子图布局,并定位到第1个子图
plt.subplot(1, 2, 1)

# 在第1个子图上绘制训练和测试的准确率曲线
plt.plot(epochs_range, train_acc, label='Training Accuracy')  # 绘制训练准确率
plt.plot(epochs_range, test_acc, label='Test Accuracy')  # 绘制测试准确率
plt.legend(loc='lower right')  # 添加图例,位置在右下角
plt.title('Training and Validation Accuracy')  # 设置子图的标题

# 创建一个1行2列的子图布局,并定位到第2个子图
plt.subplot(1, 2, 2)
# 在第2个子图上绘制训练和测试的损失曲线
plt.plot(epochs_range, train_loss, label='Training Loss')  # 绘制训练损失
plt.plot(epochs_range, test_loss, label='Test Loss')  # 绘制测试损失
plt.legend(loc='upper right')  # 添加图例,位置在右上角
plt.title('Training and Validation Loss')  # 设置子图的标题

# 显示绘制的图像
plt.show()

输出:
[图片]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值