Изучение и внедрение сетевой структуры MobileNet V3

PyTorch
Изучение и внедрение сетевой структуры MobileNet V3

MobileNetV3 — это сетевая архитектура, предложенная Google 21 марта 2019 г., см.arXivизбумага, который включает в себя две подверсии, большую и малую.

исходный кодСсылаться на:GitHub.com/spike king/no…

Особенности:

  1. PyTorch реализует архитектуру MobileNetV3;
  2. Дизайн h-swish и h-sigmoid;
  3. Новый модуль MobileNet;
  4. структура SE и остаточная структура;
  5. Последний этап: продвинутый средний пул и использование свертки 1x1;

Структура сети:

网络结构


Общая структура

Сетевую структуру MobileNetV3 можно разделить на три части:

  • Начальная часть: 1 слой свертки, свертка 3x3, извлечение признаков;
  • Средняя часть: несколько сверточных слоев, разные версии Large и Small, разные слои и параметры;
  • Последняя часть: через два сверточных слоя 1х1 вместо полносвязных вывести категорию;

Структура сети выглядит следующим образом, где параметром является Большая система:

Overall

Исходный код выглядит следующим образом:

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-переключателя.

Start

Исходный код выглядит следующим образом:

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-переключатель представляет собой нелинейную функцию активации, формула которой выглядит следующим образом:

Formula

График выглядит следующим образом:

h-switch

Исходный код:

out = F.relu6(x + 3., self.inplace) / 6.
return out * x

h-сигмоид представляет собой нелинейную функцию активации для структуры SE:

Исходный код:

return F.relu6(x + 3., inplace=self.inplace) / 6.

График выглядит следующим образом:

h-sigmoid

Формула расчета свертки

  • Входное изображение: Ш×Ш
  • Ядро свертки: F×F
  • Размер шага: S
  • Пиксельное значение заполнения: P
  • Размер выходного изображения: N×N

формула:

N = (W − F + 2P ) / S + 1

Среди них он округляется в меньшую сторону, а лишние пиксели в расчете не участвуют.


средняя часть

Средняя часть представляет собой сетевую структуру из нескольких блоков, содержащих сверточные слои (MobileBlock).Для справки, сетевая структура Large аналогична Small:

Large

в:

  • 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

Три необходимых шага:

  1. свертка 1x1, преобразованная из входного канала в канал расширения;
  2. Свертка 3x3 или 5x5, расширение канала, использование шага;
  3. Свертка 1x1, по каналу расширения, преобразованная в выходной канал.

Два необязательных шага:

  1. Структура SE: ​​сжимай и возбуждай;
  2. Операция соединения, остаточный остаток, размер шага равен 1, а входные и выходные каналы одинаковы;

Есть две функции активации: ReLU и h-swish.

Структура следующая, параметры специфические, а не общие:

MobileBlock

Исходный код:

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

  1. объединение;
  2. Сожмите линейную ссылку + RELU + Возбудите линейную ссылку +h-sigmoid;
  3. изменить размер;
  4. Вес умножается на исходное значение;

Исходный код:

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 используется напрямую, как показано на рисунке:

Last Stage

Исходный код:

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.