机器学习的入门笔记(第十周)

本周观看了李沐老师的《动手学深度学习》,李沐老师很注重代码的编写,但是对概念的讲解并不是特别多,所以还要去结合一些其他的资料去进行了解,下面是本周的所看的课程总结。

池化层

  • 我们要了解卷积层对位置十分的敏感,如下图,X输入的第一列和第二列为1,所以这个垂直边缘在第二列和第三列中间,经过一个卷积核(卷积核为1*2的-1和1),输出得到Y,在第二列,边缘全部检测出来了,但是这一列的左侧和右侧都为0,也就是说它对位置是非常敏感的,一个像素的偏移就会导致0输出 。
  • 我们不希望这样,因为边缘在真实图片中不像我们举的这个例子那么规整,可能会有弯曲的地方,所以我们需要一定的平移不变性,输入稍微有一点改动,输出不会有太大的变化,减低卷积核对位置的敏感程度 。

二维最大池化的工作原理与卷积同理,它所得到的输出形状和卷积核同理,在池化层中不需要做其他运算,只需要将这次窗口中的最大值取出就可以,如下图所示:

  • 因此卷积层提取特征,池化层压缩,如下图,我们进行垂直边缘检测,在经过卷积输出之后,再经过2*2最大池化得到输出(2*2最大池化的输出在这里是写错的,应该为3*3的矩阵,去掉最后一行,和全1的一列).
  • 我们可以发现2*2得最大池化层可以容忍最大1个像素的偏移 。
  • 因此最大池化层允许输入有较小的偏移。

  • 填充、步幅、多个通道与之前的卷积同理,最大池化层没有可以学习的参数,它只是取最大值。
  • 并且与卷积层不同的是,卷积层的输入通道数目与输出通道数目可能不相同,卷积核可以有多个,并且每个可以有多个通道;而最大池化层的输入通道数目与输出通道数目相同,因为池化不会融合多个通道,每个通道做一次池化层,所以输入通道数目与输出通道数目相同。
  • 池化层也可以相当于常数,梯度为1或者0。

  • 平均池化层顾名思义,之前的最大池化层是取当前窗口的最大值,平均池化层是取当前窗口的平均值。
  • 并且最大池化层输出的是每个窗口中的最强信号;平均池化层输出的是每个窗口的平均信号强度。

总结

  • 池化层是用来缓解卷积层位置的敏感性。
  • 池化层通常作用在卷积层的后面。
  • 并且池化层没有可以学习的参数,不会让模型变得更大。

池化层的代码实现

1、实现池化层的正向传播

与卷积层同理,输出的大小的公式与卷积层是一样的,只不过这里换成了取最大值和平均值

import torch
from torch import nn
from d2l import torch as d2l

