卷积神经网络中的经典模型(附代码,超详细)

卷积神经网络中几种常用的模型

1、LeNet

Lenet 是一系列网络的合称,包括 Lenet1 - Lenet5,由 Yann LeCun 等人在 1990 年中提出来的,被认为是最早的卷积神经网络(CNN),为后续CNN的发展奠定了基础。LeNet-5是LeNet系列中最著名的一个版本,也是最早的版本。它的设计目标是实现手写数字的自动识别,主要应用在美国的支票识别系统中。LeNet-5的网络结构相对简单,但在当时取得了令人瞩目的成果。LeNet-5的网络结构主要有两个部分组成,卷积层(Convolutional Layer)和全连接层(Fully Connected Layer)。

卷积层是CNN的核心组成部分,通过卷积操作对输入图像进行特征提取。LeNet-5中的卷积层由两个连续的卷积操作和一个池化(Pooling)操作组成,通过不断缩小特征图的尺寸和提取特征,最终得到了一个紧凑的表示。

全连接层则负责对卷积层输出的特征进行分类。LeNet-5中的全连接层由三个全连接层组成,其中包含了一些非线性激活函数,如sigmoid函数。

在这里插入图片描述

LeNet-5的主要特点

  • 层级化:LeNet-5网络结构是层级化的,由多个卷积层和全连接层组成。这种结构使得网络能够逐渐提取和组合更高级别的特征,从而实现更准确的分类,
  • 参数共享:LeNet-5的卷积层中采用了参数共享的策略,即多个卷积核共享相同的参数。这种共享可以减少网络的参数数量,降低过拟合的风险,并提高网络的训练速度和推理速度。
  • 鲁棒性:LeNet-5在设计上考虑了对图像的平移、旋转和缩放等变换的鲁棒性。通过卷积和池化操作的组合,LeNet-5可以在一定程度上对输入图像的变换具有一定的不变性,使得网络对输入的变化具有一定的鲁棒性。

上图是一个完整的LeNet5的网络图,对应到的代码如下

class LeNet5(nn.Module):
    def __init__(self,num_class,in_channels):
        super(LeNet5, self).__init__()

        self.num_class = num_class
        self.in_channels = in_channels

        self.features = nn.Sequential(
            nn.Conv2d(in_channels,6,kernel_size=5),
            nn.MaxPool2d(kernel_size=2),
            
            nn.Conv2d(6,16,kernel_size=5),
            nn.MaxPool2d(kernel_size=2)
        )

        self.classifier = nn.Sequential(
            nn.Linear(16*4*4,120),
            nn.Linear(120,84),
            nn.Linear(84,num_class)
        )

    def forward(self,x):
        x = self.features(x) # 输出16*4*4
        x = torch.flatten(x,1) # 展平(1,16*4*4),也可以用view来展平图片
        logits = self.classifier(x) # 输出10
        probas = F.softmax(logits,dim=1)

        return probas

代码中的nn.Linear(16 * 4 * 4,120),对于不同的输入要学会计算他的输出维度,图中的Input图片的维度是32 * 32大小的,所有到全连接层图片大小为16 * 5 * 5,但如果的输入图片是1 * 28 * 28大小的,到全连接时图片大小就变为16 * 4 * 4的,要学会利用下面的公式灵活变通。
O u t s i z e = I n s i z e − K e r n e l s i z e + 2 × P a d d i n g S t r i d e + 1 Out_{size}=\dfrac{In_{size}-Kernel_{size}+2\times Padding}{Stride}+1 Outsize=StrideInsizeKernelsize+2×Padding+1

其中 O u t s i z e Out_{size} Outsize为输出图片大小, I n s i z e In_{size} Insize为输入图片大小。

我们在构建网络完成之后,可以写一个main函数来测试我们的网络输出是否正确,下面就是一个简单的LeNet5的测试代码。

