STEP 6:DenseNetとInception

🌳 STEP 6: DenseNetとInception

DenseNetのDense Connection、InceptionのMulti-scaleフィルタなど、
高度なCNNアーキテクチャを学びます

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

  • DenseNet(Densely Connected Convolutional Networks)
  • Dense Connection(密な接続)の仕組み
  • Growth RateとTransition Layer
  • Inception(GoogLeNet)の設計思想
  • Inception Module(複数サイズのフィルタ並列適用)
  • 1×1 Convolutionの3つの役割
  • ResNeXt(ResNetの進化版)
  • 各アーキテクチャの比較と使い分け

🌲 1. DenseNet – 密な接続による特徴の再利用

DenseNet(Densely Connected Convolutional Networks)は、2017年にCVPRでBest Paper Awardを受賞したアーキテクチャです。ResNetのSkip Connectionをさらに発展させた「Dense Connection」が特徴です。

1-1. ResNetとDenseNetの違い

ResNetでは「加算(Addition)」を使いましたが、DenseNetでは「連結(Concatenation)」を使います。この違いが大きな効果を生みます。

💡 核心的な違い:加算 vs 連結

ResNet:H(x) = F(x) + x(特徴を加算)
DenseNet:H(x) = [x, F(x)](特徴を連結)

【ResNet(加算)】 入力 x (64ch) → [層] → F(x) (64ch) ↓ 出力 = F(x) + x = 64チャンネル ※ 加算なのでチャンネル数は変わらない ※ 元の情報と新しい情報が「混ざる」 【DenseNet(連結)】 入力 x (64ch) → [層] → F(x) (32ch) ↓ 出力 = [x, F(x)] = 64 + 32 = 96チャンネル ※ 連結なのでチャンネル数が増える ※ 元の情報と新しい情報が「両方残る」

1-2. Dense Connectionの仕組み

DenseNetでは、各層がそれ以前のすべての層の出力を入力として受け取ります。これを「Dense Connection(密な接続)」と呼びます。

【Dense Block内の接続パターン】 層0(入力): x₀ (64ch) ↓ 層1: x₀ を入力 → 32ch出力 連結: [x₀, x₁] = 64 + 32 = 96ch ↓ 層2: [x₀, x₁] を入力 → 32ch出力 連結: [x₀, x₁, x₂] = 96 + 32 = 128ch ↓ 層3: [x₀, x₁, x₂] を入力 → 32ch出力 連結: [x₀, x₁, x₂, x₃] = 128 + 32 = 160ch ↓ 層4: [x₀, x₁, x₂, x₃] を入力 → 32ch出力 連結: [x₀, x₁, x₂, x₃, x₄] = 160 + 32 = 192ch → 各層は、前のすべての層の特徴マップにアクセスできる!
💡 Dense Connectionの4つの利点

① 特徴の再利用:全ての層が前の層の特徴を直接使える。無駄な再学習が不要。

② 勾配の直接伝播:勾配が各層に直接伝わる。100層以上でも勾配消失しにくい。

③ パラメータ効率:各層は少ないチャンネル(32ch程度)を出力するだけ。ResNetの1/3のパラメータで同等以上の精度。

④ 正則化効果:特徴の再利用により過学習しにくい。

1-3. Growth Rate(成長率)

Growth Rate(k)は、DenseNet固有のハイパーパラメータです。各層が出力するチャンネル数を決定します。

【Growth Rate = 32 の場合】 Dense Block内で、各層は32チャンネルを出力する。 入力: 64チャンネル 層1: +32ch → 合計 64 + 32 = 96ch 層2: +32ch → 合計 96 + 32 = 128ch 層3: +32ch → 合計 128 + 32 = 160ch 層4: +32ch → 合計 160 + 32 = 192ch 層5: +32ch → 合計 192 + 32 = 224ch 層6: +32ch → 合計 224 + 32 = 256ch → チャンネル数が線形に増加していく
🎯 Growth Rateの選び方