def pool2d(X,pool_size,mode='max'):
    p_h,p_w = pool_size
    Y = torch.zeros((X.shape[0]-p_h+1,X.shape[1]-p_w+1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i,j] = X[i:i+p_h,j:j+p_w].max()
            elif mode == 'avg':
                Y[i,j] = X[i:i+p_h,j:j+p_w].mean()
    return Y

2、验证二维池化层的输出

# 验证最大池化层
X = torch.tensor([[0.0,1.0,2.0],[3.0,4.0,5.0],[6.0,7.0,8.0]])
pool2d(X,(2,2))

'''
tensor([[4., 5.],
        [7., 8.]])
'''

# 验证平均池化层
pool2d(X,(2,2),'avg')

'''
tensor([[2., 3.],
        [5., 6.]])
'''

3、深度学习框架中的步幅与池化窗口的大小相同

并且我们需要注意使用pytorch中的nn.MaxPool2d函数,输入至少要是三维的张量。

pytorch中使用nn.MaxPool2d函数,若不指定stride步长,则默认与池化层的长度一致。

X = torch.arange(16,dtype=torch.float32).reshape((1,1,4,4))

pool2d = nn.MaxPool2d(3) # 默认stride也为3,下一个窗口跟现在没有重叠部分,没有中间的平滑过度
pool2d(X)

'''
tensor([[[[10.]]]])
'''

4、设定一个任意大小的矩形池化窗口,并分别设定填充和步幅的高度和宽度

输出形状与之前的公式计算一致,[(输入长度-池化长度+2*填充)/步长]向下取整 + 1

pool2d = nn.MaxPool2d((2,3),padding=(1,1),stride=(2,3))
pool2d(X)

'''
tensor([[[[ 1.,  3.],
          [ 9., 11.],
          [13., 15.]]]])
'''

5、池化层在每个通道上单独运算

我们需要注意torch.cat是在现有的维度上连接,是等维连接,不改变维度;而torch.stack是在新维度上堆叠,是升维连接,会改变维度。

X是四维张量(1,1,4,4),X+1也是四维张量(1,1,4,4),所以X = torch.cat((X,X+1),1)是dim=1上进行连接,增加一个通道,所以连接后的维度为(1,2,4,4)。

X = torch.cat((X,X+1),1)
X

'''
tensor([[[[ 0.,  1.,  2.,  3.],
          [ 4.,  5.,  6.,  7.],
          [ 8.,  9., 10., 11.],
          [12., 13., 14., 15.]],

         [[ 1.,  2.,  3.,  4.],
          [ 5.,  6.,  7.,  8.],
          [ 9., 10., 11., 12.],
          [13., 14., 15., 16.]]]])
'''
X.shape

'''
torch.Size([1, 2, 4, 4])
'''

pool2d = nn.MaxPool2d(3,padding=1,stride=2)
pool2d(X)

'''
tensor([[[[ 5.,  7.],
          [13., 15.]],

         [[ 6.,  8.],
          [14., 16.]]]])
'''

LeNet

LeNet,它是最早发布的卷积神经网络之一,因其在计算机视觉任务中的高效性能而受到广泛关注。 LeNet被广泛用于自动取款机(ATM)机中,帮助识别处理支票的数字。

如下图,为LeNet的架构

  • 进行输入的是一个1通道的,28*28的图片。
  • 之后进入卷积层,通过5*5的卷积核和填充,将其变为6个通道,再进行sigmoid激活函数,将其变为非线性。
  • 在进行池化层改变其大小,变为6通道,14*14。
  • 再次经过卷积层,通过5*5的卷积核,改变输出的形状和通道数目,变为16通道,10*10,再经过sigmoid激活函数。
  • 之后进行池化层改变其大小,变为16通道,5*5。
  • 将其展开为一维张量,进行Flatten()操作。
  • 进行全连接层模块Linear,将16*5*5输入,最终变为10的输出。

一般都是高宽减半,通道数翻倍,像素信息密度增加了,像素中信息分散到通道。

6 ->16,每个通道都会对前面通道的卷积信息做融合

如下图是LeNet的简化版

总结

LeNet是通过卷积层学习图片的空间信息,通过池化层降低图片信息敏感度,通过全连接层转换到类别空间。

LeNet的代码实现

1、LeNet由两个部分组成,卷积编码器和全连接层密集块

先将输入重塑为1通道,28*28的形状。

import torch
from torch import nn
from d2l import torch as d2l

# reshape不需要依赖目标tensor是否在内存连续,view需要
class Reshape(nn.Module):
    def forward(self,x):
        return x.view(-1,1,28,28) # x.view只是改变张量的视图不复制数据

2、构建LeNet网络

net = nn.Sequential(Reshape(),
                   nn.Conv2d(1,6,kernel_size=5,padding=2),nn.Sigmoid(),
                   nn.AvgPool2d(kernel_size=2,stride=2),
                   nn.Conv2d(6,16,kernel_size=5),nn.Sigmoid(),
                   nn.AvgPool2d(kernel_size=2,stride=2),nn.Flatten(),
                   nn.Linear(16*5*5,120),nn.Sigmoid(),
                   nn.Linear(120,84),nn.Sigmoid(),
                   nn.Linear(84,10)
                   )

3、检查模型 一般都是让通道变多,高宽变小

X = torch.rand(size=(1,1,28,28),dtype=torch.float32)
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t',X.shape)
    
'''
Reshape output shape:     torch.Size([1, 1, 28, 28])
Conv2d output shape:     torch.Size([1, 6, 28, 28])
Sigmoid output shape:     torch.Size([1, 6, 28, 28])
AvgPool2d output shape:     torch.Size([1, 6, 14, 14])
Conv2d output shape:     torch.Size([1, 16, 10, 10])
Sigmoid output shape:     torch.Size([1, 16, 10, 10])
AvgPool2d output shape:     torch.Size([1, 16, 5, 5])
Flatten output shape:     torch.Size([1, 400])
Linear output shape:     torch.Size([1, 120])
Sigmoid output shape:     torch.Size([1, 120])
Linear output shape:     torch.Size([1, 84])
Sigmoid output shape:     torch.Size([1, 84])
Linear output shape:     torch.Size([1, 10])
'''

4、LeNet在Fashion-MNIST数据集上的表现,构建训练迭代器和测试迭代器

batch_size = 256
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

5、对evaluate_accuracy函数进行轻微的修改

def evaluate_accuracy_gpu(net,data_iter,device=None):
    # 使用gpu计算模型在数据集的精度
    if isinstance(net,nn.Module): #检查一个对象是否是某个类
        net.eval()
        if not device:
            device = next(iter(net.parameters())).device
    metric = d2l.Accumulator(2)
    for X,y in data_iter:
        if isinstance(X,list):
            X = [x.to(device) for x in X]
        else:
            X = X.to(device)
        y = y.to(device)
        metric.add(d2l.accuracy(net(X),y),y.numel())
    return metric[0] / metric[1]

6、为了使用GPU,要进行一点小改动

def train_ch6(net,train_iter,test_iter,num_epochs,lr,device):
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    net.apply(init_weights)
    print('training on',device)
    net.to(device)
    optimizer = torch.optim.SGD(net.parameters(),lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch',xlim=[1,num_epochs],
                            legend=['train_loss','train_acc','test_acc'])
    timer,num_batches = d2l.Timer(),len(train_iter)
    for epoch in range(num_epochs):
        metric = d2l.Accumulator(3)
        net.train()
        for i,(X,y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X,y = X.to(device),y.to(device)
            y_hat = net(X)
            dl = loss(y_hat,y)
            l.backward()
            optimizer.step()   
            metric.add(l*X.shape[0],d2l.accuracy(y_hat,y),X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
            if (i+1) % (num_batches // 5)==0 or i == num_batches -1:
                animator.add(epoch+(i+1)/num_batches,(train_l,train_acc,None))
        test_acc = evaluate_accuracy_gpu(net,test_iter)
        animator.add(epoch+1,(None,None,test_acc))
    print(f'loss {train_l:.3f},train acc {train_acc:.3f},test acc {test_acc:.3f}')      
    print(f'{metric[2]*num_epochs/timer.sum():.1f} example/sec, on {str(device)}')
          

7、训练和评估LeNet-5 模型

lr,num_epochs = 0.9,10
train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())

AlexNet

AlexNet是由Alex Krizhevsky、Ilya Sutskever和Geoffrey Hinton设计的深度卷积神经网络架构,是在2012年ImageNet大规模视觉识别挑战赛中取得突破性成果的关键因素之一。AlexNet的出现引领了深度学习在计算机视觉领域的新篇章。

ImageNet数据集是一个大规模的视觉识别数据集,由斯坦福大学视觉实验室创建和维护。它包含超过1400万张图像,这些图像被手动标注为超过2万个不同的类别或类别组。

AlexNet赢得了ImageNet竞赛,相当于更深更大的LeNet,采用了丢弃法、激活函数由Sigmoid转为ReLu,池化层由平均池化层转为最大池化层(MaxPooling使得数据更大,梯度更大,更好训练)。

AlexNet的架构,相比LeNet,它有更大的核窗口和步长,更大的池化窗口

并且AlexNet比LeNet新加了3层卷积层,有更多的输出通道

dense 稠密层 也称作全连接层 把特征提取成一维帮助最后分类

  • 并且AlexNet的激活函数由LeNet的sigmoid变为ReLu,减缓了梯度消失。
  • 并且隐藏全连接层后加入了丢弃层。
  • 最后也进行了数据增强,提高了训练难度,让模型鲁棒性更强。
  • 当然,AlexNet的效果很好,同时也增加了复杂度,AlexNet的无论是参数个数还是复杂度都远高于LeNet。
  • 在第一个卷积层的参数个数为3输入通道*96输出通道*11卷积核高度*11卷积核宽度约等于35K,后面以此类推。

总结

AlexNet相比LeNet多了10倍的参数个数和260倍多计算复杂度,也引入了一些新的方法(Drop out、ReLU、MaxPooling、数据增强),AlexNet标志着神经网络热潮的开始。

AlexNet代码实现

1、构建AlexNet网络

  • 进行输入的是一个1通道的,224*224的图片。
  • 之后进入卷积层,通过11*11的卷积核和填充,将其变为96个通道,再进行ReLU激活函数,将其变为非线性,大小为54*54。
  • 在进行最大池化层改变其大小,变为96通道,26*26。
  • 再次经过卷积层,通过5*5的卷积核,改变输出的形状和通道数目,变为256通道,26*26,再经过ReLU激活函数。
  • 之后进行最大池化层改变其大小,变为256通道,12*12。
  • 经过卷积层,通过3*3的卷积核,填充为1,变为384通道,12*12。
  • 经过卷积层,通过3*3的卷积核,填充为1,变为384通道,12*12。
  • 经过卷积层,通过3*3的卷积核,填充为1,变为256通道,12*12。
  • 经过最大池化层改变大小,变为256通道,5*5。
  • 将其展开为一维张量,进行Flatten()操作。
  • 进行全连接层模块Linear,将256*5*5输入,最终变为10的输出。
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))

2、构造一个单通道数据,来观察每一层输出的形状

X = torch.rand(size=(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])
'''

3、Fashion-MNIST图像的分辨率 低于ImageNet图像。我们将它们增加到224*224

batch_size = 128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size,resize=224)

4、训练AlexNet

lr,num_epochs = 0.01,10
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,device=d2l.try_gpu())

使用块的网络VGG

在上节学习的AlexNet,我们了解到AlexNet的缺点是结构不规则,不规范,不清晰。

若要向更大规模的网络发展,必须用更规范的方式去构建网络。

VGG比AlexNet有更多的卷积层。

  • VGG思想是通过堆叠卷积块构建网络。
  • 使用卷积核更小、层数更深的网络效果优于卷积核更大、层数较浅的网络。
  • 之所以选择3*3的卷积核,是因为3*3能学习到更多的特征而且参数更少,更多的3*3比更少的5*5效果更好。

注:卷积层是用来提取特征的,用全连接层来进行分类

从最初的LeNet,有两个卷积层,池化层,有2个全连接层;再到后来的AlexNet,更大更深,激活函数变成了ReLU,也添加了Dropout,数据增强;再到现在的VGG是更大更深的AlexNet,有重复的VGG块。

并且VGG的准确度要比AlexNet要更高。

总结

VGG的代码实现

1、构建VGG块

该函数有三个参数,分别对应于卷积层的数量num_convs、输入通道的数量in_channels 和输出通道的数量out_channels。

并且每经过一次卷积核,它的输入形状和输出形状相同。

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))
    return nn.Sequential(*layers)

2、VGG网络可以分为两部分:第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成。

其中有超参数变量conv_arch。该变量指定了每个VGG块里卷积层个数和输出通道数。

原始VGG网络有5个卷积块,其中前两个块各有一个卷积层,后三个块各包含两个卷积层。 第一个模块有64个输出通道,每个后续模块将输出通道数量翻倍,直到该数字达到512。由于该网络使用8个卷积层和3个全连接层,因此它通常被称为VGG-11。

conv_arch = ((1,64),(1,128),(2,256),(2,512),(2,512))

# 共有5块,8个卷积层,3个全连接层 故为VGG-11

3、VGG网络

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)

4、观察每个层的输出形状,通道数翻倍,高宽减半。

X = torch.rand(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])
'''

5、由于VGG-11比AlexNet计算量更大,构建一个通道数较少的网络,方便计算。

ratio = 4
small_conv_arch = [(pair[0],pair[1]//ratio) for pair in conv_arch]
net = vgg(small_conv_arch)

6、比之前的输出通道减少了4倍。

X = torch.rand(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, 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])
'''

7、训练网络

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,'mps')

网络中的网络NiN

在之前学习了LeNet,AlexNet和VGG,它们都是通过一系列的卷积层与汇聚层来提取空间结构特征,之后通过全连接层对特征的表征进行处理。

AlexNet和VGG都是对LeNet对改进在于扩大加深这两个模块。

如下图,我们可以看到,卷积层需要的参数不会很多,它们的输入通道,输出通道,高宽不会太大,但是在卷积层后的第一个全连接层,将它展平过后,它们的参数需要的越来越多,且非常占用空间。

于是网络中的网络(NiN)提供了一个很简单的解决方案,在每个像素位置应用一个全连接层,可以将其视为1*1的卷积层,1*1卷积代替全连接的意识就是在做加权求和,并且使用1*1卷积充当全连接层,并放在网络中间。

  • NiN块以一个普通卷积层开始,后面是两个1*1的卷积层,这两个1*1卷积层充当带有ReLU激活函数的逐像素全连接层,第一个层的卷积窗口形状由用户设置,随后的卷积窗口形状固定为1*1,简单来说就是替换全连接,有多少个类,就有多少个核的1*1。
  • NiN交替使用NiN块和步幅为2的最大池化层,进行逐步减小高宽和增大通道数目,最后使用全局平均池化层得到输出,输入通道数目是类别数,其中全局平均池化层为该池化层的高宽等于输入的高宽,等价于对每一个通道取平均值。

总结

  • NiN的核心思想是NiN块,NiN块是一个卷积层加上两个1 * 1的卷积层作为全连接层来使用。
  • NiN使用全局平均池化层(通道数量为所需的输出数量)来替代VGG和AlexNet中的全连接层。
  • NiN归根到底是为了减小参数,使用全局平均池化层来代替VGG和AlexNet中的全连接层且不容易过拟合。

NiN的代码实现

1、实现NiN块,其中NiN块最开始的输入通道和输出通道是由用户决定,在经过一次卷积操作后,经过ReLU激活函数,经过连个1*1的卷积层,我们可以看到,输入通道和输出通道一致且输入形状与输出形状一致。

import torch
from torch import nn
from d2l import torch as d2l

def nin_block(in_channels,out_channels,kenerl_size,strides,padding):
    
    return nn.Sequential(
        nn.Conv2d(in_channels,out_channels,kenerl_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()
    )

2、

  • NiN使用窗口形状为11*11,5*5和3*3的卷积层,输出通道数量与AlexNet中的相同。 每个NiN块后有一个最大汇聚层,汇聚窗口形状为3*3,步幅为2。
  • NiN和AlexNet之间的一个显著区别是NiN完全取消了全连接层。 相反,NiN使用一个NiN块,其输出通道数等于标签类别的数量。最后放一个全局平均汇聚层,生成一个对数几率 ,大大减少了模型所需的参数数量。
net = nn.Sequential(
    nin_block(1,96,kenerl_size=11,strides=4,padding=0),
    nn.MaxPool2d(3,stride=2),
    nin_block(96,256,kenerl_size=5,strides=1,padding=2),
    nn.MaxPool2d(3,stride=2),
    nin_block(256,384,kenerl_size=3,strides=1,padding=1),
    nn.MaxPool2d(3,stride=2),
    nn.Dropout(0.5),
    nin_block(384,10,kenerl_size=3,strides=1,padding=1),
    nn.AdaptiveAvgPool2d((1,1)),
    nn.Flatten()
)

3、查看每个层的输出形状,其中MaxPooling改变高宽不改变通道数目;drop减少参数,防止过拟合;全局平均汇聚层为最终每个channel的尺寸;最终为(batch_size,10,1,1)->Flatten->(batch_size,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, 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])
Dropout 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])
'''

4、训练网络

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,'mps')

并行连接的网络GoogLeNet

在2014年的ImageNet图像识别挑战赛中,一个名叫GoogLeNet的网络架构大放异彩。 GoogLeNet吸收了NiN中串联网络的思想,并在此基础上做了改进。

在我们之前学习的网络,有小到1*1的卷积核,有大到11*11的卷积核,那我们最好的卷积层参数是什么呢?

在GoogLeNet中,基本的卷积块被称为Inception块,Inception块的基本思想是"我全要"。

  • Inception块由四条并行路径组成。
  • 前三条路径使用窗口大小为1*1,3*3和5*5的卷积层,从不同空间大小中提取信息。
  • 中间的两条路径在输入上执行1*1卷积,以减少通道数,从而降低模型的复杂性。
  • 第四条路径使用3*3最大汇聚层,然后使用1*1卷积层来改变通道数。
  • 这四条路径都使用合适的填充来使输入与输出的高和宽一致,最后我们将每条线路的输出在通道维度上连结,并构成Inception块的输出。
  • 在Inception块中,通常调整的超参数是每层输出通道数。
  • 因此最后没有改变高宽,最后进行输出通道合并从而增加了通道数目。
  • 里面白色的窗口代表减少通道数,蓝色的窗口代表提取信息,从而增加鲁棒性。

如下图的第一个Inception块的计算如下,输入为192通道,高宽为28*28。

  • 最左边的路径进入1*1的卷积层,将通道数目压缩为64。
  • 第二条路径,先经过1*1的卷积层,将通道数目压缩为96,再经过3*3的卷积层通道数目增加为128,且高宽不改变。
  • 第三条路径,先经过1*1的卷积层,将通道数目压缩为16,再经过5*5的卷积层通道数目增加为32,且高宽不改变。
  • 第四条路径,先经过3*3的最大池化层,不改变通道数目,也不改变高宽,提取信息,再经过1*1的卷积,将通道数目压缩为32。
  • 所以,这四条路径没有改变高宽,第一条路径最后的输出通道为64;第二条路径最后的输出通道为128;第三条路径最后的输出通道为32;第四条路径最后的输出通道为32;最后将它们合并输出为256通道,高宽为28*28。
  • 每条路径的输出通道都不一样,将有限的输出通道分配给不同路径,第二条路径分配的更多,因为3*3的卷积层计算量不大且可以很好的抽取信息,再剩下的分给1*1的卷积层,最后再第三、四条路径平分。

并且使用Inception块比单使用3*3或者5*5的卷积层比,Inception块有更少的参数个数和计算复杂度。

GoogLeNet的架构由5段组成,有9个Inception块,其中在这5段中宽高减半即为一个stage,以池化层做划分的,且最后一个Inception块的的输出通道为标签的类别个数。每一段都在进行通道数目增加,高宽减小的操作,如下图所示:

在我们的stage1和stage2中,没有Inception块,主要做的是更小的高宽和更多的通道,输入为3通道,224*224的高宽,经过stage1和stage2,输出为192通道,高宽为28*28,如下图所示:

在stage3中,串联了两个完整的Inception块和一个最大池化层,输入为192通道,高宽28*28,经过第一个Inception块,通过每条路径的输出通道分配,输出为256通道,高宽不变;紧接着经过第二个Inception块,输入为256通道,最后输出为280通道,高宽不变;再经过3*3的池化层,高宽减半,为480通道,高宽14*14。

在stage4和stage5中,共有7个Inception块,输入为480通道,高宽为14*14,经过stage4的5个Inception块的输出通道为832通道,高宽不变;在经过stage4的3*3最大池化层,高宽减半,最后输出为832通道,高宽为7*7;stage5的输入通道为832,高宽为7*7,经过stage5的2个Inception块的输出通道为1024,有1024维特征输出;再经过全局平均汇聚层,将每个通道的高宽变成1;最后输出变成二维,再通过全连接层为(1024,10)。

到目前为止,Inception块也有很多变种,其中Inception-V3/Inception-V4现在使用也很广泛。

总结:

GoogLeNet的代码实现

1、实现Inception块,其中Inception块分为四条路径,第一条路径为一个1*1的卷积层;第二条路径为一个1*1的卷积层和3*3的卷积层;第三条路径为一个1*1的卷积层和5*5的卷积层;第四条路径为一个3*3的最大池化层和一个1*1的卷积层。

通过我们传递过来的输入通道数进行计算,最后将这四条路径所得到的数据,通道数目拼接起来,返回即可。

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):
        super().__init__()
        # road 1
        self.p1_1 = nn.Conv2d(in_channels,c1,kernel_size=1)
        # road 2
        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)
        # road 3
        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)
        # road 4
        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)))
        # connatation
        return torch.cat((p1,p2,p3,p4),dim=1)

