第六节:重要和常见的卷积神经网络结构

Posted 快乐江湖

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第六节:重要和常见的卷积神经网络结构相关的知识,希望对你有一定的参考价值。

文章目录

过去几年计算机视觉研究中的大量研究都集中在如何把这些基本构件组合起来,形成有效的卷积神经网络。最直观的方式之一就是去看一些案例,就像很多人通过看别人的代码来学习编程一样,通过研究别人构建有效组件的案例是个不错的办法。实际上在计算机视觉任务中表现良好的神经网络框架往往也适用于其它任务,也许你的任务也不例外。也就是说,如果有人已经训练或者计算出擅长识别猫、狗、人的神经网络或者神经网络框架,而你的计算机视觉识别任务是构建一个自动驾驶汽车,你完全可以借鉴别人的神经网络框架来解决自己的问题

1998年卷积神经网络开山之作LeNet被提出,但当时受限于算力和数据集,所以它提出之后一直被传统目标识别算法(特征提取+分类器)所压制,终于在沉寂了14年之后的2012年,AlexNet横空出世,在ImageNet挑战赛一举夺魁,使得CNN再次被人重视,并且一发不可收拾,不断研究发展

如下图是卷积神经网络发展概述,主要是如下三个发展趋势

  • 经典网络结构:以LeNet、AlexNet、VGGNet为代表,主要是卷积层的简单堆叠
  • 复杂神经网络:以ResNet、InceptionNetV1-V4、DenseNet为代表
  • 轻量型神经网络:以MobileNetV1-V3、ShuffleNet、SqueezeNet为代表

一:经典网络结构

(1) LeNet-5(CNN开山始祖)

LeNet-5:是一个较简单的卷积神经网络。下图显示了其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用softmax分类作为输出层

  • 注意:使用的激活函数为tanh

import torch.nn as nn
import torch.nn.functional as F

class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5)
        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
        self.fc1 = nn.Linear(in_features=16 * 5 * 5, out_features=120)
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = x.view(-1, 16 * 5 * 5)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

(2)AlexNet

A:简介

LeNet提出之后,卷积神经网络在CV和ML中就小有名气了,但是那是它并没有开始主导这些领域,这是因为虽然LeNet在小数据集上取到了很好的效果,但是在更大、更真实的数据集上训练卷积神经网络的性能和可行性还有待研究,事实上在上世纪90年代和2012年大部分时间里,神经网络往往被其他机器学习方法超越,例如非常出名的SVM

但在CV中,神经网络和传统机器学习算法仍然是有很大区别的,例如传统机器学习方法并不会把原始像素直接作为输入,而是会有一个非常重要的步骤——特征工程 虽然上世纪90年代已经有了一些神经网络的加速卡,但仅仅靠他们还不足以开发出有大量参数的深层多通道多层卷积神经网络。因此,与卷积神经网络这种端到端(像素输入分类输出)的系统不同,传统机器学习流水线应该是这样

  • 收集数据
  • 根据光学、几何学等其他知识手工对数据集进行预处理
  • 通过标准特征提取算法,例如SIFT(尺度不变特征变换)、SURF(加速鲁邦特征)输入数据
  • 将提取好的特征送入到你喜欢的分类器中,例如SVM

在2012年AlexNet出现之前,图像特征都是机械地算出来的,而有一些研究人员认为特征本身是可以被学习的,并且,在合理地复杂性前提下,特征应该有多个共同学习的神经网络层组成,每个层都应该有可以学习的层数。AlexNet便是这样的网络,2012年在ImageNet上夺得第一名,准确率比传统机器学习算法高出不少

和人眼提出物体特征一样,AlexNet的底层会提取到如边缘、颜色等基础信息,然后高层会建立在这些底层特征上再次提取,以标识更大的特征,例如眼睛、鼻子、草叶等等,而更高层可以检测整个物体,例如人、飞机等等

