Depthwise Separable Convolution vs 通常Conv2Dを比較|パラメータ削減で精度はどう変わる?【Keras×CIFAR-10】

投稿日:2026年5月16日土曜日 最終更新日:

CIFAR-10 CNN Google Colab Keras MobileNet SeparableConv2D 画像分類 軽量化

X f B! P L
Depthwise Separable Convolution vs 通常Conv2Dを比較|パラメータ削減で精度はどう変わる?【Keras×CIFAR-10】 アイキャッチ画像

「MobileNetって軽いらしいけど、何がそんなに違うの?」

MobileNetの軽さの核心はDepthwise Separable Convolutionという畳み込み方式にあります。通常のConv2Dを2段階に分解することで、パラメータ数と計算量を大幅に削減しながら、なるべく精度を維持するというアイデアです。

今回はKerasの SeparableConv2D を使ってこの軽量化技術を実装し、CIFAR-10で通常Conv2Dとの精度・パラメータ数・学習時間を直接比較します。

関連記事:Residual接続(スキップ接続)あり vs なし を比較【Keras×CIFAR-10】

📘 この記事でわかること
  • Depthwise Separable Convolutionの仕組みと通常Conv2Dとの違い
  • パラメータ数・計算量がどれくらい削減されるか(理論値と実測)
  • Kerasでの実装方法(SeparableConv2D の使い方)
  • 精度・パラメータ数・学習時間のトレードオフの実態

Depthwise Separable Convolutionとは

通常のConv2Dは、入力のすべてのチャンネルにまたがって同時にフィルタリングを行います。これに対してDepthwise Separable Convolutionは、畳み込みを2段階に分解します。

① Depthwise Convolution(深さ方向の畳み込み)

チャンネルごとに独立して空間方向の畳み込みを行います。入力が C チャンネルなら C 個のフィルタを使い、それぞれのチャンネルに 1 つずつ適用します。チャンネルをまたぐ情報の混合はここでは行いません。

② Pointwise Convolution(点方向の畳み込み)

1×1 Conv でチャンネル間の情報を混合します。Depthwise で得た C チャンネルの特徴を、目的のフィルタ数 M チャンネルに変換します。

パラメータ数の比較(理論値)

3×3 Conv、入力チャンネル C、出力チャンネル M とすると:

\[ \text{通常Conv2D} : 3 \times 3 \times C \times M \]

\[ \text{Depthwise Separable} : 3 \times 3 \times C + 1 \times 1 \times C \times M \]

比率は次の通りです。

\[ \frac{\text{DSConv}}{\text{通常Conv}} = \frac{1}{M} + \frac{1}{9} \approx \frac{1}{9} \quad (M \gg 1 \text{ のとき}) \]

出力チャンネル数 M が大きいほど削減率が高く、M=64 では理論上 約1/9 ≈ 11% のパラメータ数になります。

⚠️ ハマりポイント:SeparableConv2D の引数名
Kerasの SeparableConv2D は通常の Conv2D とほぼ同じインターフェースで使えます。
ただし use_bias=True はデフォルトで有効なので、BNと組み合わせる場合は use_bias=False にすることを検討してください。また depth_multiplier(デフォルト=1)を上げるとDepthwise側のフィルタ数が増え、パラメータ数が増加します。

実験コード

使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。Conv層の種類以外の条件は全て同一にします。

環境準備(最初に一度だけ実行)

# ── 環境準備(最初に一度だけ実行)──────────────────────
!apt-get -y install fonts-ipafont-gothic
!rm -rf /root/.cache/matplotlib
!pip install -q japanize_matplotlib
print("環境準備完了")
実行結果をクリックして内容を開く
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
  fonts-ipafont-mincho
The following NEW packages will be installed:
  fonts-ipafont-gothic fonts-ipafont-mincho
