GAPモデルでMaxPoolingのpool_sizeを変えると精度はどう変わる?【Keras実験・予想外の結果】

投稿日:2026年4月14日火曜日 最終更新日:

CIFAR-10 CNN Global Average Pooling Google Colab Keras MaxPooling 画像分類

X f B! P L
アイキャッチ画像 GAPモデルでMaxPoolingのpool_sizeを変えると精度はどう変わる?【Keras実験・予想外の結果】

KerasでCNNを書くとき、MaxPooling2Dの pool_size はなんとなく (2, 2) にしていませんか?

「pool_sizeを大きくすると空間情報が失われて精度が下がるはず」——今回の実験を始める前、私もそう思っていました。

ところが実際に動かしてみると、pool_sizeが大きいほど精度が高く、学習時間も短いという予想外の結果になりました。その原因を探っていくと、今回のモデルで使っていた GlobalAveragePooling2D(GAP)の挙動が鍵でした。

📘 この記事でわかること

  • GAP使用モデルでpool_sizeを変えたときの精度・学習時間の変化
  • なぜGAPを使うとpool_sizeの影響が小さくなるのか
  • pool_sizeの選び方はGAP/Flattenどちらを使うかで変わること

実験設定

使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10(32×32×3のカラー画像、10クラス)です。

pool_size以外の条件(Conv2Dのフィルター数・層数・Optimizer・エポック数・バッチサイズ)は全て同一にして、pool_sizeの影響だけを観察します。モデルの最終集約層には GlobalAveragePooling2D(GAP)を使っています。

pool_sizeによる特徴マップサイズの変化(入力32×32の場合):

pool_size 1回目のPooling後 2回目のPooling後 GAP後の次元数
(2, 2) 16×16 8×8 全パターン共通:128次元
(3, 3) 10×10 3×3
(4, 4) 8×8 2×2

ここが重要なポイントです。 pool_sizeが異なっても、GAPを通過した後はすべて 128次元のベクトル になります。この時点でDense層への入力は完全に揃っています。