if __name__ == '__main__':
    test = torch.randn((64,1,28,28))
    model = LeNet5(num_class=10,in_channels=1)
    output = model(test)
    print(test.size())
    print(output.size())

输出结果如下

torch.Size([64, 1, 28, 28])
torch.Size([64, 10])

然后是用文章末尾的测试代码在Fashion_MNIST数据集上面对LeNet的测试

从结果可以看出,LeNet对10个类别的Fashion_MNIST还是有较高的准确度的,而且训练速度快。

在这里插入图片描述

2、AlexNet

AlexNet是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton于2012年提出的深度卷积神经网络模型,标志着深度学习在计算机视觉领域的突破性进展。它在ImageNet图像分类竞赛中取得了巨大的成功,大幅超越了传统方法。

AlexNet相比于之前LeNet的浅层网络,AlexNet采用了相对较深的网络结构。它包含8个卷积层和3个全连接层,使得网络能够学习到更高级别的抽象特征,提高了图像分类的准确性。

AlexNet的主要特点

  • ReLU激活函数:AlexNet引入了ReLU(Rectified Linear Unit)作为卷积层的非线性激活函数。相比于传统的sigmoid函数,ReLU具有线性增长的特性,可以更好地解决梯度消失问题,并加速网络的收敛速度。
  • Dropout正则化:AlexNet首次引入了Dropout正则化技术。通过在训练过程中随机丢弃一部分神经元的输出,Dropout可以减少过拟合现象,提高网络的泛化能力,使得网络更具鲁棒性。
  • 双GPU:为了提高网络的训练速度,AlexNet在两个GPU上进行了并行计算。这种并行计算架构使得模型的训练时间大幅缩短,促进了深度学习的发展。

在这里插入图片描述

下面就是一个简单的包含八层网络结构的AlexNet的代码实现

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.Linear(4096,10)
        )

    def forward(self,img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0],-1)) # 利用view 讲输入到全连接里的图片展平
        return output

下面是使用AlexNet对Fashion_MNIST数据集的测试结果(测试代码在文章末尾)

从结果可以看出,采用更深层次卷积结构的AlexNet其训练出来的准确度也更高,损失也更小。相应的训练代价也高,其结果中的训练时间几乎是LeNet的五倍了。

在这里插入图片描述

3、VGG

在了解VGG模型之前,我们先了解一个概念——感受野

感受野(Receptive Field)是指神经网络中某个特定神经元对于输入数据的局部区域的感知范围,感受野决定了神经元对输入信息的敏感度和响应模式,是深度学习模型中重要的概念之一。通俗一点讲感受野就是输出图像上一个像素点对应输入图像上的面积大小。

例如:下图的中间特征对应到原始像素的感受野就是3 * 3,最终特征对应到原始像素的感受野就是5 * 5。

在这里插入图片描述

那么感受野到底有什么用呢?

感受野的大小决定了神经元对输入数据的感知范围。较小的感受野适合捕捉局部细节特征,例如边缘、纹理等;而较大的感受野可以捕捉更大范围的特征,例如物体的整体形状和结构。通过设计不同大小的感受野,神经网络可以有效地提取不同尺度和抽象层次的特征,从而实现对输入数据的多层次理解和表达能力。

VGG就是利用了这样的思想,通过连续堆叠小卷积核来代替大卷积核,VGG网络认为,连续堆叠小卷积核可以做到和大卷积核一样的感受野,而且参数更少,层次更深,特征的表达能力更强。VGG模型的基本结构可以分为两部分:卷积层和全连接层。卷积层部分是由多个卷积层和池化层交替堆叠而成,用于提取图像的特征。卷积层中的卷积核大小固定为3 * 3,步长为1,并使用ReLU激活函数进行非线性映射。池化层使用2x2的最大池化操作,步长为2,用于下采样和减小特征图的尺寸。

在这里插入图片描述

