图像分类网络-经典CNN网络简介

在CNN网络结构的演化上,出现过许多优秀的CNN网络,CNN的经典结构始于1998年的LeNet,成于2012年历史性的AlexNet,从此大盛于图像相关领域,主要包括:

发展历史:Lenet --> Alexnet --> ZFnet --> VGG --> NIN --> GoogLeNet --> ResNet --> DenseNet -->ResNeXt ---> EfficientNet

  1. LeNet,1998年
  2. AlexNet,2012年
  3. ZF-net,2013年
  4. GoogleNet,2014年
  5. VGG,2014年
  6. ResNet,2015年

图像分类网络-经典CNN网络简介_第1张图片

图像分类网络-经典CNN网络简介_第2张图片

 

目录

 

Lenet(1988):

Lenet-5网络结构详解

AlexNet(2012):

Alexnet网络结构详解

ZF-Net

ZF-Net 网络解析

VGG-Net(2014):

VGG16网络结构详解

NIN网络-Network In Network

NIN网络结构详解

GoogLeNet:

GoogLeNet 网络结构解析

GoogLeNet Inception V2

GoogLeNet Inception V3

GoogLeNet Inception V4

ResNet(2015)(里程碑式创新):

DenseNet

总结

REFERENCE


Lenet(1988):

广为流传的LeNet诞生于1998年,网络结构比较完整,包括卷积层、pooling层、全连接层,这些都是现代CNN网络的基本组件,其被认为是CNN的开端。主要用于识别10个手写邮政编码数字,5*5卷积核,stride=1,应用最大池化。

Lenet-5网络结构详解

图像分类网络-经典CNN网络简介_第3张图片

闪光点:LeCun在1998年提出,定义了CNN的基本组件,是CNN的鼻祖。
自那时起,CNN的最基本的架构就定下来了:卷积层、池化层、全连接层。
LetNet-5 是一种入门级的神经网络模型,是一个简单的卷积神经网络,可以用来做手写体识别
含输入层总共8层网络,分别为:输入层(INPUT)、卷积层(Convolutions,C1)、池化层(Subsampling,S2)、C3、 S4、 C5、全连接层(F6)、输出层
LeNet-5跟现有的conv->pool->ReLU的套路不同,它使用的方式是conv1->pool->conv2->pool2再接全连接层,但是不变的是,卷积层后紧接池化层的模式依旧不变

经过卷积后输出维度大小的公式:
N: 输入的维度、F:卷积核大小、stride: 步长、pad: 扩充边缘
output = (N + 2*pad -F)/stride+1

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)  
        self.conv2 = nn.Conv2d(6, 16, 5)  
        self.fc1   = nn.Linear(16*5*5, 120)  
        self.fc2   = nn.Linear(120, 84)
        self.fc3   = nn.Linear(84, 10)

    def forward(self, x): 
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2)) 
        x = F.max_pool2d(F.relu(self.conv2(x)), 2) 
        x = x.view(x.size()[0], -1)  #展开成一维的,x.size()[0] --> batch_size
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)        
        return x

def show_lenet_strcucture():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    #net = NiN(10).to(device) # 10 分类
    net = LeNet().to(device) # 10 分类
    print("net struct \n", net)
    summary(net, (1, 32, 32)) #输入是灰度图


show_lenet_strcucture()

AlexNet(2012):

2012年Geoffrey和他学生Alex在ImageNet的竞赛中,刷新了image classification的记录,一举奠定了deep learning 在计算机视觉中的地位。这次竞赛中Alex所用的结构就被称为作为AlexNet。

Alexnet网络结构详解

图像分类网络-经典CNN网络简介_第4张图片

图像分类网络-经典CNN网络简介_第5张图片

上图 Alexnet的整个网络结构是由5个卷积层和3个全连接层组成的,深度总共8层。AlexNet针对的是1000类的分类问题,输入图片规定是256×256的三通道彩色图片,为了增强模型的泛化能力,避免过拟合,作者使用了随机裁剪的思路对原来256×256的图像进行随机裁剪,得到尺寸为3×224×224的图像,输入到网络训练。
注意:输入Input的图像规格: 224X224X3(RGB图像),实际上会经过预处理变为227X227X3。因为 (224-11)/4+1 ≠ 55,所以这里是做了padding再做卷积的,即先padiing图像至227×227,再做卷积(227-11)/4+1 = 55

