MobileNet 是一种专为移动和嵌入式设备设计的轻量级卷积神经网络架构,其核心优势在于通过创新的结构设计大幅减少参数量和计算量,同时保持较好的模型性能。接下来我们来探讨一下它的原理和优势。
一、MobileNet的核心原理
1. 深度可分离卷积 (Depthwise Separable Convolution)
-
传统卷积:同时处理空间(宽高)和通道维度,计算量为:
H × W × C_in × C_out × K × K
其中K
为卷积核大小。 -
深度可分离卷积:分解为两步:
- 深度卷积 (Depthwise Conv):每个输入通道单独卷积,不跨通道计算。
计算量:H × W × C_in × K × K
- 逐点卷积 (Pointwise Conv):1×1卷积融合通道信息。
计算量:H × W × C_in × C_out
- 深度卷积 (Depthwise Conv):每个输入通道单独卷积,不跨通道计算。
-
计算量对比:
对于输入特征图尺寸为H×W×C_in
,输出通道C_out
,卷积核K×K
:
标准卷积FLOPs = H × W × C_in × C_out × K × K × 2(乘加各算1次操作,所以×2)。
深度可分离卷积计算量分解:分为深度卷积 + 逐点卷积两部分:
(1) 深度卷积(Depthwise Conv)
深度卷积FLOPs = H × W × C_in × K × K × 2 (每个输入通道独立计算)
(2) 逐点卷积(Pointwise Conv)
逐点卷积FLOPs = H × W × C_in × C_out × 1 × 1 × 2 (1×1卷积混合通道)
总计算量:
总FLOPs = H×W×2 × (C_in×K² + C_in×C_out)
传统卷积计算量是深度可分离卷积的 `1/C_out + 1/K²` 倍(例如当 `C_out=256`、`K=3` 时,计算量减少约8-9倍)。
当 C_out=256
,K=3
时:
1/C_out = 1/256 ≈ 0.0039
1/K² = 1/9 ≈ 0.1111
比例 = 1 / (0.0039 + 0.1111) ≈ 8.7
即标准卷积计算量是深度可分离卷积的约8.7倍。这个数量是很客观的,因此,MobileNet 是一种专为移动和嵌入式设备设计的轻量级卷积神经网络架构。
对于MobileNet 可以这样去理解:想象你要处理一堆彩色积木(图像数据),传统方法需要同时考虑:
- 积木的颜色(通道维度)
- 积木的摆放位置(宽高维度)
MobileNet说:“太麻烦了!我们分开处理吧!”
传统卷积
- 操作:一次性检查所有颜色的积木+位置
- 计算量公式:
图片高度 × 图片宽度 × 输入通道 × 输出通道 × 卷积核大小 × 卷积核大小
MobileNet的聪明办法
第一步:深度卷积(只看位置) - 操作:把不同颜色的积木分开,单独检查每种颜色的摆放位置
- 计算量:
高度 × 宽度 × 输入通道 × 卷积核大小 × 卷积核大小
第二步:逐点卷积(只看颜色)
- 操作:用1×1的小漏斗混合颜色
- 计算量:
高度 × 宽度 × 输入通道 × 输出通道
按照这样算下来,就可以节省了87%的计算量!
2. 宽度乘子 (Width Multiplier)
- 通过系数
α
(默认0.25-1)等比例减少所有层的通道数,参数量近似减少α²
倍。
3. 分辨率乘子 (Resolution Multiplier)
- 通过系数
ρ
降低输入分辨率,计算量减少ρ²
倍。
二、MobileNet的结构设计
1. 基础结构单元
# MobileNetV1 基础块
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_ch, out_ch, stride):
super().__init__()
self.depthwise = nn.Conv2d(in_ch, in_ch, 3, stride, 1, groups=in_ch, bias=False)
self.bn1 = nn.BatchNorm2d(in_ch)
self.pointwise = nn.Conv2d(in_ch, out_ch, 1, 1, 0, bias=False)
self.bn2 = nn.BatchNorm2d(out_ch)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.relu(self.bn1(self.depthwise(x)))
x = self.relu(self.bn2(self.pointwise(x)))
return x
2. 整体架构
- MobileNetV1:由1个标准卷积 + 13个深度可分离卷积块堆叠而成。
import torch
import torch.nn as nn
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, stride):
super().__init__()
self.depthwise = nn.Conv2d(
in_channels, in_channels, kernel_size=3,
stride=stride, padding=1, groups=in_channels, bias=False
)
self.pointwise = nn.Conv2d(
in_channels, out_channels,
kernel_size=1, stride=1, bias=False
)
self.bn1 = nn.BatchNorm2d(in_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.depthwise(x)
x = self.bn1(x)
x = self.relu(x)
x = self.pointwise(x)
x = self.bn2(x)
x = self.relu(x)
return x
class MobileNetV1(nn.Module):
def __init__(self, num_classes=1000):
super().__init__()
# 标准卷积层 (第一层)
self.conv1 = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True)
)
# 深度可分离卷积层
self.ds_conv = nn.Sequential(
DepthwiseSeparableConv(32, 64, stride=1),
DepthwiseSeparableConv(64, 128, stride=2),
DepthwiseSeparableConv(128, 128, stride=1),
DepthwiseSeparableConv(128, 256, stride=2),
DepthwiseSeparableConv(256, 256, stride=1),
DepthwiseSeparableConv(256, 512, stride=2),
*[DepthwiseSeparableConv(512, 512, stride=1) for _ in range(5)],
DepthwiseSeparableConv(512, 1024, stride=2),
DepthwiseSeparableConv(1024, 1024, stride=1)
)
# 分类层
self.avgpool = nn.AdaptiveAvgPool2d((1, 1))
self.fc = nn.Linear(1024, num_classes)
def forward(self, x):
x = self.conv1(x)
x = self.ds_conv(x)
x = self.avgpool(x)
x = torch.flatten(x, 1)
x = self.fc(x)
return x
关键结构说明:
-
标准卷积层 (仅第一层):
- 3x3卷积,步长2,输出通道32
- 后接BN和ReLU
-
深度可分离卷积堆叠 :
- 共13个深度可分离卷积层
- 特征图尺寸逐步下降(224→112→56→28→14→7)
- 通道数逐步增加(32→64→128→256→512→1024)
-
分类头 :
- 全局平均池化
- 全连接层输出分类结果
-
参数特点 :
- 总参数量约4.2M
- 计算量约569M FLOPs (输入224x224)
- 相比标准卷积网络(VGG16)计算量减少约8-9倍
这个实现严格遵循了原论文《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》的网络结构设计。当然,现在的MobileNets也已经发展了多个版本出来,但是怎么变化,深度可分离卷积还是这里面的核心。