Day 052 — ロジスティック回帰入門(シグモイド・確率予測・係数の解釈)

2026-06-02 緑 / Phase 2 コーディング ロジスティック回帰

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

🎯
Day 052 — 交差検証でモデル評価の設計を学んだ次のステップ。Kaggleで最もよく使われる分類アルゴリズムの基礎、ロジスティック回帰を実装します。

「ある人がローンを返済できるか?」「このメールはスパムか?」—— こうした Yes/No の2択を確率として予測するのがロジスティック回帰です。

⚠️
名前に「回帰」が付いていますが、実際は分類アルゴリズムです。出力が 0〜1 の「確率」であることが特徴です。
手法出力用途
線形回帰連続値(-∞ ~ +∞)家賃予測・売上予測
ロジスティック回帰確率(0 ~ 1)生存予測・スパム判定・離脱予測

📈 シグモイド関数(S字曲線の直感的理解)

線形回帰は -∞ ~ +∞ の値を出しますが、確率は 0 ~ 1 の範囲でなければなりません。シグモイド関数はどんな値でも 0〜1 に押し込む「変換装置」です。

1.0 0.5 0.0 -6 -4 -2 0 2 4 6 決定境界 p=0.5 z=2 → p≈0.88 z=-2 → p≈0.12 z(線形結合の値) σ(z)=確率
シグモイド曲線
決定境界(p=0.5)
z の値シグモイド出力解釈
z ≫ 0(大きい正)≈ 1.0クラス1の確率が非常に高い
z = 0= 0.5五分五分(決定境界)
z ≪ 0(大きい負)≈ 0.0クラス0の確率が非常に高い

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

特徴量説明処理
pclassint客室クラス(1=一等, 2=二等, 3=三等)そのまま使用
agefloat年齢(欠損あり:約20%)中央値で補完
farefloat運賃そのまま使用
sibspint兄弟・配偶者の数そのまま使用
parchint親・子の数そのまま使用
sexstr性別(male/female)male=1, female=0
embarkedstr乗船港(C/Q/S)get_dummies(drop_first=True)
survivedint目的変数(1=生存, 0=死亡)y として使用
📊
Titanic データの生存率は約 38%(不均衡データ)。Accuracy だけでなく ROC-AUCRecall も確認することが重要です。

📝 問題

import pandas as pd
import numpy as np
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,
    roc_auc_score, roc_curve, confusion_matrix
)
import matplotlib.pyplot as plt

# Titanic データの読み込み(seabornから)
import seaborn as sns
df = sns.load_dataset("titanic")

