「Dropoutはとりあえず入れておけばいい」と思っていませんか?GlobalAveragePooling2D(GAP)の前に置くか、後に置くかで、実は効き方が大きく変わる可能性があります。
Dropout自体の割合(0.0/0.2/0.5)による効果は既存記事で検証済みですが、今回は割合を0.2で固定し、挿入位置(GAP前/GAP後/両方)だけを変えて精度と過学習の抑制度を比較します。実は、GAPの「平均化」という性質がDropoutのノイズをある程度打ち消してしまう可能性があり、位置によって同じ0.2でも効き目が変わることが予想されます。
📘 この記事でわかること
- Dropout(0.2)をGAP前・GAP後・両方に置いた場合のtest_accuracyと過学習度の違い
- GAPの平均化がDropoutの正則化効果を弱める可能性がある理由
- Dropoutを両方に入れる「二重正則化」が精度を下げるリスク
なぜGAPの前後でDropoutの効き方が変わりうるのか
Dropoutは学習時にランダムに要素を0にし、残った要素を1/(1-p)倍にスケーリングする(inverted dropout)ことで、期待値を変えずにノイズを加える仕組みです。
$$ y_{i,j} = \frac{x_{i,j} \cdot m_{i,j}}{1-p}, \quad m_{i,j} \sim \text{Bernoulli}(1-p) $$
今回のモデルはMaxPooling2Dを2回通すため、GAP直前の特徴マップは8×8=64要素あります。GAPはこの64要素を単純平均するため、DropoutをGAP前に置くと、次のような形になります。
$$ \text{GAP}(\text{Dropout}(x)) = \frac{1}{64}\sum_{i,j} \frac{x_{i,j} \cdot m_{i,j}}{1-p} $$
64要素の平均を取るため、大数の法則により個々の要素がランダムに0になるノイズは平均化の過程である程度打ち消され、GAP後の値の期待値は変わらないもののブレ(分散)は小さくなります。一方、GAP後(Dense層の直前)にDropoutを置いた場合は、GAPで集約された後の低次元ベクトル(今回は128要素)に直接ノイズが乗るため、平均化による打ち消し効果を受けません。この構造の違いが、同じ0.2という割合でも正則化としての「効き目」に差を生む可能性があります。
| パターン | Dropout(0.2)の位置 | ノイズが乗る対象 | GAPによる平均化の影響 |
|---|---|---|---|
| A:GAP前 | Conv層の出力(8×8特徴マップ) | 64要素の各ピクセル | あり(ノイズが平均化で弱まる可能性) |
| B:GAP後 | Dense(128)の出力 | 128要素の各ユニット | なし |
| C:両方 | Conv出力とDense出力の両方 | 両方 | GAP前のみあり |
パターンCは実質的に二重正則化になります。既存記事「Weight Decay(L2正則化)の強さで過学習はどう変わる?」でも、Dropout+Weight Decayの組み合わせが過剰な正則化として精度を下げるケースを確認しており、Dropout同士の組み合わせでも同様の現象が起きるかを検証します。
実験コード
環境準備(最初に一度だけ実行)
# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 53 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 3s (2,714 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122403 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 112.5 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
(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
DROPOUT_RATE = 0.2
def build_model(position, name):
inputs = keras.layers.Input(shape=(32, 32, 3))
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)
# GAP前:Conv出力(8x8x128の特徴マップ)に直接Dropout
if position in ('before', 'both'):
x = keras.layers.Dropout(DROPOUT_RATE)(x)
x = keras.layers.GlobalAveragePooling2D()(x)
x = keras.layers.Dense(128, activation='relu')(x)
# GAP後:Dense(128)の出力にDropout
if position in ('after', 'both'):
x = keras.layers.Dropout(DROPOUT_RATE)(x)
outputs = keras.layers.Dense(10, activation='softmax')(x)
model = keras.Model(inputs, outputs, name=name)
return model
def compile_and_fit(position, name):
keras.backend.clear_session()
model = build_model(position, name)
model.compile(optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(x_train, y_train, epochs=30, batch_size=64,
validation_split=0.2, verbose=1)
return model, history
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 1659s 10us/step
3パターンの学習実行(GAP前 / GAP後 / 両方)
# (position, モデル名=ASCII, 表示ラベル=日本語) の組で管理する
# Kerasのモデル名(name_scope)は日本語を含められないため分離している
patterns = [
('before', 'dropout_before', 'GAP前'),
('after', 'dropout_after', 'GAP後'),
('both', 'dropout_both', '両方'),
]
histories, scores = {}, {}
for position, model_name, label in patterns:
print(f"\n=== {label}(position={position}) ===")
model, h = compile_and_fit(position, model_name)
s = model.evaluate(x_test, y_test, verbose=0)
histories[label] = h
scores[label] = s
train_acc = h.history['accuracy'][-1]
val_acc = h.history['val_accuracy'][-1]
print(f"train_acc: {train_acc:.4f} val_acc: {val_acc:.4f} "
f"test_acc: {s[1]:.4f} train-val gap: {train_acc - val_acc:.4f}")
実行結果をクリックして内容を開く
=== GAP前(position=before) === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 9ms/step - accuracy: 0.2745 - loss: 1.9052 - val_accuracy: 0.3447 - val_loss: 1.7314 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3661 - loss: 1.6785 - val_accuracy: 0.4146 - val_loss: 1.5894 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4296 - loss: 1.5554 - val_accuracy: 0.4431 - val_loss: 1.5017 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4712 - loss: 1.4557 - val_accuracy: 0.4696 - val_loss: 1.4375 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4967 - loss: 1.3901 - val_accuracy: 0.5157 - val_loss: 1.3403 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5152 - loss: 1.3358 - val_accuracy: 0.5363 - val_loss: 1.2945 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5284 - loss: 1.2970 - val_accuracy: 0.5308 - val_loss: 1.3012 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5443 - loss: 1.2591 - val_accuracy: 0.5608 - val_loss: 1.2138 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5573 - loss: 1.2214 - val_accuracy: 0.5398 - val_loss: 1.2829 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5691 - loss: 1.1972 - val_accuracy: 0.5821 - val_loss: 1.1681 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5796 - loss: 1.1698 - val_accuracy: 0.5917 - val_loss: 1.1405 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5861 - loss: 1.1507 - val_accuracy: 0.5960 - val_loss: 1.1241 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5929 - loss: 1.1256 - val_accuracy: 0.5946 - val_loss: 1.1257 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5996 - loss: 1.1083 - val_accuracy: 0.5988 - val_loss: 1.1393 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6100 - loss: 1.0867 - val_accuracy: 0.6080 - val_loss: 1.0919 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6190 - loss: 1.0666 - val_accuracy: 0.6294 - val_loss: 1.0453 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6265 - loss: 1.0497 - val_accuracy: 0.6138 - val_loss: 1.0918 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6308 - loss: 1.0327 - val_accuracy: 0.6199 - val_loss: 1.0585 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6352 - loss: 1.0172 - val_accuracy: 0.6316 - val_loss: 1.0400 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6395 - loss: 1.0007 - val_accuracy: 0.6407 - val_loss: 0.9974 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6463 - loss: 0.9871 - val_accuracy: 0.6492 - val_loss: 0.9843 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6548 - loss: 0.9673 - val_accuracy: 0.6445 - val_loss: 0.9912 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6574 - loss: 0.9571 - val_accuracy: 0.6624 - val_loss: 0.9517 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6626 - loss: 0.9488 - val_accuracy: 0.6698 - val_loss: 0.9394 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6626 - loss: 0.9374 - val_accuracy: 0.6573 - val_loss: 0.9635 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6696 - loss: 0.9241 - val_accuracy: 0.6686 - val_loss: 0.9188 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6745 - loss: 0.9115 - val_accuracy: 0.6673 - val_loss: 0.9320 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6805 - loss: 0.8982 - val_accuracy: 0.6614 - val_loss: 0.9501 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6818 - loss: 0.8924 - val_accuracy: 0.6664 - val_loss: 0.9274 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6834 - loss: 0.8830 - val_accuracy: 0.6750 - val_loss: 0.9052 train_acc: 0.6834 val_acc: 0.6750 test_acc: 0.6747 train-val gap: 0.0084 === GAP後(position=after) === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 8ms/step - accuracy: 0.2709 - loss: 1.9168 - val_accuracy: 0.3639 - val_loss: 1.7175 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3799 - loss: 1.6628 - val_accuracy: 0.4241 - val_loss: 1.5507 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4360 - loss: 1.5292 - val_accuracy: 0.4640 - val_loss: 1.4651 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4697 - loss: 1.4493 - val_accuracy: 0.4952 - val_loss: 1.3871 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4940 - loss: 1.3805 - val_accuracy: 0.4998 - val_loss: 1.3432 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5138 - loss: 1.3297 - val_accuracy: 0.5312 - val_loss: 1.2882 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5281 - loss: 1.2923 - val_accuracy: 0.5289 - val_loss: 1.2875 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5417 - loss: 1.2560 - val_accuracy: 0.5566 - val_loss: 1.2157 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5532 - loss: 1.2264 - val_accuracy: 0.5634 - val_loss: 1.1947 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5602 - loss: 1.2007 - val_accuracy: 0.5774 - val_loss: 1.1708 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5725 - loss: 1.1730 - val_accuracy: 0.5817 - val_loss: 1.1554 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.5840 - loss: 1.1496 - val_accuracy: 0.5951 - val_loss: 1.1102 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5918 - loss: 1.1265 - val_accuracy: 0.5994 - val_loss: 1.1152 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5978 - loss: 1.1077 - val_accuracy: 0.6012 - val_loss: 1.1062 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6054 - loss: 1.0872 - val_accuracy: 0.5943 - val_loss: 1.1099 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6110 - loss: 1.0706 - val_accuracy: 0.6116 - val_loss: 1.0622 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6217 - loss: 1.0493 - val_accuracy: 0.6083 - val_loss: 1.0771 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6259 - loss: 1.0369 - val_accuracy: 0.6176 - val_loss: 1.0641 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6356 - loss: 1.0113 - val_accuracy: 0.6219 - val_loss: 1.0586 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6390 - loss: 1.0020 - val_accuracy: 0.6347 - val_loss: 1.0136 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6485 - loss: 0.9838 - val_accuracy: 0.6332 - val_loss: 1.0149 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6506 - loss: 0.9691 - val_accuracy: 0.6510 - val_loss: 0.9651 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6540 - loss: 0.9606 - val_accuracy: 0.6609 - val_loss: 0.9561 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6629 - loss: 0.9393 - val_accuracy: 0.6605 - val_loss: 0.9582 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6659 - loss: 0.9296 - val_accuracy: 0.6692 - val_loss: 0.9364 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6696 - loss: 0.9227 - val_accuracy: 0.6672 - val_loss: 0.9346 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6770 - loss: 0.9034 - val_accuracy: 0.6496 - val_loss: 0.9779 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6824 - loss: 0.8895 - val_accuracy: 0.6647 - val_loss: 0.9299 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6846 - loss: 0.8837 - val_accuracy: 0.6552 - val_loss: 0.9604 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6893 - loss: 0.8734 - val_accuracy: 0.6712 - val_loss: 0.9264 train_acc: 0.6893 val_acc: 0.6712 test_acc: 0.6646 train-val gap: 0.0181 === 両方(position=both) === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 9ms/step - accuracy: 0.2636 - loss: 1.9359 - val_accuracy: 0.3533 - val_loss: 1.7221 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3631 - loss: 1.6933 - val_accuracy: 0.3824 - val_loss: 1.6197 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4220 - loss: 1.5635 - val_accuracy: 0.4491 - val_loss: 1.5166 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4572 - loss: 1.4783 - val_accuracy: 0.4746 - val_loss: 1.4531 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4828 - loss: 1.4172 - val_accuracy: 0.4869 - val_loss: 1.3988 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5013 - loss: 1.3637 - val_accuracy: 0.5069 - val_loss: 1.3447 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5162 - loss: 1.3263 - val_accuracy: 0.5208 - val_loss: 1.3204 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5278 - loss: 1.2992 - val_accuracy: 0.5519 - val_loss: 1.2417 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5404 - loss: 1.2693 - val_accuracy: 0.5491 - val_loss: 1.2243 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5503 - loss: 1.2395 - val_accuracy: 0.5649 - val_loss: 1.1878 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5623 - loss: 1.2126 - val_accuracy: 0.5766 - val_loss: 1.1706 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5692 - loss: 1.1928 - val_accuracy: 0.5734 - val_loss: 1.1496 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5748 - loss: 1.1692 - val_accuracy: 0.5952 - val_loss: 1.1186 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5854 - loss: 1.1516 - val_accuracy: 0.5965 - val_loss: 1.1202 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5921 - loss: 1.1314 - val_accuracy: 0.6063 - val_loss: 1.0812 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6001 - loss: 1.1094 - val_accuracy: 0.6173 - val_loss: 1.0644 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6054 - loss: 1.0944 - val_accuracy: 0.6139 - val_loss: 1.0663 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6141 - loss: 1.0765 - val_accuracy: 0.6186 - val_loss: 1.0533 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6158 - loss: 1.0671 - val_accuracy: 0.6389 - val_loss: 1.0051 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6269 - loss: 1.0434 - val_accuracy: 0.6300 - val_loss: 1.0365 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6282 - loss: 1.0321 - val_accuracy: 0.6456 - val_loss: 0.9827 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6321 - loss: 1.0183 - val_accuracy: 0.6540 - val_loss: 0.9629 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6394 - loss: 1.0076 - val_accuracy: 0.6527 - val_loss: 0.9689 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6400 - loss: 0.9986 - val_accuracy: 0.6436 - val_loss: 0.9866 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6499 - loss: 0.9754 - val_accuracy: 0.6634 - val_loss: 0.9375 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6517 - loss: 0.9727 - val_accuracy: 0.6585 - val_loss: 0.9522 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6576 - loss: 0.9601 - val_accuracy: 0.6634 - val_loss: 0.9326 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6636 - loss: 0.9427 - val_accuracy: 0.6564 - val_loss: 0.9561 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6649 - loss: 0.9352 - val_accuracy: 0.6672 - val_loss: 0.9238 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6670 - loss: 0.9254 - val_accuracy: 0.6761 - val_loss: 0.8981 train_acc: 0.6670 val_acc: 0.6761 test_acc: 0.6791 train-val gap: -0.0091
グラフ+サマリー
# ── 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('dropout_position_comparison.png', dpi=150)
plt.show()
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>8} | {'Train Acc':>9} | {'Val Acc':>8} | {'Test Acc':>9} | {'Train-Val Gap':>13}")
print("-" * 60)
for label in ['GAP前', 'GAP後', '両方']:
train_acc = histories[label].history['accuracy'][-1]
val_acc = histories[label].history['val_accuracy'][-1]
test_acc = scores[label][1]
gap = train_acc - val_acc
print(f"{label:>8} | {train_acc:>9.4f} | {val_acc:>8.4f} | {test_acc:>9.4f} | {gap:>13.4f}")
print("-" * 60)
実行結果をクリックして内容を開く
===== 最終結果サマリー =====
Pattern | Train Acc | Val Acc | Test Acc | Train-Val Gap
------------------------------------------------------------
GAP前 | 0.6834 | 0.6750 | 0.6747 | 0.0084
GAP後 | 0.6893 | 0.6712 | 0.6646 | 0.0181
両方 | 0.6670 | 0.6761 | 0.6791 | -0.0091
------------------------------------------------------------
実験結果
精度・損失グラフ
結果サマリー
| パターン | train_accuracy | val_accuracy | test_accuracy | train-val gap(過学習度の目安) |
|---|---|---|---|---|
| A:GAP前 | 68.34% | 67.50% | 67.47% | 0.84pt |
| B:GAP後 | 68.93% | 67.12% | 66.46% | 1.81pt |
| C:両方 | 66.70% | 67.61% | 67.91% | -0.91pt |
test_accuracyは両方(67.91%)>GAP前(67.47%)>GAP後(66.46%)という順でした。train-val gapもGAP前(0.84pt)の方がGAP後(1.81pt)より小さく、両方に至ってはマイナス(-0.91pt)という逆転が起きています。これは事前の仮説(GAPの平均化でGAP前の正則化が弱まる/両方は二重正則化で精度が下がる)と逆の結果であり、考察で仮説を修正します。
ハマりポイント
- 標準のDropoutは「要素単位」でランダムに0にする。畳み込み層の出力(4次元テンソル)に対して
keras.layers.Dropoutを使うと、チャンネル全体ではなくピクセル単位でランダムに0になる。チャンネルごとまるごと落としたい場合はSpatialDropout2Dを使うのが定石だが、今回は「GAPとの位置関係」を純粋に比較する目的で、あえて標準のDropoutで統一している。 - GAP前のDropoutは、GAPの平均化によってノイズが弱まる可能性がある。8×8=64要素の平均を取る過程で、ランダムに0になった要素の影響が均されるため、同じ0.2という割合でもGAP後(128要素→そのまま次の層へ)ほど強い正則化にならない可能性がある。
- 「両方」は二重正則化になりうる。Dropout+Weight Decayの組み合わせで過学習抑制が効きすぎて精度が下がった既存記事の知見から類推すると、Dropout×2でも同様の過剰正則化が起きる可能性がある。
- functional APIでモデルを分岐させる際、DropoutをGAP前後どちらに挿入したか変数名だけでは分かりにくくなりがち。
model.summary()で層の並び順を都度確認するのが安全。
考察
① GAP前 vs GAP後:同じ割合でも効き方は違うか
結果は、記事冒頭で立てた「GAPの平均化がノイズを弱める」という仮説とは逆でした。GAP前(test_accuracy 67.47%、gap 0.84pt)の方が、GAP後(66.46%、gap 1.81pt)よりtest_accuracyが高く、過学習も抑えられていました。
考えられる説明は、「ノイズの平均化による弱まり」よりも「過学習の発生源に近い位置で効かせる効果」の方が支配的だったというものです。今回のモデルは、Conv層の出力(8×8×128=8192要素)というパラメータ数の多い特徴マップを経て、GAPで128次元まで一気に圧縮しています。過学習しやすいのは表現力の高いConv層側であり、GAP前のDropoutはまさにその位置に直接効きます。一方GAP後のDropoutは、すでに情報が圧縮された128次元のDense出力に効かせるため、Conv層側で起きた過学習を後から抑えることしかできず、効果が限定的だった可能性があります。
② 「両方」は二重正則化として精度を下げたか
結果はこちらも仮説と逆で、「両方」がtest_accuracy 67.91%と3パターン中もっとも高く、Weight Decayとの組み合わせで見られたような過剰正則化による精度低下は起きませんでした。GAP前・GAP後それぞれの正則化が異なる位置(Conv層側/Dense層側)に効いているため、単純な「同じ効果の重ね掛け」にはならず、むしろ相互補完的に働いたと考えられます。
ただし、train-val gapが-0.91ptとマイナスになっている点には注意が必要です。これは「両方が最も過学習していない」と単純には読めません。Kerasのfit()で表示されるtrain_accuracyはDropoutが有効な学習モードで計算され、val_accuracy/test_accuracyはDropoutを無効にした推論モードで計算されます。Dropoutを2箇所に入れると学習モード時のノイズが強くなり、train_accuracyが実力より低く表示されやすくなるため、gapがマイナスになったのはこの測定上の特性による部分が大きいと考えられます。
③ 過学習の抑制度(train-val gap)の違い
train-val gapはGAP後(1.81pt)>GAP前(0.84pt)>両方(-0.91pt)の順で、単純な数値だけ見ると「両方」が最も過学習していないように見えます。しかし②で触れた通り、この指標はDropoutが学習モードでtrain_accuracyを押し下げる影響を含むため、Dropout箇所が増えるほどgapが小さく(あるいはマイナスに)見えやすいという性質があります。過学習の実質的な抑制度を見るなら、train-val gapよりtest_accuracyそのものを主指標にする方が安全という教訓が今回得られました。その上でtest_accuracyを見ても、両方>GAP前>GAP後の順で、GAP前・両方の優位は変わりません。
④ 想定と逆の結果が出た理由と確認ポイント
今回はまさに「GAP前のDropoutがGAP後より強く効き、両方が単独パターンより良い結果」という、事前仮説とは逆のパターンが実際に起きました。原因は、①②で述べた通り「GAPの平均化によるノイズの弱まり」よりも「過学習の発生源(Conv層)に近い位置で効かせる効果」の方が支配的だったこと、および「両方」のtrain-val gapのマイナスは正則化の強さそのものではなくDropoutが学習モードのtrain_accuracyを押し下げる測定上の特性によるものと考えられます。同様の逆転が手元の実験で起きた場合は、以下を確認してください。
- train_accuracyとval_accuracyの算出条件(Dropoutの有無)を混同していないか。gapがマイナスの場合は「過学習していない」ではなく、この測定上の特性を疑う
- test_accuracy(推論モードでの評価)を主指標にして、train-val gapは補助的に見る
- Conv層の出力サイズ(今回は8×8×128)とDense層の出力サイズ(128)のどちらがより過学習しやすい構造かをモデルサイズの観点で確認する
| ケース | 実務での推奨 |
|---|---|
| 過学習をしっかり抑えたい | GAP後単体(Dense直前)よりGAP前(Conv出力直後)に置く方が、今回の実験では過学習抑制・精度の両面で有利だった。まずはGAP前を試す価値がある。 |
| 精度を最優先したい | 今回は「両方」がtest_accuracy最良(67.91%)だった。GAP前後どちらかで悩むより、両方に入れて様子を見る選択肢も検討に値する。 |
| train-val gapで過学習度を判断する場合 | Dropout箇所が増えるとtrain_accuracyが学習モードの影響で押し下げられ、gapが実態以上に小さく(マイナスに)見えることがある。test_accuracyも必ず併記して判断する。 |
| Conv層の特徴マップに正則化をかけたい | 標準DropoutよりSpatialDropout2Dの方がチャンネル単位の正則化として理論的に適している。別実験での比較が望ましい。 |
まとめ
CIFAR-10・Conv2D×2層のモデルでDropout(0.2)の位置を比較した結果、GAP前(Conv出力直後)に置く方がGAP後(Dense出力直後)よりtest_accuracyが高く、過学習も抑えられました(67.47% vs 66.46%)。「GAPの平均化でノイズが弱まる」という事前仮説とは逆に、過学習の発生源に近い位置で正則化をかける効果の方が大きかったと考えられます。また「両方」に入れても二重正則化による精度低下は起きず、むしろ3パターン中もっとも高いtest_accuracy(67.91%)になりました。ただしtrain-val gapがマイナスになった点は、Dropoutが学習モードのtrain_accuracyを押し下げる測定上の特性であり、「過学習していない」という意味には直結しない点に注意が必要です。
関連記事もあわせてどうぞ:
- Dropoutの割合による効果 → Dropoutの割合(0.0 vs 0.2 vs 0.5)を変えると過学習はどう変わる?【Keras×CIFAR-10実験】
- Weight Decayとの二重正則化 → Weight Decay(L2正則化)の強さで過学習はどう変わる?【Keras×CIFAR-10実験】
- GAP vs Flatten → GAP vs Flatten:CNN最終層の精度と速度をKerasで比較実験
- GAP vs GMP → GlobalAveragePooling vs GlobalMaxPooling どちらが精度・過学習耐性で優れるか?【Keras×CIFAR-10実験】



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