pool_size=(3,3) のstridesはデフォルトでpool_sizeと同じ(3,3)になります。32÷3=10余り2 で端が切り捨てられ10×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 42 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 2s (4,623 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122354 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 41.2 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
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

# モデル構築関数(pool_sizeを引数で切り替え)
def build_model(pool_size, name):
    return keras.Sequential([
        keras.layers.Input(shape=(32, 32, 3)),
        keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D(pool_size=pool_size),
        keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D(pool_size=pool_size),
        keras.layers.GlobalAveragePooling2D(),
        keras.layers.Dense(128, activation='relu'),
        keras.layers.Dense(10, activation='softmax'),
    ], 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
    )
    elapsed = time.time() - start
    return history, elapsed
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 4s 0us/step

3パターンの学習実行

print("\n=== Pattern A:pool_size=(2,2) ===")
model_A = build_model((2, 2), 'A_pool2x2')
print(model_A.summary())
history_A, time_A = compile_and_fit(model_A)
print(f"学習時間:{time_A:.1f}秒 パラメータ数:{model_A.count_params():,}")
 
print("\n=== Pattern B:pool_size=(3,3) ===")
model_B = build_model((3, 3), 'B_pool3x3')
print(model_B.summary())
history_B, time_B = compile_and_fit(model_B)
print(f"学習時間:{time_B:.1f}秒 パラメータ数:{model_B.count_params():,}")
 
print("\n=== Pattern C:pool_size=(4,4) ===")
model_C = build_model((4, 4), 'C_pool4x4')
print(model_C.summary())
history_C, time_C = compile_and_fit(model_C)
print(f"学習時間:{time_C:.1f}秒 パラメータ数:{model_C.count_params():,}")
 
# テストデータで最終精度を評価
test_results = {
    'A:pool_size=(2,2)': model_A.evaluate(x_test, y_test, verbose=0),
    'B:pool_size=(3,3)': model_B.evaluate(x_test, y_test, verbose=0),
    'C:pool_size=(4,4)': model_C.evaluate(x_test, y_test, verbose=0),
}
実行結果をクリックして内容を開く
=== Pattern A:pool_size=(2,2) ===
Model: "A_pool2x2"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D)                 │ (None, 32, 32, 64)     │         1,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D)    │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D)               │ (None, 16, 16, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D)  │ (None, 8, 8, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d        │ (None, 128)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense)                   │ (None, 128)            │        16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (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 ━━━━━━━━━━━━━━━━━━━━ 13s 10ms/step - accuracy: 0.2754 - loss: 1.9032 - val_accuracy: 0.3508 - val_loss: 1.7013
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3980 - loss: 1.6292 - val_accuracy: 0.4259 - val_loss: 1.5462
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4483 - loss: 1.5058 - val_accuracy: 0.4652 - val_loss: 1.4658
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4855 - loss: 1.4145 - val_accuracy: 0.4894 - val_loss: 1.4294
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5099 - loss: 1.3502 - val_accuracy: 0.5083 - val_loss: 1.3523
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5282 - loss: 1.2988 - val_accuracy: 0.5261 - val_loss: 1.2898
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5410 - loss: 1.2653 - val_accuracy: 0.5367 - val_loss: 1.2872
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5551 - loss: 1.2282 - val_accuracy: 0.5571 - val_loss: 1.2080
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5679 - loss: 1.1961 - val_accuracy: 0.5669 - val_loss: 1.1924
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5795 - loss: 1.1688 - val_accuracy: 0.5866 - val_loss: 1.1524
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5874 - loss: 1.1421 - val_accuracy: 0.5552 - val_loss: 1.2374
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5995 - loss: 1.1158 - val_accuracy: 0.5925 - val_loss: 1.1216
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6054 - loss: 1.0980 - val_accuracy: 0.5915 - val_loss: 1.1317
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6139 - loss: 1.0720 - val_accuracy: 0.6148 - val_loss: 1.0766
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6192 - loss: 1.0535 - val_accuracy: 0.6136 - val_loss: 1.0759
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6285 - loss: 1.0340 - val_accuracy: 0.6123 - val_loss: 1.0878
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6362 - loss: 1.0158 - val_accuracy: 0.6024 - val_loss: 1.0955
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6404 - loss: 0.9963 - val_accuracy: 0.6441 - val_loss: 1.0024
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6485 - loss: 0.9797 - val_accuracy: 0.6285 - val_loss: 1.0236
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6545 - loss: 0.9674 - val_accuracy: 0.6425 - val_loss: 1.0060
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6596 - loss: 0.9499 - val_accuracy: 0.6462 - val_loss: 0.9905
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6642 - loss: 0.9416 - val_accuracy: 0.6478 - val_loss: 0.9850
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6715 - loss: 0.9189 - val_accuracy: 0.6574 - val_loss: 0.9647
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6763 - loss: 0.9096 - val_accuracy: 0.6546 - val_loss: 0.9646
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6783 - loss: 0.8979 - val_accuracy: 0.6602 - val_loss: 0.9519
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6836 - loss: 0.8830 - val_accuracy: 0.6646 - val_loss: 0.9471
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6889 - loss: 0.8701 - val_accuracy: 0.6736 - val_loss: 0.9152
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6929 - loss: 0.8569 - val_accuracy: 0.6807 - val_loss: 0.8993
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6989 - loss: 0.8464 - val_accuracy: 0.6743 - val_loss: 0.9143
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7001 - loss: 0.8373 - val_accuracy: 0.6800 - val_loss: 0.9080
学習時間:128.4秒 パラメータ数:93,450

=== Pattern B:pool_size=(3,3) ===
Model: "B_pool3x3"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_2 (Conv2D)               │ (None, 32, 32, 64)     │         1,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 10, 10, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 10, 10, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 3, 3, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d_1      │ (None, 128)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 128)            │        16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_3 (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 ━━━━━━━━━━━━━━━━━━━━ 7s 8ms/step - accuracy: 0.3261 - loss: 1.8095 - val_accuracy: 0.4172 - val_loss: 1.5821
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.4447 - loss: 1.5130 - val_accuracy: 0.4837 - val_loss: 1.4189
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 5ms/step - accuracy: 0.5027 - loss: 1.3759 - val_accuracy: 0.5142 - val_loss: 1.3455
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.5368 - loss: 1.2925 - val_accuracy: 0.5312 - val_loss: 1.3112
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.5600 - loss: 1.2260 - val_accuracy: 0.5748 - val_loss: 1.1974
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.5814 - loss: 1.1719 - val_accuracy: 0.5769 - val_loss: 1.1897
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.5966 - loss: 1.1304 - val_accuracy: 0.6067 - val_loss: 1.1148
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6092 - loss: 1.0947 - val_accuracy: 0.5689 - val_loss: 1.2110
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6237 - loss: 1.0563 - val_accuracy: 0.6085 - val_loss: 1.0860
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6330 - loss: 1.0284 - val_accuracy: 0.6252 - val_loss: 1.0613
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6471 - loss: 0.9976 - val_accuracy: 0.6226 - val_loss: 1.0480
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6570 - loss: 0.9669 - val_accuracy: 0.6459 - val_loss: 1.0024
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6649 - loss: 0.9469 - val_accuracy: 0.6568 - val_loss: 0.9692
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 5ms/step - accuracy: 0.6753 - loss: 0.9180 - val_accuracy: 0.6179 - val_loss: 1.0709
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6795 - loss: 0.9024 - val_accuracy: 0.6490 - val_loss: 0.9898
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6901 - loss: 0.8813 - val_accuracy: 0.6382 - val_loss: 1.0171
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6975 - loss: 0.8595 - val_accuracy: 0.6422 - val_loss: 1.0168
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7019 - loss: 0.8471 - val_accuracy: 0.6642 - val_loss: 0.9555
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 5ms/step - accuracy: 0.7100 - loss: 0.8208 - val_accuracy: 0.6690 - val_loss: 0.9530
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7150 - loss: 0.8132 - val_accuracy: 0.6842 - val_loss: 0.9039
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7218 - loss: 0.7926 - val_accuracy: 0.6752 - val_loss: 0.9329
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7224 - loss: 0.7778 - val_accuracy: 0.6846 - val_loss: 0.9128
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7312 - loss: 0.7653 - val_accuracy: 0.6854 - val_loss: 0.9147
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7355 - loss: 0.7522 - val_accuracy: 0.6959 - val_loss: 0.8767
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7386 - loss: 0.7395 - val_accuracy: 0.6923 - val_loss: 0.8910
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7430 - loss: 0.7227 - val_accuracy: 0.6991 - val_loss: 0.8720
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7465 - loss: 0.7154 - val_accuracy: 0.6900 - val_loss: 0.9084
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7506 - loss: 0.7035 - val_accuracy: 0.6931 - val_loss: 0.8929
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7587 - loss: 0.6878 - val_accuracy: 0.7070 - val_loss: 0.8442
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7574 - loss: 0.6839 - val_accuracy: 0.7016 - val_loss: 0.8715
学習時間:103.8秒 パラメータ数:93,450

=== Pattern C:pool_size=(4,4) ===
Model: "C_pool4x4"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d_4 (Conv2D)               │ (None, 32, 32, 64)     │         1,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_4 (MaxPooling2D)  │ (None, 8, 8, 64)       │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 8, 8, 128)      │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_5 (MaxPooling2D)  │ (None, 2, 2, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d_2      │ (None, 128)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 128)            │        16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_5 (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 ━━━━━━━━━━━━━━━━━━━━ 7s 6ms/step - accuracy: 0.3523 - loss: 1.7521 - val_accuracy: 0.4241 - val_loss: 1.5548
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.4915 - loss: 1.4009 - val_accuracy: 0.5257 - val_loss: 1.3107
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.5471 - loss: 1.2546 - val_accuracy: 0.5449 - val_loss: 1.2546
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.5830 - loss: 1.1639 - val_accuracy: 0.5901 - val_loss: 1.1428
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6063 - loss: 1.0992 - val_accuracy: 0.6009 - val_loss: 1.1121
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6311 - loss: 1.0404 - val_accuracy: 0.6163 - val_loss: 1.0783
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6478 - loss: 0.9915 - val_accuracy: 0.6413 - val_loss: 1.0188
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.6602 - loss: 0.9581 - val_accuracy: 0.6215 - val_loss: 1.0618
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6748 - loss: 0.9202 - val_accuracy: 0.6600 - val_loss: 0.9911
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6885 - loss: 0.8822 - val_accuracy: 0.6810 - val_loss: 0.9123
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.6978 - loss: 0.8594 - val_accuracy: 0.6801 - val_loss: 0.9184
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7074 - loss: 0.8302 - val_accuracy: 0.6786 - val_loss: 0.9188
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7152 - loss: 0.8083 - val_accuracy: 0.6673 - val_loss: 0.9752
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7262 - loss: 0.7846 - val_accuracy: 0.6922 - val_loss: 0.8877
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7304 - loss: 0.7620 - val_accuracy: 0.6968 - val_loss: 0.8694
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7398 - loss: 0.7409 - val_accuracy: 0.7066 - val_loss: 0.8601
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7463 - loss: 0.7199 - val_accuracy: 0.7097 - val_loss: 0.8472
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7539 - loss: 0.7035 - val_accuracy: 0.7006 - val_loss: 0.8870
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7587 - loss: 0.6869 - val_accuracy: 0.6943 - val_loss: 0.8831
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7629 - loss: 0.6741 - val_accuracy: 0.6955 - val_loss: 0.8977
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7675 - loss: 0.6550 - val_accuracy: 0.6925 - val_loss: 0.9150
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7736 - loss: 0.6425 - val_accuracy: 0.7178 - val_loss: 0.8426
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7804 - loss: 0.6234 - val_accuracy: 0.7255 - val_loss: 0.8109
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7836 - loss: 0.6126 - val_accuracy: 0.7087 - val_loss: 0.8676
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.7890 - loss: 0.5983 - val_accuracy: 0.7183 - val_loss: 0.8382
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.7931 - loss: 0.5900 - val_accuracy: 0.7132 - val_loss: 0.8502
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8009 - loss: 0.5692 - val_accuracy: 0.7200 - val_loss: 0.8295
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 4ms/step - accuracy: 0.8017 - loss: 0.5607 - val_accuracy: 0.7252 - val_loss: 0.8370
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.8054 - loss: 0.5535 - val_accuracy: 0.7261 - val_loss: 0.8269
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.8121 - loss: 0.5278 - val_accuracy: 0.7351 - val_loss: 0.8147
学習時間:91.7秒 パラメータ数:93,450

グラフ(val_accuracy + val_loss 比較)+ サマリー

histories = {
    'A:pool_size=(2,2)': history_A,
    'B:pool_size=(3,3)': history_B,
    'C:pool_size=(4,4)': history_C,
}
 
# ── 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('poolsize_gap_comparison.png', dpi=150)
plt.show()

# ── 最終結果サマリー ─────────────────────────────────
key_order = ['A:pool_size=(2,2)', 'B:pool_size=(3,3)', 'C:pool_size=(4,4)']
time_list = {'A:pool_size=(2,2)': time_A, 'B:pool_size=(3,3)': time_B, 'C:pool_size=(4,4)': time_C}
 
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>22} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8} | {'2nd Map Size':>12}")
print("-" * 75)
map_sizes = {'A:pool_size=(2,2)': '8×8', 'B:pool_size=(3,3)': '3×3', 'C:pool_size=(4,4)': '2×2'}
for key in key_order:
    val_acc = histories[key].history['val_accuracy'][-1]
    test_loss, test_acc = test_results[key]
    elapsed = time_list[key]
    mapsize = map_sizes[key]
    print(f"{key:>22} | {val_acc:>8.4f} | {test_acc:>9.4f} | {elapsed:>8.1f} | {mapsize:>12}")
