Day 053 — ロジスティック回帰の正則化と多クラス分類

2026-06-03 緑 / Phase 2 分析 正則化 / Iris

📚 背景知識(読んでから問題へ)

🎯
Day 053 — ロジスティック回帰の基礎(Day 052)を踏まえ、正則化(C パラメータ)多クラス分類を Iris データセットで実践します。

正則化とは?(過学習を防ぐ仕組み)

モデルが訓練データに「合わせすぎる(過学習)」と、新しいデータへの予測精度が落ちます。正則化はモデルの係数を小さく保つことで過学習を防ぐ仕組みです。

C の値正則化の強さモデルの傾向リスク
C = 0.001強い係数が小さく収まる → シンプルアンダーフィット
C = 1.0中程度(デフォルト)バランスが取れる通常はこれで試す
C = 100弱い係数が大きくなれる → 複雑オーバーフィット
💡
身近な例え:C は「モデルの自由度」のつまみ。締めると(小さいC)おとなしいモデル、緩めると(大きいC)訓練データに追従しすぎる。

L1 正則化 vs L2 正則化

種類penalty特徴使いどころ
L2(Ridge)"l2" (デフォルト)全係数を小さく保つ特徴量を残したい場合
L1(Lasso)"l1"不要な係数をゼロにする特徴選択したい高次元データ
ElasticNet"elasticnet"L1+L2 の組み合わせバランスを取りたい場合

📊 正則化の影響チャート(CV Accuracy イメージ)

1.00 0.95 0.90 0.85 0.80 0.001 0.01 0.1 1.0 10 100 C の値(対数スケール) 最適 C=1.0 アンダーフィット CV Accuracy
CV Accuracy(Iris の典型例)
測定点

🗂️ データスキーマ(Iris Dataset)

特徴量単位説明スケール感
sepal length (cm)cmがく片の長さ4.3 〜 7.9
sepal width (cm)cmがく片の幅2.0 〜 4.4
petal length (cm)cm花弁の長さ1.0 〜 6.9
petal width (cm)cm花弁の幅0.1 〜 2.5
target花の種類(目的変数)0=setosa, 1=versicolor, 2=virginica
⚖️
特徴量のスケールが異なるため(sepal width は最大4.4、petal length は最大6.9)、StandardScaler による標準化が必須です。

📝 問題

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix
import matplotlib.pyplot as plt
import seaborn as sns

問1 — データの確認(EDA)

  • load_iris() でデータを読み込み、pd.DataFrame に変換する
  • 特徴量名・クラス名を確認する
  • 各クラスのサンプル数が均等かどうか確認する(value_counts()
  • df.describe() で特徴量のスケールを確認し、標準化が必要か理由とともに答えよ

問2 — C パラメータの影響分析

  • C = [0.001, 0.01, 0.1, 1.0, 10, 100] の6種類を試す
  • 各 C で StratifiedKFold(n_splits=5) を使ったCV Accuracy を計算する
  • 結果をデータフレームで表示する
  • 最も高いCV Accuracyを示した C の値を報告する

問3 — 最適Cでのモデル学習と評価

  • 問2で見つけた最適な C を使って train_test_split(80:20, stratify=y, random_state=42)で分割して学習する
  • classification_report を出力する(3クラスの Precision, Recall, F1 を確認)
  • 混同行列(Confusion Matrix)を表示する

問4 — 係数の分析(多クラス版)

  • pipeline.named_steps["model"].coef_ の形状を確認する(何行×何列か?)
  • 各クラスに対して、最も重要な特徴量(係数の絶対値が最大)を1つずつ答えよ
  • L1 正則化(penalty="l1", solver="liblinear")で同じCを使った場合、係数がゼロになる特徴量はあるか確認する

問5 — 考察:過学習と正則化のトレードオフ

以下の問いに言葉で答えよ(コードは不要):

  • C を非常に小さくしたとき(例: C=0.001)、訓練精度とCV精度はどうなるか?
  • C を非常に大きくしたとき(例: C=100)、訓練精度とCV精度はどうなるか?
  • Kaggleコンペでは C をどう選ぶのがよいか?

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: iris = load_iris(); X = pd.DataFrame(iris.data, columns=iris.feature_names)
  • 問2: cross_val_score(pipeline, X, y, cv=cv, scoring="accuracy").mean() をループで回す
  • 問3: confusion_matrix(y_test, y_pred) → seaborn の heatmap で可視化できる
  • 問4: coef_.shape(n_classes, n_features) の2次元配列
  • 問5: 小さいC → 高バイアス(underfitting)、大きいC → 高バリアンス(overfitting)
ヒント2 — アプローチ
# C パラメータのループ処理
C_list = [0.001, 0.01, 0.1, 1.0, 10, 100]
results = []

for C in C_list:
    pipeline = Pipeline([
        ("scaler", StandardScaler()),
        ("model", LogisticRegression(C=C, max_iter=1000, random_state=42))
    ])
    cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
    scores = cross_val_score(pipeline, X, y, cv=cv, scoring="accuracy")
    results.append({"C": C, "cv_accuracy": scores.mean(), "cv_std": scores.std()})

results_df = pd.DataFrame(results)
print(results_df)
ヒント3 — コード骨格(ほぼ答え)
# 係数の確認(多クラス版)
model = pipeline.named_steps["model"]
print("coef_ の形状:", model.coef_.shape)  # (3, 4)

feature_names = iris.feature_names
class_names = iris.target_names

for i, class_name in enumerate(class_names):
    coef_series = pd.Series(model.coef_[i], index=feature_names)
    most_important = coef_series.abs().idxmax()
    print(f"{class_name}: 最重要特徴量 = {most_important} (係数={coef_series[most_important]:.4f})")

模範解答

import pandas as pd
import numpy as np
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix

# ── 問1: データの確認 ──
iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = pd.Series(iris.target, name="species")

print("特徴量名:", iris.feature_names)
print("クラス名:", iris.target_names)
print("\nクラスごとのサンプル数:")
print(y.value_counts().sort_index())
# → 各クラス50サンプル(均等)

print("\n特徴量の基本統計:")
print(X.describe().round(2))
# → sepal length は 4.3〜7.9、petal length は 1.0〜6.9 など
# → スケールが異なる → StandardScaler による標準化が必要

# ── 問2: C パラメータの影響分析 ──
C_list = [0.001, 0.01, 0.1, 1.0, 10, 100]
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
results = []

for C in C_list:
    pipe = Pipeline([
        ("scaler", StandardScaler()),
        ("model", LogisticRegression(C=C, max_iter=1000, random_state=42))
    ])
    scores = cross_val_score(pipe, X, y, cv=cv, scoring="accuracy")
    results.append({"C": C, "cv_accuracy": scores.mean(), "cv_std": scores.std()})

results_df = pd.DataFrame(results)
print("\n[C パラメータの影響]")
print(results_df.to_string(index=False))

best_C = results_df.loc[results_df["cv_accuracy"].idxmax(), "C"]
print(f"\n最適な C: {best_C}")

# ── 問3: 最適Cでのモデル学習と評価 ──
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

best_pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(C=best_C, max_iter=1000, random_state=42))
])
best_pipeline.fit(X_train, y_train)
y_pred = best_pipeline.predict(X_test)

