Numerai Tournamentの概要とどう取り組むかの種

34.3K Views

December 31, 21

スライド概要

NumeraiMeetupJapan2021の発表資料です。
notebookには https://github.com/currypurin/numerai/blob/main/numerai_meetup_japan_2021/README.md からリンクを貼っていますので、データに触ってみたい人はぜひ。

profile-image

『面倒なことはChatGPTにやらせよう』が1/29に発売になりました。KaggleGrandMaster。 要望や質問などなんでも:http://marshmallow-qa.com/currypurin まで。 KaggleスタートブックとKaggleのチュートリアル第6版を執筆しました。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

Numerai Tournamentの概要と どう取り組むかの種 Numer i Meetup JAPAN2021 a 2021/12/18

2.

自己紹介 カレーちゃんというアカウントで twitterやっています numeraiは11月からはじめました Kaggleが好き 2

3.

話すこと 1. Numerai Tournamentの概要 - 神資料を参照しつつ、簡単なまとめ 2. 新データの概要( Super Massive Data) 3. どう取り組むか考える • validation • objective • custom metics - validationのcorrでearly stopping - LihgtGBM、XGBoost、Catboost • ターゲット、特徴 3

4.

1 .Numerai Tournamentの概要

5.

神まとめありがとうございます Numerai って何?? Numerai をはじめるにあたり、次の3つの資料にとてもお世話になりまし • Numeraiの日本語版DOCS - 日本語でめちゃくちゃ詳細に書かれている • Numeraiはいいぞ(2021.5.25) - 圧倒的にわかりやすく、網羅されている • 機械学習による株価予測 はじめようNumerai - numeraiの変化も時系列で書かれている 後からの参加者も、当時の変化を知ることができる 5

6.

Numerai って何?? Numeraiとは • 2015年に設立されたNumeraiは、毎週データサイエンスのトーナメントを開催して います。世界中のデータサイエンティストが、難読化されたデータを無料でダウン ロードし、株式市場の予測モデルを構築することでトーナメントに参加できます。 ダウンロードできるデータは難読化されたいるため、金融の知識がなくても参加す ることができます。 • 参加者は、自分の予測に賭け金をかけることでトーナメントに参加できます。良い 予測を提出すると、より多くの仮想通貨(NMR)を得ることができ、悪い予測を提 出すると、NMRを没収されます。 Numeraiの日本語版DOCS 新規参加者向けのTipsより 6

7.

Numerai って何?? Numeraiには2種類の種目がある • トーナメント (Tournament) - ターゲットと特徴が与えられるので、ターゲットを予想し提出 • シグナルズ (Signals) - 株式市場の騰落を予測する 7

8.

トーナメントのデータ トーナメントには旧データと新データがある • 旧データ - ターゲット: 1列 - 特徴: 310列 + id列 + era列 + data̲type列 • 新データ (Super Massive Data) - ターゲット: 21列 (1列は重複) - 特徴: 1050列 + id列(index) + era列 + data̲type列 8

9.

旧データ全般 id • 重複なし era • 時期を表す data̲type • train: 訓練データ • validation: 参加者のための検証データ • test: Numeraiのための検証データ • live: テストデータ feature̲{group}{i} • 6つの集合からなる310の特徴 • {0, 0.25, 0.5, 0.75, 1} target • {0, 0.25, 0.5, 0.75, 1} Numerai はいいぞ を参考に作成 9

10.

旧データ data_type, era Train : era1 ~ era120 Val1 : era121 ~ era132 Test1: era575 ~ era852 Val2: era197 ~ 212 Test2: 927 ~ Live: 1000 2020年4月にval2が追加されている Test2、Liveが毎週更新される。 Validation 2 Announcement より引用 10

11.

サンプルコードを動かせばすぐにsubmitできました ベースライン 旧データ:ベースライン • 公式example - xgboost • Numerai tournamentベースライン - KATSU1110さんのベースライン。とてもわかりやすい。日本語。 - LightGBM • How to get started with Numerai - CARLO LEPELAARS さんのベースライン。特徴作成やパラメータサーチも。 - LightGBM 11

12.

Numerai tournament - validationの評価 旧データをsubmitすると、左の情報がメールで送られてくる Numeraiの日本語版DOCS 新規参加者向けのTipsより 12

13.

ステーク&ペイアウト サブミットしたモデルに、NMRをステーキング(=預け入れ)できる 預け入れたNMRは、サブミットしたモデルのCorrとMMCの値に応じてペイアウトされ る。 • payout = stake̲value * payout̲factor * (corr * corr̲multiplier + mmc * mmc̲multipler) 現在選択できる、corr_multiplierとmmc_multiplier: Numerai Tournament Overviewより • payout̲factorは、全体のstake量に応じて変動 Numerai Tournament Overviewより 13