print("-" * 75)

実行結果

===== 最終結果サマリー =====
               Pattern |  Val Acc |  Test Acc |  Time(s) | 2nd Map Size
---------------------------------------------------------------------------
     A:pool_size=(2,2) |   0.6800 |    0.6780 |    128.4 |          8×8
     B:pool_size=(3,3) |   0.7016 |    0.6952 |    103.8 |          3×3
     C:pool_size=(4,4) |   0.7351 |    0.7230 |     91.7 |          2×2
---------------------------------------------------------------------------

実験結果

精度グラフ

精度グラフ MaxPooling

損失グラフ

損失グラフ MaxPooling

サマリ

パターン 最終 val_accuracy 最終 test_accuracy 2回目Pooling後のマップ 学習時間
A:pool_size=(2,2) 68.00% 67.80% 8×8 128.4秒
B:pool_size=(3,3) 70.16% 69.52% 3×3 103.8秒
C:pool_size=(4,4) 73.51% 72.30% 2×2 91.7秒

pool_sizeが大きいほど精度が高く、学習時間も短いという、予想に反する結果になりました。


考察:GAPがpool_sizeの影響を吸収する

① GAPを通るとpool_size後のサイズ差が消える

今回の結果を理解する鍵は GlobalAveragePooling2D(GAP)の動作にあります。

