📚 背景知識(読んでから問題へ)
🎯
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 イメージ)
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(参考値)
📌
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
⚖️ バイアス・バリアンス トレードオフ
アンダーフィット(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)が効率的 |
🚀 次のステップ
- 発展:
GridSearchCVとLogisticRegressionを組み合わせて C と penalty を自動チューニングする - 次回予告 (Day 054): 評価指標の深掘り(多クラス版)— Macro/Micro/Weighted F1 の違いと使い分け
📋 自己評価(解いた後に記入)
理解度:
自分の回答:
気づき・メモ: