株価チャートをベクトルに変換するchart2vec(autokerasベース)を作った話

今回はchart2vecを作った話です。

株において銘柄の強弱はチャートに現れるのでチャートの分析は非常に重要です。時系列としてではなく図形として視覚的に投資判断をするプレイヤーが多そうなので需要予測に使えるのではないかと思い、これを作りました。

# 前準備的な話
autokerasの使い方
https://qiita.com/shiibass/items/28ffcaa9aec2799d5691

特徴量抽出器の作り方
https://qiita.com/shiibass/items/c1b7649019c1df965db1

# 今回作ったもの
https://github.com/shiibashi/chart2vec

# チャートを画像データに変換する
株価の日足データは始値、高値、安値、終値の4項目で表すことが多く、Pythonではmpl-financeという超便利ライブラリがあります。https://pypi.org/project/mpl-finance/
その中のcandlestick_ohlcでローソクを生成でき、残りはmatplotlibで描画します。細かいことはgithubを見てほしいですが、抜粋するとこんな感じ↓

```python
    fig = plt.figure()
    ax = plt.subplot()
    plt.xticks(color="None")
    plt.yticks(color="None")
    plt.tick_params(length=0)
    xdate = [
        datetime.datetime.strptime(x, "%Y-%m-%d") for x in chart_data["date"]
    ]
    ohlc = numpy.vstack((date2num(xdate), chart_data[["open", "high", "low", "close"]].values.T)).T
    mpf.candlestick_ohlc(ax, ohlc, width=0.7, colorup='r', colordown='g')
    ax.set_xlim(xdate[0], xdate[-1])
    fig.autofmt_xdate()
    fig.savefig("{}/img/{}".format(DATA_PATH, jpg_name))
    plt.close(fig)

```
これを実行すると↓みたいなCSVが↓↓みたいな画像に変換できます。


