「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% のパラメータ数になります。
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
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では
Conv2DをSeparableConv2Dに置き換えるだけで実装できる - MobileNetの軽さはこの技術を全層に適用し、さらに幅乗数でチャンネルごとスケールダウンした設計から来ている
関連記事もあわせてどうぞ:
- Residual接続の比較 → Residual接続(スキップ接続)あり vs なし を比較【Keras×CIFAR-10】
- Conv2Dフィルター数の比較 → Conv2Dのフィルター数を変えると精度はどう変わる?【Keras実験】
- GAPとFlattenの比較 → Global Average Pooling vs Flatten|CNNの最終層、どっちが精度・速度で有利か?【Keras実験】





0 件のコメント:
コメントを投稿