0 upgraded, 2 newly installed, 0 to remove and 51 not upgraded.
Need to get 8,237 kB of archives.
After this operation, 28.7 MB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-gothic all 00303-21ubuntu1 [3,513 kB]
Get:2 http://archive.ubuntu.com/ubuntu jammy/universe amd64 fonts-ipafont-mincho all 00303-21ubuntu1 [4,724 kB]
Fetched 8,237 kB in 0s (35.5 MB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122412 files and directories currently installed.)
Preparing to unpack .../fonts-ipafont-gothic_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-gothic (00303-21ubuntu1) ...
Selecting previously unselected package fonts-ipafont-mincho.
Preparing to unpack .../fonts-ipafont-mincho_00303-21ubuntu1_all.deb ...
Unpacking fonts-ipafont-mincho (00303-21ubuntu1) ...
Setting up fonts-ipafont-mincho (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-mincho/ipam.ttf to provide /usr/share/fonts/truetype/fonts-japanese-mincho.ttf (fonts-japanese-mincho.ttf) in auto mode
Setting up fonts-ipafont-gothic (00303-21ubuntu1) ...
update-alternatives: using /usr/share/fonts/opentype/ipafont-gothic/ipag.ttf to provide /usr/share/fonts/truetype/fonts-japanese-gothic.ttf (fonts-japanese-gothic.ttf) in auto mode
Processing triggers for fontconfig (2.13.1-4.2ubuntu5) ...
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 MB 33.7 MB/s eta 0:00:00
  Preparing metadata (setup.py) ... done
  Building wheel for japanize_matplotlib (setup.py) ... done
環境準備完了

import・データ準備

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import japanize_matplotlib
import time

# CIFAR-10 読み込み・正規化
(x_train, y_train), (x_test, y_test) = keras.datasets.cifar10.load_data()
x_train = x_train.astype('float32') / 255.0
x_test  = x_test.astype('float32')  / 255.0
print("データ準備完了")
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 6s 0us/step
データ準備完了

モデル構築関数

use_depthwise フラグ一つで通常Conv2D / SeparableConv2D を切り替えます。それ以外の構造(フィルタ数・BN・Pooling・GAP・Dense)は完全に同一です。

def conv_block(x, filters, use_depthwise):
    """
    Conv ブロック:通常Conv2D または SeparableConv2D を切り替え
    """
    if use_depthwise:
        x = layers.SeparableConv2D(filters, (3, 3), padding='same', use_bias=False)(x)
    else:
        x = layers.Conv2D(filters, (3, 3), padding='same', use_bias=False)(x)
    x = layers.BatchNormalization()(x)
    x = layers.Activation('relu')(x)
    return x

def build_model(use_depthwise, name):
    """
    use_depthwise=True  → SeparableConv2D(Depthwise Separable)
    use_depthwise=False → 通常 Conv2D
    """
    inputs = keras.Input(shape=(32, 32, 3))

    # Stage 1: 64ch × 2ブロック
    x = conv_block(inputs, 64,  use_depthwise)
    x = conv_block(x,      64,  use_depthwise)
    x = layers.MaxPooling2D((2, 2))(x)        # 32→16

    # Stage 2: 128ch × 2ブロック
    x = conv_block(x, 128, use_depthwise)
    x = conv_block(x, 128, use_depthwise)
    x = layers.MaxPooling2D((2, 2))(x)        # 16→8

    # Stage 3: 256ch × 2ブロック
    x = conv_block(x, 256, use_depthwise)
    x = conv_block(x, 256, use_depthwise)

    # 出力
    x = layers.GlobalAveragePooling2D()(x)
    x = layers.Dense(128, activation='relu')(x)
    x = layers.Dropout(0.3)(x)
    outputs = layers.Dense(10, activation='softmax')(x)

    return keras.Model(inputs, outputs, name=name)

def compile_and_fit(model):
    model.compile(optimizer='adam',
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    start = time.time()
    history = model.fit(x_train, y_train,
                        epochs=30, batch_size=64,
                        validation_split=0.2,
                        verbose=1)
    return history, time.time() - start

2パターンの学習実行

configs = [
    (False, 'A_standard_conv'),   # 通常 Conv2D
    (True,  'B_depthwise_sep'),   # Depthwise Separable Conv
]

histories, times, scores, params = {}, {}, {}, {}

for use_depthwise, name in configs:
    print(f"\n=== {name} ===")
    model = build_model(use_depthwise, name)
    model.summary()
    h, t = compile_and_fit(model)
    s = model.evaluate(x_test, y_test, verbose=0)
    label = name.split('_', 1)[1]   # 'standard_conv' / 'depthwise_sep'
    histories[label] = h
    times[label]     = t
    scores[label]    = s
    params[label]    = model.count_params()
    print(f"学習時間:{t:.1f}秒 パラメータ数:{model.count_params():,} test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_standard_conv ===
Model: "A_standard_conv"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer)        │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d (Conv2D)                 │ (None, 32, 32, 64)     │         1,728 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization             │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation (Activation)         │ (None, 32, 32, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 32, 32, 64)     │        36,864 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_1           │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_1 (Activation)       │ (None, 32, 32, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 16, 16, 128)    │        73,728 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_2           │ (None, 16, 16, 128)    │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_2 (Activation)       │ (None, 16, 16, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 16, 16, 128)    │       147,456 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_3           │ (None, 16, 16, 128)    │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_3 (Activation)       │ (None, 16, 16, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 8, 8, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_4 (Conv2D)               │ (None, 8, 8, 256)      │       294,912 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_4           │ (None, 8, 8, 256)      │         1,024 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_4 (Activation)       │ (None, 8, 8, 256)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 8, 8, 256)      │       589,824 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_5           │ (None, 8, 8, 256)      │         1,024 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_5 (Activation)       │ (None, 8, 8, 256)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d        │ (None, 256)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 128)            │        32,896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense)                 │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 1,182,282 (4.51 MB)
 Trainable params: 1,180,490 (4.50 MB)
 Non-trainable params: 1,792 (7.00 KB)
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 23s 20ms/step - accuracy: 0.5087 - loss: 1.3436 - val_accuracy: 0.3202 - val_loss: 2.3254
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 16ms/step - accuracy: 0.6698 - loss: 0.9352 - val_accuracy: 0.3902 - val_loss: 2.1045
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 18ms/step - accuracy: 0.7377 - loss: 0.7558 - val_accuracy: 0.7228 - val_loss: 0.7846
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.7846 - loss: 0.6334 - val_accuracy: 0.6513 - val_loss: 0.9986
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 20s 17ms/step - accuracy: 0.8164 - loss: 0.5391 - val_accuracy: 0.6811 - val_loss: 0.9700
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.8421 - loss: 0.4651 - val_accuracy: 0.7419 - val_loss: 0.7479
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 17ms/step - accuracy: 0.8605 - loss: 0.4054 - val_accuracy: 0.7792 - val_loss: 0.6899
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.8815 - loss: 0.3491 - val_accuracy: 0.7501 - val_loss: 0.7941
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.8987 - loss: 0.2985 - val_accuracy: 0.6752 - val_loss: 1.3020
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9105 - loss: 0.2602 - val_accuracy: 0.6414 - val_loss: 1.4857
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9212 - loss: 0.2300 - val_accuracy: 0.8057 - val_loss: 0.6964
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 18ms/step - accuracy: 0.9354 - loss: 0.1897 - val_accuracy: 0.7796 - val_loss: 0.8605
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9436 - loss: 0.1626 - val_accuracy: 0.7265 - val_loss: 1.1443
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9500 - loss: 0.1472 - val_accuracy: 0.7709 - val_loss: 0.9435
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9562 - loss: 0.1264 - val_accuracy: 0.8125 - val_loss: 0.7246
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9618 - loss: 0.1103 - val_accuracy: 0.7990 - val_loss: 0.8006
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 18ms/step - accuracy: 0.9663 - loss: 0.0974 - val_accuracy: 0.7760 - val_loss: 0.9337
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9675 - loss: 0.0955 - val_accuracy: 0.7751 - val_loss: 1.1134
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9718 - loss: 0.0821 - val_accuracy: 0.7274 - val_loss: 1.4469
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9725 - loss: 0.0820 - val_accuracy: 0.8005 - val_loss: 0.7669
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 18ms/step - accuracy: 0.9751 - loss: 0.0736 - val_accuracy: 0.8234 - val_loss: 0.8344
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 18ms/step - accuracy: 0.9773 - loss: 0.0685 - val_accuracy: 0.7907 - val_loss: 0.9476
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9799 - loss: 0.0613 - val_accuracy: 0.7874 - val_loss: 1.0966
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9804 - loss: 0.0594 - val_accuracy: 0.7903 - val_loss: 1.0647
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9798 - loss: 0.0617 - val_accuracy: 0.7813 - val_loss: 1.1628
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9825 - loss: 0.0512 - val_accuracy: 0.7267 - val_loss: 1.7640
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9832 - loss: 0.0526 - val_accuracy: 0.7005 - val_loss: 1.8340
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9827 - loss: 0.0518 - val_accuracy: 0.8043 - val_loss: 1.0613
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9844 - loss: 0.0470 - val_accuracy: 0.8241 - val_loss: 0.8047
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 17ms/step - accuracy: 0.9845 - loss: 0.0462 - val_accuracy: 0.7807 - val_loss: 1.1458
学習時間:344.6秒 パラメータ数:1,182,282 test_accuracy:0.7791

