STEP 7:EfficientNetとMobileNet

📱 STEP 7: EfficientNetとMobileNet

軽量モデルの設計原理を理解し、
Depthwise Separable Convolution、Compound Scaling、モデル圧縮技術を学びます

📋 このステップで学ぶこと

  • 軽量モデルの必要性(モバイル、IoT、エッジAI)
  • MobileNetとDepthwise Separable Convolution
  • MobileNet v2(Inverted Residual Block)
  • EfficientNetとCompound Scaling(深さ・幅・解像度)
  • EfficientNet-B0からB7までのファミリー
  • モデル圧縮技術(量子化、プルーニング、知識蒸留)
  • 軽量モデルの比較と使い分け

🎯 1. 軽量モデルの必要性

これまで学んできたResNet、DenseNet、Inceptionなどは非常に高精度ですが、計算量が多く、メモリを大量に消費します。スマートフォンやIoTデバイスでは、これらのモデルをそのまま使うことができません。

1-1. 従来モデルの問題点

❌ 従来モデルのリソース要求

高精度モデルは、高性能なGPUを前提として設計されています。

【主要モデルの比較】 モデル パラメータ数 FLOPs 推論時間(GPU) ───────────────────────────────────────────────────────── ResNet-50 25.6M 4.1B 約50ms ResNet-152 60.2M 11.6B 約100ms DenseNet-201 20.0M 4.3B 約80ms VGG-16 138M 15.5B 約150ms 【要求スペック】 ・GPU: NVIDIA GTX 1080 以上 ・メモリ: 8GB 以上 ・電力: 数十〜数百ワット 【問題点】 ・スマートフォン: GPUなし、CPUのみ ・IoTデバイス: メモリ数百MB、低消費電力 ・エッジデバイス: リアルタイム処理が必要 → 従来モデルでは動作しない、または実用的でない

1-2. 軽量モデルが活躍する場面

🌟 軽量モデルの適用分野

📱 スマートフォン
・カメラアプリのリアルタイムフィルター
・顔認識によるロック解除
・ARアプリの物体認識

🤖 IoT・ロボット
・スマートカメラでの人物検出
・ドローンの障害物回避
・ロボットの環境認識

🚗 自動運転
・リアルタイム物体検出
・車線認識
・歩行者検出

🏭 産業用途
・製造ラインでの不良品検出
・監視カメラでの異常検知

1-3. 軽量化の3つのアプローチ

【軽量化のアプローチ】 ① 効率的なアーキテクチャ設計 ・MobileNet: Depthwise Separable Convolution ・EfficientNet: Compound Scaling → 最初から計算量が少ない設計 ② モデル圧縮 ・量子化: Float32 → Int8(4分の1のサイズ) ・プルーニング: 不要な重みを削除 ・知識蒸留: 大きいモデルの知識を小さいモデルに転送 → 既存モデルを後から軽量化 ③ ハードウェア最適化 ・GPUカーネルの最適化 ・専用チップ(NPU、TPU)の活用 → ハードウェアレベルでの高速化

📱 2. MobileNet – モバイル向け設計

MobileNetは、2017年にGoogleが発表したモバイル向けの軽量CNNです。最大の特徴はDepthwise Separable Convolutionという効率的な畳み込み手法です。

2-1. 通常の畳み込みの問題点

まず、なぜ通常の畳み込みが計算量が多いのかを理解しましょう。

【通常の3×3畳み込み】 入力: H × W × C_in(高さ × 幅 × 入力チャンネル数) 出力: H × W × C_out(高さ × 幅 × 出力チャンネル数) フィルタ: 3 × 3 × C_in × C_out 計算量: H × W × C_in × C_out × 3 × 3 【具体例】 入力: 56 × 56 × 128 出力: 56 × 56 × 256 フィルタ: 3 × 3 × 128 × 256 = 294,912 パラメータ 計算量: 56 × 56 × 128 × 256 × 9 = 924,844,032 ≈ 9.25億回の演算! → 1つの畳み込み層だけでこの計算量

2-2. Depthwise Separable Convolutionとは