GAPは特徴マップ全体をチャンネルあたり1つの平均値に集約します。そのため、2回目Pooling後の特徴マップが 8×8×128 であっても 2×2×128 であっても、GAPを通過した後は同じ 128次元のベクトル になります。

つまり pool_size=(2,2) で8×8の特徴マップを丁寧に残しても、GAPで平均化して潰してしまうため「空間情報を保持するメリット」がDense層には届きません。このモデル構成では、pool_sizeの違いが精度に直結しにくい設計になっていました。

② 強い圧縮が正則化として機能した

pool_sizeを大きくすると特徴マップが強く圧縮されます。この圧縮が一種の正則化効果として働き、過学習を抑えた可能性があります。学習時間が短い(= 1エポックあたりの計算量が少ない)ことからも、モデルがよりシンプルな表現を学習できていたことが示唆されます。

③ この実験はGAP使用モデルでの「挙動観察」として読む

今回の実験は「pool_sizeの純粋な比較」としては設計に限界があります。GAPがpool_sizeの空間サイズ差を吸収するため、pool_sizeの影響を単独で取り出す実験にはなっていないからです。

ただし「GAPを使うモデルではpool_sizeを大きくしても精度が落ちない、むしろ正則化効果で上がる場合もある」という実用的な知見は得られました。

pool_sizeの効果をより純粋に比較したい場合は、GAPの代わりにFlattenを使い、かつパラメータ数が揃うよう設計を工夫した実験が必要です。その実験については別記事で行います。


まとめ

今回はGAP使用CNNモデルでpool_sizeを2×2・3×3・4×4の3パターンで比較しました。

  • pool_size=(4,4) が最高精度(72.30%)・最短学習時間(91.7秒) と予想に反する逆転結果に
  • 原因は GAPがPooling後の空間サイズの差を吸収する ため。pool_sizeが何であっても、GAP後は同じ128次元ベクトルになる
  • さらに強い圧縮による 正則化効果 が(4,4)の精度を後押しした可能性がある
  • GAP使用モデルではpool_sizeの選択が精度に与える影響は限定的。それよりもフィルター数・Dropout・学習率を先に調整する方が効果的
  • pool_sizeの効果を純粋に比較するには、パラメータ数が揃う設計での実験が必要(続編で検証予定)

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