VGG模型的主要特点有:

  • 统一的卷积层设计:VGG网络采用了相同大小的卷积核(3 * 3)和相同的步长(1),并且在每个卷积层后面都紧跟着一个池化层。这种统一的设计使得网络结构非常规整,易于理解和实现。
  • 小尺寸的卷积核:VGG网络采用了较小的卷积核尺寸(3 * 3),相比于更大的卷积核,如5 * 5或7 * 7,有以下优势:一是减少了模型的参数量,降低了计算和存储成本;二是增强了网络的非线性能力,通过多个3 * 3的卷积层堆叠,可以学习到更复杂的特征表示。
  • 深层次的网络结构:VGG网络相对于之前的网络模型,如LeNet和AlexNet,具有更深的结构。常见的VGG模型包括VGG16和VGG19,分别由16和19个卷积层组成。通过增加网络的深度,VGG网络能够更好地捕捉图像中的细节和复杂特征,从而提高了分类性能。

下面是一个简单的VGG模型的代码

import torch.nn as nn
import torch

# official pretrain weights
model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
}


class VGG(nn.Module):
    def __init__(self, features, num_classes=1000, init_weights=False):
        super(VGG, self).__init__()
        self.features = features

        # 构建分类网络结构
        self.classifier = nn.Sequential(
            nn.Linear(512 * 7 * 7, 4096),  # 第一层全连接层
            nn.ReLU(True),
            nn.Dropout(p=0.5),  # 50%的比例随机失活
            nn.Linear(4096, 4096),  # 第二层全连接层
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, num_classes)  # 第三层全连接层
        )
        if init_weights:  # 是否进行权重初始化
            self._initialize_weights()

    # 正向传播过程
    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.features(x)  # 输入到特征提取网络
        # N x 512 x 7 x 7
        x = torch.flatten(x, start_dim=1)  # 展平处理,从第1维度展平(第0维度为batch)
        # N x 512*7*7
        x = self.classifier(x)  # 输入到分类网络中,得到输出
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


# 构建提取特征网络结构
def make_features(cfg: list):  # 传入对应配置的列表
    layers = []  # 定义空列表,存放每一层的结构
    in_channels = 3  # 输入为RGB图片,输入通道为3
    for v in cfg:  # 遍历配置列表
        if v == "M":  # 如果为M,则为池化层,创建一个最大池化下采样层
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:  # 不等于M,则为数字,创建卷积层
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(True)]  # 每个卷积层都采用RELU激活函数,将定义好的卷积层和RELU拼接
            in_channels = v
    return nn.Sequential()  # 非关键字参数,*layers可以传递任意数量的实参,以元组的形式导入


cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}


# 实例化配置模型
def vgg(model_name="vgg16", **kwargs):
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    cfg = cfgs[model_name]

    model = VGG(make_features(cfg), **kwargs)  # 可以传递任意数量的实参,以字典的形式导入
    return model

代码来源:https://www.bilibili.com/video/BV1i7411T7ZN/?vd_source=319410d103e01d1a5fe13c7d118976c1(视频里有详细的代码解释)

同理,像VGG这样复杂的网络模型,我们也可以用main函数来通过注释的方法来逐行输出网络结果,观察网络结构是否正确。

if __name__ == '__main__':
    test = torch.randn((64,3,224,224))
    model = vgg('vgg16',num_classes=10)
    output = model(test)
    print(output.shape)

输出结果如下

torch.Size([64, 3, 224, 224])
torch.Size([64, 10])

下面是用CIFAR10数据集对VGG的测试,可以很明显看到,深层次的卷积带来的代价就是训练时间长。观察他的训练和测试精度可以看出,出现了过拟合的现象,一种可能的原因是VGG是一个非常深层的模型,具有大量的参数。如果数据集较小,模型容量过大可能导致模型过度拟合训练数据,无法泛化到测试数据。我们可以通过进行数据增强或者加入正则的方式解决这个问题,这在下文中会有提及。
在这里插入图片描述
在这里插入图片描述

