機械学習における正則化とは?L1・L2正則化を分かりやすく解説

正則化(Regularization)は、機械学習モデルの過学習を防ぐための基本的な手法です。モデルの損失関数にパラメータの大きさに関するペナルティ項を加えることで、パラメータが過度に大きくなることを抑制し、汎化性能を向上させます。

正則化は回帰モデルだけでなく、ニューラルネットワークやロジスティック回帰など、幅広いモデルで利用されています。

本記事の内容

  • 過学習と正則化の関係
  • L1正則化(Lasso)の理論と特徴
  • L2正則化(Ridge)の理論と特徴
  • Elastic Netによる組み合わせ
  • Pythonでの実装と比較

前提知識

この記事を読む前に、以下の概念を押さえておくと理解が深まります。

過学習とは

過学習(Overfitting)とは、モデルが訓練データに過度に適合し、未知のデータに対する予測性能が低下する現象です。

モデルのパラメータ数が多い場合や、訓練データが少ない場合に発生しやすく、パラメータの絶対値が大きくなる傾向があります。正則化はこのパラメータの大きさを制約することで過学習を防ぎます。

正則化付き損失関数

一般的な正則化付き損失関数は以下の形をしています。

$$ L_{\text{reg}}(\bm{w}) = L_{\text{data}}(\bm{w}) + \lambda \Omega(\bm{w}) $$

  • $L_{\text{data}}(\bm{w})$: データへの当てはまり(残差の二乗和など)
  • $\Omega(\bm{w})$: 正則化項(パラメータの大きさに関するペナルティ)
  • $\lambda > 0$: 正則化パラメータ(ペナルティの強さを制御)

L2正則化(Ridge)

L2正則化は、パラメータのL2ノルムの二乗をペナルティとして加えます。

$$ \Omega_{L2}(\bm{w}) = \|\bm{w}\|_2^2 = \sum_{j=1}^{p} w_j^2 $$

$$ L_{\text{Ridge}}(\bm{w}) = \|\bm{y} – \bm{X}\bm{w}\|^2 + \lambda \sum_{j=1}^{p} w_j^2 $$

L2正則化の特徴は、パラメータを0に近づけますが、厳密に0にはしないことです。全ての特徴量を少しずつ使う形になります。

L1正則化(Lasso)

L1正則化は、パラメータのL1ノルムをペナルティとして加えます。

$$ \Omega_{L1}(\bm{w}) = \|\bm{w}\|_1 = \sum_{j=1}^{p} |w_j| $$

$$ L_{\text{Lasso}}(\bm{w}) = \|\bm{y} – \bm{X}\bm{w}\|^2 + \lambda \sum_{j=1}^{p} |w_j| $$

L1正則化の最大の特徴は、スパース性 です。$\lambda$ が十分大きいとき、一部のパラメータが厳密に0になり、特徴量選択(feature selection)の効果を持ちます。

L1とL2の幾何学的な違い

L1とL2の違いは制約領域の形状で理解できます。

L2正則化の制約領域は $\|\bm{w}\|_2^2 \leq t$ で、これはパラメータ空間上の円(超球)です。一方、L1正則化の制約領域は $\|\bm{w}\|_1 \leq t$ で、菱形(超正八面体)です。

菱形の「角」が座標軸上にあるため、等高線と菱形の接点が座標軸上(すなわちパラメータが0)になりやすく、これがスパース性の幾何学的な理由です。

Elastic Net

Elastic Netは、L1とL2の正則化を組み合わせた手法です。

$$ L_{\text{EN}}(\bm{w}) = \|\bm{y} – \bm{X}\bm{w}\|^2 + \lambda_1 \|\bm{w}\|_1 + \lambda_2 \|\bm{w}\|_2^2 $$

L1のスパース性とL2の安定性を兼ね備えています。相関の高い特徴量がある場合、Lassoは一方のみを選択しがちですが、Elastic Netは両方をグループとして選択する傾向があります。

Pythonでの実装

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge, Lasso, ElasticNet
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import cross_val_score

np.random.seed(42)

# --- データ生成(多くの特徴量、一部のみ有効)---
n = 100
p = 20  # 特徴量数
p_informative = 5  # 有効な特徴量数

