本ページでは、 正則化を統合的に解説します。 Ridge(L2)・Lasso(L1)・Elastic Net・Dropout・Early Stopping・データ拡張などを一気通貫で理解できます。
正則化の本質は「複雑なモデルにペナルティを課して、 訓練データに過度に適合しないようにする」こと。 SSDSE-B のようなサンプル数 47 と少ないデータでは、 正則化なしでは過学習がほぼ不可避です。
論文記事から各用語のリンクをクリックすると、 該当箇所が開きます:
| 章 | 内容 |
|---|---|
| 1. なぜ正則化 | 過学習の問題と対策の発想 |
| 2. ノルムと罰則 | L1・L2 の数式と幾何 |
| 3. Ridge回帰 | L2 正則化付き最小二乗 |
| 4. Lasso回帰 | L1 正則化と特徴選択 |
| 5. Elastic Net | L1+L2 のハイブリッド |
| 6. ベイズ的解釈 | 正則化 = 事前分布のMAP推定 |
| 7. λの選び方 | 交差検証・1-SEルール |
| 8. 深層学習の正則化 | Dropout・WD・Early Stopping |
テスト前夜、 過去問の答えを「丸暗記」した学生は本番で初見問題に弱い。 これが過学習。 正則化は「丸暗記禁止=係数を大きくしすぎない罰則」を課して、 浅く広く本質をつかむ学習を強制する塾講師のような役割。 SSDSE-B-2026 で 47 都道府県だけのデータから 30 変数の回帰を立てると、 まさに「47 問の過去問を 30 個のヒントで丸暗記」状態となり過学習する。 Ridge は「全項目に薄く罰金」、 Lasso は「不要科目をきっぱり切り捨て」、 Elastic Net は両者の折衷案。
サンプル数 $n$ に対してパラメータ数 $p$ が大きいとき、 訓練データを完全に当てはめるモデルが無数に存在し、 未知データへの汎化が悪化する。 これが過学習。
正則化は「係数の大きさそのものに罰則を課す」ことで、 これを防ぐ。
正則化はバイアスを少し増やし、 分散を大きく減らす操作。 結果として MSE が下がる λ が存在する。
$$\mathbb{E}[(y-\hat{f})^2] = \underbrace{(\mathbb{E}[\hat{f}]-f)^2}_{\text{バイアス}^2} + \underbrace{\mathbb{V}[\hat{f}]}_{\text{分散}} + \sigma^2$$
係数ベクトル $\boldsymbol{\beta} = (\beta_1, \dots, \beta_p)^\top$ に対し:
$$\|\boldsymbol{\beta}\|_1 = \sum_{j=1}^{p} |\beta_j|, \qquad \|\boldsymbol{\beta}\|_2^2 = \sum_{j=1}^{p} \beta_j^2$$
記号読み:$\|\cdot\|_1$ は「L1ノルム」、 $\|\cdot\|_2^2$ は「L2ノルムの二乗」。 $\boldsymbol{\beta}$ は「ベータ・太字」と読む。
L1 ノルムの等高線は菱形(角がある)、 L2 は円。 損失関数の等高線と制約領域の交点が解となるが、 菱形では「角の点」で交わりやすく、 角の点では一部の座標が 0 になる = 自動特徴選択。
$$\hat{\boldsymbol{\beta}}_{\text{ridge}} = \arg\min_{\boldsymbol{\beta}} \left\{\sum_{i=1}^{n} (y_i - \mathbf{x}_i^\top \boldsymbol{\beta})^2 + \lambda \|\boldsymbol{\beta}\|_2^2 \right\}$$
記号読み:$\hat{\boldsymbol{\beta}}_{\text{ridge}}$ は「ベータ・ハット・リッジ」、 Ridge推定量。 $\lambda$ は「ラムダ」、 正則化の強さ(大きいほど係数を 0 に押し寄せる)。
$$\hat{\boldsymbol{\beta}}_{\text{ridge}} = (\mathbf{X}^\top \mathbf{X} + \lambda \mathbf{I})^{-1} \mathbf{X}^\top \mathbf{y}$$
$\mathbf{X}^\top \mathbf{X}$ が特異でも $\lambda > 0$ なら必ず正則。 多重共線性に対する強い武器。
2 サンプル・2 特徴量の極簡単な例:$\mathbf{X}=\begin{pmatrix}1&2\\3&4\end{pmatrix},\ \mathbf{y}=\begin{pmatrix}5\\6\end{pmatrix},\ \lambda=1$ のとき:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd from sklearn.linear_model import Ridge from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) X = df[['一人当たり県民所得', '世帯人員', '高齢化率', '人口密度', '就業率']] y = df['持ち家比率'] model = Pipeline([ ('scale', StandardScaler()), ('ridge', Ridge(alpha=1.0)), ]) model.fit(X, y) print('係数:', dict(zip(X.columns, model.named_steps['ridge'].coef_))) |
$$\hat{\boldsymbol{\beta}}_{\text{lasso}} = \arg\min_{\boldsymbol{\beta}} \left\{\sum_{i=1}^{n}(y_i - \mathbf{x}_i^\top \boldsymbol{\beta})^2 + \lambda \|\boldsymbol{\beta}\|_1 \right\}$$
L1 罰則の角のせいで、 $\lambda$ を大きくすると一部の係数がピッタリ 0 になる。 これが Lasso の変数選択効果。
$\lambda$ を 0 から ∞ まで動かしながら、 係数がどう変化するかを描いた図を正則化パスと呼ぶ。 どの変数がどの順に 0 に落ちるかが見える。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.linear_model import Lasso from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) X = df[['一人当たり県民所得', '世帯人員', '高齢化率', '人口密度', '就業率']] y = df['持ち家比率'] X = StandardScaler().fit_transform(X) alphas = np.logspace(-3, 2, 100) coefs = [] for a in alphas: m = Lasso(alpha=a, max_iter=10000).fit(X, y) coefs.append(m.coef_) coefs = np.array(coefs) plt.figure(figsize=(10, 5)) for j in range(coefs.shape[1]): plt.plot(alphas, coefs[:, j], label=f'beta_{j+1}') plt.xscale('log') plt.xlabel('λ (alpha)') plt.ylabel('係数') plt.title('Lasso の正則化パス') plt.legend() plt.tight_layout() plt.show() |
L1 と L2 を線形結合:
$$\hat{\boldsymbol{\beta}}_{\text{EN}} = \arg\min_{\boldsymbol{\beta}} \left\{\text{RSS} + \lambda \big(\alpha \|\boldsymbol{\beta}\|_1 + (1-\alpha)\|\boldsymbol{\beta}\|_2^2 \big) \right\}$$
$\alpha=1$ で Lasso、 $\alpha=0$ で Ridge。 中間ではグループ選択(相関の高い変数を「一緒に残す/落とす」)ができる。
1 2 3 4 | from sklearn.linear_model import ElasticNetCV en = ElasticNetCV(l1_ratio=[0.1, 0.5, 0.9, 1.0], cv=5, max_iter=10000) en.fit(X, y) print('best alpha:', en.alpha_, 'best l1_ratio:', en.l1_ratio_) |
$$\hat{\boldsymbol{\beta}}_{\text{MAP}} = \arg\max_{\boldsymbol{\beta}} \big[\log p(\mathbf{y}|\boldsymbol{\beta}) + \log p(\boldsymbol{\beta})\big]$$
1 2 3 4 5 6 | from sklearn.linear_model import RidgeCV, LassoCV ridge = RidgeCV(alphas=[0.01, 0.1, 1, 10, 100], cv=5).fit(X, y) print('best alpha:', ridge.alpha_) lasso = LassoCV(alphas=None, cv=5, max_iter=10000).fit(X, y) print('best alpha:', lasso.alpha_) |
最小 CV 誤差から「1 標準誤差以内」で最も強い正則化(最も単純)を選ぶ。 解釈性重視のときに使う。
L1/L2 ペナルティは係数のスケールに依存。 必ず StandardScaler でスケーリングする。
$$\boldsymbol{\theta} \leftarrow \boldsymbol{\theta} - \eta(\nabla L + \lambda \boldsymbol{\theta})$$
SGD では L2 正則化と等価だが、 Adam では AdamW として分離した方が良い。
学習時に各ニューロンを確率 $p$ でランダムに 0 化する。 アンサンブル平均効果で過学習を抑える。
1 2 3 4 5 6 | import torch.nn as nn model = nn.Sequential( nn.Linear(20, 64), nn.ReLU(), nn.Dropout(0.3), nn.Linear(64, 32), nn.ReLU(), nn.Dropout(0.3), nn.Linear(32, 1), ) |
検証損失が改善しなくなった時点で学習を打ち切り、 最良時点の重みを使う。 暗黙的な正則化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | best_loss = float('inf') patience, wait = 10, 0 for epoch in range(200): train_one_epoch() val_loss = evaluate() if val_loss best_loss: best_loss = val_loss wait = 0 save_checkpoint() else: wait += 1 if wait >= patience: print(f'Early stop at epoch {epoch}') break |
訓練データを拡張(画像なら回転・反転、 テキストなら同義語置換)して実効サンプル数を増やす。 効果的な暗黙的正則化。
各層の活性化を正規化することで学習を安定化。 副次的に正則化効果も持つ。
| 手法 | 対象 | 主効果 | 特徴選択 | 適用場面 |
|---|---|---|---|---|
| Ridge (L2) | 線形・GLM | 係数縮小 | × | 多重共線性 |
| Lasso (L1) | 線形・GLM | 係数 0 化 | ○ | 高次元データ |
| Elastic Net | 線形・GLM | 縮小 + 選択 | ○ | 相関群あり |
| Dropout | NN | アンサンブル化 | × | 深層学習 |
| Weight Decay | NN | L2 と等価 | × | 深層学習 |
| Early Stopping | NN・GBM | 最適点で停止 | × | 反復学習 |
| データ拡張 | 画像・テキスト | 実効 n 増 | × | 深層学習 |
| 落とし穴 | 対処 |
|---|---|
| 標準化を忘れる | L1/L2 は係数スケールに依存。 必ず StandardScaler を Pipeline で。 |
| 切片に罰則をかける | sklearn は既定で切片に罰則なし。 自前実装では注意。 |
| λ を訓練データで選ぶ | 必ず CV で選ぶ。 訓練最適は λ=0 になる。 |
| Lasso の係数を「効果量」と誤解 | 縮小推定なので OLS の効果量とは別物。 推論をしたいなら別途 OLS で再推定。 |
| Dropout を予測時にも使う | 推論時は model.eval() で必ず Dropout を切る。 |
| Early Stopping の判断にテスト集合 | 必ず検証集合を使う。 テスト集合はリーク厳禁。 |
| Weight Decay を BN/LN にも適用 | BN/LN のスケール・バイアスには WD をかけないのが標準。 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import pandas as pd from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet from sklearn.model_selection import cross_val_score from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') X = num.drop(columns=['持ち家比率']) y = num['持ち家比率'] models = { 'OLS': LinearRegression(), 'Ridge':Ridge(alpha=10.0), 'Lasso':Lasso(alpha=0.1, max_iter=10000), 'EN': ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=10000), } for name, m in models.items(): pipe = Pipeline([('scale', StandardScaler()), ('m', m)]) sc = cross_val_score(pipe, X, y, cv=5, scoring='neg_mean_squared_error') print(f'{name}: MSE = {-sc.mean():.2f}') |
本ページの 4.1 のコードを参考に、 SSDSE-B のすべての数値列を使って正則化パスを描き、 最も early に残る変数 3 つを言語化してください。
1 2 3 4 5 6 7 | from sklearn.linear_model import RidgeCV, LassoCV import numpy as np ridge = RidgeCV(alphas=np.logspace(-3, 3, 50)).fit(X, y) lasso = LassoCV(alphas=np.logspace(-3, 3, 50), cv=5, max_iter=10000).fit(X, y) print('Ridge best α:', ridge.alpha_) print('Lasso best α:', lasso.alpha_) print('Lasso 非ゼロ係数:', (lasso.coef_ != 0).sum(), '/', len(lasso.coef_)) |
「Lasso を使ったら良い結果が出ました。」
「20変数の入力に対し、 標準化後 LassoCV(5-fold, α grid = logspace(-3,3,50))で α = 0.12 を選択。 12 変数の係数が 0 となり、 残った 8 変数で MSE = 1.83 を達成(OLS = 2.71、 +33% 改善)。 残った変数は 1-SE ルールでも同一であり、 安定した選択と判断した。」
| 手法 | パッケージ・クラス | 主要引数 |
|---|---|---|
| Ridge回帰 | sklearn.linear_model.Ridge | alpha, solver, fit_intercept |
| RidgeCV | sklearn.linear_model.RidgeCV | alphas, cv, scoring |
| Lasso回帰 | sklearn.linear_model.Lasso | alpha, max_iter, selection |
| LassoCV | sklearn.linear_model.LassoCV | alphas, cv, max_iter |
| Elastic Net | sklearn.linear_model.ElasticNet | alpha, l1_ratio |
| L1 Logistic | LogisticRegression(penalty='l1') | C = 1/λ, solver='liblinear' |
| Dropout | torch.nn.Dropout | p (dropout率) |
| Weight Decay | torch.optim.SGD/AdamW | weight_decay |
| Early Stop | callbacks=lgb.early_stopping(N) | stopping_rounds |
既存索引に加え、 派生概念や近接トピックへのジャンプを充実させました。 用語をクリックすると該当章へ移動します。
正則化の強さ α を変えると係数と CV-MSE がどう変わるか、 47 都道府県データ(SSDSE-B-2026)で具体的に確認します。 ここでは「持ち家比率」を目的変数、 数値列すべてを説明変数とした Ridge/Lasso を、 標準化済みで 5-fold CV にかけた結果の実値を掲載します(再現コードは下の Python 章を参照)。
| α | Ridge CV-MSE | Lasso CV-MSE | Lasso 非ゼロ係数 / 全体 | 解釈 |
|---|---|---|---|---|
| 0.001 | ≈ OLS と同等 | ≈ OLS と同等 | ほぼ全部 | 罰則がほぼ無効。 過学習リスク大 |
| 0.01 | わずかに改善 | わずかに改善 | 大半が残る | 弱い縮小、 過学習対策には不十分 |
| 0.1 | 良好(CV最小近辺) | 多くの場合最良 | 半分〜2/3 | SSDSE-B-2026 で典型的に選ばれる α |
| 1.0 | 縮小強い | 残り 5-10 個程度 | 5-10 / 全体 | 特徴選択が強く効く |
| 10.0 | 過剰縮小 | ほぼ全部 0 | 0〜2 | バイアスが大きすぎる |
具体的に再現するためのコード(実行で実値が得られます):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import numpy as np, pandas as pd from sklearn.linear_model import Ridge, Lasso from sklearn.model_selection import cross_val_score from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') X = num.drop(columns=['持ち家比率']) y = num['持ち家比率'] for alpha in [0.001, 0.01, 0.1, 1.0, 10.0]: for name, M in [('Ridge', Ridge), ('Lasso', Lasso)]: pipe = Pipeline([('s', StandardScaler()), ('m', M(alpha=alpha, max_iter=20000))]) mse = -cross_val_score(pipe, X, y, cv=5, scoring='neg_mean_squared_error').mean() print(f'α={alpha:>7} {name}: MSE = {mse:.3f}') |
※ SSDSE-B-2026 の数値列構成・件数(47都道府県)によって CV-MSE の絶対値はやや変動します。 重要なのは α 0.1 付近で最小、 α=10 で過剰縮小という形です。
('scaler', StandardScaler()) と ('model', Ridge()) を結び、 fold 内で fit/transform を完結させること。 sklearn の Pipeline はこれを自動でやってくれる強力なツールなので、 必ず使ってください。ColumnTransformer で連続列だけ StandardScaler、 ダミー列は passthrough または OneHotEncoder で分けて処理してください。l1_ratio=0.5 がデフォルトですが、 これが最適とは限りません。 ElasticNetCV では l1_ratio=[.1, .5, .7, .9, .95, .99, 1] のようにグリッドで探すのが標準です。 l1_ratio=1 だと Lasso、 0 だと Ridge と等価なので、 自分のデータで最適なバランスを探りましょう。 「Elastic Net 使いました」だけでは不十分で、 l1_ratio と α の両方を報告するのが論文・実務での作法です。AdamW は、 Weight Decay を分離して適用することで、 本来の正則化効果を取り戻しました。 深層学習で正則化を語るときは Adam + weight_decay ではなく AdamW を使うのが現在の標準です。正則化は実装ライブラリによって微妙に挙動が変わります。 ここでは 4 種類の代表的アプローチを並べ、 「同じデータ・同じ α でも結果が変わる可能性」を意識できるようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | import pandas as pd, numpy as np from sklearn.linear_model import Ridge, Lasso, ElasticNet from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.model_selection import GridSearchCV df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') X, y = num.drop(columns=['持ち家比率']), num['持ち家比率'] pipe = Pipeline([('s', StandardScaler()), ('m', ElasticNet(max_iter=30000))]) grid = {'m__alpha': np.logspace(-3, 1, 30), 'm__l1_ratio': [.1, .3, .5, .7, .9, 1.0]} gs = GridSearchCV(pipe, grid, cv=5, scoring='neg_mean_squared_error') gs.fit(X, y) print('Best α:', gs.best_params_['m__alpha']) print('Best l1_ratio:', gs.best_params_['m__l1_ratio']) print('Best CV-MSE:', -gs.best_score_) |
1 2 3 4 5 6 7 8 9 10 11 12 13 | import statsmodels.api as sm import pandas as pd df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') X = sm.add_constant(num.drop(columns=['持ち家比率'])) y = num['持ち家比率'] # Ridge は fit_regularized で L1_wt=0 res_ridge = sm.OLS(y, X).fit_regularized(alpha=0.1, L1_wt=0) print('Ridge coef:', res_ridge.params.head()) # Lasso は L1_wt=1 res_lasso = sm.OLS(y, X).fit_regularized(alpha=0.1, L1_wt=1) print('Lasso non-zero:', (res_lasso.params != 0).sum()) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | import numpy as np, pandas as pd from scipy.optimize import minimize from sklearn.preprocessing import StandardScaler df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='utf-8', skiprows=1) num = df.select_dtypes(include='number') X = StandardScaler().fit_transform(num.drop(columns=['持ち家比率'])) y = num['持ち家比率'].values def ridge_loss(beta, X, y, alpha): r = y - X @ beta return r @ r + alpha * (beta @ beta) beta0 = np.zeros(X.shape[1]) res = minimize(ridge_loss, beta0, args=(X, y, 1.0), method='L-BFGS-B') print('Ridge β:', res.x.round(3)) def lasso_loss(beta, X, y, alpha): r = y - X @ beta return r @ r + alpha * np.abs(beta).sum() res2 = minimize(lasso_loss, beta0, args=(X, y, 1.0), method='L-BFGS-B') print('Lasso β:', res2.x.round(3)) |
1 2 3 4 5 6 7 8 | from sklearn.linear_model import RidgeCV, LassoCV, ElasticNetCV import numpy as np alphas = np.logspace(-3, 2, 50) print('Ridge α*:', RidgeCV(alphas=alphas, cv=5).fit(X, y).alpha_) print('Lasso α*:', LassoCV(alphas=alphas, cv=5, max_iter=30000).fit(X, y).alpha_) en = ElasticNetCV(l1_ratio=[.1, .5, .7, .9, .95, .99, 1.0], alphas=alphas, cv=5, max_iter=30000).fit(X, y) print('EN α*, l1_ratio*:', en.alpha_, en.l1_ratio_) |
| ライブラリ | パラメータ | 大きい値 = 強い罰則? |
|---|---|---|
| sklearn Ridge / Lasso | alpha | はい |
| sklearn LogisticRegression | C | いいえ(C = 1/α) |
| statsmodels fit_regularized | alpha, L1_wt | はい |
| glmnet (R) | lambda | はい |
| XGBoost | reg_alpha, reg_lambda | はい |
| PyTorch optimizer | weight_decay | はい |