pytorch CV入门二:单层CNN和多层CNN

专栏链接:https://blog.csdn.net/qq_33345365/category_12578430.html

初次编辑:2024/2/15;最后编辑:2024/3/8

参考网站-微软教程:https://learn.microsoft.com/en-us/training/modules/intro-computer-vision-pytorch

更多的内容可以参考本作者其他专栏:

Pytorch基础:https://blog.csdn.net/qq_33345365/category_12591348.html

Pytorch NLP基础:https://blog.csdn.net/qq_33345365/category_12597850.html

CNN


上一部分描述了如何使用类定义来定义多层神经网络,但是这些网络是通用的,而不是专门用于计算机视觉任务的。在本部分,将描述卷积神经网络(CNN),它是专门为计算机视觉设计的。

计算机视觉不同于一般的分类,因为当我们试图在图片中找到某个物体时,我们是在扫描图像,寻找一些特定的**模式**(Pattern)及其组合。例如,在寻找一只猫时,我们首先可能会寻找水平线,这些水平线可以形成胡须,然后胡须的某些组合可以告诉我们这实际上是一只猫的照片。某些图案的相对位置和存在是重要的,而不是它们在图像上的确切位置。

为了提取模式,我们将使用卷积过滤器(convolutional filters)的概念。

准备工作与加载库

创建一个空的python文件,确保上一部分使用的辅助python文件pytorchcv.py在同一目录下。

加载库,我们使用修改builtins的方式来修改全局变量从而加载数据集(load_mnist)

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)

卷积过滤器

卷积滤波器类似一个窗口,运行在图像的每个像素上,并计算相邻像素的加权平均值。它们由权重系数矩阵定义。让我们看看在MNIST手写数字上应用两种不同卷积滤波器的例子:

plot_convolution(torch.tensor([[-1.,0.,1.],[-1.,0.,1.],[-1.,0.,1.]]),'Vertical edge filter')
plot_convolution(torch.tensor([[-1.,-1.,-1.],[0.,0.,0.],[1.,1.,1.]]),'Horizontal edge filter')

在这里插入图片描述
在这里插入图片描述

plot_convolution函数在pytorchcv.py中,具体含义后面叙述,第一个过滤器称为垂直边缘过滤器,由下式矩阵定义:

A = ( − 1 0 1 − 1 0 1 − 1 0 1 ) A = \begin{pmatrix} -1 & 0 & 1 \\ -1 & 0 & 1 \\ -1 & 0 & 1 \end{pmatrix} A= 111000111

当这个过滤器经过相对均匀的像素场时,所有值加起来等于0。但是,当它遇到图像中的垂直边缘时,会产生很高的尖峰值。这就是为什么在上面的图像中,可以看到垂直边缘由高值和低值表示,而水平边缘是平均的。当应用水平边缘过滤器时,会发生相反的事情-水平线被放大,垂直线被平均掉。

如果对大小为28×28的图像应用3×3过滤器-图像的大小将变为26×26,因为过滤器不会越过图像边界。然而,在某些情况下,可能希望保持图像的大小相同,在这种情况下,图像的每一边都填充0。

在经典的计算机视觉中,对图像应用多个过滤器来生成特征,然后由机器学习算法使用这些特征来构建分类器。然而,深度学习中通过构建学习最佳卷积过滤器的网络来解决分类问题。

为了做到这一点,我们引入了卷积层(convolutional layers)。

卷积层

卷积层是用nn.Conv2d构造来定义的。我们需要指定以下内容:

  • In_channels:输入通道数。在此处理的是灰度图像,因此输入通道数为1。彩色图像有3个通道(RGB)。
  • Out_channels:要使用的过滤器数量。此处为9种不同的过滤器,这将给网络提供大量的机会来探索哪种过滤器最适合。
  • Kernel_size:滑动窗口的大小。通常为3x3或5x5。过滤器尺寸的选择通常是通过实验来选择的,即通过尝试不同的过滤器尺寸并比较结果的准确性。

