当前位置:   article > 正文

ResNet解读和1×1卷积介绍_resnet 1*1卷积核

resnet 1*1卷积核

1、Introduction

  深度卷积网络好在哪里?

-----它可以加很多层,把网络变得很深,不同程度的层可以得到不同等级的特征。

 作者提出一个问题--一个网络只是简单的堆叠就好了嘛?

——不是,当网络变得很深的时候,梯度会出现爆炸或者消失。一个解决方法,初始化时设置一个合适的权重,不要太大也不要太小。又或者在中间加入normalization(包括BN),可以使得校验每个层之间的那些输出和它梯度的那些均值和方差。上述两个方法可以让网络收敛,但是网络变深后,性能会变差(不是过拟合造成的,因为训练误差和测试误差都会变得很差),所以现在网络虽然收敛了,但是不能得到一个好的效果。

考虑一个比较浅的网络版本和一个比较深的网络版本(所谓深的版本是在所说的浅的网络中多加一些层进去),作者说如果你的浅的网络效果还不错的话,理论上深的网络是不应该变差的,因为深的网络加的那些层,总是可以把那些层学的变成一个恒等映射(identity mapping)。实际上做不到,SGD找不到这样一个优解。

这篇文章提出了一个方法,使得显示的构造出一个 identity mapping,深的网络不会变得比浅的网络差。作者命名为deep residual learning framework 。

2、结构

X 为浅层网络的输出。如果我们想要得到的映射为H(X),则我们让添加的非线性网络层去拟合残差映射F(X):=H(X)-X。原始的映射就可以写成F(X)+X。

shortcut connection快捷连接通常会跳过 1 个或者多个层,在 ResNet 中快捷连接直接运用了 identity mapping,意思就是将一个卷积栈的输入直接与这个卷积栈的输出相加。

这样有什么好处呢?

  1. 并没有增加新的参数
  2. 整个网络也仍然可以由 SGD 进行训练。
  3. 容易被通用的神经网络框架实现。

F(X)和X直接相加,因此需要保证他们的维度一定要一样。

如何处理输入和输出不同形状的情况?

---第一种,在输入和输出上添加一些额外的零,使得这两个形状能够对应起来,然后可以相加。

--第二种,投影,使用一个1×1卷积调整通道,使得形状可以对应起来。(1×1卷积层特点--在空间维度上不做任何的东西,主要是在通道维度上做改变,选择一个1×1卷积使得输出通道是输入通道的两倍,这样就能将残差连接的输入和输出对应起来。在ResnNet里面,如果把输出通道数翻了两倍,那么输入的高和宽会减小一半,所以这里步幅设置为2,使在高宽和通道上都能匹配上)。第三种,所有连接都做投影。(这里在论文中有做实验比较三种处理方法的优劣)

各种层数的残差结构

残差网络结构(以34层为例)--VGG-19是浅层网络

 更深的残差网络结构(50层以上),结构有所不同,设计了一个bottleneck结构

 通道数为256时,变得很大,出现的问题是计算复杂度会很高,这里做法是通过1×1卷积投影映射回64维,再做一个3×3通道数不变的卷积,然后再通过1×1卷积投影回去256维,因为输入是256维u,输出要匹配上,这样设计之后复杂度就跟左图差不多了。

补充-关于1×1卷积

理解1×1卷积的作用_dxwell6的博客-CSDN博客_1×1卷积

1*1卷积核的作用_nefetaria的博客-CSDN博客_1*1卷积核的作用

详细学习1*1卷积核_来一包板栗的博客-CSDN博客_1*1卷积

1×1卷积就是将卷积核的尺寸设置为1×1,如图所示

 1×1卷积的意义

  1. 跨通道的特征整合

      如果当前层和下一层都只有一个通道那么1×1卷积核确实没什么作用,但是如果它们分别为m层和n层的话,1×1卷积核可以起到一个跨通道聚合的作用,所以进一步可以起到降维(或者升维)的作用,起到减少参数的目的。

    一个例子来直观地介绍1x1卷积。输入6x6x1的矩阵,这里的1x1卷积形式为1x1x1,即为元素2,输出也是6x6x1的矩阵。但输出矩阵中的每个元素值是输入矩阵中每个元素值x2的结果。