print("\n[Classification Report]")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

cm = confusion_matrix(y_test, y_pred)
print("[Confusion Matrix]")
print(cm)

# ── 問4: 係数の分析 ──
model = best_pipeline.named_steps["model"]
print(f"\ncoef_ の形状: {model.coef_.shape}")  # (3, 4)

for i, class_name in enumerate(iris.target_names):
    coef_series = pd.Series(model.coef_[i], index=iris.feature_names)
    most_important = coef_series.abs().idxmax()
    print(f"{class_name}: 最重要特徴量 = {most_important} ({coef_series[most_important]:.4f})")

# L1 正則化での係数確認
pipe_l1 = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(C=best_C, penalty="l1", solver="liblinear",
                                  max_iter=1000, random_state=42))
])
pipe_l1.fit(X_train, y_train)
model_l1 = pipe_l1.named_steps["model"]

print("\n[L1 正則化 係数(ゼロの特徴量を確認)]")
for i, class_name in enumerate(iris.target_names):
    coef_series = pd.Series(model_l1.coef_[i], index=iris.feature_names)
    zero_features = coef_series[coef_series == 0].index.tolist()
    print(f"{class_name}: ゼロ係数 = {zero_features if zero_features else 'なし'}")

# ── 問5: 考察 ──
print("\n── 問5 考察 ──")
print("C=0.001: 訓練精度↓、CV精度も低い(アンダーフィット)")
print("C=100  : 訓練精度↑、CV精度は Iris では変わらないが一般にオーバーフィットリスク")
print("Kaggle : CV精度が最大になるCをグリッドサーチで探す(Optuna 等を活用)")

📈 C パラメータ比較(典型的な結果)

Iris データセットでの典型的な CV Accuracy(参考値)

C=0.001
≈ 0.82
C=0.01
≈ 0.88
C=0.1
≈ 0.95
C=1.0
★ Best
≈ 0.97
C=10
≈ 0.97
C=100
≈ 0.97
📌
Iris は単純なデータなので C=1〜100 の差は小さい。複雑なデータではC=0.1 → C=10 の差が大きく出る。

🔢 混同行列(典型的な結果)

C=1.0 での典型的な混同行列(train/test split: 80:20)

↓実際 / 予測→
setosa
versicolor
virginica
setosa
10
0
0
versicolor
0
9
1
virginica
0
0
10
setosa
F1=1.00
1.00
versicolor
F1=0.95
0.95
virginica
F1=0.95
0.95
全体精度
Acc=0.967
0.967

⚖️ バイアス・バリアンス トレードオフ

