现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类

Posted 青云遮夜雨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类相关的知识,希望对你有一定的参考价值。

专栏:神经网络复现目录


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

文章目录


含并行连结的网络(GoogLeNet)

GoogleNet,也叫Inception v1,是Google在2014年提出的深度卷积神经网络,它的主要特点是使用了Inception模块来替代原来的单一卷积层,以更好地提取特征。GoogleNet是在ImageNet数据集上训练出来的,它在当年的ImageNet比赛中获得了最好的结果。

GoogleNet的整体架构相对于之前的模型更为复杂,具有22层。其中,它的最大特点是使用了Inception模块,使得模型的参数量比之前的模型更小,但是精度更高。

Inception模块主要是将不同卷积核大小的卷积层和最大池化层进行组合,以获得不同大小的感受野,从而更好地提取特征。同时,Inception模块使用了1x1的卷积层进行通道数的调整,进一步减少了参数量。

除了Inception模块之外,GoogleNet还采用了全局平均池化层来代替全连接层,这也是后续很多模型都采用的做法,可以减少过拟合,同时减少参数量。

Inception块

定义和结构

Inception模块是GoogleNet中的一个核心组成部分,用于提取图像特征。该模块采用并行的多个卷积层和池化层来提取不同尺度的特征,然后将它们在通道维度上进行拼接,得到更丰富的特征表达。

一个基本的Inception模块包含了四个分支(Branch),每个分支都有不同的卷积核或者池化核,如下图所示:

在这个模块中,我们可以看到分支1、2、3都是卷积层,分支4则是最大池化层,其目的是提取图像中不同大小的特征。通过这些分支的组合,Inception模块可以有效地提取图像的多尺度特征,且计算代价相对较小。

具体来说,假设输入的特征图的大小是 h × w × c h\\times w\\times c h×w×c,Inception模块将其输入到四个分支中,这四个分支分别是:

分支1:1×1卷积层,用于对通道数进行降维,可以看做是对输入的特征进行了线性变换,从而提取空间信息;

分支2:1×1卷积层后接3×3卷积层,先通过1×1卷积层将通道数降维,然后再通过3×3卷积层提取特征。在这个分支中,1×1卷积层可以用来降低计算量,3×3卷积层用于提取空间信息,组合起来可以在计算效率和特征表达能力之间取得平衡;

分支3:1×1卷积层后接5×5卷积层,和分支2类似,该分支也是先通过1×1卷积层将通道数降维,然后再通过5×5卷积层提取特征。与3×3卷积核相比,5×5卷积核可以捕捉更大的空间范围,从而更好地提取空间信息;

分支4:3×3最大池化层后接1×1卷积层,通过池化层可以提取图像中不同大小的特征,然后使用1×1卷积层进行通道数降维。

最后,将四个分支的输出在通道维度上进行拼接,得到的结果就是Inception模块的输出。

实现

import torch
from torch import nn
from torch.nn import functional as F
from d2l import torch as d2l


class Inception(nn.Module):
    # c1--c4是每条路径的输出通道数
    def __init__(self, in_channels, c1, c2, c3, c4, **kwargs):
        super(Inception, self).__init__(**kwargs)
        # 线路1,单1x1卷积层
        self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1)
        # 线路2,1x1卷积层后接3x3卷积层
        self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1x1卷积层后接5x5卷积层
        self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4,3x3最大汇聚层后接1x1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1)

    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(F.relu(self.p2_1(x))))
        p3 = F.relu(self.p3_2(F.relu(self.p3_1(x))))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 在通道维度上连结输出
        return torch.cat((p1, p2, p3, p4), dim=1)

GoogLeNet模型

GoogLeNet一共使用9个Inception块和全局平均汇聚层的堆叠来生成其估计值。Inception块之间的最大汇聚层可降低维度。 第一个模块类似于AlexNet和LeNet,Inception块的组合从VGG继承,全局平均汇聚层避免了在最后使用全连接层。