k = 12〜16:軽量モデル。メモリ効率が良い。
k = 32:標準的な設定。DenseNet-121などで使用。
k = 48〜64:高精度モデル。計算量が多い。

小さいGrowth Rateでも高精度を達成できるのが、DenseNetの大きな特徴です。

1-4. Transition Layer(圧縮層)

Dense Block内ではチャンネル数がどんどん増えていきます。このままでは計算量が爆発してしまうため、Transition Layerでチャンネル数を圧縮します。

【Transition Layerの構造】 Dense Block 1の出力: 256チャンネル ↓ ┌─────────────────────────┐ │ BatchNorm │ │ ReLU │ │ Conv 1×1 (256 → 128ch) │ ← チャンネル数を半分に │ AvgPool 2×2 (stride 2) │ ← 空間サイズを半分に └─────────────────────────┘ ↓ Dense Block 2への入力: 128チャンネル ※ Compression Factor θ = 0.5(半分に圧縮)
⚠️ DenseNetの全体構造

DenseNetは「Dense Block」と「Transition Layer」が交互に繰り返される構造です。

【DenseNet-121の全体構造】 入力画像 (224×224×3) ↓ Conv 7×7 (stride 2) → 112×112×64 ↓ MaxPool 3×3 (stride 2) → 56×56×64 ↓ Dense Block 1 (6層, k=32) → 56×56×256 ↓ Transition Layer 1 → 28×28×128 ↓ Dense Block 2 (12層, k=32) → 28×28×512 ↓ Transition Layer 2 → 14×14×256 ↓ Dense Block 3 (24層, k=32) → 14×14×1024 ↓ Transition Layer 3 → 7×7×512 ↓ Dense Block 4 (16層, k=32) → 7×7×1024 ↓ Global Average Pooling → 1×1×1024 ↓ FC → 1000クラス

💻 2. DenseNetの実装

2-1. Dense Layerの実装

まず、Dense Block内の1層(Dense Layer)を実装します。DenseNetでは「BN-ReLU-Conv」の順序を使います(Pre-activation構造)。

ステップ1:クラスの定義

import torch import torch.nn as nn class DenseLayer(nn.Module): “”” DenseNet内の1層(Bottleneck構造付き) 構造:BN → ReLU → Conv1×1 → BN → ReLU → Conv3×3 Args: in_channels: 入力チャンネル数(前の全層の出力の合計) growth_rate: この層が出力するチャンネル数(k) “”” def __init__(self, in_channels, growth_rate): super(DenseLayer, self).__init__()

ステップ2:Bottleneck部分(1×1畳み込み)

# Bottleneck部分 # 1×1畳み込みで次元を削減してから3×3畳み込みを行う # 出力チャンネル数は通常 4 × growth_rate self.bn1 = nn.BatchNorm2d(in_channels) self.relu1 = nn.ReLU(inplace=True) # 1×1畳み込み:チャンネル数を 4*k に削減 # なぜ4倍?→ 経験的にこの値が良いとされている self.conv1 = nn.Conv2d(in_channels, 4 * growth_rate, kernel_size=1, bias=False)

ステップ3:3×3畳み込み部分

# 3×3畳み込み部分 self.bn2 = nn.BatchNorm2d(4 * growth_rate) self.relu2 = nn.ReLU(inplace=True) # 3×3畳み込み:growth_rateチャンネルを出力 # padding=1で空間サイズを維持 self.conv2 = nn.Conv2d(4 * growth_rate, growth_rate, kernel_size=3, padding=1, bias=False)

ステップ4:順伝播(連結処理)

def forward(self, x): “”” 順伝播:入力と出力を連結して返す Args: x: 入力テンソル(前の全層の出力が連結されたもの) Returns: 入力と新しい特徴マップを連結したテンソル “”” # Bottleneck + 3×3畳み込み out = self.conv1(self.relu1(self.bn1(x))) out = self.conv2(self.relu2(self.bn2(out))) # 重要:入力と出力を連結(これがDenseNetの核心) # dim=1 はチャンネル方向での連結 return torch.cat([x, out], dim=1)