4、InceptionNet

InceptionNet(也称为GoogLeNet)是由Google团队于2014年提出的一种卷积神经网络模型,其设计目标是在保持较低的参数量和计算复杂度的同时提高模型的表达能力和分类准确性。InceptionNet采用了一种被称为"Inception模块"的特殊结构,通过并行组合多个不同大小的卷积核和池化操作,实现了多尺度特征提取,并将不同尺度的特征图进行拼接,从而增强了模型的感受野和特征表示能力。

在这里插入图片描述

上图就是一个简单的Inception Module,当然,InceptionNet有很多模块,这里只是提到了其中的一种。它的实现就是在一个块里面同时使用几种卷积方式,具体来说,每个Inception模块包含了1 * 1、3 * 3、5 * 5卷积核和1* 1卷积后接3 * 3最大池化层四个分支,以及1 * 1卷积核的池化分支,用于降维减少参数然后按照通道方向拼接起来输出到下一层网络中,这种多尺度的特征提取有助于捕捉图像中的细节和全局信息。其中重点讲一下1 * 1卷积,它代表卷积核就是一个值,但它可以跨越不同通道相同位置的值,从而得到不同通道之间的联系,做到信息融合。而且1 * 1卷积还可以对不同方式的卷积权重进行调整,例如上图中,通过1 * 1卷积可以调整每一种卷积方式在输出结果上的比重,从而得到更好的训练结果。此外,1 * 1卷积层还可以用于调整特征图的通道数,降低运算次数,以便在不同分支或模块之间进行特征的整合和交互。

InceptionNet主要验证了网络宽度对特征表示也会有影响,通过合理控制宽度,使得这种多尺度的卷积核在网络层次不算很深的情况下,也能得到一个较好的结果。

接下来我们就来看一下这个InceptionNet怎么用代码实现

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

class InceptionA(nn.Module):
    def __init__(self,in_channels):
        super(InceptionA,self).__init__()
        self.branch1x1 = nn.Conv2d(in_channels,16,kernel_size=1)

        self.branch5x5_1 = nn.Conv2d(in_channels,16,kernel_size=1)
        self.branch5x5_2 = nn.Conv2d(16,24,kernel_size=5,padding=2)

        self.branch3x3_1 = nn.Conv2d(in_channels,16,kernel_size=1)
        self.branch3x3_2 = nn.Conv2d(16,24,kernel_size=3,padding=1)
        self.branch3x3_3 = nn.Conv2d(24,24,kernel_size=3,padding=1)

        self.branch_pool = nn.Conv2d(in_channels,24,kernel_size=1)


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

        branch5x5 = self.branch5x5_1(x)
        branch5x5 = self.branch3x3_2(branch5x5)

        branch3x3 = self.branch3x3_1(x)
        branch3x3 = self.branch3x3_2(branch3x3)
        branch3x3 = self.branch3x3_3(branch3x3)

        branch_pool = F.avg_pool2d(x,kernel_size=3,stride=1,padding=1)
        branch_pool = self.branch_pool(branch_pool)

        outputs = [branch1x1,branch5x5,branch3x3,branch_pool]
        return torch.cat(outputs,dim=1)


if __name__ == '__main__':   # 通过这个主函数,可以测试输出尺寸的正确性
    test_set = torch.randn(64,3,28,28)
    incep = InceptionA(in_channels=3)
    out = incep(test_set)
    print(out.shape)  
 
# 输出结果为
torch.Size([64, 10])

推荐视频:https://www.bilibili.com/video/BV1Y7411d7Ys/?p=11&spm_id_from=pageDriver&vd_source=319410d103e01d1a5fe13c7d118976c1(老师在里面详细讲解了InceptionNet以及代码如何实现)

我们实现了上面的InceptionModule之后。就可以通过引用这个模块创建更加复杂,层次更深的网络。如下图所示的网络,它就是通过反复引用Inception模块,以得到更好的训练效果。

