だいぶ投稿が遅くなってしまいましたがこちらの記事は深層学習以外の機械学習と応用技術 by QuantumCore Advent Calendar 2019の7日目の記事です。
はじめに
まずはじめにこちらのQoreSDKの紹介記事読んで、Qoreの強みが時系列データに強く、学習が高速でデータが少なくても精度が出せるという点であるということを理解しました。
そこで通貨取引の価格上昇下降のタイミングが素早く検出できれば取引の利益を大きく損失を小さくすることができるのではないかと考えました。
次に扱う題材ですがBTC(ビットコン)に決めました。また、BTCの取引履歴はオープンデータとして公開されているため開発者が扱いやすいという利点も選択理由の一つです。
というわけでKaggleに公開されていますこちらのデータをKaggleDataset - bitcoin-historical-dataを今回は使います。
これ以降の紹介で使っておりますソースコードは下記GooglaColabratoryで動作確認したコードになります。
GoogleColabratory - btc.ipynb
データの読み込み
まずKaggleDatasetからデータをダウンロードしたことを前提に話を進めます。GoogleColabratory上でKaggleDatasetからデータをダウンロードする方法については下記記事を参考にしました。
Qiita - Google Colab上でKaggleのデータをロード、モデル訓練、提出の全てを行う
まずはデータを読み込む部分です、元々加工済みのCSVファイルということもありpandasでそのまま読み込めます。
import numpy as np
import pandas as pd
btc = pd.read_csv("/content/bitstampUSD_1-min_data_2012-01-01_to_2019-08-12.csv")
btc.head()
"""
Timestamp Open High Low Close Volume_(BTC) Volume_(Currency) Weighted_Price
0 1325317920 4.39 4.39 4.39 4.39 0.455581 2.0 4.39
1 1325317980 NaN NaN NaN NaN NaN NaN NaN
2 1325318040 NaN NaN NaN NaN NaN NaN NaN
3 1325318100 NaN NaN NaN NaN NaN NaN NaN
4 1325318160 NaN NaN NaN NaN NaN NaN NaN
"""
データの前処理
データを見てみると欠損値が存在するため、直前の値で欠損値を埋めるffillを使って欠損値を埋めます。
また今回の予測対象である価格上昇・下降の定義ですが、終値(カラムではClose)が始値(カラムではOpen)よりも値が上回っていた場合は価格が上昇したと定義します。
最後にTimestampカラムをto_datetimeを使って扱いやすいように型変換しておきます。
btc[btc.columns.values] = btc[btc.columns.values].ffill()
btc['Delta'] = btc['Close'] - btc['Open']
def digitize(n):
if n > 0:
return 1
return 0
btc['label'] = btc['Delta'].apply(lambda d: digitize(d))
# https://stackoverflow.com/questions/19231871/convert-unix-time-to-readable-date-in-pandas-dataframe
btc["Timestamp"] = pd.to_datetime(btc["Timestamp"], unit='s')
btc.head()
"""
Timestamp Open High Low Close Volume_(BTC) Volume_(Currency) Weighted_Price Delta label
0 2011-12-31 07:52:00 4.39 4.39 4.39 4.39 0.455581 2.0 4.39 0.0 0
1 2011-12-31 07:53:00 4.39 4.39 4.39 4.39 0.455581 2.0 4.39 0.0 0
2 2011-12-31 07:54:00 4.39 4.39 4.39 4.39 0.455581 2.0 4.39 0.0 0
3 2011-12-31 07:55:00 4.39 4.39 4.39 4.39 0.455581 2.0 4.39 0.0 0
4 2011-12-31 07:56:00 4.39 4.39 4.39 4.39 0.455581 2.0 4.39 0.0 0
"""
btc.tail()
"""
Timestamp Open High Low Close Volume_(BTC) Volume_(Currency) Weighted_Price Delta label
3997692 2019-08-11 23:56:00 11555.57 11555.57 11540.37 11540.58 0.036868 425.909106 11552.336234 -14.99 0
3997693 2019-08-11 23:57:00 11553.49 11556.22 11553.49 11556.22 0.623462 7204.428272 11555.520505 2.73 1
3997694 2019-08-11 23:58:00 11559.73 11561.22 11546.77 11561.22 0.159070 1838.731403 11559.252199 1.49 1
3997695 2019-08-11 23:59:00 11559.73 11589.73 11528.73 11528.73 16.198210 187504.635170 11575.638889 -31.00 0
3997696 2019-08-12 00:00:00 11527.44 11551.57 11520.00 11520.00 23.805939 274731.256920 11540.450291 -7.44 0
"""
モデルの学習に使う訓練データと評価に使うテストデータを作っていきます。
データを作る際の注意点としてQoreSDKの使い方について事前にメールにて以下のように注意書きがあったためそれに従うため、訓練・テストデータ共に時間長を約12時間(1サンプル1分間隔)に抑えています。
なお、一度にAPIに投げられるデータサイズに制限がありまして、NTV < 150,000 && N*T < 10,000の大きさまでのデータとして頂きますようお願いいたします。
またAPIのドキュメントを読むと訓練、予測時共に型がnumpy.ndarray
でありかつ学習・推論用データの次元が(データ数, 時間, 実データ)、正解ラベルの次元が(データ数, 1)であったのでそのように変換しておきます。
X (numpy.ndarray) – 学習データ X.shape = (データ数, 時間, 実データ)
Y (numpy.ndarray) – 正解ラベル Y.shape = (データ数, 1)
X (numpy.ndarray) – 推論用データ X.shape = (データ数, 時間, 実データ)
Y (numpy.ndarray) – 正解ラベル Y.shape = (データ数, 1)
data = btc[('2019-01-01' < btc['Timestamp']) & (btc['Timestamp'] < '2019-01-01 12:00')]
data
"""
Timestamp Open High Low Close Volume_(BTC) Volume_(Currency) Weighted_Price Delta label
3676577 2019-01-01 00:01:00 3694.72 3694.72 3690.65 3690.65 9.500151 35080.265871 3692.600865 -4.07 0
3676578 2019-01-01 00:02:00 3689.73 3689.73 3686.62 3686.62 0.965966 3562.371230 3687.884698 -3.11 0
3676579 2019-01-01 00:03:00 3692.85 3692.85 3688.32 3692.35 0.296662 1095.220713 3691.813285 -0.50 0
3676580 2019-01-01 00:04:00 3692.35 3692.35 3690.34 3690.34 0.111622 412.065433 3691.614849 -2.01 0
3676581 2019-01-01 00:05:00 3690.40 3690.85 3690.40 3690.85 2.247676 8295.406915 3690.659723 0.45 1
... ... ... ... ... ... ... ... ... ... ...
3677291 2019-01-01 11:55:00 3706.01 3706.01 3706.01 3706.01 0.412970 1530.471061 3706.010000 0.00 0
3677292 2019-01-01 11:56:00 3706.01 3706.01 3706.01 3706.01 0.412970 1530.471061 3706.010000 0.00 0
3677293 2019-01-01 11:57:00 3706.01 3706.01 3706.01 3706.01 0.412970 1530.471061 3706.010000 0.00 0
3677294 2019-01-01 11:58:00 3706.01 3706.01 3706.01 3706.01 0.071164 263.734496 3706.010000 0.00 0
3677295 2019-01-01 11:59:00 3706.01 3706.01 3700.00 3700.00 14.862874 55009.746820 3701.151476 -6.01 0
"""
labels = data["label"]
data.drop(columns=["Timestamp", "label", "Delta"], inplace=True)
data = np.array(data)
labels = np.array(labels)
data = data.reshape((data.shape[0], data.shape[1], 1))
labels = labels.reshape((labels.shape[0], 1))
test_data = btc[('2019-01-01 12:00' < btc['Timestamp']) & (btc['Timestamp'] < '2019-01-01 23:59')]
test_data
"""
Timestamp Open High Low Close Volume_(BTC) Volume_(Currency) Weighted_Price Delta label
3677297 2019-01-01 12:01:00 3694.63 3694.63 3694.63 3694.63 2.572921 9505.991188 3694.630000 0.00 0
3677298 2019-01-01 12:02:00 3694.63 3694.63 3694.63 3694.63 2.572921 9505.991188 3694.630000 0.00 0
3677299 2019-01-01 12:03:00 3694.63 3694.63 3694.63 3694.63 2.572921 9505.991188 3694.630000 0.00 0
3677300 2019-01-01 12:04:00 3701.03 3704.20 3701.03 3704.20 2.176152 8060.808805 3704.156299 3.17 1
3677301 2019-01-01 12:05:00 3704.20 3704.20 3704.20 3704.20 0.045025 166.779901 3704.200000 0.00 0
... ... ... ... ... ... ... ... ... ... ...
3678010 2019-01-01 23:54:00 3812.78 3821.38 3812.78 3821.38 21.387205 81679.855061 3819.099098 8.60 1
3678011 2019-01-01 23:55:00 3819.70 3819.70 3816.41 3816.41 1.699109 6488.410322 3818.713657 -3.29 0
3678012 2019-01-01 23:56:00 3816.41 3816.99 3813.81 3816.99 3.007816 11480.373641 3816.847055 0.58 1
3678013 2019-01-01 23:57:00 3816.41 3816.99 3813.81 3816.99 3.007816 11480.373641 3816.847055 0.58 1
3678014 2019-01-01 23:58:00 3816.41 3816.99 3813.81 3816.99 3.007816 11480.373641 3816.847055 0.58 1
"""
test_labels = test_data["label"]
test_data.drop(columns=["Timestamp", "label", "Delta"], inplace=True)
test_data = np.array(test_data)
test_labels = np.array(test_labels)
test_data = test_data.reshape((test_data.shape[0], test_data.shape[1], 1))
test_labels = test_labels.reshape((test_labels.shape[0], 1))
モデルの学習
準備した訓練データを使ってモデルの学習をしてゆきます。今回は分類問題なので評価結果をsklearn.metrics.classification_reportを使って求めます。
今回予測したかった価格の上昇がうまく学習できていないのかまったく予測できていません。
from qore_sdk.client import WebQoreClient
# api_keyの読み取り
with open(api_key_path, "r") as f:
api_key = json.load(f)
client = WebQoreClient(**api_key)
client.classifier_train(data, labels)
# {'res': 'ok', 'train_time': 0.8283402919769287}
response = client.classifier_predict(data)
from sklearn.metrics import classification_report
report = classification_report(labels, response['Y'])
print(report)
"""
precision recall f1-score support
0 0.66 1.00 0.79 473
1 0.00 0.00 0.00 246
accuracy 0.66 719
macro avg 0.33 0.50 0.40 719
weighted avg 0.43 0.66 0.52 719
"""
モデルのテスト
最後に訓練データにはない未知データを使って予測してゆきます。
不思議なことに学習では一切予測が当たらなかった価格の上昇をそれなりに当てています。
response = client.classifier_predict(test_data)
report = classification_report(test_labels, response['Y'])
print(report)
"""
precision recall f1-score support
0 0.11 0.02 0.03 441
1 0.34 0.79 0.47 277
accuracy 0.32 718
macro avg 0.22 0.41 0.25 718
weighted avg 0.20 0.32 0.20 718
"""