前言
因为以后会用到,所以这块尝试看一下轻量型神经网络的代码,同样是写给小白的,不深究只教大家大致如何去看。而且MobileNetV3有两个版本,我们以small为例。
代码文件结构
其中MobileNetV3—small和large是网络结构搭建,MobileNetV3—factory是将两个结构整合到一起。layers是各个子模块的构建。其他是训练和测试文件。
MobileNetV3—small结构
MobileNetV3—small代码
整体代码
# Copyright 2019 Bisonai Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Implementation of paper Searching for MobileNetV3, https://arxiv.org/abs/1905.02244
MobileNetV3 Small
"""
import tensorflow as tf
from layers import ConvNormAct
from layers import Bneck
from layers import LastStage
from utils import _make_divisible
from utils import LayerNamespaceWrapper
class MobileNetV3(tf.keras.Model):
def __init__(
self,
num_classes: int=1001,
width_multiplier: float=1.0,
name: str="MobileNetV3_Small",
divisible_by: int=8,
l2_reg: float=1e-5,
):
super().__init__(name=name)
# First layer
self.first_layer = ConvNormAct(
16,
kernel_size=3,
stride=2,
padding=1,
norm_layer="bn",
act_layer="hswish",
use_bias=False,
l2_reg=l2_reg,
name="FirstLayer",
)
# Bottleneck layers
self.bneck_settings = [
# k exp out SE NL s
[ 3, 16, 16, True, "relu", 2 ],
[ 3, 72, 24, False, "relu", 2 ],
[ 3, 88, 24, False, "relu", 1 ],
[ 5, 96, 40, True, "hswish", 2 ],
[ 5, 240, 40, True, "hswish", 1 ],
[ 5, 240, 40, True, "hswish", 1 ],
[ 5, 120, 48, True, "hswish", 1 ],
[ 5, 144, 48, True, "hswish", 1 ],
[ 5, 288, 96, True, "hswish", 2 ],
[ 5, 576, 96, True, "hswish", 1 ],
[ 5, 576, 96, True, "hswish", 1 ],
]
self.bneck = tf.keras.Sequential(name="Bneck")
for idx, (k, exp, out, SE, NL, s) in enumerate(self.bneck_settings):
out_channels = _make_divisible(out * width_multiplier, divisible_by)
exp_channels = _make_divisible(exp * width_multiplier, divisible_by)
self.bneck.add(
LayerNamespaceWrapper(
Bneck(
out_channels=out_channels,
exp_channels=exp_channels,
kernel_size=k,
stride=s,
use_se=SE,
act_layer=NL,
),
name=f"Bneck{idx}")
)
# Last stage
penultimate_channels = _make_divisible(576 * width_multiplier, divisible_by)
last_channels = _make_divisible(1_280 * width_multiplier, divisible_by)
self.last_stage = LastStage(
penultimate_channels,
last_channels,
num_classes,
l2_reg=l2_reg,
)
def call(self, input):
x = self.first_layer(input)
x = self.bneck(x)
x = self.last_stage(x)
return x
这段代码看着比较长,其实只是分三部分,因为子模块在layers都定义好了,直接调用就行了。所以只是类的重写,其实就是传参。下面我们挑主要介绍一下。
self.first_layer = ConvNormAct(
16,
kernel_size=3,
stride=2,
padding=1,
norm_layer="bn",
act_layer="hswish",
use_bias=False,
l2_reg=l2_reg,
name="FirstLayer",
)
这是一层的定义,对应结构的:
self.bneck_settings = [
# k exp out SE NL s
[ 3, 16, 16, True, "relu", 2 ],
[ 3, 72, 24, False, "relu", 2 ],
[ 3, 88, 24, False, "relu", 1 ],
[ 5, 96, 40, True, "hswish", 2 ],
[ 5, 240, 40, True, "hswish", 1 ],
[ 5, 240, 40, True, "hswish", 1 ],
[ 5, 120, 48, True, "hswish", 1 ],
[ 5, 144, 48, True, "hswish", 1 ],
[ 5, 288, 96, True, "hswish", 2 ],
[ 5, 576, 96, True, "hswish", 1 ],
[ 5, 576, 96, True, "hswish", 1 ],
]
这块是对这几层的定义:
但是请注意这并不是真正的层的定义,只不过是把好多层参数写在一起,方便后面一起传参,或者说类的重写。
self.bneck = tf.keras.Sequential(name="Bneck")
for idx, (k, exp, out, SE, NL, s) in enumerate(self.bneck_settings):
out_channels = _make_divisible(out * width_multiplier, divisible_by)
exp_channels = _make_divisible(exp * width_multiplier, divisible_by)
这块大家可能有点费解,这是一个重要参数:宽度因子。
MobileNet本身的网络结构已经比较小并且执行延迟较低,但为了适配更定制化的场景,MobileNet提供了称为宽度因子(Width Multiplier)的超参数给我们调整。宽度因子在MobileNetV1、V2、V3都可以运用。
通过宽度因子,可以调整神经网络中间产生的特征的大小,调整的是特征数据通道数大小,从而调整了运算量的大小。
其实就是一个调节参数量的因子。用它乘以各个参数就能调节网络大小。
然后是这几层的定义:
self.bneck.add(
LayerNamespaceWrapper(
Bneck(
out_channels=out_channels,
exp_channels=exp_channels,
kernel_size=k,
stride=s,
use_se=SE,
act_layer=NL,
),
name=f"Bneck{idx}")
)
然后是最后几层的定义:
)
penultimate_channels = _make_divisible(576 * width_multiplier, divisible_by)
last_channels = _make_divisible(1_280 * width_multiplier, divisible_by)
self.last_stage = LastStage(
penultimate_channels,
last_channels,
num_classes,
l2_reg=l2_reg,
)
然后就是让整体网络一步步计算:
def call(self, input):
x = self.first_layer(input)
x = self.bneck(x)
x = self.last_stage(x)
return x
最后
轻量型网络的MobileNetV3—small就写到这了,希望能帮到大家。