PR

Python機械学習実装ガイド:scikit-learnで始める実践的な予測モデル構築

Python機械学習実装ガイド:scikit-learnで始める実践的な予測モデル構築

高精度な機械学習モデルを構築したはずなのに、いざ本番環境に投入すると全く役に立たない。あるいは、評価指標の数値ばかりを追ってしまい、肝心のビジネスインパクト(売上向上やコスト削減)に結びつかない……。これは、データサイエンティストとしてのキャリア初期に、私がまさに頭をぶつけ、何度も苦渋を舐めた壁です。

Pythonの scikit-learn は、機械学習モデルの構築から評価までを網羅した素晴らしいライブラリですが、ただAPIを呼び出すだけでは実用に耐えうるモデルは作れません。本記事では、ビジネスで最も需要の高い「顧客の解約予測」を題材に、不均衡データへの対処から特徴量エンジニアリング、モデルの解釈性まで、現場で即戦力となる実装アプローチを解説します。


【Haruの実体験】「精度98%」の無駄モデルで大恥をかいた失敗と、150万円の損失回避

数年前、私が初めて主導したサブスクリプション型SaaS(月額2万円)のユーザー解約予測プロジェクトで、私は手元のデータをランダムフォレストに放り込み、テストデータで「Accuracy(正解率)98%」という驚異的な数値を叩き出しました。自信満々で役員会に報告したのですが、現場の営業責任者から「で、来月誰が解約するの?」と聞かれた時、言葉が詰まりました。

実は、データの解約率はわずか2%(不均衡データ)でした。モデルは「全員解約しない(解約フラグ=0)」と予測するだけで、自動的に98%の正解率を出していたのです。実質的な解約予測能力は「ゼロ」でした。

この手痛い大失敗の後、私は以下の改善を行いました。
1. class_weight='balanced' の設定やSMOTEによる不均衡データの補正。
2. 顧客行動ログから「過去7日間のログイン回数」「主要機能Aの使用頻度の変化率(減少傾向)」といった、時間の経過に伴う変化を示す動的な特徴量(時系列特徴量)の設計。

モデルを再構築した結果、最も重要である「実際に解約する人をどれだけ見つけ出せたか」を表すRecall(再現率)が従来の10%から72%へと劇的に向上。この予測に基づいて営業チームが事前フォローを行うことで、月間約150万円(年間約1,800万円)の解約による機会損失を食い止めることができました。


1. 予測モデル構築の全体フローと前処理

機械学習モデルのコードを書く前に、データ構造を整える必要があります。

1.1. 不均衡データ(Imbalanced Data)の罠

現実のビジネスデータ(解約、不正検知、コンバージョンなど)は、ターゲットとなるポジティブ例(1)が極端に少ない不均衡データであることがほとんどです。正解率(Accuracy)だけを評価指標にすると失敗します。評価には必ず Precision(適合率)Recall(再現率)F1-score、そして AUC-ROC を使いましょう。

1.2. 実践的なデータ前処理コード

まずは、不均衡なダミーデータを作成し、scikit-learn で前処理と学習用分割を行うコードです。

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
# ダミーのSaaS顧客データ作成(10,000件、解約率約5%)
np.random.seed(42)
n_samples = 10000
ages = np.random.normal(35, 8, n_samples).astype(int)
usage_hours = np.random.gamma(shape=2, scale=10, size=n_samples)
support_calls = np.random.poisson(lam=1.2, size=n_samples)
plan = np.random.choice(['Basic', 'Standard', 'Enterprise'], n_samples, p=[0.7, 0.2, 0.1])
# 解約フラグの設計(サポートへの電話が多く、利用時間が短いと解約しやすい)
churn_prob = 1 / (1 + np.exp(-(-3 + 0.8 * support_calls - 0.05 * usage_hours)))
churn = (np.random.rand(n_samples) < churn_prob).astype(int)
df = pd.DataFrame({
'age': ages,
'usage_hours': usage_hours,
'support_calls': support_calls,
'plan': plan,
'churn': churn
})
# 特徴量とターゲットに分離
X = df.drop('churn', axis=1)
y = df['churn']
# 訓練データとテストデータに分割(層化分割 Stratified で比率を維持)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
print(f"解約者数(Train): {y_train.sum()} / 全体: {len(y_train)} (比率: {y_train.mean():.2%})")

2. パイプライン(Pipeline)を使用したモデル構築

