正則化(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を組み合わせ、スパース性と安定性を両立する
次のステップとして、以下の記事も参考にしてください。