实战3. 利用ResNet-18预测电视剧《辛普森一家》中的人物
你将训练一个神经网络来预测电视剧《辛普森一家》中的人物。
您将获得一个训练和测试数据集。您将需要在训练数据上训练神经网络并在测试数据上获得预测。

加载数据
下载数据档案并解压该数据集。
python
! pip install wldhx.yadisk-direct
! curl -L $(yadisk-direct https://disk.yandex.com/d/Ggl9017wNIN0vg) -o simpsons.zip
注意! Yandex云盘对每天从链接同时下载的数量施加了一些限制。因此,上面单元格中的代码可能会产生错误。在这种情况下,您需要取消注释并从下面的单元格中运行代码(其中,数据是从备用链接下载的):
python
#! pip install wldhx.yadisk-direct
#! curl -L $(yadisk-direct https://disk.yandex.ru/d/MweozIqon2ybsQ) -o simpsons.zip
接下来,解压:
python
! unzip -qq simpsons.zip
导入必要的库:
python
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
from torchvision import datasets, models, transforms
让我们定义在将图像输入神经网络之前要执行的转换。您可以按照自己的意愿改变这些转换。
请注意,如果您要使用预先训练的神经网络(vgg、resnet 等),则转换应该与预先训练该神经网络时使用的转换相同。在 pytorch 文档中可以找到给定网络使用哪些转换以及如何在代码中使用它们。
如果您从头开始训练自己的神经网络,则可以自行选择转换。
python
resnet_transforms = transforms.Compose([
transforms.Resize(256), # 每幅图像的尺寸将缩小到 256*256
transforms.CenterCrop(224), # 图片的中心部分将被剪掉,尺寸为 224*224
transforms.ToTensor(), # 图像从python数组转换为torch.Tensor格式
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 图像像素值归一化
])
# 特定预训练网络的转换更容易像这样获得
# resnet_transforms = models.ResNet18_Weights.IMAGENET1K_V1.transforms()
让我们从数据中创建数据集。请注意,对于测试数据,您不知道答案:在 ./simpsons_data/test
文件夹中没有带有类名的子文件夹,只有一个包含所有图像的文件夹。您的任务是使用您知道答案的训练数据来训练神经网络,使用训练后的神经网络获得测试数据的预测,并将其作为答案提交。
python
train_data = datasets.ImageFolder('./simpsons_data/train', transform=resnet_transforms)
test_data = datasets.ImageFolder('./simpsons_data/test', transform=resnet_transforms)

我们来得到类别编号和类别名称的对应关系:
python
class_to_idx = train_data.class_to_idx
class_to_idx
输出:
{'abraham_grampa_simpson': 0,
'agnes_skinner': 1,
'apu_nahasapeemapetilon': 2,
'barney_gumble': 3,
'bart_simpson': 4,
'carl_carlson': 5,
'charles_montgomery_burns': 6,
'chief_wiggum': 7,
'cletus_spuckler': 8,
'comic_book_guy': 9,
'disco_stu': 10,
'edna_krabappel': 11,
'fat_tony': 12,
'gil': 13,
'groundskeeper_willie': 14,
'homer_simpson': 15,
'kent_brockman': 16,
'krusty_the_clown': 17,
'lenny_leonard': 18,
'lionel_hutz': 19,
'lisa_simpson': 20,
'maggie_simpson': 21,
'marge_simpson': 22,
'martin_prince': 23,
'mayor_quimby': 24,
'milhouse_van_houten': 25,
'miss_hoover': 26,
'moe_szyslak': 27,
'ned_flanders': 28,
'nelson_muntz': 29,
'otto_mann': 30,
'patty_bouvier': 31,
'principal_skinner': 32,
'professor_john_frink': 33,
'rainier_wolfcastle': 34,
'ralph_wiggum': 35,
'selma_bouvier': 36,
'sideshow_bob': 37,
'sideshow_mel': 38,
'snake_jailbird': 39,
'troy_mcclure': 40,
'waylon_smithers': 41}
我们将训练样本分为两部分:训练和验证。在训练部分,像往常一样,我们将训练神经网络,在验证部分我们将测试网络。
python
# 我们将把 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])
让我们为三部分数据创建三个数据加载器:
python
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)
让我们看一下训练集中的几张图片,以了解我们正在处理的内容。
python
for batch in test_loader:
# 一批图片和一批图片答案
images, labels = batch
break
python
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()
图像将在转换之后绘制,因此如果在转换过程中进行了规范化,则图像的颜色可能不太自然。不要害怕=)
python
show_images(images, labels)
输出:
模型建立与训练
您的任务是根据训练和验证数据训练神经网络,并获得测试数据的预测。要建立和训练神经网络,您可以而且应该使用以前的课程和实战的材料。
关于如何提高速度的想法:
- 尝试从头开始训练你的神经网络,就像我们在第四课和实战2中所做的那样。尝试改变网络架构和各种超参数(层数、层中的过滤器等)以获得最佳验证分数;
- 尝试采用预先训练的神经网络(例如 resnet-18),并在我们的数据上重新训练它,就像我们在第五课中所做的那样。注意转换:它们必须与您正在训练的架构相匹配。尝试冻结不同数量的层并观察它如何影响结果。
- 尝试将数据增强应用于训练样本。您可以在 Habr 上阅读有关数据增强的内容。 pytorch 中增强的示例可以在文档中找到。
请注意,增强仅适用于训练集。它不应应用于验证和测试样本。因此,您需要创建两个不同的转换管道:一个用于训练部分,一个用于验证和测试部分。
在网络训练过程中,要注意验证速度。验证指标的值是一个指导方针,可让您了解网络在测试样本上的行为方式。但请注意,测试和验证样本的结果可能有所不同。
检查 GPU 是否可用(如果不可用,请在笔记本电脑设置中启用 GPU):
python
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
建立神经网络:
python
# 你的代码:建立并训练一个神经网络
# resnet-18 模型
model = models.resnet18(pretrained=True)
# model.eval()
# model.eval可查看该神经网络结构
设置 evaluate 函数用于评估模型性能,train 函数用于训练模型并在每个训练周期结束后进行验证。
python
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) % 50 == 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
python
# 再次声明模型
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)
输出:
获取样本
使用训练好的模型对测试数据集进行预测,并将预测结果保存到一个 .npy 文件中。具体步骤包括:定义一个用于获取预测标签的函数,使用该函数对测试数据进行预测,将预测的索引转换为对应的类别名称,最后将结果保存为 submission_hw05.npy 文件。
python
def get_predictions(model, dataloader):
predicted_labels = []
model.eval()
predicted_labels = []
for i, batch in enumerate(dataloader):
# 这就是我们获取当前批次的方法
X_batch, _ = batch
with torch.no_grad():
logits = model(X_batch.to(device))
y_pred = torch.argmax(logits, dim=1)
predicted_labels.append(y_pred)
predicted_labels = torch.cat(predicted_labels)
return predicted_labels
# model --- 包含你的模型的变量。
predicted_labels = get_predictions(model, test_loader)
idx_to_class = {y:x for x, y in class_to_idx.items()}
predicted_labels = [idx_to_class[x] for x in predicted_labels.data.cpu().numpy()]
np.save('submission_hw05.npy', predicted_labels, allow_pickle=True)
print('The answer has been saved to file. `submission_hw05.npy`')