=== B_depthwise_sep ===
Model: "B_depthwise_sep"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer_1 (InputLayer)      │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d                │ (None, 32, 32, 64)     │           219 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_6           │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_6 (Activation)       │ (None, 32, 32, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d_1              │ (None, 32, 32, 64)     │         4,672 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_7           │ (None, 32, 32, 64)     │           256 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_7 (Activation)       │ (None, 32, 32, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d_2              │ (None, 16, 16, 128)    │         8,768 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_8           │ (None, 16, 16, 128)    │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_8 (Activation)       │ (None, 16, 16, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d_3              │ (None, 16, 16, 128)    │        17,536 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_9           │ (None, 16, 16, 128)    │           512 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_9 (Activation)       │ (None, 16, 16, 128)    │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 8, 8, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d_4              │ (None, 8, 8, 256)      │        33,920 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_10          │ (None, 8, 8, 256)      │         1,024 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_10 (Activation)      │ (None, 8, 8, 256)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ separable_conv2d_5              │ (None, 8, 8, 256)      │        67,840 │
│ (SeparableConv2D)               │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_11          │ (None, 8, 8, 256)      │         1,024 │
│ (BatchNormalization)            │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ activation_11 (Activation)      │ (None, 8, 8, 256)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d_1      │ (None, 256)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 128)            │        32,896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_1 (Dropout)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (Dense)                 │ (None, 10)             │         1,290 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
 Total params: 170,725 (666.89 KB)
 Trainable params: 168,933 (659.89 KB)
 Non-trainable params: 1,792 (7.00 KB)
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 31s 25ms/step - accuracy: 0.5084 - loss: 1.3478 - val_accuracy: 0.4815 - val_loss: 1.4429
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 13ms/step - accuracy: 0.6500 - loss: 0.9869 - val_accuracy: 0.5921 - val_loss: 1.1694
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.7052 - loss: 0.8419 - val_accuracy: 0.6130 - val_loss: 1.1250
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.7408 - loss: 0.7461 - val_accuracy: 0.6777 - val_loss: 0.9664
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 13ms/step - accuracy: 0.7678 - loss: 0.6702 - val_accuracy: 0.7085 - val_loss: 0.8469
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.7902 - loss: 0.6092 - val_accuracy: 0.7198 - val_loss: 0.8379
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8057 - loss: 0.5598 - val_accuracy: 0.6562 - val_loss: 1.1338
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8213 - loss: 0.5165 - val_accuracy: 0.6817 - val_loss: 1.0630
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.8330 - loss: 0.4768 - val_accuracy: 0.6838 - val_loss: 1.0097
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8465 - loss: 0.4411 - val_accuracy: 0.7408 - val_loss: 0.8576
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8569 - loss: 0.4112 - val_accuracy: 0.6968 - val_loss: 0.9710
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8656 - loss: 0.3841 - val_accuracy: 0.7308 - val_loss: 0.9434
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8750 - loss: 0.3595 - val_accuracy: 0.7267 - val_loss: 0.9852
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.8814 - loss: 0.3388 - val_accuracy: 0.7743 - val_loss: 0.7660
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8914 - loss: 0.3084 - val_accuracy: 0.6755 - val_loss: 1.1993
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.8965 - loss: 0.2919 - val_accuracy: 0.7692 - val_loss: 0.7802
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9067 - loss: 0.2663 - val_accuracy: 0.7013 - val_loss: 1.2963
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9097 - loss: 0.2547 - val_accuracy: 0.7844 - val_loss: 0.7476
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9168 - loss: 0.2390 - val_accuracy: 0.7325 - val_loss: 1.1109
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9202 - loss: 0.2278 - val_accuracy: 0.7563 - val_loss: 0.8798
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9262 - loss: 0.2091 - val_accuracy: 0.7160 - val_loss: 1.2051
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9329 - loss: 0.1927 - val_accuracy: 0.7246 - val_loss: 1.2240
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9325 - loss: 0.1917 - val_accuracy: 0.6068 - val_loss: 2.3144
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9354 - loss: 0.1839 - val_accuracy: 0.6866 - val_loss: 1.6091
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9396 - loss: 0.1728 - val_accuracy: 0.7411 - val_loss: 1.1404
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9445 - loss: 0.1598 - val_accuracy: 0.7689 - val_loss: 0.8986
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 12ms/step - accuracy: 0.9459 - loss: 0.1554 - val_accuracy: 0.7564 - val_loss: 1.0765
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9478 - loss: 0.1473 - val_accuracy: 0.7690 - val_loss: 0.9618
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9485 - loss: 0.1480 - val_accuracy: 0.7551 - val_loss: 1.1746
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9516 - loss: 0.1389 - val_accuracy: 0.7028 - val_loss: 1.5010
学習時間:254.7秒 パラメータ数:170,725 test_accuracy:0.7028

グラフ+パラメータ削減率サマリー

# ── val_accuracy / val_loss 比較グラフ ────────────────────
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for label, h in histories.items():
    axes[0].plot(h.history['val_accuracy'], label=label)
    axes[1].plot(h.history['val_loss'],     label=label)
axes[0].set_title('val_accuracy の比較(全30エポック)')
axes[1].set_title('val_loss の比較(全30エポック)')
for ax in axes:
    ax.set_xlabel('Epoch'); ax.legend(); ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('depthwise_comparison.png', dpi=150)
plt.show()

# ── train_loss vs val_loss(過学習の比較)────────────────
fig2, axes2 = plt.subplots(2, 1, figsize=(7, 10))
for i, (label, h) in enumerate(histories.items()):
    axes2[i].plot(h.history['loss'],     label='train_loss')
    axes2[i].plot(h.history['val_loss'], label='val_loss')
    axes2[i].set_title(label)
    axes2[i].set_xlabel('Epoch'); axes2[i].legend(); axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('depthwise_overfit.png', dpi=150)
plt.show()

print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>16} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8} | {'Params':>12}")
print("-" * 66)
p_std = params['standard_conv']
for label in ['standard_conv', 'depthwise_sep']:
    val_acc  = histories[label].history['val_accuracy'][-1]
    test_acc = scores[label][1]
    t        = times[label]
    p        = params[label]
    ratio    = p / p_std * 100
    print(f"{label:>16} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f} | {p:>12,}  ({ratio:.1f}%)")