完成コード(Dense Layer)

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

import torch import torch.nn as nn class DenseLayer(nn.Module): “””DenseNet内の1層(Bottleneck構造付き)””” def __init__(self, in_channels, growth_rate): super(DenseLayer, self).__init__() # Bottleneck: BN → ReLU → Conv1×1 self.bn1 = nn.BatchNorm2d(in_channels) self.relu1 = nn.ReLU(inplace=True) self.conv1 = nn.Conv2d(in_channels, 4 * growth_rate, kernel_size=1, bias=False) # 3×3畳み込み: BN → ReLU → Conv3×3 self.bn2 = nn.BatchNorm2d(4 * growth_rate) self.relu2 = nn.ReLU(inplace=True) self.conv2 = nn.Conv2d(4 * growth_rate, growth_rate, kernel_size=3, padding=1, bias=False) def forward(self, x): out = self.conv1(self.relu1(self.bn1(x))) out = self.conv2(self.relu2(self.bn2(out))) return torch.cat([x, out], dim=1) # 連結! # テスト layer = DenseLayer(in_channels=64, growth_rate=32) x = torch.randn(1, 64, 56, 56) output = layer(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”)

実行結果:

入力: torch.Size([1, 64, 56, 56]) 出力: torch.Size([1, 96, 56, 56]) ← 64 + 32 = 96チャンネル

2-2. Dense Blockの実装

class DenseBlock(nn.Module): “””複数のDense Layerからなるブロック””” def __init__(self, num_layers, in_channels, growth_rate): “”” Args: num_layers: ブロック内の層数 in_channels: 入力チャンネル数 growth_rate: 各層が出力するチャンネル数 “”” super(DenseBlock, self).__init__() layers = [] for i in range(num_layers): # 各層の入力チャンネル数 = 初期 + これまでの層の出力の合計 layer_in_channels = in_channels + i * growth_rate layers.append(DenseLayer(layer_in_channels, growth_rate)) self.layers = nn.ModuleList(layers) def forward(self, x): for layer in self.layers: x = layer(x) # 各層で連結が行われる return x # テスト:6層のDense Block block = DenseBlock(num_layers=6, in_channels=64, growth_rate=32) x = torch.randn(1, 64, 56, 56) output = block(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”)

実行結果:

入力: torch.Size([1, 64, 56, 56]) 出力: torch.Size([1, 256, 56, 56]) ← 64 + 6×32 = 256チャンネル

2-3. Transition Layerの実装

class TransitionLayer(nn.Module): “””Dense Block間の圧縮層””” def __init__(self, in_channels, out_channels): “”” Args: in_channels: 入力チャンネル数 out_channels: 出力チャンネル数(通常は半分) “”” super(TransitionLayer, self).__init__() self.bn = nn.BatchNorm2d(in_channels) self.relu = nn.ReLU(inplace=True) # 1×1畳み込みでチャンネル数を削減 self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) # Average Poolingで空間サイズを半分に self.pool = nn.AvgPool2d(kernel_size=2, stride=2) def forward(self, x): out = self.conv(self.relu(self.bn(x))) out = self.pool(out) return out # テスト transition = TransitionLayer(in_channels=256, out_channels=128) x = torch.randn(1, 256, 56, 56) output = transition(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”)

実行結果:

入力: torch.Size([1, 256, 56, 56]) 出力: torch.Size([1, 128, 28, 28]) ← チャンネル数もサイズも半分に

2-4. PyTorchの事前学習済みDenseNetを使う

