MobileNet系列:移动端的深度学习革命

MobileNet系列:移动端的深度学习革命

引言

在经历了GoogLeNet多年多个版本的递进研究后,深度学习各模型之间的竞争大多集中在大规模计算硬件算力上。2017年,Google团队转而将目光投向了深度学习在小规模计算集群的部署上。

MobileNet,正如其名——可以在移动设备上部署的深度学习网络。Google团队通过创新的网络设计,让算力较低的设备(如手机和小型电脑)也能完成深度学习任务。

系列概览

论文列表

MobileNet V1 (2017)

设计目标

  • ✅ 减少参数量
  • ✅ 降低计算量
  • ✅ 保持合理的精度
  • ✅ 适合移动端部署

核心创新:深度可分离卷积

MobileNet V1的核心是深度可分离卷积(Depthwise Separable Convolution) [1]

标准卷积的问题

标准卷积的计算量:

\[\text{Params} = K \times K \times M \times N\] \[\text{FLOPs} = K \times K \times M \times N \times H \times W\]

其中:

  • (K\times K):卷积核大小
  • (M):输入通道数
  • (N):输出通道数(卷积核个数)
  • (H \times W):特征图尺寸

深度可分离卷积

DW+PW卷积

深度可分离卷积 = 深度卷积(DW) + 逐点卷积(PW)

1. 深度卷积(Depthwise Convolution, DW)

思想:每个输入通道使用独立的卷积核。

# DW卷积:每个通道独立处理
nn.Conv2d(in_channels=M, out_channels=M, 
          kernel_size=3, groups=M)  # groups=M是关键

计算量: \(\text{FLOPs}_{DW} = K \times K \times M \times H \times W\)

2. 逐点卷积(Pointwise Convolution, PW)

思想:使用1×1卷积进行通道间信息融合。

# PW卷积:1×1卷积
nn.Conv2d(in_channels=M, out_channels=N, kernel_size=1)

计算量: \(\text{FLOPs}_{PW} = M \times N \times H \times W\)

计算量对比

总计算量: \(\text{FLOPs}_{DSC} = K^2 \cdot M \cdot H \cdot W + M \cdot N \cdot H \cdot W\)

压缩比: \(\frac{\text{FLOPs}_{DSC}}{\text{FLOPs}_{Standard}} = \frac{1}{N} + \frac{1}{K^2}\)

当(K=3)时,压缩比约为 1/9

graph LR
    subgraph STD["标准卷积"]
        S_IN["M 通道"] --> S_C["K×K Conv<br/>M×N kernels"]
        S_C --> S_OUT["N 通道<br/>FLOPs: K²·M·N·H·W"]
    end
    subgraph DSC["深度可分离卷积"]
        D_IN["M 通道"] --> DW["Depthwise<br/>K×K · groups=M<br/>FLOPs: K²·M·H·W"]
        DW --> PW["Pointwise<br/>1×1 · M→N<br/>FLOPs: M·N·H·W"]
        PW --> D_OUT["N 通道<br/>≈ 1/9 FLOPs"]
    end
    style STD fill:#fce4ec,stroke:#c62828
    style DSC fill:#e8f5e9,stroke:#2e7d32

网络结构

MobileNet V1结构

class DepthwiseSeparableConv(nn.Module):
    """深度可分离卷积模块"""
    def __init__(self, in_channels, out_channels, stride=1):
        super(DepthwiseSeparableConv, self).__init__()
        
        # Depthwise卷积
        self.depthwise = nn.Sequential(
            nn.Conv2d(in_channels, in_channels, kernel_size=3, 
                     stride=stride, padding=1, groups=in_channels, bias=False),
            nn.BatchNorm2d(in_channels),
            nn.ReLU(inplace=True)
        )
        
        # Pointwise卷积
        self.pointwise = nn.Sequential(
            nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )
    
    def forward(self, x):
        x = self.depthwise(x)
        x = self.pointwise(x)
        return x

模型复现

MobileNet V2 (2018)

核心创新

V2在V1的基础上提出了两个重要概念:

  1. Linear Bottleneck(线性瓶颈)
  2. Inverted Residual(逆残差结构)

问题:ReLU对低维信息的破坏

ReLU的非线性损失

ReLU激活函数会将负值全部置零: \(\text{ReLU}(x) = \max(0, x)\)

问题:当特征维度较低时,ReLU会丢失大量信息!

ReLU6激活函数

解决方案1:ReLU6

ReLU6定义: \(\text{ReLU6}(x) = \min(\max(0, x), 6)\)

# ReLU6实现
nn.ReLU6(inplace=True)

优势

  • 在6处截断,防止数值过大
  • 减少精度损失
  • 对低精度计算友好

解决方案2:逆残差结构

逆残差结构对比

传统残差 vs 逆残差