問1 — データの前処理

  • survived を目的変数 y にする
  • 特徴量 Xpclass, age, fare, sibsp, parch, sex, embarked を使う
  • sex を数値化(male=1, female=0)
  • embarked をダミー変数化(pd.get_dummies, drop_first=True
  • age の欠損値を中央値で埋める
  • embarked の欠損値を最頻値で埋める

問2 — モデルの学習と確率予測

  • train_test_split で 80:20 に分割(stratify=y, random_state=42
  • Pipeline([("scaler", StandardScaler()), ("model", LogisticRegression(max_iter=500))]) を使って学習
  • テストデータで predict_proba を使い、クラス1(生存)の確率を取得
  • classification_report を出力する

問3 — ROC-AUC の計算と曲線の描画

  • roc_auc_score で AUC を計算する
  • roc_curve で FPR, TPR を取得し、matplotlib でROC曲線を描画する
  • グラフに AUC の値をタイトルまたはラベルに含める

問4 — 係数の解釈

  • pipeline.named_steps["model"].coef_[0] で係数を取得する
  • 特徴量名と係数をデータフレームにまとめ、係数の絶対値で降順に並べて表示する
  • 最も生存に正の影響を与える特徴量と、負の影響を与える特徴量を1つずつ答えよ

問5 — 閾値の変更による影響

  • デフォルト閾値(0.5)の Accuracy と Recall(生存クラス)を計算する
  • 閾値を 0.3 に変更したとき(pred = (proba >= 0.3).astype(int))の Accuracy と Recall を計算する
  • なぜ閾値を下げると Recall が上がるのかを言葉で説明せよ

🔍 ヒント(段階的開示)

ヒント1 — 方向性
  • 問1: df["sex"] = df["sex"].map({"male": 1, "female": 0})、欠損は .fillna(df["age"].median())
  • 問2: pipeline.predict_proba(X_test)[:, 1] でクラス1の確率を取得
  • 問3: from sklearn.metrics import roc_curve, roc_auc_score
  • 問4: pd.Series(coef, index=feature_names).abs().sort_values(ascending=False)
  • 問5: (proba >= threshold).astype(int) で閾値を変えた予測値を作る
ヒント2 — アプローチ
# 前処理の骨格
df["sex"] = df["sex"].map({"male": 1, "female": 0})
df["age"] = df["age"].fillna(df["age"].median())
df["embarked"] = df["embarked"].fillna(df["embarked"].mode()[0])
embarked_dummies = pd.get_dummies(df["embarked"], prefix="embarked", drop_first=True)

feature_cols = ["pclass", "age", "fare", "sibsp", "parch", "sex"]
X = pd.concat([df[feature_cols], embarked_dummies], axis=1)
y = df["survived"]
ヒント3 — コード骨格(ほぼ答え)
# ROC曲線の描画
fpr, tpr, thresholds = roc_curve(y_test, proba)
auc_score = roc_auc_score(y_test, proba)

plt.figure(figsize=(7, 5))
plt.plot(fpr, tpr, color="royalblue", lw=2, label=f"ROC curve (AUC = {auc_score:.4f})")
plt.plot([0, 1], [0, 1], "k--", lw=1, label="Random classifier")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve — Titanic Survival Prediction")
plt.legend()
plt.tight_layout()
plt.show()

模範解答

import pandas as pd
import numpy as np
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.metrics import (
    accuracy_score, classification_report,
    roc_auc_score, roc_curve, recall_score
)
import matplotlib.pyplot as plt

# ── データ読み込み ──
df = sns.load_dataset("titanic")

# ── 問1: 前処理 ──
df["sex"] = df["sex"].map({"male": 1, "female": 0})
df["age"] = df["age"].fillna(df["age"].median())
df["embarked"] = df["embarked"].fillna(df["embarked"].mode()[0])
embarked_dummies = pd.get_dummies(df["embarked"], prefix="embarked", drop_first=True)

feature_cols = ["pclass", "age", "fare", "sibsp", "parch", "sex"]
X = pd.concat([df[feature_cols], embarked_dummies], axis=1)
y = df["survived"]

print(f"データ形状: {X.shape}")
print(f"生存率: {y.mean():.3f}")

# ── 問2: 学習と予測 ──
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(max_iter=500, random_state=42))
])
pipeline.fit(X_train, y_train)

y_pred = pipeline.predict(X_test)
proba  = pipeline.predict_proba(X_test)[:, 1]

print("\n[Classification Report]")
print(classification_report(y_test, y_pred, target_names=["死亡(0)", "生存(1)"]))

# ── 問3: ROC-AUC ──
auc_score = roc_auc_score(y_test, proba)
print(f"ROC-AUC: {auc_score:.4f}")

fpr, tpr, _ = roc_curve(y_test, proba)
plt.figure(figsize=(7, 5))
plt.plot(fpr, tpr, color="royalblue", lw=2, label=f"ROC curve (AUC = {auc_score:.4f})")
plt.plot([0, 1], [0, 1], "k--", lw=1, label="Random classifier")
plt.xlabel("False Positive Rate")
plt.ylabel("True Positive Rate")
plt.title("ROC Curve — Titanic Survival Prediction")
plt.legend()
plt.tight_layout()
plt.show()

# ── 問4: 係数の解釈 ──
coef = pipeline.named_steps["model"].coef_[0]
feature_names = X.columns.tolist()
coef_df = pd.DataFrame({
    "feature": feature_names,
    "coefficient": coef
}).sort_values("coefficient", key=abs, ascending=False)

print("\n[係数ランキング(絶対値降順)]")
print(coef_df.to_string(index=False))

print(f"\n生存に最も正の影響: {coef_df[coef_df['coefficient'] > 0].iloc[0]['feature']}")
print(f"生存に最も負の影響: {coef_df[coef_df['coefficient'] < 0].iloc[0]['feature']}")

# ── 問5: 閾値の変更 ──
for threshold in [0.5, 0.3]:
    pred = (proba >= threshold).astype(int)
    acc  = accuracy_score(y_test, pred)
    rec  = recall_score(y_test, pred)
    print(f"\n閾値={threshold:.1f}: Accuracy={acc:.4f}, Recall(生存)={rec:.4f}")

print("\n【解説】閾値を下げる = より積極的に「生存」と予測する")
print("→ 本来生存だったのに「死亡」と間違えるケース(偽陰性)が減り、Recallが上がる")
📌
期待される結果の傾向:
ROC-AUC ≈ 0.85 前後(Titanic の LogisticRegression の典型的な性能)
係数の絶対値が最大 → sex(女性が生存しやすいという歴史的事実を反映)
閾値 0.3 では Recall が大幅に上がるが Accuracy・Precision は下がる

📊 係数チャート(Titanic 典型値)

StandardScaler 適用後の係数。絶対値が大きいほど予測への影響が大きい。

sex
-1.35
−1.35
pclass
-0.83
−0.83
age
-0.45
−0.45
fare
+0.68
+0.68
sibsp
-0.30
−0.30
embarked_Q
+0.22
+0.22
💡
sex の係数が最大 — Titanic では「女性・子供を先に」という原則があり、女性(female=0, male=1 → 係数が負)の生存率が圧倒的に高い。歴史的事実がモデルの係数に反映されています。

📈 ROC曲線の概念図

横軸: FPR(偽陽性率)、縦軸: TPR(真陽性率=Recall)。曲線が左上に近いほど良いモデル。

0.0 0.5 1.0 0.0 0.5 1.0 Random (AUC=0.5) Perfect (AUC=1.0) AUC ≈ 0.85 False Positive Rate (FPR) True Positive Rate (TPR)
LogisticRegression (Titanic)
Random classifier

⚖️ 閾値(Threshold)の変更と Precision-Recall トレードオフ

デフォルト: 閾値 = 0.5
≈ 80%
Accuracy
≈ 72%
Recall(生存クラス)
バランスの取れた予測。「死亡と予測して実は生存」というケースが多め。
カスタム: 閾値 = 0.3
≈ 74%
Accuracy(低下)
≈ 88%
Recall(生存クラス)大幅向上
「生存をより多く検出」。救命活動など「見逃しを減らしたい」場面に適切。
Precision vs Recall トレードオフ(閾値を下げると) Recall ↑ 88% Recall 72%(threshold=0.5) Precision ↓ 65% Precision 78%(threshold=0.5)

🪜 Step-by-Step 解説

1 カテゴリ変数の数値化

# sex: male=1, female=0(二値なので map が簡単)
df["sex"] = df["sex"].map({"male": 1, "female": 0})

# embarked: C/Q/S の3値 → one-hot encoding
# drop_first=True で多重共線性を防ぐ(C を基準とする)
embarked_dummies = pd.get_dummies(df["embarked"], prefix="embarked", drop_first=True)
# → embarked_Q, embarked_S の2列になる(embarked_C が落ちる)

drop_first=True を必ず使う理由:3つのカラムを作ると「3つ目 = 1 - 1つ目 - 2つ目」という関係が成立し、情報が重複(多重共線性)するためです。

2 predict と predict_proba の違い

y_pred  = pipeline.predict(X_test)           # クラス(0 or 1)
y_proba = pipeline.predict_proba(X_test)     # 各クラスの確率 [[0.3, 0.7], ...]
proba1  = pipeline.predict_proba(X_test)[:, 1]  # クラス1の確率のみ取得

確率を持っておくと、閾値変更・ROC-AUC 計算・スタッキングなど幅広く使えます。

3 ROC-AUC の意味

from sklearn.metrics import roc_auc_score, roc_curve

auc = roc_auc_score(y_test, proba)
# AUC = ランダムに選んだ正例と負例を比較したとき、正例の方が
#       高いスコアを持つ確率
# → AUC=1.0 が完璧、AUC=0.5 がランダムと同等

fpr, tpr, thresholds = roc_curve(y_test, proba)
# fpr: 各閾値での偽陽性率
# tpr: 各閾値での真陽性率(= Recall)
# thresholds: 対応する閾値リスト

4 係数の解釈(オッズ比)

import numpy as np

coef = pipeline.named_steps["model"].coef_[0]
feature_names = X.columns.tolist()

