PyTorch学习笔记(九)——VGG与NiN

目录

  • 一、VGG(使用块的网络)
    • 1.1 VGG 简介
    • 1.2 搭建 VGG
    • 1.3 训练/测试 VGG
    • 1.4 VGG 完整代码
  • 二、NiN(网络中的网络)
    • 2.1 NiN 简介
    • 2.2 搭建 NiN
    • 2.3 训练/测试 NiN
    • 2.4 NiN 完整代码

一、VGG(使用块的网络)

1.1 VGG 简介

虽然 AlexNet 证明深层神经网络卓有成效,但由于它的结构 “混乱”,无法提供一个通用的模板来指导后续的研究人员设计新的网络。

为了使神经网络的结构看起来更加规整,我们可以将若干卷积层和一个汇聚层封装成一个块,再将若干个块与全连接层连接起来以形成一个完整的网络。

使用块的想法首先出现在牛津大学的视觉几何组(Visual Geometry Group)中的 VGG 网络中。通过使用循环和子程序,我们可以很容易地在任何现代深度学习框架中实现这些重复的架构。

经典CNN的基本组成部分是如下这样一个序列:

  • 带填充以保持分辨率的卷积层;
  • 非线性激活函数,如ReLU;
  • 汇聚层,如最大汇聚层。

而一个VGG块与之类似,由若干个卷积层和一个最大汇聚层组成。在最初的VGG论文中,作者使用了带有 3 × 3 3\times3 3×3 卷积核、填充为 1 1 1保持高度和宽度)的卷积层,和带有 2 × 2 2\times2 2×2 汇聚窗口、步幅为 2 2 2(每个块后的分辨率减半)的最大汇聚层。

AlexNet 和 VGG 的架构比较如下图:

PyTorch学习笔记(九)——VGG与NiN_第1张图片

1.2 搭建 VGG

为了实现 VGG 网络,我们首先需要构造 VGG 块。一个 VGG 块应包含三个参数:输入通道数、输出通道数和卷积层的个数

很显然,因为 VGG 包含了多个卷积层,所以只有第一个卷积层的输入通道数与输出通道数不同,剩余卷积层的输入通道数与输出通道数保持一致。

代码实现如下:

class VGGBlock(nn.Module):
    def __init__(self, in_channels, out_channels, num_convs):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(),
        )
        for _ in range(num_convs - 1):
            self.conv.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
            self.conv.append(nn.ReLU())
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)

    def forward(self, x):
        x = self.conv(x)
        x = self.pool(x)
        return x

尝试创建一个 VGG 块实例并打印结构:

vgg_block = VGGBlock(3, 5, 3)
print(vgg_block)
# VGGBlock(
#   (conv): Sequential(
#     (0): Conv2d(3, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
#     (1): ReLU()
#     (2): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
#     (3): ReLU()
#     (4): Conv2d(5, 5, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
#     (5): ReLU()
#   )
#   (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
# )

接下来使用 VGG 块构造 VGG 网络。VGG 网络初始化应传入的参数为由元组构成的可迭代对象,这里假设是列表。其中每个元组的第一个参数代表当前 VGG 块中输入通道个数,第二个参数代表输出通道个数,第三个参数代表卷积层的个数。

注意 VGG 网络也是针对 224 × 224 224\times 224 224×224 分辨率的图像。

class VGG(nn.Module):
    def __init__(self, vgg_arch):
        super().__init__()
        self.conv = nn.Sequential()
        for in_channels, out_channels, num_convs in vgg_arch:
            self.conv.append(VGGBlock(in_channels, out_channels, num_convs))
        self.fc = nn.Sequential(
            nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096), nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10),
        )

    def forward(self, x):
        x = self.conv(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x

尝试创建一个 VGG 网络实例并打印结构:

vgg_arch = [(3, 64, 1), (64, 128, 1)]
vgg = VGG(vgg_arch)
print(vgg)
# VGG(
#   (conv): Sequential(
#     (0): VGGBlock(
#       (conv): Sequential(
#         (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
#         (1): ReLU()
#       )
#       (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#     )
#     (1): VGGBlock(
#       (conv): Sequential(
#         (0): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
#         (1): ReLU()
#       )
#       (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
#     )
#   )
#   (fc): Sequential(
#     (0): Linear(in_features=6272, out_features=4096, bias=True)
#     (1): ReLU()
#     (2): Dropout(p=0.5, inplace=False)
#     (3): Linear(in_features=4096, out_features=4096, bias=True)
#     (4): ReLU()
#     (5): Dropout(p=0.5, inplace=False)
#     (6): Linear(in_features=4096, out_features=10, bias=True)
#   )
# )

1.3 训练/测试 VGG

考虑这样一个 VGG 网络,它有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。 第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,因此它通常被称为VGG-11。

前面已经实现了 VGG 类,于是很容易构造 VGG-11

# 假设输入图片的通道数是1
vgg_arch = [(1, 64, 1), (64, 128, 1), (128, 256, 2), (256, 512, 2), (512, 512, 2)]
vgg = VGG(vgg_arch)

我们使用 Fashion-MNIST 数据集来检验 VGG 的性能

transformer = torchvision.transforms.Compose([Resize(224), ToTensor()])
train_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=True, transform=transformer, download=True)
test_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=False, transform=transformer, download=True)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_data, batch_size=64, num_workers=4)

由于 VGG-11 比 AlexNet 计算量更大,为节省时间,这里构造一个减弱版的 VGG-11,设置学习率为 0.05,训练 20 个 Epoch

vgg = VGG([(1, 16, 1), (16, 32, 1), (32, 64, 2), (64, 128, 2), (128, 128, 2)])
e = E(train_loader, test_loader, vgg, 20, 0.05)
e.main()
e.show()

在 NVIDIA GeForce RTX 3090 上的训练结果如下所示(这里仅展示最后一个Epoch以及整体的变化图):

Epoch 20
--------------------------------------------------
Train Avg Loss: 0.044154, Train Accuracy: 0.983633
Test  Avg Loss: 0.387796, Test  Accuracy: 0.914200

--------------------------------------------------
5410.8 samples/sec
--------------------------------------------------

Done!

PyTorch学习笔记(九)——VGG与NiN_第2张图片

1.4 VGG 完整代码

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torchvision.transforms import ToTensor, Resize
from Experiment import Experiment as E


class VGGBlock(nn.Module):
    def __init__(self, in_channels, out_channels, num_convs):
        super().__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
            nn.ReLU(),
        )
        for _ in range(num_convs - 1):
            self.conv.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
            self.conv.append(nn.ReLU())
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)

    def forward(self, x):
        x = self.conv(x)
        x = self.pool(x)
        return x


class VGG(nn.Module):
    def __init__(self, vgg_arch):
        super().__init__()
        self.conv = nn.Sequential()
        for in_channels, out_channels, num_convs in vgg_arch:
            self.conv.append(VGGBlock(in_channels, out_channels, num_convs))
        self.fc = nn.Sequential(
            nn.Linear(out_channels * 7 * 7, 4096), nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096), nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10),
        )

    def forward(self, x):
        x = self.conv(x)
        x = torch.flatten(x, 1)
        x = self.fc(x)
        return x


transformer = torchvision.transforms.Compose([Resize(224), ToTensor()])
train_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=True, transform=transformer, download=True)
test_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=False, transform=transformer, download=True)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_data, batch_size=64, num_workers=4)

vgg = VGG([(1, 16, 1), (16, 32, 1), (32, 64, 2), (64, 128, 2), (128, 128, 2)])
e = E(train_loader, test_loader, vgg, 20, 0.05)
e.main()
e.show()

二、NiN(网络中的网络)

2.1 NiN 简介

LeNet、AlexNet 和 VGG 都有一个共同的设计模式:通过一系列的卷积层与汇聚层来提取空间结构特征,然后通过全连接层对特征的表征进行处理。 AlexNet 和 VGG 对 LeNet 的改进主要在于如何扩大和加深这两个模块,而 NiN 的想法是在每个像素位置(针对每个高度和宽度)应用一个全连接层。 如果我们将权重连接到每个空间位置,我们可以将其视为一个 1 × 1 1\times1 1×1卷积层,或作为在每个像素位置上独立作用的全连接层。从另一个角度看,即将空间维度中的每个像素视为单个样本,将通道维度视为不同特征。

与 VGG 类似,NiN 网络也有一系列的 NiN 块构成。NiN 块从一个普通的卷积层开始,后面接了两个 1 × 1 1\times1 1×1 的卷积层, 第一层的卷积窗口形状通常由用户设置。

下图展示了 VGG 与 NiN 架构的差异:

PyTorch学习笔记(九)——VGG与NiN_第3张图片

2.2 搭建 NiN

先从 NiN 块开始,代码实现如下:

class NiNBlock(nn.Module):

    def __init__(self, in_channels, out_channels, kernel_size, stride, padding):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding), nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        )

    def forward(self, x):
        return self.block(x)

最初的 NiN 网络是在 AlexNet 后不久提出的,显然从中得到了一些启示。 NiN 使用窗口形状为 11 × 11 11\times11 11×11 5 × 5 5\times5 5×5 3 × 3 3\times3 3×3 的卷积层,输出通道数量与 AlexNet 中的相同。 每个 NiN 块后有一个最大汇聚层,汇聚窗口形状为 3 × 3 3\times 3 3×3,步幅为2。

NiN 和 AlexNet 之间的一个显著区别是 NiN 完全取消了全连接层。 相反,NiN 使用一个 NiN 块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层(Global Average Pooling)。这样设计的一个优点是,它显著减少了模型参数的数量。然而在实践中,这种设计有时会增加训练模型的时间。

class NiN(nn.Module):

    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            NiNBlock(1, 96, kernel_size=11, stride=4, padding=0),
            nn.MaxPool2d(3, stride=2),
            NiNBlock(96, 256, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(3, stride=2),
            NiNBlock(256, 384, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(3, stride=2),
            nn.Dropout(0.5),
            NiNBlock(384, 10, kernel_size=3, stride=1, padding=1),
        )
        self.gap = nn.Sequential(
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Flatten(),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.gap(x)
        return x

2.3 训练/测试 NiN

我们依然使用 FashionMNIST 数据集,设置 batch size 为 64 64 64,学习率为 0.13 0.13 0.13,使用 Xavier 初始化,在 NVIDIA GeForce RTX 3090 上训练 20 个 Epoch 的结果如下(只展示最后一个Epoch和整体变化图):

Epoch 20
--------------------------------------------------
Train Avg Loss: 0.228536, Train Accuracy: 0.916433
Test  Avg Loss: 0.295190, Test  Accuracy: 0.894600

--------------------------------------------------
4227.0 samples/sec
--------------------------------------------------

Done!

PyTorch学习笔记(九)——VGG与NiN_第4张图片

2.4 NiN 完整代码

import torch
import torchvision
from torch import nn
from torch.utils.data import DataLoader
from torchvision.transforms import ToTensor, Resize
from Experiment import Experiment as E


class NiNBlock(nn.Module):

    def __init__(self, in_channels, out_channels, kernel_size, stride, padding):
        super().__init__()
        self.block = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding), nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
            nn.Conv2d(out_channels, out_channels, kernel_size=1), nn.ReLU(),
        )

    def forward(self, x):
        return self.block(x)


class NiN(nn.Module):
    
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            NiNBlock(1, 96, kernel_size=11, stride=4, padding=0),
            nn.MaxPool2d(3, stride=2),
            NiNBlock(96, 256, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(3, stride=2),
            NiNBlock(256, 384, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(3, stride=2),
            nn.Dropout(0.5),
            NiNBlock(384, 10, kernel_size=3, stride=1, padding=1),
        )
        self.gap = nn.Sequential(
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Flatten(),
        )
    
    def forward(self, x):
        x = self.features(x)
        x = self.gap(x)
        return x


transformer = torchvision.transforms.Compose([Resize(224), ToTensor()])
train_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=True, transform=transformer, download=True)
test_data = torchvision.datasets.FashionMNIST('/mnt/mydataset', train=False, transform=transformer, download=True)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, num_workers=4)
test_loader = DataLoader(test_data, batch_size=64, num_workers=4)


def init_net(m):
    if type(m) == nn.Linear or type(m) == nn.Conv2d:
        nn.init.xavier_uniform_(m.weight)


nin = NiN()
nin.apply(init_net)
e = E(train_loader, test_loader, nin, 20, 0.13)
e.main()

你可能感兴趣的:(Pytorch,Computer,Vision,pytorch,学习,深度学习)