KerasのLSTMでFX予測
他にこんな記事は腐るほどあるのですが、実際やってみたので中身と結果を書いていきます。
LSTMと検索すると2番目にStock predictionと候補に出てきたことからも、誰もが考えることなんだと思います。
多くの人が同じことを試し、うまくいっていないのはすぐに分かりましたが、自分で試して納得したかったし、勉強にもなりそうだったので試してみました。
今回作ったものはここに置いています。
satken2/lstm_prediction - Github
(注)この記事はKerasの実装方法と結果を淡々と描いただけで、数学的・統計学的要素はありません。
やりたいこと
今回やりたいのは、ビットコインの価格予測です。
直近TIME_STEP
分間の価格や取引量データを入力として与えると、現在からOUTPUT_GAP
分後の価格を予測してくれるモデルを作成します。
下の図で言うと、赤枠のデータを入力として与えた時に、青枠のデータを予測してくれるようなプログラムを作ります。
この図の場合、「現在」を7:29とした時に、直近5分のデータを元に5分後のbest_bidを予測するということです。
プログラム
このモデルをLSTMで実装したので、やり方をざっくり書いていきます。
そもそもLSTMが何なのかについては他にいくらでも記事があるので、ここでは省略します。
Kerasって何?
Kerasというのは高水準のニューラルネットワークライブラリです。
ディープラーニングとかニューラルネットワークとかを1から実装するのは大変なのでTernsorFlowやTheanoとかのライブラリが作られましたが、そういうライブラリでもニューラルネットワークを作るにはそれなりのコードを書く必要があります。
そこで、TensorFlowやTheanoをラッピングしてもっと簡単にしたライブラリがKerasです。
ニューラルネットワークを少なく直感的なコードで実装できるようになっているので、初心者にも扱いやすいです。
今回はKerasを使ってシンプルに実装しました。
データの準備
前提として、CSV形式(CSV以外でもいいですが、この例ではCSVです)で集まった時系列のデータを準備しておきます。
今回は、Bitflyer APIで取得した1分単位のビットコインの価格データをCSVにして準備しています。
いい感じのデータが見つからなかったので、自分で集めました。
↓こんな感じのものです。
date,best_bid,best_ask,total_bid_depth,total_ask_depth,volume_by_product
2020-03-23T07:25:00.297,650579.0,650615.0,7017.64451802,6469.23510851,192733.63015592
2020-03-23T07:26:00.667,650560.0,650583.0,7023.45609319,6479.40277842,192773.208189
2020-03-23T07:27:00.227,649695.0,649786.0,7012.07827614,6321.83079407,192857.03498211
2020-03-23T07:28:00.697,650312.0,650377.0,7050.70418556,6327.61982308,192875.0513729
2020-03-23T07:29:00.443,651010.0,651066.0,7065.2146304,6465.56175859,192902.85924585
2020-03-23T07:30:00.73,650617.0,650678.0,7052.47989075,6462.6312908,192959.98713855
:
実装
じゃあ、肝心の実装に行きます。
やることは大きくこんな感じです。
- CSVデータの読み込み
- データを学習用と検証用に分割
- インプットデータと正解データのセットを作成
- データの正規化
- モデルの作成
- 学習
- 結果の検証
この中でKerasを使っているのは、「モデルの作成」「学習の実行」「結果の検証」です。
1.CSVデータの取り込み
データの読み込みではPandasというライブラリをよく使います。
笹食ってるクマ🐼じゃないですよ。英語のpanel dataから取ってpan(el)-da(ta)-sだそうです。(Wikipedia)
pandasでCSVを読み込むとDataFrameというpandasの形式で返してくれます。
df_input = pd.read_csv(input_path, engine='python')
print(df_input.dtypes)
date object
best_bid float64
best_ask float64
total_bid_depth float64
total_ask_depth float64
volume_by_product float64
dtype: object
2.データを学習用と検証用に分割
LSTMに限らず、ディープラーニング全般は学習し終わってハイ終わりではなくてちゃんと学習したモデルで予測がうまくいくかを検証する必要があります。
学習に使ったデータを使って検証したら正しく評価できないこともあるので、データの一部を敢えて学習には使わずに検証用として残しておきます。
今回は8:2の割合で学習用と検証用に分けています。
train_test_split
というDataFrameを分けてくれる関数がsklearnにあるのでそれを使います。便利ですね。
df_train, df_test = train_test_split(df_input, train_size=0.8, test_size=0.2, shuffle=False)
print("Train-Test size", len(df_train), len(df_test))
Train-Test size 7826 1957
3.インプットデータと正解データのセットを作成
下の図で、赤枠と青枠のデータのセットを学習用データとしてモデルに与えるために、大量に作っていきます。
赤枠が問題で青枠が正解と思えばいいでしょう。
実際にはデータはもっと下に続いてるので、枠を1つずつ下にずらしながら大量のセットを作っていきます。
具体的な実装としてはこんな感じです。
dimention_0 = input_data.shape[0] - time_steps - output_gap
dimention_1 = input_data.shape[1]
x = np.zeros((dimention_0, time_steps, dimention_1))
y = np.zeros((dimention_0,))
for i in range(dimention_0):
x[i] = input_data[i:time_steps+i]
y[i] = input_data[time_steps+output_gap+i, y_col_index]
print("length of time-series i/o",x.shape,y.shape)
input_data
は読み込んだCSVをnumpy配列にしたものです。これを、問題のリストであるx
と正解のリストであるy
に変換しています。x[n]
に対応する正解データがy[n]
といった具合です。
ndarrayの次元
numpyを扱っているとどの数字がどの次元かがいつも分からなくなるんですが、自分は「Shapeを表示した時、左の数字ほど大きな単位の次元」と覚えています。すごく初心者っぽいですが、、
例えば、「5個の数字で構成される時系列データが1000行あって、それを20セット作った」場合、Shapeは(20, 1000, 5)になります。日本語にした時と逆になるのですね。
今回学習のために作りたい入力データのShapeは、上の例で言うとx = np.zeros((dimention_0, time_steps, dimention_1))
の部分です。
これをさっきの考え方で解釈すると、1行が**dimention_1
個の数字で構成されているデータをtime_steps
行並べて、さらにそのセットをdimention_0
個作る。という意味です。
結果データは単純に1つの数字なので、yは数字がdimention_0
**個集まった配列です。
4.入力データの正規化
ディープラーニングでは、効率的に学習を進めるために入力データを処理することが多いです。
正規化や標準化といった方法がよく使われますが、今回はMinMaxScaler
を使って簡単に正規化をしています。
正規化は、データの最大値を1, 最小値を0として、それに合わせて全ての値をスケールするやり方です。
min_max_scaler = MinMaxScaler()
x_train = min_max_scaler.fit_transform(df_train.loc[:,feature_columns].values)
x_test = min_max_scaler.transform(df_train.loc[:,feature_columns])
入力データの処理がなぜ必要なのかは、自分よりももっと賢い人が解説してくれているので、そちらを見てください。
Feature Scalingはなぜ必要?
5.モデルの作成
肝心のモデルを作成します。
モデルはKerasの機能を使って簡単に書けます。
lstm_model = Sequential()
lstm_model.add(LSTM(100, batch_input_shape=(batch_size, time_steps, len(feature_columns)),
dropout=0.0, recurrent_dropout=0.0, stateful=True, return_sequences=True,
kernel_initializer='random_uniform'))
lstm_model.add(Dropout(0.4))
lstm_model.add(LSTM(60, dropout=0.0))
lstm_model.add(Dropout(0.4))
lstm_model.add(Dense(20,activation='relu'))
lstm_model.add(Dense(1,activation='sigmoid'))
optimizer = optimizers.RMSprop(lr=learning_rate)
lstm_model.compile(loss='mean_squared_error', optimizer=optimizer)
Sequential()
でモデルの元を作成して、add()
でレイヤーを追加していきます。
今回作ったモデルをよくある図にするとこんな感じですかね。
入力のShapeは(batch_size, time_steps, len(feature_columns)
です。
さっきの次元の覚え方みたいに逆から読んで日本語にすると、len(feature_columns)
個の数字で構成される時系列データがtime_steps
分集まっていて、それがbatch_size
ずつかたまりになっているということですね。
出力は数分後のbest_bid(1つの値)です。
だったら出力は0次元?と思いそうですが、実際にはbatch_size
分をまとめて放り込んでるので、実際に出てくる値は要素数がbatch_size
の1次元のndarrayになりますね。
バッチ
LSTMに限らず、通常ニューラルネットワークでは問題データと正解データを1つずつ入れていくということはせず、計算効率を上げるためにバッチという塊の単位で渡していきます。なので今回一番上位の次元はbatch_size
になっているわけです。
なぜバッチで渡したら計算が早くなるのかは正直よく知りません。GPUは数値の並列処理に特化した造りになっているので、バッチで並列計算した方がリソースを効率よく使えるというような感じです。
6.学習
ここまで準備ができたらよくやく学習です。
model.fit
で学習を開始できます。
from keras import backend as K
model = create_model(learning_rate, batch_size, time_steps, feature_columns)
history = model.fit(x_t, y_t, epochs=epochs, verbose=2, batch_size=batch_size,
shuffle=False, validation_data=(trim_dataset(x_val, batch_size),
trim_dataset(y_val, batch_size)))
結果
作ったプログラムを動かしてみて結果を見てみました。
予測値と実際の値の比較
最初に分けておいた検証用データで価格を予測したグラフがこれです。
一見きれいに沿っているように見えますが、よく見ると実データのグラフを少し右にずらしただけ(つまり、直前の値をそのまま出しているだけ)にも見えます。
取引シュミレーション
過去の価格データを元に、取引をシュミレートするプログラムを書いて試しました。
上がると思ったらASK, 下がると思ったらBIDで成行注文を入れて、10分後に反対注文を入れてポジション解消するという単純な想定です。
確か3, 4時間ぐらいのデータを使いました。
うーん。なんか微妙ですね。ほぼ横ばいです。
Bitflyer APIで実取引
Bitflyer APIとcronジョブを使って実際にお金を入れて自動取引を試してみました。
条件はさっきと同じで、25時間ほど動かし続けて1000件ぐらい注文が入りました。
最初は順調に上昇していたのですが、、やっぱり横ばいでした。
詳細な分析はしていないので見た目だけの感想ですが、よくよく見るといい感じに上がってたのは2時間目~6時間目ぐらいの間だけでそれ以外は完全に横ばいと言っていい感じですね。最初上がったのはまぐれでしょう。
感想
株価やFXというのは大量の時系列データが手に入りやすいのでLSTMのお試しとしてよく使われますが、モデルを作りやすいというのと予測に適しているかは必ずしも一致しないのでしょう。
なぜうまくいかないのか
冒頭でも書いたように、他の多くの人が株価や外為を予測しようとして上手くいっていないようです。
なぜ相場予測が難しいのか少し考えてみました。
あくまで自分の仮説ですが、
相場というのは裁定取引の最たるものなので、仮に動き方に何らかの法則が見つかれば、すぐにその法則で儲けようとする人が増えて法則が崩れるという現象が常に連続して起きていると思います。なので短期的には一定の法則が成り立ちにくい性質のものなのかもしれないと感じました。
でも、ドル円の相場とかは本質的にはマクロ経済を反映しているはずなので、長期的な価格の動きは色々な経済指標を学習させたら予測することはできるかもしれません。(ビットコインはわかりませんが、、)
データを集めるのが大変ですけどね。
投資におけるDLの使いどころ
また、これは自分の意見ではないですが、以下のサイトでは過去の株価から未来の株価を予測することはできないとした上で、そもそもチャートに固執するやり方ではなく、人間の労力では分析できない大量のIR情報をニューラルネットワークに読み込ませて、株価ではなくそのビジネス全体の動向を分析するような使い方ができるのではないかと意見を述べています。
Stock Prices Don’t Predict Stock Prices - Medium
なんにせよ、ただチャートを読み込ませるだけで価格予測をするというのはあまりにも芸がなかったですね。
参考
How to Use the Keras Functional API for Deep Learning
Predicting Stock Price with LSTM
Stock Prices Don’t Predict Stock Prices - Medium