# PyTorchで事前学習済みDenseNetを読み込む import torch import torchvision.models as models from torchvision.models import DenseNet121_Weights # DenseNet-121を読み込み(ImageNetで事前学習済み) model = models.densenet121(weights=DenseNet121_Weights.IMAGENET1K_V1) # モデル構造の確認 print(model) # パラメータ数を確認 total_params = sum(p.numel() for p in model.parameters()) print(f”\n総パラメータ数: {total_params:,}”) # 転移学習用にclassifierを置き換え num_classes = 2 # 例:猫 vs 犬 num_features = model.classifier.in_features # 1024 model.classifier = nn.Linear(num_features, num_classes) print(f”新しい分類器: {model.classifier}”)

実行結果:

DenseNet( (features): Sequential( (conv0): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False) … (denseblock4): _DenseBlock(…) (norm5): BatchNorm2d(1024, …) ) (classifier): Linear(in_features=1024, out_features=1000, bias=True) ) 総パラメータ数: 7,978,856 新しい分類器: Linear(in_features=1024, out_features=2, bias=True)

🏛️ 3. Inception(GoogLeNet)- マルチスケール処理

Inception(GoogLeNet)は、2014年にGoogleが発表したアーキテクチャです。「どのサイズのフィルタが最適かわからないなら、全部使えばいい」という大胆な発想が特徴です。

3-1. Inceptionの設計思想

💡 Inceptionの核心的アイデア

問題:画像内の物体サイズは様々。大きい物体には大きいフィルタ、小さい物体には小さいフィルタが有効。でも、どのサイズが最適かは事前にわからない。

解決:1×1、3×3、5×5のフィルタを並列に適用し、その結果を連結する。ネットワーク自身に最適な組み合わせを学習させる。

【Inception Moduleの基本構造】 入力 ↓ ┌────────┬────────┬────────┬────────┐ ↓ ↓ ↓ ↓ ↓ 1×1 3×3 5×5 MaxPool Conv Conv Conv 3×3 ↓ ↓ ↓ ↓ │ │ │ │ └────────┴────────┴────────┘ ↓ Concatenate(連結) ↓ 出力 ※ 異なるサイズのフィルタで異なるスケールの特徴を捉える ※ 結果を連結してチャンネル方向にまとめる

3-2. 1×1畳み込みの3つの役割

1×1畳み込みはInceptionの重要な要素です。「1×1のフィルタで何ができるの?」と思うかもしれませんが、実は非常に強力です。

💡 1×1畳み込みの3つの役割

① 次元削減(Dimension Reduction)
チャンネル数を減らして計算量を削減。256ch → 64ch など。

② 非線形性の追加
ReLUと組み合わせることで、モデルの表現力を向上。

③ クロスチャンネル学習
チャンネル間の相関関係を学習。空間的な情報は変えずに、チャンネル方向の特徴を抽出。

なぜ次元削減が重要なのか?

【計算量の比較:192ch入力、5×5畳み込み、32ch出力】 ■ 次元削減なしの場合 5×5×192×32 = 153,600 パラメータ ■ 1×1で次元削減してからの場合 1×1×192×16 = 3,072 パラメータ(192→16に削減) 5×5×16×32 = 12,800 パラメータ 合計 = 15,872 パラメータ 比較:153,600 ÷ 15,872 ≒ 9.7 → 約10分の1の計算量で同等の特徴抽出!

3-3. Inception Module(Dimension Reduction付き)

【Inception Module with Dimension Reduction】 入力 (192ch) ↓ ┌──────────┬──────────┬──────────┬──────────┐ ↓ ↓ ↓ ↓ ↓ 1×1 1×1 1×1 MaxPool 64ch 96ch 16ch 3×3 ↓ ↓ ↓ ↓ │ 3×3 5×5 1×1 │ 128ch 32ch 32ch ↓ ↓ ↓ ↓ └──────────┴──────────┴──────────┘ ↓ Concatenate ↓ 出力 (256ch) 64+128+32+32=256 ※ 3×3と5×5の前に1×1で次元削減 ※ MaxPoolの後にも1×1で次元削減

3-4. Inception Moduleの実装

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

