MobileNet 是一种设计用于资源受限环境中的深度卷积神经网络架构,由 Google 的研究团队提出。其主要目的是在保持高效计算和较小模型体积的同时,提供令人满意的分类性能。MobileNet 特别适合移动设备和嵌入式系统,主要因为其优化了计算资源和内存使用。
MobileNet 的核心特点:
-
深度可分离卷积 (Depthwise Separable Convolutions):
- 深度卷积 (Depthwise Convolution): 这一层对每个输入通道应用一个单独的卷积核,进行空间特征的提取。
- 逐点卷积 (Pointwise Convolution): 这一层通过 $1 \times 1$ 卷积将深度卷积的输出进行通道整合,生成最终的输出。
- 优点: 这种卷积方式显著减少了计算量和模型参数,使得网络更为高效。
-
轻量级设计:
- MobileNet 网络设计致力于降低计算和存储需求,使其适用于计算资源有限的设备,如智能手机和嵌入式系统。
- 模型体积小,推理速度快,但仍能提供较高的准确率。
-
可调节的宽度因子 (Width Multiplier):
- MobileNet 提供了一个可调节的宽度因子(
α
),可以控制网络的宽度(即每层的卷积核数量)。通过调整α
,用户可以在准确性和计算资源之间进行权衡。 - 例如,
α
值为 1.0 表示原始模型,α
值为 0.5 表示减小模型规模的版本。
- MobileNet 提供了一个可调节的宽度因子(
-
可调节的分辨率因子 (Resolution Multiplier):
- MobileNet 还允许用户调整输入图像的分辨率(
ρ
),从而进一步影响计算量和准确性。降低输入图像的分辨率会减少计算量,但可能会影响模型的性能。
- MobileNet 还允许用户调整输入图像的分辨率(
MobileNet 的版本:
MobileNetV1:
- 发布年份: 2017
- 架构: 使用深度可分离卷积代替标准卷积。模型设计简单,易于实现。
- 特点: 模型参数和计算量较少,但准确率较高,适合移动设备使用。
MobileNetV2:
- 发布年份: 2018
- 架构: 引入了反向残差块(Inverted Residual Blocks)和线性瓶颈(Linear Bottleneck)。这些改进提高了特征提取能力和效率。
- 反向残差块: 在每个反向残差块中,输入通过一个深度可分离卷积进行变换,然后通过逐点卷积调整通道数,最后通过一个线性瓶颈进行特征整合。
- 线性瓶颈: 移除了激活函数(如 ReLU)在卷积的最后阶段,保留了线性输出,以减少信息丢失和计算开销。
- 特点: 在提高计算效率的同时,进一步增强了模型的表示能力和准确性。
MobileNetV3:
- 发布年份: 2019
- 架构: 结合了 MobileNetV2 的设计与网络架构搜索(NAS)技术,自动化寻找最优网络结构。
- 网络架构搜索: 通过搜索算法自动调整卷积核大小、网络深度等超参数,以实现最佳性能。
- 改进: 包括高效的非线性激活函数(如 Swish),以及使用自适应的神经网络结构优化技术。
- 版本:
- MobileNetV3-Large: 针对计算资源较多的场景,如高性能设备。
- MobileNetV3-Small: 针对计算资源有限的设备,如边缘设备和物联网设备。
模型配置和调整
MobileNet 提供了以下几种调整方式,以适应不同计算资源和应用场景:
-
宽度因子 (Width Multiplier):
- 控制网络中每层的卷积核数量。例如,宽度因子
α=1.0
表示标准模型,α=0.5
表示每层卷积核数量减半,从而减少计算量和模型参数。
- 控制网络中每层的卷积核数量。例如,宽度因子
-
分辨率因子 (Resolution Multiplier):
- 控制输入图像的分辨率。例如,分辨率因子
ρ=1.0
表示标准输入分辨率,ρ=0.5
表示输入图像分辨率减半,从而减少计算量。
- 控制输入图像的分辨率。例如,分辨率因子
应用场景
MobileNet 由于其高效的计算能力和较小的模型体积,适合于各种计算资源受限的应用场景:
-
图像分类:
- 在移动设备上进行实时图像分类,例如照片分类或相册管理。
-
目标检测:
- 结合 MobileNet 与目标检测框架(如 SSD),进行移动设备上的目标检测和识别。
-
语义分割:
- 在嵌入式设备上进行图像分割任务,例如道路场景分析或医疗图像分析。
-
实时视频处理:
- 在智能手机和无人机等设备上进行实时视频处理和分析。
MobileNet 的高效设计使得其成为深度学习模型在资源受限环境中的一个理想选择,同时提供了多种可调节参数以平衡准确性和计算效率。
MobileNetV1实现:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1. 定义模型
class DepthwiseSeparableConv(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, padding=1):
super(DepthwiseSeparableConv, self).__init__()
self.depthwise = nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=stride, padding=padding, groups=in_channels, bias=False)
self.pointwise = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False)
self.bn = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
x = self.depthwise(x)
x = self.bn(x)
x = self.relu(x)
x = self.pointwise(x)
x = self.bn(x)
x = self.relu(x)
return x
class MobileNetV1(nn.Module):
def __init__(self, num_classes=10):
super(MobileNetV1, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(32),
nn.ReLU(inplace=True),
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),
DepthwiseSeparableConv(512, 512, stride=1),
DepthwiseSeparableConv(512, 512, stride=1),
DepthwiseSeparableConv(512, 512, stride=1),
DepthwiseSeparableConv(512, 1024, stride=2),
DepthwiseSeparableConv(1024, 1024, stride=1),
nn.AdaptiveAvgPool2d(1),
)
self.fc = nn.Linear(1024, num_classes)
def forward(self, x):
x = self.model(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# 2. 设置数据加载器
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
train_dataset = datasets.FakeData(transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# 3. 实例化模型、损失函数和优化器
model = MobileNetV1(num_classes=10)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 4. 训练模型
def train_model(model, criterion, optimizer, dataloader, num_epochs=1):
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
for inputs, labels in dataloader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
epoch_loss = running_loss / len(dataloader.dataset)
print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')
# 5. 训练
train_model(model, criterion, optimizer, train_loader, num_epochs=5)
MobileNetV2实现:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1. 定义倒残差块
class InvertedResidual(nn.Module):
def __init__(self, in_channels, out_channels, stride, expand_ratio):
super(InvertedResidual, self).__init__()
hidden_channels = in_channels * expand_ratio
self.use_res_connect = stride == 1 and in_channels == out_channels
layers = []
if expand_ratio != 1:
layers.append(nn.Conv2d(in_channels, hidden_channels, kernel_size=1, stride=1, padding=0, bias=False))
layers.append(nn.BatchNorm2d(hidden_channels))
layers.append(nn.ReLU6(inplace=True))
layers.extend([
nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, stride=stride, padding=1, groups=hidden_channels, bias=False),
nn.BatchNorm2d(hidden_channels),
nn.ReLU6(inplace=True),
nn.Conv2d(hidden_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_channels),
])
self.block = nn.Sequential(*layers)
def forward(self, x):
if self.use_res_connect:
return x + self.block(x)
else:
return self.block(x)
# 2. 定义 MobileNetV2 模型
class MobileNetV2(nn.Module):
def __init__(self, num_classes=10):
super(MobileNetV2, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 32, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(32),
nn.ReLU6(inplace=True),
InvertedResidual(32, 16, stride=1, expand_ratio=1),
InvertedResidual(16, 24, stride=2, expand_ratio=6),
InvertedResidual(24, 24, stride=1, expand_ratio=6),
InvertedResidual(24, 32, stride=2, expand_ratio=6),
InvertedResidual(32, 32, stride=1, expand_ratio=6),
InvertedResidual(32, 64, stride=2, expand_ratio=6),
InvertedResidual(64, 64, stride=1, expand_ratio=6),
InvertedResidual(64, 64, stride=1, expand_ratio=6),
InvertedResidual(64, 64, stride=1, expand_ratio=6),
InvertedResidual(64, 96, stride=1, expand_ratio=6),
InvertedResidual(96, 96, stride=1, expand_ratio=6),
InvertedResidual(96, 160, stride=2, expand_ratio=6),
InvertedResidual(160, 160, stride=1, expand_ratio=6),
InvertedResidual(160, 320, stride=1, expand_ratio=6),
nn.Conv2d(320, 1280, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(1280),
nn.ReLU6(inplace=True),
nn.AdaptiveAvgPool2d(1),
)
self.fc = nn.Linear(1280, num_classes)
def forward(self, x):
x = self.model(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# 3. 设置数据加载器
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
train_dataset = datasets.FakeData(transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# 4. 实例化模型、损失函数和优化器
model = MobileNetV2(num_classes=10)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 5. 训练模型
def train_model(model, criterion, optimizer, dataloader, num_epochs=1):
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
for inputs, labels in dataloader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
epoch_loss = running_loss / len(dataloader.dataset)
print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')
# 6. 训练
train_model(model, criterion, optimizer, train_loader, num_epochs=5)
MobileNetV3实现:
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 1. 定义 SE 模块
class SqueezeAndExcitation(nn.Module):
def __init__(self, in_channels, reduction=4):
super(SqueezeAndExcitation, self).__init__()
hidden_channels = max(1, in_channels // reduction)
self.fc1 = nn.Conv2d(in_channels, hidden_channels, kernel_size=1)
self.relu = nn.ReLU(inplace=True)
self.fc2 = nn.Conv2d(hidden_channels, in_channels, kernel_size=1)
self.sigmoid = nn.Sigmoid()
def forward(self, x):
b, c, _, _ = x.size()
se = nn.functional.adaptive_avg_pool2d(x, 1)
se = self.fc1(se)
se = self.relu(se)
se = self.fc2(se)
se = self.sigmoid(se)
return x * se
# 2. 定义倒残差块
class InvertedResidualV3(nn.Module):
def __init__(self, in_channels, out_channels, stride, expansion, use_se):
super(InvertedResidualV3, self).__init__()
hidden_channels = in_channels * expansion
self.use_residual = stride == 1 and in_channels == out_channels
self.conv = nn.Sequential(
nn.Conv2d(in_channels, hidden_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(hidden_channels),
nn.ReLU(inplace=True),
nn.Conv2d(hidden_channels, hidden_channels, kernel_size=3, stride=stride, padding=1, groups=hidden_channels, bias=False),
nn.BatchNorm2d(hidden_channels),
nn.ReLU(inplace=True),
nn.Conv2d(hidden_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(out_channels)
)
if use_se:
self.se = SqueezeAndExcitation(out_channels)
else:
self.se = None
def forward(self, x):
identity = x
x = self.conv(x)
if self.se:
x = self.se(x)
if self.use_residual:
return x + identity
else:
return x
# 3. 定义 MobileNetV3 模型
class MobileNetV3(nn.Module):
def __init__(self, num_classes=10):
super(MobileNetV3, self).__init__()
self.model = nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(16),
nn.ReLU(inplace=True),
InvertedResidualV3(16, 16, stride=1, expansion=1, use_se=False),
InvertedResidualV3(16, 24, stride=2, expansion=4, use_se=False),
InvertedResidualV3(24, 24, stride=1, expansion=3, use_se=False),
InvertedResidualV3(24, 40, stride=2, expansion=3, use_se=True),
InvertedResidualV3(40, 40, stride=1, expansion=3, use_se=True),
InvertedResidualV3(40, 80, stride=2, expansion=6, use_se=False),
InvertedResidualV3(80, 80, stride=1, expansion=6, use_se=False),
InvertedResidualV3(80, 80, stride=1, expansion=6, use_se=False),
InvertedResidualV3(80, 112, stride=1, expansion=6, use_se=True),
InvertedResidualV3(112, 112, stride=1, expansion=6, use_se=True),
InvertedResidualV3(112, 160, stride=2, expansion=6, use_se=True),
InvertedResidualV3(160, 160, stride=1, expansion=6, use_se=True),
nn.Conv2d(160, 960, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(960),
nn.ReLU(inplace=True),
nn.AdaptiveAvgPool2d(1),
)
self.fc = nn.Linear(960, num_classes)
def forward(self, x):
x = self.model(x)
x = x.view(x.size(0), -1)
x = self.fc(x)
return x
# 4. 设置数据加载器
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
train_dataset = datasets.FakeData(transform=transform)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
# 5. 实例化模型、损失函数和优化器
model = MobileNetV3(num_classes=10)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 6. 训练模型
def train_model(model, criterion, optimizer, dataloader, num_epochs=1):
model.train()
for epoch in range(num_epochs):
running_loss = 0.0
for inputs, labels in dataloader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item() * inputs.size(0)
epoch_loss = running_loss / len(dataloader.dataset)
print(f'Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}')
# 7. 训练
train_model(model, criterion, optimizer, train_loader, num_epochs=5)