💰

Nishika 金融コンペ 16位解法概要

2023/10/21に公開

コンペへのリンク

https://competition.nishika.com/competitions/finance_ts/summary

今回の記事は以下に記載の内容に少し追加しています。

https://competition.nishika.com/competitions/finance_ts/topics/645


🏆 どういうコンペ?

株関連の値予測コンペです。

株予測とのことで時系列のように思えますが、同一IDが隠されている上に、1万行ごとにシャッフルされているため、かなり時系列要素の薄いデータとなっています。


💾 どういうデータ?

特徴量、予測する値共に匿名化およびビン化されていました。

コンペの特性上、詳細なデータの公開は行いませんが、以下のようなデータ形式からEra(日本語にすると時代で時系列IDのこと)が無くなっているものと思っていただければOKです。

https://docs.numer.ai/numerai-tournament/data


👍 効いた処理

データ前処理 → 機械学習モデル → 評価 → ハイパラ の順で記載しています。


🛠️ データ前処理

  • 時系列?差分

相関の値を見ると、feature_c_xxxとfeature_d_xxx、およびfeature_b_xxxとfeature_d_xxx(xxxは同じ番号が入る場合)が比較的相関しています。

おそらく時系列のデータだと思われます。

そこで、feature_cおよびfeature_dの差分値やfeature_bとfeature_dの差分値をいくつか取得しました。

最低限のフィルタリングとしてcdとcbの符号が同じかという確認と、targetに対してある程度相関があるかを見ています。

cols_diff = []
for i in range(135):
    i_str = str(i).zfill(3)
    feature_cb_diff = train[f"feature_c_{i_str}"] - train[f"feature_b_{i_str}"]
    feature_cd_diff = train[f"feature_c_{i_str}"] - train[f"feature_d_{i_str}"]
    
    per_cd, pep_cd = st.pearsonr(feature_cd_diff, train["target"])
    per_cb, pep_cb = st.pearsonr(feature_cb_diff, train["target"])
    
    # 符号の向きが同じ
    if abs(per_cd) > 0.008 and per_cd * per_cb > 0:
        train[f"feature_cb_{i_str}_diff"] = feature_cb_diff
        train[f"feature_cd_{i_str}_diff"] = feature_cd_diff
        cols_diff.append(i_str)
  • カウント変数

1や5が多いということは銘柄に特徴的なことが起きている(ボラティリティが高いなど)と考え、絶対値のカウントを特徴量に入れました。

これはCVとPB共に上がったため、明確に効いていそうでした。Privateでどうかは見てないです。。

train_a_abs = (train[features_a] - 3).abs()
train_b_abs = (train[features_b] - 3).abs()
train_c_abs = (train[features_c] - 3).abs()
train_d_abs = (train[features_d] - 3).abs()
for val in [0, 1, 2]:
    train[f"feature_count_a{val}"] = (train_a_abs == val).sum(axis=1)
    train[f"feature_count_b{val}"] = (train_b_abs == val).sum(axis=1)
    train[f"feature_count_c{val}"] = (train_c_abs == val).sum(axis=1)
    train[f"feature_count_d{val}"] = (train_d_abs == val).sum(axis=1)
del train_a_abs, train_b_abs, train_c_abs, train_d_abs; gc.collect()

🤖 機械学習モデル

LightGBM(GBDT, 回帰;アンサンブル70%)+ AutoGluon(アンサンブル30%)です。

AutoGluonは以下のチューニングを全くやらないでアンサンブルの種になったので楽でした。でもValidationの切り方は人間が指定したほうがいいかな。。LightGBMは2つRandom seed averagingしています。


📊 評価

5folds(KFold, shuffleなし)です。RMSEを損失関数として与えていました。

トピックでRMSEとコサイン類似度が相関しないという話もありましたが、自分は各FoldごとにRMSEとコサイン類似度の相関を見てみたところ、基本的にはRMSEを信じてよいと判断しました。

(一部のFoldでは負の相関が怪しかったが、ほとんどのFoldでは負の相関を示した)

10,000行ごとに時系列になっている半時系列データであったため、ShuffleなしのK-Foldで切るのがCVとLBのコサイン類似度が近くなったのでShuffleなしのKFoldを採用しています。


⚙️ ハイパーパラメーター

基本的には、一部の特徴量に依存しない(低い feature_fractionを利用する)ということを方針としています。

https://www.docswell.com/s/8980249862/KVVV3D-2023-04-08-011124#p14

また、実際は最初の方にLightGBMTunerでチューニングしました。

https://zenn.dev/nishimoto/articles/8d575924cc619d

便利なんだけど、feature fractionが0.4までしか下がらないのが玉に瑕。

あと、Learning rateは下げれば下げるほどコサイン類似度があがったので、最終的には0.0005まで下げました。学習大変だった。。

Learning rateを下げた際に学習時間が膨大になったことからfeature fractionを最後に下げたので、LightGBMTunerに頼りきる必要はなかったと思います。


🧯 効かなかったこと

  • 次元圧縮系の特徴量

    • testデータも入れてUMAPするとtestデータのスコア爆上がりするのですが、規約的に使えないので最終Subには含めず
    • trainデータのみでUMAPすると成績あがらず
    • UMAP, NMF, PCAあたりを試した
  • AutoGluon以外のAutoML

  • feature_xの集約量

    • feature_a_xxxの平均値など
  • target encoding

  • コサイン類似度を直接損失関数にする

  • sample_weightを変える

  • ID(train.index // 10000

    • これCVとPublic LB共に上がるんですよね。。謎でしたね。。
    • IDは使用したものと非使用のものを提出し、非使用のほうが最終スコアはよかったです。
  • 後処理

    • いくつか提出したところ不安定だと感じたので、極一部しか後処理してません

感想とつぶやき

Discussion