こんにちは、Aidemy研修生の八木です。 皆さんは楽してお金が欲しいと思ったことはないでしょうか? もし仮想通貨の未来の価格が予想できたら一生遊んで暮らせそうですよね。 そこで、今回はAidemyで学んだ時系列解析を生かし、仮想通貨の予測に挑戦してみました! また、データの加工でどのように予測が変わるか検証してみました。
対象者
- 株、FX、仮想通貨などに興味がある方
- それらを機械学習で予測してみたい方
概要
BCHのチャートデータをPoloniexのAPIから取得。 kerasのLSTMモデルを構築し、BCHの回帰予測を行う。 その結果から次の時間の騰落予測し、正答確率を算出する。
python構築環境
- MacBook Air (プロセッサ: 2.2 GHz Intel Core i7)
- Python3.6.0
- Jupyter4.4.0
また、以下のモジュールを使用しました。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import poloniex
import time
import tensorflow as tf
from __future__ import print_function
from keras.layers.core import Activation
from keras.layers.core import Dense
from keras.layers.core import Dropout
from keras.models import Sequential
from keras.optimizers import Adam
from keras.layers.recurrent import LSTM
from keras.callbacks import EarlyStopping
データの取得
まず、通貨価格の価格データを取得するのですが、取引所であるPoloniexがAPIを公開しているためそこからデータを取得することができます。 また、それをPython でさらに使いやすくしたpython-poloniexがGitHubで公開されていて、今回はそれを利用しました。 python-poloniexの導入は以下のようにできます。
- GitHubのサイトからzipファイルをダウンロード github.com
- ダウンロードしたzipを解凍し、ファイル内で以下を実行 python setup.py install
これでpython-poloniexを使うことができます。 では実際にpoloniexを使いデータを取得して行きます。 今回は、BCH(ビットコインキャッシュ)の4時間おきの価格を500日分取得しました。 BCHを選んだ理由は昔買ったことがあるからです(笑) データの日数は実際にチャートを見て適当な範囲で決定しました。
polo = poloniex.Poloniex()
chart_data = polo.returnChartData('USDT_BCH', period= 7200, start=time.time()-polo.DAY*500, end=time.time())
df = pd.DataFrame(chart_data)
polo.returnCharatDataには、通貨ペア、periodに取得間隔、startに開始時刻、endに終了時刻を指定します。 また、polo.DAYで1日分を指定できます。 取得したデータはpandasのdataframeに変換して扱います。 では次に、取得したデータをプロットして正しく取得できているかを確認してみます。
data = df["close"].astype("float32")
rcParams['figure.figsize'] = 15, 6
plt.title("BCH_Chart")
plt.xlabel("date")
plt.ylabel("price")
plt.grid(True)
plt.plot(data)
plt.show()
正しくプロットすることができました!
データの加工
次にデータを加工し学習しやすいようにしていきます。
時系列データでは移動平均を求めることによってグラフ滑らかにすることがよく行われます。
今回は4時間おきでデータを集めたため、6つ分のデータで平均を取り1日幅での移動平均を求めました。
data1 = df["close"].rolling(6).mean()
data1 = data1.fillna(data1[5])
plt.plot(data)
plt.plot(data1)
plt.grid(True)
plt.show()
このように多少滑らかな線に変わりました。
では次にこのデータから入力変数と出力変数を取り出し訓練データとテストデータに分割するところまで行います。
今回は変換するデータを変えながら検証を行なったため関数化しておきました。
def data_split(data, v_size=30, train_split_latio=0.7):
data = data.astype("float32")
x, t = [], []
data_len = len(data)
for i in range(data_len - v_size):
x_valu = data[i : i+v_size]
t_valu = data[i+v_size]
x.append(x_valu)
t.append(t_valu)
x = np.array(x).reshape(data_len-v_size, v_size, 1)
t = np.array(t).reshape(data_len-v_size, 1)
border = int(data_len * train_split_latio)
x_train, x_test = x[: border], x[border :]
t_train, t_test = t[: border], t[border :]
return x_train, x_test, t_train, t_test
入力変数は与えられたサイズの幅で1つずつずらしながら取集していき、出力変数はその直後の値としています。
また、今回学習に使うLSTMモデル用に、入力変数は3次元ベクトル、出力変数が2次元ベクトルに型を直しています。
訓練データとテストデータの分割は、与えられた割合で前半のデータを訓練用、後半のデータをテスト用に分けています。
モデル定義
今回の学習ではKerasのLSTMモデルを採用しました。
時系列データのような、前のデータが次のデータに影響を与えてしまうデータは通常の学習モデルではうまく学習ができません。
そのため、時系列データの解析ではそういった場合にも対応して学習ができるRNN(リカレントネットワーク)が採用されることが多いです。
近年ではその中でもLSTMモデルがよく使われているため、今回はそれを使って学習を行います。
def create_LSTM(v_size, in_size, out_size, hidden_size):
tf.set_random_seed = (20180822)
model = Sequential()
model.add(LSTM(hidden_size, batch_input_shape = (None, v_size, in_size),
recurrent_dropout = 0.5))
model.add(Dense(out_size))
model.add(Activation("linear"))
return model
このモデルは入力データ幅、入力数、出力数、隠れ層の数を指定することで使うことができます。
今回は回帰予測を行うため、活性化関数は’linear’を指定しています。
パラメーターの定義
モデルも定義したし早速学習に移りたいのですが、その前にパラメータをまとめて定義しておきましょう。
この部分をいじることで後の学習精度に大きく影響します。
now_data = data
v_size = 30
train_split_ratio = 0.7
x_train, x_test, t_train, t_test= data_split(now_data, v_size, train_split_ratio)
mean = np.mean(x_train)
std = np.std(x_train)
x_train = (x_train - mean) / std
x_test = (x_test - mean) / std
tmean = np.mean(t_train)
tstd = np.std(t_train)
t_train = (t_train - tmean) / tstd
t_test = (t_test - tmean) / tstd
in_size = x_train.shape[2]
out_size = t_train.shape[1]
hidden_size = 300
epochs = 100
batch_size = 30
見てわかる通り訓練データとテストデータの標準化をこの部分で行なっています。
標準化とはデータを平均が0、分散を1にすることで、値のサイズを小さくし扱いやすくします。
標準化により学習精度も上がります。
データの学習と予測
では学習を始めましょう!
early_stopping = EarlyStopping(patience=10)
model = create_LSTM(v_size, in_size, out_size, hidden_size)
model.compile(loss="mean_squared_error", optimizer = Adam(0.0001))
model.fit(x_train, t_train, batch_size = batch_size, epochs = epochs, shuffle = True, callbacks = [early_stopping], validation_split = 0.1)
EarlyStoppingを使うことで、損失が減少しなくなってから一定カウント進んだとことで学習をストップできます。
これにより過学習を防ぐことができます。
今回は回帰問題を扱うため”mean_squared_error”と”Adom”を使用しました。
では早速学習したデータで予測し、プロットしてみましょう!
pred_train = model.predict(x_train)
pred_train = pred_train * tstd + tmean
a = np.zeros((v_size, 1))
b = pred_train
pred_train = np.vstack((a, b))
plt.figure()
plt.plot(pred_train, color="r", label="predict")
plt.plot(now_data, color="b", label="real")
plt.grid(True)
plt.legend()
plt.show()
pred_test = model.predict(x_test)
pred_test = pred_test * tstd + tmean
a = np.zeros((v_size + x_train.shape[0], 1))
b = pred_test
pred_test = np.vstack((a, b))
plt.figure()
plt.plot(pred_test, color="r", label="predict")
plt.plot(now_data, color="b", label="real")
plt.grid(True)
plt.legend()
plt.show()
上が訓練データに対する回帰、下がテストデータに対する回帰となっています。
実測値線に沿った予測値線が引けたのではないでしょうか!
結果解釈
しかし、よく見てみると実測値線と少しずれて予測値線が構成されているようにも見えます。
つまり、今回は30サンプル取って学習しているわけですが、その中の直前の値を参考に次の値の予測を行なっているということです。
実は、仮想通貨や株のチャートはランダムウォークといい、値が上下する確率はほぼ五分五分になっていることが知られています。
そのため、予測をする直前の値の依存性が高くなり、予測値線が実測値線から少しずれたようになっています。
試しに次の値の騰落(値が上がったか下がったか)の予測が当たっている確率を出してみたいと思います。
data_score = 0
for i in range(pred_train.shape[0] - v_size - 1):
if pred_train[i + v_size + 1] - pred_train[i + v_size] > 0:
if data[i + v_size + 1]-data[i + v_size] > 0:
data_score += 1
if pred_train[i + v_size + 1] - pred_train[i + v_size] < 0:
if data[i + v_size + 1] - data[i + v_size] < 0:
data_score += 1
print("train score: {}".format(data_score / (pred_train.shape[0] - v_size - 1)))
N = x_train.shape[0] + v_size
data_score = 0
for i in range(pred_test.shape[0] - N - 1):
if pred_test[i + N + 1] - pred_test[i + N] > 0:
if data[i + N + 1] - data[i + N] > 0:
data_score += 1
if pred_test[i + N + 1] - pred_test[i + N] < 0:
if data[i + N + 1] - data[i + N] < 0:
data_score += 1
print("test score: {}".format(data_score / (pred_test.shape[0] - N - 1)))
騰落予測結果はランダムウォークの性質通り50%付近になってしまいました。
しかも50%を下回っています。
やはり仮想通貨の騰落を予測するのは難しい……
使用データを変えて再学習
実測値で学習をした場合では正答確率46%という結果になってしまいました。
では、はじめに作った1日幅で移動平均をとったデータで学習した場合はどうでしょうか?
データが滑らかになった分、精度が上がるかもしれません。
学習データを変えるには、パラメータ定義の”now_data”の部分を変更するだけです。
結果をプロットして見ましょう。
どうでしょうか。
さっきよりも実測値線に近くプロットされているように見えます。
では騰落予測の正答確率も出して見ます。
ソースコードは先ほどと同じものを使用しました。
先ほどよりも精度をあげることができました!
しかし結果としてはやはり五分五分というところです。
まとめ
今回はBCHの500日分のデータを使い回帰予測と騰落予測を行なってみました!
回帰に関しては、おおよそ実測値線に沿った予測値線を引くことができましたが、騰落予測は正答確率約50%というところです。
初めは正答確率50%を切っていましたが、移動平均と標準化により越えることができました。
しかし高い確率とは言えないため、綺麗に引けた予測線も、結局は直前の値に大きく影響を受けた線と言えそうですね……
やはり、前の値と依存関係のある時系列データの解析に使えるLSTMモデルでも、ランダムウォークである仮想通貨の予測は難しかったようです。
理由として、仮想通貨はその通貨の価格データだけでなく、他の通貨の情報や世界情勢など多くの要因で変動してしまうことが挙げられます。
また株などのデータと比べ、激しく価格が上下することも解析を難しくしていると考えられます。
ただこのモデルを使えば、どんなに投資が下手な人でも50%の確率では騰落を当てられるのではないでしょうか(笑)