赞
踩
比如:输入的feature map 是28*28*192,即长、宽、通道数。
通过3*3*128的卷积核,则参数量为3*3*192*128=221,184;
若在3*3*128的卷积核前加上一个1*1*96的卷积核,那么参数量为1*1*192*96+3*3*96*128=129,024。
参数量下降了近一半。
以下知识转自https://www.dazhuanlan.com/2020/02/29/5e5a3b85231f9/,写的太好了,就不写了。
深度可分离卷积(depthwise separable convolutions),这是 Xception 以及 MobileNet 系列的精华所在。而它最早是由Google Brain的一名实习生Laurent Sifre于2013年提出。
举一个具体例子,假设输入层的大小是 7×7×3(高×宽×通道),而过滤器的大小是 3×3×3。经过与一个过滤器的 2D 卷积之后,输出层的大小是 5×5×1(仅有一个通道)。
一般来说,两个神经网络层之间会应用多个过滤器。假设我们这里有 128 个过滤器。在应用了这 128 个 2D 卷积之后,我们有 128 个 5×5×1 的输出映射图(map)。然后我们将这些映射图堆叠成大小为 5×5×128 的单层。通过这种操作,我们可将输入层(7×7×3)转换成输出层(5×5×128)。空间维度(即高度和宽度)会变小,而深度会增大。
现在使用深度可分卷积,看看我们如何实现同样的变换。
首先,我们将深度卷积应用于输入层。但我们不使用 2D 卷积中大小为 3×3×3 的单个过滤器,而是分开使用 3 个核。每个过滤器的大小为 3×3×1。每个核与输入层的一个通道卷积(仅一个通道,而非所有通道!)。每个这样的卷积都能提供大小为 5×5×1 的映射图。然后我们将这些映射图堆叠在一起,创建一个 5×5×3 的图像。经过这个操作之后,我们得到大小为 5×5×3 的输出。现在我们可以降低空间维度了,但深度还是和之前一样。
深度可分卷积——第一步:我们不使用 2D 卷积中大小为 3×3×3 的单个过滤器,而是分开使用 3 个核。每个过滤器的大小为 3×3×1。每个核与输入层的一个通道卷积(仅一个通道,而非所有通道!)。每个这样的卷积都能提供大小为 5×5×1 的映射图。然后我们将这些映射图堆叠在一起,创建一个 5×5×3 的图像。经过这个操作之后,我们得到大小为 5×5×3 的输出。
在深度可分卷积的第二步,为了扩展深度,我们应用一个核大小为 1×1×3 的 1×1 卷积。将 5×5×3 的输入图像与每个 1×1×3 的核卷积,可得到大小为 5×5×1 的映射图。因此,在应用了 128 个 1×1x3的卷积之后,我们得到大小为 5×5×128 的层。
通过这两个步骤,深度可分卷积也会将输入层(7×7×3)变换到输出层(5×5×128)。
下图展示了深度可分卷积的整个过程。
所以,深度可分卷积有何优势呢?效率!相比于 2D 卷积,深度可分卷积所需的操作要少得多。
普通卷积下,有 128 个 3×3×3 个核移动了 5×5 次,也就是 128 * 3 * 3 * 3 * 5 * 5 = 86400 次乘法。
可分离卷积在第一个深度卷积步骤,有 3 个 3×3×1 核移动 5×5 次,也就是 3*3*3*5*5 = 675 次乘法。在 1x1x3 卷积的第二步,有 128 个 1×1×3 核移动 5×5 次,即 128 * 1 * 1 * 3 * 5 * 5 = 9600 次乘法。因此,深度可分卷积共有 675 + 9600 = 10275 次乘法。这样的成本大概仅有 2D 卷积的 12%!
所以,对于任意尺寸的图像,如果我们应用深度可分卷积,我们可以节省多少时间?让我们泛化以上例子。现在,对于大小为 H×W×D 的输入图像,如果使用 Nc 个大小为 h*h*D 的核执行 2D 卷积(步幅为 1,填充为 0,其中 h 是偶数)。为了将输入层(H*W*D)变换到输出层((H-h+1)* (W-h+1) * Nc),所需的总乘法次数为:
另一方面,对于同样的变换,深度可分卷积所需的乘法次数为:
现代大多数架构的输出层通常都有很多通道,可达数百甚至上千。对于这样的层(Nc >> h),则上式可约简为 1 / h²。基于此,如果使用 3×3 过滤器,则 2D 卷积所需的乘法次数是深度可分卷积的 9 倍。如果使用 5×5 过滤器,则 2D 卷积所需的乘法次数是深度可分卷积的 25 倍。
使用深度可分卷积有什么坏处吗?当然是有的。深度可分卷积会降低卷积中参数的数量。因此,对于较小的模型而言,如果用深度可分卷积替代 2D 卷积,模型的能力可能会显著下降。因此,得到的模型可能是次优的。但是,如果使用得当,深度可分卷积能在不降低你的模型性能的前提下帮助你实现效率提升。
以下是dw卷积块的Pytorch代码实现:
- import torch.nn as nn
-
- def (inp, oup, stride):
- return nn.Sequential(
- nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
- nn.BatchNorm2d(inp),
- nn.ReLU(inplace=True),
-
- nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
- nn.BatchNorm2d(oup),
- nn.ReLU(inplace=True),
- )
注意:第一步里的卷积里有个参数groups=inp,这就是每个通道单独进行卷积。
conv_dw在Mobilenet里的应用:
- import torch.nn as nn
- import torch.nn.functional as F
-
-
- class Mb(nn.Module):
-
- def __init__(self, num_classes=1000):
- super(Mb, self).__init__()
-
- def conv_bn(inp, oup, stride):
- return nn.Sequential(
- nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
- nn.BatchNorm2d(oup),
- nn.ReLU(inplace=True)
- )
-
- def (inp, oup, stride):
- return nn.Sequential(
- nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
- nn.BatchNorm2d(inp),
- nn.ReLU(inplace=True),
-
- nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
- nn.BatchNorm2d(oup),
- nn.ReLU(inplace=True),
- )
-
- self.model = nn.Sequential(
- conv_bn( 3, 32, 2),
- conv_dw( 32, 64, 1),
- conv_dw( 64, 128, 2),
- conv_dw(128, 128, 1),
- conv_dw(128, 256, 2),
- conv_dw(256, 256, 1),
- conv_dw(256, 512, 2),
- conv_dw(512, 512, 1),
- conv_dw(512, 512, 1),
- conv_dw(512, 512, 1),
- conv_dw(512, 512, 1),
- conv_dw(512, 512, 1),
- conv_dw(512, 1024, 2),
- conv_dw(1024, 1024, 1),
- nn.AvgPool2d(7),
- )
- self.fc = nn.Linear(1024, num_classes)
-
- def forward(self, x):
- x = self.model(x)
- x = F.avg_pool2d(x, 7)
- x = x.view(-1, 1024)
- x = self.fc(x)
- return x
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。