はじめに
Keras+Tensorflowを用いてLSTMでFXの予想してみる その2(GPUで計算してみる)の最後にようやくスタートを切れると書きました。その理由は、ディープラーニングやFXで使用されるパラメータは少なくないですが、その中で重要もしくは正しい値を見つけるのも少なくない時間がかかると考えたためです。
そうですGPUを活用しなければ見つけることもできないのです。
ということで今回はGPUを使用できるようになったので念願の各種パラメーターを総当たりしていい値を探ってみます。
ソース
ソースはhttps://github.com/rakichiki/keras_fxにあります。
もしくはgit cloneしてください。
git clone https://github.com/rakichiki/keras_fx.git
今回のソースはkeras_fx_gpu_multi.ipynbになります。これを取得してjupyterにアップロードしてください。
それでは少し解説を。
パラメータ総当たり
まず変更したいパラメータを決めました。以下にします。(よくよく見るとすべてではないな...)
- 振り返る日数
- 売買してから判定するまでに日数
- 売買判定するために増減するの割合
- 活性関数の種類
- 目的関数の種類
- 最適化の種類
- trainデータのうち学習用データの割合
- 通貨ペア
l_of_s_list = [20,25]
n_next_list = [5,7]
check_treshhold_list = [0.50,0.60]
#activation_list = ['sigmoid','tanh','linear']
activation_list = ['tanh']
#loss_func_list = ['mean_squared_error','mean_absolute_error','mean_squared_logarithmic_error']
loss_func_list = ['mean_squared_error','mean_absolute_error']
#optimizer_func_list = ['sgd','adadelta','adam','adamax']
optimizer_func_list = ['adadelta','adam','adamax']
#validation_split_number_list = [0.1,0.05]
validation_split_number_list = [0.05]
currency_pair_list = ['usdjpy']
# 結果ファイルの格納
if os.path.exists('result') == False:
os.mkdir('result')
if os.path.exists('png') == False:
os.mkdir('png')
save_file_name = 'result/result_' + dt.today().strftime("%Y%m%d%H%M%S") + '.txt'
save_file_name = dt.today().strftime("%Y%m%d%H%M%S")
# fxのデータ取得
start_day = "20010101"
end_day = dt.today().strftime("%Y%m%d")
for currency_pair in currency_pair_list:
(train_start_count, train_end_count,test_start_count, test_end_count,data) = \
get_date(start_day, end_day, currency_pair)
file_name = currency_pair + '_d.csv'
for l_of_s in l_of_s_list:
for n_next in n_next_list:
for check_treshhold in check_treshhold_list:
#
(chane_data,average_value,diff_value, up_down,check_percent) = \
get_data(l_of_s, n_next,check_treshhold, file_name,train_start_count,\
train_end_count,test_start_count, test_end_count,data)
#
for activation in activation_list:
for loss_func in loss_func_list:
for optimizer_func in optimizer_func_list:
for validation_split_number in validation_split_number_list:
print('--------------------------')
fit_starttime = time.time()
fit(l_of_s, n_next,check_treshhold,file_name,save_file_name,activation,loss_func,optimizer_func,\
validation_split_number,train_start_count, train_end_count,test_start_count, test_end_count,\
chane_data,average_value,diff_value,up_down,check_percent)
print(str(math.floor(time.time() - fit_starttime)) + "s")
print('')
これらを予想の範囲を総当たりしたいと言いたいところですが、時間が指数関数的に増えてしまうのである程度絞って少しずつ調べるのがいいでしょう。まぁ、GPUを使用できてもせいぜい10倍の高速化で計算量が1000倍になったら、元の木阿弥ですね。(けどCPUで計算できる範囲じゃなくなってきていますが)
また後述する問題により最初からぶんまわすのではな少しずつ行きましょう(最初に大量にぶんまわして失敗した人は私です)。
EarlyStoppingの導入
パラメータ総当たりで計算量が爆発してしまいましたが、GPUの導入だけの改善では足りません。そこでEalryStoppingの導入して、無駄にepochs分ループさせないようにします。
early_stopping = EarlyStopping(monitor='val_loss', patience=10, verbose=1)
~~
high_history = high_model.fit(X_high_train, y_high_train, batch_size=100, epochs=300, \
validation_split=validation_split_number, callbacks=[early_stopping])
この辺りはKeras楽だなと思うのです。けど、EarlyStoppingするときの条件はこれでいいのかは不明です。
学習曲線見たい
当然ですが、学習曲線を見ないとそのパラメータが正しいのか判断はできません。それの導入もそれほど難しくありません。
fitの返り値を保持し、それをグラフにするだけです。
# 学習
high_history = high_model.fit(X_high_train, y_high_train, batch_size=100, epochs=300, \
validation_split=validation_split_number, callbacks=[early_stopping])
~~~~
# high
val_loss = high_history.history['val_loss']
plt.rc('font',family='serif')
fig = plt.figure()
plt.plot(range(len(high_history.history['val_loss'])), val_loss, label='val_loss', color='black')
plt.xlabel('epochs')
plt.savefig('png/' + save_file_name + '_high_' + \
str(l_of_s) + '_' + str(n_next) + \
'_' + str(check_treshhold) + '_' + file_name + \
'_' + activation + '_' + loss_func + \
'_' + optimizer_func + '_' + str(validation_split_number) + \
'.png')
plt.show()
注意点としてグラフを残そうとした場合にplt.savefigの後にplt.show()を行いましょう。理由は不明ですが、逆だと残りません(どこかの質問コーナーで回答を参考にさせていただきました)。
いいときは以下のようにval_lossが遷移したグラフが表示されます。
まぁ、これが綺麗だからと言って的中率がいいかはまた別問題なんですけどね。けど学習可否はこのグラフで見れると思います。
結果をファイル保存
非常に時間がかかることが想定されますが、途中でPCが落ちてしまう可能性があります。私は
、ECCのメモリを搭載していないPCに10時間オーバーの作業させて、且つ途中で落ちないことを祈り続けたいタイプでありません。
ということで解析結果をファイルに保存して途中でPCが落ちてもいい対応をしておきます(ストレージの故障の場合はあきらめですが)。
f = open(save_file_name, 'a')
f.write('l_of_s: ' + str(l_of_s) + ' n_next: ' + str(n_next) + \
' check_treshhold:' + str(check_treshhold) + ' file_name:' + file_name + \
' activation:' + activation + ' loss_func:' + loss_func + \
' optimizer_func:' + optimizer_func + ' validation_split_number:' + str(validation_split_number) + \
'\n')
f.write('UP: ' + str(up_ok_count) + ' - ' + str(up_ng_count) + ' - ' + str(up_ev_count) + '\n')
f.write('DN: ' + str(down_ok_count) + ' - ' + str(down_ng_count) + ' - ' + str(down_ev_count) + '\n')
f.close()
csv形式のほうがよかったかな?いや、JSON形式(私はJSON形式が好き)のほうがよかったかな?と思いながらもとりあえず途中経過を出力することにします。あっ、途中で失敗することを考慮に入れるとJSON形式は駄目ですね。
上記に書いたようにグラフも保存しておくといいことがあるかもしれません。
結果(良いとは言っていない...)
とりあえずぶん回しました。ただし、後述する理由により少し穏やかに(活性化関数とか一パターンしかねーじゃないか言わないで)といったところにです。
通貨ペアはusdjpyのみです。結果は以下です(売買判定の日数オーバーは的中率に入れません)。
売買判定用日数 | 売買後の日数 | 売買判定用変更割合 | 活性化関数 | 目的関数 | 最適化アルゴリズム | 学習データの割合(%) | 上がった場合の的中数 | 上がった場合の外れ数 | 下がった場合の的中数 | 下がった場合の外れ数 | 合計的中率(%) |
---|---|---|---|---|---|---|---|---|---|---|---|
20 | 5 | 0.5 | tanh | mse | adadelta | 0.05 | 55 | 34 | 114 | 81 | 59.5 |
20 | 5 | 0.5 | tanh | mse | adam | 0.05 | 24 | 22 | 66 | 46 | 57.0 |
20 | 5 | 0.5 | tanh | mse | adamax | 0.05 | 14 | 14 | 46 | 33 | 56.1 |
20 | 5 | 0.5 | tanh | mae | adadelta | 0.05 | 69 | 58 | 95 | 88 | 52.9 |
20 | 5 | 0.5 | tanh | mae | adam | 0.05 | 31 | 28 | 69 | 58 | 53.8 |
20 | 5 | 0.5 | tanh | mae | adamax | 0.05 | 29 | 26 | 84 | 69 | 54.3 |
20 | 5 | 0.6 | tanh | mse | adadelta | 0.05 | 72 | 53 | 129 | 98 | 57.1 |
20 | 5 | 0.6 | tanh | mse | adam | 0.05 | 64 | 52 | 111 | 97 | 54.0 |
20 | 5 | 0.6 | tanh | mse | adamax | 0.05 | 43 | 33 | 59 | 52 | 54.5 |
20 | 5 | 0.6 | tanh | mae | adadelta | 0.05 | 51 | 40 | 140 | 120 | 54.4 |
20 | 5 | 0.6 | tanh | mae | adam | 0.05 | 75 | 57 | 102 | 75 | 57.3 |
20 | 5 | 0.6 | tanh | mae | adamax | 0.05 | 45 | 39 | 107 | 93 | 53.5 |
20 | 7 | 0.5 | tanh | mse | adadelta | 0.05 | 11 | 12 | 84 | 81 | 50.5 |
20 | 7 | 0.5 | tanh | mse | adam | 0.05 | 7 | 5 | 45 | 35 | 56.5 |
20 | 7 | 0.5 | tanh | mse | adamax | 0.05 | 22 | 18 | 61 | 40 | 58.9 |
20 | 7 | 0.5 | tanh | mae | adadelta | 0.05 | 46 | 37 | 92 | 81 | 53.9 |
20 | 7 | 0.5 | tanh | mae | adam | 0.05 | 25 | 28 | 47 | 31 | 55.0 |
20 | 7 | 0.5 | tanh | mae | adamax | 0.05 | 20 | 28 | 75 | 62 | 51.4 |
20 | 7 | 0.6 | tanh | mse | adadelta | 0.05 | 23 | 16 | 39 | 39 | 53.0 |
20 | 7 | 0.6 | tanh | mse | adam | 0.05 | 24 | 21 | 77 | 67 | 53.4 |
20 | 7 | 0.6 | tanh | mse | adamax | 0.05 | 27 | 26 | 61 | 45 | 55.3 |
20 | 7 | 0.6 | tanh | mae | adadelta | 0.05 | 56 | 43 | 120 | 107 | 54.0 |
20 | 7 | 0.6 | tanh | mae | adam | 0.05 | 40 | 36 | 65 | 58 | 52.8 |
20 | 7 | 0.6 | tanh | mae | adamax | 0.05 | 49 | 41 | 60 | 54 | 53.4 |
25 | 5 | 0.5 | tanh | mse | adadelta | 0.05 | 54 | 32 | 86 | 60 | 60.3 |
25 | 5 | 0.5 | tanh | mse | adam | 0.05 | 25 | 21 | 59 | 41 | 57.5 |
25 | 5 | 0.5 | tanh | mse | adamax | 0.05 | 15 | 14 | 53 | 39 | 56.2 |
25 | 5 | 0.5 | tanh | mae | adadelta | 0.05 | 46 | 37 | 126 | 95 | 56.6 |
25 | 5 | 0.5 | tanh | mae | adam | 0.05 | 34 | 30 | 56 | 41 | 55.9 |
25 | 5 | 0.5 | tanh | mae | adamax | 0.05 | 25 | 24 | 69 | 47 | 57.0 |
25 | 5 | 0.6 | tanh | mse | adadelta | 0.05 | 23 | 21 | 108 | 94 | 53.3 |
25 | 5 | 0.6 | tanh | mse | adam | 0.05 | 19 | 20 | 58 | 51 | 52.0 |
25 | 5 | 0.6 | tanh | mse | adamax | 0.05 | 18 | 19 | 86 | 69 | 54.2 |
25 | 5 | 0.6 | tanh | mae | adadelta | 0.05 | 92 | 80 | 92 | 85 | 52.7 |
25 | 5 | 0.6 | tanh | mae | adam | 0.05 | 26 | 28 | 117 | 100 | 52.8 |
25 | 5 | 0.6 | tanh | mae | adamax | 0.05 | 32 | 31 | 126 | 102 | 54.3 |
25 | 7 | 0.5 | tanh | mse | adadelta | 0.05 | 32 | 18 | 110 | 95 | 55.7 |
25 | 7 | 0.5 | tanh | mse | adam | 0.05 | 16 | 16 | 37 | 19 | 60.2 |
25 | 7 | 0.5 | tanh | mse | adamax | 0.05 | 9 | 10 | 42 | 28 | 57.3 |
25 | 7 | 0.5 | tanh | mae | adadelta | 0.05 | 33 | 23 | 40 | 30 | 57.9 |
25 | 7 | 0.5 | tanh | mae | adam | 0.05 | 25 | 21 | 71 | 55 | 55.8 |
25 | 7 | 0.5 | tanh | mae | adamax | 0.05 | 36 | 29 | 55 | 38 | 57.6 |
25 | 7 | 0.6 | tanh | mse | adadelta | 0.05 | 43 | 35 | 104 | 92 | 53.6 |
25 | 7 | 0.6 | tanh | mse | adam | 0.05 | 23 | 23 | 63 | 58 | 51.5 |
25 | 7 | 0.6 | tanh | mse | adamax | 0.05 | 25 | 22 | 90 | 70 | 55.6 |
25 | 7 | 0.6 | tanh | mae | adadelta | 0.05 | 37 | 25 | 118 | 108 | 53.8 |
25 | 7 | 0.6 | tanh | mae | adam | 0.05 | 33 | 25 | 76 | 63 | 55.3 |
25 | 7 | 0.6 | tanh | mae | adamax | 0.05 | 40 | 25 | 74 | 59 | 57.6 |
良くて60%、悪くて50%で平均55%とさいころより少しマシな結果になりました。ついでに48パターンを計算するのに約2時間ぐらいかかりました(Geforce GTX 1070で)。また、パラメータを増やすと指数関数的に時間がかかることが予想されます。このため、どこかで高速化のため対策が必要でしょうし、的中率がショボいので対策が必要ですが、その前に、大きな問題が見つかりました。
問題発生
目的であるパラメータ探しはある程度できたのですが、残念な問題が見つかりました。
それはメモリを大量に消費してしまう問題です。当初1000パターン前後で探していたのですが、途中でものすごく遅くなる事象が発生しました。その対策を行った後に48パターンと少なめでやった結果のPCの状態は以下になります。
なんとPC本体のメモリが12GB消費し、GPUが2GBも消費しています。1パターン実行時は掲載していませんが、GPUは1GB以下、本体は4GB以下しか消費していませんでした。
まぁ、ありていに言えばメモリリークしているわけです。当初このPCは8GBしかメモリを搭載していませんでしたが急遽メモリを換装(筐体がMini-ITXでメモリスロットが2つしかなく)32GBに増量しました(ここで16GBでよかったんじゃないかという意見もありますが、中途半端な投資はだいたいいい結果にならないため一気に32GBにしました)。
どうしてここまでメモリを消費(というか解放)していないかはわかりませんが、このスクリプトでもっとぶん回したいと考える場合はメモリの容量と実行するパターンと時間を考慮に入れておくべきでしょう。今のところ回避策は思いついておりません。
最後に
このシリーズは実はここまでは予定しておりました。今後はさいころよりましかもしくは誤差の範囲内の結果をよくするために頑張ろうと思っていますが、結果が出る保証は今のところありません。
このため、どこまで行けるかはわかりませんが、やれることはたくさんあると考えています。現時点で私が想定している行えることは以下になります。
- 適切なパラメータ探しの旅の続き
- 売買しない条件の追加(例:当日のcloseと翌日のopenの差がある場合を除外など)
- 複数回実施して売買判定にする方式の導入(ランダム要素があるため、毎回同じではない)
- 複数のパラメータを実施し、総合的に売買評価を行う方式の導入(パラメータによっては得手・不得手があるように見えるため)
- 変更するパラメータを増やす(隠れ予想数等)
- highのみ、lowのみの1次元のデータで予想していたが、次元を増やす(hihg-lowの追加とかOpen/Closeを使ったものとか)
- 高速化(GRU等)の導入
このように多くのことをできそうですが、はっきり言って1GPUではできそうにないレベルです。そうなると複数GPUの導入やAWSを借りるなどを行う必要性があるかもしれません。この辺りは少し考えながら次の対策を考える予定でいます。