倒置残差块(Inverted Residual Block),是MobileNetV2网络中提升效率的关键结构。
class IRBlock(nn.Module):
def __init__(self, inp, oup, stride=1, expansion=4):
IRBlock
类继承自 nn.Module
,是一个神经网络模块。__init__
方法是类的构造函数,用于初始化实例。inp
: 输入通道数。oup
: 输出通道数。stride
: 卷积的步长,决定了输出特征图的大小。expansion
: 扩展因子,用于控制内部隐藏层通道数的扩展。self.stride = stride
assert stride in [1, 2]
hidden_dim = int(inp * expansion)
self.use_res_connect = self.stride == 1 and inp == oup
在编程中,断言(
assert
)是一种检查代码是否满足某些条件的方式。如果条件不成立,程序会抛出异常。在 IRBlock
类中,使用断言有几个目的:
assert stride in [1, 2]
这行代码确保传递给类的
stride
参数只能是1或2。步长(stride)在卷积神经网络中是一个关键参数,它决定了卷积层如何在输入数据上移动。步长为1意味着卷积核每次移动一个像素,步长为2则意味着卷积核每次移动两个像素。这对于输出特征图的尺寸有直接影响。在这个特定的
MV2Block
中,只设计了处理步长为1或2的情况,因为这在大多数应用场景中是最常见和最有效的。步长为1通常用于保持特征图的尺寸,而步长为2用于减半特征图的宽度和高度,实现下采样。通过使用断言,如果有人试图用不支持的步长值(比如3或更大的数)初始化
MV2Block
,程序会立即抛出错误,这有助于及早发现问题,避免更深层次的错误和混乱。
根据扩展因子的值构建不同的卷积块结构:
根据扩展因子的值构建不同的卷积块结构是MobileNetV2架构中的一个关键设计决策,目的在于优化网络的性能和效率。让我们来详细解释这个设计选择的原因:
扩展因子(Expansion Factor): 扩展因子是MobileNetV2中引入的一个概念,用于控制倒置残差块(Inverted Residual Block)内部的中间扩展层的大小。具体来说,它决定了第一层逐点卷积(Pointwise Convolution)增加的通道数。
扩展层的作用:
- 扩展因子 > 1: 当扩展因子大于1时,卷积块的第一部分是一个扩展层,它通过逐点卷积增加通道数。这样做的目的是在后续的深度可分离卷积(Depthwise Convolution)中提供更多的特征表示空间。这对于捕捉更复杂的特征是有益的,尤其是在网络的深层部分。
- 扩展因子 = 1: 如果扩展因子等于1,就意味着不需要扩展层。这通常用于网络的输入层或者当输入和输出维度相同时。在这种情况下,直接使用深度可分离卷积就足够了,因为没有必要进一步增加通道数。
效率与性能的权衡:
- 减少计算成本: 通过调整扩展因子,可以有效控制网络的计算复杂度。深度可分离卷积本身就是一种减少计算成本的设计,通过在其中加入适当的扩展层,可以进一步平衡网络的性能和计算成本。
- 适应不同层的需求: 网络不同层对特征的需求不同。在某些层,可能需要更丰富的特征表示来捕捉复杂的模式,而在其他层,简单的特征提取可能就足够了。通过调整扩展因子,MobileNetV2可以灵活地适应这些不同层的需求。
if expansion == 1:
# 构建没有扩展层的卷积块
else:
# 构建包含扩展层的卷积块
代码实现:
if expansion == 1: # 构建没有扩展层的卷积块
self.conv = nn.Sequential(
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else: # 构建包含扩展层的卷积块
self.conv = nn.Sequential(
# pw
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# dw
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# pw-linear
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
expansion == 1
): 这种情况下,块由深度可分离卷积(Depthwise Convolution)和逐点卷积(Pointwise Convolution)组成。expansion != 1
): 这种情况下,块先通过一个逐点卷积扩展特征维度,然后进行深度可分离卷积,最后再通过逐点卷积压缩维度到输出通道数。每个卷积操作后都跟有批量归一化(Batch Normalization)和SiLU(也称为Swish)激活函数。
forward
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
forward
方法定义了数据通过块的方式。self.use_res_connect
为 True
时,将执行残差连接,即将块的输入加到块的输出上。否则,只返回卷积块的输出。class IRBlock(nn.Module):
def __init__(self, inp, oup, stride=1, expansion=4):
super().__init__()
self.stride = stride
assert stride in [1, 2]
hidden_dim = int(inp * expansion)
self.use_res_connect = self.stride == 1 and inp == oup
if expansion == 1: # 构建没有扩展层的卷积块
self.conv = nn.Sequential(
# 深度可分离卷积(Depthwise Convolution)
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# “线性”逐点卷积 (Pointwise-Linear Convolution)
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
else: # 构建包含扩展层的卷积块
self.conv = nn.Sequential(
# 逐点卷积 (Pointwise Convolution)
nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# 深度可分离卷积 (Depthwise Convolution)
nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
nn.BatchNorm2d(hidden_dim),
nn.SiLU(),
# “线性”逐点卷积 (Pointwise-Linear Convolution)
nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)