Alex的成功主要归功于以下两点

  • 数据:2009年ImageNet数据集发布,并发起了ImageNet挑战赛,要求比赛人员需要从100万个样本中训练模型,以区分1000个不同类别的对象,这种规模是前所未有的
  • 硬件:1999年NVIDA发明了GPU,用于为游戏玩家服务,用来加速图形处理。GPU可以优化高吞吐量的4×4矩阵和向量加法,从而服务于基本图形任务,仔细思考,这些数学运算和卷积运算非常相似。因此GPU的出现大大改变了神经网络训练的窘境

B:网络结构

AlexNet:Alex网络结构如下图所示,其设计理念和LeNet非常相似,主要区别如下

  • 网络结构要比LeNet5深很多
  • 由5个卷积层、两个全连接层隐藏层和一个全连接输出层组成
  • AlexNet使用ReLU作为激活函数
  • AlexNet第一层的卷积核比较大,为11×11,这是因为ImageNet中大多图像要比MNIST图像多10倍以上
  • AlexNet使用dropout算法来控制全连接层复杂程度,而LeNet5只使用了权重衰减
  • AlexNet对图像数据进行了增广(例如对一张图像翻转、裁切和变色相等于有了3张以上的图像),这增大了样本量,减少了过拟合

细节过程

import torch
import torch.nn as nn
import torch.nn.functional as F

class AlexNet(nn.Module):
    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(
            nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x

(3)VGGNet

A:简介

虽然AlexNet证明了深层神经网络网络是有效的,但是它没有提供一个通用的模板来指导后续的研究人员设计新的网络。与芯片设计中工程师从放置晶体管到逻辑元件再到逻辑块的过程类似,神经网络架构的设计也逐渐变得更加抽象。研究人员开始从单个神经元角度思考问题,发展到整个层,现在又转向块,重复层的模式

在VGG网络中,通过使用循环和子程序,可以很容易地在任何现代深度学习框架中实现这些重复的架构

经典CNN的基本组成部分一般是下面的序列

  • 带填充以保持分辨率的卷积层
  • 激活函数
  • 汇聚层

一个VGG块与之类似,由一系列卷积层组成,后面再加上利用空间下采样的最大汇聚层

下面代码中定义了一个名字叫做vgg_block的函数来实现一个VGG块

```python
import torch
from torch import nn

# num_convs表示该VGG块内卷积层的数量
def vgg_block(num_convs, in_chaneels, out_channels):
    layers = []
    for _ in range(num_convs):
        layers.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
        layers.apeend(nn.ReLu())
        in_channels = out_channels
    layers.append(nn.MaxPool2d(kernel_size=2, stride=2))
    return nn.Sequential(*layers)

B:网路结构

VGGNet:VGG网络结构如下图所示,和AlexNet、LeNet-5一样,VGG网络可以分为两个部分

  • 第一部分主要由卷积层和汇聚层组成
  • 第二部分由全连接层组成

其优点如下

  • VGG模型以较深的网络结构,较小的卷积核和池采样域,使其能够在获得更多图像特征的同时控制参数的个数,避免计算量过多和过于复杂的结构
  • 采用小的卷积核进行堆叠,两个3×3卷积核的堆叠相当于5×5卷积核的视野,三个3×3卷积核的堆叠相当于7×7卷积核的视野。这样做可以有更少的参数(3个堆叠的3×3卷积核参数为27个,而一个7×7卷积核参数有49个),另外还拥有了更多的非线性变换,增强了CNN的学习能力
  • 在卷积结构中,引入1×1卷积核,在不影响输入输出维度的情况下,增强了网络的表达能力,降低了计算力量
  • 训练时,先训练级别简单(层数较浅)的A级网络,然后使用A级网络的权重来初始化后面的复杂模型,加快训练收敛速度
  • 采用Multi-Scale方法来训练和预测,可以增加训练的数据量,防止模型过拟合,提升预测准确率
    • Multi-Scale(多尺度图像预测):将图片进行不同尺度的缩放,得到图像金字塔,然后对每层图片提取不同尺度的特征,得到特征图。最后对每个尺度的特征都进行单独的预测
  • VGG可以单独作为特征抽取器或预训练模型来使用

VGG有很多的变体

如下,实现一个VGG16,16表示它有13个卷积层和3个全连接层

import torch
import torch.nn as nn
import torch.nn.functional as F

class VGG16(nn.Module):
    def __init__(self, num_classes=1000):
        super(VGG16, self).__init__()

        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(64, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(128, 128, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(128, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(256, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),

            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(512, 512, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=2, stride=2),
        )

        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, num_classes),
		)
	def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

model = VGG16()

二:复杂网络结构

(1)ResNet(残差网络)

A:简介

从LeNet-5、AlexNet、VGG中可以看出,深层网络一般会比浅层网络的效果要好。那么要想进一步提升模型的准确率,那么把网络设计的越深越好不就行了吗。但事实并非如此,如下图,在ImageNet上做过一个实验,对于一个常规的网络,其在56层时的错误率却远远高于20层时的错误率

因此,随着网络层级的不断加深,模型的精度在前期也会随之提升,但是当网络层级增加到一定的数目以后,训练精度和测试精度迅速下降,这说明网络变得很深以后,深度网络变得更加难以训练了。回想神经网络反向传播原理,神经网络在反向传播中要不断传播梯度,而当网络层数加深时,梯度在传播过程中会逐渐消失,最终导致无法对前面网络层进行有效调整

B:网络结构

ResNet:残差网络的基本结构如下图所示,很明显该图是带有跳跃结构的

假定某段神经网络的输入是 x x x,期望的复杂输出是 H ( x ) H(x) H(x),如果要学习这样的模型,则训练的难度会比较大。残差网络的思想是,如果已经学习到了较为饱和的准确率,或者说当发现下层的误差会变大时,那么接下来的学习目标就转变为恒等映射的学习,也就是使输入 x x x近似于输出 H ( x ) H(x) H(x),以保证在后面的层次中不会造成精度的下降。在上图中,通过“shortcut connections”的方式,直接把输入 x x x传到输出作为初始结果,输出结构为 H ( x ) = F ( x ) + x H(x)=F(x)+x H(x)=F(x)+x,当 F ( x ) = 0 F(x)=0 F(x)=0时,那么 H ( x ) = x H(x)=x H(x)=x

因此,残差网络将学习目标改变了:不再是学习一个完整的输出,而是目标值 H ( x ) H(x) H(x) x x x的差值,也即残差:= H ( x ) = x H(x)=x H(x)=x,因此后续的训练目标就是要将残差结果逼近于0,使得随着网络加深,准确率不下降

这种残差跳跃式的结构,打破了传统的神经网络n-1层的输出只能给n层作为输入的惯例,使某一层的输出可以直接跨过几层作为后面某一层的输入,其意义在于为叠加多层网络而使得整个学习模型的错误率不降反升的难题提供了新的方向

下图是一个34层的深度残差网络结构图,下图中的实线和虚线分别表示

  • 实线:表示通道相同,所以 H ( x ) = F ( x ) + x H(x)=F(x)+x H(x)=F(x)+x
  • 虚线:表示通道不同,所以 H ( x ) = F ( x ) + W x H(x)=F(x)+Wx H(x)=F(x)+Wx,W为卷积操作

import torch
import torch.nn as nn
import torch.nn.functional as F

# 基本跳连模块
class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_planes, planes, stride=1):
        super(BasicBlock, self).__init__()
        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)
        self.bn1 = nn.BatchNorm2d数论--因子和线性筛模板

网络层-第六节1:路由选择协议概述

基于空洞卷积的多尺度2D特征融合网络

基于空洞卷积的多尺度2D特征融合网络

网络层-第六节4:边界网关协议BGP的基本工作原理

网络层-第六节2:路由信息协议RIP的基本工作原理