MobileNetV3 — это сетевая архитектура, предложенная Google 21 марта 2019 г., см.arXivизбумага, который включает в себя две подверсии, большую и малую.
исходный кодСсылаться на:GitHub.com/spike king/no…
Особенности:
- PyTorch реализует архитектуру MobileNetV3;
- Дизайн h-swish и h-sigmoid;
- Новый модуль MobileNet;
- структура SE и остаточная структура;
- Последний этап: продвинутый средний пул и использование свертки 1x1;
Структура сети:
Общая структура
Сетевую структуру MobileNetV3 можно разделить на три части:
- Начальная часть: 1 слой свертки, свертка 3x3, извлечение признаков;
- Средняя часть: несколько сверточных слоев, разные версии Large и Small, разные слои и параметры;
- Последняя часть: через два сверточных слоя 1х1 вместо полносвязных вывести категорию;
Структура сети выглядит следующим образом, где параметром является Большая система:
Исходный код выглядит следующим образом:
def forward(self, x):
# 起始部分
out = self.init_conv(x)
# 中间部分
out = self.block(out)
# 最后部分
out = self.out_conv1(out)
batch, channels, height, width = out.size()
out = F.avg_pool2d(out, kernel_size=[height, width])
out = self.out_conv2(out)
out = out.view(batch, -1)
return out
начальная часть
Начальная часть одинакова в Large и Small, то есть это первый сверточный слой в списке структур, который включает в себя 3 части, а именно сверточный слой, слой BN и слой активации h-переключателя.
Исходный код выглядит следующим образом:
init_conv_out = _make_divisible(16 * multiplier)
self.init_conv = nn.Sequential(
nn.Conv2d(in_channels=3, out_channels=init_conv_out, kernel_size=3, stride=2, padding=1),
nn.BatchNorm2d(init_conv_out),
h_swish(inplace=True),
)
h-переключатель и h-сигмоид
h-переключатель представляет собой нелинейную функцию активации, формула которой выглядит следующим образом:
График выглядит следующим образом:
Исходный код:
out = F.relu6(x + 3., self.inplace) / 6.
return out * x
h-сигмоид представляет собой нелинейную функцию активации для структуры SE:
Исходный код:
return F.relu6(x + 3., inplace=self.inplace) / 6.
График выглядит следующим образом:
Формула расчета свертки
- Входное изображение: Ш×Ш
- Ядро свертки: F×F
- Размер шага: S
- Пиксельное значение заполнения: P
- Размер выходного изображения: N×N
формула:
N = (W − F + 2P ) / S + 1
Среди них он округляется в меньшую сторону, а лишние пиксели в расчете не участвуют.
средняя часть
Средняя часть представляет собой сетевую структуру из нескольких блоков, содержащих сверточные слои (MobileBlock).Для справки, сетевая структура Large аналогична Small:
в:
- SE: структура сжатия и возбуждения, сжатие и возбуждение;
- NL: нелинейность, нелинейность, HS: функция активации h-swish, RE: функция активации ReLU;
- bneck: слои узкого места, слой узкого места;
- exp size: коэффициент расширения, параметр расширения;
Каждая строка представляет собой MobileBlock, т.е. bneck.
Исходный код:
self.block = []
for in_channels, out_channels, kernal_size, stride, nonlinear, se, exp_size in layers:
in_channels = _make_divisible(in_channels * multiplier)
out_channels = _make_divisible(out_channels * multiplier)
exp_size = _make_divisible(exp_size * multiplier)
self.block.append(MobileBlock(in_channels, out_channels, kernal_size, stride, nonlinear, se, exp_size))
self.block = nn.Sequential(*self.block)
MobileBlock
Три необходимых шага:
- свертка 1x1, преобразованная из входного канала в канал расширения;
- Свертка 3x3 или 5x5, расширение канала, использование шага;
- Свертка 1x1, по каналу расширения, преобразованная в выходной канал.
Два необязательных шага:
- Структура SE: сжимай и возбуждай;
- Операция соединения, остаточный остаток, размер шага равен 1, а входные и выходные каналы одинаковы;
Есть две функции активации: ReLU и h-swish.
Структура следующая, параметры специфические, а не общие:
Исходный код:
def forward(self, x):
# MobileNetV2
out = self.conv(x) # 1x1卷积
out = self.depth_conv(out) # 深度卷积
# Squeeze and Excite
if self.SE:
out = self.squeeze_block(out)
# point-wise conv
out = self.point_conv(out)
# connection
if self.use_connect:
return x + out
else:
return out
Подэтапы следующие:
Шаг 1: свертка 1x1
self.conv = nn.Sequential(
nn.Conv2d(in_channels, exp_size, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(exp_size),
activation(inplace=True)
)
Шаг 2: операция расширенной свертки
groups — значение exp, каждому каналу соответствует свертка,Ссылаться на, и не содержит активного слоя.
self.depth_conv = nn.Sequential(
nn.Conv2d(exp_size, exp_size, kernel_size=kernal_size, stride=stride, padding=padding, groups=exp_size),
nn.BatchNorm2d(exp_size),
)
Шаг 3: свертка 1x1
self.point_conv = nn.Sequential(
nn.Conv2d(exp_size, out_channels, kernel_size=1, stride=1, padding=0),
nn.BatchNorm2d(out_channels),
activation(inplace=True)
)
Необязательное действие 1: Структура SE
- объединение;
- Сожмите линейную ссылку + RELU + Возбудите линейную ссылку +h-sigmoid;
- изменить размер;
- Вес умножается на исходное значение;
Исходный код:
class SqueezeBlock(nn.Module):
def __init__(self, exp_size, divide=4):
super(SqueezeBlock, self).__init__()
self.dense = nn.Sequential(
nn.Linear(exp_size, exp_size // divide),
nn.ReLU(inplace=True),
nn.Linear(exp_size // divide, exp_size),
h_sigmoid()
)
def forward(self, x):
batch, channels, height, width = x.size()
out = F.avg_pool2d(x, kernel_size=[height, width]).view(batch, -1)
out = self.dense(out)
out = out.view(batch, channels, 1, 1)
return out * x
Необязательная операция 2: Остаточная структура
Окончательный вывод добавляется к исходному значению, и исходный код выглядит следующим образом:
self.use_connect = (stride == 1 and in_channels == out_channels)
if self.use_connect:
return x + out
else:
return out
Последняя часть
В последней части (последний этап) за счет продвижения среднего пула объем вычислений уменьшается, операция сжатия опускается, а свертка 1x1 используется напрямую, как показано на рисунке:
Исходный код:
out = self.out_conv1(out)
batch, channels, height, width = out.size()
out = F.avg_pool2d(out, kernel_size=[height, width])
out = self.out_conv2(out)
Первый сверточный слой conv1, структура SE такая же, как и выше, исходный код:
out_conv1_in = _make_divisible(96 * multiplier)
out_conv1_out = _make_divisible(576 * multiplier)
self.out_conv1 = nn.Sequential(
nn.Conv2d(out_conv1_in, out_conv1_out, kernel_size=1, stride=1),
SqueezeBlock(out_conv1_out),
h_swish(inplace=True),
)
Второй сверточный слой conv2:
out_conv2_in = _make_divisible(576 * multiplier)
out_conv2_out = _make_divisible(1280 * multiplier)
self.out_conv2 = nn.Sequential(
nn.Conv2d(out_conv2_in, out_conv2_out, kernel_size=1, stride=1),
h_swish(inplace=True),
nn.Conv2d(out_conv2_out, self.num_classes, kernel_size=1, stride=1),
)
Наконец, вызовите метод изменения размера, чтобы преобразовать Cx1x1 в категорию, вы можете
out = out.view(batch, -1)
Кроме того, вы также можете установить параметр множителя для увеличения и уменьшения количества каналов в равных пропорциях, чтобы удовлетворить число, кратное 8. Исходный код выглядит следующим образом:
def _make_divisible(v, divisor=8, min_value=None):
if min_value is None:
min_value = divisor
new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
# Make sure that round down does not go down by more than 10%.
if new_v < 0.9 * v:
new_v += divisor
return new_v
На данный момент представлена сетевая структура MobileNet V3.