使用图像增广训练CIFAR10数据集

387 阅读6分钟

使用图像增广训练CIFAR10数据集

1. 设置训练和测试的图像增广方式

训练比测试多了一个RandomHorizontalFlip随机水平翻转,两者都需要调用ToTensor()变换。

图像在深度学习和数据处理任务中主要以以下三种格式表示:TensorPILNumPy。此外,还有其他存储格式,如 OpenCV 使用的格式、原始二进制格式等,但这三种是最常见的。

torchvision.transforms.ToTensor() 是一种常用的图像变换操作,主要用于将图像数据从 PIL 图像或 NumPy 数组的形式转换为 PyTorch 的张量(torch.Tensor)形式,以便后续可以被 PyTorch 的模型和函数直接处理。

train_augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomHorizontalFlip(),
    torchvision.transforms.ToTensor()])
​
test_augs = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()])

2.加载CIFAR10数据集

使用pytorch框架来加载CIFAR10数据集,主要是要设置好dataset数据集和dataloader数据集迭代器。

def load_cifar10(is_train, augs, batch_size):
    dataset = torchvision.datasets.CIFAR10(root='./DataCIFAR10',
                                           train=is_train, transform=augs, download=True)
    # shuffle:是否在每个 epoch 开始时打乱数据顺序
    # num_workers:用于数据加载的子进程数量 windows系统只能取0
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
                                             shuffle=is_train, num_workers=0)
    # 返回的dataloader即数据集迭代器iter
    return dataloader

3.小批量训练及损失函数计算损失

在 PyTorch 中,nn.CrossEntropyLoss 是用来计算交叉熵损失(Cross Entropy Loss)的函数,它通常用于多分类问题。reductionnn.CrossEntropyLoss 中的一个参数,用于控制如何计算损失的返回值。

reduction 参数定义了如何处理每个样本的损失计算结果。它有几个不同的取值选项:

  • 'none' :返回每个样本的损失值。也就是说,返回一个与输入批次大小(batch size)相同长度的张量,其中每个元素对应一个样本的损失。
  • 'mean' (默认值):返回所有样本的损失的平均值。这是最常用的选择,因为它将所有样本的损失缩减为一个标量。
  • 'sum' :返回所有样本的损失之和。适用于希望得到总损失值的场景。

本例中损失函数及相关参数选取如下:

loss = nn.CrossEntropyLoss(reduction='none')

那么,小批量训练函数则如下所示,其中l计算会得到一个与输入批次大小(batch size)相同长度的张量,其中每个元素对应一个样本的损失,所以需要进行.sum()求和

def train_batch_ch13(net, X, y, loss, trainer, devices):
    if isinstance(X, list):
        X = [x.to(devices[0]) for x in X]
    else:
        X = X.to(devices[0])
    y = y.to(devices[0])
    net.train()
    trainer.zero_grad()
    pred = net(X)
    # 根据loss的定义:loss = nn.CrossEntropyLoss(reduction='none')
    # 这里的l计算会得到一个与输入批次大小(batch size)相同长度的张量,其中每个元素对应一个样本的损失
    l = loss(pred, y)
    # 所以这里需要进行.sum()求和
    l.sum().backward()
    trainer.step()
    train_loss_sum = l.sum()
    train_acc_sum = d2l.accuracy(pred, y)
    return train_loss_sum, train_acc_sum

4.完整训练代码

import torch
import torchvision
from torch import nn
from d2l import torch as d2l
import matplotlib.pyplot as plt
​
# 设置 Matplotlib 后端
import matplotlib
​
matplotlib.use('TkAgg')
​
all_images = torchvision.datasets.CIFAR10(
    root='./DataCIFAR10', train=True, download=True)
​
# 提取前 32 张图片
# [i]表示第i个样本 样本是图片和标签的组合 因此后面跟着的[0]表示图片 如果是[1]则表示label标签
images = [all_images[i][0] for i in range(32)]
​
# 使用 d2l 的 show_images 方法显示图像
# d2l.show_images(images, num_rows=4, num_cols=8, scale=0.8)# 确保显示图像
# plt.show()train_augs = torchvision.transforms.Compose([
    torchvision.transforms.RandomHorizontalFlip(),
    torchvision.transforms.ToTensor()])
​
test_augs = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()])
​
​
def load_cifar10(is_train, augs, batch_size):
    dataset = torchvision.datasets.CIFAR10(root='./DataCIFAR10',
                                           train=is_train, transform=augs, download=True)
    # shuffle:是否在每个 epoch 开始时打乱数据顺序
    # num_workers:用于数据加载的子进程数量 windows系统只能取0
    dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size,
                                             shuffle=is_train, num_workers=0)
    # 返回的dataloader即数据集迭代器iter
    return dataloader
​
​
def train_batch_ch13(net, X, y, loss, trainer, devices):
    if isinstance(X, list):
        X = [x.to(devices[0]) for x in X]
    else:
        X = X.to(devices[0])
    y = y.to(devices[0])
    net.train()
    trainer.zero_grad()
    pred = net(X)
    # 根据后面loss的定义:loss = nn.CrossEntropyLoss(reduction='none')
    # 这里的l计算会得到一个与输入批次大小(batch size)相同长度的张量,其中每个元素对应一个样本的损失
    l = loss(pred, y)
    # 所以这里需要进行.sum()求和
    l.sum().backward()
    trainer.step()
    train_loss_sum = l.sum()
    train_acc_sum = d2l.accuracy(pred, y)
    return train_loss_sum, train_acc_sum
​
​
def train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
               devices=d2l.try_all_gpus()):
    timer, num_batchs = d2l.Timer(), len(train_iter)
    net = nn.DataParallel(net, device_ids=devices).to(devices[0])
    for epoch in range(num_epochs):
        metric = d2l.Accumulator(4)
        for i, (features, labels) in enumerate(train_iter):
            timer.start()
            l, acc = train_batch_ch13(
                net, features, labels, loss, trainer, devices)
            # labels.shape[0] 就是当前批次的大小,也即样本数
            # 如果 labels 是一个一维向量(比如分类任务中的标签) labels.numel() 就是批次中的样本数量
            metric.add(l, acc, labels.shape[0], labels.numel())
            timer.stop()
    test_acc = d2l.evaluate_accuracy_gpu(net, test_iter)
    print(f'loss {metric[0] / metric[2]:.3f}, train acc '
          f'{metric[1] / metric[3]:.3f}, test acc {test_acc:.3f}')
    print(f'{metric[2] * num_epochs / timer.sum():.1f} examples/sec on '
          f'{str(devices)}')
​
​
#   d2l.resnet18(10, 3) 第一个参数表示要分类的数目  第二个参数输入的通道数 这里是彩色图片所以输入通道数为3
batch_size, devices, net = 256, d2l.try_all_gpus(), d2l.resnet18(10, 3)
​
​
def init_weights(m):
    if type(m) in [nn.Linear, nn.Conv2d]:
        # 使用 Xavier 正态分布normal初始化权重
        # init.xavier_normal_(m.weight)
​
        # Xavier 均匀分布uniform来初始化 m.weight
        nn.init.xavier_uniform_(m.weight)
​
​
net.apply(init_weights)
​
​
def train_with_data_aug(train_augs, test_augs, net, lr=0.001):
    train_iter = load_cifar10(True, train_augs, batch_size)
    test_iter = load_cifar10(False, test_augs, batch_size)
    # 当你设置 reduction='none' 时,nn.CrossEntropyLoss
    # 不会对每个样本的损失进行平均或求和,而是返回每个样本的独立损失值
    # 返回一个与输入批次大小(batch size)相同长度的张量,其中每个元素对应一个样本的损失
    loss = nn.CrossEntropyLoss(reduction='none')
    trainer = torch.optim.Adam(net.parameters(), lr=lr)
    train_ch13(net, train_iter, test_iter, loss, trainer, 10, devices)
​
​
# 训练集使用了图像增广的训练
print("训练集使用了图像增广的训练")
train_with_data_aug(train_augs, test_augs, net)
​
# 训练集没有使用了图像增广的训练
print("训练集没有使用了图像增广的训练")
train_with_data_aug(test_augs, test_augs, net)

该代码在学校实验室服务器上跑出来结果如下所示:

运行结果:
训练集使用了图像增广的训练
Files already downloaded and verified
Files already downloaded and verified
loss 0.191, train acc 0.935, test acc 0.843
2862.6 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1), 
device(type='cuda', index=2), device(type='cuda', index=3), device(type='cuda', index=4), 
device(type='cuda', index=5), device(type='cuda', index=6), device(type='cuda', index=7)]
训练集没有使用了图像增广的训练
Files already downloaded and verified
Files already downloaded and verified
loss 0.039, train acc 0.987, test acc 0.848
3049.2 examples/sec on [device(type='cuda', index=0), device(type='cuda', index=1), 
device(type='cuda', index=2), device(type='cuda', index=3), device(type='cuda', index=4), 
device(type='cuda', index=5), device(type='cuda', index=6), device(type='cuda', index=7)]

5.参考连接

13.1. 图像增广 — 动手学深度学习 2.0.0 documentation