使用图像增广训练CIFAR10数据集
1. 设置训练和测试的图像增广方式
训练比测试多了一个RandomHorizontalFlip随机水平翻转,两者都需要调用ToTensor()变换。
图像在深度学习和数据处理任务中主要以以下三种格式表示:Tensor、PIL 和 NumPy。此外,还有其他存储格式,如 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)的函数,它通常用于多分类问题。reduction 是 nn.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)]