Augmentationと正規化、順番で精度は変わる?【Keras×CIFAR-10実験】

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

CIFAR-10 CNN Data Augmentation Google Colab Keras 画像分類 前処理

X f B! P L
Augmentationと正規化、順番で精度は変わる?【Keras×CIFAR-10実験】 アイキャッチ画像

Data Augmentationと正規化(/255)、どちらを先に書くか——意識したことはありましたか?

「なんとなく正規化を先に書いている」「Augmentationが先のサンプルを見た気がする」。コードを書く中でこんな疑問を抱えたことがある方も多いはずです。今回はGoogle ColabとCIFAR-10を使い、処理の順番(Augmentation→正規化 vs 正規化→Augmentation)が精度に影響するかどうかを実験で確認します。

なお、正規化方法の違い(/255 vs BatchNormalization vs LayerNormalization)については → 正規化方法の比較(/255 vs BatchNormalization vs LayerNormalization)【Keras×CIFAR-10実験】 をご覧ください。本記事は「処理順番」に絞ってピンポイントで検証します。

📘 この記事でわかること
  • AugmentationをRescalingの前後どちらに置くかで精度が変わるかどうか
  • 処理順番の違いがモデルに与える影響の考え方
  • Kerasで前処理パイプラインを組む際の正しい書き方

なぜ順番が気になるのか

Kerasでデータ拡張を行う場合、よく使われる構成が2通りあります。

パターンA:Augmentation → 正規化

augmentation = keras.Sequential([
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomRotation(0.1),
    keras.layers.RandomZoom(0.1),
    keras.layers.Rescaling(1./255),   # 最後に正規化
])

パターンB:正規化 → Augmentation

augmentation = keras.Sequential([
    keras.layers.Rescaling(1./255),   # 最初に正規化
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomRotation(0.1),
    keras.layers.RandomZoom(0.1),
])

「どちらが正しいか」は検索してもはっきりした答えが見つかりにくいポイントです。数学的には「Augmentation(アフィン変換)と線形スケーリングは可換なはずでは?」という議論もあります。実際に実験して確かめましょう。

⚠️ ハマりポイント
ランダム系のAugmentation(RandomRotation, RandomZoomなど)はピクセル値の補間処理を行います。補間後の値が 0〜255 のまま整数として扱われると、Rescaling(1./255)後の結果と、先に正規化してから補間した場合とでは数値的に微妙に異なる可能性があります。また、RandomZoomなどは境界を「0埋め」(fill_mode='reflect' or 'constant')しますが、正規化前は0が「黒」、正規化後は0が「平均から大きく離れた値」として機能する場合があります。このあたりが順番の議論の核心です。

実験コード

使用環境は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 100 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.8 MB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122402 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 56.6 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 numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import time

# ── データ読み込み(正規化はAugmentationレイヤーで行うためここでは整数のまま)
(x_train_raw, y_train), (x_test_raw, y_test) = keras.datasets.cifar10.load_data()
x_train_raw = x_train_raw.astype('float32')   # float32に変換するだけ(/255はしない)
x_test_raw  = x_test_raw.astype('float32')

# ── パターンA:Augmentation → 正規化
aug_A = keras.Sequential([
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomRotation(0.1),
    keras.layers.RandomZoom(0.1),
    keras.layers.Rescaling(1./255),        # 最後に正規化
], name='aug_A')

# ── パターンB:正規化 → Augmentation
aug_B = keras.Sequential([
    keras.layers.Rescaling(1./255),        # 最初に正規化
    keras.layers.RandomFlip("horizontal"),
    keras.layers.RandomRotation(0.1),
    keras.layers.RandomZoom(0.1),
], name='aug_B')

# ── パターンC:Augmentationなし・正規化のみ(ベースライン)
aug_C = keras.Sequential([
    keras.layers.Rescaling(1./255),
], name='aug_C_baseline')

def build_model(aug_layer, name):
    inputs = keras.Input(shape=(32, 32, 3))
    x = aug_layer(inputs)
    x = keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same')(x)
    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)
    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=name)

def compile_and_fit(model, x_train, y_train):
    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 ━━━━━━━━━━━━━━━━━━━━ 6s 0us/step

3パターンの学習実行

