KerasでCNNを組むとき、BatchNormalizationをどこに入れるかで迷ったことはないでしょうか。
よく見るのは「Conv→BatchNormalization→ReLU」という並び順です。原論文や多くのチュートリアルもこの形を採用しています。一方で「Conv→ReLU→BatchNormalization」という順番を使っているコードも見かけます。理論上はConv→BN→ReLUが定石とされていますが、実際にどれくらい差が出るのかは動かしてみないとわかりません。今回はCIFAR-10でこの2パターンを同条件で比較します。
- BatchNormalizationを「活性化関数の前」に置く理論的根拠
- Conv→BN→ReLU と Conv→ReLU→BN の実装方法(Keras Functional API)
- CIFAR-10で実際にどちらが精度・過学習耐性で優れるか
- 実務でどちらを選ぶべきかの結論
BatchNormalizationの位置とは
BatchNormalizationは「内部共変量シフト(Internal Covariate Shift)」——各層への入力分布が学習中に変化し続け、学習を不安定にする現象——を抑えるために提案された手法です。この理屈に従えば、活性化関数に入力される前に分布を安定させる「Conv→BN→ReLU」が理にかなっています。
| 並び順 | 考え方 |
|---|---|
| Conv→BN→ReLU(従来型) | ReLUに入る前の分布を正規化することで、活性化関数の出力を安定させる |
| Conv→ReLU→BN | ReLU後の非線形な出力を正規化する。実装がシンプルという理由で使われることもある |
どちらの並び順でもエラーは出ず、学習も一見正常に進みます。差が出るとしても精度で数%以内のことが多く、既存コードのBN位置が理論通りかどうかを意識していない実装者も少なくありません。
実験コード
使用環境はGoogle Colab、データセットはCIFAR-10です。BatchNormalizationの位置以外は完全に同一条件にして、位置の影響だけを取り出します。
① 環境準備(最初に一度だけ実行)
!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 3 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 1s (6,911 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 118243 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 38.3 MB/s eta 0:00:00
Preparing metadata (setup.py) ... done
Building wheel for japanize_matplotlib (setup.py) ... done
環境準備完了
② import・データ準備・モデル定義
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt
import japanize_matplotlib
import time
SEED = 42
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
y_train = y_train.flatten()
y_test = y_test.flatten()
EPOCHS = 30
BATCH_SIZE = 64
def build_model(bn_position="before"):
"""
bn_position:
"before" -> Conv→BN→ReLU(従来型)
"after" -> Conv→ReLU→BN
"""
inputs = keras.Input(shape=(32, 32, 3))
x = inputs
for filters in [32, 64, 128]:
x = layers.Conv2D(filters, 3, padding="same")(x)
if bn_position == "before":
x = layers.BatchNormalization()(x)
x = layers.Activation("relu")(x)
else:
x = layers.Activation("relu")(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling2D()(x)
x = layers.GlobalAveragePooling2D()(x)
x = layers.Dense(128, activation="relu")(x)
x = layers.Dropout(0.3)(x)
outputs = layers.Dense(10, activation="softmax")(x)
model = keras.Model(inputs, outputs)
model.compile(
optimizer="adam",
loss="sparse_categorical_crossentropy",
metrics=["accuracy"],
)
return model
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 21s 0us/step
③ 2パターンの学習実行
configs = [
("before", "Conv→BN→ReLU"),
("after", "Conv→ReLU→BN"),
]
histories, times, scores = {}, {}, {}
for key, label in configs:
print(f"\n=== {label} ===")
tf.random.set_seed(SEED)
model = build_model(key)
start = time.time()
history = model.fit(
x_train, y_train,
validation_data=(x_test, y_test),
epochs=EPOCHS,
batch_size=BATCH_SIZE,
verbose=1,
)
elapsed = time.time() - start
score = model.evaluate(x_test, y_test, verbose=0)
histories[label] = history
times[label] = elapsed
scores[label] = score
print(f"学習時間:{elapsed:.1f}秒 test_accuracy:{score[1]:.4f}")
実行結果をクリックして内容を開く(実行後に差し替え)
=== Conv→BN→ReLU === Epoch 1/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 16s 12ms/step - accuracy: 0.4673 - loss: 1.4660 - val_accuracy: 0.4848 - val_loss: 1.4080 Epoch 2/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.5882 - loss: 1.1485 - val_accuracy: 0.4705 - val_loss: 1.5923 Epoch 3/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6361 - loss: 1.0191 - val_accuracy: 0.5673 - val_loss: 1.2393 Epoch 4/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6704 - loss: 0.9295 - val_accuracy: 0.4921 - val_loss: 1.5208 Epoch 5/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.6954 - loss: 0.8608 - val_accuracy: 0.5410 - val_loss: 1.3564 Epoch 6/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7188 - loss: 0.8020 - val_accuracy: 0.5439 - val_loss: 1.3481 Epoch 7/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7357 - loss: 0.7532 - val_accuracy: 0.6029 - val_loss: 1.1503 Epoch 8/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7541 - loss: 0.7091 - val_accuracy: 0.6159 - val_loss: 1.1556 Epoch 9/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7659 - loss: 0.6695 - val_accuracy: 0.5603 - val_loss: 1.3602 Epoch 10/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7807 - loss: 0.6342 - val_accuracy: 0.6059 - val_loss: 1.1472 Epoch 11/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7915 - loss: 0.5997 - val_accuracy: 0.5930 - val_loss: 1.2974 Epoch 12/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8041 - loss: 0.5631 - val_accuracy: 0.6562 - val_loss: 1.0121 Epoch 13/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8139 - loss: 0.5372 - val_accuracy: 0.6374 - val_loss: 1.0951 Epoch 14/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8243 - loss: 0.5060 - val_accuracy: 0.6377 - val_loss: 1.0912 Epoch 15/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8330 - loss: 0.4812 - val_accuracy: 0.6381 - val_loss: 1.1516 Epoch 16/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8425 - loss: 0.4549 - val_accuracy: 0.6181 - val_loss: 1.3041 Epoch 17/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8517 - loss: 0.4292 - val_accuracy: 0.6865 - val_loss: 0.9805 Epoch 18/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8568 - loss: 0.4109 - val_accuracy: 0.6478 - val_loss: 1.1632 Epoch 19/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8627 - loss: 0.3897 - val_accuracy: 0.6354 - val_loss: 1.3315 Epoch 20/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.8715 - loss: 0.3630 - val_accuracy: 0.6545 - val_loss: 1.2378 Epoch 21/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8775 - loss: 0.3499 - val_accuracy: 0.6279 - val_loss: 1.3489 Epoch 22/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8839 - loss: 0.3304 - val_accuracy: 0.6870 - val_loss: 1.0859 Epoch 23/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8895 - loss: 0.3130 - val_accuracy: 0.5899 - val_loss: 1.6674 Epoch 24/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8939 - loss: 0.3010 - val_accuracy: 0.6022 - val_loss: 1.7114 Epoch 25/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8972 - loss: 0.2898 - val_accuracy: 0.5931 - val_loss: 1.6473 Epoch 26/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9032 - loss: 0.2728 - val_accuracy: 0.6250 - val_loss: 1.5257 Epoch 27/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9070 - loss: 0.2595 - val_accuracy: 0.6785 - val_loss: 1.3018 Epoch 28/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9107 - loss: 0.2469 - val_accuracy: 0.5977 - val_loss: 1.8090 Epoch 29/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9144 - loss: 0.2411 - val_accuracy: 0.6122 - val_loss: 1.8161 Epoch 30/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9176 - loss: 0.2295 - val_accuracy: 0.6365 - val_loss: 1.5774 学習時間:145.6秒 test_accuracy:0.6365 === Conv→ReLU→BN === Epoch 1/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 14s 11ms/step - accuracy: 0.4924 - loss: 1.3991 - val_accuracy: 0.5156 - val_loss: 1.4062 Epoch 2/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6373 - loss: 1.0162 - val_accuracy: 0.5166 - val_loss: 1.5203 Epoch 3/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7014 - loss: 0.8489 - val_accuracy: 0.6756 - val_loss: 0.9284 Epoch 4/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.7413 - loss: 0.7420 - val_accuracy: 0.6774 - val_loss: 0.9623 Epoch 5/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.7716 - loss: 0.6613 - val_accuracy: 0.6887 - val_loss: 0.9359 Epoch 6/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.7934 - loss: 0.5944 - val_accuracy: 0.6676 - val_loss: 1.0223 Epoch 7/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8117 - loss: 0.5397 - val_accuracy: 0.6766 - val_loss: 1.0708 Epoch 8/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.8274 - loss: 0.4950 - val_accuracy: 0.6783 - val_loss: 1.1244 Epoch 9/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8423 - loss: 0.4504 - val_accuracy: 0.6823 - val_loss: 1.1053 Epoch 10/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8555 - loss: 0.4091 - val_accuracy: 0.7077 - val_loss: 0.9943 Epoch 11/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8681 - loss: 0.3734 - val_accuracy: 0.7097 - val_loss: 0.9926 Epoch 12/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8764 - loss: 0.3465 - val_accuracy: 0.7203 - val_loss: 1.0085 Epoch 13/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.8869 - loss: 0.3203 - val_accuracy: 0.7010 - val_loss: 1.1772 Epoch 14/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.8935 - loss: 0.2949 - val_accuracy: 0.6655 - val_loss: 1.3933 Epoch 15/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9016 - loss: 0.2723 - val_accuracy: 0.6290 - val_loss: 1.7875 Epoch 16/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9082 - loss: 0.2542 - val_accuracy: 0.7238 - val_loss: 1.1941 Epoch 17/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9110 - loss: 0.2437 - val_accuracy: 0.7068 - val_loss: 1.2764 Epoch 18/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9164 - loss: 0.2291 - val_accuracy: 0.6742 - val_loss: 1.5924 Epoch 19/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9224 - loss: 0.2123 - val_accuracy: 0.7239 - val_loss: 1.2381 Epoch 20/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9279 - loss: 0.1984 - val_accuracy: 0.7167 - val_loss: 1.4438 Epoch 21/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9295 - loss: 0.1957 - val_accuracy: 0.7266 - val_loss: 1.3196 Epoch 22/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9353 - loss: 0.1780 - val_accuracy: 0.7040 - val_loss: 1.5654 Epoch 23/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9381 - loss: 0.1703 - val_accuracy: 0.7243 - val_loss: 1.3757 Epoch 24/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9387 - loss: 0.1704 - val_accuracy: 0.6951 - val_loss: 1.6370 Epoch 25/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.9469 - loss: 0.1479 - val_accuracy: 0.7211 - val_loss: 1.5619 Epoch 26/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9467 - loss: 0.1486 - val_accuracy: 0.7069 - val_loss: 1.5639 Epoch 27/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9487 - loss: 0.1426 - val_accuracy: 0.7009 - val_loss: 1.6734 Epoch 28/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9528 - loss: 0.1329 - val_accuracy: 0.6962 - val_loss: 1.7242 Epoch 29/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 4s 5ms/step - accuracy: 0.9533 - loss: 0.1289 - val_accuracy: 0.7109 - val_loss: 1.6523 Epoch 30/30 782/782 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.9526 - loss: 0.1324 - val_accuracy: 0.6864 - val_loss: 1.9557 学習時間:144.4秒 test_accuracy:0.6864
④ グラフ+サマリー出力
import pandas as pd
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("bn_position_val.png", dpi=150)
plt.show()
rows = []
for label in ["Conv→BN→ReLU", "Conv→ReLU→BN"]:
val_acc = histories[label].history["val_accuracy"][-1]
test_acc = scores[label][1]
train_acc = histories[label].history["accuracy"][-1]
gap = train_acc - val_acc
rows.append({
"パターン": label,
"val_accuracy": f"{val_acc:.4f}",
"test_accuracy": f"{test_acc:.4f}",
"train-val gap": f"{gap:.4f}",
"学習時間(s)": f"{times[label]:.1f}",
})
df = pd.DataFrame(rows)
print(df.to_string(index=False))
最終結果サマリー
パターン val_accuracy test_accuracy train-val gap 学習時間(s) Conv→BN→ReLU 0.6365 0.6365 0.2811 145.6 Conv→ReLU→BN 0.6864 0.6864 0.2662 144.4
実験結果
精度・損失グラフ
精度グラフ
損失グラフ
model.fitのvalidation_dataにテストセットをそのまま使用しているため、val_accuracyとtest_accuracyの値は一致しています。学習中の進捗確認用データとしてテストセットを使う簡易的な構成である点にご留意ください。
| パターン | val_accuracy | test_accuracy | train-val gap | 学習時間 |
|---|---|---|---|---|
| Conv→BN→ReLU(従来型) | 0.6365 | 0.6365 | 0.2811 | 145.6秒 |
| Conv→ReLU→BN | 0.6864 | 0.6864 | 0.2662 | 144.4秒 |
考察
① 精度差はどの程度だったか——理論の定石が逆転
test_accuracyでConv→ReLU→BNが0.6864、Conv→BN→ReLUが0.6365となり、その差は約5.0ptでした。乱数シードのブレでは説明しにくい、明確な差です。「Conv→BN→ReLUが定石」という一般的な理解とは逆の結果になりました。
② 学習序盤の収束スピード——ReLU→BNが明確に早い
エポックごとのval_accuracyを見ると、差は学習の早い段階から出ています。
| エポック | Conv→BN→ReLU | Conv→ReLU→BN |
|---|---|---|
| 3 | 0.5673 | 0.6756 |
| 5 | 0.5410 | 0.6887 |
| 10 | 0.6059 | 0.7077 |
Conv→ReLU→BNはエポック3の時点で早くも67.6%に到達しているのに対し、Conv→BN→ReLUは同じタイミングで56.7%にとどまっています。エポック10でもその差は10pt前後開いたままで、学習全体を通じてConv→ReLU→BNが優位に推移しました。
③ 過学習耐性(train-val gap)——僅差だがReLU→BNがやや優位
最終エポックのtrain-val gap(train_accuracy − val_accuracy)は、Conv→BN→ReLUが0.2811、Conv→ReLU→BNが0.2662でした。Conv→ReLU→BNの方がtrain_accuracy自体は高い(0.9526 vs 0.9176)にもかかわらず、gapはむしろ小さく収まっています。どちらのパターンも30エポックではかなり過学習している点は共通しており、位置の違いだけで過学習を大きく抑えられるわけではなさそうです。
なお両パターンとも、学習後半でval_lossがtrain_lossから大きく乖離し始めています(Conv→BN→ReLUは最終エポックでtrain_loss 0.2295に対しval_loss 1.5774、Conv→ReLU→BNはtrain_loss 0.1324に対しval_loss 1.9557)。この規模の乖離は、BN位置の調整だけでなく、Dropout強化やEarlyStopping・Data Augmentationの併用も合わせて検討すべきサインです。
④ 理論と実験結果は一致しなかった——なぜか
内部共変量シフトの理論に従えば「ReLUに入る前に正規化するConv→BN→ReLU」の方が有利なはずですが、今回はその逆の結果になりました。考えられる要因はいくつかあります。
- ReLU後の分布は非負に偏っている:Conv→ReLU→BNでは、常に0以上の値を正規化することになります。この非負分布に対するBatchNormalizationの挙動が、今回のネットワーク規模・Optimizer(Adam)との組み合わせでたまたま良く噛み合った可能性があります。
- 単一シードの結果である:今回はSEED=42で1回のみの実行です。複数シードで平均を取ると差が縮まる、あるいは逆転する可能性は否定できません。
- 学習率スケジュールなしのAdam:学習後半のval_loss急上昇(過学習)が両パターンで見られることから、今回の設定自体が「過学習しやすい条件」になっている可能性があります。BN位置の効果が、この過学習ノイズに埋もれている可能性も考えられます。
「Conv→BN→ReLUが常に正解」と鵜呑みにせず、手元のデータ・モデル規模で実際に試す価値があることを示す結果と言えそうです。
実務での推奨
| 状況 | 推奨 | 理由 |
|---|---|---|
| 新規にモデルを設計する場合 | 両方試す価値あり。特にConv→ReLU→BNも選択肢に | 今回の結果ではConv→ReLU→BNが精度・収束速度の両面で優位だった |
| 既存コードがConv→BN→ReLUの場合 | 無理に書き換える必要はない | 差は条件依存。理論的裏付けのある定石を崩してまで変える根拠は弱い |
| 過学習が問題になっている場合 | BN位置の変更だけに頼らない | 両パターンとも30エポックで大きく過学習しており、Dropout強化・EarlyStopping・Data Augmentationの併用が本質的な対策になる |
- test_accuracyはConv→ReLU→BNが0.6864、Conv→BN→ReLUが0.6365で、約5.0ptの差がついた。一般的な定石(Conv→BN→ReLU)とは逆の結果
- 収束スピードもConv→ReLU→BNが明確に早く、エポック3時点で67.6%(Conv→BN→ReLUは56.7%)に到達
- train-val gapはConv→ReLU→BNの方がわずかに小さく(0.2662 vs 0.2811)、過学習耐性でもやや優位。ただし両パターンとも30エポックでは過学習が進んでおり、位置の違いだけで解決する規模ではない
- 「BNはConv直後・活性化関数の前」という定石は今回のCIFAR-10・Adam・30エポックという条件では支持されなかった。手元の環境で実際に試す価値があることを示す結果
- 今回は単一シードでの検証のため、複数シードでの再現性確認や、学習率スケジュール・Data Augmentation併用時の傾向は今後の検証課題
English Summary
We compared two BatchNormalization placements on CIFAR-10 over 30 epochs: Conv→BN→ReLU (the conventional order) and Conv→ReLU→BN. Contrary to the common wisdom that BN should precede the activation function, Conv→ReLU→BN outperformed Conv→BN→ReLU by roughly 5.0 points in test accuracy (68.6% vs 63.7%) and converged noticeably faster, reaching 67.6% validation accuracy by epoch 3 versus 56.7% for the conventional order. The train-val gap was also slightly smaller for Conv→ReLU→BN. Both configurations showed substantial overfitting by epoch 30, suggesting that BN placement alone is not a substitute for additional regularization such as Dropout, EarlyStopping, or data augmentation. As this result is based on a single random seed, further verification across multiple seeds is recommended before generalizing.
関連記事
- BatchNormalizationとは → BatchNormalizationとは?Kerasでの使い方・位置・効果を解説
- BatchNormalization×Dropout併用比較 → BatchNormalizationはDropoutと併用すべきか?Keras×MNISTで4パターン比較
- 画像の正規化方法比較 → 画像の正規化方法で精度は変わる?/255・BatchNorm・LayerNormをCIFAR-10で比較



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