结构

  1. 卷积层,输入为 224 × 224 224\\times224 224×224的彩色图像,使用 7 × 7 7\\times7 7×7的卷积核,步幅为2,输出通道数为64,偏置和ReLU激活函数。
  2. 最大池化层,使用 3 × 3 3\\times3 3×3的卷积核,步幅为2,输出尺寸为 112 × 112 112\\times112 112×112
  3. 卷积层,输入通道数为64,使用 1 × 1 1\\times1 1×1的卷积核,输出通道数为64,偏置和ReLU激活函数。
  4. 卷积层,输入通道数为64,使用 3 × 3 3\\times3 3×3的卷积核,输出通道数为192,步幅为1,偏置和ReLU激活函数。
  5. 最大池化层,使用 3 × 3 3\\times3 3×3的卷积核,步幅为2,输出尺寸为 56 × 56 56\\times56 56×56
  6. 使用两个Inception模块,输出通道数为256和480,分别将它们堆叠在一起。
  7. 最大池化层,使用 3 × 3 3\\times3 3×3的卷积核,步幅为2,输出尺寸为 28 × 28 28\\times28 28×28
  8. 使用五个Inception模块,输出通道数分别为 512 , 512 , 512 , 528 512, 512, 512, 528 512,512,512,528 832 832 832,分别将它们堆叠在一起。
  9. 最大池化层,使用 3 × 3 3\\times3 3×3的卷积核,步幅为2,输出尺寸为 14 × 14 14\\times14 14×14
  10. 使用两个Inception模块,输出通道数分别为 832 832 832 1024 1024 1024,分别将它们堆叠在一起。
  11. 全局平均池化层,使用一个 7 × 7 7\\times7 7×7的池化窗口,输出大小为 1 × 1 1\\times1 1×1
  12. Dropout层,随机将一定比例的元素归零,防止过拟合。
  13. 全连接层,输出为10,对应10个类别。

实现

class GoogLeNet(nn.Module):
    def __init__(self, in_channels=3, num_classes=1000):
        super(GoogLeNet, self).__init__()

        # 第一阶段
        self.stage1 = nn.Sequential(
            nn.Conv2d(in_channels, 64, kernel_size=7, stride=2, padding=3),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        # 第二阶段
        self.stage2 = nn.Sequential(
            nn.Conv2d(64, 64, kernel_size=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 192, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        # 第三阶段
        self.stage3 = nn.Sequential(
            Inception(in_channels=192, c1=64, c2=(96, 128), c3=(16, 32), c4=32),#64+128+32+32=256
            Inception(256, 128, (128, 192), (32, 96), 64),#128+192+96+64=480
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        # 第四阶段
        self.stage4 = nn.Sequential(
            Inception(480, 192, (96, 208), (16, 48), 64),
            Inception(512, 160, (112, 224), (24, 64), 64),
            Inception(512, 128, (128, 256), (24, 64), 64),
            Inception(512, 112, (144, 288), (32, 64), 64),
            Inception(528, 256, (160, 320), (32, 128), 128),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        # 第五阶段
        self.stage5 = nn.Sequential(
            Inception(832, 256, (160, 320), (32, 128), 128),
            Inception(832, 384, (192, 384), (48, 128), 128),
            nn.AdaptiveAvgPool2d((1, 1)),
            nn.Flatten()
        )

        # 全连接层
        self.fc = nn.Linear(1024, num_classes)

    def forward(self, x):
        x = self.stage1(x)
        x = self.stage2(x)
        x = self.stage3(x)
        x = self.stage4(x)
        x = self.stage5(x)
        x = self.fc(x)
        return x

实战

导包

import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
torch.manual_seed(1234)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)

数据集

transform_train = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])

transform_test = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    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_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=32,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=32,
                                         shuffle=False, num_workers=2)

优化器,损失函数

net = GoogLeNet(num_classes=10).to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

训练和评估

import time
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(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))
train(net,trainloader,testloader,criterion,optimizer,device,10)

从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化

点击 机器学习算法与Python学习选择加星标

精彩内容不迷路

作者 | MrCharles ,本文为AI科技大本营授权发布

前言

深度学习的兴起使卷积神经网络在计算机视觉方面大放异彩,本文将按时间和创新点顺序介绍一系列网络结构:LeNet、AlexNet、VGGNet、InceptionNet 与 ResNet。


网络上大部分文章都只是草草讲述,本文小波仔仔细梳理,从问题的背景,网络结构,为什么设计这样的结构,参数数量各方面详细讲述CNN的进化之路。


LeNet


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


LeNet 是整个卷积神经网络的开山之作,是卷积神经网络的现代雏形,1998年由LeCun提出。最初设计的目的是识别手写字母,被广泛应用在美国支票手写识别系统当中。


LeNet一共有7层(不包括输入层)


  • 输入层:


输入图像的大小为3232,这要比mnist数据库中的最大字母(2828)还大。

作用:图像较大,这样做的目的是希望潜在的明显特征,比如笔画断续,角点等能够出现在最高层特征监测子感受野的中心。


  • 其他层:


C1,C3,C5为卷积层,S2,S4为降采样层,F6为全连接层,还有一个输出层。


每一个层都有多个Feature Map(每个Feature Map中含有多个神经元),输入通过一种过滤器作用,提取输入的一种特征,得到一个不同的Feature Map。


  • C1层-卷积层



    • 输入图片:32*32

    • 卷积核大小:5*5

    • 卷积核种类:6

    • 输出featuremap大小:28*28 (32-5+1)=28

    • 神经元数量:28286

    • 可训练参数:(55+1) * 6(每个滤波器55=25个unit参数和一个bias参数,一共6个滤波器)

    • 连接数:(55+1)62828=122304


  • S2层-池化层(下采样层)



    • 输入:28*28

    • 采样区域:2*2

    • 采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

    • 采样种类:6

    • 输出featureMap大小:14*14(28/2)

    • 神经元数量:14146

    • 可训练参数:2*6(和的权+偏置)

    • 连接数:(22+1)61414