# exp(coef) = オッズ比
odds_ratio = np.exp(coef)
# sex の係数が -1.35 なら → exp(-1.35) ≈ 0.26
# つまり「male(sex=1)であると、生存のオッズが 0.26 倍(=74%減少)」

5 閾値(Threshold)の調整

from sklearn.metrics import accuracy_score, recall_score

for threshold in [0.5, 0.3]:
    pred = (proba >= threshold).astype(int)
    acc  = accuracy_score(y_test, pred)
    rec  = recall_score(y_test, pred)
    print(f"閾値={threshold:.1f}: Accuracy={acc:.4f}, Recall={rec:.4f}")

# Recall = TP / (TP + FN)
# FN = 「実際は生存だが死亡と予測」
# → 閾値を下げるとFNが減りRecallが上がる
# → ただし偽陽性(死亡なのに生存と予測)が増えPrecisionは下がる

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

シグモイド関数を身近な例えで

体重計の数値をどんなに大きくても「100点満点」に変換する装置を想像してください。シグモイド関数は「どんな数値入力でも 0〜1 の確率に変換する装置」です。

z の値σ(z)日本語の解釈
z = 2≈ 0.8888%の確率でクラス1
z = 0= 0.50五分五分(決定境界)
z = -2≈ 0.1212%の確率でクラス1

オッズ比(Odds Ratio)

係数 βexp(β)(オッズ比)解釈
β = 0.5≈ 1.651特徴量増えるたびにオッズが1.65倍
β = 0= 1.0影響なし
β = -1.35≈ 0.26オッズが0.26倍(74%減少)

正則化パラメータ C

C の値正則化の強さ特徴使い所
C = 0.01強い係数が小さく保たれる → 過学習防止特徴量が多い・ノイズが多い
C = 1.0標準(デフォルト)バランスが良いまず試す値
C = 100弱い係数が自由になる → 過学習しやすいデータが豊富・ノイズが少ない

🏆 Kaggleでの実践的な使い方

用途説明コンペ例
ベースラインモデルシンプルで解釈性が高い → まず LogisticRegression で確認Titanic, Credit Risk
確率キャリブレーションpredict_proba の出力が本当に確率か確認保険コンペ
スタッキングの1層目弱い予測器として GBDTの入力に使うほぼ全てのコンペ
解釈・説明係数でビジネス向けの説明が可能金融・医療系コンペ
# Kaggle 実践: cross_val_score で AUC を評価
from sklearn.model_selection import StratifiedKFold, cross_val_score

cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(max_iter=500, C=1.0))
])

auc_scores = cross_val_score(pipeline, X, y, cv=cv, scoring="roc_auc", n_jobs=-1)
print(f"CV AUC: {auc_scores.mean():.4f} ± {auc_scores.std():.4f}")
# C パラメータ(正則化の強さの逆数)を変えて最適化することもできる

# C の比較(Optuna の前哨戦)
for c in [0.01, 0.1, 1.0, 10.0, 100.0]:
    pipe = Pipeline([("scaler", StandardScaler()),
                     ("model",  LogisticRegression(C=c, max_iter=500))])
    score = cross_val_score(pipe, X, y, cv=cv, scoring="roc_auc").mean()
    print(f"C={c:7.2f}: AUC={score:.4f}")

⚠️ よくある誤解・ミス

誤解・ミスなぜ起こるか正しい理解
ロジスティック回帰は「回帰」だと思う 名前が紛らわしい 分類アルゴリズム。出力は確率(0〜1)
predict だけ使い確率を捨てる predict があれば十分と思いがち ROC-AUC・閾値調整には predict_proba が必須
カテゴリ変数をそのまま渡す エンコードを忘れる get_dummies / LabelEncoder で数値化する
max_iter の警告を無視する エラーではなく警告だから 収束していない = 係数が不正確。max_iter を増やす
C=1.0(デフォルト)から変えない パラメータを知らない C が小さい → 強い正則化(過学習防止)、C が大きい → 弱い正則化
neg_mean_squared_error の符号を忘れる cross_val_score の符号反転に気づかない -scores.mean() で正の値にする(回帰時)

🚀 次のステップ

  • 発展: LogisticRegression(C=0.01)C=100 でスコアを比較し、正則化の効果を体験する
  • 次回予告 (Day 053): ロジスティック回帰の正則化と多クラス分類multi_class="multinomial"、Iris データセットで3クラス分類を実装します

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

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