14.

Corr corr • スピアマンの順位相関係数 • targetを上手く予想できると良い, 0.03程度出せると上位 14

15.

mmc(Meta Model Contribution) mmc(Meta Model Contribution) • メタモデルへの貢献度 • 他の人と異なる独自のモデルを作れば高いMMCを得ることができる • メタモデルへの貢献(MMC)について、MMC2 Announcement 、Numerai はいいぞ / An encouragement of Numerai、機械学習による株価予測 Qiita などがとても詳しい はじめようNumerai - 15

16.

評価期間 4週間(20日間)のスコアの加重平均 でpayoutが決まる 毎週新しいsubmitをし、並行して stakeできる https://docs.numer.ai/tournament/learn より 16

17.

2. 新データの概要 ( Super Massive Data)

18.

新データ (Super Massive Data) Super Massive Data Release: Deep Dive • リリース時のフォーラム • eraは完全にweeklyになった - era̲nとera̲{n+4}は重複なし - era̲nからera̲{n+3}は重複あり • era - train: 0001~0574 - tournament̲data: 0575~0852 - live: X - valid: 0857~0961 18

19.

新データ (Super Massive Data) • target21列 • 「target」と「target̲nomi̲20」は完全に同一で、予想すべき target • 20日間と60日間の10種類のtarget • targetの分布 19

20.
[beta]
私が使っている関数

APIでのデータダウンロード等
!pip install numerapi
napi = numerapi.NumerAPI(verbosity="info")
current_round = napi.get_current_round() # 現在のround
def get_massive_data(mode: str, round: int):
"""
mode: training, validation, tournament
round: ラウンド数
"""
parquet_path = Path(f'{mode}_data_int8_{current_round}.parquet')
if not parquet_path.is_file():
napi.download_dataset(filename=f"numerai_{mode}_data_int8.parquet",
dest_path=str(parquet_path),
round_num=round)
df = pd.read_parquet(f'{mode}_data_int8_{current_round}.parquet')
return df
train = get_massive_data('training', current_round)
valid = get_massive_data('validation', current_round)
test = get_massive_data('tournament', current_round

napi.download_dataset('features.json', 'features.json')
import json
with open("features.json", "r") as f:
feature_metadata = json.load(f)

)


20

21.
[beta]
APIでvalidの提出
!pip install numerapi
public_id = 'xxx'
public_id, secret_keyは、Account>SettingsのCreate API Keyから作成
secret_key = ‘xxx'
model_id = ‘xxx'

model_idは、https://numer.ai/models のModelsのMoreから取得、
napi.get_models() でも一覧を取得可能

# validの書き出し、必要なのはindexとprediction
valid_diagnostics_df = valid.index.to_frame().reset_index(drop=True)
valid_diagnostics_df["prediction"] = valid_prediction
valid_diagnostics_df.to_csv('valid_diagnostics_df.csv', index=False)
napi.upload_diagnostics('valid_diagnostics_df.csv', model_id=model_id)
napi = numerapi.NumerAPI(public_id=public_id, secret_key=secret_key)
napi.upload_diagnostics('valid_diagnostics_df.csv', model_id=model_id)

提出すると右図のVALIDATION DIAGNOSTICSが、
https://numer.ai/tournament で閲覧可能となる
21

22.
[beta]
APIでのサブミット
!pip install numerapi
public_id = 'xxx'
public_id, secret_keyは、Account>SettingsのCreate API Keyから作成
secret_key = ‘xxx'
model_id = ‘xxx'

model_idは、https://numer.ai/models のModelsのMoreから取得、
napi.get_models() でも一覧を取得可能

# testの書き出し
predictions_df = test.index.to_frame().reset_index(drop=True)
predictions_df["prediction"] = predictions
predictions_df.to_csv("predictions.csv", index=False)
napi = numerapi.NumerAPI(public_id=public_id, secret_key=secret_key)
submit_file_path = "predictions.csv"
submission_id = napi.upload_predictions(submit_file_path, model_id=model_id, version=2)
新データはversion=2, 旧データはversion=1

22

23.

まずはサブミットしてみると、色々わかると思います ベースライン 新データ:ベースライン • 公式example - Lightgbm • [Numerai] NN baseline with new massive data | Kaggle - KATSU1110さんがのベースライン - Keras 23

24.

3. どう取り組むか考える 1.validation 2.objective 3.Custom meticでvalidationのcorrを最大化 • LihgtGBM、XGBoost、Catboost 4.ターゲット、特徴

25.

validationをどのようにするか 与えられた、trainを学習データにして、validationデータをvalidationにするように思え るが、このデータの区分は特殊でそれが正解とも限らない • ただ、初手として取り組むには十分だし、これで十分に上位も狙えそう validationデータは、testに期間が近いため、学習データに使えると精度が向上する可 能性は高い trainとvalidationをconcatして、交差検証する方法が、kaggleなどのコンペに慣れてい ると普通に思えるが、eraに重複があるので注意が必要 旧データではあるが、Numeraiのモデル紹介(旧Dataset)はとても参考になる 25