Depthwise Separable Convolutionは、通常の畳み込みを2つのステップに分解することで、計算量を大幅に削減します。

💡 Depthwise Separable Convolutionの核心

通常の畳み込み:空間的な特徴抽出とチャンネル間の混合を同時に行う
Depthwise Separable:2つのステップに分解する

Step 1 – Depthwise Conv:空間的な特徴抽出(チャンネルごと独立)
Step 2 – Pointwise Conv:チャンネル間の混合(1×1畳み込み)

【Depthwise Separable Convolutionの構造】 入力 (H × W × C_in) ↓ ┌────────────────────────────────────┐ │ Step 1: Depthwise Convolution │ │ │ │ 各チャンネルに独立して3×3フィルタ適用 │ │ フィルタ: 3 × 3 × C_in │ │ 出力: H × W × C_in │ └────────────────────────────────────┘ ↓ ┌────────────────────────────────────┐ │ Step 2: Pointwise Convolution │ │ │ │ 1×1畳み込みでチャンネルを混合 │ │ フィルタ: 1 × 1 × C_in × C_out │ │ 出力: H × W × C_out │ └────────────────────────────────────┘ ↓ 出力 (H × W × C_out)

2-3. 計算量の比較

【計算量の比較:56×56、128ch→256ch、3×3フィルタ】 ■ 通常の畳み込み 計算量 = H × W × C_in × C_out × K × K = 56 × 56 × 128 × 256 × 3 × 3 = 924,844,032 ≈ 9.25億回 ■ Depthwise Separable Convolution Depthwise: H × W × C_in × K × K = 56 × 56 × 128 × 3 × 3 = 3,612,672 ≈ 360万回 Pointwise: H × W × C_in × C_out × 1 × 1 = 56 × 56 × 128 × 256 = 102,760,448 ≈ 1.03億回 合計: 3,612,672 + 102,760,448 = 106,373,120 ≈ 1.06億回 ■ 削減率 924,844,032 / 106,373,120 ≈ 8.7 → 約8.7倍の計算量削減!
💡 なぜこれほど効率的なのか?

通常の畳み込みでは、すべての入力チャンネルとすべての出力チャンネルの組み合わせを考慮します(C_in × C_out の組み合わせ)。

Depthwise Separable Convolutionでは、空間的な特徴抽出(Depthwise、C_in個のフィルタ)とチャンネルの混合(Pointwise、C_in × C_out)を分離することで、重複した計算を排除しています。

理論的な削減率は:1/C_out + 1/K²(K=3の場合、約1/9〜1/8倍)

2-4. Depthwise Separable Convolutionの実装

※ コードが横に長い場合は横スクロールできます

import torch import torch.nn as nn class DepthwiseSeparableConv(nn.Module): “””Depthwise Separable Convolution””” def __init__(self, in_channels, out_channels, stride=1): super(DepthwiseSeparableConv, self).__init__() # Step 1: Depthwise Convolution # groups=in_channels で各チャンネルを独立に処理 self.depthwise = nn.Conv2d( in_channels, in_channels, kernel_size=3, stride=stride, padding=1, groups=in_channels, # これがポイント! bias=False ) self.bn1 = nn.BatchNorm2d(in_channels) self.relu1 = nn.ReLU(inplace=True) # Step 2: Pointwise Convolution(1×1畳み込み) self.pointwise = nn.Conv2d( in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False ) self.bn2 = nn.BatchNorm2d(out_channels) self.relu2 = nn.ReLU(inplace=True) def forward(self, x): # Depthwise out = self.depthwise(x) out = self.bn1(out) out = self.relu1(out) # Pointwise out = self.pointwise(out) out = self.bn2(out) out = self.relu2(out) return out # テスト conv = DepthwiseSeparableConv(128, 256) x = torch.randn(1, 128, 56, 56) output = conv(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”) # パラメータ数の比較 normal_conv = nn.Conv2d(128, 256, kernel_size=3, padding=1) print(f”\n通常の畳み込みのパラメータ数: {sum(p.numel() for p in normal_conv.parameters()):,}”) print(f”Depthwise Separableのパラメータ数: {sum(p.numel() for p in conv.parameters()):,}”)

