演習:ML問題を設計しよう
田中VPoE:「さて、ここまで学んだ知識を使って、実際に NetShop の離反予測を ML 問題として設計してみよう。コードを書く前に、問題設定を明確にすることが成功の第一歩だ。」
あなた:「問題定義からデータ分割、評価指標の選定まで、一通りやってみます。」
田中VPoE:「良い心構えだ。実務では、ここの設計が甘いままモデル構築に突入して、後から手戻りになるケースが多い。しっかり取り組んでくれ。」
ミッション概要
NetShop 社の顧客離反予測プロジェクトの ML 問題設計を行います。ビジネス課題の理解から、問題の定式化、データ分割、評価指標の選定までを実践します。
Mission 1: ビジネス課題を ML 問題に変換する
NetShop 社のビジネス課題を整理し、ML 問題として定式化してください。
要件
- 以下のビジネス背景を読み、ML 問題として定義する
- NetShop 社は EC サイトを運営
- 月間アクティブユーザー 50,000 人
- 直近の月次離反率は約 8%
- 離反した顧客の再獲得コストは、維持コストの 5 倍
- 以下を明確に定義する
- 離反の定義(何をもって「離反」とするか)
- 予測対象期間
- 予測のタイミングと頻度
- 利用可能な特徴量の候補
- 予測結果の活用方法
# ヒント: 問題定義のテンプレート
problem_definition = {
"business_goal": "顧客離反を事前に予測し、防止施策を打つ",
"ml_task": "???", # 分類 or 回帰
"target_variable": "???", # 何を予測するか
"prediction_window": "???", # 何日先まで予測するか
"features_available": ["???"], # 使える特徴量
"evaluation_metric": "???", # 主要な評価指標
"success_criteria": "???", # 成功基準
}
解答例
problem_definition = {
"business_goal": "顧客離反を事前に予測し、防止施策を打つ",
"ml_task": "二値分類(Binary Classification)",
"target_variable": "30日以内に離反するか(1: 離反, 0: 継続)",
"churn_definition": "30日以上ログインおよび購入がない状態",
"prediction_window": "30日先",
"prediction_timing": "毎日バッチ実行",
"features_available": [
"過去90日の購入回数",
"過去90日の平均購入額",
"最終ログインからの経過日数",
"最終購入からの経過日数",
"サポート問い合わせ回数",
"会員期間(月数)",
"利用デバイス",
"過去30日のページビュー数",
"クーポン利用回数",
"お気に入り登録数",
],
"evaluation_metric": "F1スコア(再現率を重視)",
"success_criteria": "F1スコア 0.75 以上、再現率 0.80 以上",
"action_plan": "離反確率が閾値以上の顧客にクーポン配布・フォローメール送信",
}
print("ML問題定義:")
for key, value in problem_definition.items():
print(f" {key}: {value}")
Mission 2: データを適切に分割する
サンプルデータを使って、学習・検証・テストの3分割を行ってください。
要件
- 不均衡データ(離反率 8%)を生成する
- 層化分割(stratify)を使ってデータを分割する
- 各データセットの離反率が維持されていることを確認する
- 分割の妥当性を検証する
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
# サンプルデータ生成
np.random.seed(42)
n_samples = 5000
churn_rate = 0.08
# ここにデータ分割のコードを書く
解答例
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
np.random.seed(42)
n_samples = 5000
churn_rate = 0.08
# サンプルデータ生成
n_churned = int(n_samples * churn_rate)
n_active = n_samples - n_churned
y = np.array([0] * n_active + [1] * n_churned)
X = np.random.randn(n_samples, 10) # 10個の特徴量
# シャッフル
shuffle_idx = np.random.permutation(n_samples)
X, y = X[shuffle_idx], y[shuffle_idx]
print(f"全データ: {len(y)} 件, 離反率: {y.mean():.3f}")
# Step 1: テストデータを分離(20%)
X_temp, X_test, y_temp, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# Step 2: 学習データと検証データに分割
X_train, X_val, y_train, y_val = train_test_split(
X_temp, y_temp, test_size=0.25, random_state=42, stratify=y_temp
)
# 結果の確認
print(f"\n--- データ分割結果 ---")
print(f"学習データ: {len(y_train)} 件 ({len(y_train)/len(y):.0%}), 離反率: {y_train.mean():.3f}")
print(f"検証データ: {len(y_val)} 件 ({len(y_val)/len(y):.0%}), 離反率: {y_val.mean():.3f}")
print(f"テストデータ: {len(y_test)} 件 ({len(y_test)/len(y):.0%}), 離反率: {y_test.mean():.3f}")
# 分割の妥当性検証
print(f"\n--- 妥当性検証 ---")
print(f"各セットの離反率の差が小さいことを確認:")
rates = [y_train.mean(), y_val.mean(), y_test.mean()]
print(f" 最大差: {max(rates) - min(rates):.4f}")
print(f" → {'OK: 十分小さい' if max(rates) - min(rates) < 0.01 else 'NG: 偏りあり'}")
Mission 3: 評価指標を比較する
ダミーの予測結果を使って、各評価指標を計算し、指標ごとの特徴を理解してください。
要件
- 3つの異なるモデルの予測結果(ダミー)を作成する
- モデルA: 高い適合率、低い再現率
- モデルB: 低い適合率、高い再現率
- モデルC: バランス型
- 各モデルについて、正解率・適合率・再現率・F1スコアを計算する
- NetShop の離反予測に最適なモデルを選び、理由を述べる
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
confusion_matrix, classification_report
)
# ここに評価指標の比較コードを書く
解答例
import numpy as np
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
classification_report
)
np.random.seed(42)
# 実際のラベル(離反率10%)
n = 1000
y_true = np.array([0]*900 + [1]*100)
# モデルA: 高適合率・低再現率(慎重に予測)
y_pred_A = np.zeros(n, dtype=int)
y_pred_A[900:930] = 1 # 30件だけ離反と予測、うち28件が正解
# モデルB: 低適合率・高再現率(積極的に予測)
y_pred_B = np.zeros(n, dtype=int)
y_pred_B[700:] = 1 # 300件を離反と予測、離反者はほぼ網羅
# モデルC: バランス型
y_pred_C = np.zeros(n, dtype=int)
y_pred_C[850:] = 1 # 150件を離反と予測
models = {
"モデルA(高適合率型)": y_pred_A,
"モデルB(高再現率型)": y_pred_B,
"モデルC(バランス型)": y_pred_C,
}
print("=" * 60)
for name, y_pred in models.items():
print(f"\n{name}")
print("-" * 40)
print(f" 正解率: {accuracy_score(y_true, y_pred):.3f}")
print(f" 適合率: {precision_score(y_true, y_pred):.3f}")
print(f" 再現率: {recall_score(y_true, y_pred):.3f}")
print(f" F1スコア: {f1_score(y_true, y_pred):.3f}")
print("\n" + "=" * 60)
print("\n【結論】")
print("NetShop の離反予測では、離反者の見逃し(FN)が")
print("大きな損失につながるため、再現率を重視すべき。")
print("ただし適合率が極端に低いとクーポンコストが膨らむため、")
print("モデルC(バランス型)をベースに、閾値調整で")
print("再現率を高めるアプローチが推奨される。")
達成度チェック
- ビジネス課題を ML 問題として定式化できた
- 離反の定義、予測対象、利用特徴量を明確にした
- データを3分割し、層化分割の効果を確認した
- 複数の評価指標を計算し、特徴を理解した
- NetShop に適した評価指標を選択し、理由を述べた
推定所要時間: 60分