勾配クリッピング(Gradient Clipping)で学習は安定する?なし・clipnorm・clipvalue比較【Keras×CIFAR-10実験】

投稿日:2026年6月14日日曜日 最終更新日:

CIFAR-10 CNN Google Colab Gradient Clipping Keras 過学習 画像分類

X f B! P L
勾配クリッピング(Gradient Clipping)で学習は安定する?なし・clipnorm・clipvalue比較【Keras×CIFAR-10実験】 アイキャッチ画像

「勾配爆発(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の引数動作
なし(デフォルト)勾配をそのまま使う
clipnormclipnorm=1.0勾配ベクトルのL2ノルムが指定値を超えたらスケールダウン。方向は保持
clipvalueclipvalue=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

clipnorm=

clipvalue=1.0

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 / Transformerclipnorm=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 を試すのが定石

関連記事