AlexNet将CNN用到了更深更宽的网络中,其效果分类的精度更高相比于以前的LeNet 用到的trick:

  1. 数据增广技巧来增加模型泛化能力 256×256×3 -->随机裁剪224×224×3 -->进入网络。
  2. 非线性激活函数ReLU:AlexNet使用ReLU代替了Sigmoid,其能更快的训练,同时解决sigmoid在训练较深的网络中出现的梯度消失。
  3. 防止过拟合的方法:Dropout,Data augmentation等。Dropout随机失活,随机忽略一些神经元,以避免过拟合。
  4. 以前的CNN中普遍使用平均池化层,AlexNet全部使用最大池化层,避免了平均池化层的模糊化的效果,并且步长比池化的核的尺寸小,这样池化层的输出之间有重叠,提升了特征的丰富性。
  5. 提出了LRN层,局部响应归一化增加了泛化能力,做了平滑处理,提高了1%~2%的识别率, LRN 对局部神经元的活动创建竞争机制,使得其中响应比较大的值变得相对更大,并抑制其他反馈较小的神经元。
  6. 使用多个GPU,LRN归一化层。其主要的优势有:网络扩大(5个卷积层+3个全连接层+1个softmax层);解决过拟合问题(dropout,data augmentation,LRN);多GPU加速计算。

ZF-Net

ZFNet是2013ImageNet分类任务的冠军,其网络结构没什么改进,只是调了调参,性能较Alex提升了不少。

ZF-Net 网络解析

ZF-Net只是将AlexNet第一层卷积核由11变成7步长由4变为2,第3,4,5卷积层转变为384,384,256。这一年的ImageNet还是比较平静的一届,其冠军ZF-Net的名堂也没其他届的经典网络架构响亮。

 

图像分类网络-经典CNN网络简介_第6张图片

import torch
import torch.nn as nn
import torch.nn.functional as F
from torchsummary import summary

class ZFNet(nn.Module):
    def __init__(self):
        super(ZFNet, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=96,
                               kernel_size=7, stride=2, padding=1)  # [-1, 96, 110, 110]
        self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)  # [-1, 96, 55, 55]

        self.conv2 = nn.Conv2d(in_channels=96, out_channels=256,
                               kernel_size=5, stride=2)  # [-1, 256, 26, 26] 
        self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)  #[-1, 256, 13, 13]

        self.conv3 = nn.Conv2d(in_channels=256, out_channels=384,
                                    kernel_size=3, stride=1, padding=1)#[-1, 384, 13, 13]

        self.conv4 = nn.Conv2d(384, 384, kernel_size=3, stride=1, padding=1)#[-1, 384, 13, 13]

        self.conv5 = nn.Conv2d(384, 256, kernel_size=3, stride=1, padding=1)#[-1, 256, 13, 13]
        self.maxpool3 = nn.MaxPool2d(kernel_size=3, stride=2)#[-1, 256, 6, 6]

        self.fc1 = nn.Linear(in_features=6 * 6 * 256, out_features=4096)#[-1, 4096]
        self.fc2 = nn.Linear(in_features=4096, out_features=4096)#[-1, 4096]
        self.fc3 = nn.Linear(in_features=4096, out_features=1000)#[-1, 1000]

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = self.maxpool1(x)

        x = F.relu(self.conv2(x))
        x = self.maxpool2(x)

        x = F.relu(self.conv3(x))
        x = F.relu(self.conv4(x))

        x = self.maxpool3(F.relu(self.conv5(x)))

        x = x.view(-1, 6 * 6 * 256)

        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

def show_zfnet_strcucture():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    net = ZFNet().to(device) # 1000 分类
    print("net struct \n", net)
    summary(net, (3, 224, 224)) #输入为224 X 224

show_zfnet_strcucture()

VGG-Net(2014):

VGG-Net来自 Andrew Zisserman 教授的组 (Oxford),在2014年的 ILSVRC localization and classification 两个问题上分别取得了第一名和第二名,其不同于AlexNet的地方是:VGG-Net使用更多的层,通常有16/19层,而AlexNet只有8层。同时,VGG-Net的所有 convolutional layer 使用同样大小的 convolutional filter,大小为 3 x 3。总体来说,其由5层卷积层、3层全连接层、softmax输出层构成。用了3*3的卷积核(c模型用了1*1的卷积核,但一般都只用d、e模型,1*1不必管它),步长stride=1,padding=1,max池化,pooling窗口为2*2,pooling步长为2

VGG16网络结构详解

VGG系列网络使用的是3x3的小卷积核,这是有中心和上下左右的最小单元(中心+八邻域);两个3x3的卷积层连在一起可视为5x5的filter,三个连在一起可视为一个7x7的。和大卷积核相比:(1)可以减少参数(2)进行了更多的非线性映射,增加网络的拟合、表达能力。

图像分类网络-经典CNN网络简介_第7张图片

 

 

VGG16模型(2+2+3+3+3个卷积层,3个全连接层):

VGG19模型:

 

图像分类网络-经典CNN网络简介_第8张图片

不同模型参数数量

VGG16 是在 AlexNet 网络上每一层进行了改造,5个 stage 对应 AlexNet 中的5层卷积,3层全连接仍然不变,图片输入的大小还是沿用了 224x224x3。结构上的变化:

  1. 共16层(不包括Max pooling层和softmax层)
  2. 所有的卷积核都使用3*3的大小,max pooling池化核都使用大小为2*2
  3. 采用步长stride=2,padding=0的Max pooling
  4. 卷积层深度依次为64 -> 128 -> 256 -> 512 ->512