configs = [
    (aug_A, 'A_aug_then_norm'),
    (aug_B, 'B_norm_then_aug'),
    (aug_C, 'C_baseline_noaug'),
]
histories, times, scores = {}, {}, {}

for aug_layer, name in configs:
    print(f"\n=== {name} ===")
    model = build_model(aug_layer, name)
    print(model.summary())
    h, t = compile_and_fit(model, x_train_raw, y_train)
    s = model.evaluate(x_test_raw, y_test, verbose=0)
    label = name.split('_')[0]
    histories[label] = h
    times[label] = t
    scores[label] = s
    print(f"学習時間:{t:.1f}秒 test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_aug_then_norm ===
Model: "A_aug_then_norm"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer (InputLayer)        │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ aug_A (Sequential)              │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ 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 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout)               │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ 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 ━━━━━━━━━━━━━━━━━━━━ 12s 9ms/step - accuracy: 0.2464 - loss: 1.9902 - val_accuracy: 0.2760 - val_loss: 1.9800
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.3379 - loss: 1.7670 - val_accuracy: 0.3873 - val_loss: 1.6749
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.3912 - loss: 1.6571 - val_accuracy: 0.4278 - val_loss: 1.5600
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4245 - loss: 1.5706 - val_accuracy: 0.4556 - val_loss: 1.4792
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4494 - loss: 1.5139 - val_accuracy: 0.4529 - val_loss: 1.5148
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 13s 13ms/step - accuracy: 0.4654 - loss: 1.4671 - val_accuracy: 0.4697 - val_loss: 1.4714
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 7s 8ms/step - accuracy: 0.4760 - loss: 1.4446 - val_accuracy: 0.4880 - val_loss: 1.4128
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4848 - loss: 1.4201 - val_accuracy: 0.4919 - val_loss: 1.3839
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4961 - loss: 1.3870 - val_accuracy: 0.4924 - val_loss: 1.3892
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5011 - loss: 1.3686 - val_accuracy: 0.5101 - val_loss: 1.3582
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5094 - loss: 1.3494 - val_accuracy: 0.4778 - val_loss: 1.4770
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5160 - loss: 1.3377 - val_accuracy: 0.5037 - val_loss: 1.3710
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5231 - loss: 1.3176 - val_accuracy: 0.5378 - val_loss: 1.2772
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5252 - loss: 1.3033 - val_accuracy: 0.5086 - val_loss: 1.3922
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5328 - loss: 1.2902 - val_accuracy: 0.5386 - val_loss: 1.2801
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 9ms/step - accuracy: 0.5362 - loss: 1.2818 - val_accuracy: 0.5580 - val_loss: 1.2278
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5383 - loss: 1.2739 - val_accuracy: 0.5725 - val_loss: 1.1890
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5477 - loss: 1.2505 - val_accuracy: 0.5697 - val_loss: 1.2109
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5475 - loss: 1.2469 - val_accuracy: 0.5526 - val_loss: 1.2464
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5552 - loss: 1.2316 - val_accuracy: 0.5592 - val_loss: 1.2104
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5562 - loss: 1.2265 - val_accuracy: 0.5744 - val_loss: 1.1960
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5601 - loss: 1.2124 - val_accuracy: 0.5818 - val_loss: 1.1637
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5641 - loss: 1.2050 - val_accuracy: 0.5584 - val_loss: 1.2694
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 9ms/step - accuracy: 0.5684 - loss: 1.1998 - val_accuracy: 0.5812 - val_loss: 1.1798
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.5728 - loss: 1.1837 - val_accuracy: 0.5860 - val_loss: 1.1742
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5751 - loss: 1.1769 - val_accuracy: 0.5703 - val_loss: 1.2252
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.5791 - loss: 1.1701 - val_accuracy: 0.5767 - val_loss: 1.1934
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5822 - loss: 1.1589 - val_accuracy: 0.6047 - val_loss: 1.1148
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5869 - loss: 1.1492 - val_accuracy: 0.5857 - val_loss: 1.1830
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5891 - loss: 1.1471 - val_accuracy: 0.5956 - val_loss: 1.1381
学習時間:192.1秒 test_accuracy:0.5956

