• 跟李沐学AI之现代卷积神经网络


    现代卷积神经网络

    对于最终模型精度影响来说,更大或更干净的数据集,稍微进行改进的特征提取,比任何学习算法带来的进步要大的多。

    深度卷积学习网络 AlexNet

    最火的机器学习是核方法,核心是特征提取,选择核函数来计算相关性,然后转换成凸优化问题,会有较好的定理,能够计算模型的复杂度。
    在网络包含许多特征的深度模型需要大量的标签数据。训练可能需要数百个迭代轮数,对计算资源要求很高。AlexNet与LetNet在本质上没有任何区别。计算机视觉方法论的改变,之前专注于人工特征提取,但是后来利用CNN来进行学习特征,形成端到端的学习。
    在这里插入图片描述
    在这里插入图片描述
    在最低层,模型学习到了一些类似于传统滤波器的特征提取器,AlexNet的更高层建立在这些底层表示的基础上,以表示更大的特征。最终的隐藏神经单元可以学习图像的综合表示,使得不同类别的数据易于区分。
    在这里插入图片描述
    AlexNet由八层组成,五个卷积层、两个全连接层和一个全连接输出层,使用ReLU作为激活函数,使用不同的参数初始化方法,ReLU激活函数使训练模型更加容–sigmoid函数的输出非常接近0时,这些区域的梯度几乎为0,会影响反向传播继续更新模型参数。使用暂退法(dropout=0.5)来控制全连接层的模型复杂度,为了进一步扩充数据,在训练时候增加了大量的图像增强数据,使得模型更加健壮,更大的样本量有效的减少过拟合。使用更大的卷积核,因为图片的输入更大了,同时输出的通道数也增加,希望在第一层的时候就能学到更多的特征。

    import torch
    from torch import nn
    from d2l import torch as d2l
    
    net = nn.Sequential(
          nn.Conv2d(1,96,kernel_size = 11,stride=4,padding=1),
          nn.ReLU(),
          nn.MaxPool2d(kernel_size=3,stride=2),
          nn.Conv2d(96,256,kernel_size=5,padding=2),
          nn.ReLU(),
          nn.MaxPool2d(kernel_size=3,stride=2),
          nn.Conv2d(256,384,kernel_size=3,padding=1),
          nn.ReLU(),
          nn.Conv2d(384,384,kernel_size=3,padding=1),
          nn.ReLU(),
          nn.Conv2d(384,256,kernel_size=3,padding=1),
          nn.ReLU(),
          nn.MaxPool2d(kernel_size=3,stride=2),
          nn.Flatten(),
          nn.Linear(6400,4096),nn.ReLU(),nn.Dropout(p=0.5),
          nn.Linear(4096,4096),nn.ReLU(),nn.Dropout(p=0.5),
          nn.Linear(4096,10))
    # 构造一个单通道数据 来观察每一层的输出的形状
    X= torch.randn(1,1,224,224)
    for layer in net:
        X = layer(X)
        print(layer.__class__.__name__,'output shape:\t',X.shape)
    运行结果:
    Conv2d output shape:	 torch.Size([1, 96, 54, 54])
    ReLU output shape:	 torch.Size([1, 96, 54, 54])
    MaxPool2d output shape:	 torch.Size([1, 96, 26, 26])
    Conv2d output shape:	 torch.Size([1, 256, 26, 26])
    ReLU output shape:	 torch.Size([1, 256, 26, 26])
    MaxPool2d output shape:	 torch.Size([1, 256, 12, 12])
    Conv2d output shape:	 torch.Size([1, 384, 12, 12])
    ReLU output shape:	 torch.Size([1, 384, 12, 12])
    Conv2d output shape:	 torch.Size([1, 384, 12, 12])
    ReLU output shape:	 torch.Size([1, 384, 12, 12])
    Conv2d output shape:	 torch.Size([1, 256, 12, 12])
    ReLU output shape:	 torch.Size([1, 256, 12, 12])
    MaxPool2d output shape:	 torch.Size([1, 256, 5, 5])
    Flatten output shape:	 torch.Size([1, 6400])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 10])
    
    # Fashion-MNIST图像的分辨率低于ImageNet图像,我们将他增加到224*224
    batch_size = 128
    train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size,resize=224)
    #训练AlexNet 学习率的下降会增加训练的时间
    lr,num_epochs = 0.01,10
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    运行结果:
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    在这里插入图片描述
    1.使用更多的卷积层和更多的参数来拟合大规模的数据集
    2.Dropout(正则项)、ReLU和预处理是提升计算机视觉任务性能的关键
    3.使用了数据增强(随机截取,调整图片的亮度色温等)
    4.ALexNet比LeNet学习的参数更多,机器找到的特征是不符合人类逻辑的。论文中提及的LRN是正则化技术,但是用处不大。因为前面的卷积特征抽取的不够深刻,所以需要两个dense来进行补充。
    在这里插入图片描述

    使用块的网络VGG

    在这里插入图片描述
    AlexNet没有提供一个通用的模板来指导后续的研究人员设计新的网络,结构不够清晰,研究人员开始从单个神经元的角度思考问题,发展到整个层,现在又转向块,重复层的模式。
    思考怎样才能获得更深和更大的网络?更多的全连接层?更多的卷积层?将卷积层组合成块?
    经典的卷积神经网络的基本组成部分:
    1.带填充以保持分辨率的卷积层 2.非线性激活层 3.汇聚层
    VGG块由一系列的卷积层组成,后面再加上用于空间下采样的最大池化层。使用了带有33卷积核,填充为1的卷积层,和带有22窗口,步幅为2的最大池化层。
    在这里插入图片描述
    VGG神经网络连接几个VGG块,其中有超参数变量conv_arch。该变量指定了每个VGG块里卷积层个数和输出通道数。全连接模块则与AlexNet中的相同。原始VGG网络有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,被称为VGG-11。
    VGG-11使用可复用的卷积块构造网络。不同的VGG模型可以通过每个块中卷积层的数量和输出通道数量的差异来定义。块的使用导致网络定义的非常简洁,使用块可以有效的设计更复杂的网络。在VGG论文中,发现深层且窄的卷积比较浅层且宽的卷积更有效。

    import torch
    from torch import nn
    from d2l import torch as d2l
    
    # 参数:重复的卷积层的个数,输出的通道数,输入的通道数
    def vgg_block(num_convs,in_channels,out_channels):
        layers =[]
        for _ in range(num_convs):
            layers.append(nn.Conv2d(
            in_channels,out_channels,kernel_size=3,padding=1))
            layers.append(nn.ReLU())
            in_channels = out_channels
        layers.append(nn.MaxPool2d(kernel_size=2,stride=2))
        # 放入sequential中变成一个vgg的块
        return nn.Sequential(*layers)
    
    #VGG网络
    # 每块会做最大池化,输入尺寸减半
    conv_arch = ((1,64),(1,128),(2,256),(2,512),(2,512))
    def vgg(conv_arch):
        conv_blks=[]
        in_channels=1
        for (num_convs,out_channels) in conv_arch:
            conv_blks.append(vgg_block(
            num_convs,in_channels,out_channels))
            in_channels = out_channels
            
        return nn.Sequential(
        *conv_blks,nn.Flatten(),
        nn.Linear(out_channels*7*7,4096),nn.ReLU(),
        nn.Dropout(0.5),nn.Linear(4096,4096),nn.ReLU(),
        nn.Dropout(0.5),nn.Linear(4096,10))
    net = vgg(conv_arch)
    # 观察每一层的输出形状
    X = torch.randn(size=(1,1,224,224))
    for blk in net:
        X = blk(X)
        print(blk.__class__.__name__,'output shape:\t',X.shape)
    
    Sequential output shape:	 torch.Size([1, 64, 112, 112])
    Sequential output shape:	 torch.Size([1, 128, 56, 56])
    Sequential output shape:	 torch.Size([1, 256, 28, 28])
    Sequential output shape:	 torch.Size([1, 512, 14, 14])
    Sequential output shape:	 torch.Size([1, 512, 7, 7])
    Flatten output shape:	 torch.Size([1, 25088])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 10])
    
    
    # 由于VGG-11比AlexNet计算量更大,构建了一个通道数较少的网络 将所有的通道数除以4
    ratio = 4
    small_conv_arch =[(pair[0],pair[1]//ratio) for pair in conv_arch]
    net = vgg(small_conv_arch)
    
    # 观察每一层的输出形状2:
    Y = torch.randn(size=(1,1,224,224))
    for blk in net:
        Y = blk(Y)
        print(blk.__class__.__name__,'output shape:\t',Y.shape)
    
    运行结果:
    Sequential output shape:	 torch.Size([1, 16, 112, 112])
    Sequential output shape:	 torch.Size([1, 32, 56, 56])
    Sequential output shape:	 torch.Size([1, 64, 28, 28])
    Sequential output shape:	 torch.Size([1, 128, 14, 14])
    Sequential output shape:	 torch.Size([1, 128, 7, 7])
    Flatten output shape:	 torch.Size([1, 6272])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 4096])
    ReLU output shape:	 torch.Size([1, 4096])
    Dropout output shape:	 torch.Size([1, 4096])
    Linear output shape:	 torch.Size([1, 10])
    
    
    # 训练模型
    lr,num_epochs,batch_size = 0.05,10,128
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=224)
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85

    在这里插入图片描述

    使用网络中的网络

    在这里插入图片描述
    全连接层会特别占据参数的空间,更加容易过拟合
    在这里插入图片描述
    LeNet、AlexNet和VGG都有一个共同的设计模式:通过一系列的卷积层和汇聚层来提取空间结构特征;然后通过全连接层对特征的表征进行处理。AlexNet和VGG对LeNet的改进主要在于如何扩大和加深这两个模块。使用全连接层可能会完全放弃表征的空间结构。网络中的网络提供了一个非常简单的解决方案:在每个像素的通道上分别使用多层感知机。
    11的卷积层可以等价为一个全连接层,步幅为1,无填充,输出形状跟卷积层输出一样,起到全连接层的作用。
    在这里插入图片描述
    卷积层的输入和输出由四维张量组成,张量的每个轴分别对应样本、通道、高度和宽度。全连接层的输入和输出分别对应于样本和特征的二维张量。NiN的想法是在每个像素位置应用一个全连接层。如果将权重连接到每个位置空间,可以视为1
    1的卷积层,或作为在每个像素位置上独立作用的全连接层,将空间维度中的每个像素视为单个样本,将通道维度视为不同的特征。
    在这里插入图片描述
    NiN块以一个普通的卷积层开始,后面是两个1*1 的卷积层,这两个卷积层充当带有Re
    LU的激活函数的逐像素全连接层。第一层卷积窗口的形状通常都是由用户设置的。每个NiN块之后有一个最大汇聚层。NiN块中输出通道数等于标签类别的数量。最后一个全局平均汇聚层,生成一个对数几率,显著减少了模型所需参数的数量,但是会增加模型训练的时间。

    import torch
    from torch import nn
    from d2l import torch as d2l
    # 输入的通道数和输出的通道数 第一个卷积层的大小 步幅和padding
    def nin_block(in_channels,out_channels,kernel_size,strides,padding):
        return nn.Sequential(
        nn.Conv2d(in_channels,out_channels,kernel_size,strides,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())
    
    #NiN模型
    net =nn.Sequential(
        nin_block(1,96,kernel_size =11,strides=4,padding=0),
        nn.MaxPool2d(3,stride=2),
        nin_block(96,256,kernel_size = 5,strides=1,padding=2),
        nn.MaxPool2d(3,stride=2),
        nin_block(256,384,kernel_size = 3,strides=1,padding=1),
        nn.MaxPool2d(3,stride=2),
        nin_block(384,10,kernel_size = 3,strides=1,padding=1),
        # 全局的平均池化层 输出是一个4D
        nn.AdaptiveAvgPool2d((1,1)),
        nn.Flatten()  
         )
    # 查看每个块的输出形状
    X = torch.rand(size=(1,1,224,224))
    for layer in net:
        X = layer(X)
        print(layer.__class__.__name__,"output shape",X.shape)
    
    运行结果
    Sequential output shape torch.Size([1, 96, 54, 54])
    MaxPool2d output shape torch.Size([1, 96, 26, 26])
    Sequential output shape torch.Size([1, 256, 26, 26])
    MaxPool2d output shape torch.Size([1, 256, 12, 12])
    Sequential output shape torch.Size([1, 384, 12, 12])
    MaxPool2d output shape torch.Size([1, 384, 5, 5])
    Sequential output shape torch.Size([1, 10, 5, 5])
    AdaptiveAvgPool2d output shape torch.Size([1, 10, 1, 1])
    Flatten output shape torch.Size([1, 10])
    
    
    # 训练模型
    lr,num_epochs,batch_size =0.1,10,128
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=224)
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在这里插入图片描述

    1.N使用由一个卷积层和多个1*1卷积层组成的块。该块可以在卷积神经网络中使用,以允许更多的每像素非线性。
    2.NiN去除了容易造成过拟合的全连接层,将它们替换为全局平均池化代替VGG和AlexNet中全连接层(即在所有位置上进行求和),池化层通道数量为所需的输出数量。
    3.交替使用NiN块和步幅为2的最大池化层,逐步减小高宽和增大通道数
    4.最后使用全局平均池化层得到输出,其输入通道数是类别数。
    5.卷积后添加全局池化层,将输出特征的宽和高压缩为1,进一步降低模型的复杂度,提高泛化性能,但是收敛变慢。

    含并行连接的网络GooleNet

    有时使用不同大小的卷积核组合是有利的
    现在的GooleNet省略了一些为稳定训练而添加的特殊性质。
    GooleNet中基本的卷积块被称为是Inception块,这四条路径使用合适的填充来使得输入与输出的高和宽一致,最后我们将每条路线的输出在通道维度上连结,通常调整的超参数是每层的输出通道数。 第二条和第三条路径首先使用11的卷积层来降低通道数,再放入33的卷积层来降低运算的复杂度。
    在这里插入图片描述
    GooleNet使用9个Inception块和全局平均池化层来生成估计值,Inception之间的最大汇聚层可以降低维度。全局池化层避免了在最后使用全连接层
    在这里插入图片描述
    每个stage相当于一个vgg,stage高宽减半。
    在这里插入图片描述
    Inception不改变高宽,只改变通道数。
    在这里插入图片描述
    在这里插入图片描述
    第一个Inception的输出通道数为64+128+32+32=256,第二个Inception的输出通道数为128+192+96+64=480
    在这里插入图片描述
    在这里插入图片描述

    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)
            # 路线11*1卷积层
            self.p1_1 = nn.Conv2d(in_channels,c1,kernel_size=1)
            # 路线2 1*1 卷积层后接3*3卷积层
            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 1*1卷积层后接5*5的卷积层
            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 3*3最大汇聚层后接1*1的卷积层
            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)
    # 逐一实现GooleNet每个模块  第一个模块使用64个通道、7*7卷积层
    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))
    # 第二个模块使用两个卷积层:第一个卷积层是64个通道、卷积层;
    # 第二个卷积层使用将通道数量增加三倍的卷积层。
    # 这对应于Inception块中的第二条路径
    b2 =nn.Sequential(nn.Conv2d(64,64,kernel_size=1),
                     nn.ReLU(),
                     nn.Conv2d(64,192,kernel_size=3,padding=1),
                     nn.ReLU(),
                     nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
    # 第三个模块串联两个完整的Inception块
    # 第一个Inception块的输出通道数为64+128+32+32=256
    # 第二个Inception块的输出通道数增加到128+192+96+64=480
    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))
    #第四个模块,串联了5个Inception,输出通道数是192+208+48+64=512
    # 160+224+64+64=512128+256+64+64=512112+288+64+64=528256+320+128+128=832
    # 这些路径的通道数分配类似,第二条和第三条路径会按比例减少通道数
    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))
    
    # 第五模块的输出通道数为256+320+128+128=832384+384+128+128=1024
    # 每个通道的分配思路和之前的类似
    # 第五个模块之后紧跟着输出层,该模块通NiN一样使用全局平均池化层,将每个通道的高和宽变成1
    # 最后将输出变成二维数组,再接上一个输出个数为标签类别数的全连接层
    b5 = nn.Sequential(Inception(832,256,(160,320),(32,128),128),
                      Inception(832,384,(192,384),(48,128),128),
                      nn.AdaptiveAvgPool2d((1,1)),
                      nn.Flatten())
    net = nn.Sequential(b1,b2,b3,b4,b5,nn.Linear(1024, ))        
    
    # GooleNet计算复杂,将输入的高宽降到96
    X = torch.rand(size=(1,1,96,96))
    for layer in net:
        X = layer(X)
        print(layer.__class__.__name__,'output shape',X.shape)           
    Sequential output shape torch.Size([1, 64, 24, 24])
    Sequential output shape torch.Size([1, 192, 12, 12])
    Sequential output shape torch.Size([1, 480, 6, 6])
    Sequential output shape torch.Size([1, 832, 3, 3])
    Sequential output shape torch.Size([1, 1024])
    Linear output shape torch.Size([1, 10])
    
    # 训练模型
    lr,num_epochs,batch_size = 0.1,10,128
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=96)
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    在这里插入图片描述
    Inception有各种后续的变种:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    1.Inception块用4条不有不同超参数的卷积层和池化层的路来抽取不同的信息
    2.模型参数小,计算复杂度高
    3.是第一个达到上百层的网络,后续也有一系列的改进

    批量规范化

    课程评论区根据课程做的笔记,十分详细:链接: link
    对深度的神经网路会好一些
    梯度的值会更大些,可以使用更大的学习率。对权重的更新会变快

    import torch
    from torch import nn
    from d2l import torch as d2l
    
    # 定义数学原理
    # moving_mean,moving_var整个数据集的均值和方差 momentum用来更新均值和方差通常为标量
    def batch_normal(X,gamma,beta,moving_mean,moving_var,eps,momentum):
        #判断当前模式是训练模式还是预测模式
        if not torch.is_grad_enabled():
            # 预测模式直接使用传入的移动平均所得的均值和方差
            X_hat = (X-moving_mean)/torch.sqrt(moving_var+eps)
        else:
            assert len(X.shape) in(2,4)
            if len(X.shape) == 2:
                # 使用全连接层的情况,计算特征维上的均值和方差 方差和均值都是行向量
                mean = X.mean(dim=0)
                var = ((X -mean)**2).mean(dim=0)
            else:
                # 使用二维卷积的情况,计算通道维(axis=1)的均值和方差 1*n*1*14D
                # 保持X的形状以后进行广播计算
                mean = X.mean(dim=(0,2,3),keepdim=True)
                var = ((X-mean)**2).mean(dim=(0,2,3),keepdim=True)
            # 训练模式下,使用当前(小批量中)的均值和方差做标准化
            X_hat =(X-mean)/torch.sqrt(var+eps)
            # 更新移动平均的均值和方差
            moving_mean = momentum*moving_mean+(1.0-momentum)*mean
            moving_var = momentum*moving_var +(1.0-momentum)*var
        Y = gamma*X_hat +beta
        return Y,moving_mean.data,moving_var.data
    # 创建一个正确的BatchNorm层  这个层保持适当的参数:拉伸gamma和偏移beta 
    # 保持均值和方差的移动平均值
    class BatchNorm(nn.Module):
        # num_features 全连接的输出数量或卷积层的输出通道数
        # num_dims:2表示完全连接层4表示卷积层
        def __init__(self,num_features,num_dims):
            super().__init__()
            if num_dims == 2:
                shape = (1,num_features)
            else:
                shape = (1,num_features,1,1)
            # 参与梯度和迭代的拉伸和偏移参数 分别初始化成10
            # gamma拟合方差 beta拟合均值
            self.gamma = nn.Parameter(torch.ones(shape))
            self.beta = nn.Parameter(torch.zeros(shape))
            # 非模型参数的变量初始化为01
            self.moving_mean = torch.zeros(shape)
            self.moving_var = torch.ones(shape)
        def forward(self,X):
            if self.moving_mean.device != X.device:
                self.moving_mean = self.moving_mean.to(X.device)
                self.moving_var = self.moving_var.to(X.device)
            # 保存更新过的moving_mean和moving_var
            Y,self.moving_mean,self.moving_var = batch_normal(
            X,self.gamma,self.beta,self.moving_mean,
            self.moving_var,eps=1e-5,momentum=0.9)
            return Y
     # 将BatchNorm应用于LeNet模型
    # 批量规范化是在卷积层或全连接层之后,相应的激活函数之前应用的
    net = nn.Sequential(
          nn.Conv2d(1,6,kernel_size=5),BatchNorm(6,num_dims=4),nn.Sigmoid(),
          nn.AvgPool2d(kernel_size=2,stride=2),
          nn.Conv2d(6,16,kernel_size=5),BatchNorm(16,num_dims=4),nn.Sigmoid(),
          nn.AvgPool2d(kernel_size=2,stride=2),nn.Flatten(),
          nn.Linear(16*4*4,120),BatchNorm(120,num_dims=2),nn.Sigmoid(),
          nn.Linear(120,84),BatchNorm(84,num_dims=2),nn.Sigmoid(),
          nn.Linear(84,10))  
    # 使用大学习率来训练LeNet
    lr,num_epochs,batch_size=1.0,10,256
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size)
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))    
    
    运行结果: 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    在这里插入图片描述

    # 使用深度学习框架中定义的BatchNorm
    net = nn.Sequential(
        nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
        nn.AvgPool2d(kernel_size=2, stride=2),
        nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
        nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
        nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
        nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
        nn.Linear(84, 10))
    d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述
    数据集不是很稳定,多训练几次,精度会有变化。

    残差网络ResNet

    随着设计越来越深的网络,新增添的层如何提升神经网络的性能变得至关重要。
    残差网络的核心思想是:每个附加层都应该更容易的包含原始函数作为其元素之一。
    更复杂的模型不一定能够带来好处
    在这里插入图片描述
    左:可能越深的网络反倒距离最优点越远。右:resnet是增加深度的同时,使得这个模型不会变得更差。

    串联一层改变函数类,希望能够扩大函数类,残差块加入快速通道来得的f(x)=x+g(x)的结构。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    resnet:
    高宽减半的ResNet块(步幅为2)后接多个高宽不变的ResNet块。残差块是得很深的网络更加容易训练。
    在这里插入图片描述

    import torch
    from torch import nn
    from torch.nn import functional as F
    from d2l import torch as d2l
    
    # 残差设计
    class Residual(nn.Module):  #@save
        def __init__(self, input_channels, num_channels,
                     use_1x1conv=False, strides=1):
            super().__init__()
            self.conv1 = nn.Conv2d(input_channels, num_channels,
                                   kernel_size=3, padding=1, stride=strides)
            self.conv2 = nn.Conv2d(num_channels, num_channels,
                                   kernel_size=3, padding=1)
            if use_1x1conv:
                self.conv3 = nn.Conv2d(input_channels, num_channels,
                                       kernel_size=1, stride=strides)
            else:
                self.conv3 = None
            self.bn1 = nn.BatchNorm2d(num_channels)
            self.bn2 = nn.BatchNorm2d(num_channels)
    
        def forward(self, X):
            Y = F.relu(self.bn1(self.conv1(X)))
            Y = self.bn2(self.conv2(Y))
            if self.conv3:
                X = self.conv3(X)
            Y += X
            return F.relu(Y)
    
    # 输入和输出形状一致
    blk = Residual(3,3)
    X = torch.rand(4,3,6,6)
    Y = blk(X)
    Y.shape
    
    torch.Size([4, 3, 6, 6])
    
    # 增加输出通道数的同时,减半输出的高和宽
    blk = Residual(3,6,use_1x1conv=True,strides=2)
    blk(X).shape
    
    torch.Size([4, 6, 3, 3])
    
    #ResNet模型
    b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                       nn.BatchNorm2d(64), nn.ReLU(),
                       nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
    # 多少个残差块
    def resnet_block(input_channels, num_channels, num_residuals,
                     first_block=False):
        blk = []
        for i in range(num_residuals):
            # 第一个残差块高宽减半
            if i == 0 and not first_block:
                blk.append(Residual(input_channels, num_channels,
                                    use_1x1conv=True, strides=2))
            # 之后的残差块保持不变
            else:
                blk.append(Residual(num_channels, num_channels))
        return blk
    b2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))
    b3 = nn.Sequential(*resnet_block(64, 128, 2))
    b4 = nn.Sequential(*resnet_block(128, 256, 2))
    b5 = nn.Sequential(*resnet_block(256, 512, 2))
    
    net = nn.Sequential(b1, b2, b3, b4, b5,
                        nn.AdaptiveAvgPool2d((1,1)),
                        nn.Flatten(), nn.Linear(512, 10))
    X = torch.rand(size=(1, 1, 224, 224))
    for layer in net:
        X = layer(X)
        print(layer.__class__.__name__,'output shape:\t', X.shape)
    
    Sequential output shape:	 torch.Size([1, 64, 56, 56])
    Sequential output shape:	 torch.Size([1, 64, 56, 56])
    Sequential output shape:	 torch.Size([1, 128, 28, 28])
    Sequential output shape:	 torch.Size([1, 256, 14, 14])
    Sequential output shape:	 torch.Size([1, 512, 7, 7])
    AdaptiveAvgPool2d output shape:	 torch.Size([1, 512, 1, 1])
    Flatten output shape:	 torch.Size([1, 512])
    Linear output shape:	 torch.Size([1, 10])
    
    
    # 训练模型
    lr,num_epochs,batch_size = 0.05,10,256
    train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size,resize=96)
    d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88

    在这里插入图片描述
    ResNet是怎么处理梯度消失的?
    简单理解是:小数*小数还是小数,但是大数+小数还是大数,尽管梯度下降存在小数,但是存在另一个分支的大数,所以可以通过加法来缓解梯度消失问题。

    稠密连接DenseNet

    ResNet和DenseNet的关键区别在于:DesNe的输出是连接,而不是ResNet的简单相加。ResNet将f分解为两个不服:一个简单的线性项和一个复杂的非线性项,DenseNet将F分解成超过两部分的信息。
    在这里插入图片描述在这里插入图片描述
    稠密网路主要由两部分构成:稠密块和过渡层。前者定义如何连接输入和输出,后者则控制通道数量,使其不会变得太复杂。

    import torch
    from torch import nn
    from d2l import torch as d2l
    
    def conv_block(input_channels, num_channels):
        return nn.Sequential(
            nn.BatchNorm2d(input_channels), nn.ReLU(),
            nn.Conv2d(input_channels, num_channels, kernel_size=3, padding=1))
    
    class DenseBlock(nn.Module):
        def __init__(self, num_convs, input_channels, num_channels):
            super(DenseBlock, self).__init__()
            layer = []
            for i in range(num_convs):
                layer.append(conv_block(
                    num_channels * i + input_channels, num_channels))
            self.net = nn.Sequential(*layer)
    
        def forward(self, X):
            for blk in self.net:
                Y = blk(X)
                # 连接通道维度上每个块的输入和输出
                X = torch.cat((X, Y), dim=1)
            return X
    # 我们定义一个有2个输出通道数为10的DenseBlock。 使用通道数为3的输入时,我们会得到通道数为# 的输出。 卷积块的通道数控制了输出通道数相对于输入通道数的增长,因此也被称为增长率
    #(growth rate)。
    blk = DenseBlock(2, 3, 10)
    X = torch.randn(4, 3, 8, 8)
    Y = blk(X)
    Y.shape
    
    torch.Size([4, 23, 8, 8])
    # 过渡层
    def tranistion_block(input_channels,num_channels):
        return nn.Sequential(
        nn.BatchNorm2d(input_channels),nn.ReLU(),
        nn.Conv2d(input_channels,num_channels,kernel_size=1),
        nn.AvgPool2d(kernel_size=2,stride=2))
    # 每个稠密块都会带来通道数的增加,使用过多则会过于复杂化模型,过渡层可以用来控制模型复杂度
    # 通过1x1卷积层来减小通道数,并使用步幅为2的平均汇聚层减半高和宽,从而进一步的减低模型复杂
    # 度
    blk = tranistion_block(23,10)
    blk(Y).shape
    
    torch.Size([4, 10, 4, 4])
    
    
    # 构造DenseNet模型,首先使用通ResNet一样的单卷积层和最大汇聚层
    b1 = nn.Sequential(
    nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
    nn.BatchNorm2d(64),nn.ReLU(),
    nn.MaxPool2d(kernel_size=3,stride=2,padding=1))
    
    # 使用4个稠密块,设置每个块4个卷积层,稠密块里的卷积层通道数设为32,每个稠密块将增加128个
    # 通道数
    # 在每个模块之间,DenseNet使用过渡层来减半高和宽,并减半通道数
    # num_channels为当前的通道数
    num_channels,growth_rate = 64,32
    num_convs_in_dense_blocks = [4,4,4,4]
    blks =[]
    for i,num_convs in enumerate(num_convs_in_dense_blocks):
        blks.append(DenseBlock(num_convs,num_channels,growth_rate))
        # 上一个稠密块的输出通道数
        num_channels += num_convs*growth_rate
        # 在稠密块之间添加一个转换层,使通道数量减半
        if i != len(num_convs_in_dense_blocks) -1:
            blks.append(tranistion_block(num_channels,num_channels//2))
            num_channels = num_channels //2
    # 使用全局汇聚层和全连接层来输出结果
    net = nn.Sequential(
    b1,*blks,
    nn.BatchNorm2d(num_channels),nn.ReLU(),
    nn.AdaptiveAvgPool2d((1,1)),
        nn.Flatten(),
        nn.Linear(num_channels,10)
    )
    
    # 使用较深的网络 输入变成96*96 存在一定的过拟合现象
    lr, num_epochs, batch_size = 0.1, 10, 256
    train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96)
    d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    在这里插入图片描述

  • 相关阅读:
    Hibernate 和 Spring Data JPA中的 N+1 问题
    Python实现自动更换IP的方法
    监测难?误差大?北斗突破铁路监测预警难题,24小时全方位守护
    Cholesterol-PEG-Acid,Cholesterol-PEG-COOH,疏水性分子胆固醇-聚乙二醇-羧基
    命令行下编译与运行简单的OC程序
    【Spring Security】springboot + mybatis-plus + mysql 用户logout登出后token失效
    微信小程序的资源引用方式
    【C++】函数对象(仿函数)、谓词
    Java中 ==、equals() 、equalsIgnoreCase() 和compareTo() 方法对比详解
    DV SSL证书便宜吗?申请后多久签发?
  • 原文地址:https://blog.csdn.net/weixin_56368033/article/details/126744460