上述情况,并没有显示1x1卷积的特殊之处,那是因为上面输入的矩阵channel为1,所以1x1卷积的channel也为1。这时候只能起到升维的作用。这并不是1x1卷积的魅力所在。 

当输入为6x6x32时,1x1卷积的形式是1x1x32,当只有一个1x1卷积核的时候,此时输出为6x6x1。此时便可以体会到1x1卷积的实质作用:降维。当1x1卷积核的个数小于输入channels数量时,即降维。

注:1x1卷积一般只改变输出通道数(channels),而不改变输出的宽度和高度

2.  特征通道的升维和降维

 

  1. 3、减少卷积核参数(简化模型)

可以看到1×1卷积核通过控制卷积核的数量来进行降维和升维
网络深度由原来的2层变成了三层,广泛使用1×1卷积可以增加模型的深度,增加模型非线性能力

应用一

Inception网络

按照上面的说法,我们的这层的模型参数与输入的特征维数(28x28x192),卷积核大小以及卷积通道数(包括三种卷积核,分别是1x1x64,3x3x128,5x5x32),所以参数为:

参数:(1×1×192×64) + (3×3×192×128) + (5×5×192×32) = 153600
最终输出的feature map个数:64+128+32+192 = 416

池化层不引人参数!

feature map个数就是filter个数,一个滤波器在前一个feature map上进行一次卷积操作(特征抽取)会产生一个feature map或者叫通道、深度。

上图中在3x3,5x5 卷积层前新加入的1x1的卷积核为96和16通道的,max pooling后加入的1x1卷积为32通道。

图中该层的参数:

参数:(1×1×192×64)+(1×1×192×96+3×3×96×128)+(1×1×192×16+5×5×16×32)+(1x1x32)=15904
最终输出的feature map个数: 64+128+32+32=256

所以,加入1×1的卷积后,在降低大量运算的前提下,降低了维度。

 应用二 

ResNet网络