在这里插入图片描述

下面我们通过引用InceptionA来定义一个属于我们自己的网络模型。

from InceptionModule import *

class MyGoogleNet(nn.Module):
    def __init__(self):
        super(MyGoogleNet,self).__init__()
        self.conv1 = nn.Conv2d(1,10,kernel_size=5)
        self.conv2 = nn.Conv2d(88,20,kernel_size=5)
        
        self.incep1 = InceptionA(in_channels=10)
        self.incep2 = InceptionA(in_channels=20)
        
        self.mp = nn.MaxPool2d(2)
        self.fc = nn.Linear(1408,10)
        
    def forward(self,x):
        in_size = x.size(0)
        x = F.relu(self.mp(self.conv1(x)))
        x = self.incep1(x)
        x = F.relu(self.mp(self.conv2(x)))
        x = self.incep2(2)
        x = x.view(in_size,-1)
        x = self.fc(x)
        return x

接下来就是上文中提到的我们自己用InceptionModule构建的网络模型的测试结果

我们用的是FashionMNIST的数据集,可以看到,训练效果非常好,模型泛化到测试集上的效果也不错,训练时间也相对较短,构建这个MyGoogleNet主要是想学习它的模块化思想,把有不同功能的网络封装起来,到需要的时候再调用,会极大的提高我们的开发效率。同时对比VGG网络,看到拓展网络宽度也可以有效的提高模型的学习能力,而且训练时间对比VGG也更短。

在这里插入图片描述

6、ResNet

ResNet(残差网络)是由Kaiming He等人于2015年提出的一种深度卷积神经网络模型,它在解决深层网络中梯度消失和网络退化问题上取得了重要突破。ResNet通过引入残差连接(residual connection)和残差块(residual block)的概念,使得网络可以更有效地训练和优化非常深的层次。

ResNet主要想解决这样一个问题,如果我们把3 * 3的卷积一直堆下去,网络整体的性能会不会越来越好,由表上的信息得知,答案是不会,二十层的卷积效果明显要好于五十层的卷积,这是由于卷积层更深之后,会出现梯度消失的现象,导致梯度趋近于零,使得参数得不到更新,进而使得训练效果不好。

在这里插入图片描述

所以ResNet就提出了这样一个概念 ,它在输出层级加入了一个跳连接,使输入x与经过卷积之后的 F ( x ) F(x) F(x)的加和经过激活之后作为输出中,使 O u t p u t ′ ( x ) = F ′ ( x ) + 1 Output'(x) = F'(x)+1 Output(x)=F(x)+1, 这样就可以让模型训练中回传的梯度在1附近,而不是在0附近,就可以解决梯度消失的问题。

在这里插入图片描述

o u p u t ( x ) = R e L U ( F ( x ) + x ) ouput(x)=ReLU(F(x)+x) ouput(x)=ReLU(F(x)+x)
ResNet的主要特点

  • 残差连接:ResNet引入了残差连接,通过跳跃连接将输入直接添加到网络的后续层中,解决了深层网络中的梯度消失和退化问题。
  • 深度扩展:由于残差连接的引入,使ResNet允许更深层次的结构接入网络,从而学习到更深层次的特征。

接下来我们就来看一下他的代码实现

class ResidualBlock(nn.Module):
    def __init__(self,channels):
        super(ResidualBlock,self).__init__()
        self.channels = channels

        self.conv1 = nn.Conv2d(channels,channels,kernel_size=3,padding=1)

        self.conv1 = nn.Conv2d(channels,channels,kernel_size=3,padding=1)

    def forward(self,x):
        y = F.relu(self.conv1(x))
        y = self.conv2(y)
        return F.relu(x+y)

和上文中的InceptionNet同理,我们可以把这种ResidualNet封装成一个模块,在以后我们构建更加复杂的网络的时候,可以方便的调用这个模块,来解决相应的问题。