import torch import torch.nn as nn class InceptionModule(nn.Module): “””Inception Module with Dimension Reduction””” def __init__(self, in_channels, ch1x1, ch3x3_reduce, ch3x3, ch5x5_reduce, ch5x5, pool_proj): “”” Args: in_channels: 入力チャンネル数 ch1x1: 1×1ブランチの出力チャンネル数 ch3x3_reduce: 3×3ブランチの1×1削減後のチャンネル数 ch3x3: 3×3ブランチの出力チャンネル数 ch5x5_reduce: 5×5ブランチの1×1削減後のチャンネル数 ch5x5: 5×5ブランチの出力チャンネル数 pool_proj: プーリングブランチの1×1後のチャンネル数 “”” super(InceptionModule, self).__init__() # ブランチ1: 1×1畳み込み self.branch1 = nn.Sequential( nn.Conv2d(in_channels, ch1x1, kernel_size=1), nn.BatchNorm2d(ch1x1), nn.ReLU(inplace=True) ) # ブランチ2: 1×1 → 3×3 self.branch2 = nn.Sequential( nn.Conv2d(in_channels, ch3x3_reduce, kernel_size=1), nn.BatchNorm2d(ch3x3_reduce), nn.ReLU(inplace=True), nn.Conv2d(ch3x3_reduce, ch3x3, kernel_size=3, padding=1), nn.BatchNorm2d(ch3x3), nn.ReLU(inplace=True) ) # ブランチ3: 1×1 → 5×5 self.branch3 = nn.Sequential( nn.Conv2d(in_channels, ch5x5_reduce, kernel_size=1), nn.BatchNorm2d(ch5x5_reduce), nn.ReLU(inplace=True), nn.Conv2d(ch5x5_reduce, ch5x5, kernel_size=5, padding=2), nn.BatchNorm2d(ch5x5), nn.ReLU(inplace=True) ) # ブランチ4: MaxPool → 1×1 self.branch4 = nn.Sequential( nn.MaxPool2d(kernel_size=3, stride=1, padding=1), nn.Conv2d(in_channels, pool_proj, kernel_size=1), nn.BatchNorm2d(pool_proj), nn.ReLU(inplace=True) ) def forward(self, x): # 各ブランチを並列に計算 branch1_out = self.branch1(x) branch2_out = self.branch2(x) branch3_out = self.branch3(x) branch4_out = self.branch4(x) # チャンネル方向に連結 return torch.cat([branch1_out, branch2_out, branch3_out, branch4_out], dim=1) # テスト inception = InceptionModule( in_channels=192, ch1x1=64, ch3x3_reduce=96, ch3x3=128, ch5x5_reduce=16, ch5x5=32, pool_proj=32 ) x = torch.randn(1, 192, 28, 28) output = inception(x) print(f”入力: {x.shape}”) print(f”出力: {output.shape}”) print(f”出力チャンネル数: 64+128+32+32 = {64+128+32+32}”)

実行結果:

入力: torch.Size([1, 192, 28, 28]) 出力: torch.Size([1, 256, 28, 28]) 出力チャンネル数: 64+128+32+32 = 256

3-5. PyTorchの事前学習済みInceptionを使う

# PyTorchで事前学習済みInception v3を読み込む import torch import torchvision.models as models from torchvision.models import Inception_V3_Weights # Inception v3を読み込み # 注意:Inception v3は入力サイズが299×299 model = models.inception_v3(weights=Inception_V3_Weights.IMAGENET1K_V1) # 訓練時には補助分類器も使われる(aux_logits=True) # 推論時にはメイン分類器のみ使用 print(f”補助分類器の有無: {model.aux_logits}”) # 転移学習用に分類器を置き換え num_classes = 2 model.fc = nn.Linear(model.fc.in_features, num_classes) # 補助分類器も置き換え(訓練時に使用) if model.aux_logits: model.AuxLogits.fc = nn.Linear(model.AuxLogits.fc.in_features, num_classes) print(f”メイン分類器: {model.fc}”) # 推論時の使用例 model.eval() # 評価モードに(補助分類器は使われない) x = torch.randn(1, 3, 299, 299) # 299×299が必要! with torch.no_grad(): output = model(x) print(f”出力: {output.shape}”)