print("-" * 66)

最終結果サマリー

===== 最終結果サマリー =====
         Pattern |  Val Acc |  Test Acc |  Time(s) |       Params
------------------------------------------------------------------
   standard_conv |   0.7807 |    0.7791 |    344.6 |    1,182,282  (100.0%)
   depthwise_sep |   0.7028 |    0.7028 |    254.7 |      170,725  (14.4%)
------------------------------------------------------------------

実験結果

精度グラフ

精度グラフ

損失グラフ

損失グラフ

standard_conv

standard_conv

depthwise_sep

depthwise_sep
パターン 最終 val_accuracy 最終 test_accuracy パラメータ数 学習時間
A:通常 Conv2D 78.07% 77.91% 1,182,282(100%) 344.6秒
B:Depthwise Separable Conv 70.28% 70.28% 170,725(14.4%) 254.7秒

考察

① パラメータ数は理論値通り約86%削減された

今回の実測でのパラメータ削減率は85.6%削減(14.4%に圧縮)で、理論値の約88%削減とほぼ一致しました。各Conv層の削減率は下表の通りです。

ブロック 通常Conv2D DSConv(理論値) 削減率
3→64ch(入力Conv) 3×3×3×64 = 1,728 3×3×3 + 1×1×3×64 = 219 約87%削減
64→64ch 3×3×64×64 = 36,864 3×3×64 + 1×1×64×64 = 4,672 約87%削減
64→128ch 3×3×64×128 = 73,728 3×3×64 + 1×1×64×128 = 8,768 約88%削減
128→256ch 3×3×128×256 = 294,912 3×3×128 + 1×1×128×256 = 33,920 約88%削減
256→256ch 3×3×256×256 = 589,824 3×3×256 + 1×1×256×256 = 67,840 約88%削減