26.

何をObjectiveに学習するか LightGBMで使えるobjectiveの表現で 考える • MSE(regression) 一般的 • multiclass(softmax) multiclassは多少重いが、5値分類と考えればかなりあり。 argmaxを取ると予測が5つの値になるので、加重平均をとる • xentropy targetを0~1にできる場合、xentropyはよく使われて精度も良い傾向にあ る • その他の可能性 - MAE、huber、fair 、Sorting and Ranking … 👉 今回のデータでは、上の太字の3つは精度がそんなに変わらない印象。Sorting nd R nkingはまだ試せていない a a 26

27.

何をmetricにするか(early stoppingの指標) payout = stake̲value * payout̲factor * (corr * corr̲multiplier + mmc * mmc̲multipler) validationのcorrを最大化するようにearly stoppingするのが、取り組みやすそう (mmc直ちに算出できないので、まずはcorrを最大化するのが良さそう) 次のページから、corrでearly stoppingするcustom metricsの使い方の説明 - LightGBM - XGboost - CatBoost 27

28.
[beta]
custom metricsの使い方

Custom metrics(LightGBM)その1
def compute_corr_for_lgb(preds: np.ndarray, target: lgb.Dataset):
# validの全体のcorrを返すmetric
# https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.train.html#lightgbm.train
preds_rank = pd.Series(preds).rank(pct=True, method="first")
corr = np.corrcoef(preds_rank, target.get_label())[0, 1]
return 'corr', corr, True
eval_name, eval_result, is_higher_betterの順に返す
params = {'objective': 'regression',
'metric': 'None'}

‘None’が必須, ないとobjectiveとmetricの両方でearly stoppingがかかる
参考:小ネタ:LightGBM の objective を metric から消し去る

d_train = lgb.Dataset(tr_x, tr_y)
d_valid = lgb.Dataset(va_x, va_y)
evals_result = {}
model = lgb.train(params,
d_train,
num_boost_round=10000,
valid_sets=d_valid,

[d_train, d_valid]にしても良いが、d_validで十分なことが多い。数を増
やすと重くなる

# 最大round数

trainとvalidの学習曲線を描きたい場合は、学習後に推論し直す方法も

# 検証データ

feval=compute_corr_for_lgb, # custom_metric
early_stopping_rounds=50,
verbose_eval=30, # 何roundおきに表示するか

fevalを複数にしたい場合は次のページが参考になる

)

・Python: LightGBM でカスタムメトリックを扱う

・小ネタ:LightGBM の objective を metric から消し去る

28

29.

学習後の推論とev ls_result Custom metrics(LightGBM)その2 # 学習後の推論 for itr in range(num_iteration=model.best_iteration + early_stopping_rounds): tr_preds = model.predict(tr_x, num_iteration=itr) va_preds = model.predict(va_x, num_iteration=itr) # ここでtr_preds, va_preds, targetで確認したい指標を計算して # リストにappendしたりする # valid_sets=[d_train, d_valid]にしておけば、trainとvalidのf_evalのitrとcorrの関係は確認可能 # リストにappendしたりする plt.plot(evals_result['training']['corr'], label='train_corr') plt.plot(evals_result['valid_0']['corr'], label='valid_corr') plt.legend() a 29

30.
[beta]
er 毎のcorrの平均でe rly stopping

Custom metrics(LightGBM)その3
ここまでのは、全体のcorrだったので、era毎のcorrの平均を計算するようにする
!pip install numpy-indexe
import numpy_indexed as npi

numpy_indexedというライブラリを使うと、簡単にかける

class MetricCorr:
def __init__(self, era_group: pd.Series):
self.group = npi.group_by(era_group.values)
def metric_corr_mean_lgb(self, preds: np.ndarray, data: lgb.Dataset): group毎に分割して、そのリストを返すメソッド
labels = self.group.split(data.get_label())
preds_rank = [pd.Series(p).rank(pct=True, method="first").values for p in self.group.split(preds)]
corr_mean = np.mean([np.corrcoef(preds, rank)[0, 1] for preds, rank in zip(preds_rank, labels)])
return 'corr_mean', corr_mean, True
d_train = lgb.Dataset(tr_x, tr_y)
d_valid = lgb.Dataset(va_x, va_y)
evals_result = {}
me_corr = MetricCorr(valid['era'])
model = lgb.train(params,
d_train,
num_boost_round=10000,
valid_sets=d_valid,

出力の例

# 最大round数

# 検証データ

feval=me_corr.compute_corr_mean_lgb, # custom_metric
early_stopping_rounds=50,
verbose_eval=30 # 何roundおきに表示するか

d


a

a

)