维度 传统残差(ResNet) 逆残差(MobileNet V2)
路径 高维→低维→高维 低维→高维→低维
操作流程 降维→处理→升维 升维→处理→降维
Shortcut 高维 低维
核心思想 压缩表示 扩展表示

为什么要”逆”?

  1. DW卷积不改变通道数:需要先升维才能提取更丰富的特征
  2. 低维shortcut节省内存:低维的残差连接更高效
  3. 高维处理更有效:在高维空间进行特征提取效果更好

Inverted Residual Block

Bottleneck Block

class InvertedResidual(nn.Module):
    """逆残差模块"""
    def __init__(self, in_channels, out_channels, stride, expand_ratio):
        super(InvertedResidual, self).__init__()
        
        hidden_dim = in_channels * expand_ratio
        self.use_residual = stride == 1 and in_channels == out_channels
        
        layers = []
        
        # 1. 升维(如果需要)
        if expand_ratio != 1:
            layers.append(nn.Sequential(
                nn.Conv2d(in_channels, hidden_dim, kernel_size=1, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True)
            ))
        
        # 2. Depthwise卷积
        layers.append(nn.Sequential(
            nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, 
                     stride=stride, padding=1, groups=hidden_dim, bias=False),
            nn.BatchNorm2d(hidden_dim),
            nn.ReLU6(inplace=True)
        ))
        
        # 3. 降维(Linear Bottleneck)
        layers.append(nn.Sequential(
            nn.Conv2d(hidden_dim, out_channels, kernel_size=1, bias=False),
            nn.BatchNorm2d(out_channels),
            # 注意:这里没有ReLU!
        ))
        
        self.conv = nn.Sequential(*layers)
    
    def forward(self, x):
        if self.use_residual:
            return x + self.conv(x)
        else:
            return self.conv(x)

Linear Bottleneck

关键设计:最后的1×1卷积后不使用ReLU

原因

  • 输出是低维的
  • ReLU会破坏低维信息
  • 使用线性激活保留更多信息

主要贡献

  1. 逆残差结构:更高效的特征提取
  2. Linear Bottleneck:保护低维信息
  3. ReLU6:更适合移动端的激活函数
  4. 更好的性能:参数量减少,精度提升

MobileNet V3 (2019)

核心创新

V3引入了三个主要改进 [3]

  1. NAS(神经架构搜索)
  2. SE模块(Squeeze-and-Excitation)
  3. H-Swish激活函数

NAS - 神经架构搜索

暴力美学:使用优化算法自动搜索最优的网络结构。

搜索空间

  • 层数
  • 卷积核大小
  • 扩展比例
  • 通道数

代价:需要极大的算力!

SE模块 - 通道注意力

SE模块

Squeeze-and-Excitation:学习通道之间的重要性。

