Trader-Company method
tradercompany method
trader-company methodを実装しました。
より論文実装に近い形で実装していますが、大枠は参考文献のqiita記事を参考にしました。
実装とチュートリアルはGitに載せました。
[https://github.com/yoshida-chem/tradercompany/edit/main/README.md]
参考文献
使い方
- ライブラリのインポート
import os
import random
import pickle
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import tradercompany
from tradercompany.activation_funcs import identity, ReLU, sign, tanh
from tradercompany.binary_operators import add, diff, get_x, get_y, multiple, x_is_greater_than_y
from tradercompany.trader import Trader
from tradercompany.company import Company
%matplotlib inline
SEED = 2021
def fix_all_seeds(seed):
np.random.seed(seed)
random.seed(seed)
os.environ['PYTHONHASHSEED'] = str(seed)
fix_all_seeds(SEED)
- データ準備
pandas.DataFrame形式の時系列df_y
を準備します。訓練用のデータと検証用に分割します。
def create_dataset(T, sigma_t):
def simulation(y_t, sigma):
y_t1 = np.zeros(2)
y_t1[0] = 1.0*tanh(y_t[0]) + 0.8*y_t[0]*y_t[1] + 1.0*y_t[1] - 1.0*ReLU(min(y_t[0], y_t[1])) + sigma*np.random.randn()
y_t1[1] = +0.6*sign(y_t[1]) + 0.5*y_t[0]*y_t[1] - 1.0*max(y_t[0], y_t[1]) + sigma*np.random.randn()
return y_t1
y = np.zeros((2, T))
y_without_noise = np.zeros((2, T))
y[:,0] = np.array([0.1, 0.1])
y_without_noise[:,0] = np.array([0.1, 0.1])
for t in range(1, T):
y[:,t] = simulation(y[:,t-1], sigma_t)
y_without_noise[:,t] = simulation(y[:,t-1], 0.0)
plt.plot(y[0], color = "#cc0000", label = "stock0")
plt.plot(y[1], color = "#083090", label = "stock1")
plt.plot(y_without_noise[0], color = "#cc0000", linestyle = "--", label = "stock0" + "(w/o noise)")
plt.plot(y_without_noise[1], color = "#083090", linestyle = "--", label = "stock1" + "(w/o noise)")
plt.xlabel("time", fontsize = 18)
plt.ylabel("y", fontsize = 18)
plt.xlim([T-100, T])
plt.legend()
plt.show()
plt.close()
return y, y_without_noise
sigma = 0.1
T_total = 500
y, y_without_noise = create_dataset(T_total, sigma)
T_train = 800
df_y_train = df_y.iloc[:T_train, :]
df_y_test = df_y.iloc[T_train:, :]
- trader-company methodのパラメータを指定する
activation_funcs = [identity, ReLU, sign, tanh]
binary_operators = [max, min, add, diff, multiple, get_x, get_y, x_is_greater_than_y]
stock_names = ["stock0", "stock1"]
time_window = 200
delay_time_max = 2
num_factors_max = 4
- モデルを構築する
model = Company(stock_names,
num_factors_max,
delay_time_max,
activation_funcs,
binary_operators,
num_traders=40,
Q=0.2,
time_window=time_window,
how_recruit="random")
- 学習する
model.fit(df_y_train)
- モデルの保存
with open("model.pkl", "wb") as f:
pickle.dump(model, f)
- 次の時刻の予測
# 時刻t+1の予測
model.aggregate()
8-1. 検証用データに対する予測(tuningなし)
with open("model.pkl", "rb") as f:
model = pickle.load(f)
errors_test_notuning = []
for i, row in df_y_test.iterrows():
prediction_test = model.aggregate()
errors_test_notuning.append(np.abs(row.values - prediction_test))
# tuning==Falseの場合、データが追加されても重みの更新などパラメータは変わらない
model.fit_new_data(row.to_dict(), tuning=False)
8-2. 検証用データに対する予測(tuningあり)
with open("model.pkl", "rb") as f:
model = pickle.load(f)
errors_test_tuning = []
for i, row in df_y_test.iterrows():
prediction_test = model.aggregate()
errors_test_tuning.append(np.abs(row.values - prediction_test))
# tuning==Trueの場合、データが追加された際に重みの更新などパラメータが調整される
model.fit_new_data(row.to_dict(), tuning=True)
- 精度比較
下限やベースラインに比べてもそれなりの精度で予測できていることがわかります。
また、オンライン学習をしたモデル(tuningあり)の方が僅かに精度が良いこともわかります。
days_ma = 5
# trader-company errors with no-tuning
errors_test_notuning = np.array(errors_test_notuning)
errors_test_notuning_ma = pd.DataFrame(errors_test_notuning).rolling(days_ma).mean()
# trader-company errors with tuning
errors_test_tuning = np.array(errors_test_tuning)
errors_test_tuning_ma = pd.DataFrame(errors_test_tuning).rolling(days_ma).mean()
# baseline method
errors_baseline = np.abs(y[:,T_train+1:] - y[:,T_train:-1])
errors_baseline_ma = pd.DataFrame(errors_baseline.T).rolling(days_ma).mean()
# lower bound
errors_lower_bound = np.abs(y[:,T_train+1:] - y_without_noise[:,T_train+1:])
errors_lower_bound_ma = pd.DataFrame(errors_lower_bound.T).rolling(days_ma).mean()
for i_stock, name in enumerate(stock_names):
print(name)
plt.plot(errors_test_notuning_ma[i_stock], label="trader-company_notuning")
plt.plot(errors_test_tuning_ma[i_stock], label="trader-company_tuning")
plt.plot(errors_baseline_ma[i_stock], label="baseline")
plt.plot(errors_lower_bound_ma[i_stock], label="lower-bound")
plt.xlabel("time")
plt.ylabel("mean average error")
plt.legend()
plt.show()
for i_stock, name in enumerate(stock_names):
print(name)
print("Trader-Company notunig", errors_test_notuning.T[i_stock].mean())
print("Trader-Company tuning", errors_test_tuning.T[i_stock].mean())
print("baseline", errors_baseline[i_stock].mean())
print("lower bound", errors_lower_bound[i_stock].mean())
[f:id:yamayou_1:20211120105831p:plain]
[f:id:yamayou_1:20211120105846p:plain]
- モデルの解釈
各トレーダーの中身を見ることで解釈性も高いのがこの手法の特徴です。
一番成績が良いTraderの中身を見てみましょう。
実際のDynamicsに近い式が含まれていることがわかります。
num_stock = len(stock_names)
best_trader_for = [[], []]
traders_ranking_0 = np.argsort([trader.cumulative_error[0] for trader in model.traders])
traders_ranking_1 = np.argsort([trader.cumulative_error[1] for trader in model.traders])
# best trader
print(stock_names[0])
print(model.traders[traders_ranking_0[0]].binary_operator[0])
print(model.traders[traders_ranking_0[0]].activation_func[1])
# best trader
print(stock_names[1])
print(model.traders[traders_ranking_1[0]].binary_operator[0])
print(model.traders[traders_ranking_1[0]].activation_func[1])
[f:id:yamayou_1:20211120110134p:plain]
最後に
今回はTrader-Company methodを実装し公開しました。
今回の記事ではRecruit時のパラメータの選択をランダムに初期化しているのですが、論文実装と同じGMMからサンプリングする方法も実装されています。
しかし、今回のデータセットで実施するとTraderに多様性がなくなってしまいました。同じ進化モデルの遺伝的アルゴリズムのように突然変異のような機構を取り入れる必要があるかもしれません。
追記
この実装で現在Kaggkeで行われている仮想通貨コンペに適用してみたのですが、データ量や変数の数が多い(100とか)とかなり速度が遅いです。
TraderのEducate部分が非常に時間がかかるので並列処理に変更する必要がありそうです。
以上です。
Discussion