VGGNet改进点总结:

  1. 使用了更小的3*3卷积核,和更深的网络。两个3*3卷积核的堆叠相对于5*5卷积核的视野,三个3*3卷积核的堆叠相当于7*7卷积核的视野。 (好处:有更少的参数 3个堆叠的3*3结构只有7*7结构参数
    数量的(3*3*3)/(7*7)=55% ) 另一方面拥有更多的非线性变换,增加了CNN对特征的学习能力。
  2. VGG采用的是一种Pre-training的方式,先训练级别简单(层数较浅)的VGGNet的A级网络,然后使用A网络的权重来初始化后面的复杂模型,加快训练的收敛速度。 
  3. 采用了Multi-Scale的方法来训练和预测。可以增加训练的数据量,防止模型过拟合,提升预测准确率 
# -*- coding:utf-8 -*-
import torch
from torchsummary import summary
#pip install torchsummary
from torchvision import models

def show_nets_strcucture():
    #[resnet, alexnet, vgg, squeezenet, densenet]标准输入为 224*224*3, inception_v3 标准输入为 299*299*3
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    # pretrained=True 会下载在  C:\Users\F7687778/.cache\torch\checkpoints\alexnet-owt-4df8aa71.pth

    vgg16 = models.vgg16(pretrained=False).to(device)# 查看网络结构其实设置 pretrained=False 就可以了
    print("net struct \n", vgg16)

    summary(vgg16, (3, 224, 224))

show_nets_strcucture()

'''
net struct 
 VGG(
  (features): Sequential(
    (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (1): ReLU(inplace=True)
    (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (3): ReLU(inplace=True)
    (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (6): ReLU(inplace=True)
    (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (8): ReLU(inplace=True)
    (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (11): ReLU(inplace=True)
    (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (13): ReLU(inplace=True)
    (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (15): ReLU(inplace=True)
    (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (18): ReLU(inplace=True)
    (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (20): ReLU(inplace=True)
    (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (22): ReLU(inplace=True)
    (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
    (24): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (25): ReLU(inplace=True)
    (26): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (27): ReLU(inplace=True)
    (28): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
    (29): ReLU(inplace=True)
    (30): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  )
  (avgpool): AdaptiveAvgPool2d(output_size=(7, 7))
  #ps:不管之前的特征图尺寸为多少,只要设置为(7, 7),那么最终特征图大小都为(7, 7)
  (classifier): Sequential(
    (0): Linear(in_features=25088, out_features=4096, bias=True)
    (1): ReLU(inplace=True)
    (2): Dropout(p=0.5, inplace=False)
    (3): Linear(in_features=4096, out_features=4096, bias=True)
    (4): ReLU(inplace=True)
    (5): Dropout(p=0.5, inplace=False)
    (6): Linear(in_features=4096, out_features=1000, bias=True)
  )
)
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 224, 224]           1,792
              ReLU-2         [-1, 64, 224, 224]               0
            Conv2d-3         [-1, 64, 224, 224]          36,928
              ReLU-4         [-1, 64, 224, 224]               0
         MaxPool2d-5         [-1, 64, 112, 112]               0

            Conv2d-6        [-1, 128, 112, 112]          73,856
              ReLU-7        [-1, 128, 112, 112]               0
            Conv2d-8        [-1, 128, 112, 112]         147,584
              ReLU-9        [-1, 128, 112, 112]               0
        MaxPool2d-10          [-1, 128, 56, 56]               0

           Conv2d-11          [-1, 256, 56, 56]         295,168
             ReLU-12          [-1, 256, 56, 56]               0
           Conv2d-13          [-1, 256, 56, 56]         590,080
             ReLU-14          [-1, 256, 56, 56]               0
           Conv2d-15          [-1, 256, 56, 56]         590,080
             ReLU-16          [-1, 256, 56, 56]               0
        MaxPool2d-17          [-1, 256, 28, 28]               0

           Conv2d-18          [-1, 512, 28, 28]       1,180,160
             ReLU-19          [-1, 512, 28, 28]               0
           Conv2d-20          [-1, 512, 28, 28]       2,359,808
             ReLU-21          [-1, 512, 28, 28]               0
           Conv2d-22          [-1, 512, 28, 28]       2,359,808
             ReLU-23          [-1, 512, 28, 28]               0
        MaxPool2d-24          [-1, 512, 14, 14]               0

           Conv2d-25          [-1, 512, 14, 14]       2,359,808
             ReLU-26          [-1, 512, 14, 14]               0
           Conv2d-27          [-1, 512, 14, 14]       2,359,808
             ReLU-28          [-1, 512, 14, 14]               0
           Conv2d-29          [-1, 512, 14, 14]       2,359,808
             ReLU-30          [-1, 512, 14, 14]               0
        MaxPool2d-31            [-1, 512, 7, 7]               0

AdaptiveAvgPool2d-32            [-1, 512, 7, 7]               0
           Linear-33                 [-1, 4096]     102,764,544
             ReLU-34                 [-1, 4096]               0
          Dropout-35                 [-1, 4096]               0
           Linear-36                 [-1, 4096]      16,781,312
             ReLU-37                 [-1, 4096]               0
          Dropout-38                 [-1, 4096]               0
           Linear-39                 [-1, 1000]       4,097,000
================================================================
'''