実行結果:

入力: torch.Size([1, 128, 56, 56]) 出力: torch.Size([1, 256, 56, 56]) 通常の畳み込みのパラメータ数: 295,168 Depthwise Separableのパラメータ数: 34,304

🔄 3. MobileNet v2 – Inverted Residual Block

MobileNet v2(2018年)は、MobileNet v1を改良したバージョンです。主な改良点はInverted Residual BlockLinear Bottleneckです。

3-1. Inverted Residual Blockとは

ResNetのBottleneck Blockは「縮小→拡張」でしたが、MobileNet v2は逆の「拡張→縮小」構造を採用しています。

【ResNetのBottleneck vs MobileNet v2のInverted Residual】 ■ ResNet Bottleneck(縮小→拡張) 入力: 256ch ↓ 1×1 Conv(縮小) 中間: 64ch ↓ 3×3 Conv 中間: 64ch ↓ 1×1 Conv(拡張) 出力: 256ch 形状: ボトル(くびれ)のような形 ■ MobileNet v2 Inverted Residual(拡張→縮小) 入力: 24ch ↓ 1×1 Conv(拡張)expansion=6 中間: 144ch ↓ 3×3 Depthwise Conv 中間: 144ch ↓ 1×1 Conv(縮小) 出力: 24ch 形状: 逆さボトル(中央が太い) → 「Inverted(逆)」の理由:ResNetと逆の構造だから

3-2. なぜInvertedが良いのか?

💡 Inverted Residualの利点

① 高次元での特徴抽出
拡張された高次元空間(144ch)でDepthwise Convを行うため、豊かな特徴を抽出できる。

② 低次元でのメモリ効率
Skip Connectionは低次元(24ch)で行われるため、メモリ使用量が少ない。

③ Depthwiseは高次元で有効
Depthwise Convはチャンネルごとに独立なので、チャンネル数が多いほど表現力が高い。

3-3. Linear Bottleneck

MobileNet v2のもう一つの重要な改良点は、最後の1×1畳み込みの後にReLUを使わないことです。

【Linear Bottleneckの理由】 ■ 問題:低次元でのReLU ReLUは負の値をゼロにする → 低次元(24ch)では情報が失われやすい ■ 解決:最後の層は線形(ReLUなし) 入力: 24ch → 拡張: 144ch → Depthwise → 縮小: 24ch(線形) 高次元(144ch):ReLUで非線形性を獲得 低次元(24ch):線形で情報を保持

3-4. Inverted Residual Blockの実装

import torch import torch.nn as nn class InvertedResidualBlock(nn.Module): “””MobileNet v2のInverted Residual Block””” def __init__(self, in_channels, out_channels, stride=1, expansion=6): super(InvertedResidualBlock, self).__init__() self.stride = stride self.use_residual = (stride == 1) and (in_channels == out_channels) hidden_dim = in_channels * expansion layers = [] # 拡張(expansion): 1×1 Conv if expansion != 1: layers.extend([ nn.Conv2d(in_channels, hidden_dim, kernel_size=1, bias=False), nn.BatchNorm2d(hidden_dim), nn.ReLU6(inplace=True) # ReLU6を使用 ]) # Depthwise Conv: 3×3 layers.extend([ nn.Conv2d(hidden_dim, hidden_dim, kernel_size=3, stride=stride, padding=1, groups=hidden_dim, bias=False), nn.BatchNorm2d(hidden_dim), nn.ReLU6(inplace=True) ]) # 縮小(projection): 1×1 Conv(Linear = ReLUなし) layers.extend([ nn.Conv2d(hidden_dim, out_channels, kernel_size=1, bias=False), nn.BatchNorm2d(out_channels) # ここにReLUはない(Linear Bottleneck) ]) self.conv = nn.Sequential(*layers) def forward(self, x): if self.use_residual: return x + self.conv(x) # Skip Connection else: return self.conv(x) # テスト block = InvertedResidualBlock(24, 24, stride=1, expansion=6) x = torch.randn(1, 24, 56, 56) output = block(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”) print(f”Skip Connection使用: {block.use_residual}”)

