Elastic Net と関連する正則化用語:
🍰 まずはやさしく
2つの手法を混ぜたハイブリッドな方法です。
データの分析をより安定させるために使います。
スマホのアプリで便利な機能だけを集める感覚です。
この手法の特徴とメリットについて読みます。
Elastic Net は Ridge と LASSO のペナルティを組み合わせた回帰:
$$ L = \sum_i (y_i - X_i \beta)^2 + \alpha [\rho \sum_j |\beta_j| + (1-\rho)/2 \sum_j \beta_j^2] $$
ρ で LASSO vs Ridge の比率を調整。 ρ=1 → LASSO、 ρ=0 → Ridge、 中間 → ハイブリッド。
🍰 まずはやさしく
データ分析でよく使われる重要な考え方です。
正しい予測モデルを作るために活用します。
都道府県のデータ分析などで役立ちます。
定義から実装までを順番に学んでいきましょう。
論文中に 「Elastic Net」として登場する用語。
Elastic Net とは:LASSO(L1)とRidge(L2)の両方のペナルティを混ぜた正則化。両者のいいとこ取り。
本ページでは「elastic net」を扱う。 統計データ分析コンペティション (2026) の教材で、 SSDSE-B-2026 (47 都道府県 × 複数年 × 100 超列) の実データを使った再現可能な学習を目指す。
「elastic net」は統計・データサイエンスの体系における重要概念のひとつ。 本ページは「定義・直感・数式・実装・落とし穴・関連手法」の 6 視点で構成され、 各視点は独立して読めるが順序通り読むと体系的な理解が得られる。
🍰 まずはやさしく
2つの手法のいいとこ取りをした方法です。
似たデータがあるときに正しく扱うためです。
部活のメンバーをグループごと選ぶ感覚です。
なぜこの方法が安定するのかを解説します。
SSDSE-B-2026 の 47 都道府県データには、 「総人口(A1101)」「15歳未満人口(A1301)」「65歳以上人口(A1303)」のように互いに相関 0.99 超の指標が並びます。 Lasso 単独では強相関のうち 1 つだけ残して他はゼロにしてしまい、 解が不安定になります。 Ridge は全変数を縮小するだけで選択しません。 Elastic Net は「グループ効果」を持ち、 強相関グループを丸ごと残すか丸ごと縮めるかのバランスをとる「Lasso と Ridge の融合」です。
$$ \hat{\beta}^{EN} = \arg\min_{\beta} \left\{ \frac{1}{2n} \sum_{i=1}^{n} (y_i - x_i^\top \beta)^2 + \lambda \left( \alpha \|\beta\|_1 + \frac{1-\alpha}{2} \|\beta\|_2^2 \right) \right\} $$
$\alpha=1$ で純 Lasso、 $\alpha=0$ で純 Ridge、 $0 < \alpha < 1$ が Elastic Net。 sklearn 既定は $\alpha = 0.5$(混合比率)です。
Elastic Net の核心を 3 つの図で押さえる。 ① L1+L2 罰則項の幾何学的形状、 ② α (Lasso 比率) の役割、 ③ 相関高変数群への扱いの違い。
図のまとめ: Elastic Net は L1+L2 罰則を α で混ぜることで、 Lasso の変数選択能力と Ridge の安定性を両立する。 相関の強い変数群を「グループ」として丸ごと残せるのが最大の強み。
Elastic Net を実際に SSDSE-B-2026 等の都道府県データへ適用する前に、 押さえておきたい前提と落とし穴を整理する。
L1 罰則も L2 罰則も「係数の大きさ」に対して働くため、 スケールが大きい特徴量ほど罰則を受けにくい。 例えば「総人口 (百万単位)」と「失業率 (%)」を混ぜると、 失業率は係数が大きくならざるを得ず、 罰則の影響を強く受けて消えやすくなる。 必ず StandardScaler で平均 0・分散 1 にそろえる。
Elastic Net は「正則化強度 α」と「L1 比率 l1_ratio」の 2 つを同時に決める必要がある。 sklearn では ElasticNetCV で交差検証付き探索を一発で行える。 l1_ratio は [0.1, 0.5, 0.7, 0.9, 0.99] あたりを試すのが定石。
Elastic Net は予測誤差を最小化する正則化推定であって、 偏微分係数を因果効果と読むためには、 さらに DML や Double Machine Learning などの手続きが必要。 「相関の強い 2 変数が両方残った」事実だけから因果関係を主張してはならない。
特徴量が真にスパース (= ほとんどがゼロでよい) なケースでは、 純粋な Lasso (l1_ratio=1) の方が変数選択が明確になり解釈しやすい。 Elastic Net が真価を発揮するのは「相関が高い特徴量群が複数ある」場面。 データの相関構造を見てから決める。
→ Elastic Net は「Lasso と Ridge の中間」だが、 中間が常に最良ではない。 ElasticNetCV で α, l1_ratio を同時探索し、 線形モデルとの比較・特徴量の標準化を必ず行うこと。
🍰 まずはやさしく
計算式で表したこの手法のルールです。
正確に分析を行うための基準になります。
テストの点数を計算する式のようなものです。
数式に登場する記号の意味を確認しましょう。
elastic net の定義や代表的な数式を以下に示す。 数式の各記号の意味は次節で言葉に翻訳する。
elastic net は文脈に応じて複数の定式化があるが、 教育目的では最も基本的な形を抑えることが重要。 具体的な値での計算例は後続セクションを参照。
$$\text{elastic net}: f(\mathbf{X}, \boldsymbol{\theta}) \to y$$
記号の対応はこうです。 $\beta$ は推定する回帰係数のベクトル、 $y$ は目的変数、 $X$ は説明変数の行列。 第 1 項 $\|y - X\beta\|_2^2$ はふつうの二乗誤差で、 当てはまりの良さを測ります。 $\alpha$ は罰則全体の強さで、 0 なら通常の最小二乗、 大きくするほど係数がゼロへ縮みます。 $\rho$(scikit-learn の l1_ratio)はL1 と L2 の配合比で、 $\rho = 1$ なら Lasso(係数がぴったり 0 になる)、 $\rho = 0$ なら Ridge(0 に近づくが 0 にはならない)。 $\|\beta\|_1 = \sum_j |\beta_j|$、 $\|\beta\|_2^2 = \sum_j \beta_j^2$ です。 $1/2$ が L2 側だけに付くのは、 微分したときに係数 2 が消えて式が綺麗になるためで、 意味上の非対称ではありません。
Elastic Net の罰則項は α [(1-l1_ratio)/2 · ||β||² + l1_ratio · ||β||₁] という二項式。 α が「罰則の総量」、 l1_ratio が「Lasso 寄りか Ridge 寄りか」を制御する。 ここでは SSDSE-B-2026(2023 年・47 都道府県)を使い、 相関 0.99 超の人口 3 変数に対して Lasso と Elastic Net の「グループ選択」の違いを実データで確かめる。
このコードでやること: SSDSE-B-2026 の 2023 年断面を読み込み、 「出生数(A4101)」を目的変数、 7 つの実在列を特徴量にする。 正則化は係数の大きさに働くので必ず標準化する。 総人口・15歳未満人口・65歳以上人口は互いに相関 0.99 超(強い多重共線性)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import pandas as pd, numpy as np from sklearn.preprocessing import StandardScaler # SSDSE-B-2026 は cp932・2 行目に日本語見出し行 → skiprows=[1] df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 2023 年・47 都道府県 feats = ['A1101', 'A1301', 'A1303', 'A5101', 'C3301', 'E3501', 'B4101'] # 総人口 15歳未満 65歳以上 転入者数 着工数 中学生 平均気温 X = StandardScaler().fit_transform(df[feats].values.astype(float)) y = df['A4101'].values.astype(float) # 出生数 print(f'X: {X.shape}, y: {y.shape}') print(f'corr(総人口, 15歳未満人口) = {np.corrcoef(df["A1101"], df["A1301"])[0, 1]:.3f}') print(f'corr(総人口, 65歳以上人口) = {np.corrcoef(df["A1101"], df["A1303"])[0, 1]:.3f}') |
📤 実行例:
💬 人口系 3 変数の相関は 0.99 超。 OLS では係数が不安定になり、 Lasso は 1 つを恣意的に選びがちな場面。 ここで Elastic Net の出番。
このコードでやること: ElasticNetCV は α(罰則強度)と l1_ratio(Lasso 比率)を交差検証で同時に選ぶ。 「予測誤差最小」の観点で最良パラメータを取り出す。
1 2 3 4 5 6 7 8 9 10 11 | from sklearn.linear_model import ElasticNetCV enet = ElasticNetCV( l1_ratio=[0.1, 0.3, 0.5, 0.7, 0.9, 1.0], alphas=[0.01, 0.1, 1, 10, 100], cv=5, max_iter=100000, random_state=0) enet.fit(X, y) print(f'best alpha : {enet.alpha_:.1f}') print(f'best l1_ratio : {enet.l1_ratio_:.1f}') print(f'非ゼロ係数数 : {(enet.coef_ != 0).sum()} / {X.shape[1]}') |
📤 実行例:
💬 このデータ・この目的(予測誤差最小)では CV が l1_ratio=1.0=純 Lasso を選んだ。 「Elastic Net が常に Lasso より良い」わけではない実例(前掲の誤解回避と整合)。 では Elastic Net の価値はどこに出るのか? 次のブロックで係数の安定性を見る。
このコードでやること: 同じ α=10 で Lasso と Elastic Net(l1_ratio=0.3)を当てはめ、 相関 0.99 超の人口 3 変数の係数を並べる。 Lasso は群のうち一部を恣意的に落とし符号も暴れるが、 Elastic Net は群をまとめて近い値で残す。
1 2 3 4 5 6 7 8 9 10 | from sklearn.linear_model import Lasso, ElasticNet # 相関 0.99 超の人口 3 変数(総人口・15歳未満・65歳以上)に注目 lasso = Lasso(alpha=10, max_iter=100000).fit(X, y) enet3 = ElasticNet(alpha=10, l1_ratio=0.3, max_iter=100000).fit(X, y) for i, name in enumerate(['総人口', '15歳未満人口', '65歳以上人口']): print(f' {name}: Lasso={lasso.coef_[i]:+.0f} ElasticNet={enet3.coef_[i]:+.0f}') print(f'人口群の生存数 Lasso={(lasso.coef_[:3] != 0).sum()}/3 ' f'ElasticNet={(enet3.coef_[:3] != 0).sum()}/3') |
📤 実行例:
💬 Lasso は総人口をゼロに落とし、 15歳未満に係数を集中させ 65歳以上には負号すら付く(不安定)。 Elastic Net は 3 変数をほぼ等しい正の係数(約 +1300)でまとめて残す=これが「グループ効果」。 相関構造を壊さず解釈が安定する。
このコードでやること: 罰則 α を強めながら、 人口 3 変数のうち何個が非ゼロで残るかを Lasso と Elastic Net で数える。 Elastic Net は α を強めても群を保ちやすい。
1 2 3 4 5 | for a in [1, 10, 30]: l = Lasso(alpha=a, max_iter=100000).fit(X, y) e = ElasticNet(alpha=a, l1_ratio=0.3, max_iter=100000).fit(X, y) print(f' alpha={a:>2}: Lasso 人口群={(l.coef_[:3] != 0).sum()}/3 ' f'ElasticNet 人口群={(e.coef_[:3] != 0).sum()}/3') |
📤 実行例:
💬 α を強めるほど Lasso は群を 3→2→1 と削るが、 Elastic Net は 3 のまま。 Lasso の変数選択能力と Ridge の安定性を両立するのが Elastic Net の核心。 相関構造のあるデータでの既定候補になる。
SSDSE-B-2026(2023 年・47 都道府県)の 7 つの実在列を説明変数、 出生数(A4101)を目的変数として、 OLS / Ridge / Lasso / Elastic Net を 5 分割 CV の RMSE で比較する。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | import numpy as np, pandas as pd from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.model_selection import KFold, cross_val_score df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', skiprows=[1]) df = df[df['SSDSE-B-2026'] == 2023].copy() # 2023 年・47 都道府県 feats = ['A1101', 'A1301', 'A1303', 'A5101', 'C3301', 'E3501', 'B4101'] X = df[feats].values.astype(float) y = df['A4101'].values.astype(float) # 出生数 cv = KFold(n_splits=5, shuffle=True, random_state=0) models = { 'OLS' : LinearRegression(), 'Ridge(a=1)' : Ridge(alpha=1.0), 'Lasso(a=1)' : Lasso(alpha=1.0, max_iter=100000), 'ENet(a=1,r=.5)': ElasticNet(alpha=1.0, l1_ratio=.5, max_iter=100000), } for name, m in models.items(): pipe = Pipeline([('sc', StandardScaler()), ('m', m)]) rmse = -cross_val_score(pipe, X, y, cv=cv, scoring='neg_root_mean_squared_error').mean() print(f'{name:<16s} CV RMSE = {rmse:8.1f}') |
📤 実行例:
💬 このデータは n=47・特徴量 7 と低次元なので、 予測 RMSE では正則化なしの OLS が最良(752)。 Elastic Net の真価は予測精度そのものより、 相関 0.99 超の人口 3 変数を「まとめて残す」係数の安定性にある(上の🔬グループ選択の実測を参照)。 高次元・強い多重共線性ほど正則化・Elastic Net の価値が出る。
2 サンプル × 2 特徴量の小さな合成データで、 Elastic Net の目的関数 (二乗誤差 + L1 + L2 ペナルティ) を完全に展開する。 係数 β を固定して各構成要素の値を手計算し、 同じ計算を NumPy / scikit-learn で再現する。 これにより「正則化が最終ロスにどう寄与するか」を一行ずつ追える。
$$ L(\beta) = \|y - X\beta\|_2^2 + \alpha \left[\rho \|\beta\|_1 + \frac{1-\rho}{2}\|\beta\|_2^2 \right] $$
α : 正則化強度、 ρ (sklearn の l1_ratio) : L1 / L2 の混合比。 ρ=1 で Lasso、 ρ=0 で Ridge、 ρ=0.5 が Elastic Net の典型値。
| 項目 | 値 |
|---|---|
| X (2 サンプル × 2 特徴) | [[1, 2], [3, 4]] |
| y | [5, 11] |
| β (検証する係数) | [1, 1] |
| α (正則化強度) | 1 |
| ρ (L1 比率) | 0.5 |
| 項目 | 計算 | 結果 |
|---|---|---|
| ŷ₁ = 1·1 + 2·1 | 1 + 2 | 3 |
| ŷ₂ = 3·1 + 4·1 | 3 + 4 | 7 |
| 残差 r₁ = y₁ - ŷ₁ | 5 - 3 | 2 |
| 残差 r₂ = y₂ - ŷ₂ | 11 - 7 | 4 |
| RSS = ||y - Xβ||² | 2² + 4² = 4 + 16 | 20 |
| 項目 | 計算 | 結果 |
|---|---|---|
| L1 ノルム ||β||₁ | |1| + |1| | 2 |
| L2² ノルム ||β||² | 1² + 1² | 2 |
| L1 寄与 ρ · ||β||₁ | 0.5 · 2 | 1.0 |
| L2 寄与 (1-ρ)/2 · ||β||² | 0.5 / 2 · 2 = 0.25 · 2 | 0.5 |
| ペナルティ合計 α·[…] | 1 · (1.0 + 0.5) | 1.5 |
| 項目 | 計算 | 結果 |
|---|---|---|
| L(β) = RSS + ペナルティ | 20 + 1.5 | 21.5 |
| RSS 占有率 | 20 / 21.5 | 93.0% |
| ペナルティ占有率 | 1.5 / 21.5 | 7.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 # Step 1: 合成データ X = np.array([[1, 2], [3, 4]]) y = np.array([5, 11]) beta = np.array([1, 1]) alpha = 1.0 rho = 0.5 # sklearn の l1_ratio # Step 2: 予測値・残差・RSS y_hat = X @ beta residual = y - y_hat rss = (residual ** 2).sum() print(f'y_hat = {y_hat}') print(f'残差 = {residual}') print(f'RSS = {rss}') # Step 3: L1 / L2 ペナルティ l1 = np.abs(beta).sum() l2_sq = (beta ** 2).sum() penalty = alpha * (rho * l1 + (1 - rho) / 2 * l2_sq) print(f'||β||1 = {l1}') print(f'||β||2² = {l2_sq}') print(f'penalty = {penalty}') # Step 4: 最終ロス loss = rss + penalty print(f'Loss = {loss}') |
💬 手計算 (Step 4: Loss = 21.5) と Python 出力 (21.5) が完全一致。 RSS が 20、 ペナルティが 1.5 で合計 21.5。 ペナルティ比率は約 7% で、 β を 1 に近づけるほど RSS は減るがペナルティは増える「綱引き」が起きていることが数値で確認できる。 α や ρ を変えれば最適 β がどう動くかを LASSO / Ridge と比較する出発点になる。
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 29 30 31 32 33 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feature_names = _feats from sklearn.linear_model import ElasticNet, ElasticNetCV from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_std = scaler.fit_transform(X) # α と l1_ratio を指定 en = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(X_std, y) print(f'係数: {en.coef_}') # CV で両方を自動選択 en_cv = ElasticNetCV(l1_ratio=[0.1, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0], cv=5).fit(X_std, y) print(f'最適α: {en_cv.alpha_}, 最適l1_ratio: {en_cv.l1_ratio_}') |
1 2 3 4 5 6 | from sklearn.linear_model import ElasticNet en = ElasticNet(alpha=0.1, l1_ratio=0.5, max_iter=20000, tol=1e-5, random_state=0, selection='random') # 'cyclic' or 'random' en.fit(Xs, ys) print('非ゼロ係数:', np.sum(en.coef_ != 0)) |
L1/L2 正則化は係数の絶対値・二乗値にペナルティを課すので、 スケールが大きい変数(例:総人口 10⁶)の係数は強く罰せられ、 スケールが小さい変数(例:失業率 0.03)は罰せられない。 結果としてスケールが大きい変数だけが落ちて選ばれない。 必ず StandardScaler を Pipeline に入れ、 fit/transform をクロスバリデーションの fold 内で完結させる。 標準化なしの ENet は意味のある係数解釈もできない。
α は対数スケールで {0.001, 0.01, 0.1, 1, 10} の 5 段階を基本にすべきで、 [0.1, 1, 10] の 3 点だけでは粗すぎる。 同様に l1_ratio も [0.1, 0.3, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0] のように 1.0 寄りを密にする(Lasso との比較が欲しいので)。 ElasticNetCV は内部で alphas を自動生成するが、 範囲指定は明示した方が再現性が高い。 探索範囲の境界に最適値が当たったら範囲を広げ直す。
CV で最小 MSE を与える α は「最尤」だが、 1-SE rule(CV の最小値から SE 1 個分以内で最大の α、 つまり最大限スパースな選択)を採るのも標準的な慣行。 glmnet の R 版では既定で 1-SE rule が出る。 sklearn ElasticNetCV は最尤のみなので、 1-SE を欲しいときは自前で計算する。 解釈性 vs 予測性能のトレードオフでどちらを採るか決める。
Lasso / ENet の係数には標準的な t 検定や信頼区間は適用できない。 OLS の漸近正規理論は罰則項の存在で崩れる。 有意性が必要なら post-selection inference(Lockhart et al. の covariance test)、 selectiveInference パッケージ、 bootstrap、 もしくはベイズ Lasso を使う。 「ENet で残った変数 = 重要」は強い主張なので、 安定性選択(Stability Selection)でブートストラップを併用するのが安全。
「全データで StandardScaler.fit_transform → 5-fold CV」とすると、 テスト fold の統計量が訓練側に漏れる(データリーク)。 結果として CV スコアが楽観的に見える。 必ず Pipeline で StandardScaler を組み込み、 cross_val_score 内で各 fold ごとに fit を完結。 また、 y のターゲット変換(log 変換等)も fold 内でやるのが理想。 これは sklearn Pipeline の最大の存在意義の一つ。
ENet の売り文句の一つは「Lasso と違って相関の強い変数を全部残せる(grouping effect)」だが、 これは l1_ratio < 1 のときに限る。 l1_ratio が 0.95 以上だとほぼ Lasso と同じで、 相関グループから 1 つだけ選ぶ挙動が再現される。 グループ選択を強く期待するなら l1_ratio = 0.3〜0.5 程度に下げる。 さらに本格的にはグループ Lasso(pyglmnet)を使う方が直接的。
Elastic Net がデータサイエンスの体系の中でどこに位置するかを、 3つの異なる視点で可視化します。 同じ情報でも見方を変えると気付きが変わります。
🌐 統計・データサイエンス › 関連・回帰 › 回帰 › Elastic Net
中心に Elastic Net を置き、 そこから Ridge回帰・LASSO・多重共線性対策 計 3 個の用語へ、 前提・兄弟・発展形といった関係を矢印で結んでいます。 線がどちら向きかを見れば、 先に読むべき用語とあとから読む用語が分かります。 ノードはドラッグで動かせ、 ホイールでズーム、 クリックでその用語のページへ遷移します。
大きな円が小さな円を包含する Circle Packing 図。 「Elastic Net」は緑色でハイライト。
長方形を入れ子に分割した Treemap 図。 各分野の規模感を面積で比較。 「Elastic Net」は緑色でハイライト。
| マップ | 分かること | こんな時に見る |
|---|---|---|
| 🔗 関係マップ | 手法間の横の関係(前提→発展→応用) | 「次に何を学べばよい?」 学習順序の判断 |
| ⭕ 包含マップ | 分類体系の入れ子構造(上位⊃下位) | 「この手法はどんなジャンルに属する?」 |
| 🌳 ツリーマップ | 分野の規模比較(面積=ボリューム) | 「データサイエンス全体の俯瞰像」 |
💡 3 つの図は同じ位置関係を別の角度から描いています。 関係マップは Elastic Net の隣に何が並ぶかを、 包含マップとツリーマップは 統計・データサイエンス → 回帰 → Elastic Net という入れ子の位置を示します。 「回帰には他に何があったか」を思い出したいときは包含マップを、 「次に何を読むか」を決めたいときは関係マップを開いてください。
Elastic Net は単独の回帰モデルではなく、 Lasso (L1) と Ridge (L2) の中間で変数選択と係数縮小を両立させる正則化族の一員である。 多重共線性 ・特徴量数 ≫ サンプル数 の状況で α と l1_ratio を交差検証で決める。
Elastic Net は「L1 と L2 を混合した正則化回帰」で、 上流の標準化と多重共線性チェックを前提とし、 並列の Lasso (L1)・Ridge (L2) と α・l1_ratio で連続的に切り替え、 下流の交差検証で最適パラメータを決定する。
「elastic net」を含む手法選択は、 データの性質・分析目的・運用制約の 3 軸で決まる。 以下に典型シナリオと対応する手法の組合せを示す。
| シナリオ | 重視する観点 | 候補手法 |
|---|---|---|
| データが大規模 (n が多い、 高次元) | 計算コスト / メモリ / 並列化が必要 | ビジネス理解 等 |
| 外れ値が多い / ノイズあり | 頑健性 / 外れ値除去 / 中央値ベース | データ理解 等 |
| 解釈性を重視する | 線形 / 木構造 / ルールベース | データ準備 等 |
| 予測精度を最優先する | アンサンブル / ハイパラ調整 / 交差検証 | モデリング 等 |
| データが少ない | 正則化 / ベイズ / 転移学習 / 簡素なモデル | データアナリスト 等 |
| リアルタイム/オンライン処理 | ストリーミング / 軽量モデル / 逐次更新 | データサイエンティスト 等 |
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 29 30 31 32 33 34 35 36 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feats = _feats # 以降 feats という名前で使う feature_names = _feats from sklearn.linear_model import ElasticNetCV enet_cv = ElasticNetCV( l1_ratio=[.1, .3, .5, .7, .9, .95, .99, 1.0], alphas=np.logspace(-3, 1, 50), cv=5, max_iter=10000, random_state=0) enet_cv.fit(Xs, ys) print(f'best α : {enet_cv.alpha_:.4f}') print(f'best l1_ratio: {enet_cv.l1_ratio_:.2f}') print(f'採択された変数(非ゼロ係数 / 全変数)= ' f'{np.sum(enet_cv.coef_ != 0)} / {len(feats)}') coef_df = pd.DataFrame({'feature': feats, 'coef': enet_cv.coef_}) \ .sort_values('coef', key=abs, ascending=False) print(coef_df) |
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 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feats = _feats # 以降 feats という名前で使う feature_names = _feats from sklearn.linear_model import ElasticNetCV # alpha の候補は「本数」ではなく配列で渡すと、どの版の scikit-learn でも通る encv = ElasticNetCV(l1_ratio=[.1, .5, .7, .9, .95, .99, 1.0], alphas=np.logspace(-3, 1, 100), cv=10, random_state=0) encv.fit(Xs, ys) print(encv.alpha_, encv.l1_ratio_) |
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 29 30 31 32 33 34 35 36 37 38 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feats = _feats # 以降 feats という名前で使う feature_names = _feats import matplotlib.pyplot as plt from sklearn.linear_model import enet_path alphas, coefs, _ = enet_path(Xs, ys, l1_ratio=.5, alphas=np.logspace(-3, 1, 100)) fig, ax = plt.subplots(figsize=(10, 5.5)) for j, name in enumerate(feats): ax.plot(alphas, coefs[j], label=name) ax.set_xscale('log') ax.set_xlabel('α (対数目盛)') ax.set_ylabel('係数') ax.set_title('Elastic Net の係数パス (l1_ratio=0.5)') ax.legend(fontsize=9) ax.grid(alpha=0.3) plt.tight_layout() plt.savefig('enet_path.png', dpi=120) print('saved: enet_path.png') |
1 2 3 4 5 6 | from sklearn.linear_model import SGDRegressor sgd = SGDRegressor(loss='squared_error', penalty='elasticnet', alpha=0.001, l1_ratio=0.15, learning_rate='optimal', max_iter=2000, random_state=0) sgd.fit(Xs, ys) |
1 2 3 4 5 6 7 | from glmnet import ElasticNet as GlmnetEN # R の glmnet と等価 g = GlmnetEN(alpha=0.5, n_splits=10, scoring='mean_squared_error', random_state=0) g.fit(Xs, ys) print('λ_min (CV最小):', g.lambda_max_) print('λ_1se (1-SE) :', g.lambda_best_) |
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 29 30 31 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feature_names = _feats from sklearn.pipeline import Pipeline from sklearn.model_selection import GridSearchCV pipe = Pipeline([('sc', StandardScaler()), ('en', ElasticNet(max_iter=20000))]) param_grid = { 'en__alpha' : np.logspace(-3, 1, 30), 'en__l1_ratio': [.1, .3, .5, .7, .9, .95, .99, 1.0], } gs = GridSearchCV(pipe, param_grid, cv=5, scoring='neg_root_mean_squared_error', n_jobs=-1) gs.fit(X, y) print(gs.best_params_) |
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 29 30 31 | # ── Elastic Net の例で使うデータを用意します(47 都道府県 × 複数の説明変数)── import numpy as np import pandas as pd from sklearn.preprocessing import StandardScaler _df = pd.read_csv('data/raw/SSDSE-B-2026.csv', encoding='cp932', header=1) _df = _df[_df['地域コード'].astype(str).str.match(r'^R\d{5}$', na=False)].copy() _df['年度'] = pd.to_numeric(_df['年度'], errors='coerce') _df = _df[_df['年度'] == _df['年度'].max()] _feats = ['総人口', '出生数', '死亡数', '婚姻件数', '食料費(二人以上の世帯)'] for _c in _feats + ['消費支出(二人以上の世帯)']: _df[_c] = pd.to_numeric(_df[_c], errors='coerce') _df = _df.dropna(subset=_feats + ['消費支出(二人以上の世帯)']) X = _df[_feats].to_numpy(dtype=float) y = _df['消費支出(二人以上の世帯)'].to_numpy(dtype=float) Xs = StandardScaler().fit_transform(X) ys = (y - y.mean()) / y.std() feature_names = _feats import numpy as np rng = np.random.RandomState(0) n_iter, n_sub = 200, 30 counts = np.zeros(X.shape[1]) for _ in range(n_iter): idx = rng.choice(len(X), n_sub, replace=False) en = ElasticNet(alpha=0.1, l1_ratio=0.5).fit(Xs[idx], ys[idx]) counts += (en.coef_ != 0) sel_freq = counts / n_iter for f, p in sorted(zip(feats, sel_freq), key=lambda x: -x[1]): print(f'{f:<25s} selection freq = {p:.2f}') |
ここまでの本文・ウィジェットで掴んだ直感を、 ① L1+L2 の幾何、 ② グループ効果がなぜ起きるかの数式、 ③ SSDSE-B-2026 実データでの Lasso との比較、 ④ 使い分け・λパス/α探索・adaptive Elastic Net・座標降下法、 の順でもう一段深めます。 既存セクションの補強であり、 新規の数値は SSDSE-B-2026(2023 年・47 都道府県)の実測のみです。
Elastic Net の罰則 λ(α‖β‖₁ + (1-α)/2‖β‖₂²) の制約領域は、 Lasso の菱形(頂点=座標軸上の角)と Ridge の円をブレンドした形です。 角があるおかげでスパース性(係数がちょうど 0 になる変数選択)が生まれ、 辺が外に膨らんで丸みを帯びるおかげで、 相関の強い変数の間で解が一意に定まり安定します。 純 Lasso(α=1)の菱形は辺が直線なので、 相関の強い 2 変数がつくる細長い誤差の谷に対して頂点が「たまたま」触れた 1 変数だけを選び、 データが少し変わると別の頂点に飛び移って不安定になります。 少しでも L2 を混ぜる(α<1)と辺が厳密凸になり、 谷の底で解が一意化されます(下の実データ検証がこの効果を示します)。
標準化済みの 2 変数 x_j, x_k が完全相関(x_j = x_k)のとき、 Elastic Net 解は必ず β̂_j = β̂_k となります(Zou & Hastie 2005 の grouping effect 定理)。 一般に係数差は次で上から抑えられます:
$$ \frac{|\hat\beta_j - \hat\beta_k|}{\|y\|_1} \le \frac{1}{\lambda(1-\alpha)}\sqrt{2\,(1-\rho_{jk})} $$
ここで ρ_jk は 2 変数の相関。 相関 ρ_jk が 1 に近いほど右辺が 0 に近づき、 2 つの係数はほぼ等しくなります(=群として一緒に残る/一緒に縮む)。 分母に (1-α) があるのがポイントで、 L2 成分(1-α)が大きいほどグループ化が強く効きます。 純 Lasso(α=1)では右辺が発散し、 この保証が消える=群から 1 本だけ選ぶ挙動になります。 落とし穴⑥「l1_ratio が 0.95 以上だとほぼ Lasso」はこの分母から理解できます。
目的変数を 出生数(A4101)、 説明変数に相関の強い人口系 5 指標を使い、 標準化後に Lasso(α=0.1)と Elastic Net(α=0.1, l1_ratio=0.5)を当てはめた実測結果です。 説明変数どうしの相関は下表のとおりいずれも 0.96 超で、 典型的な「相関変数群」を成します。
| 説明変数(標準化済) | 総人口との相関 | Lasso 係数 (α=0.1) |
Elastic Net 係数 (α=0.1, l1_ratio=0.5) |
|---|---|---|---|
| 総人口(A1101) | 1.000 | 0.000 | 0.173 |
| 15歳未満人口(A1301) | 0.997 | 0.645 | 0.261 |
| 15~64歳人口(A1302) | 0.999 | 0.000 | 0.179 |
| 65歳以上人口(A1303) | 0.991 | 0.000 | 0.104 |
| 婚姻件数(A9101) | 0.989 | 0.257 | 0.224 |
| 非ゼロ係数の本数 / 訓練 R² | — | 2 / 5 本 R²=0.9885 | 5 / 5 本 R²=0.9917 |
読み取り: Lasso は相関 0.99 超の人口 5 指標のうち 2 本だけを残し、 総人口・生産年齢人口・高齢人口を 0 にしました(どれが残るかは相関のわずかな差やスケールに左右され不安定)。 Elastic Net は 5 本すべてに符号の同じ係数を配分し(グループ効果)、 説明力もわずかに高い(R² 0.9885→0.9917)。 これは②の grouping effect が実データで現れた例です。 なお R² は同一データへの当てはめ値であり、 汎化性能ではありません(交差検証で評価すべき点に注意)。 このデータは 多重共線性 が極端に強いケースで、 ステップワイズ法だと選ばれる変数が実行ごとに揺れやすい典型でもあります。
| 状況 | 推奨 | 理由 |
|---|---|---|
| 真にスパース・変数どうしほぼ無相関 | Lasso | 明確な変数選択・解釈が容易 |
| 全変数が効く・強い多重共線性・選択は不要 | Ridge | 全係数を安定に縮小、 0 にはしない |
| 相関変数群が複数・変数選択も欲しい・p>n | Elastic Net | 群ごと選択(grouping)+スパース性の両立 |
Elastic Net は Lasso と Ridge を連続的につなぐ 正則化 の一般形なので、 「どれを使うべきか分からない」ときは l1_ratio を [0.1, 0.5, 0.7, 0.9, 0.95, 0.99, 1.0] のように振ってデータに選ばせるのが実務的な回答です。
Elastic Net は λ(強度)と α(=l1_ratio, 混合比)の 2 パラメータを同時に決めます。 各 α について λ を大きい順に下げていくと、 係数が次々と 0 から立ち上がる正則化パスが描けます。 座標降下法は「一つ前の λ の解を初期値に使う(warm start)」ことで、 パス全体を 1 本の λ 単独フィットとほぼ同じコストで計算できます。 sklearn の ElasticNetCV は l1_ratio ごとに λ パスを引き、 交差検証(交差検証)で最良の (λ, α) を選びます。 α は数点、 λ は各 α につきパスで一気に、 という「α は外側ループ・λ は内側パス」構造が計算の勘所です。 落とし穴②のとおり探索の境界に最適値が当たったら範囲を広げ直します。
通常の Elastic Net は大きい係数ほど強く縮める(係数バイアス)ため、 真に重要な変数まで過度に縮む欠点があります。 Adaptive Elastic Net は、 各変数の L1 罰則に「予備推定 β̃ の逆数に比例する重み w_j = 1/|β̃_j|^γ」を掛け、 大きい係数は軽く・小さい係数は重く罰することで、 一致性(オラクル性質)を回復させる拡張です(Zou & Zhang 2009)。 予備推定にはリッジ解や周辺回帰係数を使います。
座標降下法は Elastic Net の標準解法で、 本ページのウィジェットにも実装されています。 1 変数ずつ「他を固定して最適化」を繰り返し、 各更新は軟閾値処理と L2 の縮小の合成で閉形式に解けます:
$$ \hat\beta_j \leftarrow \frac{S(\,\rho_j,\ \lambda\alpha\,)}{1 + \lambda(1-\alpha)},\qquad S(z,\gamma)=\mathrm{sign}(z)\,(|z|-\gamma)_+ $$
分子の軟閾値 S が L1 由来のスパース性(小さい相関は 0 にする)、 分母の 1+λ(1-α) が L2 由来の一様縮小を担います。 この 1 本の式に Elastic Net の「変数選択+縮小」の両面が凝縮されています(ウィジェットの fit() と同じ更新式)。
2 つのスライダーを動かすと、 Elastic Net の係数がどう縮み・ゼロになるかがリアルタイムで変わります。 λ(正則化強度)を上げるほど全体が縮み、 α(L1 割合)を上げるほどスパース(ゼロが増える)になります。 α=1 で Lasso、 α=0 で Ridge。 左図の制約領域はドラッグ/スワイプでも操作できます(横→α, 縦→λ)。
※ このデモは挙動を体感するための合成データ(A群・B群それぞれ相関 r≈0.98 の変数対+無関係変数 2 本、標準化済み n=48)で、 実測値ではありません。 SSDSE-B-2026 の実データによる計算結果は本ページの実データ計算セクションを参照してください。
α を 1 に寄せると A群・B群の相関対から片方だけが残り、 無関係変数も消えます(スパース化)。 α を 0 に寄せると全係数が一様に小さく縮むだけでゼロにはなりません(縮小のみ)。 中間(およそ 0.3〜0.7)では、 相関の強い A群・B群を「まとめて残す」グループ選択が起きます — これが Elastic Net の核心です。 λ を上げると、 左図の緑の制約領域が原点に向かって縮み、 係数がより強く 0 へ押しつけられる様子が見えます。
上のデモで α→1 が Lasso(L1)、 α→0 が Ridge(L2) に一致することを確認できます。 Elastic Net はこの 2 つを連続的につなぐ 正則化 の一般形です。 実務では ElasticNetCV で (λ, l1_ratio) を交差検証により同時決定し、 相関構造が強いデータほど中間の α が選ばれやすくなります。