NIN网络-Network In Network

Network In Network 是发表于 2014 年 ICLR 的一篇 paper。当前被引了 3298 次。这篇文章采用较少参数就取得了 Alexnet 的效果,Alexnet 参数大小为 230M,而 Network In Network 仅为 29M,这篇 paper 主要两大亮点:mlpconv (multilayer perceptron,MLP,多层感知机) 和Global Average Pooling(全局平均池化)。

NIN网络结构详解

图像分类网络-经典CNN网络简介_第9张图片

第一个卷积核是11x11x3x96,因此在一个patch块上卷积的输出是1x1x96的feature map(一个96维的向量)。在其后又接了一个MLP层,输出仍然是96。因此这个MLP层就等价于一个1 x 1 的卷积层

mlp conv另一种理解方式:在原来每一层输出后加一个 与通道数量相同1 x 1 的卷积层。mlp conv层相当于先进行一次普通的卷积(比如3x3),紧跟再进行一次1x1的卷积。见上图MLPCONV与CNN对比。作用:
 1. 其实相当于在通道之间做了特征融合。
 2. 每一层卷积之后加一个激活函数,比原结构多了一层激活函数,增加了结构的非线性表达能力。

Global Average Pooling
整个featuremap平均池化结果作为softmax 输入,相较于Alexnet全连接的优点:
 1. 减少参数量(1000x1000+1000x6x6),全局平均池化没有参数,从而减轻过拟合。
 2. 求和平均综合了整个featuremap的所有信息,全局平均池可以对空间信息进行汇总,因此对输入的空间转换具有更强的鲁棒性。
 3. 不限输入图片的大小。

#NiN 网络由三个 mlpconv 块组成,最后一个 mlpconv 后使用全局平均池化。
#用 Pytorch 代码实现如下:假设输入是 32×32×3 (即 3 通道,32 高宽大小的图片)
import torch
import torch.nn as nn

from torchsummary import summary

class NiN(nn.Module):
    def __init__(self, num_classes):
        super(NiN, self).__init__()
        self.num_classes = num_classes
        self.classifier = nn.Sequential(
                nn.Conv2d(3, 192, kernel_size=5, stride=1, padding=2),
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 160, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),
                nn.Conv2d(160,  96, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),
                nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
                nn.Dropout(0.5),

                nn.Conv2d(96, 192, kernel_size=5, stride=1, padding=2),
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),
                nn.AvgPool2d(kernel_size=3, stride=2, padding=1),
                nn.Dropout(0.5),

                nn.Conv2d(192, 192, kernel_size=3, stride=1, padding=1),
                nn.ReLU(inplace=True),
                nn.Conv2d(192, 192, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),
                nn.Conv2d(192,  10, kernel_size=1, stride=1, padding=0),
                nn.ReLU(inplace=True),

                #nn.AvgPool2d(kernel_size=8, stride=1, padding=0),
                #相比 nn.AvgPool2d() 多了个自适应,自适应就代表了使用更简单方便
                nn.AdaptiveAvgPool2d(output_size=(1,1)),

                )

    def forward(self, x):
        x = self.classifier(x)
        logits = x.view(x.size(0), self.num_classes)
        probas = torch.softmax(logits, dim=1)
        return logits, probas


def show_nin_strcucture():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    net = NiN(10).to(device) # 10 分类
    print("net struct \n", net)
    summary(net, (3, 32, 32)) #最后 AdaptiveAvgPool2d-23  --> [-1, 10, 1, 1]


show_nin_strcucture()

GoogLeNet:

提出的Inception结构是主要的创新点,这是(Network In Network)的结构,即原来的结点也是一个网络。其使用使得之后整个网络结构的宽度和深度都可扩大,能够带来2-3倍的性能提升。

GoogLeNet 网络结构解析

神经网络除了纵向的扩展,是否能进行横向的拓展? 提升网络性能最直接的办法就是增加网络深度和宽度,深度指网络层次数量、宽度指神经元数量。

GoogLeNet在2014的ImageNet分类任务上击败了VGG-Nets夺得冠军,GoogLeNet跟AlexNet,VGG-Nets这种单纯依靠加深网络结构进而改进网络性能的思路不一样,它另辟幽径,在加深网络的同时(22层),也在网络结构上做了创新,引入Inception结构代替了单纯的卷积+激活的传统操作(这思路最早由Network in Network提出)

闪光点:

  • 引入Inception结构
  • 中间层的辅助LOSS单元
  • 后面的全连接层全部替换为简单的全局平均pooling

GoogLeNet中的基础卷积块叫作Inception块,得名于同名电影《盗梦空间》 (Inception)

通过设计一个稀疏网络结构,但是能够产生稠密的数据,既能增加神经网络表现,又能保证计算资源的使用效率。一方面增加了网络的宽度,另一方面也增加了网络对尺度的适应性。

基于Inception构建了GoogLeNet的网络结构如下(共22层)

图像分类网络-经典CNN网络简介_第10张图片

图像分类网络-经典CNN网络简介_第11张图片

 

图像分类网络-经典CNN网络简介_第12张图片

inception的结构,一分四,然后做一些不同大小的卷积,之后再堆叠feature map。残差网络做了相加的操作,而inception做了串联的操作。

inception v1

对上图做以下说明: 

  1. 采用不同大小的卷积核意味着不同大小的感受野,最后拼接意味着不同尺度特征的融合; 
  2. 之所以卷积核大小采用1、3和5,主要是为了方便对齐。设定卷积步长stride=1之后,只要分别设定pad=0、1、2,那么卷积之后便可以得到相同维度的特征,方便最后拼接在一起 out =((N + 2p -L)/s) +1
  3. 文章说很多地方都表明pooling挺有效,所以Inception里面也嵌入了。 
  4. 网络越到后面,特征越抽象,而且每个特征所涉及的感受野也更大了,因此随着层数的增加,3x3和5x5卷积的比例也要增加,以便能尽量保留信息。

但是,使用5x5的卷积核仍然会带来巨大的计算量。 文章借鉴NIN2,采用1x1卷积核来进行降维。 

1*1卷积核的主要作用:(1*1卷积核并不改变特征图的二维维度,但是可以重新定义特征图的深度)

  1. 降维
  2. 使网络更深  
  3. 增加RELU等非线性(图中每一个a*a卷积都是conv+BN+relu的操作)。

例如:上一层的输出为100x100x128,经过具有256个输出的5x5卷积层之后(stride=1,pad=2),输出数据为100x100x256。其中,卷积层的参数为128x5x5x256。假如上一层输出先经过具有32个输出的
1x1卷积层,再经过具有256个输出的5x5卷积层,那么最终的输出数据仍为为100x100x256,但卷积参数量已经减少为128x1x1x32 + 32x5x5x256,大约减少了4倍。

图像分类网络-经典CNN网络简介_第13张图片

图像分类网络-经典CNN网络简介_第14张图片

Googlenet的核心思想是inception,通过不垂直堆砌层的方法得到更深的网络(我的理解是变宽且视野范围种类多,vgg及resnet让网络变深,inception让网络变宽,在同一层整合不同感受野的信息,并让模型自己选择卷积核的大小)

对上图说明如下:

  1. GoogLeNet采用了模块化的结构(Inception结构),方便增添和修改;
  2. 网络最后采用了average pooling(平均池化)来代替全连接层,该想法来自NIN(Network in Network)事实证明这样可以将准确率提高0.6%。但是,实际在最后还是加了一个全连接层,主要是为了方便对输出进行灵活调整;
  3. 虽然移除了全连接,但是网络中依然使用了Dropout ; 
  4. 为了避免梯度消失,网络额外增加了2个辅助的softmax用于向前传导梯度(辅助分类器)。辅助分类器是将中间某一层的输出用作分类,并按一个较小的权重(0.3)加到最终分类结果中,这样相当于做了模型融合,同时给网络增加了反向传播的梯度信号,也提供了额外的正则化,对于整个网络的训练很有裨益。而在实际测试的时候,这两个额外的softmax会被去掉。

GoogLeNet Inception V2

Inception V2版本的解决方案就是修改Inception的内部计算逻辑,提出了比较特殊的“卷积”计算结构

图像分类网络-经典CNN网络简介_第15张图片

图像分类网络-经典CNN网络简介_第16张图片

图像分类网络-经典CNN网络简介_第17张图片

