理学療法士がKaggleで脳卒中予測に挑戦した話②|特徴量エンジニアリング〜ランダムフォレスト
前回のおさらい
前回の記事では、Kaggleの脳卒中予測データセットでEDA〜ロジスティック回帰のベースラインを作りました。
結果はこんな感じでした。
| 指標 | 値 |
|---|---|
| Recall | 0.795 |
| Precision | 0.134 |
| F1 | 0.229 |
脳卒中の約80%を拾えるけど、「脳卒中です」と予測した人のうち本当にそうなのは13%だけ。スクリーニング(一次検査)としてはアリだけど、もうちょっと何とかしたい。
今回は特徴量エンジニアリングとランダムフォレストに挑戦します。
特徴量エンジニアリング
特徴量エンジニアリングって要は「モデルに食わせるデータを工夫する」ことです。既存の変数を組み合わせて、新しい変数を作ります。
PTとしての臨床経験をほぼ忘れており、あまり活かせず。。。(まあ、リスクになるかなーくらい。今はClaudeが関連しそうな特徴量を提示してくれるからすごい。。。)
# 高齢かつ高血糖 → 糖尿病リスクが高く、脳卒中の危険因子
df['age_glucose'] = df['age'] * df['avg_glucose_level']
# 高齢かつ肥満 → 動脈硬化・心血管リスクが高まる
df['age_bmi'] = df['age'] * df['bmi']
# 高齢かつ高血圧 → 血管への負担が大きく、脳梗塞・脳出血のリスクが高い
df['age_hypertension'] = df['age'] * df['hypertension']
なぜ年齢との掛け算なのか?
加齢で血管は硬く・もろくなります。若い血管はゴムホースのように柔軟ですが、年を取ると古いホースのように硬くなる。そこに高血圧や高血糖が重なると、詰まりやすくなる(脳梗塞)し、破れやすくもなる(脳出血)。
だから「高齢 × 高血圧」のような組み合わせは、単体の変数よりも脳卒中との関連が強い可能性があります。
ポイントは闇雲に掛け合わせるのではなく、「臨床的に意味がある組み合わせか?」を考えること。(AIとドメイン知識を組み合わせられると強いんだろうなあと実感)
特徴量の重複チェック:相関行列
ただし、新しく作った変数が既存の変数とほぼ同じ情報を持っていたら、ノイズになるだけです。相関行列で確認しました。
df[['age', 'avg_glucose_level', 'bmi', 'hypertension', 'age_glucose', 'age_bmi', 'age_hypertension']].corr()
気になったペア:
-
ageとage_bmi:相関0.906 -
hypertensionとage_hypertension:相関0.973
特にage_hypertensionはhypertensionとほぼ同じ動きをしていました。hypertensionは0/1の二値なので、0の人は age × 0 = 0 になり、結局hypertension列のコピーに近くなってしまう。
一般的に相関0.9以上は「ほぼ同じ情報」なので除外を検討します。ただし「高血圧の中でも高齢者ほどリスクが高い」という情報はhypertension単体では表現できないので、臨床的には意味がある。統計的な判断とドメイン知識の判断がぶつかる場面です。
今回はひとまず全部残してモデルに入れました。
特徴量追加の結果
| 指標 | ベースライン | 特徴量追加後 |
|---|---|---|
| Recall | 0.795 | 0.803 |
| Precision | 0.134 | 0.136 |
| F1 | 0.229 | 0.233 |
微増。劇的な改善ではないけど、方向性としては間違っていない。
ランダムフォレストに挑戦
次はモデル自体を変えてみます。ロジスティック回帰は「各変数に重みをかけて足し算する」モデルなので、直線的な関係しか捉えられません。
ランダムフォレストは決定木の多数決です。
決定木は「年齢 > 60?→ Yesなら血糖値 > 150?→ Yesならリスク高い」のように、条件分岐で分類します。これを100本とか200本作って、最終的に多数決で予測を決めます。
イメージとしては、医師が1人で判断するより、100人の医師の多数決のほうが信頼できる、という感じです。
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(
random_state=42,
class_weight='balanced',
n_estimators=200, # 木を200本作る
max_depth=5, # 木の深さを制限(過学習防止)
min_samples_leaf=10 # 末端グループに最低10人(過学習防止)
)
つまずいたところ:ハイパーパラメータの調整
最初デフォルト設定で動かしたら、Recallがほぼ0でした。ロジスティック回帰と同じくclass_weight='balanced'を入れても改善せず焦りました。
原因は過学習。デフォルトだと木が深くなりすぎて、学習データに過剰にフィットしてしまい、テストデータでは全員を「脳卒中なし」と予測してしまう。
max_depth=5(木の深さを5段階に制限)とmin_samples_leaf=10(末端のグループに最低10人必要)を設定したら解決しました。
ランダムフォレストの結果
| 指標 | ロジスティック回帰 | ランダムフォレスト |
|---|---|---|
| Recall | 0.803 | 0.795 |
| Precision | 0.136 | 0.133 |
| F1 | 0.233 | 0.228 |
ほぼ同じ。 むしろロジスティック回帰のほうが微妙に良い。。。
結果が同じだったことの意味
最初は「ランダムフォレストのほうが複雑で高性能だから良い結果が出るはず」と期待していました。でも結果はほぼ同じ。
これは何を意味するか?
ロジスティック回帰は直線的な関係しか捉えられない。ランダムフォレストは非線形の関係も捉えられる。つまりランダムフォレストのほうが「できることが多い」モデル。それでも改善しなかったということは、このデータの変数とstrokeの関係はほぼ直線的で、ランダムフォレストの強みが活きなかった可能性が高い。
もう一つの解釈は、ボトルネックがモデルではなくデータにあるということ。2つの異なるアプローチで同じ結果なら、モデルの種類が問題ではなく、入力データ(特徴量)の情報量が足りないのかもしれない。
よく言われる「Garbage In, Garbage Out(ゴミを入れたらゴミが出る)」。モデルをいくら変えても、データが良くなければ結果は変わらない。
今回の学び
- 特徴量エンジニアリングはドメイン知識が武器になる。 闘雲に組み合わせるのではなく、「臨床的に意味があるか」で判断する。
- 相関行列で特徴量の重複をチェックする。 0.9以上は除外を検討。ただしドメイン知識との兼ね合いもある。
- 高性能なモデル ≠ 良い結果。 データの性質に合ったモデルを選ぶことが大事。
- モデルを変えても改善しないなら、データ(特徴量)を疑う。
- AIと相談しながら特徴量を作成するスキルも重要だと感じた。
次のステップ
- 特徴量選択(不要な変数の除外)
- LightGBMなど他のモデルの検討
- データの前処理の見直し
正直、「モデルを変えれば解決するだろう」と思っていた自分がいました。現実はそう甘くない。次はデータそのものと向き合います。
あと、ロジスティック回帰分析にせよ、ランダムフォレストにせよ、何故その手法を使用するのか言語化することが難しい、課題はたくさんですね。。
続きはまた書きます。
*記事作成にはClaude(Anthropic)を活用。Claudeは凄い。。。