[关闭]
@Team 2018-09-02T22:45:08.000000Z 字数 15723 阅读 3997

卷积神经网络[AlexNet,VGG,GoodLeNet,resNet(还没写),Xception,SqueezeNet,shufflenet(没写),MobileNet]

石文华


一、AlexNet
AlexNet,是以论文的第一作者 Alex Krizhevsky 的名
字命名的,另外两位合著者是 ilya Sutskever 和 Geoffery Hinton。在2012年的ILSVRC比赛中,作者通过这个模型获得了冠军。如下图是这个卷积神经网络模型的网络架构
image.png-157.6kB
可以看到网络从输入到输出分别是输入层、Conv1层,MaxPool1层、Conv2层,MaxPool2层、Conv3层,Conv4层,Conv5层,MaxPool3层、FC6层、FC7层、FC8层和OutPut层。
(1)输入层:AlexNet 首先用一张 227×227×3 的图片作为输入,实际上原文中使用的图像是 224×224×3,但是如果你尝试去推导一下,你会发现 227×227 这个尺寸更好一些,这里使用输入数据是224*224*3的图像进行推导,即长宽都是224的图像,色彩通道是R、G、B三个。
(2)Conv1层:第一个卷积层、使用的卷积核大小为11*11*3,步长为4、Padding为2,最后输出的特征图为55*55*96,其中55=(224-11+4)/2+1,输出深度为96,是因为使用了96个卷积核进行卷积。最后得到特征图维度是55*55*96
(3)MaxPool1层:卷积之后使用最大池化,池化的滑动窗口是3*3*96,步长为2,池化之后输出的特征图的长宽是27,27=(55-3)/2+1,所以池化之后的特征图维度是27*27*96。
(4)Conv2层:使用卷积核大小为5*5*96,步长为1,Padding设置为same,这里为2,因为要确保输出长度为27,所以27=(27-5+2*a)/1+1,所以也可知Padding为2。这里使用了256个卷积核进行卷积,所以最后输出的特征图是27*27*256。
(5)MaxPool2层:滑动窗口大小为3*3*256,步长为2,得到的长宽为13,13=(27-3)/2+1,最后得到的特征图大小为13*13*256。
(6)Conv3层:卷积核大小为3*3*256,步长为1,Padding为1,得到特征图的长宽为13,13=(13-3+2)/1+1=13,使用384个卷积核进行卷积。得到输出为13*13*384。
(7)Conv4层:卷积核大小为3*3*384,步长为1,Padding为1,得到特征图的长宽为13,13=(13-3+2)/1+1=13,使用384个卷积核进行卷积。得到输出为13*13*384。
(8)Conv5层:卷积核大小为3*3*384,步长为1,Padding为1,得到特征图的长宽为13,13=(13-3+2)/1+1=13,使用256个卷积核进行卷积。得到输出为13*13*256。
(9)MaxPool3:滑动窗口大小为3*3*256,步长为2,得到的长宽为6,6=(13-3)/2+1,最后得到的特征图大小为6*6*256。
(10)FC6层:第一个全连接层,输入特征维度是6*6*256,首先先对输入的特征图进行扁平化处理,将维度变为1*9216的输入特征图,因为要求输出的数据维度为1*4096,所以需要一个9216*4096的矩阵完成输入数据和输出数据的全连接,最后得到输出数据的维度为1*4096。
(11)FC7层:输入数据维度是1*4096,输出特征要求为1*4096,所以需要4096*4096的矩阵完成输入数据和输出数据的全连接,随后得到输出数据的维度是1*4096。
(12)FC8层:输入数据维度是1*4096,输出特征要求为1*1000,所以需要4096*1000的矩阵完成输入数据和输出数据的全连接,随后得到输出数据的维度是1*1000。
(13)OutPut层:输出图像对应的1000个类别的可能性值,将FC7层的输出数据传递到Softma激活函数中,能够得到1000个全新的输出值,这1000个输出值就是模型预测的输入图像对应1000个类别的可能性。
AlexNet 网络结构看起来相对复杂,包含约 6000 万个参数, 这些数字(55×55×96、 27×27×96、
27×27×256……) 都是 Alex Krizhevsky 及其合著者不得不给出的,。
注:AlexNet网络卷积之后的激活函数是relu,每个全连接层后面加上Dropout层减少模型的过拟合问题。

  1. import torch
  2. import torch.nn as nn
  3. class AlexNet(nn.Module):
  4. def __init__(self, num_classes=10):
  5. super(AlexNet, self).__init__()
  6. self.features = nn.Sequential(
  7. nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=0),
  8. nn.ReLU(inplace=True),
  9. nn.MaxPool2d(kernel_size=3, stride=2),
  10. nn.Conv2d(96, 256, kernel_size=5, padding=2, groups=2),
  11. nn.ReLU(inplace=True),
  12. nn.MaxPool2d(kernel_size=3, stride=2),
  13. nn.Conv2d(256, 384, kernel_size=3, padding=1),
  14. nn.ReLU(inplace=True),
  15. nn.Conv2d(384, 384, kernel_size=3, padding=1, groups=2),
  16. nn.ReLU(inplace=True),
  17. nn.Conv2d(384, 256, kernel_size=3, padding=1, groups=2),
  18. nn.ReLU(inplace=True),
  19. nn.MaxPool2d(kernel_size=3, stride=2),
  20. )
  21. self.classifier = nn.Sequential(
  22. nn.Linear(256 * 6 * 6, 4096),
  23. nn.ReLU(inplace=True),
  24. nn.Dropout(),
  25. nn.Linear(4096, 4096),
  26. nn.ReLU(inplace=True),
  27. nn.Dropout(),
  28. nn.Linear(4096, num_classes),
  29. )
  30. def forward(self, x):
  31. x = self.features(x)
  32. x = x.view(x.size(0), 256 * 6 * 6)
  33. x = self.classifier(x)
  34. return x
  35. alexnet=AlexNet()
  36. print(alexnet)