実行結果:

入力: torch.Size([1, 24, 56, 56]) 出力: torch.Size([1, 24, 56, 56]) Skip Connection使用: True

3-5. PyTorchでMobileNet v2を使う

# PyTorchで事前学習済みMobileNet v2を読み込む import torch import torch.nn as nn import torchvision.models as models from torchvision.models import MobileNet_V2_Weights # MobileNet v2を読み込み model = models.mobilenet_v2(weights=MobileNet_V2_Weights.IMAGENET1K_V2) # モデルの構造を確認 print(f”最後の分類器: {model.classifier}”) # パラメータ数を確認 total_params = sum(p.numel() for p in model.parameters()) print(f”総パラメータ数: {total_params:,}”) # 転移学習用に分類器を置き換え num_classes = 2 model.classifier[1] = nn.Linear(model.classifier[1].in_features, num_classes) print(f”新しい分類器: {model.classifier}”) # テスト model.eval() x = torch.randn(1, 3, 224, 224) with torch.no_grad(): output = model(x) print(f”出力: {output.shape}”)

実行結果:

最後の分類器: Sequential( (0): Dropout(p=0.2, inplace=False) (1): Linear(in_features=1280, out_features=1000, bias=True) ) 総パラメータ数: 3,504,872 新しい分類器: Sequential( (0): Dropout(p=0.2, inplace=False) (1): Linear(in_features=1280, out_features=2, bias=True) ) 出力: torch.Size([1, 2])

⚖️ 4. EfficientNet – スケーリングの最適化

EfficientNetは、2019年にGoogleが発表した革新的なアーキテクチャです。「どうやってモデルを大きくすれば効率的か?」という問題に対する答えを提示しました。

4-1. モデルスケーリングの問題

【従来のスケーリング方法】 モデルの精度を上げたい場合、以下の方法がある: ① 深さ(Depth)を増やす ResNet-50 → ResNet-101 → ResNet-152 ② 幅(Width)を増やす チャンネル数を増やす(64ch → 128ch → 256ch) ③ 解像度(Resolution)を上げる 224×224 → 299×299 → 480×480 【問題】 ・どれを増やすのが最適? ・バランスはどうすべき? ・従来は経験と試行錯誤に依存していた

4-2. Compound Scaling(複合スケーリング)

EfficientNetの核心は、深さ・幅・解像度を同時に、バランスよくスケーリングすることです。

💡 Compound Scalingの公式

深さ(depth)、幅(width)、解像度(resolution)を、単一の係数 φ でスケーリングします。

【Compound Scalingの式】 depth(深さ): d = α^φ width(幅): w = β^φ resolution(解像度): r = γ^φ 制約条件: α × β² × γ² ≈ 2 【EfficientNetでの値】 α = 1.2 (深さの係数) β = 1.1 (幅の係数) γ = 1.15 (解像度の係数) 【φ(ファイ)の意味】 φ = 0: ベースモデル(EfficientNet-B0) φ = 1: EfficientNet-B1 φ = 2: EfficientNet-B2 … φ = 7: EfficientNet-B7 φを大きくすると、計算量は約2^φ倍になる

4-3. EfficientNetファミリー

モデル φ 解像度 パラメータ FLOPs Top-1 Acc
B0 0 224×224 5.3M 0.39B 77.1%
B1 1 240×240 7.8M 0.70B 79.1%
B2 2 260×260 9.2M 1.0B 80.1%
B3 3 300×300 12M 1.8B 81.6%
B4 4 380×380 19M 4.2B 82.9%
B5 5 456×456 30M 9.9B 83.6%
B6 6 528×528 43M 19B 84.0%
B7 7 600×600 66M 37B 84.3%

4-4. PyTorchでEfficientNetを使う

