KerasのCNNで畳み込み後の特徴をまとめるとき、GlobalAveragePooling2D(GAP)とGlobalMaxPooling2D(GMP)のどちらを使うか、迷ったことはありませんか?
「GAPの方が過学習に強い」とよく言われますが、GMPは最も強い特徴だけを拾えるため、場合によっては精度で有利になるケースもあります。今回はGoogle ColabとCIFAR-10を使い、GAP・GMP・両方を組み合わせた3パターンで実験比較しました。
なお、GAPとFlattenの比較は → Global Average Pooling vs Flatten|CNNの最終層、どっちが精度・速度で有利か?【Keras実験】 をご覧ください。本記事はGAP同士の兄弟であるGAPとGMPの違いに焦点を当てます。
- GlobalAveragePooling2D(GAP)とGlobalMaxPooling2D(GMP)の動作原理の違い
- CIFAR-10での精度・val_loss・過学習の度合いをパターン別に比較した実験結果
- GAPとGMPを連結(Concatenate)した場合の効果
- 実務でどちらを選ぶべきかの判断基準
GAPとGMPの動作原理
どちらも「特徴マップ(H×W×C)→ 1次元ベクトル(C次元)」に圧縮するレイヤーですが、集約方法が異なります。
| レイヤー | 集約方法 | 何を拾うか |
|---|---|---|
| GlobalAveragePooling2D | 空間方向(H×W)の平均値 | 特徴マップ全体の「平均的な反応」 |
| GlobalMaxPooling2D | 空間方向(H×W)の最大値 | 特徴マップ中で「最も強く反応した1点」 |
GAP の出力(チャネル \(c\) について):\(\displaystyle \text{GAP}_c = \frac{1}{H \times W}\sum_{i=1}^{H}\sum_{j=1}^{W} f_{i,j,c}\)
GMP の出力(チャネル \(c\) について):\(\displaystyle \text{GMP}_c = \max_{i \in [1,H],\, j \in [1,W]} f_{i,j,c}\)
GAPは「全体的にこの特徴が出ているか」を捉え、GMPは「どこかに強い反応があるか」を捉えます。背景が多い画像(CIFAR-10の鳥・鹿など)ではGAPが有利で、エッジや鋭い特徴が重要な場合はGMPが有利になる傾向があります。
実験の設計
今回は以下の3パターンを比較します。Dense層のユニット数・Dropout率・エポック数など、Pooling層の種類以外の条件はすべて同一にして影響だけを取り出します。
| パターン | Poolingレイヤー | 出力次元 | 狙い |
|---|---|---|---|
| A:GAP | GlobalAveragePooling2D | 128次元 | ベースライン(平均集約) |
| B:GMP | GlobalMaxPooling2D | 128次元 | 最大値集約の効果を検証 |
| C:GAP + GMP(連結) | 両方をConcatenate | 256次元 | 平均+最大値の情報を両方活用 |
実験コード
使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。
環境準備(最初に一度だけ実行)
# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 (22.6 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 116.1 MB/s eta 0:00:00
Preparing metadata (setup.py) ... done
Building wheel for japanize_matplotlib (setup.py) ... done
環境準備完了
import・データ準備・モデル構築関数
import os, random
import numpy as np
import tensorflow as tf
from tensorflow import keras
import matplotlib.pyplot as plt
import japanize_matplotlib
import time
# ── 乱数シード固定(再現性確保)────────────────────
SEED = 42
os.environ['PYTHONHASHSEED'] = str(SEED)
random.seed(SEED)
np.random.seed(SEED)
tf.random.set_seed(SEED)
# ── データ準備 ─────────────────────────────────────
(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
# ── Conv 部分(共通)────────────────────────────────
def build_conv_body(inputs):
x = keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same')(inputs)
x = keras.layers.MaxPooling2D((2, 2))(x)
x = keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same')(x)
x = keras.layers.MaxPooling2D((2, 2))(x)
return x
# ── 3パターンのモデル構築 ─────────────────────────
def build_gap_model():
"""パターン A:GlobalAveragePooling2D"""
tf.random.set_seed(SEED)
inputs = keras.Input(shape=(32, 32, 3))
x = build_conv_body(inputs)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dense(128, activation='relu')(x)
x = keras.layers.Dropout(0.2)(x)
outputs = keras.layers.Dense(10, activation='softmax')(x)
return keras.Model(inputs, outputs, name='A_GAP')
def build_gmp_model():
"""パターン B:GlobalMaxPooling2D"""
tf.random.set_seed(SEED)
inputs = keras.Input(shape=(32, 32, 3))
x = build_conv_body(inputs)
x = keras.layers.GlobalMaxPooling2D()(x)
x = keras.layers.Dense(128, activation='relu')(x)
x = keras.layers.Dropout(0.2)(x)
outputs = keras.layers.Dense(10, activation='softmax')(x)
return keras.Model(inputs, outputs, name='B_GMP')
def build_gap_gmp_model():
"""パターン C:GAP + GMP を Concatenate"""
tf.random.set_seed(SEED)
inputs = keras.Input(shape=(32, 32, 3))
x = build_conv_body(inputs)
gap = keras.layers.GlobalAveragePooling2D()(x) # 128次元
gmp = keras.layers.GlobalMaxPooling2D()(x) # 128次元
x = keras.layers.Concatenate()([gap, gmp]) # 256次元
x = keras.layers.Dense(128, activation='relu')(x)
x = keras.layers.Dropout(0.2)(x)
outputs = keras.layers.Dense(10, activation='softmax')(x)
return keras.Model(inputs, outputs, name='C_GAP_GMP')
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
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 14s 0us/step
3パターンの学習実行
model_builders = [
('GAP', build_gap_model),
('GMP', build_gmp_model),
('GAP_GMP', build_gap_gmp_model),
]
histories, times, scores, params = {}, {}, {}, {}
for label, builder in model_builders:
print(f"\n=== {label} ===")
model = builder()
print(model.summary())
h, t = compile_and_fit(model)
s = model.evaluate(x_test, y_test, verbose=0)
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}")
実行結果をクリックして内容を開く
=== GAP === Model: "A_GAP" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input_layer_3 (InputLayer) │ (None, 32, 32, 3) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_6 (Conv2D) │ (None, 32, 32, 64) │ 1,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_6 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_7 (Conv2D) │ (None, 16, 16, 128) │ 73,856 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_7 (MaxPooling2D) │ (None, 8, 8, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ global_average_pooling2d_2 │ (None, 128) │ 0 │ │ (GlobalAveragePooling2D) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_6 (Dense) │ (None, 128) │ 16,512 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_3 (Dropout) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_7 (Dense) │ (None, 10) │ 1,290 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 93,450 (365.04 KB) Trainable params: 93,450 (365.04 KB) Non-trainable params: 0 (0.00 B) None Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 8ms/step - accuracy: 0.2573 - loss: 1.9331 - val_accuracy: 0.3566 - val_loss: 1.7182 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3618 - loss: 1.6897 - val_accuracy: 0.4201 - val_loss: 1.5779 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4260 - loss: 1.5613 - val_accuracy: 0.4708 - val_loss: 1.4676 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4666 - loss: 1.4597 - val_accuracy: 0.4965 - val_loss: 1.3840 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4927 - loss: 1.3878 - val_accuracy: 0.5098 - val_loss: 1.3462 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5099 - loss: 1.3438 - val_accuracy: 0.5272 - val_loss: 1.2977 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5238 - loss: 1.3040 - val_accuracy: 0.5345 - val_loss: 1.2727 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5378 - loss: 1.2717 - val_accuracy: 0.5492 - val_loss: 1.2376 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5502 - loss: 1.2403 - val_accuracy: 0.5539 - val_loss: 1.2220 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5595 - loss: 1.2143 - val_accuracy: 0.5690 - val_loss: 1.1881 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5725 - loss: 1.1821 - val_accuracy: 0.5826 - val_loss: 1.1585 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5785 - loss: 1.1613 - val_accuracy: 0.5830 - val_loss: 1.1588 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5866 - loss: 1.1389 - val_accuracy: 0.5928 - val_loss: 1.1366 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5987 - loss: 1.1139 - val_accuracy: 0.6058 - val_loss: 1.1051 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6058 - loss: 1.0956 - val_accuracy: 0.6082 - val_loss: 1.0950 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6127 - loss: 1.0759 - val_accuracy: 0.6108 - val_loss: 1.0754 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6183 - loss: 1.0593 - val_accuracy: 0.6250 - val_loss: 1.0534 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6273 - loss: 1.0440 - val_accuracy: 0.6259 - val_loss: 1.0416 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 7ms/step - accuracy: 0.6346 - loss: 1.0246 - val_accuracy: 0.6378 - val_loss: 1.0087 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6370 - loss: 1.0095 - val_accuracy: 0.6393 - val_loss: 1.0068 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6463 - loss: 0.9924 - val_accuracy: 0.6466 - val_loss: 0.9907 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6493 - loss: 0.9791 - val_accuracy: 0.6523 - val_loss: 0.9686 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6547 - loss: 0.9662 - val_accuracy: 0.6540 - val_loss: 0.9660 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6603 - loss: 0.9523 - val_accuracy: 0.6592 - val_loss: 0.9496 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6630 - loss: 0.9408 - val_accuracy: 0.6545 - val_loss: 0.9557 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6691 - loss: 0.9276 - val_accuracy: 0.6614 - val_loss: 0.9484 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6710 - loss: 0.9182 - val_accuracy: 0.6667 - val_loss: 0.9376 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6764 - loss: 0.9055 - val_accuracy: 0.6727 - val_loss: 0.9101 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6774 - loss: 0.8937 - val_accuracy: 0.6713 - val_loss: 0.9130 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6841 - loss: 0.8856 - val_accuracy: 0.6678 - val_loss: 0.9261 学習時間:122.5秒 パラメータ数:93,450 test_accuracy:0.6648 === GMP === Model: "B_GMP" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input_layer_4 (InputLayer) │ (None, 32, 32, 3) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_8 (Conv2D) │ (None, 32, 32, 64) │ 1,792 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_8 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ conv2d_9 (Conv2D) │ (None, 16, 16, 128) │ 73,856 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ max_pooling2d_9 (MaxPooling2D) │ (None, 8, 8, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ global_max_pooling2d_2 │ (None, 128) │ 0 │ │ (GlobalMaxPooling2D) │ │ │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_8 (Dense) │ (None, 128) │ 16,512 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dropout_4 (Dropout) │ (None, 128) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ dense_9 (Dense) │ (None, 10) │ 1,290 │ └─────────────────────────────────┴────────────────────────┴───────────────┘ Total params: 93,450 (365.04 KB) Trainable params: 93,450 (365.04 KB) Non-trainable params: 0 (0.00 B) None Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 8ms/step - accuracy: 0.3309 - loss: 1.8012 - val_accuracy: 0.4603 - val_loss: 1.5129 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4716 - loss: 1.4555 - val_accuracy: 0.5221 - val_loss: 1.3397 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5290 - loss: 1.3142 - val_accuracy: 0.5535 - val_loss: 1.2500 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5624 - loss: 1.2237 - val_accuracy: 0.5737 - val_loss: 1.1996 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5879 - loss: 1.1548 - val_accuracy: 0.5997 - val_loss: 1.1274 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6108 - loss: 1.0984 - val_accuracy: 0.6125 - val_loss: 1.0957 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6244 - loss: 1.0593 - val_accuracy: 0.6137 - val_loss: 1.0850 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6372 - loss: 1.0208 - val_accuracy: 0.6264 - val_loss: 1.0563 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6489 - loss: 0.9841 - val_accuracy: 0.6333 - val_loss: 1.0344 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6613 - loss: 0.9547 - val_accuracy: 0.6415 - val_loss: 1.0070 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6683 - loss: 0.9321 - val_accuracy: 0.6504 - val_loss: 0.9881 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6803 - loss: 0.9034 - val_accuracy: 0.6433 - val_loss: 1.0014 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6914 - loss: 0.8811 - val_accuracy: 0.6500 - val_loss: 0.9882 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6913 - loss: 0.8605 - val_accuracy: 0.6575 - val_loss: 0.9737 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7015 - loss: 0.8405 - val_accuracy: 0.6460 - val_loss: 1.0043 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7093 - loss: 0.8185 - val_accuracy: 0.6644 - val_loss: 0.9625 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7143 - loss: 0.8039 - val_accuracy: 0.6635 - val_loss: 0.9644 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7192 - loss: 0.7883 - val_accuracy: 0.6635 - val_loss: 0.9602 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7283 - loss: 0.7653 - val_accuracy: 0.6646 - val_loss: 0.9621 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7308 - loss: 0.7531 - val_accuracy: 0.6678 - val_loss: 0.9601 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7370 - loss: 0.7378 - val_accuracy: 0.6671 - val_loss: 0.9797 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7422 - loss: 0.7255 - val_accuracy: 0.6650 - val_loss: 0.9834 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7466 - loss: 0.7085 - val_accuracy: 0.6735 - val_loss: 0.9515 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7502 - loss: 0.7005 - val_accuracy: 0.6638 - val_loss: 0.9904 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7548 - loss: 0.6850 - val_accuracy: 0.6642 - val_loss: 0.9869 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7597 - loss: 0.6740 - val_accuracy: 0.6672 - val_loss: 1.0035 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7631 - loss: 0.6639 - val_accuracy: 0.6727 - val_loss: 0.9694 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7680 - loss: 0.6492 - val_accuracy: 0.6742 - val_loss: 0.9827 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7685 - loss: 0.6445 - val_accuracy: 0.6645 - val_loss: 1.0255 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7744 - loss: 0.6320 - val_accuracy: 0.6688 - val_loss: 1.0026 学習時間:122.7秒 パラメータ数:93,450 test_accuracy:0.6625 === GAP_GMP === Model: "C_GAP_GMP" ┏━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ Connected to ┃ ┡━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━┩ │ input_layer_5 │ (None, 32, 32, 3) │ 0 │ - │ │ (InputLayer) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_10 (Conv2D) │ (None, 32, 32, │ 1,792 │ input_layer_5[0]… │ │ │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ max_pooling2d_10 │ (None, 16, 16, │ 0 │ conv2d_10[0][0] │ │ (MaxPooling2D) │ 64) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ conv2d_11 (Conv2D) │ (None, 16, 16, │ 73,856 │ max_pooling2d_10… │ │ │ 128) │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ max_pooling2d_11 │ (None, 8, 8, 128) │ 0 │ conv2d_11[0][0] │ │ (MaxPooling2D) │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ global_average_poo… │ (None, 128) │ 0 │ max_pooling2d_11… │ │ (GlobalAveragePool… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ global_max_pooling… │ (None, 128) │ 0 │ max_pooling2d_11… │ │ (GlobalMaxPooling2… │ │ │ │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ concatenate_1 │ (None, 256) │ 0 │ global_average_p… │ │ (Concatenate) │ │ │ global_max_pooli… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dense_10 (Dense) │ (None, 128) │ 32,896 │ concatenate_1[0]… │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dropout_5 (Dropout) │ (None, 128) │ 0 │ dense_10[0][0] │ ├─────────────────────┼───────────────────┼────────────┼───────────────────┤ │ dense_11 (Dense) │ (None, 10) │ 1,290 │ dropout_5[0][0] │ └─────────────────────┴───────────────────┴────────────┴───────────────────┘ Total params: 109,834 (429.04 KB) Trainable params: 109,834 (429.04 KB) Non-trainable params: 0 (0.00 B) None Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 8ms/step - accuracy: 0.3459 - loss: 1.7682 - val_accuracy: 0.4633 - val_loss: 1.4792 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4868 - loss: 1.4164 - val_accuracy: 0.5308 - val_loss: 1.3066 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5420 - loss: 1.2749 - val_accuracy: 0.5732 - val_loss: 1.1981 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5756 - loss: 1.1803 - val_accuracy: 0.5990 - val_loss: 1.1230 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6029 - loss: 1.1089 - val_accuracy: 0.6065 - val_loss: 1.1035 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6244 - loss: 1.0536 - val_accuracy: 0.6193 - val_loss: 1.0552 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6414 - loss: 1.0040 - val_accuracy: 0.6329 - val_loss: 1.0157 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6574 - loss: 0.9643 - val_accuracy: 0.6432 - val_loss: 0.9930 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6721 - loss: 0.9278 - val_accuracy: 0.6513 - val_loss: 0.9781 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6815 - loss: 0.8933 - val_accuracy: 0.6587 - val_loss: 0.9672 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6922 - loss: 0.8636 - val_accuracy: 0.6624 - val_loss: 0.9411 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7022 - loss: 0.8393 - val_accuracy: 0.6524 - val_loss: 0.9700 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7098 - loss: 0.8165 - val_accuracy: 0.6582 - val_loss: 0.9685 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7192 - loss: 0.7922 - val_accuracy: 0.6706 - val_loss: 0.9376 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7227 - loss: 0.7768 - val_accuracy: 0.6562 - val_loss: 0.9763 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7325 - loss: 0.7540 - val_accuracy: 0.6693 - val_loss: 0.9544 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7367 - loss: 0.7353 - val_accuracy: 0.6736 - val_loss: 0.9425 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7434 - loss: 0.7223 - val_accuracy: 0.6770 - val_loss: 0.9455 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7494 - loss: 0.7006 - val_accuracy: 0.6770 - val_loss: 0.9525 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7533 - loss: 0.6891 - val_accuracy: 0.6815 - val_loss: 0.9437 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7585 - loss: 0.6794 - val_accuracy: 0.6833 - val_loss: 0.9399 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7635 - loss: 0.6606 - val_accuracy: 0.6772 - val_loss: 0.9628 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7721 - loss: 0.6403 - val_accuracy: 0.6823 - val_loss: 0.9627 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7765 - loss: 0.6296 - val_accuracy: 0.6745 - val_loss: 1.0061 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7787 - loss: 0.6198 - val_accuracy: 0.6786 - val_loss: 0.9896 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7815 - loss: 0.6082 - val_accuracy: 0.6729 - val_loss: 1.0361 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7865 - loss: 0.5949 - val_accuracy: 0.6798 - val_loss: 1.0058 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.7923 - loss: 0.5814 - val_accuracy: 0.6729 - val_loss: 1.0300 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7902 - loss: 0.5794 - val_accuracy: 0.6742 - val_loss: 1.0403 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7964 - loss: 0.5660 - val_accuracy: 0.6824 - val_loss: 1.0218 学習時間:125.5秒 パラメータ数:109,834 test_accuracy:0.6844
グラフ+サマリー
# ── 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('gap_vs_gmp_comparison.png', dpi=150)
plt.show()
# ── train_loss vs val_loss(過学習の乖離)────────────
fig2, axes2 = plt.subplots(3, 1, figsize=(7, 14))
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(f'{label}')
axes2[i].set_xlabel('Epoch'); axes2[i].legend(); axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('gap_vs_gmp_overfit.png', dpi=150)
plt.show()
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>10} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8} | {'Params':>12}")
print("-" * 58)
for label in ['GAP', 'GMP', 'GAP_GMP']:
val_acc = histories[label].history['val_accuracy'][-1]
test_acc = scores[label][1]
t = times[label]
p = params[label]
print(f"{label:>10} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f} | {p:>12,}")
print("-" * 58)
最終結果サマリー
===== 最終結果サマリー =====
Pattern | Val Acc | Test Acc | Time(s) | Params
----------------------------------------------------------
GAP | 0.6678 | 0.6648 | 122.5 | 93,450
GMP | 0.6688 | 0.6625 | 122.7 | 93,450
GAP_GMP | 0.6824 | 0.6844 | 125.5 | 109,834
----------------------------------------------------------
実験結果
精度グラフ
損失グラフ(過学習の乖離)
GAP
GMP
GAP_GMP
最終結果サマリー(シード42固定)
| パターン | 最終 val_accuracy | 最終 test_accuracy | パラメータ数 | 学習時間 |
|---|---|---|---|---|
| A:GAP | 66.78% | 66.48% | 93,450 | 122.5秒 |
| B:GMP | 66.88% | 66.25% | 93,450 | 122.7秒 |
| C:GAP+GMP | 68.24% | 68.44% | 109,834 | 125.5秒 |
考察
今回の実験では乱数シードを42に固定して比較しています。実は最初にシードなしで実験したときは GMP が GAP を +2.41ポイント上回る結果が出ていましたが、シードを固定して再実行したところ結果が大きく変わりました。その経緯も含めて考察します。
① シードなし vs シード固定で結果が逆転した
| 条件 | GAP test_acc | GMP test_acc | 差(GMP − GAP) |
|---|---|---|---|
| 1回目(シードなし) | 66.19% | 68.60% | +2.41%(GMPが優位) |
| 2回目(シード42固定) | 66.48% | 66.25% | −0.23%(GAPがわずかに上) |
1回目でGMPが大幅に上回ったのは、初期重みの運が良かっただけでした。シードを固定して条件を揃えると、GAPとGMPの精度差はほぼゼロ(0.23ポイント)に縮まりました。これは「GMP > GAP」という結論を出すには不十分な差です。
今回のように「GMP の方が2%以上精度が高かった!」という結果も、シードを固定して再実行すると差がほぼ消えることがあります。1回の実験結果だけで「○○の方が優れている」と判断するのは危険です。比較実験を行うときは必ずシードを固定しましょう。どうしても固定できない場合は複数回実行して平均を取ることを推奨します。
② シード固定後の正しい比較:GAPとGMPはほぼ同等
シードを固定した条件では、GAPとGMPの test_accuracy の差は 0.23ポイント(66.48% vs 66.25%)です。学習時間もほぼ同じ(122.5秒 vs 122.7秒)、パラメータ数も同一(93,450)です。この結果から、GAPとGMPの単体性能はほぼ同等という結論が妥当です。
③ GAP + GMP の連結は明確な効果あり
一方、パターンC(GAP+GMP連結)はシード固定後も test_accuracy 68.44% を記録し、GAPとGMPそれぞれより 約+2ポイント 安定して高い結果になりました。
| 比較 | test_accuracy の差 | パラメータ増加 |
|---|---|---|
| GAP → GAP+GMP | +1.96%(66.48% → 68.44%) | +16,384(+17.5%) |
| GMP → GAP+GMP | +2.19%(66.25% → 68.44%) | +16,384(+17.5%) |
GAPとGMPを連結することで「全体の平均的な反応」と「最も強い反応」の両方をDense層に渡せるため、単体では捉えられなかった情報が補完されていると考えられます。パラメータ増加は+17.5%に留まり、Flattenのような爆発的な増加は起きません。
④ どちらを選ぶべきか
| 状況 | 推奨 | 理由 |
|---|---|---|
| まず試す・迷ったとき | GAP | GMPと精度は同等で、過学習への一般的な安定性はGAPの方が理論的に高い |
| 少しのパラメータ増で精度を上げたい | GAP + GMP 連結 | 今回の実験で+2ポイントの安定した向上が確認できた |
| 転移学習(MobileNet/EfficientNetなど) | GAP(変更しない) | 多くのSOTAモデルがGAPを前提に設計されているため |
- シードなしの1回目は GMP が GAP を +2.41ポイント上回ったが、シードを固定した再実験では差がほぼゼロ(0.23ポイント)に縮まった
- GAPとGMPの単体性能は同等。「GMP の方が優れていた」は初期値の運による誤った結論だった
- 比較実験はシードを固定してから行うことが必須。1回の結果だけで判断すると誤った結論を出しやすい
- GAP + GMP の連結はシード固定後も +約2ポイントの安定した向上を確認。パラメータ増+17.5%に対してコスト対効果は良好
- GAP・GMP どちらを選んでも Flatten と違いパラメータ爆発しない点は共通
- 単体では「迷ったらGAP」、精度を上げたいなら「GAP+GMP連結」が今回の実験から得られる実践的な結論
関連記事もあわせてどうぞ:
- GAPとFlattenの比較 → Global Average Pooling vs Flatten|CNNの最終層、どっちが精度・速度で有利か?【Keras実験】
- Dense層のユニット数比較 → Dense層のユニット数を変えると精度はどう変わる?(32 vs 128 vs 512)【Keras×CIFAR-10実験】
- Dropout率の比較実験 → Dropoutの割合(0.0 vs 0.2 vs 0.5)を変えると過学習はどう変わる?【Keras×CIFAR-10実験】
- Conv2Dフィルター数の比較 → Conv2D フィルター数の比較記事






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