決定木とランダムフォレスト
田中VPoE:「線形モデルでベースラインを作った。次は決定木だ。決定木は『もし購入回数が3回未満で、かつ最終ログインが30日以上前なら離反する可能性が高い』というように、人間が理解しやすいルールで予測する。」
あなた:「if文の連鎖みたいなものですか?」
田中VPoE:「まさにそうだ。そして、その決定木を大量に集めて多数決を取るのがランダムフォレストだ。単体の決定木より圧倒的に精度が上がる。」
決定木(Decision Tree)
決定木の仕組み
データを条件分岐で繰り返し分割し、各末端ノード(リーフ)で予測を行います。
from sklearn.tree import DecisionTreeClassifier, plot_tree
import matplotlib.pyplot as plt
# 決定木の構築
dt = DecisionTreeClassifier(
max_depth=3, # 木の深さを制限
min_samples_leaf=20, # 葉ノードの最小サンプル数
random_state=42,
)
dt.fit(X_train, y_train)
# 決定木の可視化
plt.figure(figsize=(20, 10))
plot_tree(dt, feature_names=feature_names,
class_names=['継続', '離反'],
filled=True, rounded=True, fontsize=10)
plt.title('離反予測の決定木')
plt.tight_layout()
plt.show()
分割基準
| 基準 | 説明 |
|---|---|
| ジニ不純度(Gini) | ノード内のクラスの混合度(デフォルト) |
| エントロピー(Entropy) | 情報量ベースの不純度 |
# ジニ不純度の計算例
# 離反率50%のノード: Gini = 1 - (0.5^2 + 0.5^2) = 0.5(最大の不純度)
# 離反率10%のノード: Gini = 1 - (0.9^2 + 0.1^2) = 0.18(比較的純粋)
# 離反率0%のノード: Gini = 1 - (1.0^2 + 0.0^2) = 0.0(完全に純粋)
決定木の長所と短所
| 長所 | 短所 |
|---|---|
| 解釈性が高い | 過学習しやすい |
| スケーリング不要 | 不安定(データが少し変わると木が大きく変化) |
| 非線形パターンを捉える | 単体での精度は低め |
ランダムフォレスト(Random Forest)
バギング(Bootstrap Aggregating)
ランダムフォレストの基盤となるアンサンブル手法です。
1. 学習データからブートストラップサンプル(重複ありの復元抽出)を作成
2. 各サンプルで個別の決定木を学習
3. 全ての決定木の予測結果で多数決(分類)/ 平均(回帰)
ランダムフォレストの実装
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report, f1_score
rf = RandomForestClassifier(
n_estimators=100, # 決定木の数
max_depth=10, # 各木の最大深さ
min_samples_leaf=5, # 葉ノードの最小サンプル数
max_features='sqrt', # 各分割で使う特徴量数
random_state=42,
n_jobs=-1, # 全CPUコアを使用
)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=['継続', '離反']))
print(f"F1スコア: {f1_score(y_test, y_pred):.3f}")
なぜランダムフォレストが強いのか
- バギング: 複数の木の平均でバリアンスを削減
- 特徴量のランダム化: 各分割で特徴量のサブセットを使い、木の多様性を確保
- 過学習への耐性: 個々の木が過学習しても、集団で平均化される
特徴量重要度
import pandas as pd
import matplotlib.pyplot as plt
# 特徴量重要度の取得
importance = pd.Series(rf.feature_importances_, index=feature_names)
importance = importance.sort_values(ascending=True)
plt.figure(figsize=(10, 8))
importance.plot(kind='barh', color='steelblue')
plt.title('特徴量重要度(ランダムフォレスト)')
plt.xlabel('重要度(Gini Importance)')
plt.tight_layout()
plt.show()
# 上位5つの特徴量
print("=== 重要な特徴量 TOP 5 ===")
for i, (feat, imp) in enumerate(importance.sort_values(ascending=False).head().items(), 1):
print(f"{i}. {feat}: {imp:.4f}")
主要パラメータの調整
# パラメータの影響を確認
from sklearn.model_selection import cross_val_score
# n_estimators(木の数)の影響
for n in [10, 50, 100, 200, 500]:
rf = RandomForestClassifier(n_estimators=n, random_state=42, n_jobs=-1)
scores = cross_val_score(rf, X_train, y_train, cv=5, scoring='f1')
print(f"n_estimators={n:>3}: F1 = {scores.mean():.3f} (+/- {scores.std():.3f})")
| パラメータ | 意味 | 推奨範囲 |
|---|---|---|
| n_estimators | 木の数 | 100〜500 |
| max_depth | 木の最大深さ | 5〜20, None |
| min_samples_leaf | 葉の最小サンプル数 | 1〜20 |
| max_features | 各分割の特徴量数 | ’sqrt’, ‘log2’ |
OOB(Out-of-Bag)スコア
ブートストラップで使われなかったデータで検証する仕組みです。
rf_oob = RandomForestClassifier(
n_estimators=100,
oob_score=True,
random_state=42,
n_jobs=-1,
)
rf_oob.fit(X_train, y_train)
print(f"OOBスコア: {rf_oob.oob_score_:.3f}")
# → 検証データなしでもモデルの汎化性能を推定できる
まとめ
- 決定木は解釈性が高いが、単体では過学習しやすい
- ランダムフォレストはバギングと特徴量のランダム化で精度を向上
- 特徴量重要度でビジネスへの説明が可能
- スケーリング不要で使いやすい
- OOB スコアで追加の検証データなしに性能を推定できる
チェックリスト
- 決定木の分割基準(ジニ不純度)を理解した
- バギングの仕組みを説明できる
- ランダムフォレストの主要パラメータを把握した
- 特徴量重要度の解釈方法を理解した
次のステップへ
次のレッスンでは、現在最も精度が高いとされる勾配ブースティング手法を学びます。
推定読了時間: 30分