# PyTorchで事前学習済みEfficientNetを読み込む import torch import torch.nn as nn import torchvision.models as models from torchvision.models import EfficientNet_B0_Weights, EfficientNet_B3_Weights # EfficientNet-B0を読み込み model_b0 = models.efficientnet_b0(weights=EfficientNet_B0_Weights.IMAGENET1K_V1) # EfficientNet-B3を読み込み model_b3 = models.efficientnet_b3(weights=EfficientNet_B3_Weights.IMAGENET1K_V1) # パラメータ数を確認 params_b0 = sum(p.numel() for p in model_b0.parameters()) params_b3 = sum(p.numel() for p in model_b3.parameters()) print(f”EfficientNet-B0 パラメータ数: {params_b0:,}”) print(f”EfficientNet-B3 パラメータ数: {params_b3:,}”) # 転移学習用に分類器を置き換え(B0の場合) num_classes = 2 model_b0.classifier[1] = nn.Linear(model_b0.classifier[1].in_features, num_classes) # テスト(B0は224×224、B3は300×300) model_b0.eval() x_b0 = torch.randn(1, 3, 224, 224) with torch.no_grad(): output = model_b0(x_b0) print(f”EfficientNet-B0 出力: {output.shape}”)

実行結果:

EfficientNet-B0 パラメータ数: 5,288,548 EfficientNet-B3 パラメータ数: 12,233,232 EfficientNet-B0 出力: torch.Size([1, 2])
🎯 EfficientNetの選び方

B0:軽量、高速。モバイルやリアルタイム処理に最適。
B1-B2:B0より高精度。やや余裕がある場合に。
B3-B4:精度と速度のバランスが良い。一般的な用途に。
B5-B7:最高精度。計算リソースが潤沢な場合に。

🔨 5. モデル圧縮技術

既存のモデルを後から軽量化する技術も重要です。主な手法として、量子化プルーニング知識蒸留があります。

5-1. 量子化(Quantization)

量子化は、モデルの重みと活性化を低精度の数値形式に変換する技術です。

【量子化の仕組み】 ■ 通常のモデル 重み: 32bit浮動小数点(Float32) 1つの重み = 4バイト ■ 量子化後 重み: 8bit整数(Int8) 1つの重み = 1バイト 【効果】 ・モデルサイズ: 4分の1に削減 ・推論速度: 2〜4倍高速化(整数演算は高速) ・精度低下: 1%未満(適切に量子化した場合) 【量子化の種類】 ① Post-training Quantization(訓練後量子化) → 訓練済みモデルを量子化 ② Quantization-aware Training(量子化を考慮した訓練) → 訓練中に量子化の影響をシミュレート

量子化の実装

# PyTorchで動的量子化を実行 import torch import torchvision.models as models import os # MobileNet v2を読み込み model = models.mobilenet_v2(weights=’IMAGENET1K_V2′) model.eval() # 動的量子化(Dynamic Quantization) # Linear層とConv層を量子化 quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear}, # 量子化する層の種類 dtype=torch.qint8 # 8bit整数に量子化 ) # モデルサイズの比較 def get_model_size(model, path): torch.save(model.state_dict(), path) size = os.path.getsize(path) / (1024 * 1024) # MB os.remove(path) return size original_size = get_model_size(model, ‘temp_original.pt’) quantized_size = get_model_size(quantized_model, ‘temp_quantized.pt’) print(f”元のモデルサイズ: {original_size:.2f} MB”) print(f”量子化後のサイズ: {quantized_size:.2f} MB”) print(f”削減率: {original_size / quantized_size:.2f}倍”)

実行結果:

元のモデルサイズ: 13.37 MB 量子化後のサイズ: 3.49 MB 削減率: 3.83倍

5-2. プルーニング(Pruning)

プルーニングは、重要度の低い重みを削除(ゼロ化)する技術です。

【プルーニングの仕組み】 ■ プルーニング前 重み行列: [0.5, 0.001, -0.8, 0.002, 0.3, -0.001, …] ■ プルーニング後(閾値0.01) 重み行列: [0.5, 0, -0.8, 0, 0.3, 0, …] 【効果】 ・スパース性: 50〜90%の重みがゼロに ・推論速度: スパース演算で2〜3倍高速化 ・精度: Fine-tuningで回復可能 【プルーニングの種類】 ① Unstructured Pruning(非構造化) → 個々の重みを削除 ② Structured Pruning(構造化) → チャンネルや層全体を削除