アンダーフィット(C が小さい)
C = 0.001 〜 0.01
訓練精度: 低い
テスト精度: 低い
原因: 強すぎる正則化でモデルが単純すぎる
→ C を大きくする
適切(C が最適)
C = 0.1 〜 10(データによる)
訓練精度: 高い
テスト精度: 高い
原因: バイアスとバリアンスのバランスが取れている
→ CV で探索して発見
オーバーフィット(C が大きい)
C = 100 〜 10000
訓練精度: 高い
テスト精度: 低い
原因: 弱すぎる正則化でデータに追従しすぎ
→ C を小さくする

🪜 Step-by-Step 解説

1 なぜ標準化が必要か?

print(X.describe())
# sepal length: 4.3〜7.9(範囲≈3.6)
# petal length: 1.0〜6.9(範囲≈5.9)
# → 特徴量ごとにスケールが違う

ロジスティック回帰は係数に特徴量のスケールが直接影響します。StandardScaler で平均0・標準偏差1に揃えることで、全特徴量が公平に評価されます。

2 C パラメータのグリッドサーチ

# 対数スケールで C を探索するのが一般的
C_list = [0.001, 0.01, 0.1, 1.0, 10, 100]

# CV Accuracy の推移(典型):
# C が小さすぎる → underfitting(高バイアス)
# C が適切 → バランス良い
# C が大きすぎる → 過学習リスク

3 多クラス分類の仕組み(One-vs-Rest)

# coef_.shape = (3, 4) の意味
# → 3クラス × 4特徴量 = 各クラスを「このクラス vs その他すべて」で分類

model.coef_.shape  # (3, 4)
# 行0: setosa の係数
# 行1: versicolor の係数
# 行2: virginica の係数

4 L1 vs L2 正則化の使い分け

# L2(デフォルト): 全係数を小さく保つ → 特徴量を全部残す
# L1: 不要な特徴量の係数をゼロにする → 特徴選択効果

# L1 を使う例(高次元データで特徴選択したい場合)
pipe_l1 = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(
        C=1.0, penalty="l1",
        solver="liblinear",  # L1 は liblinear solver が必要
        max_iter=1000
    ))
])

5 混同行列の読み方

#                  Predicted
#              setosa  versicolor  virginica
# setosa         10       0          0      ← 完璧に分類
# versicolor      0       9          1      ← 1つ誤分類
# virginica       0       0         10      ← 完璧に分類

# Iris では setosa は線形分離可能なため常に完璧
# versicolor と virginica の間に誤分類が起きやすい

🔢 数学・統計の補足(文系向け)

正則化を「ペナルティ」として理解する

モデルの学習は「誤差を最小化する係数を見つける」作業です。正則化はそこに「係数が大きすぎたらペナルティを加える」ルールを追加します。

最小化する目標 = 誤差(logloss) + λ × 係数の大きさへのペナルティ
↑ データへの当てはまり ↑ モデルの複雑さへの罰則
λ = 1 / C (C が小さい → λ が大きい → ペナルティが強い)

L1 と L2 ペナルティの数式(直感)

種類ペナルティ(直感)効果
L2(Ridge)係数の2乗の合計係数を均一に小さくする
L1(Lasso)係数の絶対値の合計小さい係数をゼロに押しつぶす

🏆 Kaggleでの実践的な使い方

テクニック説明コード例
C のグリッドサーチ対数スケールで探索GridSearchCV または Optuna
L1 で特徴選択不要係数をゼロにpenalty="l1"
確率キャリブレーション予測確率の信頼性向上CalibratedClassifierCV
クラス不均衡対策少数クラスに高い重みclass_weight="balanced"
# Kaggle実践: クラス不均衡データへの対応
pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(
        C=1.0,
        class_weight="balanced",  # 少数クラスに高い重みを自動付与
        max_iter=1000
    ))
])

# Kaggle実践: GridSearchCV で C を自動探索
from sklearn.model_selection import GridSearchCV

param_grid = {"model__C": [0.001, 0.01, 0.1, 1.0, 10, 100]}
gs = GridSearchCV(pipeline, param_grid, cv=5, scoring="accuracy", n_jobs=-1)
gs.fit(X_train, y_train)
print(f"Best C: {gs.best_params_['model__C']}")
print(f"Best CV Accuracy: {gs.best_score_:.4f}")

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
C が大きいほど良いと思う 大きい = 精度が高そうに見える C が大きすぎると過学習する
L1 と L2 の違いを知らない デフォルト(L2)だけ使う 高次元データでは L1 の特徴選択効果が重要
多クラスの coef_ が2次元と知らない 2クラスの感覚で使う 多クラスは (n_classes, n_features) の形状
max_iter 警告を無視 警告だから平気と思う 収束していない = 係数が不正確。増やすべき
C の探索を線形スケールで行う 0.1, 0.2, 0.3 ... と探す 対数スケール(0.001, 0.01, 0.1, 1, 10)が効率的

🚀 次のステップ

  • 発展: GridSearchCVLogisticRegression を組み合わせて C と penalty を自動チューニングする
  • 次回予告 (Day 054): 評価指標の深掘り(多クラス版)— Macro/Micro/Weighted F1 の違いと使い分け

📋 自己評価(解いた後に記入)

理解度:
自分の回答:
気づき・メモ: