CNN基础网络(一)

CNN基础网络介绍

CNN(卷积神经网络)借助卷积层保留了输入形状,使图像在宽和高两个方向上的相关性均可能被有效识别,另一方面卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免了参数尺寸过大。有效集解决了临近像素点的特征提取和模型参数过大的问题。
卷积神经网络就是包含卷积层的网络,这个名字最早来源于Lenet论文的第一作者Yann LeCun。Lenet展示了通过梯度下降训练卷积神经网络可以达到手写数字识别在当时最先进的成果。

LeNet

LeNet模型分为卷积层块和全连接层块。

卷积层块里的基本单位是卷积层后接最大池化层(Maxpool),卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠而成,每个卷积层都使用5x5的kernel,并在输出上使用sigmoid激活函数。最大池化层的kernel形状均为2x2,且步幅为2。

卷积层块的输出传入全连接层时,将输出的形状变为二维,其中第一维是小批量中的样本,第二维是每个样本的向量表示,向量长度为CxHxW。全连接层块包含三个全联接层,他们的输出个数分别是120、84、10。其中10位输出的类别个数。

Pytorch实现:

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, img):
        feature_map = self.conv(img)
        output = self.fc(feature_map.view(img.shape[0], -1))
        return output

AlexNet

在LeNet提出的近20年间,神经网络一度被其他机器学习方法取代,虽然LeNet在小数据集上取得了不错的成绩,但是在更大的真实数据集上的表现却不尽如人意。一方面受硬件水平限制,算力无法满足计算需求,另一方面,当年的研究者没有深入研究参数初始化和非凸优化算法等诸多领域,导致复杂的神经网络训练通常较为困难。

从LeNet可以看到,神经网络可以基于图像的原始像素进行分类。这种端到端的方法节省了很多中间步骤。然而,在很长一段时间里更流行的是研究者设计并生成手工的特征。这类图像分类研究的主要流程是:

  1. 获取图像数据集;
  2. 使用已有的特征提取函数提取图像特征;
  3. 使用机器学习模型对提取的特征进行分类。

当时认为的机器学习部分仅限于最后一步,然而另一部分人认为特征本身也应该由学习得来,并且,为了表征足够复杂的输入,特征本身应该分级表示,多层神经网络可以学习到数据的多级表征,并逐级表示越来越抽象的概念。在很长一段时间里,这部分人的理念并未实现,原因有二。
其一为数据集的限制,包含许多特征的深度模型需要大量的有标签数据集才能表现的比其他经典方法更好。这一状况在2010年前后兴起的大数据浪潮中得以改善,特别是2009年诞生的ImageNet数据集,包含1000类物体,每类多达数千张不同图像。ImageNet数据集同时推动计算机视觉和机器学习研究进入了新阶段。
其二为硬件资源限制,早期的硬件计算能力有限,使得训练较为复杂的神经网络非常困难,这一格局直到通用GPU的出现才得以改善。

2012年AlexNet使用8层卷积神经网络赢得了ImageNet 2012图像识别挑战赛,首次证明了学习到的特征可以超越手工设计的特征。Alexnet结构与Lenet类似,但使用了更多的卷积层和更大的参数空间拟合大规模数据集,是浅层神经网络和深层神经网络的分界。

AlexNet与Lenet有如下区别:

  1. 模型结构变化
    AlexNet使用5层卷积和3层全连接,其中第一层卷积Kernel为11x11,这样做的目的是扩大感受野来捕获较大输入图像的特征。第二层的卷积kernel形状减小到5x5,之后全部采用3x3。此外,第一、第二和第五个卷积层之后都使用了窗口形状为3x3,步幅为2的最大池化层。接着最后一层卷积的是两个输出个数为4096的全连接层,最后使用一个输出个数为10的全连接。
  2. 激活函数变化
    AlexNet将sigmoid激活函数改成了更加简单的Relu激活函数。一方面,ReLU激活函数的计算更加简单,另一方面,ReLU激活函数在不同的参数初始化方法下使模型更加容易训练。这是因为sigmoid激活函数输出接近0或1时,这些区域的梯度几乎为0,从而造成反向传播无法继续更新部分模型参数。
  3. Dropout
    Alexnet中引入了Droupout层,用来控制全连接层的模型复杂度,避免出现过拟合。
  4. 图像处理
    Alexnet引入了大量的图像增广,如翻转、裁剪和颜色变化,从而进一步扩大数据集来缓解过拟合。

