- One-Cycle PolicyとCosine Annealingの仕組みの違い
- KerasでOne-Cycle Policyを自作コールバックとして実装する方法
- 固定学習率・Cosine Annealing・One-Cycle Policyの精度・収束速度・過学習の比較結果(test_accuracyで最大9.1ptの差)
One-Cycle PolicyとCosine Annealingの仕組み
Cosine Annealingは学習率を開始から終了まで一貫して減衰させるのに対し、One-Cycle Policyは学習前半でいったん学習率を引き上げ、後半で急激に下げるという逆転の発想を取ります。前半の高学習率フェーズが局所最適解からの脱出を促し、後半の急減衰フェーズが精密な収束を実現するとされています。
| 項目 | 固定学習率 | Cosine Annealing | One-Cycle Policy |
|---|---|---|---|
| 学習率の推移 | 一定 | 滑らかに単調減少 | 上昇→下降(山型) |
| ピーク学習率 | なし | 開始時が最大 | 訓練中盤が最大 |
| ハイパーパラメータ | lr のみ | eta_min のみ | max_lr・pct_start・div_factor |
| 実装方法 | optimizerに直接指定 | keras.optimizers.schedules.CosineDecay | 自作コールバック(Keras未実装) |
One-Cycle Policyの学習率は、上昇フェーズ(\(t \le t_1\))と下降フェーズ(\(t_1 < t \le T\))で以下のように定義されます。 \[ \eta(t) = \begin{cases} \eta_{min} + (\eta_{max} - \eta_{min}) \cdot \dfrac{t}{t_1} & (t \le t_1) \\[6pt] \eta_{end} + \dfrac{1}{2}(\eta_{max} - \eta_{end})\left(1 + \cos\left(\pi \cdot \dfrac{t - t_1}{T - t_1}\right)\right) & (t_1 < t \le T) \end{cases} \]
KerasにはCosineDecayのようなOne-Cycle Policyの組み込み実装は存在しないため、on_train_batch_beginでバッチ単位に学習率を書き換えるカスタムコールバックとして自作します。
実験コード
使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。学習率スケジュール以外の条件は全て同一にし、比較実験のためtf.config.experimental.enable_op_determinism()で乱数シードを固定しています。
環境準備(最初に一度だけ実行)
# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 33 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 noble/universe amd64 fonts-ipafont-gothic all 00303-21ubuntu1 [3,513 kB]
Get:2 http://archive.ubuntu.com/ubuntu noble/universe amd64 fonts-ipafont-mincho all 00303-21ubuntu1 [4,724 kB]
Fetched 8,237 kB in 3s (3,024 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 127332 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.15.0-1.1ubuntu2) ...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.1/4.1 MB 35.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 numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
import time
# ── 乱数シード固定(比較実験の再現性確保のため)───────
tf.config.experimental.enable_op_determinism()
tf.random.set_seed(42)
np.random.seed(42)
(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(name):
return 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'),
], name=name)
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz 170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 1277s 7us/step
One-Cycle Policyの自作コールバック
class OneCyclePolicy(keras.callbacks.Callback):
"""One-Cycle Policy:前半でLRを上昇させ、後半でコサイン状に減衰させる"""
def __init__(self, max_lr, total_steps, pct_start=0.3,
div_factor=10.0, final_div_factor=100.0):
super().__init__()
self.max_lr = max_lr
self.total_steps = total_steps
self.up_steps = int(total_steps * pct_start)
self.down_steps = total_steps - self.up_steps
self.initial_lr = max_lr / div_factor
self.final_lr = self.initial_lr / final_div_factor
self.step_count = 0
self.lr_history = []
def on_train_batch_begin(self, batch, logs=None):
if self.step_count <= self.up_steps:
progress = self.step_count / max(self.up_steps, 1)
lr = self.initial_lr + (self.max_lr - self.initial_lr) * progress
else:
progress = (self.step_count - self.up_steps) / max(self.down_steps, 1)
lr = self.final_lr + 0.5 * (self.max_lr - self.final_lr) * \
(1 + np.cos(np.pi * min(progress, 1.0)))
self.model.optimizer.learning_rate.assign(lr)
self.lr_history.append(float(lr))
self.step_count += 1
3パターンの学習実行
epochs = 30
batch_size = 64
steps_per_epoch = int(len(x_train) * 0.8 // batch_size)
total_steps = steps_per_epoch * epochs
configs = ['A_fixed_lr', 'B_cosine_annealing', 'C_onecycle']
histories, times, scores, onecycle_lr_log = {}, {}, {}, None
for name in configs:
keras.backend.clear_session()
tf.random.set_seed(42)
model = build_model(name)
fit_callbacks = []
if name == 'A_fixed_lr':
optimizer = keras.optimizers.Adam(learning_rate=0.001)
elif name == 'B_cosine_annealing':
lr_schedule = keras.optimizers.schedules.CosineDecay(
initial_learning_rate=0.001, decay_steps=total_steps, alpha=0.01)
optimizer = keras.optimizers.Adam(learning_rate=lr_schedule)
else: # C_onecycle
optimizer = keras.optimizers.Adam(learning_rate=0.001)
onecycle_cb = OneCyclePolicy(max_lr=0.005, total_steps=total_steps)
fit_callbacks.append(onecycle_cb)
model.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
print(f"\n=== {name} ===")
start = time.time()
h = model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size,
validation_split=0.2, callbacks=fit_callbacks, verbose=1)
t = time.time() - start
s = model.evaluate(x_test, y_test, verbose=0)
label = name.split('_', 1)[1]
histories[label] = h
times[label] = t
scores[label] = s
if name == 'C_onecycle':
onecycle_lr_log = onecycle_cb.lr_history
print(f"学習時間:{t:.1f}秒 test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_fixed_lr === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 21s 15ms/step - accuracy: 0.2573 - loss: 1.9450 - val_accuracy: 0.3654 - val_loss: 1.7162 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.3662 - loss: 1.6904 - val_accuracy: 0.4100 - val_loss: 1.6059 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4217 - loss: 1.5711 - val_accuracy: 0.4627 - val_loss: 1.4900 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4624 - loss: 1.4705 - val_accuracy: 0.4868 - val_loss: 1.4111 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 10ms/step - accuracy: 0.4889 - loss: 1.3972 - val_accuracy: 0.5004 - val_loss: 1.3603 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5110 - loss: 1.3435 - val_accuracy: 0.5114 - val_loss: 1.3191 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5260 - loss: 1.2992 - val_accuracy: 0.5365 - val_loss: 1.2662 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5367 - loss: 1.2654 - val_accuracy: 0.5448 - val_loss: 1.2484 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5510 - loss: 1.2344 - val_accuracy: 0.5501 - val_loss: 1.2222 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5610 - loss: 1.2066 - val_accuracy: 0.5728 - val_loss: 1.1800 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5728 - loss: 1.1730 - val_accuracy: 0.5816 - val_loss: 1.1515 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.5822 - loss: 1.1480 - val_accuracy: 0.5855 - val_loss: 1.1382 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5931 - loss: 1.1250 - val_accuracy: 0.5885 - val_loss: 1.1392 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6010 - loss: 1.1037 - val_accuracy: 0.5982 - val_loss: 1.1126 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.6112 - loss: 1.0796 - val_accuracy: 0.6037 - val_loss: 1.0803 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 10ms/step - accuracy: 0.6176 - loss: 1.0599 - val_accuracy: 0.6150 - val_loss: 1.0688 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6249 - loss: 1.0412 - val_accuracy: 0.6108 - val_loss: 1.0678 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6345 - loss: 1.0214 - val_accuracy: 0.6230 - val_loss: 1.0415 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6385 - loss: 1.0050 - val_accuracy: 0.6317 - val_loss: 1.0208 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6432 - loss: 0.9904 - val_accuracy: 0.6327 - val_loss: 1.0143 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6500 - loss: 0.9782 - val_accuracy: 0.6298 - val_loss: 1.0158 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6557 - loss: 0.9632 - val_accuracy: 0.6393 - val_loss: 0.9927 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.6581 - loss: 0.9494 - val_accuracy: 0.6486 - val_loss: 0.9735 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.6628 - loss: 0.9383 - val_accuracy: 0.6484 - val_loss: 0.9720 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.6707 - loss: 0.9230 - val_accuracy: 0.6525 - val_loss: 0.9630 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6715 - loss: 0.9127 - val_accuracy: 0.6567 - val_loss: 0.9532 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.6763 - loss: 0.9036 - val_accuracy: 0.6568 - val_loss: 0.9548 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6822 - loss: 0.8898 - val_accuracy: 0.6580 - val_loss: 0.9523 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6821 - loss: 0.8834 - val_accuracy: 0.6652 - val_loss: 0.9289 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6904 - loss: 0.8682 - val_accuracy: 0.6671 - val_loss: 0.9296 学習時間:184.0秒 test_accuracy:0.6679 === B_cosine_annealing === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 10ms/step - accuracy: 0.2514 - loss: 1.9643 - val_accuracy: 0.3529 - val_loss: 1.7305 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.3537 - loss: 1.7089 - val_accuracy: 0.4050 - val_loss: 1.6090 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4110 - loss: 1.5875 - val_accuracy: 0.4546 - val_loss: 1.5047 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4560 - loss: 1.4863 - val_accuracy: 0.4923 - val_loss: 1.3972 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.4841 - loss: 1.4121 - val_accuracy: 0.5069 - val_loss: 1.3506 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5044 - loss: 1.3548 - val_accuracy: 0.5177 - val_loss: 1.3123 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5210 - loss: 1.3090 - val_accuracy: 0.5382 - val_loss: 1.2620 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5339 - loss: 1.2728 - val_accuracy: 0.5548 - val_loss: 1.2260 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5490 - loss: 1.2399 - val_accuracy: 0.5553 - val_loss: 1.2140 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5583 - loss: 1.2096 - val_accuracy: 0.5698 - val_loss: 1.1835 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5697 - loss: 1.1898 - val_accuracy: 0.5743 - val_loss: 1.1757 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.5777 - loss: 1.1638 - val_accuracy: 0.5841 - val_loss: 1.1473 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5854 - loss: 1.1441 - val_accuracy: 0.5963 - val_loss: 1.1187 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5935 - loss: 1.1276 - val_accuracy: 0.6023 - val_loss: 1.0997 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5993 - loss: 1.1077 - val_accuracy: 0.6061 - val_loss: 1.0858 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6048 - loss: 1.0933 - val_accuracy: 0.6101 - val_loss: 1.0758 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6097 - loss: 1.0779 - val_accuracy: 0.6149 - val_loss: 1.0610 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6173 - loss: 1.0648 - val_accuracy: 0.6228 - val_loss: 1.0455 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6202 - loss: 1.0521 - val_accuracy: 0.6249 - val_loss: 1.0347 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6276 - loss: 1.0405 - val_accuracy: 0.6257 - val_loss: 1.0301 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6287 - loss: 1.0347 - val_accuracy: 0.6281 - val_loss: 1.0294 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6334 - loss: 1.0225 - val_accuracy: 0.6318 - val_loss: 1.0189 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6360 - loss: 1.0122 - val_accuracy: 0.6334 - val_loss: 1.0150 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6402 - loss: 1.0074 - val_accuracy: 0.6342 - val_loss: 1.0117 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6386 - loss: 0.9999 - val_accuracy: 0.6358 - val_loss: 1.0047 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.6425 - loss: 0.9924 - val_accuracy: 0.6371 - val_loss: 1.0030 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6441 - loss: 0.9914 - val_accuracy: 0.6361 - val_loss: 1.0031 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6423 - loss: 0.9898 - val_accuracy: 0.6392 - val_loss: 0.9990 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6449 - loss: 0.9869 - val_accuracy: 0.6394 - val_loss: 0.9980 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6432 - loss: 0.9852 - val_accuracy: 0.6399 - val_loss: 0.9970 学習時間:161.4秒 test_accuracy:0.6390 === C_onecycle === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 9ms/step - accuracy: 0.2450 - loss: 1.9812 - val_accuracy: 0.3422 - val_loss: 1.7517 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.3554 - loss: 1.7080 - val_accuracy: 0.4212 - val_loss: 1.5868 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4245 - loss: 1.5643 - val_accuracy: 0.4730 - val_loss: 1.4477 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4654 - loss: 1.4618 - val_accuracy: 0.5142 - val_loss: 1.3409 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.4965 - loss: 1.3735 - val_accuracy: 0.5371 - val_loss: 1.2770 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.5233 - loss: 1.3030 - val_accuracy: 0.5499 - val_loss: 1.2452 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5457 - loss: 1.2481 - val_accuracy: 0.5792 - val_loss: 1.1550 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 10ms/step - accuracy: 0.5697 - loss: 1.1891 - val_accuracy: 0.6035 - val_loss: 1.1061 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.5866 - loss: 1.1462 - val_accuracy: 0.6100 - val_loss: 1.0784 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6036 - loss: 1.0987 - val_accuracy: 0.6162 - val_loss: 1.0699 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6231 - loss: 1.0506 - val_accuracy: 0.6384 - val_loss: 1.0008 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 9ms/step - accuracy: 0.6398 - loss: 0.9991 - val_accuracy: 0.6565 - val_loss: 0.9641 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6517 - loss: 0.9670 - val_accuracy: 0.6570 - val_loss: 0.9693 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.6670 - loss: 0.9369 - val_accuracy: 0.6619 - val_loss: 0.9490 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6794 - loss: 0.8923 - val_accuracy: 0.6708 - val_loss: 0.9225 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.6894 - loss: 0.8676 - val_accuracy: 0.6778 - val_loss: 0.9048 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7036 - loss: 0.8322 - val_accuracy: 0.6833 - val_loss: 0.8960 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.7129 - loss: 0.8035 - val_accuracy: 0.6851 - val_loss: 0.8926 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7255 - loss: 0.7707 - val_accuracy: 0.6999 - val_loss: 0.8700 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7344 - loss: 0.7440 - val_accuracy: 0.7015 - val_loss: 0.8577 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 11s 10ms/step - accuracy: 0.7453 - loss: 0.7124 - val_accuracy: 0.7064 - val_loss: 0.8413 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.7511 - loss: 0.6909 - val_accuracy: 0.7150 - val_loss: 0.8283 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7649 - loss: 0.6633 - val_accuracy: 0.7152 - val_loss: 0.8305 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.7718 - loss: 0.6408 - val_accuracy: 0.7149 - val_loss: 0.8423 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7805 - loss: 0.6179 - val_accuracy: 0.7200 - val_loss: 0.8260 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.7860 - loss: 0.6002 - val_accuracy: 0.7241 - val_loss: 0.8129 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7939 - loss: 0.5820 - val_accuracy: 0.7319 - val_loss: 0.7921 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.7953 - loss: 0.5735 - val_accuracy: 0.7320 - val_loss: 0.7883 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 9ms/step - accuracy: 0.7981 - loss: 0.5622 - val_accuracy: 0.7315 - val_loss: 0.7854 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 9ms/step - accuracy: 0.8010 - loss: 0.5587 - val_accuracy: 0.7344 - val_loss: 0.7826 学習時間:194.1秒 test_accuracy:0.7301
グラフ+サマリー
# ── 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('onecycle_vs_cosine_comparison.png', dpi=150)
plt.show()
# ── 学習率の推移グラフ(Cosine Annealing vs One-Cycle)──
fig2, ax2 = plt.subplots(figsize=(8, 5))
cosine_lrs = [keras.optimizers.schedules.CosineDecay(
initial_learning_rate=0.001, decay_steps=total_steps, alpha=0.01)(s).numpy()
for s in range(0, total_steps, steps_per_epoch)]
onecycle_lrs = onecycle_lr_log[::steps_per_epoch]
ax2.plot(cosine_lrs, label='Cosine Annealing')
ax2.plot(onecycle_lrs, label='One-Cycle Policy')
ax2.set_xlabel('Epoch'); ax2.set_ylabel('Learning Rate')
ax2.set_title('学習率スケジュールの推移比較')
ax2.legend(); ax2.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('onecycle_vs_cosine_lr_schedule.png', dpi=150)
plt.show()
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>18} | {'Val Acc':>8} | {'Test Acc':>9} | {'Time(s)':>8}")
print("-" * 55)
for label in ['fixed_lr', 'cosine_annealing', 'onecycle']:
val_acc = histories[label].history['val_accuracy'][-1]
test_acc = scores[label][1]
t = times[label]
print(f"{label:>18} | {val_acc:>8.4f} | {test_acc:>9.4f} | {t:>8.1f}")
print("-" * 55)
実行結果をクリックして内容を開く
===== 最終結果サマリー =====
Pattern | Val Acc | Test Acc | Time(s)
-------------------------------------------------------
fixed_lr | 0.6671 | 0.6679 | 184.0
cosine_annealing | 0.6399 | 0.6390 | 161.4
onecycle | 0.7344 | 0.7301 | 194.1
-------------------------------------------------------
実験結果
精度・損失グラフ
精度グラフ
損失グラフ
val_accuracyのグラフでは、C:One-Cycle Policyだけがエポック12以降に他の2本から明確に離れ、右肩上がりのまま30エポック目まで伸び続けます。val_lossも同様に、One-Cycleだけがエポック20以降も下降を続けているのに対し、AとBはエポック20前後で横ばいに近づきます。
学習率推移グラフ
One-Cycle Policyはエポック9付近でmax_lr=0.005(固定学習率の5倍)に到達したあと、そこから約21エポックかけてほぼゼロ近くまで減衰します。Cosine Annealingは開始直後(0.001)から一貫して減衰する滑らかな曲線です。
| パターン | 最終 val_accuracy | 最終 test_accuracy | 学習時間 |
|---|---|---|---|
| A:固定学習率 | 66.71% | 66.79% | 184.0秒 |
| B:Cosine Annealing | 63.99% | 63.90% | 161.4秒 |
| C:One-Cycle Policy | 73.44% | 73.01% | 194.1秒 |
model.optimizer.learning_rate.assign(lr)は、optimizerのlearning_rateがschedulesオブジェクトではなく通常の変数(Variable)である場合のみ有効です。BパターンでCosineDecayを使った直後にCパターンを実行する際、keras.backend.clear_session()とoptimizerの再生成を忘れると意図しない挙動になります。- One-Cycle Policyの
max_lrは基準学習率(0.001)よりもかなり高め(今回は0.005=5倍)に設定するのが効果的でした。今回はこの設定で発散(lossがnanになる等)は起きませんでしたが、モデルやデータセットが変わればmax_lrの再調整が必要です。 on_train_batch_beginはエポックではなくバッチ単位で呼ばれるため、total_stepsの計算を誤ると学習終盤で学習率が0付近に到達しない、または早期に到達してしまいます。
考察
① 精度はOne-Cycle PolicyとCosine Annealingのどちらが上回ったか
test_accuracyで比較すると、One-Cycle Policy(73.01%)がCosine Annealing(63.90%)を9.11pt、固定学習率(66.79%)を6.22pt上回りました。3パターンの中で唯一、大きな差をつけて優勢だったのがOne-Cycle Policyです。
② 学習率の「山型」推移が収束に与えた影響
One-Cycle Policyの学習曲線を見ると、エポック9〜12あたり(LRがピーク0.005に到達する時期)はval_accuracyが61〜65%程度でAやBと大差ありません。ところがエポック13以降、LRが下降フェーズに入ると同時にval_accuracyが急伸し始め、エポック20で70.15%、エポック30で73.44%まで伸びています。これはOne-Cycle Policyの理論通り、「高LRで広く探索→急減衰で精密に着地」という挙動が実際に表れた結果と言えます。
参考として、MNISTで実施した学習率スケジューラ5種の比較実験では、OneCycleScheduler(val_accuracy 98.98%)がReduceLROnPlateau(99.03%)に僅差で次ぐ結果でした。今回のCIFAR-10・GAP構成では、One-Cycleが他手法にさらに大きく差をつける結果となり、タスクによる効果の出方の違いが見えた形です。
③ 学習時間への影響
学習時間はA:184.0秒、B:161.4秒、C:194.1秒でした。One-Cycleが最も長くなっていますが、固定学習率との差はわずか10秒程度で、バッチ単位のassign()によるオーバーヘッドはほぼ無視できるレベルです。一方でCosine AnnealingがAより約23秒短い点は、学習率スケジュール自体の計算コストというより、Colab実行環境側のGPU割り当て変動によるノイズの範囲と考えられます。
④ 過学習の度合いに差はあったか
最終エポックのtrain_accuracyとval_accuracyの差を見ると、A:2.33pt(0.6904→0.6671)、B:0.33pt(0.6432→0.6399)、C:6.66pt(0.8010→0.7344)でした。Cosine Annealingはほとんど過学習していない代わりに、そもそも到達した精度自体が低く「学習不足のまま収束」に近い状態です。One-Cycle Policyは3パターン中もっとも過学習の兆候(trainとvalの乖離)が大きいものの、val_accuracy自体がもっとも高いため、トレードオフとしては十分見合っています。ただしエポック数をさらに増やす場合は、Dropout率の引き上げなど過学習対策とのセット運用が必要になりそうです。
興味深いのは、固定学習率がCosine Annealingを上回った点です。これは前回のCosine Annealing vs ReduceLROnPlateau実験(固定学習率69.60% > Cosine Annealing68.76%)とも方向が一致しており、今回のalpha=0.01設定でのCosine Annealingは、固定学習率0.001よりも実質的に低い学習率で学習している時間が長く、30エポックという限られた予算の中では「まだ下げなくていい時期に下げてしまっている」可能性があります。2回の独立した実験で同じ傾向が出ているため、単発のノイズではなく再現性のある挙動と見て良さそうです。
実務での推奨
| 状況 | 推奨スケジュール | 理由 |
|---|---|---|
| エポック数が事前に固定されていて、精度を最優先したい | One-Cycle Policy | 今回の実験でtest_accuracy +6〜9ptと最も効果が大きかった |
| max_lrのチューニングに時間をかけられない | 固定学習率 | Cosine Annealing(alpha=0.01設定)より安定して良い結果だった |
| 過学習を極力避けたい・train/valの乖離を抑えたい | Cosine Annealing | 3パターン中もっとも過学習が少なかった(ただし到達精度も最も低い) |
| val_lossの停滞を見ながら安全運用したい | ReduceLROnPlateau(前回記事参照) | 実測ベースで調整するため事故が少ない |
- One-Cycle Policyは学習率を「上昇→下降」させる山型スケジュールで、Kerasには組み込み実装がないため自作コールバックで実現する
- CIFAR-10・30エポックの実験では、One-Cycle Policy(test_accuracy 73.01%)が固定学習率(66.79%)を6.2pt、Cosine Annealing(63.90%)を9.1pt上回った
- Cosine Annealingが固定学習率にすら劣るという結果は、前回のCosine Annealing vs ReduceLROnPlateau実験でも同様の傾向が見られており、alpha=0.01設定での「早すぎる減衰」が原因と考えられる
- One-Cycle Policyはtrain/valの乖離がやや大きく出るため、長期学習では過学習対策と組み合わせるのが望ましい
関連記事もあわせてどうぞ:
- 学習率スケジュールの基本比較 → Cosine Annealing vs ReduceLROnPlateau、学習率スケジュールどちらが有利?【Keras×CIFAR-10実験】
- 5種類のスケジューラを俯瞰 → Kerasで学習率スケジューラ5種を徹底比較|精度&収束の違いを実装・グラフ付きで解説
- 学習率そのものの最適値検証 → Adam学習率の最適値は?3パターンの比較実験【Keras】




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