Dense層・BNのパラメータは両モデルで共通のため、Conv層以外の差はありません。今回のモデルではConv層がパラメータの大半を占めており、その削減効果がそのままモデル全体の削減率に直結しています。

② 精度の差:7.6pt——「そこそこの精度低下」をどう見るか

test_accuracyは通常Conv2Dが77.91%、Depthwise Separable Convが70.28%で、差は約7.6ptです。パラメータ数が約7分の1(14.4%)になった代償として、精度が約1割落ちたというのが今回の実態です。

この差を「大きい」と見るか「許容範囲」と見るかは用途次第です。精度が最優先のタスクでは通常Conv2D一択ですが、「スマートフォンや組み込みデバイスで動かしたい」「推論速度が制約になっている」といった場面では、7.6ptの低下を受け入れてでも軽量モデルを選ぶ意義があります。

また、今回はData Augmentationなし・30エポックという比較的シンプルな設定です。Data Augmentationや学習率スケジューリングを加えてより長く学習すると、DSConvモデルの精度はさらに上がる可能性があります。

③ 学習時間:パラメータ7分の1でも速度は1.35倍にとどまった

学習時間は通常Conv2Dが344.6秒、DSConvが254.7秒で、約26%短縮(1.35倍速)でした。パラメータ数が約7分の1になったにもかかわらず速度差が小さい理由は、SeparableConv2D がDepthwise→Pointwiseの2段階の演算に分かれることで、GPU上のカーネル起動オーバーヘッドが増えるからです。