X = np.random.randn(n, p)
w_true = np.zeros(p)
w_true[:p_informative] = np.array([3.0, -2.0, 1.5, -1.0, 0.5])
y = X @ w_true + np.random.randn(n) * 0.5

# --- 各正則化手法の比較 ---
lambdas = np.logspace(-3, 2, 50)

ridge_coefs = []
lasso_coefs = []
elastic_coefs = []

for lam in lambdas:
    # Ridge
    ridge = Ridge(alpha=lam)
    ridge.fit(X, y)
    ridge_coefs.append(ridge.coef_.copy())

    # Lasso
    lasso = Lasso(alpha=lam, max_iter=10000)
    lasso.fit(X, y)
    lasso_coefs.append(lasso.coef_.copy())

    # Elastic Net
    en = ElasticNet(alpha=lam, l1_ratio=0.5, max_iter=10000)
    en.fit(X, y)
    elastic_coefs.append(en.coef_.copy())

ridge_coefs = np.array(ridge_coefs)
lasso_coefs = np.array(lasso_coefs)
elastic_coefs = np.array(elastic_coefs)

# --- 正則化パスの可視化 ---
fig, axes = plt.subplots(1, 3, figsize=(18, 5))

for ax, coefs, title in zip(axes,
                             [ridge_coefs, lasso_coefs, elastic_coefs],
                             ['Ridge (L2)', 'Lasso (L1)', 'Elastic Net']):
    for j in range(p):
        color = 'blue' if j < p_informative else 'gray'
        alpha = 0.8 if j < p_informative else 0.3
        ax.semilogx(lambdas, coefs[:, j], color=color, alpha=alpha)
    ax.set_xlabel('$\\lambda$ (log scale)')
    ax.set_ylabel('Coefficient')
    ax.set_title(f'{title} Regularization Path')
    ax.axhline(y=0, color='black', linestyle='-', linewidth=0.5)
    ax.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# --- パラメータの比較(最適なlambdaで)---
best_lam = 0.1
ridge = Ridge(alpha=best_lam).fit(X, y)
lasso = Lasso(alpha=best_lam, max_iter=10000).fit(X, y)
en = ElasticNet(alpha=best_lam, l1_ratio=0.5, max_iter=10000).fit(X, y)

fig, ax = plt.subplots(figsize=(12, 5))
x_pos = np.arange(p)
width = 0.2

ax.bar(x_pos - width, w_true, width, label='True', color='black', alpha=0.7)
ax.bar(x_pos, ridge.coef_, width, label='Ridge', color='blue', alpha=0.7)
ax.bar(x_pos + width, lasso.coef_, width, label='Lasso', color='red', alpha=0.7)
ax.bar(x_pos + 2 * width, en.coef_, width, label='Elastic Net', color='green', alpha=0.7)
ax.set_xlabel('Feature Index')
ax.set_ylabel('Coefficient')
ax.set_title(f'Coefficient Comparison ($\\lambda$={best_lam})')
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# 非ゼロ係数の数を確認
print(f"真のパラメータの非ゼロ数: {np.sum(w_true != 0)}")
print(f"Ridge の非ゼロ数: {np.sum(np.abs(ridge.coef_) > 1e-6)}")
print(f"Lasso の非ゼロ数: {np.sum(np.abs(lasso.coef_) > 1e-6)}")
print(f"Elastic Net の非ゼロ数: {np.sum(np.abs(en.coef_) > 1e-6)}")

正則化パスを見ると、Ridgeは全ての係数が滑らかに0に近づくのに対し、Lassoは不要な特徴量の係数が途中から厳密に0になることが確認できます。Elastic Netはその中間的な振る舞いを示します。

まとめ

本記事では、機械学習における正則化について解説しました。

  • 正則化は損失関数にパラメータの大きさに関するペナルティを加えて過学習を防ぐ手法
  • L2正則化(Ridge)はパラメータを0に近づけるが厳密に0にはしない
  • L1正則化(Lasso)はパラメータを厳密に0にする効果があり、特徴量選択として機能する
  • Elastic NetはL1とL2を組み合わせ、スパース性と安定性を両立する

次のステップとして、以下の記事も参考にしてください。