今日も窓辺でプログラム

外資系企業勤めのエンジニアが勉強した内容をまとめておくブログ

日経平均の終値が前日より上がるか下がるかをTensorFlowで予測する(1)

この記事について

この記事は、
datalab/FinancialTimeSeriesTensorFlow.ipynb at master · corrieelston/datalab · GitHub
を日経平均に置き換えてなぞっていくシリーズの3つ目の投稿で、以下の2つの記事の続きです。
日経平均と相関のある経済指数は何か?(1) - 今日も窓辺でプログラム
日経平均と相関のある経済指数は何か?(2) - 今日も窓辺でプログラム

今回はいよいよ、TensorFlowを使ったDeep Learningで、日経平均の終値が前日より上がるか下がるかを予測してみる部分に取り組みます。
ちなみに、私は機械学習、Deep Learningはほぼ素人なのでそのつもりで読んでいただけると嬉しいです。

TensorFlowって?

TensorFlow とは、Googleによって公開されているオープンソースのDeep Learningのライブラリです。
日本語でもTensorFlowについて解説している記事は無数にあります。

他にもググるとたくさん。。


実際に使うには環境を構築しないといけないので、Download and Setup  |  TensorFlow ページ通りに準備します。多分、ググると日本語で解説しているブログも出てくるかと思います。

ちなみに、Deep Learningがどんなものかは、私はこの本で勉強しましたので、詳細はこの本に譲ります。。
https://www.amazon.co.jp/dp/B018K6C99A/

下準備

使用するデータ

冒頭で述べた通り、日経平均の終値が前日より上がったか下がったかを予測します。つまり、日経平均の終値で計算した対数リターンが正になるか負になるかを予測します。
予測するのに用いるデータは、前回集めた世界のマーケットの指数(の対数リターン)です。日経平均はほかの市場より開くのが早いので、他の市場の情報は前日のものしか使用できません。

ということで、正解データとして、日経平均の対数リターンが正になったフラグを"N225_positive"、負になったフラグを"N225_negative"として、それぞれ正と負になった時に1、そうでないときに0となるようにデータを加工します。
また、前日のマーケットのデータは"DJI_1"のように、指数と一日前を表す"_1"を付けて持っておくことにします。
コードにすると、こんな感じになります。

s = StockData()
s.download()
closing_data = s.get_closing_data(1000, True, True)

closing_data["N225_positive"] = 0
closing_data.ix[closing_data["N225"] >= 0, "N225_positive"] = 1
closing_data["N225_negative"] = 0
closing_data.ix[closing_data["N225"] < 0, "N225_negative"] = 1

training_test_data = pd.DataFrame(
    # column name is "<index>_<day>".
    # E.g., "DJI_1" means yesterday's Dow.
    columns= ["N225_positive", "N225_negative"] + [s + "_1" for s in INDEX_LIST[1:]]
)

for i in range(7, len(closing_data)):
    data = {}
    # We will use today's data for positive/negative labels
    data["N225_positive"] = closing_data["N225_positive"].ix[i]
    data["N225_negative"] = closing_data["N225_negative"].ix[i]
    # Use yesterday's data for world market data
    for col in INDEX_LIST[1:]:
        data[col + "_1"] = closing_data[col].ix[i - 1]
    training_test_data = training_test_data.append(data, ignore_index=True)

訓練データとテストデータに分割

次に、準備したデータを訓練データとテストデータに8:2の割合で分けます。これは、元記事のコードそのままです。

predictors_tf = training_test_data[training_test_data.columns[2:]]
classes_tf = training_test_data[training_test_data.columns[:2]]

training_set_size = int(len(training_test_data) * 0.8)
test_set_size = len(training_test_data) - training_set_size

training_predictors_tf = predictors_tf[:training_set_size]
training_classes_tf = classes_tf[:training_set_size]
test_predictors_tf = predictors_tf[training_set_size:]
test_classes_tf = classes_tf[training_set_size:]

TensorFlowで実際に予測してみる

この部分も元の記事そのままのコードですが、自分なりに何をやっているのか解釈しながらコードを読み進めていきたいと思います。

まず初めに、セッションと呼ばれるオブジェクトを用意します。セッションとは、グラフのノードを割り当てて計算をする環境のようなもののようです。

sess = tf.Session()

次に、計算結果に使う値を入れる入れ物(feature_dataとactual_classes)を用意します。th.placeholderというのが値の入れ物、変数のようなもので、実際に計算を走らせるときにplaceholderに値を入れてあげます。

num_predictors = len(training_predictors_tf.columns)
num_classes = len(training_classes_tf.columns)

feature_data = tf.placeholder("float", [None, num_predictors])
actual_classes = tf.placeholder("float", [None, num_classes])

ネットワークの重みを初期化する。標準偏差を指定すると、ランダムに指定してくれるようです。

weights = tf.Variable(tf.truncated_normal([num_predictors, num_classes], stddev=0.0001))
biases = tf.Variable(tf.ones([num_classes]))

ここが実際に今回訓練するモデルになります。ソフトマックス回帰と呼ばれるモデルで、1層だけのネットワークの出力にソフトマックス関数を噛ませたものです。
なので今回のモデルはDeep Learningでもなんでもなくて、ただのニューラルネットですね。

model = tf.nn.softmax(tf.matmul(feature_data, weights) + biases)

コスト関数を定義しています。クロスエントロピーというものを使用しているようです。

cost = -tf.reduce_sum(actual_classes*tf.log(model))

ネットワークを最適化していくトレーニングステップをを定義しています。今回は、Adam法というを指定して、重みは0.0001としているようです。
Adam論文概要とコード - Qiita

training_step = tf.train.AdamOptimizer(learning_rate=0.0001).minimize(cost)

最後に実際に値を初期化して準備完了です。

init = tf.initialize_all_variables()
sess.run(init)

では、実際に走らせてみます。sess.run()を使ってネットワークの重みを更新します。更新方法は先ほど定義したtraning_stepに、用意した対数リターンとそのラベルを与えてあげます。feed_dictの値が、placeholderに渡される形になります。

for i in range(1, 30001):
  sess.run(
    training_step,
    feed_dict={
      feature_data: training_predictors_tf.values,
      actual_classes: training_classes_tf.values.reshape(len(training_classes_tf.values), 2)
    }
  )

その後、テストデータで性能を評価したところ、以下のようになりました。
Precision = 0.7111111111111111
Recall = 0.3333333333333333
F1 Score = 0.45390070921985815
Accuracy = 0.6130653266331658

この評価の部分のコードに関しては、元の記事を参照してください。英語ですが、Python NotebookのIn[22]で定義されている関数を使ってIn [25]で実際に評価の計算をしています。

まとめ

今回は、日経平均の終値が前日より上がるか下がるかを、各マーケットの指数の対数リターンを用いて予測しました。
予測はTensorFlowを使ったニューラルネットの実装をして行い、その結果6割程度の精度を得られました。

次回は、このネットワークに隠れ層を導入すると、結果がどのように変わるのか見てみたいと思います。



次回記事:
日経平均の終値が前日より上がるか下がるかをTensorFlowで予測する(2) - 今日も窓辺でプログラム