赞
踩
在本节实验中,我们将基于 PyTorch 实现残差神经网络 ResNet,并在一个难度稍大的图片数据集(CIFAR-10)上进行训练和测试。
具体包括如下几个部分:
(1) 熟悉新数据集 CIFAR-10,并和 MNIST 对比分类难度;
(2) 学习残差神经网络,特别是 Block 的概念;
(3) 构建残差神经网络,并基于此实现 CIFAR-10 的训练与测试。
Ref: https://arxiv.org/pdf/1512.03385.pdf
https://zhuanlan.zhihu.com/p/106764370
CIFAR-10 数据集样例和 10 个类别如下所示:
官方说明及下载地址:http://www.cs.toronto.edu/~kriz/cifar.html
我们首先来准备数据集,方法与 MNIST 类似。CIFAR-10 数据集。
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
print(torch.manual_seed(1))
<torch._C.Generator object at 0x0000015FF0CD6B50>
batch_size = 250 # 设置训练集和测试集的 batch size,即每批次将参与运算的样本数
# 训练集
train_set = torchvision.datasets.CIFAR10('./dataset_cifar10', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.4914,0.4822,0.4465), (0.2023,0.1994,0.2010)
)
])
)
# 测试集
test_set = torchvision.datasets.CIFAR10('./dataset_cifar10', train=False, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.4914,0.4822,0.4465), (0.2023,0.1994,0.2010)
)
]))
train_loader = torch.utils.data.DataLoader(train_set, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=batch_size, shuffle=True)
下面我们用实验二中定义过的卷积神经网络在 CIFAR-10 数据集上训练并测试。请注意:由于 CIFAR-10 的图片格式与 MNIST 稍有不同,所以网络结构中 conv1 的输入通道数和 fc1 的输入向量长度都进行了调整。调整后的神经网络比原先拥有更多的参数,理论上有助于增加网络的学习能力。
原先的卷积神经网络在 MNIST 数据集上取得的测试准确率在 98.9% 左右。通过如下对比,我们可以看出 CIFAR-10 的分类难度相对于 MNIST 来说有了显著增加。
class CNN5(nn.Module):
def __init__(self):
super(CNN5, self).__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=6, kernel_size=5) # in_channels 由 1 改变为 3
self.conv2 = nn.Conv2d(in_channels=6, out_channels=12, kernel_size=5)
self.fc1 = nn.Linear(in_features=12*5*5, out_features=120) # in_features 由 12*4*4 改变为 12*5*5
self.fc2 = nn.Linear(in_features=120, out_features=60)
self.out = nn.Linear(in_features=60, out_features=10)
def forward(self, t):
# conv1
t = self.conv1(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)
# conv2
t = self.conv2(t)
t = F.relu(t)
t = F.max_pool2d(t, kernel_size=2, stride=2)
t = t.reshape(batch_size, 12*5*5) # dim1 由 12*4*4 改变为 12*5*5
# fc1
t = self.fc1(t)
t = F.relu(t)
# fc2
t = self.fc2(t)
t = F.relu(t)
# output layer
t = self.out(t)
return t
network = CNN5()
network.cuda()
loss_func = nn.CrossEntropyLoss() # 损失函数
optimizer = optim.SGD(network.parameters(), lr=0.1) # 优化器
def get_num_correct(preds, labels): # get the number of correct times
return preds.argmax(dim=1).eq(labels).sum().item()
开始训练
total_epochs = 10
for epoch in range(total_epochs):
total_loss = 0
total_train_correct = 0
for batch in train_loader:
images, labels = batch
images = images.cuda()
labels = labels.cuda()
preds = network(images)
loss = loss_func(preds, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_loss += loss.item()
total_train_correct += get_num_correct(preds, labels)
print("epoch:", epoch,
"correct times:", total_train_correct,
f"training accuracy:", "%.3f" %(total_train_correct/len(train_set)*100), "%",
"total_loss:", "%.3f" %total_loss)
epoch: 0 correct times: 12042 training accuracy: 24.084 % total_loss: 412.031
epoch: 1 correct times: 19445 training accuracy: 38.890 % total_loss: 337.925
epoch: 2 correct times: 22616 training accuracy: 45.232 % total_loss: 304.583
epoch: 3 correct times: 24415 training accuracy: 48.830 % total_loss: 286.545
epoch: 4 correct times: 25799 training accuracy: 51.598 % total_loss: 271.240
epoch: 5 correct times: 26835 training accuracy: 53.670 % total_loss: 261.250
epoch: 6 correct times: 27906 training accuracy: 55.812 % total_loss: 249.286
epoch: 7 correct times: 28499 training accuracy: 56.998 % total_loss: 242.985
epoch: 8 correct times: 29324 training accuracy: 58.648 % total_loss: 235.174
epoch: 9 correct times: 29982 training accuracy: 59.964 % total_loss: 227.551
测试结果(测试准确率约 56% 左右)
total_test_correct = 0
total_loss = 0
for batch in test_loader:
images, labels = batch
images = images.cuda()
labels = labels.cuda()
preds = network(images)
loss = loss_func(preds, labels)
total_loss += loss
total_test_correct += get_num_correct(preds, labels)
print("correct times:", total_test_correct,
f"test accuracy:", "%.3f" %(total_test_correct/len(test_set)*100), "%",
"total_loss:", "%.3f" %total_loss)
correct times: 5671 test accuracy: 56.710 % total_loss: 49.122
从以上结果可以看出,CIFAR-10 数据集的分类难度远高于 MNIST 数据集。理论上,增加其准确率的一个有效方法即增加神经网络的深度(层数),例如从上面的 6 层神经网络增加至 20 层左右。网络的深度越深,可抽取的特征层次就越丰富越抽象。
然而,事实证明有时网络层数并不是越深越好。如下图所示,是两个普通的深层卷积神经网络 (plain CNN) 在 CIFAR-10 上的训练和测试结果。两个神经网络的深度分别是 20 层和 56 层。
图片来源:Kaiming He et al. “Deep Residual Learning for Image Recognition”, 2015.
我们选择加深神经网络的层数是希望深层网络的表现能比浅层好,或者是希望它的表现至少和浅层网络持平,可实际的结果却不是这样的。从结果中可以看到,56 层的神经网络在训练集和测试集上的表现均明显差于 20 层的神经网络。这一现象被称为退化问题(degradation problem)。退化问题出现的原因是随着网络变深,网络优化变得更加困难。
深度残差网络 (Deep residual network, ResNet) 正是为了解决这个问题而提出的,它的提出是计算机视觉领域的一件里程碑式的事件。残差网络解决退化问题的关键即引入恒等映射 (identity mapping)。什么是恒等映射呢?我们来看一个简单的例子:
上图中,右边的神经网络可以理解为左边的浅层网络增加了三层框起来的部分。假如我们希望右边的深层网络与左边的浅层网络相比准确率可以持平,那么额外加上的三个神经层应当输入等于输出。我们假设这三层的输入为
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。