[Keras + TensorFlow]ディープラーニングで日経平均株価を予測する(1)

# 株価を予測するためには
それなりに株で儲けている人は日経平均なり株価なりを予測するのにいくつかの情報を使っているはずである。

1. 過去の株価
2. 日経の新聞記事など
3. twitterなどの口コミ情報

おそらくこれらの情報を元に何かしらのルールで株の売買をしているはずなので、
これらの情報をうまくベクトル化、重畳して機械学習させればそれなりにいい結果が出せるのではと考えた。

ひとまず

1. 過去の株価

これをベースに考えていくのがまずは取っ付きやすそうなので過去の日経平均株価を使って予測モデルを作って見た。

# ニューラルネットワークでの予測モデル構築
LSTMなりCNNなり色々試したいが、とりあえずシンプルなニューラルネットワークを作って様子を見てみる。
以下のステップでプログラムを作っていく。

1. データソースの用意
2. アウトプット形の検討
3. データクレンジング
4. 予測・評価

### 1.データソースの用意
これについては割と色々なところに落ちていたので適当なcsvをとってくる。
今回は[k-db.comさん](http://k-db.com/ "k-db.com")からデイリーのデータ(2016〜2017)をDLさせていただいた。
ひとまず適当なディレクトリに落としてきたcsvを置いて、pandasで読み込んでみる。

```py3
import pandas as pd
import os

def GetDataFrame(DIR):
    data = pd.DataFrame()
    csvs = [DIR+i for i in os.listdir(DIR)]
    csvs.reverse() #ファイル名を大きい順に変更する(2017〜2016)
    for csv in csvs:
        if csv.find('.DS_Store') == -1: #Mac OSXではよくわからない隠しファイルが生成される
            tmp = pd.read_csv(csv)
            data = data.append(tmp,ignore_index = True)
    return data

data =GetDataFrame(<適当なディレクトリ名>)
```
読み込んだデータフレームはこんな感じ

```
           date     start       max       min       end
0    2017-06-09  19953.06  20096.01  19927.07  20013.26
1    2017-06-08  20048.28  20061.22  19896.35  19909.26
2    2017-06-07  19951.68  20023.24  19908.07  19984.62
3    2017-06-06  20122.26  20152.95  19948.02  19979.90
4    2017-06-05  20135.42  20224.54  20104.13  20170.82
5    2017-06-02  19970.23  20239.81  19967.00  20177.28
6    2017-06-01  19692.16  19887.66  19686.32  19860.03
7    2017-05-31  19630.33  19673.51  19589.25  19650.57
8    2017-05-30  19681.11  19691.44  19570.13  19677.85
9    2017-05-29  19697.18  19736.62  19627.19  19682.57
10   2017-05-26  19798.49  19801.59  19686.49  19686.84
```

### 2. アウトプット形の検討
機械学習には2値分類や線形回帰など色々なアウトプット形がある。
単純に株価が上がる/下がるの2値分類をさせてやってもいいが、実際に使うところを想像すると「どのくらい株価が上がる/下がるのか」を見たいはずなので今回は線形回帰のアプローチを取ることにする。

ニューラルネットワーク(入力1→隠れ層2→出力1)を以下の様に構築する。

```py3
def BuildNeuralNetwork(input):
    model = Sequential()
    model.add(Dense(128, input_shape=(input,)))
    model.add(Activation('relu'))
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))
    model.add(Dense(1))
    model.add(Activation('linear'))
    model.compile(loss="mean_squared_error", optimizer=RMSprop())
    return model
```

### 3. データクレンジング
データを機械学習させるにはデータのオーダを揃えてやることが結構重要だったりする。
データの整形アプローチは主に以下の2パターンがある。

1. 「平均を0」、「分散を1」になるよう標準化する
2. 「データの幅を−１〜1」に収まるように標準化する

経験則上学習データは1のアプローチを取った方がうまくいくイメージがあるので学習データは1のアプローチを取ることにした。
正解データも同じように標準化してしまっても良いのだが、正解データに関しては上がった/下がったを正負で判定をしたいため、正解データのみ2のアプローチを使うことにした。

結果以下の様にデータクレンジングしてデータを構築した。

```py3
import numpy as np
from numpy.random import *
from scipy.stats import zscore

def MakeDataSet(nd_data):
    # 全体の20%をランダムにテストデータにする
    test_rate = int(len(nd_data)*0.2)
    test_index = np.unique(randint(0,len(nd_data),test_rate))
    test_index = test_index.astype(np.int64)
    train_index = np.array([])
    for i in range(len(nd_data)):
        if(len(np.where(test_index == i)[0]) == 0):
            train_index = np.append(train_index,i)
    train_index = train_index.astype(np.int64)
    return nd_data[train_index,0:4],nd_data[train_index,4:5],nd_data[test_index,0:4],nd_data[test_index,4:5]

data['diff'] = data['end'] - data['start'] #終値-開始値
data['predict_reg'] = 0
data['predict_reg'][1:]= data['diff'][0:-1] #１日後のdiffを正解データにする
use_data = data[1:] #0番目のデータは正解データがNULLになるので不採用

# 学習データは平均0、分散1になる様に標準化
use_data.loc[:,'start'] = zscore(use_data['start'])
use_data.loc[:,'max'] = zscore(use_data['max'])
use_data.loc[:,'min'] = zscore(use_data['min'])
use_data.loc[:,'end'] = zscore(use_data['end'])

# 正解データのみ最大値を用いて-1〜1の範囲に標準化
use_data.loc[:,'predict_reg'] /= max(use_data['predict_reg'])

nd_data = use_data[['start','max','min','end','predict_reg']].as_matrix()
x_train,x_test,y_train,y_test = MakeDataSet(nd_data)

```

### 4. 予測・評価
評価の仕方が色々ある気がするが今回は以下の様な方針で性能評価することにした。

1. 予測結果は回帰のため、モデルの評価はMSE
2. predictした結果はMSEではなく正解データと符号が一致しているかを一致率で評価

2についてはシステムの利用を想定した時に上がる/下がるの結果さえ合っていれば、最終的には問題ないのかなと考え上記の評価方法を採用した。

最終的なソースは以下の様な感じ。
学習率とかを見れる様にTensorboardを仕込んである。
ちなみにTensorBoardはこんな感じで使う。
```
$ tensorboard --logdir=./TensorBoard_log/
```
デフォルトだとローカルサーバ起動後`http://localhost:6006/`でアクセスできるはず。

```py3
import numpy as np
import pandas as pd
import os
from numpy.random import *
from scipy.stats import zscore

from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.callbacks import TensorBoard
from keras import backend as K
from keras.optimizers import RMSprop


def BuildNeuralNetwork(input):
    model = Sequential()
    model.add(Dense(128, input_shape=(input,)))
    model.add(Activation('relu'))
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))
    model.add(Dense(128))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))
    model.add(Dense(1))
    model.add(Activation('linear'))
    model.compile(loss="mean_squared_error", optimizer=RMSprop())
    return model

def MakeDataSet(nd_data):
    # 全体の20%をランダムにテストデータにする
    test_rate = int(len(nd_data)*0.2)
    test_index = np.unique(randint(0,len(nd_data),test_rate))
    test_index = test_index.astype(np.int64)
    train_index = np.array([])
    for i in range(len(nd_data)):
        if(len(np.where(test_index == i)[0]) == 0):
            train_index = np.append(train_index,i)
    train_index = train_index.astype(np.int64)
    return nd_data[train_index,0:4],nd_data[train_index,4:5],nd_data[test_index,0:4],nd_data[test_index,4:5]

def trainModel(model,train,answer,batchs,epochs):
    TensorBoard_cb = TensorBoard(log_dir="./TensorBoard_log/", histogram_freq=1)
    hist = model.fit(train, answer,batch_size=batchs,verbose=1,epochs=epochs,callbacks=[TensorBoard_cb])
    return hist

def GetDataFrame(DIR):
    data = pd.DataFrame()
    csvs = [DIR+i for i in os.listdir(DIR)] # use this for training images
    csvs.reverse()
    for csv in csvs:
        if csv.find('.DS_Store') == -1:
            tmp = pd.read_csv(csv)
            data = data.append(tmp,ignore_index = True)
    return data

BATCHES = 10
EPOCHS = 1000

data =GetDataFrame("./NikkeiAverageData/day/")
data['diff'] = data['end'] - data['start'] #終値-開始値
data['predict_reg'] = 0
data['predict_reg'][1:]= data['diff'][0:-1] #１日後のdiffを正解データにする
use_data = data[1:] #0番目のデータは正解データがNULLになるので不採用

# 学習データは平均0、分散1になる様に標準化
use_data.loc[:,'start'] = zscore(use_data['start'])
use_data.loc[:,'max'] = zscore(use_data['max'])
use_data.loc[:,'min'] = zscore(use_data['min'])
use_data.loc[:,'end'] = zscore(use_data['end'])

# 正解データのみ最大値を用いて-1〜1の範囲に標準化
use_data.loc[:,'predict_reg'] /= max(use_data['predict_reg'])

nd_data = use_data[['start','max','min','end','predict_reg']].as_matrix()
x_train,x_test,y_train,y_test = MakeDataSet(nd_data)

model = BuildNeuralNetwork(4)
trainModel(model,x_train,x_test,BATCHES,EPOCHS)
predicted = model.predict(y_train)
prediction_ratio = 0
for i in range(len(y_test)):
    if(predicted[i] >= 0):
        if (y_test[i] > 0):
            prediction_ratio += 1
        print("next day will increase by ",predicted[i]," --------",y_test[i])
    else:
        if (y_test[i] <= 0):
            prediction_ratio += 1
        print("next day will decrease by ",predicted[i]," --------",y_test[i])
print("Ratio is",((prediction_ratio/len(y_test)) * 100),"%")
K.clear_session()
```

結果
```
Ratio is 51.515151515151516 %
```
確率50%って感でやるのと変わらないやん。。。
そもそも1日のデータでは翌日の予測は難しいのであろう。
ニューラルネットそのものの問題もありそうだが、まずは予測しやすい形の学習データ構築について次回は検討する。