S2中每个特征图的大小是C1中特征图大小的1/4。


  • C3层-卷积层


    • 输入:S2中所有6个或者几个特征map组合

    • 卷积核大小:5*5

    • 卷积核种类:16

    • 输出featureMap大小:10*10 (14-5+1)=10


C3中的每个特征map是连接到S2中的所有6个或者几个特征map的,表示本层的特征map是上一层提取到的特征map的不同组合。为什么不把S2的每一个Feature Map连接到S3的每一个Feature Map中?原因有亮点:


  • 第一,不完全连接机制连接的数量保持在合理范围,

  • 第二,这样破坏了网络的对称性,由于不同的Feature Map有不同的输入,所以迫使他们抽取不同的特征(理想状态特征互补)。


存在的一个方式是:C3的前6个特征图以S2中3个相邻的特征图子集为输入。接下来6个特征图以S2中4个相邻特征图子集为输入。然后3个以不相邻的4个特征图子集为输入。最后一个将S2中所有特征图为输入。则:可训练参数:6*(355+1)+6*(455+1)+3*(455+1)+1*(655+1)=1516。如下图:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


连接数:10101516=151600


  • S4层-池化层(下采样层)

    • 输入:10*10

    • 采样区域:2*2

    • 采样方式:4个输入相加,乘以一个可训练参数,再加上一个可训练偏置。结果通过sigmoid

    • 采样种类:16

    • 输出featureMap大小:5*5(10/2)

    • 神经元数量:5516=400

    • 可训练参数:2*16=32(和的权+偏置)

    • 连接数:16*(2*2+1)55=2000


S4中每个特征图的大小是C3中特征图大小的1/4


  • C5层-卷积层

    • 输入:S4层的全部16个单元特征map(与s4全相连)

    • 卷积核大小:5*5

    • 卷积核种类:120

    • 输出featureMap大小:1*1(5-5+1)

    • 可训练参数/连接:120*(1655+1)=48120


  • F6层-全连接层

    • 输入:c5 120维向量

    • 计算方式:计算输入向量和权重向量之间的点积,再加上一个偏置,结果通过sigmoid函数输出。

    • 可训练参数:84*(120+1)=10164


6层是全连接层。F6层有84个节点,对应于一个7x12的比特图,-1表示白色,1表示黑色,这样每个符号的比特图的黑白色就对应于一个编码。该层的训练参数和连接数是(120 + 1)x84=10164。ASCII编码图如下:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


所以知道为什么要选择84了吧。对应ASCII码中每个字母的比特图。


  • 输出层


输出层由欧式径向基函数(Euclidean Radial Basis Function)单元组成,每类一个单元,每个单元有84个输入。


也就是说:每个输入RBF单元计算输入向量和参数向量之间的欧氏距离。输入离参数向量越远,RBF输出越大。一个RBF输出可以理解为衡量输入模式和RBF相关联的一个模型的匹配程度的惩罚项。给定一个输入模式,损失函数应该能使F6的配置和RBF参数向量(模式的期望分类)足够接近。假设x是上一层的输入,y是RBF的输出,则RBF输出的计算方式是:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


每一个单元的参数是人工选取并保持固定的。这些参数向量的成分被设计成-1或1。虽然这些参数可以以-1或1等概论方式任取,或者是构成一个纠错码,但是被设计成一个相应字符类的7*12的格式化图片。


总结


特点:

  • LeNet主要是卷积和下采样相结合,虽然现在各位大神看着觉得不怎么样,可是在1998年,可是一个开创性的想法。他对后面所有CNN奠定了很重要的基础。

  • LeNet是一种用于手写体字符识别的非常高效的卷积神经网络。

  • 卷积神经网络能够很好的利用图像的结构信息。

  • 卷积层的参数较少,这也是由卷积层的主要特性即局部连接和共享权重所决定。


缺点:

  • 网络层数很浅

  • 没有激活层


AlexNet


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


AlexNet由Alex Krizhevsky和Geoffrey Hinton 于2012年提出,夺得2012年ImageNet比赛的冠军,top5预测的错误率为16.4%,远超第一名。AlexNet采用8层的神经网络,5个卷积层和3个全连接层(3个卷积层后面加了最大池化层),包含6亿3000万个链接,6000万个 参数和65万个神经元。


AlexNet相比于LeNet,网络更深,第一次引入ReLu激活层,验证了其效果在较深的网络中超过了Sigmoid,成功解决了Sigmoid在网络较深时的梯度弥散问题。