前処理とモデル学習を統一的に扱うため、scikit-learnPipeline を使用します。これにより、データリーク(テストデータの情報が前処理時に訓練データに混入すること)を防ぐことができます。

2.1. カテゴリ変数と数値変数の同時処理

2025年〜2026年現在のベストプラクティスとして、ColumnTransformer を使用してエンコーディングと標準化を統合します。

from sklearn.pipeline import Pipeline
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, roc_auc_score
# 数値変数とカテゴリ変数のリスト
numeric_features = ['age', 'usage_hours', 'support_calls']
categorical_features = ['plan']
# 数値用・カテゴリ用の前処理を定義
numeric_transformer = Pipeline(steps=[
('scaler', StandardScaler())
])
categorical_transformer = Pipeline(steps=[
('onehot', OneHotEncoder(drop='first', handle_unknown='ignore'))
])
# 前処理を一元化
preprocessor = ColumnTransformer(
transformers=[
('num', numeric_transformer, numeric_features),
('cat', categorical_transformer, categorical_features)
]
)
# パイプラインにモデルを結合(不均衡対策としてクラスウェイトを調整)
clf = Pipeline(steps=[
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(class_weight='balanced', random_state=42))
])
# モデルの学習
clf.fit(X_train, y_train)
# 予測と評価
y_pred = clf.predict(X_test)
y_pred_proba = clf.predict_proba(X_test)[:, 1]
print("\n--- テストデータ評価結果 ---")
print(classification_report(y_test, y_pred))
print(f"AUC-ROC Score: {roc_auc_score(y_test, y_pred_proba):.4f}")

3. ハイパーパラメータの最適化

モデルの精度を最大化するために、GridSearchCVRandomizedSearchCV を使用してパラメータ探索を行います。

from sklearn.model_selection import GridSearchCV
# 探索パラメータの定義(モデル名のプレフィックス 'classifier__' が必要)
param_grid = {
'classifier__n_estimators': [50, 100, 200],
'classifier__max_depth': [5, 10, None],
'classifier__min_samples_split': [2, 5, 10]
}
# グリッドサーチの設定(評価指標はF1値やROC-AUCを指定)
grid_search = GridSearchCV(
clf, param_grid, cv=5, scoring='f1_weighted', n_jobs=-1
)
grid_search.fit(X_train, y_train)
print(f"ベストパラメータ: {grid_search.best_params_}")
print(f"ベストクロスバリデーションスコア: {grid_search.best_score_:.4f}")

4. モデルの解釈性:なぜその顧客は解約しそうなのか?

機械学習モデルが「解約予測スコア: 85%」と出力しても、現場のメンバーは「なぜその顧客が危険なのか」が分からなければ、具体的な引き止めアクション(アプローチメールの送信など)を起こせません。

Permutation Importance の活用

モデルがどの特徴量を重視しているかを測定するため、テストデータ上で特徴量をシャッフルし、精度の低下度合いを測る permutation_importance を実行します。

from sklearn.inspection import permutation_importance
import matplotlib.pyplot as plt
# 最良のモデルを取得
best_model = grid_search.best_estimator_
# パーミュテーション重要度の計算
result = permutation_importance(
best_model, X_test, y_test, n_repeats=10, random_state=42, n_jobs=-1
)
# 重要度の視覚化
sorted_importances_idx = result.importances_mean.argsort()
importances = pd.DataFrame(
result.importances[sorted_importances_idx].T,
columns=X_test.columns[sorted_importances_idx],
)
ax = importances.plot.box(vert=False, whis=10)
ax.set_title("特徴量の重要度(Permutation Importance)")
ax.axvline(x=0, color="k", linestyle="--")
ax.set_xlabel("F1スコアの減少幅")
plt.tight_layout()
plt.show()

まとめ:ビジネスインパクトから逆算するモデル開発

機械学習モデルの構築は、clf.fit() を呼び出して終わりではありません。
「予測結果をどのように使って解約を防止するか?」
「偽陽性(解約しないのに解約すると予測)と偽陰性(解約するのに見逃す)のどちらが、ビジネス上の損失が大きいか?」
を考え、最適な確率閾値(Threshold)を決定することが重要です。

ぜひ、正解率の高さだけに惑わされず、ビジネス成果に繋がる予測モデル開発に挑戦してみてください。


関連記事

公式参考資料

コメント

タイトルとURLをコピーしました