```
date,open,high,low,close
2015-02-16,190.52002001334,193.9045580982956,189.62712940891217,191.05201340026787
2015-02-19,193.03162976389825,199.16988122028255,192.45049833749204,199.06988122028255
2015-02-20,197.9417428370527,198.6110831926739,193.94584606728242,196.10170918075815

```
![0_label1.jpg](https://qiita-image-store.s3.amazonaws.com/0/211078/3cde5f69-db86-6ad9-61e3-cb2cbb0d6517.jpeg)

ローソクチャートを使わない人はmpl-financeは不要です。matplotlibなどで自由にチャート画像を生成してください。
私はこの画像に移動平均を加えたり出来高を加えたりしてます。

# 学習
↑で書いた方法で画像データを4000枚用意しました。サンプルとして2種類に分類するタスクにしてあり、それぞれ1つの確率微分方程式に対応しています。データはgithubに上げてます。例えば↑が片方のラベルAで↓がラベルBです。
![2_label2.jpg](https://qiita-image-store.s3.amazonaws.com/0/211078/c8f86766-428f-35da-a9d2-d45ff38b5eed.jpeg)

学習の仕方は以前書いた記事にまとめました
https://qiita.com/shiibass/items/28ffcaa9aec2799d5691
autokerasを使っています。コードはこれです。

```python

    model = autokeras.ImageClassifier()
    model.fit(train_X, train_Y, time_limit=0.5*60*60)
    print(model.cnn.searcher.history)
    model.final_fit(
        train_X,
        train_Y,
        test_X,
        test_Y,
        retrain=False)
    print(model.cnn.best_model.produce_model())
    path = "model.pkl"
    model.export_autokeras_model(path)
```

学習結果はこんな感じでした。11個のネットワークを生成し、だんだん損失が小さくなっていくのがわかります。

```bash
[
{'model_id': 0, 'loss': 1.3202951815910637, 'metric_value': 0.8827067669172932},
#..略
 {'model_id': 3, 'loss': 0.542041439958848, 'metric_value': 0.9533834586466166},
 {'model_id': 4, 'loss': 0.9012323039351031, 'metric_value': 0.930827067669173},
 {'model_id': 5, 'loss': 1.0478272199252388, 'metric_value': 0.9285714285714285},
# 略
 {'model_id': 9, 'loss': 0.12342515563123016, 'metric_value': 0.9894736842105262},
 {'model_id': 10, 'loss': 0.1919486330296877, 'metric_value': 0.9789473684210526}
]
```

出来上がったネットワークはこんな感じのいろいろ層が詰まったやつ（ネットワークの結合手順は数字の順番ではないので注意。autokerasを使うとこんなことができるっていうイメージを持ってもらいたいので雰囲気こんな感じです）

```bash
TorchModel(
  (0): ReLU()
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): Conv2d(3, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (3): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (4): ReLU()
  (5): BatchNorm2d(128, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (6): Conv2d(128, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (7): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (8): ReLU()
  (9): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (10): Conv2d(512, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  (11): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (12): GlobalAvgPool2d()
  (13): Dropout2d(p=0.25)
  (14): Linear(in_features=64, out_features=64, bias=True)
  (15): ReLU()
  (16): Linear(in_features=64, out_features=2, bias=True)
  (17): Conv2d(64, 64, kernel_size=(5, 5), stride=(1, 1), padding=(2, 2))
  (18): Conv2d(512, 512, kernel_size=(1, 1), stride=(1, 1))
)
```


# 予測
学習したモデルで分類として使うのもよいですが、特徴ベクトルを抽出したいです。autokerasのソースを書き換えて、チャートデータをベクトルデータに変換します。
↑のTorchModelで(14): Linear(in_features=64, out_features=64, bias=True)の結果を取り出して↓のように64次元のベクトルに変換できました。

![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/211078/11eac5a6-5278-b0e6-1489-9b527be18377.png)

コードはこんな感じ

```python
    X, _ = load_dataset("{}/img".format(DATA_PATH))
    test_X = numpy.array(X)
    path = "model.pkl"
    model = autokeras.utils.pickle_from_file(path)
    vec_X = model.predict(test_X[0:2], output_index=-2)
    df = pandas.DataFrame(vec_X)
    df.to_csv("vec.csv", index=False)

```

# ベクトル変換するメリット
画像やテーブルデータ、時系列データなど複数のデータ構造を同時に機械学習する場合はこんな感じのネットワークを作って結合させますか？もちろんいいと思いますが計算量が巨大になるのとうまく最適化させるのが難しそうです。

![blog用.png](https://qiita-image-store.s3.amazonaws.com/0/211078/6d29f890-73f8-542a-3551-aad83b032c46.png)

2vecでベクトル化を前処理として加えることでデータの扱いやすさが格段にアップします↓。XGBoostも使えるようになりますし、クラスタリングして可視化などもしやすくなります。あとは類似チャートの自動検知システムを作ったり、爆上げチャートの分析に使ったり、使い方はいろいろです。

![blog用 (1).jpg](https://qiita-image-store.s3.amazonaws.com/0/211078/702fa27a-b1c0-fee7-6b4a-a0be906107ad.jpeg)

# 発想元資料

今回以下の２つの論文のアイデアを組み合わせるために開発しました。

### Deep Neural Networks for YouTube Recommendations
参考にした資料としてはYoutubeのレコメンドアルゴリズムです。投資をやっていなくても読む価値ありまくりの論文です。所属する企業に役立つ論文も多少は読まないとね？
論文↓の中で下図のような異なるデータ構造のデータを結合させたディープラーニングを実装していて、学習性能を高めるためには重要な要素だよなーと思った次第です。
https://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/45530.pdf


![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/211078/16dec59b-d69d-7dd8-a2b7-4a63d1a004a8.png)

### Stock Chart Pattern recognition with Deep Learning
チャート形状を認識するディープラーニングです。これは投資をやっていない人にはあまり価値のない論文です。
https://arxiv.org/pdf/1808.00418.pdf