[SIGNATE] FDUA 第2回金融データ活用チャレンジ振り返り(4th Solution)
0. はじめに
この記事は、SIGNATEで2024/01/18~2024/02/15の期間に開催された「第2回 金融データ活用チャレンジ」の振り返りと解法(4th solution)紹介をするものです。
まずはじめに、私たちのチームの結果は以下の通りになります。
最終的には4位という結果が得られたものの、コンペ最終日の混乱を考えると本来の順位とは異なるものである可能性が高く、手放しに喜べる状態ではありません。しかしながら、他の方々のコンペ振り返り記事からたくさんのことを学ぶことができたと考えており、私自身が振り返りを書くことに多少なりとも意味があるのではないか(もしかすると無意味なものかもしれないですが)と考えたため本記事を執筆しました。
1. 解法
以下、私たちのコンペの解法についての紹介です。
1.1. 解法の概要
私たちのモデルの概要は以下の通りになります。
データセットを前処理し、LightGBM baseline、LightGBM、CatBoostの3種類のモデルで予測した結果を、ロジスティック回帰を用いてスタッキングしました。そしてその予測結果を0、1に変換してsubmissionを作成しました。(偶然にも、1位のチームの方々のモデルの概要と非常に似た構造であったのは驚きでした。)
1.2. 前処理
前処理では、データ型を変換したり、新しい特徴量を作成したりしました。3種類のモデルで共通の処理と異なる処理があるので、それぞれ分けて書きたいと思います。
- 共通の処理
- 数値データとして扱うべきものをfloat型に。
- カテゴリ変数として扱うべきものをstr型に。
- LightGBM baselineの処理
- 前述した共通の処理のみ。
- LightGBMの処理
- FranchiseCodeのうち、出現回数が2回以下のものをまとめた。(外れ値の影響を回避)
- ApprovalDateから、月だけを取り出してApprovalMonthとした。(季節性の考慮)
- fred(https://fred.stlouisfed.org)から、「米国の政策金利」、「GDP変化率」、「失業率」を取得し、新しい特徴量として追加した。(これらの経済状況が影響するのではないかと判断、あまり効果はなかった。)
- 「政策金利×失業率」を新しい特徴量として追加した。(例えば、高金利/高失業率だとローンには厳しい状況である、低金利/低失業率だとその逆。)これは結構効果的だった。(単体で効果的だったというより、前述の新しい3つの特徴量とこの特徴量の4つを合わせて用いると効果的だった。)
- すべてのカテゴリ変数に対して、Target-Encodingを行った。これは非常に効果的であった。
- CatBoostの処理
- FranchiseCodeのうち、出現回数が2回以下のものをまとめた。(外れ値の影響を回避)
- ApprovalDateから、月だけを取り出してApprovalMonthとした。(季節性の考慮)
以上になります。
もちろん、これ以外にもたくさん試しましたが思いのほか効果的な特徴量を作成することができませんでした。その原因としては他の方々も言及されているように、やはり「生成データであった」ことが関係していると考えられます。これにより、「本来外れ値であるようなカテゴリが増強されてしまう」といった現象が起きたのではないかと考えられます。そのため、ApprovalDateなどの変数重要度が高く、これらを加工してしまうと精度が悪化するといった現象が起きたと考えられます。
このことから、今回のコンペでは、カテゴリ変数や数値変数を集約するといった類の処理はかえって悪手である可能性が高いと判断し、そのような作業はほとんど行いませんでした。その代わり、外部データを使用することを考えました。外部データであれば先ほど指摘したような現象が起きにくいと考えたためです(この仮説が間違っていたらすみません。)。とはいえ、無限にある外部データからピンポイントに効果的な特徴量を見つけ出すことは出来ず、SBAローンや企業融資のドメイン知識の必要性を感じました。
1.3. 学習モデル
学習モデルの作成にあたっては、特段難しいことは行っていません。しっかりとクロスバリデーションを行ったぐらいです。
1.3.1. 第1層のモデル
3種類のモデルすべてで、5-Foldのクロスバリデーションを行って学習しました。また、target-encodingを行ったLightGBMでは、leakが起きないように注意しました(詳しい実装は、「Kaggleで勝つデータ分析の技術」(通称、Kaggle本)を参照してください。)。
ハイパーパラメータをOptuna等でチューニングしたりする作業も行いませんでした。ハイパーパラメータは、「Kaggleで磨く 機械学習の実践力--実務xコンペが鍛えたプロの手順」で提示されているデフォルトの値を参考に設定しました。
1.3.2. 第2層のモデル
第1層の3種類のモデルの予測値を特徴量として、Logistic Regressionを用いました。この際も、5-Foldのクロスバリデーションを行いました。ハイパーパラメータでは、class_weigh='balanced'を付け加えました。
1.4. 後処理
スタッキングの予測値を0, 1に変換する際の閾値の定め方ですが、他の方々が言及されているように、trainデータとtestデータが似通っていたため、trainのoofのMean-F1が最大となるような閾値(best_thres)を採用しました。
def get_score(y_pred, y_true):
return f1_score(y_true, y_pred, average='macro')
thres = 0
bestscore = 0
best_thres = 0
for a in range(100):
a = a / 100
preds = np.where(train_oof[f'pred_ensemble{emsemble_num}'] > a, 1, 0)
score = get_score(train_pred_df['MIS_Status'].values, preds)
if bestscore < score:
bestscore = score
best_thres = a
thres = print(best_thres, score)
1.5. 最終提出の選択
私たちのチームでは、このモデルがcv、publicLBの両方においてある程度優れていたため、こちらのモデルを採用しました(publicLBでは最終的に70位ぐらいでした。)。もう一つはLightGBM単体の予測結果を採用しました。一方で、cvがもっと優れたモデルもいくつかありましたが、publicLBの値が悪化していたために採用を見送りました。
結果的には、今回説明したモデルが自分たちのモデルの中で最高スコアであったため、最終の選択が上手くいったことが順位に寄与したと考えています。(幸いにも最終日の前に選択していました。。)
2. まとめ
コンペを振り返って効果的だったことをまとめると、
- LightGBMにおいて、Target-Encodingを用いた。
- いくつかの追加した特徴量が効いた。
- CatBoostを用いた。
- スタッキングをした。
などではないかと思います。また、これに加えて、
- 闇雲に特徴量を加えたり、過度にハイパーパラメータをチューニングしたりしなかった。
- しっかりとクロスバリデーションを行った。
ことが、shakeに巻き込まれなかった要因ではないかと思います。
3. 最後に
第2回金融データ活用チャレンジコンペを開催していただきありがとうございました。
開催序盤、終盤に色々ありましたが、slackでの議論やブログを公開してくださったりした方々のおかげで、非常に多くのことを学ぶことができました。このような貴重な機会を提供していただけたことは非常に嬉しく、また第3回の開催を楽しみにしています。
Discussion