实战2. 利用Pytorch解决 CIFAR 数据集中的图像分类为 10 类的问题——提高精度
实战2. 利用Pytorch解决 CIFAR 数据集中的图像分类为 10 类的问题——提高精度
- 前期准备
- 加载数据
- 建立模型
- 模型训练
- 质量指标
让我们回到图像分类问题 CIFAR。
你的主要任务:实现整个模型训练流程,并在测试样本上获得良好的准确度指标值。 任务积分:
- 0,如果测试样本的准确度<0.5;
- 如果测试样本的准确度 >0.5 且 <0.6,则为 0.5;
- 1,如果测试样本的准确度>0.6;
本任务中用于训练模型的代码已完整实现。您需要做的就是为神经网络类编写代码并试验参数以获得良好的质量。除此之外,你要保证模型不超过300M的内存使用!
前期准备
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
import torchvision
from torchvision import datasets, transforms
from matplotlib import pyplot as plt
from IPython.display import clear_output
import numpy as np
np.random.seed(42)
class LinearRegression:
def init(self, **kwargs):
self.coef_ = None
pass
def fit(self, x: np.array, y: np.array):
# 添加一列全为1的偏置项
x = np.concatenate([np.ones((x.shape[0], 1)), x], axis=1)
# 使用最小二乘法计算系数
self.coef_ = np.linalg.inv(x.T @ x) @ x.T @ y
return self
def predict(self, x: np.array):
# 添加一列全为1的偏置项
x = np.concatenate([np.ones((x.shape[0], 1)), x], axis=1)
# 根据计算出的系数进行预测
y_pred = x @ self.coef_
return y_pred
加载数据
数据加载代码与我们之前课程中的相同。没有必要改变任何东西。
# 从 torchvision 加载数据集
train_data = datasets.CIFAR10(root="./cifar10_data", train=True, download=True, transform=transforms.ToTensor())
test_data = datasets.CIFAR10(root="./cifar10_data", train=False, download=True, transform=transforms.ToTensor())
# 将训练部分分为train和val
# 我们将把 80% 的图片纳入训练样本
train_size = int(len(train_data) * 0.8)
# 进行验证 - 剩余 20%
val_size = len(train_data) - train_size
train_data, val_data = torch.utils.data.random_split(train_data, [train_size, val_size])
# 我们启动将生成批次的数据加载器
train_loader = torch.utils.data.DataLoader(train_data, batch_size=64, shuffle=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=64, shuffle=False)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=64, shuffle=False)
输出:
100%|██████████| 170M/170M [00:04<00:00, 40.6MB/s]
让我们看一下数据集中的一些图像:
# 绘制图像的函数
def show_images(images, labels):
f, axes= plt.subplots(1, 10, figsize=(30,5))
for i, axis in enumerate(axes):
# 将图像从张量转换为numpy
img = images[i].numpy()
# 将图像转换为尺寸(长度、宽度、颜色通道)
img = np.transpose(img, (1, 2, 0))
axes[i].imshow(img)
axes[i].set_title(labels[i].numpy())
plt.show()
# 获取一批图像
for batch in train_loader:
images, labels = batch
break
show_images(images, labels)
建立模型
下面是用于构建模型的单元格。您不应该立即制作具有大量层的大型复杂模型:这样的网络将需要很长时间来训练,并且很可能会过度训练。
您的主要任务是训练模型并在延迟(测试样本)上获得至少 60% 准确度的质量。
注意:你的模型必须由“模型”变量表示。
您可以尝试以下方法来改善网络结果:
- 尝试不同数量的卷积层和全连接层;
- 在卷积层中尝试不同数量的过滤器;
- 在隐藏的全连接层中尝试不同数量的神经元;
- 尝试在完全连接层和卷积层之后添加 BatchNorm。请注意,nn.BatchNorm2d 用于卷积层。 num_features 参数等于卷积层的过滤器(out_channels)的数量;
- 尝试添加/删除 max_pooling;
- 改变学习率;
- 对网络进行更多次的训练。
如果您的模型过度拟合(验证指标开始变得更糟),请尝试减少模型参数的数量。如果模型没有过拟合,但是效果不佳,请尝试增加模型参数的数量。
模板为:
# 此处输入您的代码
# 声明一个卷积神经网络类
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
# 此处输入您的代码
# 定义网络的层
...
def forward(self, x):
# 维度 x ~ [64, 3, 32, 32]
# 此处输入您的代码
# 实现前向传递网络
...
我们按照要求进行解决
class ConvNet(nn.Module):
def __init__(self):
super().__init__()
# 第一个卷积层,输入通道 3(RGB 图像),输出通道 16,卷积核大小 3
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
# 第一个 BatchNorm 层,对应卷积层的输出通道数 16
self.bn1 = nn.BatchNorm2d(16)
# 最大池化层,池化核大小 2
self.pool1 = nn.MaxPool2d(2)
# 第二个卷积层,输入通道 16,输出通道 32,卷积核大小 3
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
# 第二个 BatchNorm 层,对应卷积层的输出通道数 32
self.bn2 = nn.BatchNorm2d(32)
# 最大池化层,池化核大小 2
self.pool2 = nn.MaxPool2d(2)
# 展平层
self.flatten = nn.Flatten()
# 第一个全连接层,输入维度根据前面卷积和池化层输出计算得到(8 * 8 * 32),输出维度 64
self.fc1 = nn.Linear(8 * 8 * 32, 64)
# 第一个全连接层的 BatchNorm 层
self.bn_fc1 = nn.BatchNorm1d(64)
# 第二个全连接层,输入维度 64,输出维度 10(CIFAR - 10 有 10 个类别)
self.fc2 = nn.Linear(64, 10)
def forward(self, x):
# 第一个卷积层 -> BatchNorm -> ReLU 激活 -> 最大池化
x = F.relu(self.bn1(self.conv1(x)))
x = self.pool1(x)
# 第二个卷积层 -> BatchNorm -> ReLU 激活 -> 最大池化
x = F.relu(self.bn2(self.conv2(x)))
x = self.pool2(x)
# 展平
x = self.flatten(x)
# 第一个全连接层 -> BatchNorm -> ReLU 激活
x = F.relu(self.bn_fc1(self.fc1(x)))
# 第二个全连接层得到最终输出
x = self.fc2(x)
return x
model = ConvNet()
下面的单元检查 GPU 是否可用,如果有,则将神经网络传输到 GPU。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
模型训练
网络训练功能(无需更改)。
该函数每50次训练迭代输出一次训练样本上的损失和准确率的当前值。此外,每次迭代之后都会计算并显示验证样本的损失和准确度。这些值让你了解你的模型学习得如何。
def evaluate(model, dataloader, loss_fn):
losses = []
num_correct = 0
num_elements = 0
for i, batch in enumerate(dataloader):
# 这就是我们获取当前批次的方法
X_batch, y_batch = batch
num_elements += len(y_batch)
with torch.no_grad():
logits = model(X_batch.to(device))
loss = loss_fn(logits, y_batch.to(device))
losses.append(loss.item())
y_pred = torch.argmax(logits, dim=1)
num_correct += torch.sum(y_pred.cpu() == y_batch)
accuracy = num_correct / num_elements
return accuracy.numpy(), np.mean(losses)
def train(model, loss_fn, optimizer, n_epoch=3):
# 网络训练周期
for epoch in range(n_epoch):
print("Epoch:", epoch+1)
model.train(True)
running_losses = []
running_accuracies = []
for i, batch in enumerate(train_loader):
# 这就是我们获取当前批次的方法
X_batch, y_batch = batch
# 前向传递(获取对一批图像的响应)
logits = model(X_batch.to(device))
# 计算网络给出的答案和批次的正确答案的损失
loss = loss_fn(logits, y_batch.to(device))
running_losses.append(loss.item())
loss.backward() # backpropagation (梯度计算)
optimizer.step() # 更新网络权重
optimizer.zero_grad() # 重置权重
# 计算当前训练批次的准确率
model_answers = torch.argmax(logits, dim=1)
train_accuracy = torch.sum(y_batch == model_answers.cpu()) / len(y_batch)
running_accuracies.append(train_accuracy)
# 记录结果
if (i+1) % 100 == 0:
print("Average train loss and accuracy over the last 50 iterations:",
np.mean(running_losses), np.mean(running_accuracies), end='\n')
# 每个时期之后,我们都会得到验证样本的质量指标
model.train(False)
val_accuracy, val_loss = evaluate(model, val_loader, loss_fn=loss_fn)
print("Epoch {}/{}: val loss and accuracy:".format(epoch+1, n_epoch,),
val_loss, val_accuracy, end='\n')
return model
我们正在开始训练
# 再次声明模型
model = ConvNet()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
# 选择损失函数
loss_fn = torch.nn.CrossEntropyLoss()
# 选择优化算法和学习率。
# 你可以尝试不同的 learning_rate 值
learning_rate = 1e-3
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
# 让我们开始训练模型
# 参数 n_epoch 可以变化
model = train(model, loss_fn, optimizer, n_epoch=3)
输出:
质量指标
获取测试样本的质量指标
test_accuracy, _ = evaluate(model, test_loader, loss_fn)
print('Accuracy on the test', test_accuracy)
输出:
Accuracy on the test 0.6913
检查是否满足所需的阈值:
if test_accuracy <= 0.5:
print("测试质量低于0.5,0分")
elif test_accuracy < 0.6:
print("测试质量在 0.5 至 0.6 之间,得 0.5 分")
elif test_accuracy >= 0.6:
print("测试质量高于 0.6,得 1 分")
输出:
测试质量高于 0.6,得 1 分
下面的单元格包含使用训练过的网络获取文件的代码,获取pth文件
model.eval()
x = torch.randn((1, 3, 32, 32))
torch.jit.save(torch.jit.trace(model.cpu(), (x)), "model.pth")