resnet代码(动手深度学习中的)

  1. # ResNet
  2. #首先来看一下残差块block的实现
  3. '''torch.nn是pytorch中自带的一个函数库,里面包含了神经网络中使用的一些常用函数,
  4. 如具有可学习参数的nn.Conv2d(),nn.Linear()和不具有可学习的参数(如ReLU,pool,DropOut等)(后面这几个是在nn.functional中),
  5. 这些函数可以放在构造函数中,也可以不放。
  6. torch.nn的应用:
  7. 通常引入的时候写成:
  8. import torch.nn as nn
  9. import torch.nn.functional as F'''
  10. import torch
  11. from torch import nn
  12. from torch.nn import functional as F
  13. from d2l import torch as d2l
  14. #一个残差网络层 包括两个卷积层和一个旁路支路
  15. # input_channels通道数 num_channels输出通道数 use_1x1conv是否使用1*1卷积 strides步长
  16. # 声明一个类 并继承自nn.Module # nn.Module是所有网络模型结构的基类,无论是pytorch自带的模型,还是要自定义模型,都需要继承这个类
  17. class Residual(nn.Module): #@save
  18. def __init__(self, input_channels, num_channels,
  19. use_1x1conv=False, strides=1):
  20. # 调用父类构造函数,super指的就是nn.Module这个基类
  21. super().__init__()
  22. # 一个残差块包含两个卷积层,第一个卷积层通常改变输入输出通道数,并且改变输出的尺寸的形状大小,
  23. # 第二个卷积层输入输出通道数通常不会改变,每一个卷积层会跟着一个批量规范层
  24. '''Conv2d --convolution layers中的一种 常见的还有Conv1d Conv3d
  25. 作用:对由多个输入平面组成的输入信号进行二维卷积
  26. 从整体上看,它是一个类,包含了卷积运算所需要的参数(__init__函数),以及卷积操作(forward函数)。'''
  27. self.conv1 = nn.Conv2d(input_channels, num_channels,
  28. kernel_size=3, padding=1, stride=strides)
  29. self.conv2 = nn.Conv2d(num_channels, num_channels,
  30. kernel_size=3, padding=1)
  31. if use_1x1conv: # 如果要使用1*1卷积
  32. self.conv3 = nn.Conv2d(input_channels, num_channels,
  33. kernel_size=1, stride=strides)
  34. # #当输入输出通道数(同时输出的形状大小也会改变)改变后需要加一个1x1卷积层,来改变输入X的形状大小和通道数
  35. else:
  36. self.conv3 = None
  37. self.bn1 = nn.BatchNorm2d(num_channels)
  38. self.bn2 = nn.BatchNorm2d(num_channels)
  39. self.relu = nn.ReLU(inplace=True)
  40. def forward(self, X):# 定义执行顺序的默认函数--这里是从里到外
  41. Y = F.relu(self.bn1(self.conv1(X)))
  42. Y = self.bn2(self.conv2(Y))
  43. if self.conv3:
  44. X = self.conv3(X)
  45. Y += X
  46. # #将输入经过两层卷积层得到的输出Y再与输入X相加后,再经过ReLU()激活函数,必须保证X和Y的通道数和尺寸形状大小相同
  47. return F.relu(Y)
  48. #查看输入和输出形状一致,不改变输入的通道数和尺寸大小
  49. residual = Residual(3,3)
  50. X = torch.rand(4, 3, 6, 6)
  51. Y = residual (X)
  52. print(Y.shape)
  53. #查看增加输出通道数的同时,并减半输入的高和宽
  54. residual =Residual(3,6,use_1x1conv2d=True,stride=2)
  55. X = torch.randn(size=(4,3,6,6))
  56. Y = residual(X)
  57. print(Y.shape)
  58. '''
  59. 输出结果:
  60. torch.Size([4, 3, 6, 6])
  61. torch.Size([4, 6, 3, 3]) 通道加倍 高宽减半
  62. '''

上述代码生成两种类型的网络: 一种是当use_1x1conv=False时,应用ReLU非线性函数之前,将输入添加到输出。 
另一种是当use_1x1conv=True时,添加通过卷积调整通道和分辨率。

resnet模型

1、ResNet的前两层跟之前介绍的GoogLeNet中的一样: 在输出通道数为64、步幅为2的 7×7 卷积层后,接步幅为2的 3×3 的最大汇聚层。 不同之处在于ResNet每个卷积层后增加了批量规范化层。

  1. #ResNet第一个模块跟GoogleNet第一个模块相同
  2. '''torch.nn.Sequential
  3. 一个序列容器,用于搭建神经网络的模块被按照被传入构造器的顺序添加到nn.Sequential()容器中。
  4. 除此之外,一个包含神经网络模块的OrderedDict也可以被传入nn.Sequential()容器中。
  5. 利用nn.Sequential()搭建好模型架构,模型前向传播时调用forward()方法,
  6. 模型接收的输入首先被传入nn.Sequential()包含的第一个网络模块中。
  7. 然后,第一个网络模块的输出传入第二个网络模块作为输入,按照顺序依次计算并传播,
  8. 直到nn.Sequential()里的最后一个模块输出结果。'''
  9. b1 = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=64,kernel_size=7,padding=3,stride=2),
  10. nn.BatchNorm2d(64),
  11. nn.ReLU(),
  12. nn.MaxPool2d(kernel_size=3,padding=1,stride=2))