最简单的CNN将包含一个卷积层。给定输入大小为28x28,在应用了9个5x5过滤器之后,将最终得到一个9x24x24的张量。这里,每个过滤器的结果由图像中的不同通道表示(因此第一个维度9对应于过滤器的数量)。

卷积后,张量平铺成一个大小为5184(9x24x24)的向量,然后添加线性层,输出长度为10,对应10个数字。我们还在层与层之间使用relu激活函数。

class OneConv(nn.Module):
    def __init__(self):
        super(OneConv, self).__init__()
        self.conv = nn.Conv2d(in_channels=1,out_channels=9,kernel_size=(5,5))
        self.flatten = nn.Flatten()
        self.fc = nn.Linear(5184,10)

    def forward(self, x):
        x = nn.functional.relu(self.conv(x))
        x = self.flatten(x)
        x = nn.functional.log_softmax(self.fc(x),dim=1)
        return x

net = OneConv()

summary(net,input_size=(1,1,28,28))

输出:

==========================================================================================
Layer (type:depth-idx)                   Output Shape              Param #
==========================================================================================
OneConv                                  [1, 10]                   --
├─Conv2d: 1-1                            [1, 9, 24, 24]            234
├─Flatten: 1-2                           [1, 5184]                 --
├─Linear: 1-3                            [1, 10]                   51,850
==========================================================================================
Total params: 52,084
Trainable params: 52,084
Non-trainable params: 0
Total mult-adds (M): 0.19
==========================================================================================
Input size (MB): 0.00
Forward/backward pass size (MB): 0.04
Params size (MB): 0.21
Estimated Total Size (MB): 0.25
==========================================================================================

这个网络包含大约50k个可训练参数,而在全连接的多层网络中大约有80k个可训练参数。这使得我们即使在较小的数据集上也能获得很好的结果,因为卷积网络的泛化效果要好得多。

注意,卷积层的参数数量相当少,而且它不依赖于图像的分辨率。卷积层有 9 个 5x5 的滤波器,因此权重的数量为 9 * 5 * 5 = 225。加上 9 个bias,总的参数数为 225 + 9 = 234。每个过滤器有一个bias。简单来说,bias就像一个调节旋钮,可以控制滤波器输出的整体偏移量。它可以帮助模型更好地学习数据中的模式,即使这些模式稍微偏离了滤波器的权重所表示的模式。

训练

与上一单元的全连接网络相比,我们能够实现更高的准确性,并且更快。

hist = train(net,train_loader,test_loader,epochs=5)
plot_results(hist)
# Epoch  0, Train acc=0.943, Val acc=0.967, Train loss=0.001, Val loss=0.001
# Epoch  1, Train acc=0.976, Val acc=0.977, Train loss=0.001, Val loss=0.001
# Epoch  2, Train acc=0.983, Val acc=0.976, Train loss=0.000, Val loss=0.001
# Epoch  3, Train acc=0.988, Val acc=0.974, Train loss=0.000, Val loss=0.001
# Epoch  4, Train acc=0.990, Val acc=0.973, Train loss=0.000, Val loss=0.001

在这里插入图片描述

卷积层允许我们从图像中提取某些图像模式,因此最终的分类器是基于这些特征之上的。然而,我们可以使用相同的方法在特征空间中提取模式,通过在第一个卷积层的顶部堆叠另一个卷积层。我们将在下一章学习多层卷积网络。

总代码如下所示:

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)

plot_convolution(torch.tensor([[-1., 0., 1.], [-1., 0., 1.], [-1., 0., 1.]]), 'Vertical edge filter')
plot_convolution(torch.tensor([[-1., -1., -1.], [0., 0., 0.], [1., 1., 1.]]), 'Horizontal edge filter')