2、实现GoogLeNet的5个stage

第一个stage没有Inception块,输入通道为1,高宽为96*96,经过卷积层,激活函数和最大池化层,最后的输出为(1,64,24,24)

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

3、第二个stage也没有Inception块,输入为(1,64,24,24),最后输出为(1,192,12,12)

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

4、第三个stage,经过两个Inception块,输入为(1,192,12,12),输出为(1,480,6,6)

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、第四个stage,经过5个Inception块,输入为(1,480,6,6),输出为(1,832,6,6)

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

6、第五个stage,经过2个Inception块,输入为(1,832,6,6),输出为(1,1024,1,1),进行展平为(1,1024)

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())

7、将这5个stage连接一起,形成GoogLeNet

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

8、查看每一个stage的形状

X = torch.rand(size=(1,1,96,96))
for layer in net:
    X = layer(X)
    print(layer.__class__.__name__,'output shape:\t',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])
'''

9、进行训练

lr,num_epochs,batch_size = 0.1,10,128
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size,resize=96)
d2l.train_ch6(net,train_iter,test_iter,num_epochs,lr,d2l.try_gpu())

个人总结

这周主要学习了CNN的一些经典网络架构,下周将继续学习其他网络架构,比如ResNet,还有其他的算法,并且阅读相应的文献,理论与实践相结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值