2、GoogLeNet在后面接了4个由Inception块组成的模块。 ResNet则使用4个由残差块组成的模块,每个模块使用若干个同样输出通道数的残差块。 第一个模块的输出通道数同输入通道数一致。 由于之前已经使用了步幅为2的最大汇聚层,所以无须减小高和宽。 之后的每个模块在第一个残差块里将上一个模块的输出通道数翻倍,并将高和宽减半, 下面代码来实现这个模块。注意,我们对第一个残差网络模块做了特别处理。

  1. #定义一个ResNet块,通常包含两个残差块Residul块(也即是包含两个残差网络层),
  2. #一个ResNet块通常通道数加倍,尺寸形状高和宽减半,对应到由第一个残差块输出通道是输入通道两倍,尺寸大小减半,
  3. # 第二个残差块输入输出通道数相同,输入输出尺寸形状大小不变,
  4. # 但除开第二个ResNet块,因为第一个ResNet块将输入尺寸形状大小降低了4倍(已经减半过了)
  5. def resnet_block(input_channels,output_channels,num_residuls,first_block=False):
  6. block = []
  7. for i in range(num_residuls):
  8. if i==0 and not first_block:
  9. block.append(Residul(input_channels=input_channels,output_channels=output_channels,use_1x1conv2d=True,stride=2))
  10. else:
  11. block.append(Residul(input_channels=output_channels,output_channels=output_channels))
  12. return block

3、接着在ResNet加入所有残差块,每个模块使用2个残差块。

  1. # *在python中是解包
  2. b2 = nn.Sequential(*resnet_block(64,64,2,True))#第二个ResNet块,输入输出通道数不变,输入输出尺寸形状大小不变
  3. b3 = nn.Sequential(*resnet_block(64,128,2,False))#第三个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
  4. b4 = nn.Sequential(*resnet_block(128,256,2,False))#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
  5. b5 = nn.Sequential(*resnet_block(256,512,2,False))#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2

4、最后,与GoogLeNet一样,在ResNet中加入全局平均汇聚层,以及全连接层输出。

  1. resnet = nn.Sequential(b1,b2,b3,b4,b5,
  2. nn.AdaptiveAvgPool2d((1,1)),
  3. nn.Flatten(),
  4. nn.Linear(in_features=512,out_features=10))

5、在训练ResNet之前看一下ResNet中不同模块的输出形状是如何变化的。 基本在所有架构中都是将分辨率降低,通道数量增加,直到全局平均汇聚层聚集所有特征。

  1. #查看每一层输出的通道数和形状尺寸大小
  2. X = torch.randn(size=(1,1,224,224))
  3. for layer in resnet:
  4. X = layer(X)
  5. print(layer.__class__.__name__," output shape :\t",X.shape)
  6. '''
  7. 输出结果如下:
  8. Sequential output shape: torch.Size([1, 64, 56, 56])
  9. Sequential output shape: torch.Size([1, 64, 56, 56])
  10. Sequential output shape: torch.Size([1, 128, 28, 28])
  11. Sequential output shape: torch.Size([1, 256, 14, 14])
  12. Sequential output shape: torch.Size([1, 512, 7, 7])
  13. AdaptiveAvgPool2d output shape: torch.Size([1, 512, 1, 1])
  14. Flatten output shape: torch.Size([1, 512])
  15. Linear output shape: torch.Size([1, 10])
  16. '''

6、ResNet模型训练和测试

  1. lr,num_epochs,batch_size = 0.05,10,64
  2. train_iter,test_iter = d2l.torch.load_data_fashion_mnist(batch_size,resize=224)
  3. d2l.torch.train_ch6(resnet,train_iter,test_iter,num_epochs,lr,device=d2l.torch.try_gpu())