実行結果:

補助分類器の有無: True メイン分類器: Linear(in_features=2048, out_features=2, bias=True) 出力: torch.Size([1, 2])

🔗 4. ResNeXt – ResNetとInceptionの融合

ResNeXtは、ResNetとInceptionのアイデアを組み合わせたアーキテクチャです。「Cardinality(並列経路の数)」という新しい次元を導入しました。

4-1. ResNeXtのアイデア

💡 ResNeXtの発想

ResNet:1つの経路 + Skip Connection
Inception:複数の異なる経路(1×1, 3×3, 5×5)
ResNeXt:複数の同じ経路(同じ構造を並列に)+ Skip Connection

【ResNeXtの構造(Cardinality = 32)】 入力 x ↓ ├─────────────────────────────────────┐ ↓ │ ┌─────────────────────────────────────┐ │ │ 経路1: 1×1(4ch) → 3×3(4ch) → 1×1 │ │ │ 経路2: 1×1(4ch) → 3×3(4ch) → 1×1 │ │ │ 経路3: 1×1(4ch) → 3×3(4ch) → 1×1 │ │ │ … │ │ │ 経路32: 1×1(4ch) → 3×3(4ch) → 1×1 │ │ └─────────────────────────────────────┘ │ ↓ (32経路の出力を加算) │ + ←───────────────────────────────────┘ ↓ 出力 ※ 32個の同じ構造の経路を並列に実行 ※ Cardinality = 32(並列経路の数)

4-2. Cardinalityの重要性

【同じ計算量での比較】 ■ ResNet-50(より広いチャンネル) 256チャンネル × 1経路 パラメータ数: 大 精度: ベースライン ■ ResNeXt-50 (32×4d) 4チャンネル × 32経路 = 128チャンネル相当 パラメータ数: 同程度 精度: ResNet-50より高い! → 「深さ」「幅」に加えて「Cardinality」が重要な次元 → 同じ計算量なら、チャンネルを広げるより経路を増やす方が効果的

4-3. PyTorchでResNeXtを使う

# PyTorchで事前学習済みResNeXtを読み込む import torch import torchvision.models as models from torchvision.models import ResNeXt50_32X4D_Weights # ResNeXt-50 (32×4d) を読み込み # 32 = Cardinality(並列経路の数) # 4d = 各経路のチャンネル幅 model = models.resnext50_32x4d(weights=ResNeXt50_32X4D_Weights.IMAGENET1K_V2) # パラメータ数を確認 total_params = sum(p.numel() for p in model.parameters()) print(f”ResNeXt-50 (32×4d) パラメータ数: {total_params:,}”) # 転移学習用に分類器を置き換え num_classes = 2 model.fc = nn.Linear(model.fc.in_features, num_classes) # テスト x = torch.randn(1, 3, 224, 224) model.eval() with torch.no_grad(): output = model(x) print(f”出力: {output.shape}”)

実行結果:

ResNeXt-50 (32×4d) パラメータ数: 25,028,904 出力: torch.Size([1, 2])

📊 5. アーキテクチャの比較と選び方

5-1. 主要アーキテクチャの比較表

項目 ResNet DenseNet Inception ResNeXt
核心アイデア Skip Connection(加算) Dense Connection(連結) マルチスケール(並列フィルタ) Cardinality(並列経路)
発表年 2015 2017 2014 2017
代表モデル ResNet-50 DenseNet-121 Inception v3 ResNeXt-50
パラメータ数 25.6M 8.0M 27.2M 25.0M
Top-1精度 76.1% 74.4% 77.3% 77.6%
推論速度 速い やや遅い 普通 速い
メモリ使用量 中程度 多い 中程度 中程度

5-2. 用途別の選び方

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

📱 モバイル・組み込み(速度・サイズ重視):
→ MobileNet、EfficientNet-B0(次のSTEPで学習)

💻 一般的な画像分類(バランス重視):
→ ResNet-50、ResNeXt-50

💾 パラメータ効率(メモリ制約あり):
→ DenseNet-121(ResNet-50の1/3のパラメータ)

🎯 最高精度(リソース潤沢):
→ ResNeXt-101、DenseNet-201、EfficientNet-B7

🏥 医療画像・細かい特徴が重要:
→ DenseNet(特徴の再利用で細かい特徴も保持)

📝 練習問題

問題1:DenseNetとResNetの比較(基礎)

DenseNetとResNetの主な違いを、以下の観点から説明してください。

  1. 接続方法(AdditionとConcatenation)の違い
  2. 特徴の流れ方の違い
  3. パラメータ効率の違い
解答:

1. 接続方法の違い

ResNet:Addition(加算)H(x) = F(x) + x
→ 特徴が「混ざる」。チャンネル数は変わらない。

DenseNet:Concatenation(連結)H(x) = [x, F(x)]
→ 特徴が「両方残る」。チャンネル数が増える。

2. 特徴の流れ方の違い

ResNet:各層は直前の層の出力(または2層前のSkip Connection)のみを受け取る。

DenseNet:各層は前のすべての層の出力を受け取る。Layer 4は、Layer 0, 1, 2, 3の出力すべてにアクセスできる。

3. パラメータ効率の違い

ResNet-50:25.6M パラメータ
DenseNet-121:8.0M パラメータ(ResNet-50の約1/3)

DenseNetは特徴の再利用により、少ないパラメータで同等以上の精度を達成できる。各層は少ないチャンネル(Growth Rate = 32程度)を出力するだけで済む。

問題2:Inception Moduleの実装と計算(中級)

192チャンネルの入力に対して、以下の構成のInception Moduleを実装し、出力チャンネル数と計算量を計算してください。

  • 1×1 branch: 64チャンネル出力
  • 3×3 branch: 1×1で96チャンネルに削減 → 3×3で128チャンネル出力
  • 5×5 branch: 1×1で16チャンネルに削減 → 5×5で32チャンネル出力
  • pool branch: 3×3 max pool → 1×1で32チャンネル出力
解答:

出力チャンネル数:

64 + 128 + 32 + 32 = 256チャンネル

パラメータ数の計算:

■ 1×1ブランチ 1×1×192×64 = 12,288 ■ 3×3ブランチ 1×1×192×96 = 18,432 3×3×96×128 = 110,592 小計 = 129,024 ■ 5×5ブランチ 1×1×192×16 = 3,072 5×5×16×32 = 12,800 小計 = 15,872 ■ poolブランチ 1×1×192×32 = 6,144 合計 = 12,288 + 129,024 + 15,872 + 6,144 = 163,328

実装コード:

class InceptionModule(nn.Module): def __init__(self): super(InceptionModule, self).__init__() # 1×1ブランチ self.branch1 = nn.Sequential( nn.Conv2d(192, 64, kernel_size=1), nn.ReLU(inplace=True) ) # 3×3ブランチ(1×1で次元削減) self.branch2 = nn.Sequential( nn.Conv2d(192, 96, kernel_size=1), nn.ReLU(inplace=True), nn.Conv2d(96, 128, kernel_size=3, padding=1), nn.ReLU(inplace=True) ) # 5×5ブランチ(1×1で次元削減) self.branch3 = nn.Sequential( nn.Conv2d(192, 16, kernel_size=1), nn.ReLU(inplace=True), nn.Conv2d(16, 32, kernel_size=5, padding=2), nn.ReLU(inplace=True) ) # poolブランチ self.branch4 = nn.Sequential( nn.MaxPool2d(kernel_size=3, stride=1, padding=1), nn.Conv2d(192, 32, kernel_size=1), nn.ReLU(inplace=True) ) def forward(self, x): return torch.cat([ self.branch1(x), self.branch2(x), self.branch3(x), self.branch4(x) ], dim=1)