先池化再作Inception卷积,或者先作Inception卷积再作池化。但是方法一(左图)先作pooling(池化)会导致特征表示遇到瓶颈(特征缺失),方法二(右图)是正常的缩小,但计算量很大。为了同时保持特征表示且降低计算量,将网络结构改为下图,使用两个并行化的模块来降低计算量(卷积、池化并行执行,再进行合并

图像分类网络-经典CNN网络简介_第18张图片

使用Inception V2作改进版的GoogLeNet,网络结构图如下:

图像分类网络-经典CNN网络简介_第19张图片

注:上表中的Figure 5指没有进化的Inception,Figure 6是指小卷积版的Inception(用3x3卷积核代替5x5卷积核),Figure 7是指不对称版的Inception(用1xn、nx1卷积核代替nxn卷积核)。

V2优点:
1. 学习VGGNet的特点,用两个3*3卷积代替5*5卷积,降低参数量。
2. 提出了著名的Batch Normalization(BN)方法,BN算法是一个正则化方法,可以提高大网络的收敛速度。BN算法:就是对输入层信息分布标准化处理,使得规范化为N(0,1)的高斯分布,收敛速度大大提高。

GoogLeNet Inception V3

  1. RMSProp优化器

  2. Inception V3一个最重要的改进是分解(Factorization),将7x7分解成两个一维的卷积(1x7,7x1),3x3也是一样(1x3,3x1),这样的好处,既可以加速计算,又可以将1个卷积拆成2个卷积,使得网络深度进一步增加,增加了网络的非线性(每增加一层都要进行ReLU)。另外,网络输入从224x224变为了299x299。

GoogLeNet Inception V4

Inception V4研究了Inception模块与残差连接的结合ResNet结构大大地加深了网络深度,还极大地提升了训练速度,同时性能也有提升。Inception V4主要利用残差连接(Residual Connection)来改进V3结构,得到Inception-ResNet-v1,Inception-ResNet-v2,Inception-v4网络。

图像分类网络-经典CNN网络简介_第20张图片

V1提出inception结构,到v2添加了BN层,到V3使用分解卷积,到V4和残差网络结合。

ResNet(2015)(里程碑式创新)

2015年何恺明推出的ResNet在ISLVRC和COCO上横扫所有选手,获得冠军。ResNet在网络结构上做了大创新,而不再是简单的堆积层数,ResNet在卷积神经网络的新思路,绝对是深度学习发展历程上里程碑式的事件。

ResNet提出了一种减轻网络训练负担的残差学习框架,这种网络比以前使用过的网络本质上层次更深。其明确地将这层作为输入层相关的学习残差函数,而不是学习未知的函数。在ImageNet数据集用152 层(据说层数已经超过1000==)——比VGG网络深8倍的深度来评估残差网络,但它仍具有较低的复杂度。在2015年大规模视觉识别挑战赛分类任务中赢得了第一。

设计了“bottleneck”形式的block(有跨越几层的直连)用全局平均池化GAP代替全连层FC,解决全连接层参数冗余的问题,但FC的优势在于在迁移学习中可改善微调的效果。

图像分类网络-经典CNN网络简介_第21张图片

图像分类网络-经典CNN网络简介_第22张图片

图像分类网络-经典CNN网络简介_第23张图片

闪光点

  • 层数非常深,已经超过百层(梯度消失/梯度爆炸)
  • 引入残差单元来解决退化问题(Degradation):即准确率会先上升然后达到饱和,再持续加深网络则会导致准确率下降

随着网络深度增加,网络的准确度应该同步增加,当然要注意过拟合问题。但是网络深度增加的一个问题在于这些增加的层是参数更新的信号,因为梯度是从后向前传播的,增加网络深度后,比较靠前的层梯度会很小。这意味着这些层基本上学习停滞了,这就是梯度消失问题。

深度网络的第二个问题在于训练,当网络更深时意味着参数空间更大,优化问题变得更难,因此简单地去增加网络深度反而出现更高的训练误差,深层网络虽然收敛了,但网络却开始退化了,即增加网络层数却导致更大的误差,比如下图,一个56层的网络的性能却不如20层的性能好,这不是因为过拟合(训练集训练误差依然很高),这就是烦人的退化问题。残差网络ResNet设计一种残差模块让我们可以训练更深的网络。

图像分类网络-经典CNN网络简介_第24张图片

分析一下残差单元来理解ResNet的精髓:增加一个identity mapping(恒等映射),将原始所需学的函数H(x)转换成F(x)+x,然而优化F(x)会比H(x) 简单很多

图像分类网络-经典CNN网络简介_第25张图片

图像分类网络-经典CNN网络简介_第26张图片

从上图可以看出,数据经过了两条路线,一条是常规路线,另一条则是捷径(shortcut),直接实现单位映射的直接连接的路线,这有点类似与电路中的“短路”。通过实验,这种带有shortcut的结构确实可以很好地应对退化问题。我们把网络中的一个模块的输入和输出关系看作是y=H(x),那么直接通过梯度方法求H(x)就会遇到上面提到的退化问题,如果使用了这种带shortcut的结构,那么可变参数部分的优化目标就不再是H(x),若用F(x)来代表需要优化的部分的话,则H(x)=F(x)+x,也就是F(x)=H(x)-x 因为在单位映射的假设中y=x就相当于观测值,所以F(x)就对应着残差,因而叫残差网络。

为啥要这样做,因为作者认为学习残差F(X)比直接学习H(X)简单!设想下,现在我们只需要去学习输出和输入的差值(H(x)-x 即残差), 不再是学习一个完整的输出H(x),优化起来简单很多。

考虑到x的维度与F(X)维度可能不匹配情况,需进行维度匹配。这里论文中采用两种方法解决这一问题

  1. zero_padding:对恒等层进行0填充的方式将维度补充完整。这种方法不会增加额外的参数
  2. projection:在恒等层采用1x1的卷积核来增加维度。这种方法会增加额外的参数

图像分类网络-经典CNN网络简介_第27张图片

图像分类网络-经典CNN网络简介_第28张图片

import torch
import torch.nn as nn
import torchvision
import numpy as np
from torchsummary import summary

print("PyTorch Version: ",torch.__version__)
print("Torchvision Version: ",torchvision.__version__)
#https://www.cnblogs.com/wzyuan/p/9880342.html

__all__ = ['ResNet50', 'ResNet101','ResNet152']

def Conv1(in_planes, places, stride=2):
    return nn.Sequential(
        nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,
                    stride=stride,padding=3, bias=False),
        nn.BatchNorm2d(places),
        nn.ReLU(inplace=True),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
    )

