Batch Normalizationは学習にどんな影響がある?MNISTで比較検証【TensorFlow/Keras・Colab対応】

2025年9月8日月曜日

BatchNormalization Google Colab Keras MNIST

X f B! P L

この記事では、Batch Normalization(BN)あり/なしで構成が同じCNNをMNISTで学習させ、収束速度・安定性・汎化への影響を比べます。Colabでそのまま動くコード付き。

Batch Normalization解説記事のアイキャッチ画像(イメージ)

✅ モデル設計や実装レビューの相談はココナラ へ(外部リンク)

Batch Normalizationとは

Batch Normalization(BN)は、ミニバッチごとに中間表現を正規化し、学習を安定させる手法です。各層の出力を平均0・分散1へスケーリングしつつ、学習可能なパラメータ gamma(スケール)と beta(シフト)で表現力を保ちます。

なぜ効く?—直感的な理解

  • 勾配の爆発/消失の緩和: 入力分布のスケールが整うため、勾配が安定しやすい。
  • 収束の加速: 学習率を少し高めにしても壊れにくく、同じエポック数でも到達精度が上がりやすい。
  • わずかな正則化効果: ミニバッチ統計に由来するノイズが入ることで過学習が抑制される場合がある。

実務TIP
Conv層では Conv → BatchNorm → ReLU の順がよく使われます。
Dense層でも Dense → BatchNorm → ReLU が一般的です。

実験設定(MNIST・CNN)

  • データ: MNIST(28×28グレースケール・10クラス)
  • モデル: 同一アーキテクチャで BNありBNなし を比較
  • 評価: トレーニング/検証の精度とロスの推移、最終テスト精度
  • 実行環境: Google Colab + TensorFlow/Keras(CPUでも数分)

🔧 学習率・BN配置のチューニング相談はココナラ へ(外部リンク)

Colabで実行:BNあり/なしを比較

以下のコードをColabに貼り付ければ、そのまま実験できます(ランダム性により結果は毎回少し変わります)。

# %% Colab-ready: TensorFlow/Keras MNIST BN vs No-BN
import os, random, numpy as np, tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models

# 再現性(目安)
seed = 42
random.seed(seed); np.random.seed(seed); tf.random.set_seed(seed)

# 1) データセット読み込み
(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = (x_train.astype("float32") / 255.0)[..., None]  # (60000,28,28,1)
x_test  = (x_test.astype("float32")  / 255.0)[..., None]

# 検証分割
x_train, x_val = x_train[:-5000], x_train[-5000:]
y_train, y_val = y_train[:-5000], y_train[-5000:]

def conv_block(filters, use_bn):
    block = keras.Sequential()
    block.add(layers.Conv2D(filters, 3, padding="same", use_bias=not use_bn))
    if use_bn:
        block.add(layers.BatchNormalization())
    block.add(layers.ReLU())
    return block

def build_model(use_bn=False):
    inputs = keras.Input(shape=(28,28,1))
    x = conv_block(32, use_bn)(inputs)
    x = layers.MaxPooling2D()(x)
    x = conv_block(64, use_bn)(x)
    x = layers.MaxPooling2D()(x)
    x = layers.Flatten()(x)
    x = layers.Dense(128, use_bias=not use_bn)(x)
    if use_bn:
        x = layers.BatchNormalization()(x)
    x = layers.ReLU()(x)
    outputs = layers.Dense(10, activation="softmax")(x)
    model = keras.Model(inputs, outputs, name=f"mnist_cnn_bn_{use_bn}")
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=1e-3),
        loss="sparse_categorical_crossentropy",
        metrics=["accuracy"],
    )
    return model

bn_false = build_model(use_bn=False)
bn_true  = build_model(use_bn=True)

callbacks = [keras.callbacks.EarlyStopping(monitor="val_accuracy", patience=3, restore_best_weights=True)]

hist_no = bn_false.fit(
    x_train, y_train, validation_data=(x_val, y_val),
    epochs=10, batch_size=128, callbacks=callbacks, verbose=1)

hist_bn = bn_true.fit(
    x_train, y_train, validation_data=(x_val, y_val),
    epochs=10, batch_size=128, callbacks=callbacks, verbose=1)

test_no = bn_false.evaluate(x_test, y_test, verbose=0)
test_bn = bn_true.evaluate(x_test, y_test, verbose=0)

print("=== Test Accuracy ===")
print("No-BN :", round(float(test_no[1]), 4))
print("BN    :", round(float(test_bn[1]), 4))

# 観察用:最終エポックの検証精度を出力
print("ValAcc(No-BN):", round(hist_no.history["val_accuracy"][-1], 4))
print("ValAcc(BN)   :", round(hist_bn.history["val_accuracy"][-1], 4))

