GlobalAveragePooling vs GlobalMaxPooling どちらが精度・過学習耐性で優れるか?【Keras×CIFAR-10実験】

投稿日:2026年5月14日木曜日 最終更新日:

CIFAR-10 CNN Global Average Pooling Google Colab Keras 過学習 画像分類

X f B! P L
GlobalAveragePooling vs GlobalMaxPooling どちらが精度・過学習耐性で優れるか?【Keras×CIFAR-10実験】 アイキャッチ画像

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

GAP

GMP

GMP

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連結」が今回の実験から得られる実践的な結論

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