现代卷积神经网络(NiN),并使用NIN训练CIFAR10的分类

专栏:神经网络复现目录


本章介绍的是现代神经网络的结构和复现,包括深度卷积神经网络(AlexNet),VGG,NiN,GoogleNet,残差网络(ResNet),稠密连接网络(DenseNet)。
文章部分文字和代码来自《动手学深度学习》


网络中的网络(NiN)

简介

NiN,即Network in Network,是一种由Min Lin等人于2013年提出的深度神经网络架构。相较于传统的卷积神经网络,NiN引入了“1x1卷积层”以及全局平均池化层,这两个模块的引入使得NiN模型可以通过不同的网络层抽取多层次、多尺度的特征。

NiN的核心思想是通过在卷积神经网络的最后两层之间添加一个或多个全局平均池化层(global average pooling layer),将每个空间位置的特征合并为一个全局统计值,从而减少模型的参数量,防止过拟合。与全连接层相比,全局平均池化层能够更好地提取特征,因为它不依赖于特定的位置信息,而是能够更好地保留特征图的整体信息。此外,NiN中还引入了1x1卷积层,它的作用在于改变通道数。通过对特征通道进行1x1卷积,可以让不同通道之间的特征在保留其信息的同时,使得这些特征更加明确,进而提高模型的准确率。

NiN的整体结构包括多个NiN块,每个NiN块包括一个卷积层、多个1x1卷积层和全局平均池化层。其中,NiN块的卷积层使用了普通的卷积层,而不是分离卷积层,因此模型可以学习到不同特征尺度之间的相互关系。NiN的最后一层通常是全局平均池化层和全连接层,用于分类。由于NiN中引入了全局平均池化层和1x1卷积层,因此NiN模型相较于传统的卷积神经网络具有更好的性能和更少的参数量。

全局平均汇聚层

全局平均汇聚层(Global Average Pooling Layer)是一种常见的卷积神经网络结构,在最后一层卷积层后面添加一个全局平均汇聚层,用于将卷积层的输出进行降维,从而得到一个固定长度的特征向量。

与全连接层相比,全局平均汇聚层具有以下优点:

  1. 参数少:全局平均汇聚层不包含可训练参数,因此它的计算量比全连接层小,可以避免过拟合。

  2. 降低过拟合:由于全局平均汇聚层可以对每个通道进行平均池化,因此其对空间信息的保留比较好,可以提高网络的泛化能力,从而减少过拟合。

  3. 不依赖于输入大小:由于全局平均汇聚层的输出特征向量的长度与输入大小无关,因此可以处理不同大小的输入。

  4. 更适合于可视化:全局平均汇聚层将卷积层的输出降维到一个固定长度的特征向量,这个特征向量可以更方便地进行可视化,帮助理解模型的行为。

总之,全局平均汇聚层是一种有效的降维方法,可以帮助神经网络提取更加具有代表性的特征,并在一定程度上避免过拟合。

全局平均汇聚层的逻辑如下:

  1. 对于输入的 feature map,每个通道内的所有值进行平均得到一个标量输出。

  2. 对于输入 feature map 的每个通道,都执行第 1 步,得到通道内所有值的平均值。

  3. 将每个通道的平均值串联起来,作为输出。

全局平均汇聚层的输出是对输入特征图的每个通道执行平均池化后的结果,通常是一个大小为 1x1x通道数 的特征图。这个特征图可以被视为每个通道的特征的“汇聚”,因为它将该通道的所有信息转换为一个标量值。这种操作通常在最后的卷积层后使用,以将卷积层输出的高维特征图压缩为低维的特征向量,方便后续的全连接层处理。

和VGG的区别

下图对比了VGG和NIN的区别:
在这里插入图片描述
它们的区别在于:

卷积层的组成:NIN的卷积层采用了一种被称为1x1卷积的技术,这种技术可以增加非线性变换的能力,同时可以减少参数数量;而VGG采用的是更加传统的3x3卷积层,这种卷积层可以保留更多的空间信息,但参数数量相对较多。

池化层的位置:NIN的池化层通常放置在卷积层中间,这样可以使得特征图的大小降低,同时又不会丢失太多的信息;而VGG则采用了较为传统的做法,将池化层放在每两个卷积层之间,这样可以使得特征图的大小减半,同时也会丢失一些信息。

全连接层的设计:NIN使用全局平均池化层代替了传统的全连接层,这样可以有效减少参数数量,同时也可以避免过拟合;而VGG则采用了多个全连接层,可以更好地拟合复杂的非线性映射关系,但也增加了参数数量和过拟合的风险。

总的来说,NIN相对于VGG来说,参数数量更少,计算量更小,同时也可以获得更好的效果。但是,在某些任务中,比如图像分类,VGG表现仍然非常优秀,因为其较为复杂的网络结构可以更好地捕获图像的细节和纹理信息。

优点

NIN的优点包括:

  1. 更少的参数:NIN使用1x1卷积层来代替全连接层,因此参数数量显著减少,使得模型更容易训练,同时也能减轻过拟合。

  2. 网络结构更加简单:NIN的网络结构比较简单,包含了很少的层数和卷积核尺寸,使得它更容易理解和优化。

  3. 提高了模型的表达能力:NIN引入了MLPconv层,使得模型可以在特征空间中进行非线性变换,从而提高了模型的表达能力。

  4. 易于并行计算:由于NIN采用了1x1卷积层代替全连接层,使得卷积计算可以并行化处理,从而提高了计算效率。

网络结构

定义

NIN(Network In Network)是由Min Lin、Qiang Chen、Shuicheng Yan在2013年提出的深度神经网络。它的主要思想是在传统的卷积神经网络中嵌入由一个 MLP(多层感知机)组成的模块,称为“Micro Network”,以增加模型的非线性表达能力。

具体来说,NIN的网络结构包含三个由卷积层、MLP层和全局平均池化层组成的模块,其中MLP层用于替代传统卷积层的非线性映射。整个网络的最后一层是全连接层,用于分类。

以下是NIN的网络结构:

Input -> Conv (kernel size: 11x11, stride: 4, num filters: 96) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 96) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 96) -> ReLU ->
        MaxPool (kernel size: 3x3, stride: 2) -> Dropout (p=0.5) ->

        Conv (kernel size: 5x5, stride: 1, num filters: 256, padding: 2) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 256) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 256) -> ReLU ->
        MaxPool (kernel size: 3x3, stride: 2) -> Dropout (p=0.5) ->

        Conv (kernel size: 3x3, stride: 1, num filters: 384, padding: 1) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 384) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 384) -> ReLU ->
        MaxPool (kernel size: 3x3, stride: 2) -> Dropout (p=0.5) ->

        Conv (kernel size: 3x3, stride: 1, num filters: 1024, padding: 1) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 1024) -> ReLU ->
        Conv (kernel size: 1x1, stride: 1, num filters: 10) -> ReLU ->
        GlobalAvgPool -> Output

其中,每个卷积层之后都跟随一个ReLU激活函数,用于引入非线性表达能力。全局平均池化层用于将每个卷积核的输出值求平均,进而计算输出特征图的每个通道的平均值,从而得到该通道对应的输出值。这种做法可以有效地降低特征图的维度,减少模型的参数量,进而减轻过拟合的风险

实现

def nin_block(in_channels, out_channels, kernel_size, stride, padding):
    return 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())

net = nn.Sequential(
    nin_block(3, 96, kernel_size=11, stride=4, padding=0),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nin_block(96, 256, kernel_size=5, stride=1, padding=2),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nin_block(256, 384, kernel_size=3, stride=1, padding=1),
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Dropout(0.5),
    # 标签类别数是10
    nin_block(384, 10, kernel_size=3, stride=1, padding=1),
    # 全局平均池化层可通过将窗口形状设置成输入的高和宽实现
    nn.AvgPool2d(kernel_size=5),
    # 将四维的输出转成二维的输出,其形状为(批量大小, 10)
    nn.Flatten())

实战(CIFAR10分类)

模型设计

import torch.nn as nn

def nin_block(in_channels, out_channels, kernel_size, stride, padding):
    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())
    return block

class NiN(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nin_block(3, 96, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=3, stride=2,padding=1),
            nn.Dropout(0.5),
            nin_block(96, 256, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=3, stride=2,padding=1),
            nn.Dropout(0.5),
            # 标签类别数是10
            nin_block(256, 10, kernel_size=3, stride=1, padding=1),
            #全局平均代替最后的全连接层
            nn.AdaptiveAvgPool2d((1,1))
            )

    def forward(self,input):
        x = self.net(input)
        x = x.view(x.size(0), 10)
        #print(x.shape)
        return x

导入模块

import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms
import torch.optim as optim
import time


# 获取设备,优先使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)

数据集

# 加载数据集
def load_data_cifar10(batch_size, resize=None):
    transform_list = [transforms.ToTensor()]
    if resize:
        transform_list.insert(0, transforms.Resize(resize))
    transform = transforms.Compose(transform_list)
    cifar10_train = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
    cifar10_test = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
    train_loader = torch.utils.data.DataLoader(cifar10_train, batch_size=batch_size, shuffle=True, num_workers=4)
    test_loader = torch.utils.data.DataLoader(cifar10_test, batch_size=batch_size, shuffle=False, num_workers=4)
    return train_loader, test_loader

训练和评估

def evaluate_accuracy(data_iter, net, device):
    net.eval()  # 评估模式
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            X, y = X.to(device), y.to(device)
            acc_sum += (net(X).argmax(dim=1) == y).float().sum().cpu().item()
            n += y.shape[0]
    net.train()  # 改回训练模式
    return acc_sum / n

def train_nin(net, train_iter, test_iter, loss, optimizer, device, epochs):
    net = net.to(device)
    print("training on ", device)
    batch_count = 0
    for epoch in range(epochs):
        train_l_sum, train_acc_sum, n, start = 0.0, 0.0, 0, time.time()
        for X, y in train_iter:
            X, y = X.to(device), y.to(device)
            y_hat = net(X)
            l = loss(y_hat, y)
            optimizer.zero_grad()
            l.backward()
            optimizer.step()
            train_l_sum += l.cpu().item()
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net, device)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f, time %.1f sec' % (
            epoch + 1, train_l_sum / batch_count, train_acc_sum / n, test_acc, time.time() - start))
net = NiN()
batch_size=128
train_iter,test_iter=load_data_cifar10(batch_size,resize=224)
lr, num_epochs = 0.1, 10
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=lr, momentum=0.9)
train_nin(net, train_iter, test_iter, loss, optimizer, device, num_epochs)

保存模型

torch.save(net.state_dict(),"nin.pth")

测试

import torch
import torchvision
import torchvision.transforms as transforms

# 加载数据集并进行预处理
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)

# 定义类别标签
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

# 加载模型
net = NiN()
net.load_state_dict(torch.load("nin.pth"))

# 使用CPU或GPU进行预测
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
net.to(device)

# 进行预测并输出结果
net.eval()
with torch.no_grad():
    for data in testloader:
        images, labels = data
        images, labels = images.to(device), labels.to(device)
        outputs = net(images)
        _, predicted = torch.max(outputs.data, 1)
        for i in range(4):
            print('GroundTruth: %s, Predicted: %s' % (classes[labels[i]], classes[predicted[i]]))
  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青云遮夜雨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值