一、说明
二、多层 CNN
在上一个单元中,我们学习了可以从图像中提取模式的卷积滤波器。对于我们的 MNIST 分类器,我们使用了 5 个 5 × 9 个过滤器,产生了 24 × 24 × <> 张量。
我们可以使用相同的卷积思想来提取图像中更高级别的模式。例如,数字(如 8 和 9)的圆角边缘可以由许多较小的笔画组成。为了识别这些模式,我们可以在第一层的结果之上构建另一层卷积过滤器。
!wget https://raw.githubusercontent.com/MicrosoftDocs/pytorchfundamentals/main/computer-vision-pytorch/pytorchcv.py
import torch
import torch.nn as nn
import torchvision
import matplotlib.pyplot as plt
from torchinfo import summary
import numpy as np
from pytorchcv import load_mnist, train, plot_results, plot_convolution, display_dataset
load_mnist(batch_size=128)
三、池化层
第一个卷积层寻找原始模式,例如水平线或垂直线。它们之上的下一级卷积层会寻找更高级别的模式,例如原始形状。更多的卷积层可以将这些形状组合到图片的某些部分,直到我们试图分类的最终对象。这将创建提取模式的层次结构。
这样做时,我们还需要应用一个技巧:减小图像的空间大小。一旦我们检测到滑动窗口中存在水平斯托克,它发生在哪个确切像素就不那么重要了。因此,我们可以“缩小”图像的大小,这是使用池化层之一完成的:
- 平均池化采用滑动窗口(例如,2 × 2 像素)并计算窗口内值的平均值。
- “最大池化”将窗口替换为最大值。最大池化背后的想法是检测滑动窗口中是否存在某种模式。
在典型的CNN中,将由几个卷积层组成,它们之间有池化层以减小图像的尺寸。我们还会增加过滤器的数量,因为随着模式变得更加先进,我们需要寻找更多可能的有趣组合。由于空间维度减小和要素/过滤器维度增加,此体系结构也称为金字塔体系结构。
在下一个示例中,我们将使用两层 CNN:
class MultiLayerCNN(nn.Module):
def __init__(self):
super(MultiLayerCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 10, 5)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(10, 20, 5)
self.fc = nn.Linear(320,10)
def forward(self, x):
x = self.pool(nn.functional.relu(self.conv1(x)))
x = self.pool(nn.functional.relu(self.conv2(x)))
x = x.view(-1, 320)
x = nn.functional.log_softmax(self.fc(x),dim=1)
return x
net = MultiLayerCNN()
summary(net,input_size=(1,1,28,28))
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
├─Conv2d: 1-1 [1, 10, 24, 24] 260
├─MaxPool2d: 1-2 [1, 10, 12, 12] --
├─Conv2d: 1-3 [1, 20, 8, 8] 5,020
├─MaxPool2d: 1-4 [1, 20, 4, 4] --
├─Linear: 1-5 [1, 10] 3,210
==========================================================================================
Total params: 8,490
Trainable params: 8,490
Non-trainable params: 0
Total mult-adds (M): 0.47
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.06
Params size (MB): 0.03
Estimated Total Size (MB): 0.09
=======================================================================
请注意有关定义的一些事项:
- 我们没有使用 Flatten 层,而是使用 view 函数在正向函数内展平张量,这类似于 numpy 中的重塑函数。由于扁平化层没有可训练的权重,因此我们不需要在类中创建单独的层实例 — 我们只需使用 torch.nn.functional 命名空间中的函数即可。
- 我们在模型中只使用池化层的一个实例,也是因为它不包含任何可训练的参数,因此可以有效地重用一个实例。
- 可训练参数的数量(~8.5K)比以前的情况(感知器中为80K,单层CNN中为50K)要少得多。
发生这种情况是因为卷积层通常具有很少的参数,与输入图像大小无关。此外,由于池化,在应用最终密集层之前,图像的维度显着降低。少量参数对我们的模型有积极影响,因为它有助于防止过度拟合,即使在较小的数据集大小上也是如此。
hist = train(net,train_loader,test_loader,epochs=5)
Epoch 0, Train acc=0.949, Val acc=0.978, Train loss=0.001, Val loss=0.001
您可能应该观察到的是,我们能够实现更高的精度,而且速度更快 - 只需 1 或 2 个 epoch。这意味着复杂的网络架构需要更少的数据来弄清楚发生了什么,并从我们的图像中提取通用模式。
四、探索 CIFAR-10 数据集
让我们下载不同对象的真实图像数据集,称为
CIFAR-10。它包含60k 32×32彩色图像,分为10类。
transform = torchvision.transforms.Compose(
[torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=14, shuffle=True)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=14, shuffle=False)
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
display_dataset(trainset,classes=classes)
CIFAR-10的一个著名架构称为LeNet,由Yann LeCun提出。它遵循与我们上面概述的相同原则。但是,由于所有图像都是彩色的,因此输入张量大小为 3 × 32 × 32,并且 5 × 5 卷积滤波器也应用于整个颜色维度——这意味着卷积核矩阵的大小为
3 × 5 × 5。
我们还对这个模型做了一个简化——我们不使用 log_softmax 作为输出激活函数,只返回最后一个全连接层的输出。在这种情况下,我们可以只使用交叉熵损失函数来优化模型。
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(3, 6, 5)
self.pool = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(6, 16, 5)
self.conv3 = nn.Conv2d(16,120,5)
self.flat = nn.Flatten()
self.fc1 = nn.Linear(120,64)
self.fc2 = nn.Linear(64,10)
def forward(self, x):
x = self.pool(nn.functional.relu(self.conv1(x)))
x = self.pool(nn.functional.relu(self.conv2(x)))
x = nn.functional.relu(self.conv3(x))
x = self.flat(x)
x = nn.functional.relu(self.fc1(x))
x = self.fc2(x)
return x
net = LeNet()
summary(net,input_size=(1,3,32,32))
==========================================================================================
Layer (type:depth-idx) Output Shape Param #
==========================================================================================
├─Conv2d: 1-1 [1, 6, 28, 28] 456
├─MaxPool2d: 1-2 [1, 6, 14, 14] --
├─Conv2d: 1-3 [1, 16, 10, 10] 2,416
├─MaxPool2d: 1-4 [1, 16, 5, 5] --
├─Conv2d: 1-5 [1, 120, 1, 1] 48,120
├─Flatten: 1-6 [1, 120] --
├─Linear: 1-7 [1, 64] 7,744
├─Linear: 1-8 [1, 10] 650
==========================================================================================
Total params: 59,386
Trainable params: 59,386
Non-trainable params: 0
Total mult-adds (M): 0.65
==========================================================================================
Input size (MB): 0.01
Forward/backward pass size (MB): 0.05
Params size (MB): 0.24
Estimated Total Size (MB): 0.30
==========================================================================================
正确训练此网络将花费大量时间,最好在启用 GPU 的计算上完成。
为了获得更好的训练结果,我们可能需要尝试一些训练参数,例如学习率。因此,我们在这里显式定义了一个 S对量梯度下降 (SGD) 优化器并传递训练参数。您可以调整这些参数并观察它们如何影响训练。
opt = torch.optim.SGD(net.parameters(),lr=0.001,momentum=0.9)
hist = train(net, trainloader, testloader, epochs=3, optimizer=opt, loss_fn=nn.CrossEntropyLoss())
Epoch 0, Train acc=0.261, Val acc=0.388, Train loss=0.143, Val loss=0.121
Epoch 1, Train acc=0.437, Val acc=0.491, Train loss=0.110, Val loss=0.101
Epoch 2, Train acc=0.508, Val acc=0.522, Train loss=0.097, Val loss=0.094
我们通过 3 个时期的训练能够达到的准确性似乎不是很好。但是,请记住,盲猜只能给我们10%的准确率,而且我们的问题实际上比MNIST数字分类要困难得多。在如此短的训练时间内获得超过 50% 的准确率似乎是一个很好的成就。
五、小结
在本单元中,我们学习了计算机视觉神经网络背后的主要概念——卷积网络。支持图像分类、对象检测甚至图像生成网络的真实架构都基于 CNN,只是具有更多层和一些额外的训练技巧。