=== B_norm_then_aug ===
Model: "B_norm_then_aug"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer_2 (InputLayer)      │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ aug_B (Sequential)              │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_2 (Conv2D)               │ (None, 32, 32, 64)     │         1,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_2 (MaxPooling2D)  │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_3 (Conv2D)               │ (None, 16, 16, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_3 (MaxPooling2D)  │ (None, 8, 8, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d_1      │ (None, 128)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_2 (Dense)                 │ (None, 128)            │        16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_1 (Dropout)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ 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 9ms/step - accuracy: 0.2490 - loss: 1.9841 - val_accuracy: 0.3232 - val_loss: 1.8137
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.3307 - loss: 1.7771 - val_accuracy: 0.3765 - val_loss: 1.6628
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.3768 - loss: 1.6820 - val_accuracy: 0.4004 - val_loss: 1.6306
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4058 - loss: 1.6191 - val_accuracy: 0.4223 - val_loss: 1.6089
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4283 - loss: 1.5554 - val_accuracy: 0.4627 - val_loss: 1.4820
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.4482 - loss: 1.5103 - val_accuracy: 0.4775 - val_loss: 1.4507
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.4626 - loss: 1.4637 - val_accuracy: 0.4988 - val_loss: 1.3749
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4767 - loss: 1.4410 - val_accuracy: 0.4925 - val_loss: 1.3916
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4834 - loss: 1.4136 - val_accuracy: 0.4931 - val_loss: 1.4117
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4944 - loss: 1.3913 - val_accuracy: 0.4958 - val_loss: 1.3867
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5037 - loss: 1.3678 - val_accuracy: 0.5318 - val_loss: 1.2893
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.5047 - loss: 1.3606 - val_accuracy: 0.5193 - val_loss: 1.3419
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5118 - loss: 1.3452 - val_accuracy: 0.5399 - val_loss: 1.2864
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5181 - loss: 1.3226 - val_accuracy: 0.5305 - val_loss: 1.2952
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5229 - loss: 1.3115 - val_accuracy: 0.5442 - val_loss: 1.2521
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5276 - loss: 1.2979 - val_accuracy: 0.5328 - val_loss: 1.2959
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5332 - loss: 1.2851 - val_accuracy: 0.5485 - val_loss: 1.2694
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5394 - loss: 1.2730 - val_accuracy: 0.5415 - val_loss: 1.2694
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5412 - loss: 1.2637 - val_accuracy: 0.5635 - val_loss: 1.2198
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5500 - loss: 1.2496 - val_accuracy: 0.5640 - val_loss: 1.2405
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5523 - loss: 1.2393 - val_accuracy: 0.5705 - val_loss: 1.2021
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5581 - loss: 1.2245 - val_accuracy: 0.5842 - val_loss: 1.1659
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 9ms/step - accuracy: 0.5603 - loss: 1.2200 - val_accuracy: 0.5902 - val_loss: 1.1400
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5613 - loss: 1.2123 - val_accuracy: 0.5877 - val_loss: 1.1749
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5653 - loss: 1.2022 - val_accuracy: 0.5720 - val_loss: 1.1969
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5669 - loss: 1.1968 - val_accuracy: 0.5888 - val_loss: 1.1552
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5739 - loss: 1.1848 - val_accuracy: 0.5843 - val_loss: 1.1631
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5754 - loss: 1.1803 - val_accuracy: 0.5869 - val_loss: 1.1413
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5795 - loss: 1.1692 - val_accuracy: 0.5940 - val_loss: 1.1378
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5839 - loss: 1.1616 - val_accuracy: 0.5729 - val_loss: 1.2397
学習時間:173.1秒 test_accuracy:0.5636

=== C_baseline_noaug ===
Model: "C_baseline_noaug"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type)                    ┃ Output Shape           ┃       Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ input_layer_4 (InputLayer)      │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ aug_C_baseline (Sequential)     │ (None, 32, 32, 3)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_4 (Conv2D)               │ (None, 32, 32, 64)     │         1,792 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_4 (MaxPooling2D)  │ (None, 16, 16, 64)     │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_5 (Conv2D)               │ (None, 16, 16, 128)    │        73,856 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_5 (MaxPooling2D)  │ (None, 8, 8, 128)      │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ global_average_pooling2d_2      │ (None, 128)            │             0 │
│ (GlobalAveragePooling2D)        │                        │               │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_4 (Dense)                 │ (None, 128)            │        16,512 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_2 (Dropout)             │ (None, 128)            │             0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ 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 ━━━━━━━━━━━━━━━━━━━━ 9s 9ms/step - accuracy: 0.2624 - loss: 1.9279 - val_accuracy: 0.3365 - val_loss: 1.7242
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3669 - loss: 1.6726 - val_accuracy: 0.4034 - val_loss: 1.5930
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4295 - loss: 1.5445 - val_accuracy: 0.4671 - val_loss: 1.4731
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4681 - loss: 1.4491 - val_accuracy: 0.4985 - val_loss: 1.3751
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4952 - loss: 1.3758 - val_accuracy: 0.5089 - val_loss: 1.3290
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5122 - loss: 1.3356 - val_accuracy: 0.5291 - val_loss: 1.2856
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5239 - loss: 1.3035 - val_accuracy: 0.5270 - val_loss: 1.2950
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5394 - loss: 1.2609 - val_accuracy: 0.5457 - val_loss: 1.2333
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5525 - loss: 1.2340 - val_accuracy: 0.5669 - val_loss: 1.1978
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5597 - loss: 1.2106 - val_accuracy: 0.5650 - val_loss: 1.1856
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5690 - loss: 1.1839 - val_accuracy: 0.5828 - val_loss: 1.1456
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5744 - loss: 1.1682 - val_accuracy: 0.5736 - val_loss: 1.1848
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5845 - loss: 1.1458 - val_accuracy: 0.5884 - val_loss: 1.1284
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5932 - loss: 1.1240 - val_accuracy: 0.6012 - val_loss: 1.0955
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6015 - loss: 1.1075 - val_accuracy: 0.5955 - val_loss: 1.1195
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6056 - loss: 1.0906 - val_accuracy: 0.6081 - val_loss: 1.0828
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6140 - loss: 1.0732 - val_accuracy: 0.6109 - val_loss: 1.0823
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6203 - loss: 1.0535 - val_accuracy: 0.6078 - val_loss: 1.0726
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6279 - loss: 1.0375 - val_accuracy: 0.6327 - val_loss: 1.0153
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6339 - loss: 1.0194 - val_accuracy: 0.6437 - val_loss: 1.0029
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6356 - loss: 1.0122 - val_accuracy: 0.6349 - val_loss: 1.0111
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6422 - loss: 0.9959 - val_accuracy: 0.6490 - val_loss: 0.9840
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6461 - loss: 0.9818 - val_accuracy: 0.6459 - val_loss: 0.9869
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6510 - loss: 0.9697 - val_accuracy: 0.6577 - val_loss: 0.9631
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6564 - loss: 0.9626 - val_accuracy: 0.6579 - val_loss: 0.9617
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6598 - loss: 0.9484 - val_accuracy: 0.6547 - val_loss: 0.9611
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6645 - loss: 0.9339 - val_accuracy: 0.6683 - val_loss: 0.9353
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6668 - loss: 0.9255 - val_accuracy: 0.6627 - val_loss: 0.9452
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6695 - loss: 0.9172 - val_accuracy: 0.6777 - val_loss: 0.9160
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6798 - loss: 0.9032 - val_accuracy: 0.6575 - val_loss: 0.9561
学習時間:122.5秒 test_accuracy:0.6538

グラフ+サマリー

# ── val_accuracy / val_loss 比較グラフ ───────────────
label_map = {'A': 'A: Aug→正規化', 'B': 'B: 正規化→Aug', 'C': 'C: ベースライン'}
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
for key, h in histories.items():
    axes[0].plot(h.history['val_accuracy'], label=label_map[key])
    axes[1].plot(h.history['val_loss'],     label=label_map[key])
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('aug_order_comparison.png', dpi=150)
plt.show()

# ── train vs val loss(過学習の乖離)────────────
fig2, axes2 = plt.subplots(3, 1, figsize=(7, 14))
for i, (key, 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_map[key]}')
    axes2[i].set_xlabel('Epoch'); axes2[i].legend(); axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('aug_order_overfit.png', dpi=150)
plt.show()

print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>3} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8}")
print("-" * 42)
for key in ['A', 'B', 'C']:
    val_acc  = histories[key].history['val_accuracy'][-1]
    test_acc = scores[key][1]
    t        = times[key]
    print(f"{key:>3} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f}")
