機械学習を使った株価予想（回帰編）

以前（[1](http://qiita.com/yai/items/5ec5bbfbcc53c9770c40),[2](http://qiita.com/yai/items/8c0ad280cc2f53e40256),[3](http://qiita.com/yai/items/1b7f8ef69f8f2343e3e9)）からの続き物。
分類ばかりでは面白くないので、回帰問題を試したい。
というわけで、今回は機械学習のフレームワークTensorFlowやscikit-learnを使用して株価を直接予想するという遊び。前は翌日の株価が「上がる or 下がる」の2択の分類でしたが、今回は「何円か」を直接予想します。
なお、入力に使うデータは使い回しです。毎回スミマセン。

##趣旨
- TensorFlowやscikit-learnを使用して、株価の予想をしてみる。
- 精度や使用感を確認する。

##やること
「数日分の世界中の株価指数（ダウ、日経平均、DAXなど）を使用して、次の日の日経平均株価を予想する」（回帰）

##環境
###scikit-learn
scikit-learn 0.17.1
Python 2.7
Windows 7
###TensorFlow
TensorFlow 0.7
Ubuntu 14.04
Python 2.7
AWS EC2 micro instance

##実装
[Quandl](https://www.quandl.com/data/YAHOO)というサイトから、日経、ダウ、香港ハンセン、ドイツの株価指数をダウンロード。テキストデータとして1つにまとめます。（手作業）

###ラベル
正解のデータとして、翌日の終値を使用します。（翌日の終値を予想する）
しかし、直接株価をラベルとしてつっこんでみたところ、ぶれっぶれで発散しまくりな結果となったため一工夫。予想する対象を「翌日の終値が前日より何パーセント上下するか」という間接的なものにし、後に計算し直して終値を求めることにします。後述する入力にも同様の変化率を使うので、理に適っている・・・ということにします。(・・;)

```
# JUDGE_DAY = 1, 第二添字の[3]は日経平均の終値が入っています。
y_array.append([(array_base[i][3] - array_base[i+JUDGE_DAY][3]) / array_base[i][3] * 100])
```

###入力データ
株価をそのまま入れるのではなく、「前日に比べてどれくらい（%）上下したか」のリストを与えています。（株価をそのまま入れても全然上手くいかなかったため）

```
tmp_array = []
for j in xrange(i+1, i + data_num + 1):
    for k in range(16):
        tmp_array.append((array_base[j][k] - array_base[j+1][k]) / array_base[j][k] * 100)
x_array.append(tmp_array)
```

###TensorFlow固有の話
####グラフ
TensorFlowは隠れ層2つ、ユニット数はそれぞれ50,25としています。

```
NUM_HIDDEN1 = 50
NUM_HIDDEN2 = 25
```

```
def inference(x_ph, keep_prob):

  with tf.name_scope('hidden1'):
    weights = tf.Variable(tf.truncated_normal([data_num * price_num, NUM_HIDDEN1], stddev=stddev), name='weights')
    biases = tf.Variable(tf.zeros([NUM_HIDDEN1]), name='biases')
    hidden1 = tf.nn.relu(tf.matmul(x_ph, weights) + biases)
  
  with tf.name_scope('hidden2'):
    weights = tf.Variable(tf.truncated_normal([NUM_HIDDEN1, NUM_HIDDEN2], stddev=stddev), name='weights')
    biases = tf.Variable(tf.zeros([NUM_HIDDEN2]), name='biases')
    hidden2 = tf.nn.relu(tf.matmul(hidden1, weights) + biases)
  
  #DropOut
  dropout = tf.nn.dropout(hidden2, keep_prob)
  
  with tf.name_scope('regression'):
    weights = tf.Variable(tf.truncated_normal([NUM_HIDDEN2, 1], stddev=stddev), name='weights')
    biases = tf.Variable(tf.zeros([1]), name='biases')
    y = tf.matmul(dropout, weights) + biases
  
  return y
```

####損失
損失の計算は[l2_loss()](https://www.tensorflow.org/versions/r0.7/api_docs/python/nn.html#l2_loss)を使用。
数値の差分が損失に当たるからコレが良いのかと勝手に思っていますが、正しいのか不明。「違うだろ」という方、コメントお待ちしています。

```
def loss(y, target):

  return tf.reduce_mean(tf.nn.l2_loss((y - target)))
```

####最適化
特筆することは無いですかね。
```
def optimize(loss):
  optimizer = tf.train.AdamOptimizer(learning_rate)
  train_step = optimizer.minimize(loss)
  return train_step
```

####訓練
こちらも特に特筆するところは無いですかね。

```
def training(sess, train_step, loss, x_train_array, y_flg_train_array):
  
  summary_op = tf.merge_all_summaries()
  init = tf.initialize_all_variables()
  sess.run(init)
  
  summary_writer = tf.train.SummaryWriter(LOG_DIR, graph_def=sess.graph_def)
  
  for i in range(int(len(x_train_array) / bach_size)):
    batch_xs = getBachArray(x_train_array, i * bach_size, bach_size)
    batch_ys = getBachArray(y_flg_train_array, i * bach_size, bach_size)
    sess.run(train_step, feed_dict={x_ph: batch_xs, y_ph: batch_ys, keep_prob: 0.8})
    ce = sess.run(loss, feed_dict={x_ph: batch_xs, y_ph: batch_ys, keep_prob: 1.0})

    summary_str = sess.run(summary_op, feed_dict={x_ph: batch_xs, y_ph: batch_ys, keep_prob: 1.0})
    summary_writer.add_summary(summary_str, i)
```
####評価
精度として、計算で求めた株価の変動率と、実際の変動率の差を求め、その絶対値の平均を出力しています。
つまり、平均誤差を出しているだけです。

```
accuracy = tf.reduce_mean(tf.abs(y - y_ph))
print "accuracy"
print(sess.run(accuracy, feed_dict={x_ph: test_batch_xs, y_ph: test_batch_ys, keep_prob: 1.0}))
```

###scikit-learn固有の話
####回帰アルゴリズム
色々アルゴリズムがあるのですが、・・・どれが最適なのかよく分からないので、とりあえず3つほどピックアップして引数無しで使うことにします。

```
# SGDRegressor
clf = linear_model.SGDRegressor()
testClf(clf, x_train_array, y_train_array, x_test_array, y_test_array)

# DecisionTreeRegressor
clf = tree.DecisionTreeRegressor()
testClf(clf, x_train_array, y_train_array, x_test_array, y_test_array)

# SVM
clf = svm.SVR()
testClf(clf, x_train_array, y_train_array, x_test_array, y_test_array)
```

####訓練、評価
訓練はfit()を実行するのみ。評価はscore()を実行してみたところ一部マイナス値になってしまい、どう判断していいのかよく分からなかったので（ご存知の方教えてください）、TensorFlowの時と同様、予測株価変化率を出して、実際の株価の変化率の差の絶対値の平均を取っています（要は平均誤差）。

```
def testClf(clf, x_train_array, y_flg_train_array, x_test_array, y_flg_test_array):

    print clf
    clf.fit(x_train_array, y_flg_train_array)
    result = clf.predict(x_test_array)
    print clf.score(x_test_array, y_flg_test_array)
    print np.mean(np.abs(np.array(result) - np.array(y_flg_test_array)))
```

##結果
TensorFlow

```
1.00044
```

scikit-learn

```
SGDRegressor: 0.943171296872
DecisionTreeRegressor: 1.3551351662
SVM: 0.945361479916
```
というわけで、概ね1%程度の誤差となりました。
株価の予想で1%の誤差って・・・、全く使えな・・・ゲフンゲフン。

##実際に予想
せっかくなので、実際に予測値を出してみましょう。
手元のデータが2016/03/24までのデータなので、2016/03/25の日経平均株価の終値を予想します。scikit-learnはSVMを使用します。
※数字の羅列になってしまうので、入力部分は省略します。どちらもdataに過去数日分の株価データが入っています。16892.33は3/24の終値です。

```lang:TensorFlow
p = sess.run(y, feed_dict={x_ph: data, keep_prob: 1.0})
price = ((p[0][0] / 100.) + 1.) * 16892.33
print price
```

```lang:scikit-learn
p = clf.predict(data)
price = ((p[0] / 100.) + 1.) * 16892.33
print price
```

###結果
```lang:TensorFlow
16804.3398821
```

```lang:scikit-learn
16822.6013292
```
で、実際の3/25の株価は・・・17,002.75。
**・・・まぁそんなものですよね。**

##考察
- 機械学習といっても、「なんでもつっこんで訓練してやれば機械が頑張って最適なものが出てくる」わけではないらしい。人間側も機械が考えやすく答えを出しやすいようにするにはどうすべきかを考える必要がありそう。

##所感
- 回帰問題楽しい。
- 分類のように、「全部最初のカテゴリ」とかやる気の無い回答にならないので、エラーが見つけやすい。
- 分類の時と多少コードは変わるけど、8割方使いまわせるので開発は楽。