输出结果:

  1. AlexNet(
  2. (features): Sequential(
  3. (0): Conv2d(3, 96, kernel_size=(11, 11), stride=(4, 4))
  4. (1): ReLU(inplace)
  5. (2): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  6. (3): Conv2d(96, 256, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2), groups=2)
  7. (4): ReLU(inplace)
  8. (5): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  9. (6): Conv2d(256, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  10. (7): ReLU(inplace)
  11. (8): Conv2d(384, 384, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=2)
  12. (9): ReLU(inplace)
  13. (10): Conv2d(384, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), groups=2)
  14. (11): ReLU(inplace)
  15. (12): MaxPool2d(kernel_size=3, stride=2, padding=0, dilation=1, ceil_mode=False)
  16. )
  17. (classifier): Sequential(
  18. (0): Linear(in_features=9216, out_features=4096, bias=True)
  19. (1): ReLU(inplace)
  20. (2): Dropout(p=0.5)
  21. (3): Linear(in_features=4096, out_features=4096, bias=True)
  22. (4): ReLU(inplace)
  23. (5): Dropout(p=0.5)
  24. (6): Linear(in_features=4096, out_features=10, bias=True)
  25. )
  26. )

加载数据:

  1. import torch
  2. from torchvision import datasets,transforms
  3. import os
  4. import matplotlib.pyplot as plt
  5. import time
  6. #transform = transforms.Compose是把一系列图片操作组合起来,比如减去像素均值等。
  7. #DataLoader读入的数据类型是PIL.Image
  8. #这里对图片不做任何处理,仅仅是把PIL.Image转换为torch.FloatTensor,从而可以被pytorch计算
  9. transform = transforms.Compose(
  10. [
  11. transforms.Scale([224,224]),
  12. transforms.ToTensor(),
  13. #transforms.Normalize(mean=[0.5,0.5,0.5],std=[0.5,0.5,0.5])
  14. ]
  15. )
  16. #训练集
  17. train_set = datasets.CIFAR10(root='drive/pytorch/Alexnet/', train=True, transform=transform, target_transform=None, download=True)
  18. #测试集
  19. test_set=datasets.CIFAR10(root='drive/pytorch/Alexnet/',train=False,download=True,transform=transform)
  20. trainloader=torch.utils.data.DataLoader(train_set,batch_size=32,shuffle=True,num_workers=0)
  21. testloader=torch.utils.data.DataLoader(test_set,batch_size=32,shuffle=True,num_workers=0)
  22. classes=('plane','car','bird','cat','deer','dog','frog','horse','ship','truck')
  23. (data,label)=train_set[64]
  24. print(classes[label])