在全连接引入dropout防止过拟合。在机器学习模型训练中,过拟合现象实在令人头秃。Dropout 对防止过拟合有很好的效果。之后大量 Dropout 变体涌现,这项技术也成为机器学习研究者常用的训练 trick。训练时使用Dropout随机忽略一部分神经元,以避免模型过拟合,一般在全连接层使用,在预测的时候是不使用Dropout的,即Dropout为1.19年6月,谷歌为该项技术申请了专利,而且这项专利已经生效!


在CNN中使用重叠的最大池化(步长小于卷积核)。此前CNN中普遍使用平均池化,使用最大池化可以避免平均池化的模糊效果。同时重叠效果可以提升特征的丰富性。


提出LRN(Local Response Normalization,即局部响应归一化)层,对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。


使用CUDA加速神经网络的训练,利用了GPU强大的计算能力。


数据增强,随机的从256256的图片中截取224224大小的区域(以及水平翻转的镜像),相当于增加了(256-224)2*2=2048倍的数据量,如果没有数据增强,模型会陷入过拟合中,使用数据增强可以增大模型的泛化能力。


由于AlexNet采用了两个GPU进行训练,因此,该网络结构图由上下两部分组成,一个GPU运行图上方的层,另一个运行图下方的层,两个GPU只在特定的层通信。例如第二、四、五层卷积层的核只和同一个GPU上的前一层的核特征图相连,第三层卷积层和第二层所有的核特征图相连接,全连接层中的神经元和前一层中的所有神经元相连接。


  • 第一层-卷积层


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


    • 输入图片:2242243 (RGB), 在训练时会经过预处理Padding变为227×227×3

    • 卷积核大小:11113

    • 卷积核种类:96 (图上是上下各48,两个GPU分别承担一半计算)

    • 输出featuremap大小:55*55 (new_size=floor((img_size - filter_size)/stride) +1=(227-11)/4=55 )

    • 神经元数量:55x55x48x2=290400

    • 可训练参数:(11113+1) * 96=34944(每个滤波器11113个unit参数和一个bias参数,一共96个滤波器)


  • ReLU


卷积后的55×55像素层经过ReLU单元的处理,生成激活像素层,尺寸仍为2组55×55×48的像素层数据。


  • 池化


RuLU后的像素层再经过池化运算,池化运算的尺寸为3×3,步长为2,则池化后图像的尺寸为 (55-3)/2+1=27,即池化后像素的规模为27×27×96


  • 归一化


归一化(normalization)的目的是“抑制”,局部归一化就是来实现局部抑制,尤其当使用ReLU时,因为ReLU的响应结果是无界的(可以非常大),所以需要归一化。使用局部归一化的方案有助于增加泛化能力。LRN的核心思想就是利用临近的数据做归一化。LRN仿造生物学上活跃的神经元对相邻神经元的抑制现象(侧抑制)。


好处有以下两点:


1)归一化有助于快速收敛;

2)对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元,增强了模型的泛化能力。


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


池化后的像素层再进行归一化处理,归一化运算的尺寸为5×5,归一化后的像素规模不变,仍为27×27×96,这96层像素层被分为两组,每组48个像素层,分别在一个独立的GPU上进行运算。


  • 第二层-卷积层


和第一层类似,卷积–>ReLU–>池化–>归一化。第二层的输入数据为第一层输出的27×27×96的像素层(被分成两组27×27×48的像素层放在两个不同GPU中进行运算),为方便后续处理,在这里每幅像素层的上下左右边缘都被填充了2个像素(填充0),即图像的大小变为 (27+2+2) ×(27+2+2)


    • 输入图片:313148 (2个GPU一共96)

    • 卷积核大小:5*5

    • 卷积核种类:256 (图上是上下各128,两个GPU分别承担一半计算)

    • 输出featuremap大小:27×27(new_size=floor((img_size - filter_size)/stride) +1=(31-5)/4+1=27)

    • 神经元数量:27×27x128x2=186624

    • 可训练参数:(5548*128+128) * 2=307456


  • ReLU


这些像素层经过ReLU单元的处理,生成激活像素层,尺寸仍为两组27×27×128的像素层。


  • 池化


再经过池化运算的处理,池化运算的尺寸为3×3,步长为2,池化后图像的尺寸为(27-3)/2+1=13,即池化后像素的规模为2组13×13×128的像素层


  • 归一化


然后再经归一化处理,归一化运算的尺度为5×5,归一化后的像素层的规模为2组13×13×128的像素层,分别由2个GPU进行运算。


  • 第三层(卷积层)


第三层的处理流程为:卷积–>ReLU


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


这一层中每个GPU都有192个卷积核,每个卷积核的尺寸是3×3×256。因此,每个GPU中的卷积核都能对2组13×13×128的像素层的所有数据进行卷积运算。如该层的结构图所示,两个GPU有通过交叉的虚线连接,也就是说每个GPU要处理来自前一层的所有GPU的输入。