下面是用CIFAR10数据集对ResNet的测试,Res代码来源

从结果我们可以看到,模型的学习能力非常强,训练精度可以达到90%,训练时间也短,但也和VGG一样,模型过于强大,参数过多,出现了严重的过拟合现象。

在这里插入图片描述

我们可以通过引入数据增强,例如随机的水平或垂直翻转,引入噪声,来减轻一下这种现象,但实验的出来的效果并没有非常好,我们还可以L1或L2正则化:在损失函数中引入正则化项,限制模型的权重大小,避免某些特征过度拟合。或者通过引入Dropout层:在训练过程中以一定的概率随机将某些神经元置零,减少神经元之间的依赖关系,强制模型学习更鲁棒的特征。

transform_fn = Compose([
        RandomHorizontalFlip(p=0.5),
        RandomVerticalFlip(p=0.5),
        ToTensor(),
        Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225]),
        RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0)
    ])

在这里插入图片描述

7、DenseNet

DenseNet(稠密连接网络)是由Gao Huang等人于2016年提出的一种深度卷积神经网络模型,它通过密集连接(dense connection)的方式将每一层的特征图与所有后续层的特征图连接在一起,从而有效地促进信息流动和特征重用。

在这里插入图片描述

DenseNet主要解决的也是卷积神经网络中的梯度消失和特征传递的效率问题,在传统的卷积神经网络中,由于每一层的输出只与其前一层的输出相连接,信息在网络中逐层传递时可能会逐渐丢失,导致梯度消失的问题,这限制了网络的深度和性能。DenseNet通过引入密集连接的结构,将每一层的特征图与所有后续层的特征图连接在一起,使得每一层都能够直接访问之前所有层的特征图,实现了特征的全面共享和重用。为了减小特征图的尺寸,DenseNet还会使用过渡层(Transition Layer),通过降低特征图的通道数和尺寸来控制网络的复杂度,解决了梯度消失问题。

DenseNet中的基本单元是称为"Dense Block"的模块,它由多个卷积层组成,每个卷积层的输出都会与前面所有层的输出进行连接。在每个Dense Block内部,通常还会使用批量归一化(batch normalization)和非线性激活函数(如ReLU)来增强模型的表达能力和非线性拟合能力。

下面我们来看看DenseNet的具体实现

class DenseModule(nn.Module):
    def __init__(self, num_input_features, growth_rate, bn_size, drop_rate=0):
        super(DenseModule, self).__init__()
        self.drop_rate = drop_rate
        self.dense_layer = nn.Sequential(
            nn.BatchNorm2d(num_input_features),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=num_input_features, out_channels=bn_size * growth_rate, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(bn_size * growth_rate),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_channels=bn_size * growth_rate, out_channels=growth_rate, kernel_size=3, stride=1, padding=1, bias=False)
        )
        self.dropout = nn.Dropout(p=self.drop_rate)

    def forward(self, x):
        y = self.dense_layer(x)
        if self.drop_rate > 0:
            y = self.dropout(y)

        return torch.cat([x, y], dim=1)

class DenseBlock(nn.Module):
    def __init__(self, num_layers, num_input_features, bn_size, growth_rate, drop_rate=0):
        super(DenseBlock, self).__init__()
        layers = []
        for i in range(num_layers):
            layers.append(DenseModule(num_input_features + i * growth_rate, growth_rate, bn_size, drop_rate)) 
        self.layers = nn.Sequential(*layers)

    def forward(self, x):
        return self.layers(x)

DenseNet的测试也是基于CIFAR10数据,代码来源

对比于ResNet模型,由于它的稠密连接,所以训练时间相比更长,训练损失逐渐减小,训练准确率和测试准确率逐渐提高,这表明模型在学习数据的特征和模式方面取得了进展。每个训练周期的训练时间逐渐减少,这可能是因为模型在训练过程中逐渐收敛,并且随着训练的进行,模型参数的更新速度变慢。在这里,也出现了过拟合现象,可能的原因是每个层都与后续层直接连接,导致每个层的输入通道数相对较大。这样的连接方式增加了参数的数量,所以会出现过拟合现象。