查看数据:

  1. X_example,y_example=next(iter(trainloader))
  2. print(X_example.shape)
  3. img=X_example.permute(0, 2, 3, 1)
  4. print(img.shape)
  5. import torchvision
  6. img=torchvision.utils.make_grid(X_example)
  7. img=img.numpy().transpose([1,2,0])
  8. import matplotlib.pyplot as plt
  9. plt.imshow(img)
  10. plt.show()

image.png-197.1kB
训练模型:

  1. import torch.optim as optim #导入torch.potim模块
  2. import time
  3. from torch.autograd import Variable # 这一步还没有显式用到variable,但是现在写在这里也没问题,后面会用到
  4. import torch.nn as nn
  5. import torch.nn.functional as F
  6. criterion = nn.CrossEntropyLoss() #同样是用到了神经网络工具箱 nn 中的交叉熵损失函数
  7. optimizer = optim.Adam(alexnet.classifier.parameters(), lr=0.0001) #optim模块中的SGD梯度优化方式---随机梯度下降
  8. epoch_n=5
  9. for epoch in range(epoch_n):
  10. print("Epoch{}/{}".format(epoch,epoch_n-1))
  11. print("-"*10)
  12. running_loss = 0.0 #定义一个变量方便我们对loss进行输出
  13. running_corrects=0
  14. for i, data in enumerate(trainloader, 1): # 这里我们遇到了第一步中出现的trailoader,代码传入
  15. inputs, labels = data # data是从enumerate返回的data,包含数据和标签信息,分别赋值给inputs和labels
  16. #inputs=inputs.permute(0, 2, 3, 1)
  17. #print("hahah",len(labels))
  18. y_pred = alexnet(inputs) # 把数据输进网络net,这个net()在第二步的代码最后一行我们已经定义了
  19. _,pred=torch.max(y_pred.data,1)
  20. optimizer.zero_grad() # 要把梯度重新归零,因为反向传播过程中梯度会累加上一次循环的梯度
  21. loss = criterion(y_pred, labels) # 计算损失值,criterion我们在第三步里面定义了
  22. loss.backward() # loss进行反向传播,下文详解
  23. optimizer.step() # 当执行反向传播之后,把优化器的参数进行更新,以便进行下一轮
  24. # print statistics # 这几行代码不是必须的,为了打印出loss方便我们看而已,不影响训练过程
  25. running_loss += loss.item() # 从下面一行代码可以看出它是每循环0-1999共两千次才打印一次
  26. running_corrects+=torch.sum(pred==labels.data)
  27. if(i % 2 == 0): # print every 2000 mini-batches 所以每个2000次之类先用running_loss进行累加
  28. print("Batch{},Train Loss:{:.4f},Train ACC:{:.4f}".format(i,running_loss/i,100*running_corrects/(32*i)))

二、VGGNet模型
VGGNet由牛津大学的视觉几何组提出,并在2014年举办的ILSVRC中获得了定位任务第一名和分类任务第二名的好成绩,相比AlexNet而言,VGGNet模型中统一了卷积中的使用参数,卷积核的长度宽度为3*3,卷积核步长统一为1,padding统一为1,并且增加了卷积神经网络的深度。下图是VGG网络的架构图:
image.png-327.3kB
好吧,上面那张图看起来眼花缭乱,特意找了一张VGG16网络结构图,如下:
image.png-378.6kB
VGG-16维度的变换情况可以参考AlexNet文中的方式进行推导。由图可以看到,VGG-16 是一种只需要专注于构建卷积层的简单网络。首
先用 3×3,步幅为 1 的过滤器构建卷积层, padding 参数为 same 卷积中的参数。然后用一个
2×2,步幅为 2 的过滤器构建最大池化层。因此 VGG 网络的一大优点是它确实简化了神经网
络结构。VGG-16 的这个数字 16,就是指在这个网络中包含 16 个卷积层和全连接
层。确实是个很大的网络,总共包含约 1.38 亿个参数,即便以现在的标准来看都算是非常
大的网络。但 VGG-16 的结构并不复杂,这点非常吸引人,而且这种网络结构很规整,都是
几个卷积层后面跟着可以压缩图像大小的池化层,池化层缩小图像的高度和宽度。同时,卷
积层的过滤器数量变化存在一定的规律,由 64 翻倍变成 128,再到 256 和 512。作者可能认
为 512 已经足够大了,所以后面的层就不再翻倍了。无论如何,每一步都进行翻倍,或者说
在每一组卷积层进行过滤器翻倍操作,正是设计此种网络结构的另一个简单原则。这种相对
一致的网络结构对研究者很有吸引力,而它的主要缺点是需要训练的特征数量非常巨大。

  1. import torch
  2. import torch.nn as nn
  3. class VGGNet16(nn.Module):
  4. def __init__(self,num_classes=10):
  5. super(VGGNet16,self).__init__()
  6. self.Conv=nn.Sequential(
  7. nn.Conv2d(3,64,kernel_size=3,stride=1,padding=1),
  8. nn.ReLU(),
  9. nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1),
  10. nn.ReLU(),
  11. nn.MaxPool2d(kernel_size=2,stride=2),
  12. nn.Conv2d(64,128,kernel_size=3,stride=1,padding=1),
  13. nn.ReLU(),
  14. nn.Conv2d(128,128,kernel_size=3,stride=1,padding=1),
  15. nn.ReLU(),
  16. nn.MaxPool2d(kernel_size=2,stride=2),
  17. nn.Conv2d(128,256,kernel_size=3,stride=1,padding=1),
  18. nn.ReLU(),
  19. nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1),
  20. nn.ReLU(),
  21. nn.Conv2d(256,256,kernel_size=3,stride=1,padding=1),
  22. nn.ReLU(),
  23. nn.MaxPool2d(kernel_size=2,stride=2),
  24. nn.Conv2d(256,512,kernel_size=3,stride=1,padding=1),
  25. nn.ReLU(),
  26. nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1),
  27. nn.ReLU(),
  28. nn.Conv2d(512,512,kernel_size=3,stride=1,padding=1),
  29. nn.ReLU(),
  30. nn.MaxPool2d(kernel_size=2,stride=2)
  31. )
  32. self.classifier = nn.Sequential(
  33. nn.Linear( 6 * 6*512, 1024),
  34. nn.ReLU(),
  35. nn.Dropout(p=0.5),
  36. nn.Linear(1024,1024),
  37. nn.ReLU(),
  38. nn.Dropout(p=0.5),
  39. nn.Linear(1024, num_classes),
  40. )
  41. def forward(self,inputs):
  42. x=self.Conv(inputs)
  43. x=x.view(-1,4*4*512)
  44. x=self.classifier(x)
  45. return x
  46. vgg=VGGNet16()
  47. print(vgg)

