「勾配爆発(Gradient Explosion)が心配」「でもクリッピングしすぎると学習が遅くなるのでは?」——Kerasで学習を安定させる手段のひとつが Gradient Clipping(勾配クリッピング)です。
今回はGoogle ColabとCIFAR-10を使い、クリッピングなし・clipnorm=1.0・clipvalue=0.5 の3パターンを比較しました。「Adamなら不要では?」という仮説も、実験で確かめます。
- Gradient Clippingとは何か、clipnorm と clipvalue の違い
- Adam + 浅いCNNでもGradient Clippingの効果はあるか
- CIFAR-10での精度・学習安定性・過学習への影響
- どのケースでGradient Clippingを使うべきか
Gradient Clippingとは
バックプロパゲーションで計算された勾配が非常に大きくなると、パラメータが一気に更新されすぎる勾配爆発が起きます。Gradient Clippingはこれを防ぐために勾配の大きさを上限で制限する手法です。
| 方式 | Kerasの引数 | 動作 |
|---|---|---|
| なし(デフォルト) | — | 勾配をそのまま使う |
| clipnorm | clipnorm=1.0 | 勾配ベクトルのL2ノルムが指定値を超えたらスケールダウン。方向は保持。 |
| clipvalue | clipvalue=0.5 | 各勾配要素の値を [-0.5, 0.5] の範囲に個別クリップ。方向が変わる場合あり。 |
数式で確認
clipnorm(勾配ベクトル全体をスケーリング):
\[ \mathbf{g} \leftarrow \begin{cases} \mathbf{g} & \|\mathbf{g}\| \leq \text{clipnorm} \text{ のとき} \\[6pt] \dfrac{\text{clipnorm}}{\|\mathbf{g}\|} \cdot \mathbf{g} & \|\mathbf{g}\| > \text{clipnorm} \text{ のとき} \end{cases} \]
clipvalue(各要素を独立にクリップ):
\[ g_i \leftarrow \text{clip}(g_i,\; -\text{clipvalue},\; +\text{clipvalue}) \]
clipnormは勾配ベクトルを方向を変えずにスケーリングするため、更新の意味が保たれます。clipvalueは各要素を独立にクリップするため、方向が歪む可能性がある一方、実装がシンプルです。
Kerasでは
model.compile() ではなく オプティマイザのコンストラクタ に渡します。✅ 正しい例:
Adam(learning_rate=0.001, clipnorm=1.0)❌ 間違い例:
model.compile(optimizer='adam', clipnorm=1.0)(エラーになる)
実験コード
使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。Gradient Clipping以外の条件は全て同一にして、クリッピングの影響だけを取り出します。
① 環境準備(最初に一度だけ実行)
# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 0s (17.9 MB/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 92.9 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
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
def build_model(name):
"""Gradient Clipping以外は共通のベースラインCNN"""
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)
def compile_and_fit(model, optimizer):
model.compile(optimizer=optimizer,
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 ━━━━━━━━━━━━━━━━━━━━ 314s 2us/step
③ 3パターンの学習実行
# ── 実験設定:Gradient Clippingの種類だけを変える ─────
configs = [
('A_no_clip', keras.optimizers.Adam(learning_rate=0.001)),
('B_clipnorm', keras.optimizers.Adam(learning_rate=0.001, clipnorm=1.0)),
('C_clipvalue', keras.optimizers.Adam(learning_rate=0.001, clipvalue=0.5)),
]
histories, times, scores = {}, {}, {}
for name, optimizer in configs:
print(f"\n=== {name} ===")
np.random.seed(SEED)
tf.random.set_seed(SEED)
model = build_model(name)
h, t = compile_and_fit(model, optimizer)
s = model.evaluate(x_test, y_test, verbose=0)
label = name.split('_', 1)[1]
histories[label] = h
times[label] = t
scores[label] = s
print(f"学習時間:{t:.1f}秒 test_accuracy:{s[1]:.4f}")
実行結果をクリックして内容を開く
=== A_no_clip === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 10s 8ms/step - accuracy: 0.2637 - loss: 1.9432 - val_accuracy: 0.3784 - val_loss: 1.6991 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3778 - loss: 1.6727 - val_accuracy: 0.4279 - val_loss: 1.5793 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4295 - loss: 1.5496 - val_accuracy: 0.4594 - val_loss: 1.4881 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4619 - loss: 1.4676 - val_accuracy: 0.4782 - val_loss: 1.4203 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4857 - loss: 1.4097 - val_accuracy: 0.4936 - val_loss: 1.3739 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5038 - loss: 1.3601 - val_accuracy: 0.5142 - val_loss: 1.3267 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5188 - loss: 1.3195 - val_accuracy: 0.5248 - val_loss: 1.2938 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 7ms/step - accuracy: 0.5303 - loss: 1.2818 - val_accuracy: 0.5422 - val_loss: 1.2472 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5414 - loss: 1.2578 - val_accuracy: 0.5587 - val_loss: 1.2105 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5526 - loss: 1.2285 - val_accuracy: 0.5620 - val_loss: 1.2102 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5611 - loss: 1.2028 - val_accuracy: 0.5723 - val_loss: 1.1734 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5734 - loss: 1.1788 - val_accuracy: 0.5791 - val_loss: 1.1636 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5822 - loss: 1.1527 - val_accuracy: 0.5903 - val_loss: 1.1293 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5878 - loss: 1.1327 - val_accuracy: 0.6030 - val_loss: 1.1054 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5971 - loss: 1.1093 - val_accuracy: 0.6089 - val_loss: 1.0917 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6054 - loss: 1.0937 - val_accuracy: 0.6156 - val_loss: 1.0701 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6106 - loss: 1.0737 - val_accuracy: 0.6177 - val_loss: 1.0703 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6197 - loss: 1.0538 - val_accuracy: 0.6241 - val_loss: 1.0369 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6268 - loss: 1.0382 - val_accuracy: 0.6311 - val_loss: 1.0208 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6302 - loss: 1.0245 - val_accuracy: 0.6376 - val_loss: 1.0056 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6375 - loss: 1.0079 - val_accuracy: 0.6406 - val_loss: 0.9920 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6409 - loss: 0.9950 - val_accuracy: 0.6374 - val_loss: 1.0114 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6452 - loss: 0.9824 - val_accuracy: 0.6465 - val_loss: 0.9815 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6499 - loss: 0.9687 - val_accuracy: 0.6541 - val_loss: 0.9632 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6547 - loss: 0.9568 - val_accuracy: 0.6571 - val_loss: 0.9555 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6612 - loss: 0.9459 - val_accuracy: 0.6555 - val_loss: 0.9657 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6657 - loss: 0.9346 - val_accuracy: 0.6583 - val_loss: 0.9601 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6684 - loss: 0.9237 - val_accuracy: 0.6663 - val_loss: 0.9349 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6720 - loss: 0.9141 - val_accuracy: 0.6668 - val_loss: 0.9384 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6772 - loss: 0.9022 - val_accuracy: 0.6637 - val_loss: 0.9497 学習時間:125.6秒 test_accuracy:0.6634 === B_clipnorm === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 8ms/step - accuracy: 0.2580 - loss: 1.9312 - val_accuracy: 0.3370 - val_loss: 1.7463 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3451 - loss: 1.7092 - val_accuracy: 0.4034 - val_loss: 1.6024 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4078 - loss: 1.5895 - val_accuracy: 0.4606 - val_loss: 1.4908 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4530 - loss: 1.4944 - val_accuracy: 0.4731 - val_loss: 1.4333 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4785 - loss: 1.4209 - val_accuracy: 0.4970 - val_loss: 1.3780 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.5005 - loss: 1.3638 - val_accuracy: 0.5202 - val_loss: 1.3191 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5177 - loss: 1.3212 - val_accuracy: 0.5313 - val_loss: 1.2800 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5294 - loss: 1.2842 - val_accuracy: 0.5361 - val_loss: 1.2566 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5447 - loss: 1.2533 - val_accuracy: 0.5482 - val_loss: 1.2321 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5568 - loss: 1.2183 - val_accuracy: 0.5711 - val_loss: 1.1728 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5685 - loss: 1.1836 - val_accuracy: 0.5734 - val_loss: 1.1628 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5793 - loss: 1.1614 - val_accuracy: 0.5903 - val_loss: 1.1230 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5885 - loss: 1.1409 - val_accuracy: 0.5905 - val_loss: 1.1405 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5971 - loss: 1.1171 - val_accuracy: 0.5984 - val_loss: 1.1111 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6058 - loss: 1.0937 - val_accuracy: 0.6153 - val_loss: 1.0687 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6125 - loss: 1.0775 - val_accuracy: 0.6148 - val_loss: 1.0604 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6189 - loss: 1.0620 - val_accuracy: 0.6170 - val_loss: 1.0510 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6277 - loss: 1.0394 - val_accuracy: 0.6272 - val_loss: 1.0247 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6314 - loss: 1.0267 - val_accuracy: 0.6269 - val_loss: 1.0241 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6395 - loss: 1.0088 - val_accuracy: 0.6321 - val_loss: 1.0137 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6433 - loss: 0.9937 - val_accuracy: 0.6425 - val_loss: 0.9978 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6481 - loss: 0.9786 - val_accuracy: 0.6420 - val_loss: 0.9820 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6560 - loss: 0.9659 - val_accuracy: 0.6492 - val_loss: 0.9688 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6599 - loss: 0.9518 - val_accuracy: 0.6573 - val_loss: 0.9533 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6637 - loss: 0.9402 - val_accuracy: 0.6525 - val_loss: 0.9636 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6683 - loss: 0.9271 - val_accuracy: 0.6567 - val_loss: 0.9597 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6719 - loss: 0.9194 - val_accuracy: 0.6636 - val_loss: 0.9467 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6767 - loss: 0.9053 - val_accuracy: 0.6680 - val_loss: 0.9281 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6794 - loss: 0.8949 - val_accuracy: 0.6711 - val_loss: 0.9201 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6813 - loss: 0.8857 - val_accuracy: 0.6774 - val_loss: 0.9115 学習時間:124.0秒 test_accuracy:0.6675 === C_clipvalue === Epoch 1/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 8s 9ms/step - accuracy: 0.2592 - loss: 1.9375 - val_accuracy: 0.3530 - val_loss: 1.7242 Epoch 2/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3668 - loss: 1.6776 - val_accuracy: 0.4327 - val_loss: 1.5632 Epoch 3/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4314 - loss: 1.5429 - val_accuracy: 0.4658 - val_loss: 1.4664 Epoch 4/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4670 - loss: 1.4527 - val_accuracy: 0.4880 - val_loss: 1.3962 Epoch 5/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4904 - loss: 1.3905 - val_accuracy: 0.5085 - val_loss: 1.3391 Epoch 6/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5101 - loss: 1.3424 - val_accuracy: 0.5268 - val_loss: 1.2919 Epoch 7/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5244 - loss: 1.3020 - val_accuracy: 0.5377 - val_loss: 1.2740 Epoch 8/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5356 - loss: 1.2702 - val_accuracy: 0.5489 - val_loss: 1.2372 Epoch 9/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5476 - loss: 1.2414 - val_accuracy: 0.5534 - val_loss: 1.2269 Epoch 10/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5568 - loss: 1.2148 - val_accuracy: 0.5705 - val_loss: 1.1910 Epoch 11/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5697 - loss: 1.1878 - val_accuracy: 0.5810 - val_loss: 1.1631 Epoch 12/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5769 - loss: 1.1676 - val_accuracy: 0.5884 - val_loss: 1.1396 Epoch 13/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5873 - loss: 1.1419 - val_accuracy: 0.5993 - val_loss: 1.1113 Epoch 14/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5947 - loss: 1.1227 - val_accuracy: 0.6047 - val_loss: 1.0990 Epoch 15/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6022 - loss: 1.0999 - val_accuracy: 0.6054 - val_loss: 1.0860 Epoch 16/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6086 - loss: 1.0826 - val_accuracy: 0.6136 - val_loss: 1.0648 Epoch 17/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6146 - loss: 1.0665 - val_accuracy: 0.6109 - val_loss: 1.0639 Epoch 18/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6240 - loss: 1.0480 - val_accuracy: 0.6203 - val_loss: 1.0442 Epoch 19/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6284 - loss: 1.0337 - val_accuracy: 0.6278 - val_loss: 1.0185 Epoch 20/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6338 - loss: 1.0170 - val_accuracy: 0.6327 - val_loss: 1.0057 Epoch 21/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6417 - loss: 1.0028 - val_accuracy: 0.6365 - val_loss: 1.0045 Epoch 22/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6474 - loss: 0.9894 - val_accuracy: 0.6348 - val_loss: 1.0008 Epoch 23/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6514 - loss: 0.9749 - val_accuracy: 0.6469 - val_loss: 0.9688 Epoch 24/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.6540 - loss: 0.9608 - val_accuracy: 0.6503 - val_loss: 0.9720 Epoch 25/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6571 - loss: 0.9478 - val_accuracy: 0.6554 - val_loss: 0.9531 Epoch 26/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6683 - loss: 0.9330 - val_accuracy: 0.6570 - val_loss: 0.9521 Epoch 27/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6711 - loss: 0.9217 - val_accuracy: 0.6594 - val_loss: 0.9453 Epoch 28/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6715 - loss: 0.9135 - val_accuracy: 0.6685 - val_loss: 0.9270 Epoch 29/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6758 - loss: 0.9022 - val_accuracy: 0.6697 - val_loss: 0.9208 Epoch 30/30 625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6789 - loss: 0.8905 - val_accuracy: 0.6722 - val_loss: 0.9174 学習時間:122.9秒 test_accuracy:0.6695
④ グラフ+サマリー出力
import pandas as pd
label_map = {
'no_clip': 'なし',
'clipnorm': 'clipnorm=1.0',
'clipvalue': 'clipvalue=0.5',
}
# ── 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_map[label])
axes[1].plot(h.history['val_loss'], label=label_map[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('gradient_clipping_val.png', dpi=150)
plt.show()
# ── train_loss vs val_loss(過学習の乖離確認)──────────
fig2, axes2 = plt.subplots(1, 3, figsize=(18, 5))
for i, (label, 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(label_map[label])
axes2[i].set_xlabel('Epoch'); axes2[i].legend(); axes2[i].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('gradient_clipping_overfit.png', dpi=150)
plt.show()
# ── 結果サマリー(pandas) ────────────────────────────
rows = []
for label in ['no_clip', 'clipnorm', 'clipvalue']:
val_acc = histories[label].history['val_accuracy'][-1]
test_acc = scores[label][1]
t = times[label]
rows.append({
'パターン': label_map[label],
'val_accuracy': f"{val_acc:.4f}",
'test_accuracy': f"{test_acc:.4f}",
'学習時間(s)': f"{t:.1f}",
})
df = pd.DataFrame(rows).sort_values('test_accuracy', ascending=False)
print(df.to_string(index=False))
実行結果サマリー
パターン val_accuracy test_accuracy 学習時間(s)
clipvalue=0.5 0.6722 0.6695 122.9
clipnorm=1.0 0.6774 0.6675 124.0
なし 0.6637 0.6634 125.6
実験結果
精度グラフ
損失グラフ
clipnorm=0.5
clipvalue=1.0
なし(デフォルト)
| パターン | val_accuracy | test_accuracy | 学習時間 |
|---|---|---|---|
| clipvalue=0.5 | 0.6722 | 0.6695 | 122.9秒 |
| clipnorm=1.0 | 0.6774 | 0.6675 | 124.0秒 |
| なし(デフォルト) | 0.6637 | 0.6634 | 125.6秒 |
考察
① クリッピングありが「なし」を上回った——予想と異なる結果
「Adam + 浅いCNNではGradient Clippingの効果は小さいはず」という事前予想に反して、クリッピングあり2パターンがいずれも「なし」を上回りました。
とはいえ、その差は最大でも約0.006pt(0.6%)と小幅です。乱数シードを変えれば順位が入れ替わる可能性があり、「Adamには不要」という一般論を否定するほどではありません。それでも、今回の条件下ではわずかに安定した勾配更新が精度に寄与した可能性が示唆されます。
② clipvalue=0.5 が test_accuracy で最高——ただし val_accuracy ではclipnormが上
val_accuracy(最終エポック)では clipnorm=1.0(0.6774)が最高だった一方で、test_accuracy では clipvalue=0.5(0.6695)が1位になりました。この逆転は、val_lossの推移の違いによるものです。
- clipnorm:勾配の方向を保ちながらスケールを制限するため、val_lossの推移が安定しやすい
- clipvalue:各要素を独立にクリップするため理論上は方向が歪むが、今回はtestセットへの汎化でわずかに有利に出た
ただし0.002ptの差は誤差範囲内です。どちらが優れているかの判断には複数シードでの追試が必要です。
③ 学習時間は「なし」が最長——クリッピングのオーバーヘッドは実質ゼロ
学習時間は3パターンとも 122〜126秒でほぼ同等でした。クリッピング処理自体の計算コストは無視できるレベルです。「クリッピングを入れると遅くなる」という懸念は今回の規模では当てはまりません。
④ Gradient Clippingが本当に効くケース
今回の差が小さかった理由は、Adam自体が勾配の大きさに適応的な学習率を適用する仕組みを持つためです。勾配爆発が起きやすい状況では効果が大きくなります。
| ケース | Gradient Clippingの有効性 |
|---|---|
| RNN / LSTM(時系列・テキスト) | ◎ 必須に近い。勾配爆発が頻繁に発生する |
| SGD使用時 | ○ 適応的な調整がないため効果が出やすい |
| Transformer / 深いネットワーク | ○ clipnorm=1.0 がデファクトスタンダード |
| Adam + 浅いCNN(今回) | △ 差は小さい。ただしわずかに安定化する可能性あり |
⑤ clipnorm と clipvalue、どちらを選ぶか
一般的にはclipnorm を推奨します。勾配ベクトルの方向を保ったまま大きさだけを制限するため、学習の意味が維持されます。clipvalueは各要素を独立にクリップするため、状況によっては勾配の方向が変わり不安定になることがあります。今回の実験では差がほとんどありませんでしたが、理論的な安全性ではclipnormに分があります。
実務での推奨
| モデル・状況 | 推奨 | 理由 |
|---|---|---|
| RNN / LSTM / Transformer | clipnorm=1.0 | 勾配爆発対策として標準装備。必ず使う |
| SGDで学習が発散する場合 | clipnorm=1.0〜5.0 | 発散を抑えつつ学習継続できる |
| Adam + 浅いCNN(CIFAR-10規模) | まずなしで試す。不安定ならclipnorm=1.0 | 効果は小さいが入れてもコストはほぼゼロ |
| clipvalue の使用 | clipnorm を優先 | 勾配の方向を歪めるリスクがある |
- Gradient Clippingは勾配爆発を防ぐ手法。Kerasではオプティマイザのコンストラクタに
clipnormまたはclipvalueを渡す - clipnorm はベクトル全体をスケーリングして方向を保持。clipvalue は各要素を個別クリップするため方向が歪む可能性がある
- 今回の実験(Adam + 浅いCNN)では差は小さいが、クリッピングありが「なし」をわずかに上回る結果になった
- 学習時間への影響はほぼゼロ。「遅くなる」という懸念は不要
- RNN・LSTM・Transformerや、SGDで学習が不安定なときに真価を発揮する。まず
clipnorm=1.0を試すのが定石
関連記事
- オプティマイザの比較 → optimizerを変えると精度はどう変わる?(Adam vs SGD vs RMSprop)
- 学習率スケジュールの比較 → Cosine Annealing vs ReduceLROnPlateau、学習率スケジュールどちらが有利?
- Adam学習率の比較実験 → Adam学習率の最適値は?3パターンの比較実験
- Weight Decayとの比較 → Weight Decay(L2正則化)の強さで過学習はどう変わる?
- Dense層の活性化関数の比較 → Dense層の活性化関数を比較|relu vs sigmoid vs tanh vs linear






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