print("-" * 42)

最終結果サマリー

===== 最終結果サマリー =====
Pattern |  Val Acc |  Test Acc |  Time(s)
------------------------------------------
  A |   0.5956 |    0.5956 |    192.1
  B |   0.5729 |    0.5636 |    173.1
  C |   0.6575 |    0.6538 |    122.5
------------------------------------------

実験結果

精度グラフ

精度グラフ

損失グラフ

損失グラフ

A: Aug→正規化

A: Aug→正規化

B: 正規化→Aug

B: 正規化→Aug

C: ベースライン

C: ベースライン
パターン 最終 val_accuracy 最終 test_accuracy 学習時間
A:Augmentation → 正規化 59.56% 59.56% 192.1秒
B:正規化 → Augmentation 57.29% 56.36% 173.1秒
C:ベースライン(Augなし) 65.75% 65.38% 122.5秒

考察

① 予想外の結果:Augmentationありが最下位だった

今回の実験で最も驚くべき結果は、Augmentationなしのベースライン(C)が最高精度65.38%を記録し、Augmentationありの A(59.56%)・B(56.36%)を大きく上回ったことです。

比較 test_accuracy の差
C(Augなし)vs A(Aug→正規化) +5.82% ベースラインが上
C(Augなし)vs B(正規化→Aug) +9.02% ベースラインが上