在这里插入图片描述

8、训练代码

这里为大家提供了一个通用的训练代码,可以方便大家使用它来训练各种不同的模型,在使用的时候只要稍微修改一下通道数和存储数据集的文件位置,然后把相应的网络的导入,就可以训练模型了。

import time

import torch
from torchvision.transforms import Compose,Normalize,Resize,ToTensor,RandomHorizontalFlip,RandomVerticalFlip,RandomErasing
from torchvision.datasets import CIFAR10
import torchvision.datasets
from torch.utils.data import DataLoader
from torchvision import transforms
from VGG import *
from ResNet2 import *
from DenseNet import *

def evaluate_accuracy(data_iter, net, device=None):
    if device is None and isinstance(net, torch.nn.Module):
        # 如果没指定device就使用net的device
        device = list(net.parameters())[0].device
    acc_sum, n = 0.0, 0
    with torch.no_grad():
        for X, y in data_iter:
            net.eval() # 评估模式, 这会关闭dropout
            acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
            net.train() # 改回训练模式
            n += y.shape[0]
    return acc_sum / n
def train(net, train_iter, test_iter, optimizer, device, num_epochs):
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    for epoch in range(num_epochs):
        train_l_sum, train_acc_sum, n, batch_count, start = 0.0, 0.0, 0, 0, time.time()
        for X, y in train_iter:
            X = X.to(device)
            y = 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)
        print('epoch %d, loss %.4f, train acc %.3f%%, test acc %.3f%%, time %.1f sec'
              % (epoch + 1, train_l_sum / batch_count, 100*train_acc_sum / n, 100*test_acc, time.time() - start))
        
# train_dataloader,test_dataloader,train_len = load_data_fashion(batch_size=batch_size,output_channels=1,resize=32,root='../FashinMNIST')
# num_epochs = iteration / (len(train_len) / batch_size)
# num_epochs = int(num_epochs)
# for data in train_dataloader:
#     img,target = data
#     print(img.shape)
#     # output = Model(img)
#     # print(output.size())
#     break

def CIFAR10_loader(size,train=True):
    transform_fn = Compose([
        #Resize((size,size)),
        # trans.append(transforms.RandomHorizontalFlip(p=0.5))
        # trans.append(transforms.RandomVerticalFlip(p=0.5))
        RandomHorizontalFlip(p=0.5),
        RandomVerticalFlip(p=0.5),
        ToTensor(),
        Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225]),
        RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0)
        # transforms.RandomErasing(p=0.5, scale=(0.02, 0.33), ratio=(0.3, 3.3), value=0)
    ])
    dataset = CIFAR10(root='../CIFAR10', train=train, transform=transform_fn, download=True)
    data_loader = DataLoader(dataset,batch_size=32,shuffle=True)
    return data_loader

train_dataloader = CIFAR10_loader(size=None,train=True)
test_dataloader = CIFAR10_loader(size=None,train=False)

in_channels = 3
# Model = vgg('vgg11')
Model = resnet18(pretrained=False,num_classes=10)
# Model = get_densenet121(flag=False, num_classes=10)

num_epochs = 10
learning_rate = 0.001
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
optimizer = torch.optim.Adam(Model.parameters(), lr=learning_rate)
train(Model, train_dataloader, test_dataloader, optimizer, device, num_epochs)

9 、总结

本文主要介绍了LeNet,AlexNet,VGG,InceptionNet,ResNet,DenseNet六个经典的卷积神经网络模型。以它们的特点为基础,开发者可以根据自己的需求选择相应的模块来构建自己的卷积神经网络。

  • 12
    点赞
  • 140
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值