CVPR 2020带来了它在计算机视觉领域的新想法,以及在3D视觉领域的一些有趣的想法。在探索的所有这些新想法中,华为、悉尼大学和北京大学的研究人员撰写了一篇著名的论文,题为“GhostNet:更多来自廉价运营的功能”(GhostNet:More Feature From Cheap Operations),成功地让一些人产生了兴趣。这个想法虽然相当简单,但却能够取得有竞争力和趣味性的结果。
这篇文章将首先介绍卷积神经网络中特征映射背后的基本原理,我们将在这些映射中观察到对CNN性能至关重要的一般模式。然后,我们将对GhostNet进行回顾,并深入分析其功能和不足。
在我们深入了解支配GhostNet的概念之前,有必要简要回顾一下卷积算法。对标准卷积神经网络结构中使用的卷积算法有很强理解的读者可以随意跳过这一节。要更正式地介绍和深入剖析卷积,请参阅题为深度学习卷积算法指南的文章,这是一个很好的起点。在这里,我们将复习足够多的知识来理解GhostNet。
综上所述,卷积是任何卷积神经网络(CNN)所涉及的最基本的过程。卷积源于基本信号和图像处理,是一种简单的图像滤波过程,它使用一个核来传播原始图像的某些基于纹理的信息。
在图像处理中,要理解图像的语义,关键是要增强或保留某些更易于使用算法处理的基于纹理的信息。传统的技术包括检测图像中对象的边缘、勾勒轮廓、寻找角点等。计算机视觉的发展(以及一般的深度学习)为算法提供了使用可训练的卷积核来学习输入图像中的这些特征的能力,这形成了卷积神经网络结构的基础。
这些也被称为权重矩阵的滤波器/核包含应用于输入图像并且通过反向传播进行调整以捕获给定输入图像的固有内部特征表示的数值。一般而言,卷积层取C个通道的输入张量,并输出C个通道的张量。这些C';通道由该层中存在的过滤器/内核的数量决定。
现在我们已经通过了关于深度CNN中卷积的背景调查,让我们了解GhostNet论文的基础:功能地图。
要素地图是通过应用标准卷积图层从输入生成的空间地图。这些特征映射负责基于所学习的该层的卷积滤波器权重来保存/传播输入图像的某些特征丰富表示。
GhostNet的作者分析了标准卷积层中的特征映射,以查看它们是否显示出某种模式。经过调查,他们注意到,在卷积层生成的整套特征图中,存在许多唯一固有特征图的相似副本,这些副本由于相当昂贵的卷积运算而变得多余。从上图所示的一组特征地图中可以观察到,存在几个外观相似的副本,如第6行、第1列和第3列中的副本。
作者创造了“幽灵特征地图”这个术语来指代这些冗余的副本。这是本文的动机基础:如何减少生成这些冗余特征地图所涉及的计算量?
减少参数,减少翻转,获得接近基线的精度绝对是一个双赢的局面。这就是华为诺亚方舟实验室在CVPR-2020上发表的关于GhostNet的工作的动机。尽管该方法乍一看可能很复杂,但它相当简单,易于解释和验证。本文的目的是为标准深卷积神经网络中使用的自然卷积层提供一种更便宜的替代方案。
如前所述,卷积层接受由输入通道数C定义的输入张量,并输出C个通道的张量。这些通道本质上是上面讨论的功能映射,正如我们所看到的,在该集合中存在这些功能映射的冗余副本。GhostNet所做的不是通过标准卷积来创建所有的特征地图,而是生成总输出特征地图的x%,而其余的是通过廉价的线性运算来创建的。这种廉价的线性运算大大减少了参数和FLOP,同时保持了与原始基线模型几乎相同的性能。廉价的线性运算被设计成类似于本征卷积的特性,并且通常应该是可学习的和依赖于输入的,以便可以使用反向传播在反向传递中对其进行优化。
在我们讨论GhostNet中使用的精确线性运算/变换之前,让我们花点时间来注意一下在神经网络中引入稀疏性或使其更高效的其他类似想法。最近提出了几种修剪神经网络的方法,大大降低了深度神经网络的复杂度。然而,这些方法中的大多数只能在训练后,即在原始的深层模型被训练之后才能结合。因此,它们主要用于将训练好的模型压缩到最小尺寸,同时保持它们的性能。
这一领域的一个突出想法,也被称为过滤修剪,是通过一个简单的过程完成的。一旦模型被训练,就获得模型任意一层中的训练滤波器,并计算这些滤波器中相应的权重的绝对和。仅保留具有较高绝对权重和的top-x过滤器,而丢弃其余过滤器。
现在,回到GhostNet中廉价的线性变换。本文使用深度卷积(通常表示为DWConv)作为其廉价的线性变换。我们在前面几节讨论了什么是卷积。那么,什么是深度卷积(DWConv)呢?
卷积是标准深度神经网络结构的圣杯,主要用于基于计算机视觉的问题。然而,卷积确实有一些缺点,这些缺点已经在多年来的许多工作中得到了解决。其中一些包括在卷积层中使用的滤波器/核之前添加结构,以便它们在上下文中是自适应的。其他包括更改管理卷积运算的体系结构过程。通常在深度神经网络中,卷积层从前一输出的激活接收输入,前一输出是表示为(B,C,H,W)的4维张量,也称为信道优先格式(否则在(B,H,W,C)格式中)。这里,B表示训练时使用的批量大小;H和W表示输入的空间维度,即输入特征图的高度和宽度,最后C表示输入张量中的通道数。(注:TensorFlow通常遵循后一种格式来指定张量的维度:(n,H,W,C),其中N与B相同,即批次大小。PyTorch使用通道优先格式作为标准实践)。因此,在计算多通道本征卷积时,对输入张量应用滤波器(深度与输入相同)以产生所需数量的输出通道。这增加了对应于输入通道数量和输出通道数量的复杂度。
深度卷积的引入是为了解决由正常卷积引起的高参数和翻转的问题。不是在输入的所有通道上应用滤波器来生成输出的一个通道,而是将输入张量分成单独的通道,然后只在一个片上应用滤波器;因此术语纵深上是指每个通道的卷积。简而言之,2D滤波器/核/窗口与作为二维切片的一个通道卷积,以输出用于该层的输出张量的一个通道(也是2-D)。这减少了自然卷积中线性增加的计算开销。但是,需要注意的是:对于提供增量速度和较低复杂度的深度卷积,输入张量中的通道数与特定层中的结果输出张量应该匹配。因此,这是一个双重过程:1.将2-D滤波器与输入张量的每个通道进行卷积以生成2-D输出通道,然后2.将这些2-D通道连接/堆叠以完成输出张量。(要进一步阅读不同类型的卷积并对其进行深入分析,我建议阅读本文)。
因此,深度卷积减少了大量的参数,减少了运算量,在保持与固有卷积的密切关系的同时,降低了计算成本。这是GhostNet表演背后的魔力。虽然作者在他们的论文中没有明确提到深度卷积是一种廉价的线性变换,但它纯粹是一种设计选择。这个问题甚至在这期GitHub关于报纸官方存储库的问题上也被提了出来。
因此,GhostNet提出了用于深度神经网络结构中的标准卷积层的基本上独立的替换层,其中任何卷积层的输出张量现在通过两个操作的串行化来创建。首先,通过三层的顺序堆栈为输出张量生成总通道的x%:标准卷积,然后是批归一化和非线性激活函数,默认情况下定义为校正线性单元(REU)。然后将其输出传递给第二个块,该第二个块同样是三层的顺序堆栈:深度卷积,然后是批归一化和重排。最后,将来自第一顺序块的张量与来自第二顺序块的输出进行堆叠,以完成输出张量。
假设x是深度神经网络结构中特定卷积层的维数(B,C,H,W)的输入张量。图层的输出应为尺寸(B,C1,H1,W1)的x 1。以下操作定义用来代替标准卷积的重影卷积:
步骤1(初级卷积):在输入张量x上计算f(X)以生成维数为(B,C1/2,H1,W1)的张量,其中f(X)表示标准卷积+批量归一化+RELU。这个新的张量可以表示为y1。步骤2(二次卷积):在张量y1上计算g(X)以生成维数为(B,C1/2,H1,W1)的张量,其中g(X)表示深度卷积+批量归一化+RELU。这个新的张量可以表示为y2。步骤3(Stack):堆叠/连接y1和y2以形成结果输出张量x1。
在上述步骤中,我们将主要块和次要块生成的特征图的数量固定为张量应该具有的总输出特征图的50%。但是,该图层可以灵活地以不同的比率进行计算。50%反映图层的一个参数,用s表示,默认值为2。通常,纸张记录以此比率(称为比率2";)的观测结果,但是,增加比率后,可以在速度和精度之间进行权衡,进一步减少这些参数。(=。S的较高值实质上意味着更多的特征映射由深度核计算,而不是由主块中的标准卷积计算。这意味着更大的模型压缩和更高的速度,但精度更低。
作者对不同核尺寸的纵卷积滤波器进行了烧蚀实验。这些结果将在下面的“GhostNet结果”一节中讨论。
作者还提出了一个新的主干架构,称为GhostNet,它本质上是一个MobileNet v3,其中瓶颈被Ghost瓶颈所取代。Ghost模块基本上形成了这些Ghost瓶颈的基础,它们遵循与标准MobileNetv3瓶颈相同的体系结构。
GhostNet是通过在输入层(即标准卷积层)之后的一系列张量中以递增的通道堆叠这些鬼瓶颈来构建的。(注意:输入卷积层,通常称为模型的主干,没有用重影卷积块替换)。基于输入特征映射维度,将重影瓶颈分阶段分组在一起。除最后一个使用跨度2设计的瓶颈(如上图所示)外,所有的虚拟瓶颈都以跨度1应用。对于Ghost瓶颈中的一些剩余连接,作者甚至使用挤压激励(SE)块来提供信道关注,从而以很小的计算开销提高了准确性。
让我们潜入这两个幽灵块和幽灵瓶颈的代码,这可以简单地集成到任何基线卷积神经网络。
我们将从基于PyTorch和TensorFlow的重影卷积代码开始,它可以作为标准卷积层的直接交换。
#导入必需的库和依赖simport torchimport torch.nn作为nnimport数学类GhostModule(nn.Module):def__init__(self,inp,OUP,kernel_size=1,rate=2,dw_size=3,stride=1,relu=True):Super(GhostModule,Self).__init__()self.oup=OUP#根据比率init_channel=math.ceil(OUP/Ratio)new_channel=init_channel*(比率-1)#主要标准卷积+BN+relu self.primary_conv=n.Sequential(n.Conv2d(INP,init_channel,kernel_size,stride,kernel-1)#主要标准卷积+BN+relu self.primary_conv=n.Sequential(n.Conv2d(INP,init_channel,kernel_size,stride,kernel。)#二次深度卷积+bn+relu self.dable_operation=nn.Sequential(nn.Conv2d(init_channel,new_channel,dw_size,1,dw_size//2,Groups=init_channel,bias=false),n.BatchNorm2d(New_Channel),n.ReLU(inplace=True)if relu否则n.Sequential(),)def Forward(),n.BatchNorm2d(New_Channel),n.ReLU(inplace=True)如果relu否则n.Sequential(),)def Forward(。Dim=1)返回[:,:self.oup,:,:]。
#导入必要的依赖项和库simport TensorFlow as tffrom tensorpack.model.common import layer_registerfrom tensorpack.utils.argtools从tensorpack导入shape2D、shape4d、get_data_format.model导入BatchNorm、BNReLU、Conv2D导入数学导入实用程序#方向卷积核权重初始化器kernel_initializer=tf.contrib.layers.variance_scaling_initializer(2.0)###二级方向卷积层。,W_init=None,activate=tf.entity):In_Shape=x.get_Shape().as_list()if data_format==';NHWC';:in_channel=In_Shape[3]STRIDE_Shape=[1,STRIDE,STRIDE,1]ELIF DATA_FORMAT==';NCHW';:in_channel=in_Shape[1]STRIDE_Shape=[1,1,STRIDE,STRIDE]OUT_CHANNEL=IN_CHANNEL*CHANNEL_MULT如果W_init为NONE:W_init=kernel_initializer kernel_shape=shape2d(Kernel_Shape)#[kernel_shape,kernel_shape]filter_shape=kernel_shape+[in_channel,channel_mult]W=tf.get_variable(';dw';,filter_shape,initializer=W_init)conv=tf.nn.epthwise_vv2d(x,W,stride_shape,padding=pying,rate=[rate,rate],data_format=data_format)如果激活为NONE:返回conv否则:返回激活(conv,name=';)def GhostModule(name,x,filter,kernel_size,dw_size,rate,padding=';)def GhostModule(name,x,filter,kernel_size,dw_size,rate,padding=';)def GhostModule(name,x,filter,kernel_size,dw_size,rate,padding=';,use_bias=false,activate=tf.entity):with tf.variable_scope(Name):init_channel=math.ceil(筛选器/比率)#初级标准卷积x=Conv2D(';vv1';,x,init_channel,kernel_size,strides=strides,activate=activate,data_format=data_format,kernel_initializer=kernel_initializer,use_bias=use_bias)如果比率=1:,x,[dw_size,dw_size],channel_mult=Ratio-1,Stride=1,data_format=data_format,activate=激活)DW1=DW1[:,:Filters-init_channel]if data_format==';NHWC';Else DW1[:,:Filters-init_channel,:,:]##Stack x=tf.contat([x,DW1],3 if data_format。
现在让我们来看看Ghost瓶颈的PyTorch实现,它被用作GhostNet的构建块。
#挤压激励块类SELayer(nn.Module):def__init__(self,channel,Reduce=4):Super(SELayer,self).__init_()self.avg_pool=nn.AdaptiveAvgPool2d(1)self.fc=nn.Sequential(nn.线性(通道,通道//缩减),n.ReLU(inplace=True),n.ReLU(inplace=True)。C)y=self.fc(Y).view(b,c,1,1)y=torch.calp(y,0,1)return x*y#DWConv+BN+ReLUdef Deep_conv(INP,OUP,kernel_size=3,stride=1,relu=false):return
.