class Bottleneck(nn.Module):
    def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 4):
        super(Bottleneck,self).__init__()
        self.expansion = expansion
        self.downsampling = downsampling

        self.bottleneck = nn.Sequential(
            nn.Conv2d(in_channels=in_places,out_channels=places,kernel_size=1,stride=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, 
                    stride=stride, padding=1, bias=False),
            nn.BatchNorm2d(places),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=places, out_channels=places*self.expansion, 
                              kernel_size=1, stride=1, bias=False),
            nn.BatchNorm2d(places*self.expansion),
        )

        if self.downsampling: #输出与输入通道数都不一样 就要走这一步
            self.downsample = nn.Sequential(
                nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion,
                                  kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(places*self.expansion)
            )
        self.relu = nn.ReLU(inplace=True)
    def forward(self, x):
        residual = x
        out = self.bottleneck(x)

        if self.downsampling:
            residual = self.downsample(x)

        out += residual
        out = self.relu(out)
        return out

class ResNet(nn.Module):
    def __init__(self,blocks, num_classes=1000, expansion = 4):
        super(ResNet,self).__init__()
        self.expansion = expansion

        self.conv1 = Conv1(in_planes = 3, places= 64) # [-1, 64, 112, 112]

        self.layer1 = self.make_layer(in_places = 64, places= 64, block=blocks[0], stride=1)
        self.layer2 = self.make_layer(in_places = 256,places=128, block=blocks[1], stride=2)
        self.layer3 = self.make_layer(in_places=512,places=256, block=blocks[2], stride=2)
        self.layer4 = self.make_layer(in_places=1024,places=512, block=blocks[3], stride=2)

        self.avgpool = nn.AvgPool2d(7, stride=1)
        self.fc = nn.Linear(2048,num_classes)

        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.constant_(m.weight, 1)
                nn.init.constant_(m.bias, 0)

    def make_layer(self, in_places, places, block, stride):
        layers = []
        #每一层都有downsample,因为输出与输入通道数都不一样(每一层中的第一层 downsampling =True)
        layers.append(Bottleneck(in_places, places,stride, downsampling =True))
        for i in range(1, block):
            layers.append(Bottleneck(places*self.expansion, places))

        return nn.Sequential(*layers)


    def forward(self, x):
        x = self.conv1(x)

        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)

        x = self.avgpool(x)
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

def ResNet50():
    return ResNet([3, 4, 6, 3])

def ResNet101():
    return ResNet([3, 4, 23, 3])

def ResNet152():
    return ResNet([3, 8, 36, 3])

def show_resnet_strcucture():
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    net = ResNet50().to(device) # 1000 分类
    print("net struct \n", net)
    summary(net, (3, 224, 224))

show_resnet_strcucture()

#downsampling  resnet50,每一层都有downsample,因为输出与输入通道数都不一样

 

DenseNet

2017最佳论文DenseNet,主要还是和ResNet及Inception网络做对比,思想上有借鉴,但却是全新的结构,网络结构并不复杂,却非常有效,在CIFAR指标上全面超越ResNet。可以说DenseNet吸收了ResNet最精华的部分,并在此上做了更加创新的工作,使得网络性能进一步提升。

DenseNet是借鉴了ResNet,是ResNet的升级版,从上述ResNet可以看到,一般每个Block会有一个skip connect,而DenseNet会在每层conv间有一个skip connect

图像分类网络-经典CNN网络简介_第29张图片

闪光点:

  • 密集连接:缓解梯度消失问题,加强特征传播,极大的减少了参数量

需要明确一点,dense connectivity 仅仅是在一个dense block里的,不同dense block 之间是没有dense connectivity的

在同层深度下获得更好的收敛率,自然是有额外代价的。其代价之一,就是训练需要更多内存

图像分类网络-经典CNN网络简介_第30张图片

densenet就同时做了两件事情,一是将网络中的每一层都直接与其前面层相连,提高特征的利用率;二是把网络的每一层设计得很窄,也就是卷积的输出通道数通常很小,只有几十,该层学习非常少的特征图并与输入concat使用。

图像分类网络-经典CNN网络简介_第31张图片

