Nishika 金融コンペ 16位解法概要
コンペへのリンク
今回の記事は以下に記載の内容に少し追加しています。
🏆 どういうコンペ?
株関連の値予測コンペです。
株予測とのことで時系列のように思えますが、同一IDが隠されている上に、1万行ごとにシャッフルされているため、かなり時系列要素の薄いデータとなっています。
💾 どういうデータ?
特徴量、予測する値共に匿名化およびビン化されていました。
コンペの特性上、詳細なデータの公開は行いませんが、以下のようなデータ形式からEra(日本語にすると時代で時系列IDのこと)が無くなっているものと思っていただければOKです。
👍 効いた処理
データ前処理 → 機械学習モデル → 評価 → ハイパラ の順で記載しています。
🛠️ データ前処理
- 時系列?差分
相関の値を見ると、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
を利用する)ということを方針としています。
また、実際は最初の方にLightGBMTunerでチューニングしました。
便利なんだけど、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は使用したものと非使用のものを提出し、非使用のほうが最終スコアはよかったです。
-
後処理
- いくつか提出したところ不安定だと感じたので、極一部しか後処理してません
感想とつぶやき
- 評価指標設計の難しさ
- Shakeが大きく、あんまり良くないコンペになってしまった気がします。
- コンペ的には、評価指標はコサイン類似度でもいいとは思うものの、コサイン類似度のSharpeにする方がよかった気がしますね。
- 金融コンペの難しさは以下などで語られています
Discussion