class SEBlock(nn.Module):
    """SE注意力模块"""
    def __init__(self, channels, reduction=4):
        super(SEBlock, self).__init__()
        
        # Squeeze:全局平均池化
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        
        # Excitation:两层全连接
        self.fc = nn.Sequential(
            nn.Linear(channels, channels // reduction),
            nn.ReLU(inplace=True),
            nn.Linear(channels // reduction, channels),
            nn.Hardsigmoid(inplace=True)  # V3使用Hard-Sigmoid
        )
    
    def forward(self, x):
        b, c, _, _ = x.size()
        
        # Squeeze
        y = self.avg_pool(x).view(b, c)
        
        # Excitation
        y = self.fc(y).view(b, c, 1, 1)
        
        # Scale
        return x * y.expand_as(x)

H-Swish激活函数

Swish函数

标准Swish: \(\text{Swish}(x) = x \cdot \sigma(\beta x)\)

其中 (\sigma) 是Sigmoid函数。

问题:Sigmoid计算复杂,不适合移动端。

Hard-Swish

近似Swish: \(\text{H-Swish}(x) = x \cdot \frac{\text{ReLU6}(x + 3)}{6}\)

分段表达式: \(\text{H-Swish}(x) = \begin{cases} 0, & \text{if } x \leq -3 \\ x, & \text{if } x \geq 3 \\ \frac{x(x+3)}{6}, & \text{otherwise} \end{cases}\)

class HardSwish(nn.Module):
    def forward(self, x):
        return x * F.relu6(x + 3, inplace=True) / 6

优势

  • 计算简单(只需ReLU6)
  • 硬件友好
  • 效果接近Swish

MobileNet V3架构

V3提供两个版本:

  • MobileNet V3-Large:高性能版本
  • MobileNet V3-Small:高效版本

MobileNet系列对比

版本 核心技术 参数量(M) 计算量(MFLOPs) Top-1准确率
V1 DW+PW卷积 4.2 569 70.6%
V2 逆残差+Linear Bottleneck 3.4 300 72.0%
V3-Large NAS+SE+H-Swish 5.4 219 75.2%
V3-Small NAS+SE+H-Swish 2.9 66 67.4%

设计哲学的演进

V1:基础轻量化

  • 目标:减少计算量
  • 方法:深度可分离卷积
  • 结果:计算量降到1/9

V2:特征表达优化

  • 目标:保护信息同时降低计算量
  • 方法:逆残差+Linear Bottleneck
  • 结果:精度提升,计算量继续降低

V3:极致优化

  • 目标:自动化设计+性能极致化
  • 方法:NAS+SE+新激活函数
  • 结果:精度和效率的最佳平衡

实践经验

选择合适的版本

# 场景1:高精度要求
model = mobilenet_v3_large(pretrained=True)

# 场景2:极致轻量
model = mobilenet_v3_small(pretrained=True)

# 场景3:平衡选择
model = mobilenet_v2(pretrained=True)

宽度乘数

MobileNet支持通过宽度乘数调整模型大小:

# 宽度乘数α:调整通道数
# α=1.0:标准模型
# α=0.75:减少25%通道
# α=0.5:减少50%通道

def adjust_channels(channels, width_mult=1.0):
    return int(channels * width_mult)

量化部署

# 动态量化
import torch.quantization

model_fp32 = mobilenet_v3_small(pretrained=True)
model_int8 = torch.quantization.quantize_dynamic(
    model_fp32,
    {nn.Linear, nn.Conv2d},
    dtype=torch.qint8
)

# 模型大小减少75%,速度提升2-4倍

迁移学习技巧

# 冻结特征提取层
model = mobilenet_v2(pretrained=True)

for param in model.features.parameters():
    param.requires_grad = False

# 只训练分类器
model.classifier = nn.Sequential(
    nn.Dropout(0.2),
    nn.Linear(model.last_channel, num_classes)
)

深度可分离卷积的应用

深度可分离卷积已成为轻量化网络的标准组件

  • MobileNet系列:开创者
  • ShuffleNet:结合组卷积
  • Xception:极致应用
  • EfficientNet:NAS优化

优缺点分析

优点

  1. 参数量少:适合移动端部署
  2. 计算快:推理速度快
  3. 灵活性高:可调整宽度和分辨率
  4. 可扩展:易于修改和优化

缺点

  1. 精度略低:相比ResNet等大模型
  2. 实现复杂:特别是V3的SE模块和NAS
  3. 硬件依赖:需要硬件支持DW卷积

应用场景

MobileNet系列特别适合:

  • 📱 移动应用:手机App中的AI功能
  • 🤖 嵌入式系统:树莓派、IoT设备
  • 🎥 实时视频处理:视频流分析
  • 🚗 边缘计算:自动驾驶、智能监控

总结

技术演进

graph LR
    V1["V1 (2017)<br/>Depthwise Separable Conv<br/>DW + PW<br/>9× FLOPs 降低<br/>4.2M · 70.6%"] --> V2["V2 (2018)<br/>Inverted Residual<br/>Linear Bottleneck<br/>ReLU6<br/>3.4M · 72.0%"]
    V2 --> V3["V3 (2019)<br/>NAS + SE + H-Swish<br/>自动搜索架构<br/>5.4M · 75.2%"]
    style V1 fill:#e3f2fd,stroke:#1565c0
    style V2 fill:#e8f5e9,stroke:#2e7d32
    style V3 fill:#fff3e0,stroke:#e65100

核心思想

  1. 深度可分离卷积是关键:大幅降低计算量
  2. 逆残差很巧妙:更高效的特征提取
  3. 注意力机制有用:SE模块提升性能
  4. 自动化设计是趋势:NAS找到更优结构

影响

MobileNet系列:

  • 📊 推动了移动端AI的发展
  • 🔧 成为轻量化网络的设计范式
  • 🚀 广泛应用于各类端侧场景
  • 🎓 启发了众多后续研究

模型复现

我在PyTorch平台上复现了MobileNet系列:

参考文献

  1. Howard, A. G., Zhu, M., Chen, B., Kalenichenko, D., Wang, W., Weyand, T., Andreetto, M., & Adam, H. MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications. arXiv: 1704.04861, 2017.
  2. Sandler, M., Howard, A., Zhu, M., Zhmoginov, A., & Chen, L.-C. MobileNetV2: Inverted Residuals and Linear Bottlenecks. IEEE Conference on Computer Vision and Pattern Recognition (CVPR), 2018. arXiv: 1801.04381
  3. Howard, A., Sandler, M., Chu, G., Chen, L.-C., Chen, B., Tan, M., Wang, W., Zhu, Y., Pang, R., Vasudevan, V., Le, Q. V., & Adam, H. Searching for MobileNetV3. IEEE International Conference on Computer Vision (ICCV), 2019. arXiv: 1905.02244

文章目录