L1正則化 vs L2正則化、CNNの重みはどう変わる?【Keras×CIFAR-10実験】

投稿日:2026年8月15日土曜日 最終更新日:

CIFAR-10 CNN Google Colab Keras L1正則化 L2正則化 過学習 画像分類

X f B! L
L1正則化 vs L2正則化、CNNの重みはどう変わる?【Keras×CIFAR-10実験】 アイキャッチ画像

「L2正則化(Weight Decay)は重みを均等に小さくするけど、L1正則化は重みをゼロにする」という説明を見たことはありませんか?前回の実験では、すでにDropoutが入ったモデルにL2正則化を追加すると精度が単調に悪化することがわかりました。では、重みを完全にゼロへ押しつぶす性質を持つL1正則化ではどうなるのでしょうか。今回はL1とL2を同じ強度で比較し、精度だけでなく「実際にどれだけ重みがゼロに近づいたか」というスパース性も実験で検証します。

📘 この記事でわかること
  • L1正則化とL2正則化の数式上の違いとKerasでの実装方法(l1 / l2 / l1_l2)
  • 同じ強度(λ=1e-4)で比較したときの精度・過学習への影響の違い
  • L1正則化によって重みが実際にどれだけスパース化(ゼロ化)されるか
  • Dropoutとの二重正則化問題はL1でも起きるのか(前回のL2実験との比較)

L1正則化とL2正則化の違い

どちらも損失関数に重みに関するペナルティ項を加算し、重みが大きくなりすぎるのを抑える正則化手法です。ペナルティのかけ方が異なります。

L2正則化(Weight Decay):\( L_{\text{total}} = L_{\text{CE}} + \lambda \sum_{i} w_i^2 \)

L1正則化:\( L_{\text{total}} = L_{\text{CE}} + \lambda \sum_{i} |w_i| \)

(\(\lambda\):正則化の強度、\(w_i\):各重みパラメータ)

見た目は似ていますが、勾配の性質が大きく異なります。L2は重みの値に比例した勾配(大きい重みほど強く縮小)がかかるため、重みは全体的になだらかに小さくなります。一方L1は重みの符号だけで決まる一定の勾配がかかるため、小さい重みは押し切られてちょうど0になりやすく、モデル全体がスパース(疎)になる性質があります。

L1正則化L2正則化
勾配の大きさ常に一定(符号のみ)重みの値に比例
重みへの効果一部の重みを完全にゼロへ全体をなだらかに縮小
典型的な用途特徴選択・モデルの軽量化汎化性能の向上(Weight Decay)
Kerasでの実装regularizers.l1(λ)regularizers.l2(λ)
併用regularizers.l1_l2(l1=λ1, l2=λ2)(Elastic Net)

前回の実験(Weight Decay(L2正則化)の強さで過学習はどう変わる?)では、Dropout(0.2) がすでに入っているモデルにL2正則化(λ=1e-4)を追加すると、val_accuracyが -1.5pt悪化するという結果が出ました。今回はL1正則化でも同様の悪化が起きるのか、そしてL1特有のスパース化がどの程度実際に発生するのかを、同じベースラインモデルで検証します。


実験コード

使用環境はGoogle Colab(GPU:T4)、データセットはCIFAR-10です。正則化の種類以外の条件(Conv2D×2、GlobalAveragePooling2D、Dropout=0.2、Adam lr=0.001、batch_size=64、30epoch)はすべて固定し、正則化の種類だけを変えます。

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

# ── 環境準備(最初に一度だけ実行)──────────────────────
!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 67 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 3s (2,694 kB/s)
Selecting previously unselected package fonts-ipafont-gothic.
(Reading database ... 122492 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 117.6 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
from tensorflow.keras.regularizers import l1, l2, l1_l2
import numpy as np
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(reg, name):
    return keras.Sequential([
        keras.layers.Input(shape=(32, 32, 3)),
        keras.layers.Conv2D(64,  (3, 3), activation='relu', padding='same',
                            kernel_regularizer=reg),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.Conv2D(128, (3, 3), activation='relu', padding='same',
                            kernel_regularizer=reg),
        keras.layers.MaxPooling2D((2, 2)),
        keras.layers.GlobalAveragePooling2D(),
        keras.layers.Dense(128, activation='relu', kernel_regularizer=reg),
        keras.layers.Dropout(0.2),
        keras.layers.Dense(10, activation='softmax'),
    ], name=name)

def compile_and_fit(model):
    model.compile(optimizer='adam',
                  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

# ── 重みのスパース率を計算(|w| < threshold をゼロ扱い)──
def compute_sparsity(model, threshold=1e-3):
    total, near_zero = 0, 0
    for layer in model.layers:
        if hasattr(layer, 'kernel'):
            w = layer.kernel.numpy()
            total += w.size
            near_zero += int(np.sum(np.abs(w) < threshold))
    return near_zero / total, total
実行結果をクリックして内容を開く
Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
170498071/170498071 ━━━━━━━━━━━━━━━━━━━━ 1277s 7us/step
⚠️ ハマりポイント①:kernel_regularizerはbiasには適用されない
kernel_regularizerは重み行列(kernel)のみに正則化をかけ、バイアス項(bias)には適用されません。バイアスも正則化したい場合はbias_regularizerを別途指定する必要があります。今回はKerasのデフォルト動作に合わせ、kernelのみを対象にしています。
⚠️ ハマりポイント②:l1_l2()のデフォルト値に注意
regularizers.l1_l2()を引数なしで呼ぶと、デフォルトでl1=0.01, l2=0.01という比較的強い値が設定されます。前回の実験でwd=1e-2は精度が0.35まで崩壊することが確認済みのため、Elastic Netを試す際は必ずl1_l2(l1=λ1, l2=λ2)と明示的に強度を指定してください。
⚠️ ハマりポイント③:スパース率は厳密な0ではなく閾値で判定する
L1正則化をかけても、勾配降下法による更新の都合上、重みが浮動小数点で厳密に0.0になることは稀です。「スパース化された」とみなすにはw == 0ではなく、|w| < 1e-3のような閾値を使って判定する必要があります。

4パターンの学習実行

configs = [
    (None,                          'A_none'),
    (l2(1e-4),                      'B_l2'),
    (l1(1e-4),                      'C_l1'),
    (l1_l2(l1=1e-4, l2=1e-4),       'D_l1l2'),
]
histories, times, scores, sparsities, models = {}, {}, {}, {}, {}

for reg, name in configs:
    print(f"\n=== {name} ===")
    model = build_model(reg, name)
    h, t = compile_and_fit(model)
    s = model.evaluate(x_test, y_test, verbose=0)
    sp, total_w = compute_sparsity(model)
    label = name.split('_')[1]
    histories[label]  = h
    times[label]      = t
    scores[label]     = s
    sparsities[label] = sp
    models[label]     = model  # ヒストグラム描画用に学習済みモデルを保持
    print(f"学習時間:{t:.1f}秒 test_accuracy:{s[1]:.4f} スパース率:{sp*100:.2f}%(全{total_w}重み中)")
実行結果をクリックして内容を開く
=== A_none ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 18s 11ms/step - accuracy: 0.2689 - loss: 1.9178 - val_accuracy: 0.3456 - val_loss: 1.7433
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3776 - loss: 1.6647 - val_accuracy: 0.4163 - val_loss: 1.5942
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.4286 - loss: 1.5549 - val_accuracy: 0.4510 - val_loss: 1.4910
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4653 - loss: 1.4586 - val_accuracy: 0.4913 - val_loss: 1.3956
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4916 - loss: 1.3919 - val_accuracy: 0.5070 - val_loss: 1.3408
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5125 - loss: 1.3369 - val_accuracy: 0.5044 - val_loss: 1.3662
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5238 - loss: 1.3035 - val_accuracy: 0.5403 - val_loss: 1.2514
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5359 - loss: 1.2695 - val_accuracy: 0.5478 - val_loss: 1.2450
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5527 - loss: 1.2324 - val_accuracy: 0.5503 - val_loss: 1.2576
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5596 - loss: 1.2100 - val_accuracy: 0.5723 - val_loss: 1.1868
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5697 - loss: 1.1832 - val_accuracy: 0.5781 - val_loss: 1.1634
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5797 - loss: 1.1555 - val_accuracy: 0.5591 - val_loss: 1.2183
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5892 - loss: 1.1356 - val_accuracy: 0.5664 - val_loss: 1.1825
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5983 - loss: 1.1122 - val_accuracy: 0.6101 - val_loss: 1.0771
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6026 - loss: 1.0966 - val_accuracy: 0.5937 - val_loss: 1.1170
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6109 - loss: 1.0757 - val_accuracy: 0.6211 - val_loss: 1.0570
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6196 - loss: 1.0554 - val_accuracy: 0.6170 - val_loss: 1.0571
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6235 - loss: 1.0404 - val_accuracy: 0.6195 - val_loss: 1.0364
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6306 - loss: 1.0254 - val_accuracy: 0.6210 - val_loss: 1.0319
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6366 - loss: 1.0123 - val_accuracy: 0.6404 - val_loss: 1.0027
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6434 - loss: 0.9935 - val_accuracy: 0.6488 - val_loss: 0.9742
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6467 - loss: 0.9855 - val_accuracy: 0.6445 - val_loss: 0.9864
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6523 - loss: 0.9697 - val_accuracy: 0.6497 - val_loss: 0.9772
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6535 - loss: 0.9583 - val_accuracy: 0.6486 - val_loss: 0.9706
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6616 - loss: 0.9464 - val_accuracy: 0.6438 - val_loss: 0.9991
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6647 - loss: 0.9383 - val_accuracy: 0.6475 - val_loss: 0.9790
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6671 - loss: 0.9235 - val_accuracy: 0.6639 - val_loss: 0.9365
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6719 - loss: 0.9130 - val_accuracy: 0.6685 - val_loss: 0.9177
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6755 - loss: 0.9014 - val_accuracy: 0.6519 - val_loss: 0.9575
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6816 - loss: 0.8916 - val_accuracy: 0.6645 - val_loss: 0.9237
学習時間:133.9秒 test_accuracy:0.6687 スパース率:0.69%(全93120重み中)

=== B_l2 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 9ms/step - accuracy: 0.2654 - loss: 1.9322 - val_accuracy: 0.3305 - val_loss: 1.8025
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3560 - loss: 1.7277 - val_accuracy: 0.3778 - val_loss: 1.6629
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4114 - loss: 1.6198 - val_accuracy: 0.4470 - val_loss: 1.5321
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4507 - loss: 1.5297 - val_accuracy: 0.4857 - val_loss: 1.4652
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4730 - loss: 1.4811 - val_accuracy: 0.4961 - val_loss: 1.4258
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4953 - loss: 1.4231 - val_accuracy: 0.5095 - val_loss: 1.3854
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5131 - loss: 1.3887 - val_accuracy: 0.5180 - val_loss: 1.3828
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5273 - loss: 1.3537 - val_accuracy: 0.5214 - val_loss: 1.3888
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 7ms/step - accuracy: 0.5379 - loss: 1.3307 - val_accuracy: 0.5196 - val_loss: 1.3668
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5465 - loss: 1.3083 - val_accuracy: 0.5626 - val_loss: 1.2668
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5581 - loss: 1.2811 - val_accuracy: 0.5755 - val_loss: 1.2319
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5677 - loss: 1.2598 - val_accuracy: 0.5615 - val_loss: 1.2765
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5751 - loss: 1.2425 - val_accuracy: 0.5798 - val_loss: 1.2267
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5875 - loss: 1.2155 - val_accuracy: 0.5774 - val_loss: 1.2332
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5900 - loss: 1.2024 - val_accuracy: 0.5980 - val_loss: 1.1877
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5990 - loss: 1.1867 - val_accuracy: 0.5904 - val_loss: 1.1865
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6045 - loss: 1.1712 - val_accuracy: 0.6154 - val_loss: 1.1524
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6106 - loss: 1.1567 - val_accuracy: 0.6106 - val_loss: 1.1631
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6168 - loss: 1.1384 - val_accuracy: 0.6262 - val_loss: 1.1108
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6248 - loss: 1.1268 - val_accuracy: 0.5871 - val_loss: 1.2063
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6301 - loss: 1.1105 - val_accuracy: 0.6272 - val_loss: 1.1059
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6288 - loss: 1.1043 - val_accuracy: 0.6203 - val_loss: 1.1288
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6386 - loss: 1.0909 - val_accuracy: 0.6462 - val_loss: 1.0731
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.6390 - loss: 1.0821 - val_accuracy: 0.6482 - val_loss: 1.0634
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6492 - loss: 1.0681 - val_accuracy: 0.6451 - val_loss: 1.0721
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6510 - loss: 1.0632 - val_accuracy: 0.6255 - val_loss: 1.1120
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 7ms/step - accuracy: 0.6555 - loss: 1.0529 - val_accuracy: 0.6623 - val_loss: 1.0366
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6586 - loss: 1.0426 - val_accuracy: 0.6422 - val_loss: 1.0953
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.6632 - loss: 1.0295 - val_accuracy: 0.6592 - val_loss: 1.0331
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.6670 - loss: 1.0268 - val_accuracy: 0.6542 - val_loss: 1.0700
学習時間:127.8秒 test_accuracy:0.6506 スパース率:33.56%(全93120重み中)

=== C_l1 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 9ms/step - accuracy: 0.2257 - loss: 2.1246 - val_accuracy: 0.2931 - val_loss: 1.9317
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.2991 - loss: 1.9065 - val_accuracy: 0.3332 - val_loss: 1.8284
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3291 - loss: 1.8277 - val_accuracy: 0.3679 - val_loss: 1.7761
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3535 - loss: 1.7827 - val_accuracy: 0.3813 - val_loss: 1.7335
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3723 - loss: 1.7497 - val_accuracy: 0.3893 - val_loss: 1.7139
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3909 - loss: 1.7113 - val_accuracy: 0.4020 - val_loss: 1.6986
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4129 - loss: 1.6677 - val_accuracy: 0.4476 - val_loss: 1.6125
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4361 - loss: 1.6223 - val_accuracy: 0.4526 - val_loss: 1.5874
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4489 - loss: 1.5900 - val_accuracy: 0.4652 - val_loss: 1.5511
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4599 - loss: 1.5638 - val_accuracy: 0.4798 - val_loss: 1.5209
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4736 - loss: 1.5395 - val_accuracy: 0.4734 - val_loss: 1.5380
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4801 - loss: 1.5206 - val_accuracy: 0.4920 - val_loss: 1.4873
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4907 - loss: 1.4979 - val_accuracy: 0.4896 - val_loss: 1.4957
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4961 - loss: 1.4839 - val_accuracy: 0.5047 - val_loss: 1.4616
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5066 - loss: 1.4619 - val_accuracy: 0.5236 - val_loss: 1.4290
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5102 - loss: 1.4551 - val_accuracy: 0.5343 - val_loss: 1.4060
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5165 - loss: 1.4399 - val_accuracy: 0.5413 - val_loss: 1.3873
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5230 - loss: 1.4202 - val_accuracy: 0.5285 - val_loss: 1.4163
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5272 - loss: 1.4093 - val_accuracy: 0.5359 - val_loss: 1.3889
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5336 - loss: 1.3996 - val_accuracy: 0.5460 - val_loss: 1.3762
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5342 - loss: 1.3895 - val_accuracy: 0.5367 - val_loss: 1.3893
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5419 - loss: 1.3787 - val_accuracy: 0.5490 - val_loss: 1.3601
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5468 - loss: 1.3664 - val_accuracy: 0.5475 - val_loss: 1.3550
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5499 - loss: 1.3538 - val_accuracy: 0.5664 - val_loss: 1.3150
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 6ms/step - accuracy: 0.5513 - loss: 1.3484 - val_accuracy: 0.5638 - val_loss: 1.3227
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 6s 7ms/step - accuracy: 0.5580 - loss: 1.3434 - val_accuracy: 0.5697 - val_loss: 1.3052
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5587 - loss: 1.3304 - val_accuracy: 0.5449 - val_loss: 1.3759
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5624 - loss: 1.3190 - val_accuracy: 0.5667 - val_loss: 1.3136
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5653 - loss: 1.3140 - val_accuracy: 0.5671 - val_loss: 1.3375
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5659 - loss: 1.3127 - val_accuracy: 0.5813 - val_loss: 1.2772
学習時間:126.6秒 test_accuracy:0.5758 スパース率:85.28%(全93120重み中)

=== D_l1l2 ===
Epoch 1/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 9s 10ms/step - accuracy: 0.2303 - loss: 2.1261 - val_accuracy: 0.2609 - val_loss: 2.0086
Epoch 2/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3022 - loss: 1.8920 - val_accuracy: 0.3215 - val_loss: 1.8503
Epoch 3/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3374 - loss: 1.8182 - val_accuracy: 0.3725 - val_loss: 1.7574
Epoch 4/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.3641 - loss: 1.7699 - val_accuracy: 0.3788 - val_loss: 1.7374
Epoch 5/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.3876 - loss: 1.7266 - val_accuracy: 0.4028 - val_loss: 1.6945
Epoch 6/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4099 - loss: 1.6777 - val_accuracy: 0.4046 - val_loss: 1.6905
Epoch 7/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4246 - loss: 1.6478 - val_accuracy: 0.4428 - val_loss: 1.6061
Epoch 8/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4414 - loss: 1.6184 - val_accuracy: 0.4472 - val_loss: 1.5923
Epoch 9/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4444 - loss: 1.6016 - val_accuracy: 0.4321 - val_loss: 1.6603
Epoch 10/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 5s 8ms/step - accuracy: 0.4585 - loss: 1.5760 - val_accuracy: 0.4746 - val_loss: 1.5338
Epoch 11/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4678 - loss: 1.5572 - val_accuracy: 0.4741 - val_loss: 1.5279
Epoch 12/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4780 - loss: 1.5355 - val_accuracy: 0.4895 - val_loss: 1.5051
Epoch 13/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.4876 - loss: 1.5141 - val_accuracy: 0.4992 - val_loss: 1.4892
Epoch 14/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4943 - loss: 1.4962 - val_accuracy: 0.5136 - val_loss: 1.4679
Epoch 15/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.4974 - loss: 1.4787 - val_accuracy: 0.5161 - val_loss: 1.4462
Epoch 16/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5079 - loss: 1.4675 - val_accuracy: 0.5271 - val_loss: 1.4196
Epoch 17/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5109 - loss: 1.4538 - val_accuracy: 0.5104 - val_loss: 1.4480
Epoch 18/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5202 - loss: 1.4356 - val_accuracy: 0.5293 - val_loss: 1.4091
Epoch 19/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5244 - loss: 1.4240 - val_accuracy: 0.5127 - val_loss: 1.4584
Epoch 20/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5304 - loss: 1.4092 - val_accuracy: 0.5470 - val_loss: 1.3704
Epoch 21/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5322 - loss: 1.4013 - val_accuracy: 0.5509 - val_loss: 1.3583
Epoch 22/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5362 - loss: 1.3948 - val_accuracy: 0.5597 - val_loss: 1.3510
Epoch 23/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5433 - loss: 1.3797 - val_accuracy: 0.5534 - val_loss: 1.3544
Epoch 24/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5432 - loss: 1.3764 - val_accuracy: 0.5508 - val_loss: 1.3577
Epoch 25/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5484 - loss: 1.3622 - val_accuracy: 0.5515 - val_loss: 1.3554
Epoch 26/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5533 - loss: 1.3547 - val_accuracy: 0.5581 - val_loss: 1.3298
Epoch 27/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5582 - loss: 1.3415 - val_accuracy: 0.5694 - val_loss: 1.3106
Epoch 28/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5590 - loss: 1.3392 - val_accuracy: 0.5616 - val_loss: 1.3201
Epoch 29/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 7ms/step - accuracy: 0.5603 - loss: 1.3334 - val_accuracy: 0.5673 - val_loss: 1.3156
Epoch 30/30
625/625 ━━━━━━━━━━━━━━━━━━━━ 4s 6ms/step - accuracy: 0.5697 - loss: 1.3252 - val_accuracy: 0.5799 - val_loss: 1.2972
学習時間:126.3秒 test_accuracy:0.5768 スパース率:86.43%(全93120重み中)

グラフ+サマリー

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

# ── 重み分布のヒストグラム(L1のスパース化を可視化)─────
fig2, axes2 = plt.subplots(2, 2, figsize=(14, 10))
axes2 = axes2.flatten()

for i, label in enumerate(['none', 'l2', 'l1', 'l1l2']):
    model = models[label]  # 学習済みモデルから重みを取得
    all_weights = np.concatenate([
        layer.kernel.numpy().flatten()
        for layer in model.layers if hasattr(layer, 'kernel')
    ])
    axes2[i].hist(all_weights, bins=100, range=(-0.3, 0.3))
    axes2[i].axvline(0, color='red', linewidth=0.8)
    axes2[i].set_title(f'{label}(スパース率 {sparsities[label]*100:.1f}%)')
    axes2[i].set_xlabel('重みの値')
    axes2[i].set_ylabel('頻度')
plt.tight_layout()
plt.savefig('l1_vs_l2_weight_hist.png', dpi=150)
plt.show()

# ── サマリー ──────────────────────────────────────────
print("\n===== 最終結果サマリー =====")
print(f"{'Pattern':>8} | {'Val Acc':>8} | {'Test Acc':>9} | {'Sparsity':>9} | {'Time(s)':>8}")
print("-" * 56)
for label in ['none', 'l2', 'l1', 'l1l2']:
    val_acc  = histories[label].history['val_accuracy'][-1]
    test_acc = scores[label][1]
    sp       = sparsities[label]
    t        = times[label]
    print(f"{label:>8} | {val_acc:>8.4f} | {test_acc:>9.4f} | {sp*100:>8.2f}% | {t:>8.1f}")
print("-" * 56)
⚠️ ハマりポイント④:重みのヒストグラムは学習済みmodelから直接取得する
ヒストグラムを描く際は、学習ループ内で得られた学習済みmodelオブジェクトからlayer.kernel.numpy()を取り出してplt.hist()に渡します。新しくbuild_model()を呼び直すと初期化直後の未学習の重みになってしまうため、必ず学習ループ内でmodels[label] = modelのように学習済みモデルを保持しておいてください。
実行結果をクリックして内容を開く
===== 最終結果サマリー =====
 Pattern |  Val Acc |  Test Acc |  Sparsity |  Time(s)
--------------------------------------------------------
    none |   0.6645 |    0.6687 |     0.69% |    133.9
      l2 |   0.6542 |    0.6506 |    33.56% |    127.8
      l1 |   0.5813 |    0.5758 |    85.28% |    126.6
    l1l2 |   0.5799 |    0.5768 |    86.43% |    126.3
--------------------------------------------------------

実験結果

精度・損失グラフ

精度グラフ

精度グラフ

損失グラフ

損失グラフ

重み分布のヒストグラム

A:正則化なし

A:正則化なし

B:L2(λ=1e-4)

B:L2(λ=1e-4)

C:L1(λ=1e-4)

C:L1(λ=1e-4)

D:L1_L2併用(λ=1e-4 each)

D:L1_L2併用(λ=1e-4 each)
パターン最終 val_accuracy最終 test_accuracyスパース率(|w|<1e-3)学習時間
A:正則化なし ⭐ 最高精度0.66450.66870.69%133.9秒
B:L2(λ=1e-4)0.65420.650633.56%127.8秒
C:L1(λ=1e-4)0.58130.575885.28%126.6秒
D:L1_L2併用(λ=1e-4 each)0.57990.576886.43% ⭐ 最高スパース率126.3秒

考察

L1はL2よりもさらに精度を下げた——同じλでも罰則の効き方が違う

test_accuracyは A(正則化なし:0.6687)→ B(L2:0.6506)→ C(L1:0.5758)→ D(L1_L2:0.5768)の順に悪化しました。前回のL2単体実験ではλ=1e-4でval_accuracyが-1.5pt程度の悪化でしたが、今回は同じλ=1e-4でもL1のほうがL2よりはるかに大きく精度を落としています(L2比で-7.5pt、正則化なし比では-9.3pt)。これはL1の勾配が重みの大きさに関係なく常に一定であることに起因します。L2は「大きい重みほど強く縮小し、すでに小さい重みはほとんど動かさない」のに対し、L1は重みの大小を問わず一律に同じ力で押し続けるため、まだ学習途中で小さいままの重要な重みまで押しつぶしてしまいやすいのです。Dropout(0.2)がすでに十分な正則化を提供している今回の設定では、この「一律に押しつぶす」性質が過剰な正則化として強く効きすぎたと考えられます。

L1のスパース化は実際にどれだけ起きたか——85%は「特徴選択」というより「大部分を殺した」レベル

スパース率(|w|<1e-3の割合)はL1で85.28%に達しました。これは一部の不要な重みだけを間引く「特徴選択」的な効果というより、全体の8割以上の重みがほぼ機能を失った状態に近く、test_accuracyが正則化なしから-9.3pt悪化した結果とも整合します。CNNの畳み込みカーネルは各要素が空間的な特徴検出パターンを担っているため、これだけ広範囲にゼロ化されると学習された特徴表現自体が大きく削られてしまいます。λ=1e-4という値は、今回のような浅いCNN+Dropoutの組み合わせに対しては強すぎる設定だったと言えそうです。

L2のスパース率33.56%は「本当のゼロ」ではなく「縮小によるニアゼロ」

一見意外なのが、理論上は重みを厳密にゼロへは追い込まないはずのL2でも、スパース率が33.56%まで上がっている点です。これは矛盾ではなく、L2による全体的な重みの縮小(shrinkage)が原因です。L2正則化は重みの大きさに比例して縮小をかけるため、もともと小さかった重みはより小さくなり、|w|<1e-3という閾値をたまたま下回る個体数が増えます。つまりL2の「ニアゼロ」は正則化によって縮小された結果として閾値を下回ったものであり、L1の「ニアゼロ」は最適化の過程で符号勾配によって実際にゼロへ向けて押し切られたものという点で、同じスパース率という数値でも中身の意味が異なります。L1のほうがL2よりスパース率で2倍以上高いのは、この「意図的にゼロへ向かわせる力」があるかどうかの違いが表れた結果と考えられます。

Elastic Net(L1_L2併用)はL1単体とほぼ同じ挙動——L2を足しても中間にはならない

D(L1_L2併用)はスパース率86.43%でC(L1単体)の85.28%とほぼ同水準、test_accuracyも0.5768とC(0.5758)とわずか1pt差でした。L2の要素を加えたことで「L1とL2の中間」になることを期待しがちですが、実際にはL1とL1_L2の差はごく小さく、L1の性質(ゼロ化)がほぼそのまま支配的になっています。一方でこの2パターンの微妙な大小関係(今回はD がCよりわずかに精度・スパース率とも高い)は、GPUの非決定性による誤差の範囲に収まる可能性が高く、「L1_L2併用のほうが優れている」と断定できるほどの差ではありません。重要なのは、Elastic Netが単純に「L1のスパース性とL2の安定性の両取り」になるわけではなく、今回のように弱いDropoutモデルへ追加する使い方ではL1の性質がほぼそのまま出るという点です。


実務での推奨

シーン推奨パターン備考
Dropoutをすでに使っている小〜中規模CNN正則化なし今回の設定ではA(なし)が最高精度。Dropout(0.2)だけで十分な正則化になっている
モデルの軽量化・重みの枝刈りを見据える場合L1(λはもっと弱く、1e-5〜1e-6から)今回のλ=1e-4はスパース率85%と強すぎたため、精度を保ちながら間引くにはより弱い強度から試す必要がある
汎化性能だけを純粋に上げたい場合L2の方がL1よりまだ影響が穏やかただし前回のL2実験の結論同様、Dropoutなし構成・1e-5以下からが安全
Elastic Net(L1_L2併用)を使う場合個別のλをL1単体より弱めに設定L1の性質が支配的になりやすいため、l1・l2それぞれ1e-5程度から様子を見る
まとめ
  • L1正則化は重みの絶対値の和を、L2正則化は重みの二乗和を損失に加算する正則化手法
  • L1は勾配が符号のみで一定のため、一部の重みを完全にゼロへ押しつぶす「スパース化」が起きやすい
  • 今回の実験(λ=1e-4)ではスパース率が L2:33.56% に対し L1:85.28% と2倍以上——同じスパース率でも「縮小によるニアゼロ(L2)」と「最適化によるゼロ化(L1)」で中身が異なる
  • test_accuracyは 正則化なし0.6687 → L2 0.6506 → L1 0.5758 → L1_L2 0.5768 の順に悪化。同じλでもL1のほうがL2よりはるかに精度への影響が大きい
  • Dropout(0.2)がすでに入ったモデルにL1を追加すると、前回のL2実験以上に過剰な正則化として働き、精度を大きく損なう
  • Elastic Net(L1_L2併用)は「良いとこ取り」にはならず、L1の性質がほぼそのまま支配的になった(L1単体との差はGPU誤差の範囲内)
  • l1_l2()を使う際はデフォルト値(l1=0.01, l2=0.01)に注意し、必ず強度を明示指定すること

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