mobileNetV2の解析と複数のバージョンの実装

一緒に書く習慣を身につけましょう!「ナゲッツデイリーニュープラン・4月アップデートチャレンジ」に参加して14日目です。クリックしてイベントの詳細をご覧ください

mobileNetV2はmobileNetV1を改良したもので、軽量のニューラルネットワークです。mobileNetV2は、V1バージョンの深さ方向に分離可能な畳み込みを保持し、線形ボトルネックと反転残差を追加します。

MobileNetV2のモデルを次の図に示します。ここで、tはボトルネック層の内部寸法の増加の倍数、cはフィーチャの寸法、nはボトルネック層の繰り返し回数、sはストライドです。ボトルネック層の最初のコンバージョンの

画像-20220201195718346

最初のレイヤーを除いて、ネットワーク全体で一定の拡張率が使用されます。実験では、5〜10のスケーリングレートでほぼ同じパフォーマンス曲線が得られ、小さいネットワークではわずかに低いスケーリングレートでパフォーマンスが向上し、大きいネットワークでは大きいスケーリングレートでパフォーマンスがわずかに向上することがわかりました。

MobileNetV2は、主に入力テンソルのサイズに適用される6の拡張係数を使用します。たとえば、64チャネルの入力テンソルを取り、128チャネルのテンソルを生成するボトルネック層の場合、中間拡張層は64×6=384チャネルです。

線形ボトルネック

mobileNetV1の深さ方向に分離可能な畳み込みの場合、幅乗数によって圧縮されたM次元空間は非線形変換ReLUを受けます。ReLUの性質により、入力特徴が負の場合、チャネルの特徴はクリアされます。は圧縮されているため、特徴情報がさらに失われます。入力特徴が正の数の場合、アクティベーションレイヤーの出力特徴は元の入力値になります。これは線形変換に相当します。

ボトルネック層の具体的な構造を次の表に示します。入力は、1のconv + ReLUレイヤーを介してk次元からtk次元に次元を増やし、次に3×3 conv + ReLU分離可能畳み込み(ストライド> 1の場合)を介して画像をダウンサンプリングします。この時点で、特徴次元はすでにtkです。最後に、次元は1 * 1conv(ReLUなし)だけ減少し、次元はtkからk次元に減少します。

画像-20220201201436575

反転残差

残りのブロックは、精度を向上させ、より深いネットワークを構築するのに役立つことがResNetで証明されているため、mobileNetV2でも同様のブロックが導入されています。従来の残差ブロックのプロセスは次のとおりです。1x1(次元削減)-> 3x3(畳み込み)-> 1x1(次元増加)、ただし深さ方向の畳み込み層(深さ方向の畳み込み層)抽出特徴は入力特徴次元に制限されます、残差ブロックが使用されている場合、入力特徴マップは1x1の点ごとの畳み込み操作によって圧縮され、抽出された特徴は深さ方向の畳み込み後に少なくなります。したがって、mobileNetV2は、最初に1x1のポイントごとの畳み込み演算によって特徴マップのチャネルを拡張し、特徴の数を増やし、精度を向上させます。このプロセスは、反転された残差の原点である残差ブロックの順序を逆にします:1x1(次元増加)-> 3x3(dw conv + relu)-> 1x1(次元減少+線形変換)。

上記の線形ボトルネックと反転残差の理解と組み合わせて、ブロック図を作成しました。以下に示すように:

画像-20220201200602545

ブロックのコード実装:

pytorchバージョン

def Conv3x3BNReLU(in_channels,out_channels,stride,groups):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=3, stride=stride, padding=1, groups=groups),
            nn.BatchNorm2d(out_channels),
            nn.ReLU6(inplace=True)
        )

def Conv1x1BNReLU(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels),
            nn.ReLU6(inplace=True)
        )

def Conv1x1BN(in_channels,out_channels):
    return nn.Sequential(
            nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=1),
            nn.BatchNorm2d(out_channels)
        )

class InvertedResidual(nn.Module):
    def __init__(self, in_channels, out_channels, stride, expansion_factor=6):
        super(InvertedResidual, self).__init__()
        self.stride = stride
        mid_channels = (in_channels * expansion_factor)

        self.bottleneck = nn.Sequential(
            Conv1x1BNReLU(in_channels, mid_channels),
            Conv3x3BNReLU(mid_channels, mid_channels, stride,groups=mid_channels),
            Conv1x1BN(mid_channels, out_channels)
        )

        if self.stride == 1:
            self.shortcut = Conv1x1BN(in_channels, out_channels)

    def forward(self, x):
        out = self.bottleneck(x)
        out = (out+self.shortcut(x)) if self.stride==1 else out
        return out

复制代码

ケラスバージョン

def relu6(x):
    return K.relu(x, max_value=6)

# 保证特征层数为8的倍数
def make_divisible(v, divisor, min_value=None):
    if min_value is None:
        min_value = divisor
    new_v = max(min_value, int(v+divisor/2)//divisor*divisor) #//向下取整,除
    if new_v<0.9*v:
        new_v +=divisor
    return new_v

def pad_size(inputs, kernel_size):

    input_size = inputs.shape[1:3]

    if isinstance(kernel_size, int):
        kernel_size = (kernel_size, kernel_size)

    if input_size[0] is None:
        adjust = (1,1)

    else:
        adjust = (1- input_size[0]%2, 1-input_size[1]%2)
    
    correct = (kernel_size[0]//2, kernel_size[1]//2)

    return ((correct[0] - adjust[0], correct[0]),
            (correct[1] - adjust[1], correct[1]))

def conv_block (x, nb_filter, kernel=(1,1), stride=(1,1), name=None):

    x = Conv2D(nb_filter, kernel, strides=stride, padding='same', use_bias=False, name=name+'_expand')(x)
    x = BatchNormalization(axis=3, name=name+'_expand_BN')(x)
    x = Activation(relu6, name=name+'_expand_relu')(x)

    return x


def depthwise_res_block(x, nb_filter, kernel, stride, t, alpha, resdiual=False, name=None):

    input_tensor=x
    exp_channels= x.shape[-1]*t  #扩展维度
    alpha_channels = int(nb_filter*alpha)     #压缩维度

    x = conv_block(x, exp_channels, (1,1), (1,1), name=name)

    if stride[0]==2:
        x = ZeroPadding2D(padding=pad_size(x, 3), name=name+'_pad')(x)

    x = DepthwiseConv2D(kernel, padding='same' if stride[0]==1 else 'valid', strides=stride, depth_multiplier=1, use_bias=False, name=name+'_depthwise')(x)

    x = BatchNormalization(axis=3, name=name+'_depthwise_BN')(x)
    x = Activation(relu6, name=name+'_depthwise_relu')(x)

    x = Conv2D(alpha_channels, (1,1), padding='same', use_bias=False, strides=(1,1), name=name+'_project')(x)
    x = BatchNormalization(axis=3, name=name+'_project_BN')(x)

    if resdiual:
        x = layers.add([x, input_tensor], name=name+'_add')

    return x
复制代码

おすすめ

転載: juejin.im/post/7086689010050924552