EfficientNet ファミリーの設計思想と実装詳解

画像認識タスクで精度を向上させる際、従来は「ネットワークを深くする」「チャネルを増やす」「入力画像を大きくする」のいずれかを単独で拡張する手法が主流だった。EfficientNet シリーズは、これら3軸を同時に最適化する「Compound Scaling」を提唱し、精度と計算効率の両立を実現した。

EfficientNet V1:Compound Scaling の理論

問題意識

単軸スケーリングでは、モデルが大きくなるほど精度向上の割合が低下し、かつ各軸が相互に影響するため、バランスを考慮しない設計は非効率である。そこで、深さ(depth)幅(width)解像度(resolution)を統一的に扱う拡張則を導入する。

定式化

あるステージ iLi 回繰り返すと仮定し、入力テンソルの形状を <Hi, Wi, Ci> とする。全体のネットワーク N

N = ⨁<sub>i=1…s</sub> F<sub>i</sub><sup>L<sub>i</sub></sup>(X<sub><H<sub>i</sub>,W<sub>i</sub>,C<sub>i</sub>></sub>)

と記述できる。ここで Scaling 係数 d, w, r を導入し、

depth:  d = α<sup>φ</sup>
width:  w = β<sup>φ</sup>
resol.: r = γ<sup>φ</sup>

ただし α·β²·γ ≈ 2 とし、φ は計算予算を制御するハイパーパラメータである。これにより FLOPS は約 2φ 倍に増加し、リソースに応じたスケーリングが可能になる。

SE(Squeeze-and-Excitation)モジュール

各 MBConv ブロック内に組み込まれる SE モジュールは、チャネルごとの重要度を推定し、特徴マップに重み付けを行う。

class ChannelAttention(nn.Module):
    def __init__(self, in_ch: int, mid_ch: int):
        super().__init__()
        self.pool = nn.AdaptiveAvgPool2d(1)
        self.fc1  = nn.Conv2d(in_ch, mid_ch, 1, bias=False)
        self.act  = nn.SiLU()
        self.fc2  = nn.Conv2d(mid_ch, in_ch, 1, bias=False)
        self.gate = nn.Sigmoid()

    def forward(self, x):
        w = self.pool(x)
        w = self.act(self.fc1(w))
        w = self.gate(self.fc2(w))
        return x * w

MBConv ブロック実装例

class MBConv(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.stride = cfg.stride
        self.skip   = (cfg.stride == 1) and (cfg.in_c == cfg.out_c)

        layers = []
        # expansion 1×1 conv
        if cfg.expand_ratio != 1:
            layers += [ConvBNAct(cfg.in_c, cfg.exp_c, 1)]
        # depthwise 3×3 or 5×5 conv
        layers += [ConvBNAct(cfg.exp_c, cfg.exp_c, cfg.k,
                             stride=cfg.stride, groups=cfg.exp_c)]
        # SE
        if cfg.se:
            layers += [ChannelAttention(cfg.exp_c, cfg.exp_c // 4)]
        # projection 1×1 conv
        layers += [ConvBNAct(cfg.exp_c, cfg.out_c, 1, act=None)]
        self.conv = nn.Sequential(*layers)

        self.drop = DropPath(cfg.drop) if cfg.drop else nn.Identity()

    def forward(self, x):
        out = self.conv(x)
        if self.skip:
            out = self.drop(out) + x
        return out

EfficientNet V2:学習効率を重視した改良

Training-Aware NAS

EfficientNetV2 は、精度だけでなく「学習速度」「パラメータ効率」を含むマルチ目的最適化を行う。探索空間には

  • 演算タイプ:MBConv / Fused-MBConv
  • レイヤー数、カーネルサイズ(3×3, 5×5)
  • expansion ratio(1, 4, 6)

を含め、報酬関数として A · Sw · Pv(w=−0.07, v=−0.05)を採用。

Fused-MBConv

depthwise 分離畳み込みの代わりに通常の 3×3 畳み込みを使用し、メモリアクセスを削減して学習を高速化する。

class FusedMBConv(nn.Module):
    def __init__(self, in_c, out_c, k=3, stride=1, expand=4, drop=0.0):
        super().__init__()
        hidden = in_c * expand
        self.skip = (stride == 1) and (in_c == out_c)

        layers = []
        if expand != 1:
            layers += [ConvBNAct(in_c, hidden, k, stride=stride)]
            layers += [ConvBNAct(hidden, out_c, 1, act=None)]
        else:
            layers += [ConvBNAct(in_c, out_c, k, stride=stride)]
        self.conv = nn.Sequential(*layers)

        self.drop = DropPath(drop) if drop else nn.Identity()

    def forward(self, x):
        out = self.conv(x)
        if self.skip:
            out = self.drop(out) + x
        return out

Progressive Learning & Adaptive Regularization

学習初期は小さい画像サイズと弱い正則化で高速に収束させ、徐々に画像サイズを大きくしつつ RandAugment・Mixup・Dropout などの強度を強めることで、最終精度を維持しながらトレーニング時間を短縮する。画像サイズ Si と正則化強度 Φi は線形補間で段階的に更新される。

画像サイズRandAug 強度5強度10強度15
12878.378.077.7
19281.281.681.5
30082.582.783.2

このように、画像サイズに応じて最適な正則化強度が存在することが実験的に示されている。

タグ: EfficientNet CompoundScaling NeuralArchitectureSearch MBConv FusedMBConv

5月30日 16:34 投稿