「Augmentationは汎化性能を上げる」という一般的な知識とは逆の結果です。これはAugmentationが悪いのではなく、今回の設定(エポック数30・強度設定)が不適切だった可能性が高いです。

② Augmentationが逆効果になる理由

Augmentationは学習データを「難しくする」処理です。30エポックという短い学習では、モデルが変換後の多様な画像に十分適応しきれず、精度が伸び悩んだと考えられます。Augmentationの効果を引き出すには、一般的により多くのエポック数(50〜100以上)が必要です。

また、RandomRotation(0.1)・RandomZoom(0.1)の組み合わせが CIFAR-10(32×32の小さな画像)に対してやや強すぎた可能性もあります。32×32の画像をズームすると重要な特徴が失われやすく、ノイズとして機能してしまいます。

③ A vs B:順番の影響は約3%の差

本来の実験テーマである「AとBの比較」では、A(59.56%)がB(56.36%)を約3.2%上回りました。「どちらでも同じはず」という事前予測に反して、無視できない差が出ています。

原因として考えられるのは fill_mode の挙動です。RandomRotation・RandomZoomは画像外にはみ出た領域を 0 で埋めます(fill_mode='reflect' がデフォルトですが実装依存あり)。正規化前(0〜255スケール)に0埋めされた場合、0は「真っ黒」として自然な境界になります。一方、正規化後(0〜1スケール)に幾何学変換が走る場合も同様ですが、内部の補間精度や実装の微差が積み重なった結果、Aの方がわずかに有利に働いたと推測されます。

fill_mode='constant'(0埋め)で標準化(平均\(\mu\)・標準偏差\(\sigma\))を使う場合:
正規化後の境界値 \(= \frac{0 - \mu}{\sigma} \neq 0\) ← 0が「黒」ではなくなる

④ 学習時間の差:Augmentationはコストがかかる

AはCより約70秒(57%増)、BはCより約50秒(41%増)の学習時間がかかっています。Augmentationの変換処理はGPUのデータパイプライン上でエポックごとに実行されるため、学習時間への影響は無視できません。精度が下がって時間も増えるという結果は、Augmentationの設定見直しが必要であることを示しています。

まとめ
  • 「Augmentation → 正規化(A)」と「正規化 → Augmentation(B)」では、Aが約3%高い精度となった。完全に等価ではないことが実験で示された
  • 一方で最大の発見はAugmentationなし(C)が最高精度だったこと。エポック数30ではAugmentationの効果が出きらず逆効果になった
  • CIFAR-10のような小さな画像にはAugmentationの強度を抑えるか、エポック数を増やすことが必要
  • 処理順番の安全な書き方としては「Augmentation → 正規化(パターンA)」が推奨。生の画像(0〜255)に幾何学変換をかけ、最後にスケールを揃える流れが直感的で数値的にも安定しやすい
  • fill_mode='constant' + 標準化(StandardScaler等)を使う場合は順番が特に重要になる

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