特にCIFAR-10(32×32)のような小さい特徴マップでは、メモリ帯域よりもカーネル起動コストが学習時間を左右しやすくなります。ImageNet(224×224)のような大きい特徴マップでは、より大きな高速化効果が期待できます。「パラメータが少ない=学習が速い」とは限らないことを覚えておくと実務でも役立ちます。

④ MobileNetへの繋がり

今回はDepthwise Separable Convolutionを全Conv層に一律適用し、フィルタ数は通常モデルと同じまま比較しました。実際のMobileNetはこれに加えて幅乗数(width multiplier)でチャンネル数全体をスケールダウンしており、さらなる軽量化を実現しています。また、MobileNetV2ではBottleneck構造の中にDepthwise Separable Convolutionを組み込み、Residual接続(⑦の記事を参照)との組み合わせで精度・軽量性の両立を図っています。

まとめ
  • Depthwise Separable Convolutionは、空間方向の畳み込み(Depthwise)とチャンネル混合(Pointwise = 1×1 Conv)に分解することでパラメータ数を大幅に削減する
  • 実測のパラメータ削減率は85.6%削減(1,182,282 → 170,725)で理論値とほぼ一致
  • 精度の低下は約7.6pt(77.91% → 70.28%)——用途によっては許容範囲の差
  • 学習時間の短縮は約26%(344.6秒 → 254.7秒)——パラメータ7分の1でも速度差は小さい。2段階演算のオーバーヘッドが原因
  • Kerasでは Conv2DSeparableConv2D に置き換えるだけで実装できる
  • MobileNetの軽さはこの技術を全層に適用し、さらに幅乗数でチャンネルごとスケールダウンした設計から来ている

関連記事もあわせてどうぞ: