バッチサイズを上げたら学習率も比例して上げるべき?Linear Scaling Ruleを検証【Keras×CIFAR-10実験】

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

Adam Batch Size CIFAR-10 CNN Google Colab Keras Learning Rate Linear Scaling Rule 画像分類

X f B! L
バッチサイズを上げたら学習率も比例して上げるべき?Linear Scaling Ruleを検証【Keras×CIFAR-10実験】 アイキャッチ画像

「バッチサイズを上げると精度が落ちる」——これは以前の記事 → バッチサイズを変えると精度はどう変わる?(16 vs 64 vs 256)【Keras×CIFAR-10実験】 で確認した通りです。しかし機械学習の世界には「バッチサイズを上げるときは学習率も比例して上げれば精度が戻る」という経験則(Linear Scaling Rule)があります。今回はこれが本当に成立するのか、CIFAR-10で検証します。

📘 この記事でわかること

  • バッチサイズを上げても学習率を比例して上げれば精度が維持できるのか
  • Linear Scaling RuleはAdamのような適応的最適化手法でも成立するのか
  • 「30epoch固定」という条件が、バッチサイズごとの重み更新回数にどう影響するか

Linear Scaling Ruleとは

Linear Scaling Ruleは、Goyalら(2017, "Accurate, Large Minibatch SGD")が提唱した経験則で、バッチサイズをk倍にしたら学習率もk倍にする、というものです。

$$ \eta_{new} = \eta_{base} \times \frac{B_{new}}{B_{base}} $$

直感的には、バッチサイズを大きくすると1回の重み更新に使う勾配の平均が安定する(ノイズが減る)ため、その分学習率を上げてより大きく重みを動かしても学習が不安定になりにくい、という理屈です。ただしこのルールはもともとSGD+Momentumを前提に提唱されたものです。Adamのような適応的最適化手法は、パラメータごとに勾配のスケールを内部で正規化する仕組みを持つため、一部の研究では平方根スケーリングの方が適しているという指摘もあります。

$$ \eta_{new} = \eta_{base} \times \sqrt{\frac{B_{new}}{B_{base}}} $$

今回はKerasで最も使われるAdamを使いつつ、まずは素直にLinear Scaling Rule(線形スケーリング)が成立するかを検証します。

パターンbatch_sizelearning_rateスケーリング倍率
A:baseline640.001基準(1倍)
B:2倍スケーリング1280.002batch 2倍 → lr 2倍
C:4倍スケーリング2560.004batch 4倍 → lr 4倍
D:スケーリングなし(対照群)2560.001batchのみ4倍、lrは据え置き

パターンDは「バッチサイズだけ上げてlrはそのまま」という、Linear Scaling Ruleを使わなかった場合の対照群です。もしLinear Scaling Ruleが本当に効くなら、C(4倍スケーリング)はA(baseline)に近い精度まで戻り、D(スケーリングなし)はAより明確に劣るという結果になるはずです。


実験コード

環境準備(最初に一度だけ実行)

# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 2s (4,639 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 65.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

(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

def build_model():
    model = keras.Sequential([
        keras.layers.Input(shape=(32, 32, 3)),
        keras.layers.Conv2D(64, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same'),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.GlobalAveragePooling2D(),
        keras.layers.Dense(128, activation='relu'),
        keras.layers.Dropout(0.2),
        keras.layers.Dense(10, activation='softmax'),
    ])
    return model

def compile_and_fit(batch_size, lr):
    keras.backend.clear_session()
    model = build_model()
    model.compile(optimizer=keras.optimizers.Adam(learning_rate=lr),
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    start = time.time()
    history = model.fit(x_train, y_train, epochs=30, batch_size=batch_size,
                        validation_split=0.2, verbose=1)
    return model, history, time.time() - start
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 1773s 10us/step

4パターンの学習実行

# (batch_size, learning_rate, 表示ラベル)
patterns = [
    (64,  0.001, 'A_baseline'),
    (128, 0.002, 'B_scale2x'),
    (256, 0.004, 'C_scale4x'),
    (256, 0.001, 'D_noscale'),
]
histories, scores, times, steps_per_epoch = {}, {}, {}, {}

for batch_size, lr, label in patterns:
    print(f"\n=== {label}(batch_size={batch_size}, lr={lr}) ===")
    model, h, t = compile_and_fit(batch_size, lr)
    s = model.evaluate(x_test, y_test, verbose=0)

    histories[label] = h
    scores[label] = s
    times[label] = t
    steps_per_epoch[label] = len(h.history['loss']) and (40000 // batch_size)

    print(f"test_accuracy: {s[1]:.4f}  学習時間: {t:.1f}秒  "
          f"1epochあたりstep数: {40000 // batch_size}")
実行結果をクリックして内容を開く
=== A_baseline(batch_size=64, lr=0.001) ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 16s 11ms/step - accuracy: 0.2634 - loss: 1.9214 - val_accuracy: 0.2950 - val_loss: 1.8349
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3648 - loss: 1.6868 - val_accuracy: 0.4108 - val_loss: 1.5902
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4256 - loss: 1.5565 - val_accuracy: 0.4585 - val_loss: 1.4735
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4595 - loss: 1.4732 - val_accuracy: 0.4865 - val_loss: 1.4130
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4861 - loss: 1.4062 - val_accuracy: 0.4924 - val_loss: 1.4114
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5045 - loss: 1.3624 - val_accuracy: 0.4962 - val_loss: 1.3915
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5191 - loss: 1.3227 - val_accuracy: 0.5047 - val_loss: 1.3879
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5329 - loss: 1.2845 - val_accuracy: 0.5398 - val_loss: 1.2515
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5464 - loss: 1.2517 - val_accuracy: 0.5464 - val_loss: 1.2446
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5563 - loss: 1.2196 - val_accuracy: 0.5685 - val_loss: 1.1825
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5670 - loss: 1.1986 - val_accuracy: 0.5811 - val_loss: 1.1625
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5753 - loss: 1.1692 - val_accuracy: 0.5779 - val_loss: 1.1574
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5879 - loss: 1.1442 - val_accuracy: 0.5837 - val_loss: 1.1374
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5933 - loss: 1.1229 - val_accuracy: 0.6059 - val_loss: 1.0880
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6011 - loss: 1.1076 - val_accuracy: 0.6071 - val_loss: 1.0874
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6097 - loss: 1.0811 - val_accuracy: 0.6145 - val_loss: 1.0546
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6150 - loss: 1.0707 - val_accuracy: 0.6273 - val_loss: 1.0289
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6237 - loss: 1.0511 - val_accuracy: 0.6253 - val_loss: 1.0458
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6289 - loss: 1.0362 - val_accuracy: 0.6148 - val_loss: 1.0666
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6370 - loss: 1.0163 - val_accuracy: 0.6174 - val_loss: 1.0437
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6421 - loss: 0.9968 - val_accuracy: 0.6356 - val_loss: 1.0004
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6416 - loss: 0.9924 - val_accuracy: 0.6495 - val_loss: 0.9742
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6478 - loss: 0.9750 - val_accuracy: 0.6459 - val_loss: 1.0009
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6553 - loss: 0.9634 - val_accuracy: 0.6539 - val_loss: 0.9702
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6597 - loss: 0.9500 - val_accuracy: 0.6458 - val_loss: 0.9742
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6656 - loss: 0.9384 - val_accuracy: 0.6654 - val_loss: 0.9384
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6705 - loss: 0.9248 - val_accuracy: 0.6647 - val_loss: 0.9514
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6718 - loss: 0.9205 - val_accuracy: 0.6696 - val_loss: 0.9211
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6766 - loss: 0.9026 - val_accuracy: 0.6702 - val_loss: 0.9460
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6788 - loss: 0.8983 - val_accuracy: 0.6523 - val_loss: 0.9640
test_accuracy: 0.6495  学習時間: 135.4秒  1epochあたりstep数: 625

=== B_scale2x(batch_size=128, lr=0.002) ===
Epoch 1/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 11s 22ms/step - accuracy: 0.2528 - loss: 1.9465 - val_accuracy: 0.3160 - val_loss: 1.7953
Epoch 2/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.3596 - loss: 1.7006 - val_accuracy: 0.4132 - val_loss: 1.5985
Epoch 3/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.4277 - loss: 1.5466 - val_accuracy: 0.4666 - val_loss: 1.4617
Epoch 4/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.4657 - loss: 1.4520 - val_accuracy: 0.4758 - val_loss: 1.4143
Epoch 5/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 9ms/step - accuracy: 0.4967 - loss: 1.3779 - val_accuracy: 0.5120 - val_loss: 1.3351
Epoch 6/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.5139 - loss: 1.3317 - val_accuracy: 0.5400 - val_loss: 1.2713
Epoch 7/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.5316 - loss: 1.2829 - val_accuracy: 0.5351 - val_loss: 1.2792
Epoch 8/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.5484 - loss: 1.2445 - val_accuracy: 0.5396 - val_loss: 1.2723
Epoch 9/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.5579 - loss: 1.2141 - val_accuracy: 0.5660 - val_loss: 1.1998
Epoch 10/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.5719 - loss: 1.1767 - val_accuracy: 0.5780 - val_loss: 1.1611
Epoch 11/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.5813 - loss: 1.1565 - val_accuracy: 0.5907 - val_loss: 1.1426
Epoch 12/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.5900 - loss: 1.1328 - val_accuracy: 0.6038 - val_loss: 1.1071
Epoch 13/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6007 - loss: 1.1059 - val_accuracy: 0.5993 - val_loss: 1.0958
Epoch 14/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6084 - loss: 1.0859 - val_accuracy: 0.6218 - val_loss: 1.0554
Epoch 15/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.6166 - loss: 1.0616 - val_accuracy: 0.5993 - val_loss: 1.1049
Epoch 16/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6246 - loss: 1.0419 - val_accuracy: 0.6279 - val_loss: 1.0254
Epoch 17/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6288 - loss: 1.0319 - val_accuracy: 0.6250 - val_loss: 1.0413
Epoch 18/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6346 - loss: 1.0100 - val_accuracy: 0.6404 - val_loss: 1.0049
Epoch 19/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 11ms/step - accuracy: 0.6454 - loss: 0.9886 - val_accuracy: 0.6350 - val_loss: 0.9977
Epoch 20/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 5s 10ms/step - accuracy: 0.6495 - loss: 0.9776 - val_accuracy: 0.6403 - val_loss: 0.9957
Epoch 21/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6567 - loss: 0.9636 - val_accuracy: 0.6529 - val_loss: 0.9547
Epoch 22/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6639 - loss: 0.9423 - val_accuracy: 0.6542 - val_loss: 0.9613
Epoch 23/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6688 - loss: 0.9319 - val_accuracy: 0.6626 - val_loss: 0.9476
Epoch 24/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6703 - loss: 0.9193 - val_accuracy: 0.6678 - val_loss: 0.9267
Epoch 25/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6774 - loss: 0.9105 - val_accuracy: 0.6667 - val_loss: 0.9314
Epoch 26/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6829 - loss: 0.8966 - val_accuracy: 0.6767 - val_loss: 0.9078
Epoch 27/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6837 - loss: 0.8847 - val_accuracy: 0.6770 - val_loss: 0.9126
Epoch 28/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6929 - loss: 0.8656 - val_accuracy: 0.6784 - val_loss: 0.9172
Epoch 29/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6929 - loss: 0.8624 - val_accuracy: 0.6835 - val_loss: 0.8987
Epoch 30/30
313/313 ━━━━━━━━━━━━━━━━━━━━ 3s 10ms/step - accuracy: 0.6975 - loss: 0.8494 - val_accuracy: 0.6670 - val_loss: 0.9502
test_accuracy: 0.6654  学習時間: 104.8秒  1epochあたりstep数: 312

=== C_scale4x(batch_size=256, lr=0.004) ===
Epoch 1/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 10s 39ms/step - accuracy: 0.2392 - loss: 1.9946 - val_accuracy: 0.3178 - val_loss: 1.7917
Epoch 2/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.3448 - loss: 1.7298 - val_accuracy: 0.3661 - val_loss: 1.7086
Epoch 3/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.3951 - loss: 1.6254 - val_accuracy: 0.4046 - val_loss: 1.5791
Epoch 4/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.4452 - loss: 1.5098 - val_accuracy: 0.4674 - val_loss: 1.4605
Epoch 5/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4776 - loss: 1.4290 - val_accuracy: 0.4867 - val_loss: 1.3765
Epoch 6/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4962 - loss: 1.3808 - val_accuracy: 0.4954 - val_loss: 1.3550
Epoch 7/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5119 - loss: 1.3350 - val_accuracy: 0.5130 - val_loss: 1.3311
Epoch 8/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5265 - loss: 1.3059 - val_accuracy: 0.5186 - val_loss: 1.3423
Epoch 9/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5304 - loss: 1.2881 - val_accuracy: 0.5535 - val_loss: 1.2335
Epoch 10/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.5433 - loss: 1.2555 - val_accuracy: 0.5279 - val_loss: 1.2630
Epoch 11/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5558 - loss: 1.2218 - val_accuracy: 0.5552 - val_loss: 1.2046
Epoch 12/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5570 - loss: 1.2157 - val_accuracy: 0.5528 - val_loss: 1.2707
Epoch 13/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5712 - loss: 1.1843 - val_accuracy: 0.5732 - val_loss: 1.1649
Epoch 14/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5788 - loss: 1.1621 - val_accuracy: 0.5884 - val_loss: 1.1308
Epoch 15/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 5s 18ms/step - accuracy: 0.5849 - loss: 1.1464 - val_accuracy: 0.6035 - val_loss: 1.0948
Epoch 16/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5959 - loss: 1.1183 - val_accuracy: 0.5999 - val_loss: 1.0972
Epoch 17/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6028 - loss: 1.0975 - val_accuracy: 0.5987 - val_loss: 1.1071
Epoch 18/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.6065 - loss: 1.0907 - val_accuracy: 0.6034 - val_loss: 1.0834
Epoch 19/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6130 - loss: 1.0692 - val_accuracy: 0.6190 - val_loss: 1.0554
Epoch 20/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6200 - loss: 1.0535 - val_accuracy: 0.6231 - val_loss: 1.0388
Epoch 21/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6254 - loss: 1.0392 - val_accuracy: 0.6091 - val_loss: 1.0617
Epoch 22/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.6305 - loss: 1.0298 - val_accuracy: 0.6189 - val_loss: 1.0602
Epoch 23/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.6308 - loss: 1.0215 - val_accuracy: 0.5959 - val_loss: 1.1311
Epoch 24/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6338 - loss: 1.0149 - val_accuracy: 0.6362 - val_loss: 1.0086
Epoch 25/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6420 - loss: 0.9895 - val_accuracy: 0.6318 - val_loss: 1.0138
Epoch 26/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6421 - loss: 0.9993 - val_accuracy: 0.6333 - val_loss: 1.0046
Epoch 27/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.6523 - loss: 0.9680 - val_accuracy: 0.6159 - val_loss: 1.0667
Epoch 28/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.6564 - loss: 0.9633 - val_accuracy: 0.6540 - val_loss: 0.9690
Epoch 29/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6637 - loss: 0.9442 - val_accuracy: 0.6367 - val_loss: 1.0021
Epoch 30/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.6608 - loss: 0.9436 - val_accuracy: 0.6452 - val_loss: 0.9904
test_accuracy: 0.6434  学習時間: 94.6秒  1epochあたりstep数: 156

=== D_noscale(batch_size=256, lr=0.001) ===
Epoch 1/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 9s 36ms/step - accuracy: 0.2247 - loss: 2.0396 - val_accuracy: 0.2836 - val_loss: 1.8822
Epoch 2/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.3029 - loss: 1.8264 - val_accuracy: 0.3358 - val_loss: 1.7668
Epoch 3/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.3426 - loss: 1.7280 - val_accuracy: 0.3571 - val_loss: 1.7483
Epoch 4/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.3784 - loss: 1.6674 - val_accuracy: 0.3925 - val_loss: 1.6485
Epoch 5/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.4069 - loss: 1.6071 - val_accuracy: 0.4225 - val_loss: 1.5712
Epoch 6/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4273 - loss: 1.5642 - val_accuracy: 0.4495 - val_loss: 1.5252
Epoch 7/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.4410 - loss: 1.5293 - val_accuracy: 0.4513 - val_loss: 1.4966
Epoch 8/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4570 - loss: 1.4876 - val_accuracy: 0.4807 - val_loss: 1.4385
Epoch 9/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.4703 - loss: 1.4544 - val_accuracy: 0.4696 - val_loss: 1.4510
Epoch 10/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4828 - loss: 1.4203 - val_accuracy: 0.4967 - val_loss: 1.3861
Epoch 11/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.4942 - loss: 1.3967 - val_accuracy: 0.4981 - val_loss: 1.4003
Epoch 12/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.5028 - loss: 1.3668 - val_accuracy: 0.4796 - val_loss: 1.4141
Epoch 13/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5097 - loss: 1.3524 - val_accuracy: 0.5162 - val_loss: 1.3254
Epoch 14/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5148 - loss: 1.3356 - val_accuracy: 0.5193 - val_loss: 1.3115
Epoch 15/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5219 - loss: 1.3138 - val_accuracy: 0.5334 - val_loss: 1.2826
Epoch 16/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 20ms/step - accuracy: 0.5285 - loss: 1.2949 - val_accuracy: 0.5229 - val_loss: 1.3003
Epoch 17/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5326 - loss: 1.2817 - val_accuracy: 0.5458 - val_loss: 1.2557
Epoch 18/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5437 - loss: 1.2562 - val_accuracy: 0.5411 - val_loss: 1.2457
Epoch 19/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5483 - loss: 1.2427 - val_accuracy: 0.5359 - val_loss: 1.2731
Epoch 20/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5504 - loss: 1.2386 - val_accuracy: 0.5394 - val_loss: 1.2625
Epoch 21/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 5s 18ms/step - accuracy: 0.5572 - loss: 1.2232 - val_accuracy: 0.5606 - val_loss: 1.2019
Epoch 22/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5626 - loss: 1.2072 - val_accuracy: 0.5578 - val_loss: 1.2294
Epoch 23/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5654 - loss: 1.1938 - val_accuracy: 0.5699 - val_loss: 1.1942
Epoch 24/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.5699 - loss: 1.1878 - val_accuracy: 0.5740 - val_loss: 1.1618
Epoch 25/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5767 - loss: 1.1723 - val_accuracy: 0.5643 - val_loss: 1.1875
Epoch 26/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5778 - loss: 1.1638 - val_accuracy: 0.5738 - val_loss: 1.1686
Epoch 27/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 17ms/step - accuracy: 0.5820 - loss: 1.1520 - val_accuracy: 0.5759 - val_loss: 1.1817
Epoch 28/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 19ms/step - accuracy: 0.5847 - loss: 1.1458 - val_accuracy: 0.5750 - val_loss: 1.1627
Epoch 29/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5875 - loss: 1.1351 - val_accuracy: 0.5940 - val_loss: 1.1208
Epoch 30/30
157/157 ━━━━━━━━━━━━━━━━━━━━ 3s 18ms/step - accuracy: 0.5932 - loss: 1.1242 - val_accuracy: 0.5967 - val_loss: 1.1091
test_accuracy: 0.5937  学習時間: 95.0秒  1epochあたりstep数: 156

グラフ+サマリー

# ── 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('batch_lr_scaling_comparison.png', dpi=150)
plt.show()

print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>11} | {'Batch':>5} | {'LR':>6} | {'Test Acc':>9} | {'Time(s)':>8} | {'Steps/epoch':>11}")
print("-" * 66)
for batch_size, lr, label in patterns:
    test_acc = scores[label][1]
    t        = times[label]
    sp       = 40000 // batch_size
    print(f"{label:>11} | {batch_size:>5} | {lr:>6} | {test_acc:>9.4f} | {t:>8.1f} | {sp:>11}")
print("-" * 66)
実行結果をクリックして内容を開く
===== 最終結果サマリー =====
    Pattern | Batch |     LR |  Test Acc |  Time(s) | Steps/epoch
------------------------------------------------------------------
 A_baseline |    64 |  0.001 |    0.6495 |    135.4 |         625
  B_scale2x |   128 |  0.002 |    0.6654 |    104.8 |         312
  C_scale4x |   256 |  0.004 |    0.6434 |     94.6 |         156
  D_noscale |   256 |  0.001 |    0.5937 |     95.0 |         156
------------------------------------------------------------------

実験結果

精度・損失グラフ

精度グラフ

精度グラフ

損失グラフ

損失グラフ

結果サマリー

パターンbatch_sizelearning_ratetest_accuracy学習時間1epochあたりstep数
A:baseline640.00164.95%135.4秒625
B:2倍スケーリング1280.00266.54%104.8秒312
C:4倍スケーリング2560.00464.34%94.6秒156
D:スケーリングなし2560.00159.37%95.0秒156

同じbatch=256・同じstep数(156/epoch)でlrだけが違うC・Dを比較すると、lrスケーリングの有無だけでtest_accuracyに+4.97pt(59.37%→64.34%)の差が生まれました。2倍スケーリング(B)は66.54%とbaseline(64.95%)を+1.59pt上回り、4倍スケーリング(C)はbaselineに近い水準(-0.61pt)まで戻りました。

ハマりポイント

  • 「30epoch固定」は「重み更新回数(step数)固定」ではない。batch_size=64なら1epochあたり625step、batch_size=256なら156stepと、4倍近い差がある。30epoch通しで見ると、batch=64は約18,750回、batch=256は約4,680回しか重みを更新していない。この差はLinear Scaling Ruleの効果とは別に精度差を生みうる要因なので、結果を見るときは「lrスケーリングの効果」と「総step数の差」を混同しないよう注意する。
  • Linear Scaling RuleはもともとSGD+Momentum・大規模分散学習を前提に提唱されたルール。Adamは内部で勾配を正規化する性質を持つため、同じ倍率でスケーリングしても理論的な裏付けが弱く、平方根スケーリングなど別の経験則の方が適している可能性がある。
  • 大規模バッチ学習の文献では、Linear Scaling Ruleは通常ウォームアップとセットで使われる。lrを急に大きくすると学習初期が不安定になりやすいため、本来は学習率ウォームアップと組み合わせるのが定石。今回はウォームアップなしで「スケーリング単体」の効果を見る設計にしている。
  • Adamのoptimizerインスタンスはcompile_and_fit内で毎回新規作成する必要がある。使い回すと前のパターンの内部状態(モーメント推定値など)が残ってしまう。

考察

① Linear Scaling Ruleは精度を回復させたか(C vs A・D)

Cvs Dの比較が最も明確な答えを示しています。同じbatch=256・同じstep数でありながら、lrをスケーリングしたC(64.34%)はスケーリングしなかったD(59.37%)を+4.97pt上回りました。さらにCはbaseline(64.95%)とほぼ同水準(-0.61pt)まで戻っており、Linear Scaling RuleはAdamでも「バッチサイズを上げたことによる精度低下」の大部分を打ち消す効果を持つことが確認できました。完全にbaselineへ戻りきらなかった0.61pt分については、②③で詳しく見ていきます。

② 2倍スケーリング(B)と4倍スケーリング(C)で傾向は一貫しているか

興味深いことに、2倍スケーリング(B:66.54%)はbaselineを+1.59pt上回った一方、4倍スケーリング(C:64.34%)はbaselineをわずかに下回り(-0.61pt)ました。つまりLinear Scaling Ruleは倍率が上がるにつれて効果が目減りしていく傾向が見えます。これは記事冒頭で触れた「AdamはSGDと違い、Linear Scaling Ruleではなく平方根スケーリングの方が適しているかもしれない」という指摘と整合的です。倍率が小さいうち(2倍)は線形スケーリングでも十分機能しますが、倍率が大きくなる(4倍)と学習率が本来の適正値より上振れし始め、baselineに完全には届かなくなる、という解釈ができます。

③ 総step数の差はどこまで結果に影響したか

CとDはstep数が同じ(156/epoch)なので、この2つの差(+4.97pt)は総step数の影響を含まない、純粋なlrスケーリングの効果と解釈できます。一方でA(baseline、625step/epoch)とC(156step/epoch)を比べると、Cはstep数がAの約1/4しかないにもかかわらず-0.61ptしか下回っていません。これは「step数が少ない不利」を、大きな学習率による1回あたりの更新量の大きさがかなりの部分打ち消していることを示唆しています。total step数の差そのものよりも、lrが適切にスケーリングされているかどうかの方が、今回の結果への影響が大きかったと言えそうです。

④ 想定との差分と確認ポイント

今回の結果は、事前に立てた仮説(線形スケーリングは倍率が大きくなるほど効果が目減りする可能性)と概ね一致しており、大きな異常値は見られませんでした。ただし、もし手元の実験でCがDと大差ない、またはCがAより明確に劣るような結果になった場合は、以下を優先して確認してください。

  • 学習曲線(val_loss)に、学習率が大きすぎることによる序盤の乱高下や発散の兆候がないか
  • 各パターンのAdamのlearning_rateが意図通り設定されているか(model.optimizer.learning_rateで確認)
  • ウォームアップなしでlrを4倍にしたことが、Adamの初期の不安定さを助長していないか(ウォームアップを追加した再実験で改善するか確認する価値がある)
ケース実務での推奨
Adamでバッチサイズを大きくするLinear Scaling Ruleは有効な出発点になる。今回の実験では、スケーリングなしに比べて+4.97pt(4倍バッチ時)の改善効果があった。
lrスケーリングを試す際の倍率2倍程度まではLinear Scaling Ruleがよく機能し、baselineを上回ることもある。4倍まで上げると効果が目減りし始めるため、倍率が大きい場合は平方根スケーリングなど控えめな倍率も試す価値がある。
SGD+Momentumで大規模バッチ学習を行うLinear Scaling Ruleの本来の適用対象。ウォームアップとセットで使うのが定石。
学習が不安定になった場合ウォームアップを組み合わせる、またはLinear Scaling Ruleではなく平方根スケーリングを試す。

まとめ

AdamでもLinear Scaling Ruleは有効に機能し、batch=256でlrをスケーリングしなかった場合(59.37%)に比べ、スケーリングした場合(64.34%)はtest_accuracyが+4.97pt改善し、baselineとほぼ同水準まで戻りました。ただし2倍スケーリング(66.54%、baseline比+1.59pt)の方が4倍スケーリング(64.34%、baseline比-0.61pt)より良い結果だった点から、倍率が大きくなるほど線形スケーリングの効果は目減りする傾向も見えました。総step数の差(Aは625、C/Dは156)よりも、lrが適切にスケーリングされているかどうかの方が精度への影響が大きいという結果です。


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