Pytorch实现:

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 96, 11, 4),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            nn.Conv2d(96, 256, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            nn.Conv2d(256, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(3, 2)
        )

        self.fc = nn.Sequential(
            nn.Linear(256*5*5, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10)
        )

    def forward(self, img):
        features = self.conv(img)
        output = self.fc(features.view(img.shape[0], -1))
        return output

VGG

AlexNet在Lenet的基础上加宽了卷积核获得更大的感受野,VGG则在AlexNet的基础上加深了卷积层。
VGG由多个VGG块组成,组成规律为:连续使用数个相同的pad为1,Kernel为3x3的卷积层后接上一个stride为2,kernel为2x2的最大池化层。卷积层保持输入的高和宽不变,而池化层则对其减半。

对于给定的感受野,采用堆积的小卷积核优于采用大的卷积核,因为可以增加网络深度来保证学习更复杂的模式,而且参数更少。例如,在VGG中,使用了3个3x3的卷积核来代替7x7的卷积核,使用2个3x3的卷积核来代替5x5卷积核。这样做的目的是在保证具有相同感受野的条件下,提升网络的深度来提升神经网络的效果。

与AlexNet和LeNet一样,VGG网络由卷积层模块后接全连接层模块构成。卷积层模块串联数个vgg_block,其超参数由变量conv_arch定义。该变量指定了每个VGG块里卷积层个数和输入输出通道数,全连接模块与AlexNet一样。

VGG-11 Pytorch实现:

def vgg_block(num_convs, in_channel, out_channels):
    blk = []
    for i in range(num_convs):
        if i == 0:
            blk.append(nn.Conv2d(in_channel, out_channels, kernel_size=3, padding=1))
        else:
            blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))

        blk.append(nn.ReLU())

    blk.append(nn.MaxPool2d(kernel_size=2, stride=2))

    return nn.Sequential(*blk)

def VGG(conv_arch, fc_features, fc_hidden_units):

    net = nn.Sequential()

    for i, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
        net.add_module('vgg_block_' + str(i), vgg_block(num_convs, in_channels, out_channels))

    net.add_module('fc', nn.Sequential(layer.FlattenLayer(),
                                       nn.Linear(fc_features, fc_hidden_units),
                                       nn.ReLU(),
                                       nn.Dropout(0.5),
                                       nn.Linear(fc_hidden_units, fc_hidden_units),
                                       nn.ReLU(),
                                       nn.Dropout(0.5),
                                       nn.Linear(fc_hidden_units, 10)
                                       ))
    return net

NiN

NiN使用1x1卷积替代全连接层,通过串联多个由卷积层和1x1卷积层(全连接层)构成的小网络来构建一个深层网络。

NiN使用卷积窗口形状分别为11x11、5x5和3x3的卷积层。NiN块是NiN中的基础块,它由一个卷积层加两个充当全连接层的1x1卷积层串联而成。每个NiN块后接一个stride为2、窗口形状为3x3的最大池化层。

除了使用NiN块以外,NiN去掉了AlexNet最后的3个全连接层,取而代之地,NiN使用了输出通道数等于标签类别数的NiN块,然后使用全局平均池化层对每个通道中所有元素求平均并直接用于分类,这样设计显著减小了模型的参数尺寸。

Pytorch实现:

import torch.nn.functional as F

class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        x = x.view(x.shape[0], -1)
        return x


class GlobalAvgPool2d(nn.Module):
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    def forward(self, x):
        return F.avg_pool2d(x, kernel_size=x.size()[2:])


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

def Nin():
    net = nn.Sequential(
        nin_block(1, 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),
        nin_block(384, 10, kernel_size=3, stride=1, padding=1),
        GlobalAvgPool2d(),
        FlattenLayer()
    )

    return net

GoogLeNet

GoogLeNet中的基础块叫做Inception块,与NiN块相比,这个基础块在结构上更加复杂:
在这里插入图片描述
Inception块里有4条并行线路,前三条分别使用窗口为1x1、3x3、5x5的卷积层来抽取不同空间尺寸下的信息,中间两个线路使用1x1卷积来减少输入的通道数,以降低模型复杂度。四条线路都使用了合适的填充来使输入与输出的高和宽一致。最后将每条线路的输出在通道维度上拼接,作为下一层的输入。Inception块中可以自定义每个层的输出通道数,以此来控制模型复杂度。

GoogLeNet主体卷积部分中使用了五个block,每个block之间使用stride为2的3x3最大池化层来减小输出的高和宽。

第一个block使用一个64通道的7x7卷积层,第二个block使用2个卷积层,分别为64通道的1x1卷积层,然后是将通道增大3倍的3x3卷积层。第三个block串联2个完整的Inception块。第四个block串联5个Inception块。第五个block串联两个Inception块,后面紧跟输出层,之后接全局平均池化层和全连接层。

Pytorch实现:

class Inception(nn.Module):
    def __init__(self, in_c, c1, c2, c3, c4):
        super(Inception, self).__init__()
        self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)

        self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)

        self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)

        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_c, 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)

def Googlenet():

    b1 = nn.Sequential(
        nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
        nn.ReLU(),
        nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

    b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                       nn.Conv2d(64, 192, kernel_size=3, padding=1),
                       nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

    b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32),
                       Inception(256, 128, (128, 192), (32, 96), 64),
                       nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

    b4 = 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))

    b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                       Inception(832, 384, (192, 384), (48, 128), 128),
                       GlobalAvgPool2d())

    net = nn.Sequential(b1, b2, b3, b4, b5, FlattenLayer(), nn.Linear(1024, 10))

    return net
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值