問題3:Growth Rateの計算(応用)

DenseNet-121のDense Block 1(6層)について、以下を計算してください。

  • 初期チャンネル数: 64
  • Growth Rate: 32
  • Dense Block 1の出力チャンネル数は?
  • Transition Layer 1で半分に削減した後のチャンネル数は?
解答:

Dense Block 1の出力チャンネル数:

初期チャンネル数 + 層数 × Growth Rate = 64 + 6 × 32 = 64 + 192 = 256チャンネル 【詳細】 入力: 64ch 層1後: 64 + 32 = 96ch 層2後: 96 + 32 = 128ch 層3後: 128 + 32 = 160ch 層4後: 160 + 32 = 192ch 層5後: 192 + 32 = 224ch 層6後: 224 + 32 = 256ch

Transition Layer 1後のチャンネル数:

256 ÷ 2 = 128チャンネル ※ Compression Factor θ = 0.5 なので半分に削減

問題4:モデルの選択(応用)

以下のシナリオでは、ResNet-50、DenseNet-121、Inception v3、ResNeXt-50のどれを選ぶべきですか?理由とともに答えてください。

  1. スマートフォンでリアルタイム画像認識(推論速度重視)
  2. 医療画像診断(精度最優先、細かい特徴が重要)
  3. 組み込みデバイス(メモリ8GBの制約)
  4. Kaggleコンペティション(精度最大化)
解答:

1. スマートフォン(推論速度重視)

推奨:ResNet-50
理由:推論速度が速く、精度とのバランスが良い。DenseNetは連結操作が多くメモリアクセスが多いため、モバイルではやや遅い。ただし、より軽量なMobileNetが最適(次のSTEPで学習)。

2. 医療画像診断(精度重視、細かい特徴)

推奨:DenseNet-121 または DenseNet-201
理由:Dense Connectionにより、初期層の細かい特徴が最終層まで保持される。医療画像では微細な病変を見逃さないことが重要なため、特徴の再利用が有効。

3. 組み込みデバイス(メモリ制約)

推奨:DenseNet-121
理由:パラメータ数が8.0Mと最も少ない(ResNet-50は25.6M)。ただし、DenseNetは中間特徴マップのメモリ使用量が多い点に注意。メモリが厳しい場合はMobileNetやEfficientNet-B0を検討。

4. Kaggleコンペティション(精度最大化)

推奨:ResNeXt-50 または アンサンブル
理由:ResNeXt-50は同程度のパラメータ数でResNet-50より高精度。さらに、複数のモデル(ResNeXt + DenseNet + EfficientNet)のアンサンブルで精度を最大化するのがKaggleの定石。

📝 STEP 6 のまとめ

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

1. DenseNet
・Dense Connection:各層が前の全層の出力を受け取る
・Growth Rate:各層が出力するチャンネル数
・Transition Layer:チャンネル数と空間サイズを圧縮
・パラメータ効率が非常に良い(ResNet-50の1/3)

2. Inception
・マルチスケール:1×1, 3×3, 5×5のフィルタを並列適用
・1×1畳み込み:次元削減、非線形性追加、クロスチャンネル学習
・入力サイズ:Inception v3は299×299が必要

3. ResNeXt
・Cardinality:並列経路の数という新しい次元
・同じ計算量なら、チャンネルを広げるより経路を増やす方が効果的

4. 使い分け
・速度重視:ResNet-50
・パラメータ効率:DenseNet-121
・精度重視:ResNeXt-50

💡 重要ポイント

ResNet以降も、様々な設計思想のCNNアーキテクチャが登場しています。それぞれに長所と短所があり、タスクや制約に応じて使い分けることが重要です。

次のSTEP 7では、EfficientNetとMobileNetを学びます。これらはモバイル・エッジデバイス向けの軽量モデルで、実務で非常によく使われます。

📝

学習メモ

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

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