import torch
import torch.nn as nn
import torch.nn.functional as F
from collections import OrderedDict

class _DenseLayer(nn.Sequential):
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate):
        super(_DenseLayer, self).__init__()
        self.add_module('norm1', nn.BatchNorm2d(num_input_features)),
        self.add_module('relu1', nn.ReLU(inplace=True)),
        self.add_module('conv1', nn.Conv2d(num_input_features, bn_size *
                                           growth_rate, kernel_size=1, stride=1, bias=False)),
        self.add_module('norm2', nn.BatchNorm2d(bn_size * growth_rate)),
        self.add_module('relu2', nn.ReLU(inplace=True)),
        self.add_module('conv2', nn.Conv2d(bn_size * growth_rate, growth_rate,
                                           kernel_size=3, stride=1, padding=1, bias=False)),
        self.drop_rate = drop_rate

    def forward(self, x):
        new_features = super(_DenseLayer, self).forward(x)
        if self.drop_rate > 0:
            new_features = F.dropout(new_features, p=self.drop_rate, training=self.training)
        return torch.cat([x, new_features], 1)


class _DenseBlock(nn.Sequential):
    def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate):
        super(_DenseBlock, self).__init__()
        for i in range(num_layers):
            layer = _DenseLayer(num_input_features + i * growth_rate, growth_rate, bn_size, drop_rate)
            self.add_module('denselayer%d' % (i + 1), layer)


class _Transition(nn.Sequential):
    def __init__(self, num_input_features, num_output_features):
        super(_Transition, self).__init__()
        self.add_module('norm', nn.BatchNorm2d(num_input_features))
        self.add_module('relu', nn.ReLU(inplace=True))
        self.add_module('conv', nn.Conv2d(num_input_features, num_output_features,
                                          kernel_size=1, stride=1, bias=False))
        self.add_module('pool', nn.AvgPool2d(kernel_size=2, stride=2))


class DenseNet(nn.Module):
    def __init__(self, growth_rate=32, block_config=(6, 12, 24, 16),
                 num_init_features=64, bn_size=4, drop_rate=0, num_classes=1000):

        super(DenseNet, self).__init__()

        # First convolution
        self.features = nn.Sequential(OrderedDict([
            ('conv0', nn.Conv2d(3, num_init_features, kernel_size=7, stride=2, padding=3, bias=False)),
            ('norm0', nn.BatchNorm2d(num_init_features)),
            ('relu0', nn.ReLU(inplace=True)),
            ('pool0', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)),
        ]))

        # Each denseblock
        num_features = num_init_features
        for i, num_layers in enumerate(block_config):
            block = _DenseBlock(num_layers=num_layers, num_input_features=num_features,
                                bn_size=bn_size, growth_rate=growth_rate, drop_rate=drop_rate)
            self.features.add_module('denseblock%d' % (i + 1), block)
            num_features = num_features + num_layers * growth_rate
            if i != len(block_config) - 1:
                trans = _Transition(num_input_features=num_features, num_output_features=num_features // 2)
                self.features.add_module('transition%d' % (i + 1), trans)
                num_features = num_features // 2

        # Final batch norm
        self.features.add_module('norm5', nn.BatchNorm2d(num_features))

        # Linear layer
        self.classifier = nn.Linear(num_features, num_classes)

        # Official init from torch repo.
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal(m.weight.data)
            elif isinstance(m, nn.BatchNorm2d):
                m.weight.data.fill_(1)
                m.bias.data.zero_()
            elif isinstance(m, nn.Linear):
                m.bias.data.zero_()

    def forward(self, x):
        features = self.features(x)
        out = F.relu(features, inplace=True)
        out = F.avg_pool2d(out, kernel_size=7, stride=1).view(features.size(0), -1)
        out = self.classifier(out)
        return out


def densenet121(**kwargs):
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 24, 16), **kwargs)
    return model


def densenet169(**kwargs):
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 32, 32), **kwargs)
    return model


def densenet201(**kwargs):
    model = DenseNet(num_init_features=64, growth_rate=32, block_config=(6, 12, 48, 32), **kwargs)
    return model


def densenet161(**kwargs):
    model = DenseNet(num_init_features=96, growth_rate=48, block_config=(6, 12, 36, 24), **kwargs)
    return model


if __name__ == '__main__':
    # 'DenseNet', 'densenet121', 'densenet169', 'densenet201', 'densenet161'
    # Example
    net = DenseNet()
    print(net)

总结

图像分类网络-经典CNN网络简介_第32张图片 CNN网络性能演进

2016年ILSVRC的Top5-error已降到3%以下,不过主要采用的ensemble方法,相比前几年,出现较大革新方法的脚步有所放缓了。

REFERENCE

https://zhuanlan.zhihu.com/p/192835137

http://ziyubiti.github.io/2016/11/27/cnnnet/

https://blog.csdn.net/weixin_41819299/article/details/81115365

你可能感兴趣的:(#,图像分类网络)