输出结果:

  1. VGGNet16(
  2. (Conv): Sequential(
  3. (0): Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  4. (1): ReLU()
  5. (2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  6. (3): ReLU()
  7. (4): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  8. (5): Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  9. (6): ReLU()
  10. (7): Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  11. (8): ReLU()
  12. (9): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  13. (10): Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  14. (11): ReLU()
  15. (12): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  16. (13): ReLU()
  17. (14): Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  18. (15): ReLU()
  19. (16): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  20. (17): Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  21. (18): ReLU()
  22. (19): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  23. (20): ReLU()
  24. (21): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  25. (22): ReLU()
  26. (23): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  27. )
  28. (classifier): Sequential(
  29. (0): Linear(in_features=18432, out_features=1024, bias=True)
  30. (1): ReLU()
  31. (2): Dropout(p=0.5)
  32. (3): Linear(in_features=1024, out_features=1024, bias=True)
  33. (4): ReLU()
  34. (5): Dropout(p=0.5)
  35. (6): Linear(in_features=1024, out_features=1000, bias=True)
  36. )
  37. )

三、GoodLeNet
GoodLeNet在2014年举办的ILSVRC中获得了分类任务第一名,与同年在分类任务中获得亚军的VGGNet模型相比,它的深度更深,达到22层。该网络引入Inception模块,Inception能够代替人工来确定卷积层中的过滤器类型,或者确定是否需要创建卷积层或者池化层,比如,在Alexnet和vggnet中,可以看到使用过11*11、5*5、3*3的卷积核,这些卷积核的大小都是我们人工选定的。
1、Inception单元结构
最初开始设计的Inception 模块(Native Inception)。它使用 3 个不同大小的滤波器(1x1、3x3、5x5)对输入执行卷积操作,此外它还会执行最大池化。最后将所有子层的输出合并起来,作为输出的特征图。
image.png-110.1kB
假设输入是28*28*192的特征图。
(1)使用64个1*1卷积核,输出结果会是28*28*64;
(2)使用128个3*3的卷积核,为了使最后不同卷积核得到的特征图合并时维度匹配,padding为same,使得输出维度还是28*28,那么得到的特征图维度是28*28*128.
(3)使用32个5*5的卷积核,padding为same,输出特征图为28*28*32.
(4)使用maxpool,在最大池化之前,设置padding为same。步长为1,然后再进行最大池化,使得输出特征图维度为28*28*192,192个通道数太大,为了避免最后输出时,池化层
占据大多数的通道。使用32个1*1的卷积压缩通道数,最后得到的特征图维度是28*28*32。
将上述输出的特征图叠加在一起,那么最后得到特度特征图大小为28*28*(64+128+32+32),如下图所示:
image.png-132.9kB
上述的结构存在一个非常大的问题,就是计算成本特别的大,还是以28*28*192的输入特征图为例子,重点来看5*5的过滤器,由于输出特征图有192个通道,所以一个过滤器的参数为5*5*192。由于期望得到的输出特征图通道为32,长宽为28,所以需要计算28*28*32次。最后乘法运算的总次数为每个输出值所需要执行的乘法运算次数
(5×5×192)乘以输出值个数(28×28×32),把这些数相乘结果等于 1.2 亿(120422400) 。运算成本是非常高的。
解决这个问题的方法就是先使用1*1卷积,压缩通道数,比如像将输入特征图的通道数压缩为16,然后在这个压缩之后的特征图上使用32个5*5的卷积核进行卷积。
image.png-45.1kB
计算量为(1*1*192*28*28*16)+(5*5*16*28*28*32)=2408448+10035200,约等于1204万,相比之前的1.2亿下降到了原来的十分之一。
因此,Native Inception结构使用1*1的卷积之后变为(Inception V1):
image.png-132.7kB
利用实现降维的 Inception 模块可以构建 GoogLeNet(Inception v1),其架构如下图所示:
image.png-126.8kB
2、Inception V2
将 5×5 的卷积分解为两个 3×3 的卷积运算以提升计算速度。因为一个 5×5 的卷积在计算成本上是一个 3×3 卷积的 2.78 倍。所以叠加两个 3×3 卷积实际上在性能上会有所提升,将 n*n 的卷积核尺寸分解为 1×n 和 n×1 两个卷积。例如,一个 3×3 的卷积等价于首先执行一个 1×3 的卷积再执行一个 3×1 的卷积。他们还发现这种方法在成本上要比单个 3×3 的卷积降低 33%。如下图所示:
image.png-309.2kB

3、Inception v3
V3整合了前面v2 中提到的所有升级,还使用了RMSProp 优化器、Factorized 7x7 卷积、辅助分类器使用了 BatchNorm;标签平滑(添加到损失公式的一种正则化项,旨在阻止网络对某一类别过分自信,即阻止过拟合)
注:Inception v4和Inception -resnet可以阅读论文:https://arxiv.org/pdf/1602.07261.pdf

  1. from torchvision import dmodels
  2. inceptionv3=models.inception_v3(pretrained=True)
  3. print(inceptionv3)

参考文献:
http://baijiahao.baidu.com/s?id=1601882944953788623&wfr=spider&for=pc
Andrew Ng 《Deep Learning》
https://blog.csdn.net/u014114990/article/details/52583912

四、Xception
Xception目的不在于模型压缩,而是提高性能,
卷积神经网络近年来已成为计算机视觉中的主流算法,对于常见的神经网络结构,比如AlexNet,VGG,goodlenet,resnet等,在进行卷积操作的时候,都是使用3D的卷积核(具有2个空间维度(宽度和高度)和通道尺寸)对特征图进行卷积操作的,即卷积核需要同时学习空间上的相关性和通道间的相关性。如下图所示:
image.png-70.9kB
之前在学习1*1卷积作用的时候说过,1*1卷积由于大小只有1x1,所以并不需要考虑像素跟周边像素的关系(空间相关性),主要用于调节通道数,对不同的通道上的像素点进行线性组合,然后进行非线性化操作,可以完成升维和降维的功能,也就是说它在学习通道间的相关性。Inception模块中对输入特征使用1*1卷积学习跨通道相关性,按照通道将原始特征空间分成3,4个较小的空间,
然后再通过较小的3*3*D(D表示特征图划分之后的通道数)或者较小的5*5*D的3维卷积核进行卷积操作,但每部分还是会同时学习空间上的相关性和通道间的相关性。Inception V3模块:
image.png-107.7kB
为了使空间上的相关性和通道间的相关性分开来进行学习,而不是使用3D卷积核同时学习空间上的相关性和通道间的相关性,采用深度可分卷积操作,它是Inception的极端情况,也就是按照通道数划分原始特征空间,然后卷积每一个划分出来的特征空间,由于划分出来的每部分只有一个通道,所以卷积核的深度为1。如下图的特征图只有4个通道,每个卷积核大小为3*3*1:
image.png-113.2kB
简化Inception模块,去掉pooling,保留3*3的卷积分支。如下图所示:
image.png-103.3kB
然后将上图中的所有1*1的卷积用一个较大的1*1卷积替换,然后将产生的特征图分成三个不重叠的部分,进行卷积操作,如下图所示:
image.png-108.2kB
在深度神经网络中,特征图的通道数一般都比3要大很多,3个不重叠的部分分别进行卷积操作,每部分里面还是没有将 空间相关性和跨通道相关性分离开,如果对特征图的每个通道单独进行卷积,也就是特征图有多少个通道,就划分出多少个部分,再对每个通道进行卷积。这样空间相关性上的学习就跟跨通道相关性学习无关了。如下图所示:
image.png-156kB
上述的几个步骤,就完成了Inception到Xception的转变。可以看到,Inception其实就是常规卷积和深度可分离卷积的之间的中间状态。
代码:
keras.applications.xception.Xception(include_top=True, weights='imagenet',
input_tensor=None, input_shape=None,
pooling=None, classes=1000)
参考文献:
https://blog.csdn.net/jningwei/article/details/80628139
https://arxiv.org/pdf/1610.02357.pdf
https://www.sohu.com/a/226395640_500659
https://www.leiphone.com/news/201708/KGJYBHXPwsRYMhWw.html

五、SqueezeNet
1、概述:
具有相同精度的CNN模型,较小的CNN架构至少有以下三个优点:1、在分布式训练期间,较小的CNN需要较少的服务器通信。2、较小的CNN可以减少从云端下载模型的带宽。3、较小的CNN更适合部署在FPGA和内存有限的硬件上。SqueezeNet是一种小型化的CNN架构,具备上述提到的几点优势,SqueezeNet在ImageNet上实现了AlexNet级精度,参数却减少了50倍。 此外,通过模型压缩技术,我们能够将SqueezeNet压缩到小于0.5MB(比AlexNet小510倍)。
2、设计此网络采用的策略:
(1)用1x1卷积核替换3x3卷积核,通道数相同的情况下,1x1的卷积核参数要比3x3的卷积核减少9倍。
(2)减少输入特征图的通道数,因为普通卷积的卷积核是:长通道,使用瓶颈层减少通道数的话参数就自然少了很多。
(3)延迟下采样,以使卷积层具有较大的激活的特征图。
3、网络的核心模块(Fire模块)
Fire有:首先是squeeze 卷积层组成(只有1x1过滤器),接着是由1x1和3x两种卷积都有的混合层(expand层)如下图所示:
image.png-54.2kB
上图中,s1x1 = 3,e1x1 = 4,e3x3 = 4,s1x1表示的是挤压层中1x1卷积的数量,e1x1表示的是拓展层中1x1卷积的数量,e3x3表示的是拓展层中3x3卷积的数量。它们三个是超参数,当我们使用Fire模块时,我们将s1x1设置为小于(e1x1 + e3x3),所以挤压层有助于限制3x3过滤器的输入通道数量,也就是拓展层的输入特征图的通道数。
4、网络结构
我们现在描述SqueezeNet CNN架构。 如下图可知,SqueezeNet
从一个独立的卷积层(conv1)开始,然后是8个Fire模块(fire2-9),
最后一个卷积层(conv10)。 从网络的开始到结束,逐渐增加每个Fire模块的过滤器数量
。根据策略3,需要延迟下采样,所以SqueezeNet分别在图层conv1,fire4,fire8和conv10以步幅2执行最大池化,尽可能使卷积层具有较大的特征图;
image.png-136.9kB
有关SqueezeNet的详细信息和设计选择:
(1)1x1和3x3卷积核的输出激活具有相同的高度和宽度,因此需要对3x3卷积前的特征图进行填充,使输出特征图跟输入特征图大小相同。
(2)Fire模块里面的挤压层和拓展层采用Relu函数进行激活
(3)在Fire9模块之后应用Dropout,比例为50%,
(4)没有全连接层
(5)在训练SqueezeNet时,学习率从0.04开始,并且在整个训练中线性降低学习率。

六、MobileNet
1、移动端和其他嵌入式系统通常是内存空间小,能耗要求低的,也就是计算资源有限。一般的模型,比如ImageNet数据集上训练的VGG,goodlenet,resnet等,需要巨大的计算资源,所以很难用在嵌入式系统上。
2、深度可分离卷积(depthwise separable convolution)
MobileNet使用技术之一是深度可分离卷积(depthwise separable convolution)代替传统的3D卷积操作,这样可以减少参数数量以及卷积过程中的计算量,但是也会导致特征丢失,使得精度下降。MobileNets其实就是Exception思想的应用。区别就是Exception文章重点在提高精度,而MobileNets重点在压缩模型。
假设输入特征图有M个,大小为DF,输出的特征图是N个,卷积核尺寸是Dk*Dk,那么传统的3D卷积的卷积核是立体的,也就是每一个卷积核都是Dk*Dk*M,总共有N个Dk*Dk*M的卷积核,如下图所示:
image.png-20.4kB
所以总的参数个数为Dk*Dk*M*N,假设输出使用的padding参数是same,则输出特征图大小也是DF,那么总的计算量为Dk*Dk*M*N*DF*DF。
而MobileNet将普通卷积操作分为两部分,第一部分是逐通道卷积,使用M个通道数为1,大小为Dk*Dk的卷积核,每一个卷积核负责其中的一个通道。如下图所示:
image.png-10.6kB
逐通道卷积之后的参数个数为:Dk*Dk*M,同样假设padding为same,则计算量为:Dk*Dk*M*DF*DF
第二部分是点卷积,也就是采用3D的1*1卷积改变通道数,对深度方向上的特征信息进行组合,最终将输出通道数变为指定的数。如下图所示:
image.png-20.5kB
这部分的参数个数为:M*N,padding为same时的计算量为M*N*DF*DF.
因此这种分离之后的总的计算量为::Dk*Dk*M*DF*DF+M*N*DF*DF。
深度可分离卷积跟传统3D卷积计算量为:
image.png-19.9kB
如下图,左边是3D卷积常见结构,右边是深度可分离卷积的使用方式:
image.png-17.4kB
3、两个超参数:宽度因子和分辨率因子
(1)宽度因子
上述的逐通道卷积的卷积核个数通常是M,也就是Dk*Dk*1的卷积核个数等于输入通道数,宽度因子是一个参数为(0,1]之间的参数,作用于通道数,可以理解为按照比例缩减输入通道数。同理,输出的通道数也可以通过这个参数进行按比例缩减。用α表示这个参数,则计算量为:
image.png-12.7kB
不同的α取值在ImageNet上的准确率,为准确率,参数数量和计算量之间的权衡情况:
image.png-52kB
(2)分辨率因子
上述的输入特征图大小为DF*DF,分辨率因子取值范围在(0,1]之间,可以理解为对特征图进行下采样,也就是按比例降低特征图的大小,使得输入数据以及由此在每一个模块产生的特征图都变小,用β表示这个参数,结合宽度因子α,则计算量为:
image.png-16.4kB
不同的β系数作用于标准MobileNet时,对精度和计算量以的影响(α固定):
image.png-51.9kB
4、改进:MobileNet V2
在 MobileNet-V1 基础上结合当下流行的残差思想而设计,V2 主要引入了两个改动:Linear Bottleneck 和 Inverted Residual Blocks。
V1与V2的结构对比:
image.png-17.4kB
两者相同的地方在于都采用 Depth-wise (DW) 卷积搭配 Point-wise (PW) 卷积的方式来提特征。
(1)改进一:
V2在 DW 卷积之前新加了一个 PW 卷积,主要目的是为了提升维度数。相比V1直接在每个通道上单独提取特征,V2的这种做法能够先组合不同深度上的特征,并升维,使得特征更加丰富。比直接DW的话,特征提取的效果更好。
(2)改进二:
V2 去掉了第二个 PW 的激活函数。论文作者称其为 Linear Bottleneck。这么做的原因,是因为作者认为激活函数在高维空间能够有效的增加非线性,而在低维空间时则会破坏特征,由Relu的性质,Relu对于负的输入,输出全为零,所以relu会使得一部分特征失效。由于第二个 PW 的主要功能就是降维,再经过Relu的话,又要“损失”一部分特征,因此按照上面的理论,降维之后就不宜再使用 Relu了。
(3)使用短路连接: 倒残差(Inverted Residual)
典的残差块是:1x1(压缩)->3x3(卷积)->1x1(升维),而inverted residual顾名思义是颠倒的残差:1x1(升维)->3x3(dw conv+relu)->1x1(降维+线性变换),skip-connection(跳过连接)是在低维的瓶颈层间发生的(如下图),这对于移动端有限的宽带是有益的。连接情况如下图所示:
image.png-59.8kB
参考文献:
https://blog.csdn.net/t800ghb/article/details/78879612
https://www.cnblogs.com/CodingML-1122/p/9043078.html
https://mp.weixin.qq.com/s/YhBvTLOliXzOw_7QEEtaKw
https://blog.csdn.net/u011995719/article/details/79135818
https://zhuanlan.zhihu.com/p/33075914
https://zhuanlan.zhihu.com/p/39386719

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注