実行結果

Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11490434/11490434 ━━━━━━━━━━━━━━━━━━━━ 1s 0us/step
Epoch 1/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 12s 12ms/step - accuracy: 0.8475 - loss: 0.4886 - val_accuracy: 0.9804 - val_loss: 0.0707
Epoch 2/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 12s 5ms/step - accuracy: 0.9817 - loss: 0.0601 - val_accuracy: 0.9894 - val_loss: 0.0449
Epoch 3/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.9885 - loss: 0.0384 - val_accuracy: 0.9874 - val_loss: 0.0493
Epoch 4/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.9909 - loss: 0.0295 - val_accuracy: 0.9902 - val_loss: 0.0408
Epoch 5/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.9928 - loss: 0.0228 - val_accuracy: 0.9894 - val_loss: 0.0411
Epoch 6/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 2s 4ms/step - accuracy: 0.9950 - loss: 0.0167 - val_accuracy: 0.9896 - val_loss: 0.0435
Epoch 7/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.9963 - loss: 0.0128 - val_accuracy: 0.9892 - val_loss: 0.0436
Epoch 1/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 8s 12ms/step - accuracy: 0.9353 - loss: 0.2316 - val_accuracy: 0.4478 - val_loss: 1.7774
Epoch 2/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 6s 5ms/step - accuracy: 0.9907 - loss: 0.0360 - val_accuracy: 0.9868 - val_loss: 0.0466
Epoch 3/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 2s 5ms/step - accuracy: 0.9962 - loss: 0.0171 - val_accuracy: 0.9908 - val_loss: 0.0316
Epoch 4/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 3s 5ms/step - accuracy: 0.9988 - loss: 0.0080 - val_accuracy: 0.9796 - val_loss: 0.0655
Epoch 5/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 3s 6ms/step - accuracy: 0.9989 - loss: 0.0057 - val_accuracy: 0.9894 - val_loss: 0.0410
Epoch 6/10
430/430 ━━━━━━━━━━━━━━━━━━━━ 5s 5ms/step - accuracy: 0.9985 - loss: 0.0058 - val_accuracy: 0.9886 - val_loss: 0.0472
=== Test Accuracy ===
No-BN : 0.9848
BN    : 0.9915
ValAcc(No-BN): 0.9892
ValAcc(BN)   : 0.9886

観察ポイント(収束・汎化・安定性)

  • 収束速度: 同じエポック数ならBNありモデルの方が検証精度の立ち上がりが早いことが多い。
  • 汎化: BNにより検証ロスの乱高下が減り、過学習に入りにくい挙動が見える場合がある。
  • 安定性: バッチ統計でスケールが整い、学習率を少し上げても破綻しにくい傾向。

※ 実際の数値は実行環境・乱数により変動します。上記は一般的に観察されやすい傾向です。

うまく使うコツ & よくある落とし穴

コツ

  • 層の順序: Conv/Dense → BatchNorm → 活性化(ReLUなど)。
  • 学習率: BN導入時は 1e-3 から開始し、学習が安定する上限を探る。
  • ドロップアウトとの併用: まずBNだけで安定させ、必要に応じて最後段に少量のDropoutを追加。

落とし穴

  • バッチサイズが極端に小さい: 統計が不安定。32〜128程度を目安に。
  • 推論時の挙動: 学習時と推論時でBNは動作が異なるため、model.eval()(PyTorch)や training=False(TF一部API)などの扱いに注意。
  • 転移学習時: 事前学習モデル内のBNを凍結/微調整する戦略で精度が変わる。両方試すと良い。

📘 モデル設計の壁打ち・コードレビューは ココナラ が便利(外部リンク)

まとめ

  • BNは学習の安定化収束の加速に寄与しやすく、MNISTでもその傾向を観察しやすい。
  • 実装は簡単:Conv/Dense → BN → 活性化 を基本に設計。
  • 学習率・バッチサイズ・Dropoutなどと合わせて最適点を探すと、より効果が出る。

FAQ

Q. 小規模データでもBNは有効?

A. 有効なことが多いですが、バッチサイズが極小だと不安定になることがあります。GroupNormやLayerNormを試すのも手です。

Q. 活性化の前と後、どっちに置く?

A. 本記事では一般的な実装として活性化の前(Conv/Dense → BN → ReLU)を推奨しています。

Q. BNとDropoutはどちらを先に?

A. まずBNで安定化し、必要に応じて最終段にDropoutを少量追加する構成を試すと、チューニングしやすいです。

※ 広告・アフィリエイトの表記:本記事には外部リンク(アフィリエイトを含む)が含まれる場合があります。

このブログを検索

おすすめツール(PR)

このブログのまとめページ

自己紹介

はじめまして、機械学習を独学中のSHOU TAKEと申します。本ブログでは、Python・Keras・Google Colabを活用した画像分類やニューラルネットワークの実験記事を中心に発信しています。初学者の方にも分かりやすく、学んだことをそのまま実験形式でまとめるスタイルです。これまで取り組んだテーマには、学習率やOptimizerの比較、Batch Sizeの検証、事前学習の活用などがあります。ご質問やご感想は、お問い合わせフォームからお気軽にどうぞ。

お問い合わせフォーム

名前

メール *

メッセージ *

プライバシーポリシー

QooQ