30

31.
[beta]
Custom metrics(Xgboost)その1
Xgboostは、LightGBMに近い書き方
class MetricCorr:
def __init__(self, era_group: pd.Series):
self.group = npi.group_by(era_group.values)
def metric_corr_mean_xgb(self, preds: np.ndarray, data: xgb.DMatrix):
# https://xgboost.readthedocs.io/en/latest/tutorials/custom_metric_obj.html#customized-metric-function
labels = self.group.split(data.get_label())
preds_rank = [pd.Series(p).rank(pct=True, method="first").values for p in self.group.split(preds)]
corr_mean = np.mean([np.corrcoef(preds, rank)[0, 1] for preds, rank in zip(preds_rank, labels)])
return 'corr_mean', corr_mean
d_train = xgb.DMatrix(tr_x, tr_y)
d_valid = xgb.DMatrix(va_x, va_y)
watchlist = [(d_valid, 'valid')]
evals_result = {}

eval_name, eval_resultの順に返す

me_corr = MetricCorr(valid['era'])
watchlist = [(d_valid, 'valid')]
model = xgb.train(params,
d_train,
num_boost_round=num_boost_round
evals=watchlist,
feval=me_corr.metric_corr_mean_for_xgb,
maximize=True, # fevalを最大化

最大化したいのか、最小化したいのか選択

early_stopping_rounds=early_stopping_rounds,
evals_result=evals_result,
verbose_eval=50
)

31

32.
[beta]
Custom metrics(Catboost)その1
Catboostは、LightGBM・XGboostとは違う書き方、CPUを使う場合
from catboost import CatBoost, Pool
class CorrMetricCatboost:
"""https://catboost.ai/en/docs/concepts/python-usages-examples#rmse1"""
def get_final_error(self, error, weight):
return error
def is_max_optimal(self):
最大化の場合はTrue
return True
def evaluate(self, approxes, target, weight):
assert len(approxes) == 1
assert len(target) == len(approxes[0])
approx = approxes[0]
output_weight = 1 # weight is not used
corr = np.corrcoef(approx, target)[0, 1]
return corr, output_weight

metric, weightの順に返す。get_ inal_errorでweightを使わないなら、weightは使用されない

train_pool = Pool(tr_x, tr_y)
valid_pool = Pool(va_x, va_y)

パラメータに、custom metricsを返すclassをセットする

params = {'loss_function': 'RMSE',
'iterations': 100,
'eval_metric':CorrMetricCatboost(),
# 'task_type': 'GPU'
CustomMetricsを使うとGPUを使えない
}
model = CatBoost(params)
https://github.com/catboost/catboost/issues/736
model.fit(train_pool,
eval_set=[valid_pool],
early_stopping_rounds=20,
verbose_eval=30)

f

32

33.
[beta]
Custom metrics(Catboost)その2
GPUを使う場合、trainのみで学習した後に、後からvalidを計算する必要がある?
from catboost import CatBoost, Pool
train_pool = Pool(tr_x, tr_y)
params = {'loss_function': 'RMSE',
'iterations': num_iterations,
'task_type': 'GPU'}
model = CatBoost(params)
model.fit(train_pool)

metrics, eval_setを設定せずにまずは学習する
num_iterationsの値は重要

class MetricCorr:
def __init__(self, era_group: pd.Series):
self.group = npi.group_by(era_group.values)
def compute_corr_mean(self, preds, target):
# era毎のcorrの平均を返す
labels = self.group.split(target)
preds_rank = [pd.Series(p).rank(pct=True, method="first").values for p in self.group.split(preds)]
corr_list = [np.corrcoef(preds, rank)[0, 1] for preds, rank in zip(preds_rank, labels)]
return np.mean(corr_list)
mean_corrs = []
me_corr = MetricCorr(valid['era'])
for valid_pred in model.staged_predict(valid_pool, ntree_end=num_iterations):
mean_corr = me_corr.compute_corr_mean(valid_pred, va_y])
mean_corrs.append(mean_corr)

model.staged_predictは、 itに比べてかなり
遅い。

f

33

34.

target まずはtargetのみで十分だが、他のtargetも使うとスコアが改善しそう • 欠損のないtargetを使ってすぐにアンサンブルや multi task学習はできる • 329の欠損も除いてしまえばʼ̲20ʼのtargetは簡単に使える • ʻ̲60ʼのtargetは欠損が多いので使うのに工夫が必要 34

35.

特徴 一番工夫が必要そう 1050列もある。データが重たい。 次の記事が参考になりました • 20 models ensemble diagnostics result • Numeraiのtraining dataの周期構造について • Numeraiのモデル紹介(旧Dataset) 35