本层卷积的步长是1个像素,经过卷积运算后的尺寸为 (13+1+1-3)/1+1=13,即每个GPU中共13×13×192个卷积核,2个GPU中共有13×13×384个卷积后的像素层。


    • 输入图片:13×13×128 2个GPU一共84,每幅像素层的上下左右边缘都填充1个像素,填充后变为 (13+1+1)×(13+1+1)×128,

    • 卷积核大小:3×3×256

    • 卷积核种类:192*2

    • 输出featuremap大小:13×13(new_size=floor((img_size - filter_size)/stride) +1=(15-3)/1+1=13)

    • 神经元数量:13×13x192*2=64896

    • 可训练参数:3x3x256x384+384=885120


  • ReLU


卷积后的像素层经过ReLU单元的处理,生成激活像素层,尺寸仍为2组13×13×192的像素层,分配给两组GPU处理。


  • 第四层(卷积层)


和第三层类似,卷积–>ReLU


    • 输入图片:13×13×192 填充后的尺寸变为 (13+1+1)×(13+1+1)×192,分布在两个GPU中进行运算。

    • 卷积核大小:3×3*192(与第三层不同,第四层的GPU之间没有虚线连接,也即GPU之间没有通信,卷积的移动步长是1个像素)

    • 卷积核种类:192*2

    • 输出featuremap大小:13 * 13 (new_size=floor((img_size - filter_size)/stride) +1 =(13+1+1-3)/1+1=13

    • 神经元数量:13x13x192x2=64896

    • 可训练参数:(3x3x192x192+192)x2=663936


  • ReLU


卷积后的像素层经过ReLU单元处理,生成激活像素层,尺寸仍为2组13×13×192像素层,分配给两个GPU处理。


  • 第五层(卷积层)


第五层的处理流程为:卷积–>ReLU–>池化


    • 输入图片:13×13×192 填充后的尺寸变为 (13+1+1)×(13+1+1)×192,分布在两个GPU中进行运算。

    • 卷积核大小:3×3*128(GPU之间没有通信,卷积的移动步长是1个像素)

    • 卷积核种类:128*2

    • 输出featuremap大小:13 * 13 (new_size=floor((img_size - filter_size)/stride) +1 =(13+1+1-3)/1+1=13

    • 神经元数量:13x13x128x2=43264

    • 可训练参数:(3x3x192x128+128)x2=442624


  • ReLU


经过ReLU单元处理,生成激活像素层,尺寸仍为2组13×13×128像素层,由两个GPU分别处理。


  • 池化


2组13×13×128像素层分别在2个不同GPU中进行池化运算处理,池化运算的尺寸为3×3,步长为2,池化后图像的尺寸为 (13-3)/2+1=6,即池化后像素的规模为两组6×6×128的像素层数据,共有6×6×256的像素层数据。


  • 第六层(全连接层)


处理流程为:卷积(全连接)–>ReLU–>Dropout


    • 输入图片:6×6×256

    • 卷积核大小:6×6×256 (卷积核的尺寸刚好与待处理特征图(输入)的尺寸相同,即卷积核中的每个系数只与特征图(输入)尺寸的一个像素值相乘,一一对应,因此,该层被称为全连接层。)

    • 卷积核种类:4096

    • 输出featuremap大小:4096×1×1

    • 神经元数量:4096

    • 可训练参数:(6x6x128x2)x4096+4096=37752832


  • 第七层(全连接层)


全连接–>ReLU–>Dropout


第六层输出的4096个数据与第七层的4096个神经元进行全连接,然后经ReLU进行处理后生成4096个数据,再经过Dropout处理后输出4096个数据。


    • 神经元数量:4096

    • 可训练参数:4096x4096+4096=16781312


  • 第八层(全连接层)


处理流程为:全连接

第七层输出的4096个数据与第八层的1000个神经元进行全连接,经过训练后输出1000个float型的值,这就是预测结果。


    • 神经元数量:4096

    • 可训练参数:4096x1000+1000=4097000

总结


  • 神经元数量总数:809800

  • 可训练参数:60965224

  • 卷积层参数:2334080,占总参数的3.8%

  • 全连接层参数:58631144,占总参数量的96.2% 说明全连接还是占了大部分的参数


一些人提出的改进可能:


  • 第一个卷积层使用的是11*11的滤波器,导致无法提取更细粒度的特征,是否可以使用更多更小的滤波器来代替第一个卷积层

  • LRN在往后的算法中效果不是很大,所以用的人并不多。

  • 相比于LeNet,一共有八层,但是还能使用更多层的结构么?


VGGNet


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


前面我们很详细的讲了AlexNet的结构,参数,特点。一个是卷积核很大,无法提取更细粒度的特征。再一个AlexNet只有八层结构,加深是否可以达到更好的精度呢?


2014年,牛津大学计算机视觉组(Visual Geometry Group)和Google DeepMind公司的研究员一起研发出了新的深度卷积神经网络:VGGNet,并取得了ILSVRC2014比赛分类项目的第二名(第一名是GoogLeNet,也是同年提出的)和定位项目的第一名。VGGNet可以看成是加深版本的AlexNet,都是由卷积层、全连接层两大部分构成。


VGG16相比AlexNet的一个改进是采用连续的几个3x3的卷积核代替AlexNet中的较大卷积核(11x11,7x7,5x5)。对于给定的感受野(与输出有关的输入图片的局部大小),采用堆积的小卷积核是优于采用大的卷积核,因为多层非线性层可以增加网络深度来保证学习更复杂的模式,而且代价还比较小(参数更少)。比如,3个步长为1的3x3卷积核的一层层叠加作用可看成一个大小为7的感受野(其实就表示3个3x3连续卷积相当于一个7x7卷积),其参数总量为 3x(9xC2) 3x(9xC^2)3x(9xC 2 ) ,如果直接使用7x7卷积核,其参数总量为 49xC2 49xC^249xC 2,这里 C 指的是输入和输出的通道数。很明显,27xC2 27xC^227xC 2小于49xC2 49xC^249xC 2,即减少了参数;而且3x3卷积核有利于更好地保持图像性质。


具体而言,VGGNet 用2个3x3卷积核可以来代替5*5卷积核,如下图所示,5x5卷积看做一个小的全连接网络在5x5区域滑动(最底层5x5),我们可以先用一个3x3的卷积滤波器卷积(中间层),然后再用一个全连接层连接这个3x3卷积输出(顶层,顶层就等价于5x5的卷积了),这个全连接层我们也可以看做一个3x3卷积层。这样我们就可以用两个3x3卷积级联(叠加)起来代替一个 5x5卷积。


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


VGG增加网络的深度能够在一定程度上影响网络最终的性能,使错误率大幅下降,同时拓展性又很强,迁移到其它图片数据上的泛化性也非常好。VGGNet论文中分别使用了A、A-LRN、B、C、D、E6种网络结构进行测试,如下图:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


小波仔以网络结构D(VGG16)为例,介绍其处理过程,请对比上面的表格和开始的架构图,以及下面的图:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


A、A-LRN、B、C、D、E这6种网络结构的深度虽然从11层增加至19层,但参数量变化不大,这是由于基本上都是采用了小卷积核(3x3,只有9个参数),这6种结构的参数数量(百万级)并未发生太大变化,这是因为在网络中,参数主要集中在全连接层。经作者对A、A-LRN、B、C、D、E这6种网络结构进行单尺度的评估,错误率结果如下:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


我们在AlexNet中就已经讲过,LRN可能不是那么有用,在上表VGG也证实这一点。VGG也证实,随着深度增加,分类性能逐渐提高(A、B、C、D、E。同时我们也发现,多个小卷积核比单个大卷积核性能好。


  • VGG16的特点总结:


1、通过增加深度能有效地提升性能;

2、最佳模型:VGG16,从头到尾只有3x3卷积与2x2池化,简洁优美;

3、卷积可代替全连接,可适应各种尺寸的图片

4、池化核变小,VGG中的池化核是2x2,stride为2,Alexnet池化核是3x3,步长为2


然而VGG并不是完美的,稍加计算我们就会发现,VGG、AlexNet的绝大多数参数都集中于最后几个全连接层上,然而全连接层这玩意不仅线性强,参数多,还容易过拟合。我们同时还要问一句,模型还可以更深么?


GoogleNet / InceptionNet


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


从前面的讨论可以看到,VGG16这类模型仍然还有很多不够完美的地方。在VGG之前就已经有一篇文章《Provable Bounds for Learning Some Deep Representations》尝试阐述了sparsely connected architectures,即“稀疏连接结构”,从本质上提高网络性能. 传统的提高网络精度的方法,也就是扩大网络规模或增大训练数据集,可能并不是一个很好的方法,这带来两个问题:一是网络参数量的急剧增加会导致网络陷入过拟合,尤其是对小数据集而言;二是消耗巨大的计算资源。


而想从本质上提高网络性能,就得用“稀疏连接结构”。


这个观点有两方面解释:一方面,现实中的生物神经网络连接本身就是稀疏的;另一方面,Arora等人证明,对于大规模的稀疏网络,可通过分析前一层激活值的相关统计数据和对高度相关的输出神经元聚类来逐层构建最优的网络结构。这种方法可以在不损失性能的前提下降低网络参数量。


实际上,传统的网络基本都使用了随机稀疏连接。但是,计算机对非均匀稀疏数据的计算非常低效,所以在AlexNet中又重新使用了全连接层,目的是就为了更好地进行并行运算。


那么问题来了,是不是有一种方法既能够保持网络结构的稀疏性,并且充分利用密集矩阵的高效计算?还真有。大量文献表明对稀疏矩阵进行聚类为密集矩阵可以提高性能。Inception module就是基于这种思想提出来的。


下图为Google提出的Inception module的基本结构:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


该结构采用了四个分支,每个分支分别由1x1卷积、3x3卷积、5x5卷积和3x3max pooling组成,既增加了网络的宽度,也增加了网络对不同尺度的适用性。四个分支输出后在通道维度上进行叠加,作为下一层的输入。四个分支输出的feature map的尺寸可由padding的大小进行控制,以保证它们的特征维度相同(不考虑通道数)。原来造神经网络,都是一条线下来,我们可以回想一下AlexNet、VGG等著名网络,而IA是“分叉-汇聚”型网络,也就是说在一层网络中存在多个不同尺度的kernels,卷积完毕后再汇聚。


但是,3x3和5x5卷积依然会带来很大的计算量。假如branch1x1、branch3x3、branch5x5都有256个kernels,加上branch_pool的kernels(假定为256),经过tf.concat操作,最终的kernels是256×4=1024个kernels!受Network in Network的启发,作者使用1x1卷积对特征图厚度进行降维,这就是Inception v1的网络结构,如下:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


那么加进去的1×1卷积的作用是什么呢?主要是两个:


  1. 实现跨通道的交互和信息整合

  2. 进行卷积核通道数的降维和升维


对于单通道的feature map和单个卷积核之间的卷积来说,CNN里的卷积大都是多通道的feature map和多通道的卷积核之间的操作(输入的多通道的feature map和一组卷积核做卷积求和得到一个输出的feature map),如果使用1x1的卷积核,这个操作实现的就是多个feature map的线性组合,可以实现feature map在通道个数上的变化。接在普通的卷积层的后面,配合激活函数,就可以实现network in network的结构了。


对于每一个Inception模块(如上图a,b),原始模块是a,b图中是加入了1×1卷积进行降维的。虽然a图的卷积核都比较小,但是当输入和输出的通道数很大时,乘起来也会使得卷积核参数变的很大,而b图加入1×1卷积后可以降低输入的通道数,卷积核参数、运算复杂度也就跟着降下来了。


以GoogLeNet的3a模块为例,输入的feature map是28×28×192,3a模块中1×1卷积通道为64,3×3卷积通道为128,5×5卷积通道为32,如果是a图结构,那么卷积核参数为1×1×192×64+3×3×192×128+5×5×192×32,而b图对3×3和5×5卷积层前分别加入了通道数为96和16的1×1卷积层,这样卷积核参数就变成了1×1×192×64+(1×1×192×96+3×3×96×128)+(1×1×192×16+5×5×16×32),参数大约减少到原来的三分之一。


同时在并行pooling层后面加入1×1卷积层后也可以降低输出的feature map数量,a图pooling后feature map是不变的,再加卷积层得到的feature map,会使输出的feature map扩大到416,如果每个模块都这样,网络的输出会越来越大。而b图在pooling后面加了通道为32的1×1卷积,使得输出的feature map数降到了256。GoogLeNet利用1×1的卷积降维后,得到了更为紧凑的网络结构,虽然总共有22层,但是参数数量却只是8层的AlexNet的十二分之一(当然也有很大一部分原因是去掉了全连接层)。


基于Inception的GoogLeNet网络结构如下:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


GoogLeNet一共包含22层,网络参数量只有Alexnet的1/12。但是随着网络层数的加深,梯度弥散的问题依然存在,所以作者在中间层加入两个辅助的softmax,以此增加反向传播的梯度大小,同时也起到了正则化的作用。在计算网络损失的时候,中间的辅助softmax loss会乘以一个权重(0.3)加入到最后层的loss值中。在预测时,则忽略中间softmax层的输出。GoogLeNet也可以看做Hebbian Principle的应用:进入第一个inception前,feature map为 56x56,经过两个inception后,缩小为28x28,经过7个inception后变成14x14,经过9个inception后为7x7。最后通过7x7的average pool变成1x1。


ResNet


根据通用逼近定理,给定足够的容量,我们知道具有单层的前馈网络足以表示任何功能。但是,该层可能很庞大,并且网络倾向于过度拟合数据。因此,研究社区有一个共同的趋势,那就是我们的网络架构需要更深入地研究。


自从AlexNet以来,最先进的CNN架构越来越深入。虽然AlexNet只有5个卷积层,但VGG网络和GoogleNet(也代号为Inception_v1)分别有19和22层。


但是,仅通过将层堆叠在一起,无法增加网络深度。由于臭名昭著的消失的梯度问题,难以训练深层网络-随着梯度向后传播到较早的层,重复相乘可能会使梯度无限小。结果,随着网络的不断深入,其性能将达到饱和甚至开始迅速下降。


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化

图:网络深度的增加导致性能下降


在ResNet之前,有几种方法可以解决消失的梯度问题,例如,Inception_v1在中间层添加了辅助损失作为额外的监督,但似乎没有一种方法能够一劳永逸地解决该问题。


ResNet的核心思想是引入一个跳过一层或多层的所谓“身份快捷方式连接”,如下图所示:


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


ResNet的作者认为,堆叠层不应降低网络性能,因为我们可以简单地在当前网络上堆叠身份映射(不执行任何操作的层),并且最终的架构将执行相同的操作。这表明,较深的模型不应产生比其较浅的模型更高的训练误差。他们提出让堆叠的层(stacked layers)拟合残差映射 residual mapping比让它们直接拟合desired underlaying mapping更容易。上面的residual block允许它精确地做到这一点。


实际上,ResNet并不是第一个使用shortcut connections的,Highway Network引入了 gated shortcut connections。这些参数化的门控制允许多少信息流过shortcut connection。在长期短期记忆(LSTM)单元中可以找到类似的想法,在该单元中,有一个参数化的“忘记门”,该门控制着多少信息将流向下一个时间步。因此,可以将ResNet视为Highway Network的特例。


但是,实验表明,Highway Network的性能并不比ResNet好,这很奇怪,因为Highway Network的解决方案空间包含ResNet,因此其性能至少应与ResNet一样好。这表明保持这些“gradient highways”的通行性比寻求更大的solution space更为重要。


根据这种直觉,ResNet的作者改进了residual block并提出了一个预激活的residual block,其中gradients 可以无阻碍地通过shortcut connections流到任何其他较早的层。实际上,使用ResNet中的原始residual block,训练1202层的ResNet会比110层的ResNet表现差。


预激活的residual block的作者通过实验证明,他们现在可以训练1001层深的ResNet来胜过其较浅的ResNet。由于其出色的结果,ResNet迅速成为各种计算机视觉任务中最受欢迎的体系结构之一。


InceptionNet V4: Inception-ResNet


受ResNet性能的启发,InceptionNet 提出了一种混合模型。Inception ResNet有两个子版本,即v1和v2。在讨论主要功能之前,让我们看一下这两个子版本之间的细微差异。


  1. Inception-ResNet v1的计算成本与Inception v3相似。

  2. Inception-ResNet v2的计算成本与Inception v4相似。

  3. 它们具有不同的主干

  4. 两个子版本对于模块A,B,C和reduction blocks具有相同的结构。唯一的区别是超参数设置。


总体而言,就是引入 residual connections ,这些 residual connections 将 inception module的卷积运算的输出添加到输入中。为了进行residual addition 运算,卷积后的输入和输出必须具有相同的尺寸。因此,我们在原始卷积之后使用1x1卷积,以匹配深度大小(卷积后深度增加)。



从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


(从左起)Inception modulesA(尺寸从35x35减小到17x17)和Inception modulesB(尺寸从17x17减小到8x8)。


为有利于residual connections,主要inception modules内部的pooling 操作已替换。但是,您仍然可以在reduction blocks中找到这些操作。reduction blocks A与Inception v4 (本位没有介绍,但是和 Inception-ResNet同一篇论文)相同。


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


如果过滤器的数量超过1000,则具有较深单元的网络会导致网络“死亡”。因此,为了提高稳定性,作者将残余激活值的范围定为0.1到0.3。


从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化


求和后,原始论文没有使用BatchNorm在单个GPU上训练模型(以在单个GPU上拟合整个模型)。结果发现,Inception-ResNet模型能够在较低的时期实现较高的精度。


Inception v4和Inception-ResNet的最终网络布局如下:



顶部图像是Inception v4的布局。底部图像是Inception-ResNet的布局。(来源:[Inception v4](https://arxiv.org/pdf/1602.07261.pdf))


Reference: GoogLeNet (Inception v1)— Winner of ILSVRC 2014 (Image Classification


    在看”,我都认真当成了AI

以上是关于现代卷积神经网络(GoogleNet),并使用GoogleNet进行实战CIFAR10分类的主要内容,如果未能解决你的问题,请参考以下文章

从LeNet到GoogLeNet:逐层详解,看卷积神经网络的进化

[人工智能-深度学习-35]:卷积神经网络CNN - 常见分类网络- GoogLeNet Incepetion网络架构分析与详解

Inception系列

卷积神经网络Inception Net

pytorch学习笔记:卷积神经网络CNN(进阶篇)

深度学习方法:卷积神经网络CNN经典模型整理Lenet,Alexnet,Googlenet,VGG,Deep Residual Learning