class OneConv(nn.Module):
    def __init__(self):
        super(OneConv, self).__init__()
        self.conv = nn.Conv2d(in_channels=1, out_channels=9, kernel_size=(5, 5))
        self.flatten = nn.Flatten()
        self.fc = nn.Linear(5184, 10)

    def forward(self, x):
        x = nn.functional.relu(self.conv(x))
        x = self.flatten(x)
        x = nn.functional.log_softmax(self.fc(x), dim=1)
        return x


net = OneConv()

summary(net, input_size=(1, 1, 28, 28))

hist = train(net, train_loader, test_loader, epochs=5)
plot_results(hist)

plt.show()

多层CNN


在上一单元中,我们学习了可以从图像中提取模式的卷积过滤器。对于我们的MNIST分类器,我们使用了9个5x5过滤器,得到9x24x24张量。

我们可以使用相同的卷积思想来提取图像中的高级模式。例如,数字8和9的圆边可以由许多较小的笔画组成。为了识别这些模式,我们可以在第一层的结果之上构建另一层卷积过滤器。

准备工作

创建新文件multilayer_CNN.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)

池化层Pooling layers

提取模式的层次结构:第一个卷积层寻找基本模式,比如水平线或垂直线。在它们之上的下一层卷积层寻找更高级的模式,比如原始形状。更多的卷积层可以将这些形状组合成图像的某些部分,直到分类的最终对象。

这样做时,我们还需要应用一个技巧:减小图像的空间大小。一旦我们检测到滑动窗口中有一个水平笔画,那么它发生在哪个像素就不那么重要了。因此,我们可以“缩小”图像的大小,可以使用下面其中一个池化层来完成:

  • Average Pooling:取一个滑动窗口(例如,2x2像素)并计算窗口内值的平均值;
  • Max Pooling:用最大值替换窗口。最大池化背后的思想是检测滑动窗口内某个模式的存在。

因此,在一个典型的CNN中,它将由几个卷积层组成,在它们之间有池化层来降低图像的维度。我们还会增加过滤器的数量,因为随着模式变得更高级,我们需要寻找更多可能的有趣组合。

由于空间维度的减少和特征/过滤器维度的增加,这种结构也被称为金字塔结构(pyramid architecture)。

一个使用双层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 #
==========================================================================================
MultiLayerCNN                            [1, 10]                   --
├─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函数将前向函数中的张量扁平化,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.980, Train loss=0.001, Val loss=0.000

多层CNN能够获得更高的精度,并且更快—只需1或2个epoch。这意味着复杂的网络架构需要更少的数据来弄清楚正在发生什么,并从图像中提取通用模式。

multilayer_CNN.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)


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))

hist = train(net, train_loader, test_loader, epochs=5)

使用来自CIFAR-10数据集的真实图像

下面探索更高级的图片数据集,称为CIFAR-10。它包含60k个32x32彩色图像,分为10类。

首先从网上下载数据并解压缩,放到python文件所在的data文件夹下,网站https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz

准备与展示数据集

创建新文件cifar.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

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)

plt.show()

请添加图片描述

一个著名的CIFAR-10架构被称为LeNet。它遵循与我们上面概述的相同的原则。由于所有图像都是彩色的,输入张量大小为3×32×32,并且5×5卷积滤波器也跨颜色维度应用-这意味着卷积核矩阵的大小为3×5×5。

此处还对这个模型做了一个简化——不使用log_softmax作为输出激活函数,而只返回最后一个完全连接层的输出。在这种情况下,使用CrossEntropyLoss损失函数来优化模型。

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 #
==========================================================================================
LeNet                                    [1, 10]                   --
├─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.66
==========================================================================================
Input size (MB): 0.01
Forward/backward pass size (MB): 0.05
Params size (MB): 0.24
Estimated Total Size (MB): 0.30
==========================================================================================

训练

正确地训练这个网络将花费大量的时间,最好是在支持gpu的计算上完成。

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%以上的准确率似乎是一个很好的成就。

cifar.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

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)

# plt.show()  # 展示数据集


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))

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())
  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

whyte王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值