上述代码实现的resnet结构如下图:

 

  1. // resnet完整代码(李沐)
  2. import d2l.torch
  3. import torch
  4. from torch.nn import functional as F
  5. from torch import nn
  6. #一个残差网络层,包含两个卷积层和一个旁路支路
  7. class Residul(nn.Module):
  8. def __init__(self,input_channels,output_channels,use_1x1conv2d=False,stride=1):
  9. super(Residul, self).__init__()
  10. #一个残差块包含两个卷积层,第一个卷积层通常改变输入输出通道数,并且改变输出的尺寸的形状大小,第二个卷积层输入输出通道数通常不会改变,每一个卷积层会跟着一个批量规范层
  11. self.conv2d_1 = nn.Conv2d(in_channels=input_channels,out_channels=output_channels,kernel_size=3,padding=1,stride=stride)
  12. self.conv2d_2 = nn.Conv2d(in_channels=output_channels,out_channels=output_channels,kernel_size=3,padding=1,stride=1)
  13. self.bn1 = nn.BatchNorm2d(num_features=output_channels)
  14. self.bn2 = nn.BatchNorm2d(num_features=output_channels)
  15. #当输入输出通道数(同时输出的形状大小也会改变)改变后需要加一个1x1卷积层,来改变输入X的形状大小和通道数
  16. if use_1x1conv2d:
  17. self.conv2d_3 = nn.Conv2d(in_channels=input_channels,out_channels=output_channels,kernel_size=1,stride=stride)
  18. else:
  19. self.conv2d_3 = None
  20. def forward(self,X):
  21. Y = F.relu(self.bn1(self.conv2d_1(X)))
  22. Y = self.bn2(self.conv2d_2(Y))
  23. if self.conv2d_3:
  24. X = self.conv2d_3(X)
  25. Y +=X
  26. #将输入经过两层卷积层得到的输出Y再与输入X相加后,再经过ReLU()激活函数,必须保证X和Y的通道数和尺寸形状大小相同
  27. return F.relu(Y)
  28. residul =Residul(3,6,use_1x1conv2d=True,stride=2)
  29. X = torch.randn(size=(4,3,6,6))
  30. Y = residul(X)
  31. print(Y.shape)
  32. #ResNet第一个模块跟GoogleNet第一个模块相同
  33. b1 = nn.Sequential(nn.Conv2d(in_channels=1,out_channels=64,kernel_size=7,padding=3,stride=2),
  34. nn.BatchNorm2d(64),
  35. nn.ReLU(),
  36. nn.MaxPool2d(kernel_size=3,padding=1,stride=2))
  37. #定义一个ResNet块,通常包含两个残差块Residul块(也即是包含两个残差网络层),一个ResNet块通常通道数加倍,尺寸形状高和宽减半,对应到由第一个残差块输出通道是输入通道两倍,尺寸大小减半,第二个残差块输入输出通道数相同,输入输出尺寸形状大小不变,但除开第二个ResNet块,因为第一个ResNet块将输入尺寸形状大小降低了4
  38. def resnet_block(input_channels,output_channels,num_residuls,first_block=False):
  39. block = []
  40. for i in range(num_residuls):
  41. if i==0 and not first_block:
  42. block.append(Residul(input_channels=input_channels,output_channels=output_channels,use_1x1conv2d=True,stride=2))
  43. else:
  44. block.append(Residul(input_channels=output_channels,output_channels=output_channels))
  45. return block
  46. # *在python中是解包
  47. b2 = nn.Sequential(*resnet_block(64,64,2,True))#第二个ResNet块,输入输出通道数不变,输入输出尺寸形状大小不变
  48. b3 = nn.Sequential(*resnet_block(64,128,2,False))#第三个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
  49. b4 = nn.Sequential(*resnet_block(128,256,2,False))#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
  50. b5 = nn.Sequential(*resnet_block(256,512,2,False))#第二个ResNet块,输出通道数是输入通道数2倍,则输出尺寸形状是输入尺寸形状高和宽的1/2
  51. resnet = nn.Sequential(b1,b2,b3,b4,b5,
  52. nn.AdaptiveAvgPool2d((1,1)),
  53. nn.Flatten(),
  54. nn.Linear(in_features=512,out_features=10))
  55. X = torch.randn(size=(1,1,224,224))
  56. for layer in resnet:
  57. X = layer(X)
  58. print(layer.__class__.__name__," output shape :\t",X.shape)
  59. lr,num_epochs,batch_size = 0.05,10,64
  60. train_iter,test_iter = d2l.torch.load_data_fashion_mnist(batch_size,resize=224)
  61. d2l.torch.train_ch6(resnet,train_iter,test_iter,num_epochs,lr,device=d2l.torch.try_gpu())
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/92542
推荐阅读
  

闽ICP备14008679号