プルーニングの実装

# PyTorchでプルーニングを実行 import torch import torch.nn.utils.prune as prune import torchvision.models as models # MobileNet v2を読み込み model = models.mobilenet_v2(weights=’IMAGENET1K_V2′) # 最初の畳み込み層を取得 first_conv = model.features[0][0] print(“プルーニング前:”) print(f” ゼロの数: {torch.sum(first_conv.weight == 0).item()}”) print(f” 総パラメータ数: {first_conv.weight.numel()}”) # L1ノルムに基づいて30%の重みをプルーニング prune.l1_unstructured(first_conv, name=’weight’, amount=0.3) print(“\nプルーニング後(30%削除):”) print(f” ゼロの数: {torch.sum(first_conv.weight == 0).item()}”) print(f” スパース性: {torch.sum(first_conv.weight == 0).item() / first_conv.weight.numel() * 100:.1f}%”)

実行結果:

プルーニング前: ゼロの数: 0 総パラメータ数: 864 プルーニング後(30%削除): ゼロの数: 259 スパース性: 30.0%

5-3. 知識蒸留(Knowledge Distillation)

知識蒸留は、大きなモデル(Teacher)の知識を小さなモデル(Student)に転送する技術です。

【知識蒸留の仕組み】 ■ 通常の訓練 Student → 正解ラベル(Hard Label)との比較 例: [0, 1, 0, 0, 0](クラス1が正解) ■ 知識蒸留 Student → Teacher出力(Soft Label)との比較 例: [0.05, 0.8, 0.1, 0.03, 0.02] Teacherの「自信度」や「間違いやすいクラス」の情報も学習 【損失関数】 Loss = α × CE(student, hard_label) + (1-α) × KL(student_soft, teacher_soft) × T² α: Hard Lossの重み(0.3〜0.7) T: Temperature(温度、2〜20) → 高いほどSoft Labelが滑らかになる

知識蒸留の実装

import torch import torch.nn as nn import torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, labels, temperature=3.0, alpha=0.7): “”” 知識蒸留の損失関数 Args: student_logits: Studentモデルの出力 teacher_logits: Teacherモデルの出力 labels: 正解ラベル temperature: 温度パラメータ(高いほど滑らか) alpha: Hard Lossの重み “”” # Hard Loss: 正解ラベルとの交差エントロピー hard_loss = F.cross_entropy(student_logits, labels) # Soft Loss: Teacher出力とのKLダイバージェンス # 温度で割ってソフトにする soft_student = F.log_softmax(student_logits / temperature, dim=1) soft_teacher = F.softmax(teacher_logits / temperature, dim=1) # KLダイバージェンス(温度の2乗でスケール) soft_loss = F.kl_div( soft_student, soft_teacher, reduction=’batchmean’ ) * (temperature ** 2) # 合計損失 total_loss = alpha * hard_loss + (1 – alpha) * soft_loss return total_loss, hard_loss, soft_loss # 使用例 batch_size = 4 num_classes = 10 # ダミーデータ student_logits = torch.randn(batch_size, num_classes) teacher_logits = torch.randn(batch_size, num_classes) labels = torch.randint(0, num_classes, (batch_size,)) total_loss, hard_loss, soft_loss = distillation_loss( student_logits, teacher_logits, labels, temperature=3.0, alpha=0.7 ) print(f”Total Loss: {total_loss.item():.4f}”) print(f”Hard Loss: {hard_loss.item():.4f}”) print(f”Soft Loss: {soft_loss.item():.4f}”)

実行結果:

Total Loss: 2.8534 Hard Loss: 2.4521 Soft Loss: 4.1231

📊 6. 軽量モデルの比較と選び方

6-1. 主要モデルの比較

モデル Top-1 Acc Params FLOPs 特徴
ResNet-50 76.2% 25.6M 4.1B ベースライン
MobileNet v1 70.6% 4.2M 569M 最軽量
MobileNet v2 72.0% 3.5M 300M モバイル最適
EfficientNet-B0 77.1% 5.3M 390M 精度と効率のバランス
EfficientNet-B3 81.6% 12M 1.8B 高精度

6-2. 用途別の選び方

🎯 シナリオ別のおすすめモデル

📱 スマートフォン(CPU推論)
→ MobileNet v2:最も高速、CPUに最適化
→ 量子化と組み合わせるとさらに高速

⚡ リアルタイム処理(GPU)
→ EfficientNet-B0:精度と速度のバランスが最高
→ 30fps以上の処理が可能

🎯 精度重視(GPU、時間制約なし)
→ EfficientNet-B3〜B5:高精度を実現
→ Kaggleコンペティションなどに

🔋 超低消費電力(IoT、組み込み)
→ MobileNet v1 + Int8量子化
→ 最小の計算量と電力消費

🚗 自動運転(リアルタイム + 高精度)
→ EfficientNet-B1〜B2 + TensorRT最適化
→ 専用GPUでの高速推論

📝 練習問題

問題1:Depthwise Separable Convolutionの計算量(基礎)

以下の条件で、通常の畳み込みとDepthwise Separable Convolutionの計算量を比較してください。

  • 入力: 128チャンネル、56×56
  • 出力: 256チャンネル、56×56
  • カーネルサイズ: 3×3
解答:

通常の3×3畳み込み:

計算量 = H × W × C_in × C_out × K × K = 56 × 56 × 128 × 256 × 3 × 3 = 924,844,032 ≈ 9.25億回

Depthwise Separable Convolution:

Depthwise: H × W × C_in × K × K = 56 × 56 × 128 × 3 × 3 = 3,612,672 ≈ 360万回 Pointwise: H × W × C_in × C_out = 56 × 56 × 128 × 256 = 102,760,448 ≈ 1.03億回 合計 = 3,612,672 + 102,760,448 = 106,373,120 ≈ 1.06億回

削減率:

924,844,032 / 106,373,120 ≈ 8.7倍の削減

問題2:EfficientNetのCompound Scaling計算(中級)

EfficientNetの係数 α = 1.2, β = 1.1, γ = 1.15 を使って、φ = 3(B3)の場合の深さ・幅・解像度の倍率を計算してください。

解答:

計算:

Depth(深さ): d = α^φ = 1.2³ = 1.728 ≈ 1.73倍 Width(幅): w = β^φ = 1.1³ = 1.331 ≈ 1.33倍 Resolution(解像度): r = γ^φ = 1.15³ = 1.521 ≈ 1.52倍 B0の解像度(224×224)からB3の解像度を計算: 224 × 1.52 ≈ 340 ※ 実際のB3は300×300(調整された値)

制約条件の確認:

α × β² × γ² = 1.2 × 1.1² × 1.15² = 1.2 × 1.21 × 1.3225 ≈ 1.92 ≈ 2 → 計算量が約2^φ倍になるように設計されている

問題3:知識蒸留の損失関数(応用)

ResNet-50(Teacher)の知識をMobileNet v2(Student)に蒸留する場合、温度T=4、α=0.5を使った損失関数を実装してください。

解答:
import torch import torch.nn as nn import torch.nn.functional as F class DistillationLoss(nn.Module): “””知識蒸留用の損失関数クラス””” def __init__(self, temperature=4.0, alpha=0.5): super(DistillationLoss, self).__init__() self.temperature = temperature self.alpha = alpha self.ce_loss = nn.CrossEntropyLoss() def forward(self, student_logits, teacher_logits, labels): # Hard Loss: 正解ラベルとの交差エントロピー hard_loss = self.ce_loss(student_logits, labels) # Soft Loss: Teacher出力とのKLダイバージェンス T = self.temperature soft_student = F.log_softmax(student_logits / T, dim=1) soft_teacher = F.softmax(teacher_logits / T, dim=1) soft_loss = F.kl_div(soft_student, soft_teacher, reduction=’batchmean’) * (T ** 2) # 合計損失 return self.alpha * hard_loss + (1 – self.alpha) * soft_loss # 使用例 criterion = DistillationLoss(temperature=4.0, alpha=0.5) loss = criterion(student_output, teacher_output, labels)

問題4:モデル選択のシナリオ(応用)

