ResNet(残差网络)和MobileNet分别针对不同的目标设计,这导致了它们在架构上的不同选择。
ResNet中的残差块
在ResNet中,残差块的设计初衷是为了解决深度神经网络训练中的梯度消失和退化问题。残差块通常包括两个或更多卷积层,其输入可能先经过一个下采样(例如通过1x1卷积减少通道数)的操作,然后再进行特征提取,最后通过 shortcut 连接与原始输入相加。减少通道数的目的是为了降低计算复杂度和模型参数量,使得网络能够更深而不至于过拟合或者训练困难。
减少通道数的操作(通常使用1x1卷积)可以视为一种维度压缩策略,它帮助控制模型的复杂性,同时保持足够的表达能力。这样的设计确保了ResNet能够在保证性能的同时,拥有比传统网络更深的层次。
MobileNet中的倒残差模块
MobileNet则是为了移动设备等资源受限环境设计的轻量级网络。它的主要目标是在保持较高精度的同时,大幅度减少模型的大小和计算成本,使之适用于嵌入式系统和移动应用。
在MobileNet中,倒残差模块(Inverse Residual Block)采用了“expand -> depthwise convolution -> project”的结构。首先通过1x1卷积(expand层)增加通道数,然后进行深度可分离卷积以高效地提取特征,最后再通过1x1卷积(project层)减少通道数。增加通道数的目的是在进行深度可分离卷积之前,提供更多的信息和表示能力,从而在相对较低的计算成本下提升模型的表现。
增加通道数可以帮助在轻量化模型中维持一定的表达能力,尤其是在没有大量参数和计算资源的情况下。深度可分离卷积随后在这些扩展的特征图上操作,这是一种高效的卷积方式,能够进一步减少计算量。
总结来说,ResNet通过减少通道数来控制模型复杂度,允许网络加深而不会遇到训练问题;而MobileNet则通过先增加通道数,利用深度可分离卷积高效地处理这些特征,再通过后续的1x1卷积减少通道数以保持模型的小巧,这种设计是为了在资源有限的环境下达到性能与效率的平衡。
Resnet 残差模块
残差模块,先通过(1,1)的卷积,将通道数压缩到1/4,然后使用(3,3)的卷积,最后通过(1,1)的卷积,将通道数还原,与输入相加。
class Bottleneck(nn.Module):
expansion = 4
def __init__(self, in_channel, out_channel, stride=1, downsample=None):
super(Bottleneck, self).__init__()
self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
kernel_size=1, stride=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channel)
# squeeze channels,in_channel = out_channel*self.expansion
self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel,
kernel_size=3, stride=stride, bias=False, padding=1)
self.bn2 = nn.BatchNorm2d(out_channel)
# -----------------------------------------
self.conv3 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel*self.expansion,
kernel_size=1, stride=1, bias=False) # unsqueeze channels
self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)
self.relu = nn.ReLU(inplace=True)
self.downsample = downsample
def forward(self, x):
identity = x
if self.downsample is not None:
identity = self.downsample(x)
out = self.relu(self.bn1( self.conv1(x)))
out = self.relu(self.bn2( self.conv2(x)))
out = self.bn3( self.conv3(x))
out += identity
out = self.relu(out)
return out
downsample = nn.Sequential(
nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(channel * block.expansion))
Mobilenet 倒残差模块
倒残差模块,先通过(1,1)的卷积,将通道数放大,然后使用(3,3)的分组卷积,最后通过(1,1)的卷积,将通道数还原,与输入相加。
class ConvBNReLU(nn.Sequential):
def __init__(self, in_channel, out_channel, kernel_size=3, stride=1, groups=1):
padding = (kernel_size - 1) // 2
super(ConvBNReLU, self).__init__(
nn.Conv2d(in_channel, out_channel, kernel_size, stride, padding, groups=groups, bias=False),
nn.BatchNorm2d(out_channel),
nn.ReLU6(inplace=True)
)
class InvertedResidual(nn.Module):
def __init__(self, in_channel, out_channel, stride, expand_ratio):
super(InvertedResidual, self).__init__()
hidden_channel = in_channel * expand_ratio
self.use_shortcut = stride == 1 and in_channel == out_channel
layers = []
if expand_ratio != 1:
# 1x1 pointwise conv
layers.append(ConvBNReLU(in_channel, hidden_channel, kernel_size=1))
layers.extend([
# 3x3 depthwise conv
ConvBNReLU(hidden_channel, hidden_channel, stride=stride, groups=hidden_channel),
# 1x1 pointwise conv(linear)
nn.Conv2d(hidden_channel, out_channel, kernel_size=1, bias=False),
nn.BatchNorm2d(out_channel),
])
self.conv = nn.Sequential(*layers)
def forward(self, x):
if self.use_shortcut:
return x + self.conv(x)
else:
return self.conv(x)