以下のシナリオで最適なモデルと圧縮技術の組み合わせを選んでください。

  1. ドローンでリアルタイム物体検出(バッテリー制約、処理速度20fps以上)
  2. スマートフォンでARフィルター(60fps必要、精度よりも速度優先)
  3. 監視カメラでの顔認識(精度最優先、専用GPU使用可)
解答:

1. ドローン(バッテリー制約、20fps以上)

推奨: MobileNet v2 + Int8量子化 理由: ・MobileNet v2は低消費電力 ・量子化でさらに4倍高速化 ・300M FLOPsなら20fps以上可能 ・バッテリー持続時間を最大化

2. ARフィルター(60fps、速度優先)

推奨: MobileNet v1 + TFLite最適化 または MediaPipe 理由: ・60fps = 16.6ms/frameの制約 ・MobileNet v1は最軽量(569M FLOPs) ・TFLite最適化でさらに高速化 ・MediaPipeは顔検出に特化しており超高速

3. 監視カメラ顔認識(精度優先、GPU使用可)

推奨: EfficientNet-B3 または ResNet-50 理由: ・専用GPUがあるので計算量は問題なし ・EfficientNet-B3は81.6%の高精度 ・顔認識では精度が最重要 ・TensorRT最適化でリアルタイム処理も可能

問題5:量子化の実装(応用)

EfficientNet-B0を静的量子化(Static Quantization)する手順を説明し、精度の変化を確認するコードを書いてください。

解答:
import torch import torchvision.models as models # 1. モデルの読み込み model = models.efficientnet_b0(weights=’IMAGENET1K_V1′) model.eval() # 2. 動的量子化の実行 # (静的量子化はキャリブレーションデータが必要なため、 # ここでは動的量子化で代用) quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear, torch.nn.Conv2d}, dtype=torch.qint8 ) # 3. モデルサイズの比較 import os def get_size_mb(model, filename): torch.save(model.state_dict(), filename) size = os.path.getsize(filename) / (1024 * 1024) os.remove(filename) return size original_size = get_size_mb(model, ‘original.pt’) quantized_size = get_size_mb(quantized_model, ‘quantized.pt’) print(f”元のモデル: {original_size:.2f} MB”) print(f”量子化後: {quantized_size:.2f} MB”) print(f”圧縮率: {original_size / quantized_size:.2f}倍”) # 4. 推論速度の比較 import time x = torch.randn(1, 3, 224, 224) # 元のモデル start = time.time() for _ in range(100): with torch.no_grad(): _ = model(x) original_time = (time.time() – start) / 100 # 量子化モデル start = time.time() for _ in range(100): with torch.no_grad(): _ = quantized_model(x) quantized_time = (time.time() – start) / 100 print(f”\n推論時間(平均):”) print(f” 元のモデル: {original_time*1000:.2f} ms”) print(f” 量子化後: {quantized_time*1000:.2f} ms”) print(f” 高速化: {original_time/quantized_time:.2f}倍”)

📝 STEP 7 のまとめ

✅ このステップで学んだこと

1. 軽量モデルの必要性
・モバイル、IoT、エッジAIでの需要
・従来モデルの計算量とメモリの問題

2. MobileNet
・Depthwise Separable Convolution:計算量を約8.7倍削減
・MobileNet v2:Inverted Residual Block、Linear Bottleneck

3. EfficientNet
・Compound Scaling:深さ・幅・解像度を同時に最適化
・B0〜B7:用途に応じて選択可能

4. モデル圧縮技術
・量子化:モデルサイズを4分の1に
・プルーニング:不要な重みを削除
・知識蒸留:大きいモデルの知識を転送

💡 重要ポイント

軽量モデルは、エッジAIの時代に不可欠な技術です。精度を維持しながら計算量を削減するこれらの技術は、実務で非常に重要です。

次のSTEP 8では、「アーキテクチャの選択と転移学習」を学びます。これまで学んだすべてのアーキテクチャをどう選び、どう活用するかを体系的に理解します。

📝

学習メモ

コンピュータビジョン(CV) - Step 7

📋 過去のメモ一覧
#artnasekai #学習メモ
LINE