使用RNN預測股票價格系列一

概述

我們將解釋如何建立一個有LSTM單元的RNN模型來預測S&P500指數(shù)的價格甩栈。 數(shù)據(jù)集可以從Yahoo!下載欣孤。 在例子中郎任,使用了從1950年1月3日(Yahoo! Finance可以追溯到的最大日期)的S&P 500數(shù)據(jù)到2017年6月23日史翘。 為了簡單起見颖医,我們只使用每日收盤價進行預測。 同時裆蒸,我將演示如何使用TensorBoard輕松調試和模型跟蹤熔萧。

關于RNN和LSTM

RNN的目的使用來處理序列數(shù)據(jù)。在傳統(tǒng)的神經(jīng)網(wǎng)絡模型中僚祷,是從輸入層到隱含層再到輸出層佛致,層與層之間是全連接的,每層之間的節(jié)點是無連接的辙谜。但是這種普通的神經(jīng)網(wǎng)絡對于很多問題卻無能無力俺榆。例如,你要預測句子的下一個單詞是什么装哆,一般需要用到前面的單詞罐脊,因為一個句子中前后單詞并不是獨立的定嗓。RNN之所以稱為循環(huán)神經(jīng)網(wǎng)路,即一個序列當前的輸出與前面的輸出也有關萍桌。具體的表現(xiàn)形式為網(wǎng)絡會對前面的信息進行記憶并應用于當前輸出的計算中宵溅,即隱藏層之間的節(jié)點不再無連接而是有連接的,并且隱藏層的輸入不僅包括輸入層的輸出還包括上一時刻隱藏層的輸出上炎。理論上恃逻,RNN能夠對任何長度的序列數(shù)據(jù)進行處理。

Long Short Term 網(wǎng)絡藕施,一般就叫做 LSTM寇损,是一種 RNN 特殊的類型,LSTM區(qū)別于RNN的地方裳食,主要就在于它在算法中加入了一個判斷信息有用與否的“處理器”矛市,這個處理器作用的結構被稱為cell。一個cell當中被放置了三扇門胞谈,分別叫做輸入門尘盼、遺忘門和輸出門。一個信息進入LSTM的網(wǎng)絡當中烦绳,可以根據(jù)規(guī)則來判斷是否有用卿捎。只有符合算法認證的信息才會留下,不符的信息則通過遺忘門被遺忘径密。說起來無非就是一進二出的工作原理午阵,卻可以在反復運算下解決神經(jīng)網(wǎng)絡中長期存在的大問題。目前已經(jīng)證明享扔,LSTM是解決長序依賴問題的有效技術底桂,并且這種技術的普適性非常高,導致帶來的可能性變化非常多惧眠。各研究者根據(jù)LSTM紛紛提出了自己的變量版本籽懦,這就讓LSTM可以處理千變萬化的垂直問題。

數(shù)據(jù)準備

股票價格是長度為NN氛魁,定義為p0暮顺,p1,...秀存,pN-1捶码,其中pi是第i天的收盤價,0≤i<N或链。 我們有一個大小固定的移動窗口w(后面我們將其稱為input_size)惫恼,每次我們將窗口向右移動w個單位,以使所有移動窗口中的數(shù)據(jù)之間沒有重疊澳盐。


我們使用一個移動窗口中的內容來預測下一個祈纯,而在兩個連續(xù)的窗口之間沒有重疊令宿。

我們將建立RNN模型將LSTM單元作為基本的隱藏單元。 我們使用此值從時間t內將第一個移動窗口W0移動到窗口Wt:

預測價格在下一個窗口在Wt+1

我們試圖學習一個近似函數(shù)盆繁,



展開的RNN

考慮反向傳播(BPTT)是如何工作的掀淘,我們通常將RNN訓練成一個“unrolled”的樣式,這樣我們就不需要做太多的傳播計算油昂,而且可以節(jié)省訓練的復雜性革娄。

以下是關于Tensorflow教程中input_size的解釋:

By design, the output of a recurrent neural network (RNN) depends on arbitrarily distant inputs. Unfortunately, this makes backpropagation computation difficult. In order to make the learning process tractable, it is common practice to create an “unrolled” version of the network, which contains a fixed number (num_steps) of LSTM inputs and outputs. The model is then trained on this finite approximation of the RNN. This can be implemented by feeding inputs of length num_steps at a time and performing a backward pass after each such input block.

價格的順序首先被分成不重疊的小窗口。 每個窗口都包含input_size數(shù)字冕碟,每個數(shù)字被視為一個獨立的輸入元素拦惋。 然后,任何num_steps連續(xù)的輸入元素被分配到一個訓練輸入中安寺,形成一個訓練

在Tensorfow上的“unrolled”版本的RNN厕妖。 相應的標簽就是它們后面的輸入元素。
例如挑庶,如果input_size = 3和num_steps = 2言秸,我們的第一批的訓練樣例如下所示:



以下是數(shù)據(jù)格式化的關鍵部分:

seq = [np.array(seq[i * self.input_size: (i + 1) * self.input_size])       for i in range(len(seq) // self.input_size)]    
# Split into groups of `num_steps`

X = np.array([seq[i: i + self.num_steps] for i in range(len(seq) - self.num_steps)])
y = np.array([seq[i + self.num_steps] for i in range(len(seq) - self.num_steps)])

培訓/測試拆分

由于我們總是想預測未來,我們以最新的10%的數(shù)據(jù)作為測試數(shù)據(jù)迎捺。

正則化

標準普爾500指數(shù)隨著時間的推移而增加举畸,導致測試集中大部分數(shù)值超出訓練集的范圍,因此模型必須預測一些以前從未見過的數(shù)字凳枝。 但這卻不是很理想抄沮。

為了解決樣本外的問題,我們在每個移動窗口中對價格進行了標準化岖瑰。 任務變成預測相對變化率而不是絕對值叛买。 在t時刻的標準化滑動窗口W't中,所有的值除以最后一個未知價格 Wt-1中的最后一個價格:

建立模型

定義

  • lstm_size:一個LSTM圖層中的單元數(shù)蹋订。
  • num_layers:堆疊的LSTM層的數(shù)量率挣。
  • keep_prob:單元格在 dropout 操作中保留的百分比。
  • init_learning_rate:開始學習的速率露戒。
  • learning_rate_decay:后期訓練時期的衰減率难礼。
  • init_epoch:使用常量init_learning_rate的時期數(shù)。
  • max_epoch:訓練次數(shù)在訓練中的總數(shù)
  • input_size:移動窗口的大小/一個訓練數(shù)據(jù)點
  • batch_size:在一個小批量中使用的數(shù)據(jù)點的數(shù)量玫锋。

The LSTM model has num_layers stacked LSTM layer(s) and each layer contains lstm_sizenumber of LSTM cells. Then a dropout mask with keep probability keep_prob is applied to the output of every LSTM cell. The goal of dropout is to remove the potential strong dependency on one dimension so as to prevent overfitting.

*T
he training requires max_epoch epochs in total; an epoch is a single full pass of all the training data points. In one epoch, the training data points are split into mini-batches of size batch_size. We send one mini-batch to the model for one BPTT learning. The learning rate is set to init_learning_rate during the first init_epoch epochs and then decay

by learning_rate_decay during every succeeding epoch.???*

# Configuration is wrapped in one object for easy tracking and passing.
class RNNConfig():
   input_size=1
   num_steps=30
   lstm_size=128
   num_layers=1
   keep_prob=0.8
   batch_size = 64
   init_learning_rate = 0.001
   learning_rate_decay = 0.99
   init_epoch = 5
   max_epoch = 50

config = RNNConfig()

定義圖形

(1) Initialize a new graph first.

import tensorflow as tf
tf.reset_default_graph()
lstm_graph = tf.Graph()

(2) How the graph works should be defined within its scope.

with lstm_graph.as_default():

(3) Define the data required for computation. Here we need three input variables, all defined as

tf.placeholder

because we don’t know what they are at the graph construction stage.

  • inputs:
    the training data X, a tensor of shape (# data examples, num_steps, input_size); the number of data examples is unknown, so it is None. In our case, it would be batch_sizein training session. Check the input format example if confused.
  • targets:
    the training label y, a tensor of shape (# data examples, input_size).
  • learning_rate:
    a simple float.
# Dimension = (
   #     number of data examples, 
   #     number of input in one computation step, 
   #     number of numbers in one input
   # )
   # We don't know the number of examples beforehand, so it is None.
   inputs = tf.placeholder(tf.float32, [None, config.num_steps, config.input_size])
   targets = tf.placeholder(tf.float32, [None, config.input_size])
   learning_rate = tf.placeholder(tf.float32, None)

(4) This function returns one

LSTMCell

with or without dropout operation.

def _create_one_cell():
   return tf.contrib.rnn.LSTMCell(config.lstm_size, state_is_tuple=True)
   if config.keep_prob < 1.0:
           return tf.contrib.rnn.DropoutWrapper(lstm_cell, output_keep_prob=config.keep_prob)

(5) Let’s stack the cells into multiple layers if needed.

MultiRNNCell

helps connect sequentially multiple simple cells to compose one cell.

cell = tf.contrib.rnn.MultiRNNCell(
       [_create_one_cell() for _ in range(config.num_layers)], 
       state_is_tuple=True
   ) if config.num_layers > 1 else _create_one_cell()

(6)
tf.nn.dynamic_rnn
constructs a recurrent neural network specified by cell (RNNCell). It returns a pair of (model outpus, state), where the outputs val is of size (batch_size, num_steps, lstm_size) by default. The state refers to the current state of the LSTM cell, not consumed here.

val, _ = tf.nn.dynamic_rnn(cell, inputs, dtype=tf.float32)

(7)

tf.transpose
converts the outputs from the dimension (batch_size, num_steps, lstm_size) to (num_steps, batch_size, lstm_size). Then the last output is picked.

# Before transpose, val.get_shape() = (batch_size, num_steps, lstm_size)
# After transpose, val.get_shape() = (num_steps, batch_size, lstm_size)
val = tf.transpose(val, [1, 0, 2])
# last.get_shape() = (batch_size, lstm_size)
ast = tf.gather(val, int(val.get_shape()[0]) - 1, name="last_lstm_output")

(8) Define weights and biases between the hidden and output layers.

weight = tf.Variable(tf.truncated_normal([config.lstm_size, config.input_size]))
bias = tf.Variable(tf.constant(0.1, shape=[targets_width]))
prediction = tf.matmul(last, weight) + bias

(9) We use mean square error as the loss metric and
the RMSPropOptimizer algorithm
for gradient descent optimization.

loss = tf.reduce_mean(tf.square(prediction - targets))
optimizer = tf.train.RMSPropOptimizer(learning_rate)
minimize = optimizer.minimize(loss)

開始訓練過程

(1) To start training the graph with real data, we need to start a tf.session
first.

with tf.Session(graph=lstm_graph) as sess:

(2) Initialize the variables as defined.

tf.global_variables_initializer().run()

(0) The learning rates for training epochs should have been precomputed beforehand. The index refers to the epoch index.

learning_rates_to_use = [
   config.init_learning_rate * (
       config.learning_rate_decay ** max(float(i + 1 - config.init_epoch), 0.0)
   ) for i in range(config.max_epoch)]

(3) Each loop below completes one epoch training.

for epoch_step in range(config.max_epoch):
   current_lr = learning_rates_to_use[epoch_step]
       
   # Check https://github.com/lilianweng/stock-rnn/blob/master/data_wrapper.py
   # if you are curious to know what is StockDataSet and how generate_one_epoch() 
   # is implemented.
   for batch_X, batch_y in stock_dataset.generate_one_epoch(config.batch_size):
       train_data_feed = {
           nputs: batch_X, 
           targets: batch_y, 
           learning_rate: current_lr
       }
       train_loss, _ = sess.run([loss, minimize], train_data_feed)

(4) Don’t forget to save your trained model at the end.

saver.save(sess, "your_awesome_model_path_and_name", global_step=max_epoch_step)

使用TensorBoard

在沒有可視化的情況下構建圖形就像在黑暗中繪制,非常模糊和容易出錯讼呢。 Tensorboard提供了圖形結構和學習過程的簡單可視化撩鹿。 看看下面這個案例,非常實用:

Brief Summary

  • Use with [tf.name_scope]
    (https://www.tensorflow.org/api_docs/python/tf/name_scope)("your_awesome_module_name")
    : to wrap elements working on the similar goal together.
  • Many tf.*
    methods accepts
    name=
    argument. Assigning a customized name can make your life much easier when reading the graph.
  • Methods like
    tf.summary.scalar
    and
    tf.summary.histogram
    help track the values of variables in the graph during iterations.
  • In the training session, define a log file using
    tf.summary.FileWriter.
with tf.Session(graph=lstm_graph) as sess:
   merged_summary = tf.summary.merge_all()
   writer = tf.summary.FileWriter("location_for_keeping_your_log_files", sess.graph)
   writer.add_graph(sess.graph)

Later, write the training progress and summary results into the file.

_summary = sess.run([merged_summary], test_data_feed)
writer.add_summary(_summary, global_step=epoch_step)  # epoch_step in range(config.max_epoch)


結果

我們在例子中使用了以下配置悦屏。

num_layers=1
keep_prob=0.8
batch_size = 64
init_learning_rate = 0.001
learning_rate_decay = 0.99
init_epoch = 5
max_epoch = 100
num_steps=30

總的來說預測股價并不是一件容易的事情节沦。 特別是在正則化后键思,價格趨勢看起來非常嘈雜。

測試數(shù)據(jù)中最近200天的預測結果甫贯。 模型是用 input_size= 1 和 lstm_size= 32 來訓練的吼鳞。


image.png

測試數(shù)據(jù)中最近200天的預測結果。 模型是用 input_size= 1 和 lstm_size= 128 來訓練的叫搁。


image.png

測試數(shù)據(jù)中最近200天的預測結果赔桌。 模型是用 input_size= 5 和 lstm_size= 128 來訓練的。

image.png

代碼:

stock-rnn/main.py

import os
import pandas as pd
import pprint

import tensorflow as tf
import tensorflow.contrib.slim as slim

from data_model import StockDataSet
from model_rnn import LstmRNN

flags = tf.app.flags
flags.DEFINE_integer("stock_count", 100, "Stock count [100]")
flags.DEFINE_integer("input_size", 5, "Input size [5]")
flags.DEFINE_integer("num_steps", 30, "Num of steps [30]")
flags.DEFINE_integer("num_layers", 1, "Num of layer [1]")
flags.DEFINE_integer("lstm_size", 128, "Size of one LSTM cell [128]")
flags.DEFINE_integer("batch_size", 64, "The size of batch images [64]")
flags.DEFINE_float("keep_prob", 0.8, "Keep probability of dropout layer. [0.8]")
flags.DEFINE_float("init_learning_rate", 0.001, "Initial learning rate at early stage. [0.001]")
flags.DEFINE_float("learning_rate_decay", 0.99, "Decay rate of learning rate. [0.99]")
flags.DEFINE_integer("init_epoch", 5, "Num. of epoches considered as early stage. [5]")
flags.DEFINE_integer("max_epoch", 50, "Total training epoches. [50]")
flags.DEFINE_integer("embed_size", None, "If provided, use embedding vector of this size. [None]")
flags.DEFINE_string("stock_symbol", None, "Target stock symbol [None]")
flags.DEFINE_integer("sample_size", 4, "Number of stocks to plot during training. [4]")
flags.DEFINE_boolean("train", False, "True for training, False for testing [False]")

FLAGS = flags.FLAGS

pp = pprint.PrettyPrinter()

if not os.path.exists("logs"):
   os.mkdir("logs")


def show_all_variables():
   model_vars = tf.trainable_variables()
   slim.model_analyzer.analyze_vars(model_vars, print_info=True)


def load_sp500(input_size, num_steps, k=None, target_symbol=None, test_ratio=0.05):
   if target_symbol is not None:
       return [
           StockDataSet(
               target_symbol,
               input_size=input_size,
               num_steps=num_steps,
               test_ratio=test_ratio)
       ]

   # Load metadata of s & p 500 stocks
   info = pd.read_csv("data/constituents-financials.csv")
   info = info.rename(columns={col: col.lower().replace(' ', '_') for col in info.columns})
   info['file_exists'] = info['symbol'].map(lambda x: os.path.exists("data/{}.csv".format(x)))
   print info['file_exists'].value_counts().to_dict()

   info = info[info['file_exists'] == True].reset_index(drop=True)
   info = info.sort('market_cap', ascending=False).reset_index(drop=True)

   if k is not None:
       info = info.head(k)

   print "Head of S&P 500 info:\n", info.head()

   # Generate embedding meta file
   info[['symbol', 'sector']].to_csv(os.path.join("logs/metadata.tsv"), sep='\t', index=False)

   return [
       StockDataSet(row['symbol'],
                    input_size=input_size,
                    num_steps=num_steps,
                    test_ratio=0.05)
       for _, row in info.iterrows()]


def main(_):
   pp.pprint(flags.FLAGS.__flags)

   # gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.333)
   run_config = tf.ConfigProto()
   run_config.gpu_options.allow_growth = True

   with tf.Session(config=run_config) as sess:
       rnn_model = LstmRNN(
           sess,
           FLAGS.stock_count,
           lstm_size=FLAGS.lstm_size,
           num_layers=FLAGS.num_layers,
           num_steps=FLAGS.num_steps,
           input_size=FLAGS.input_size,
           keep_prob=FLAGS.keep_prob,
           embed_size=FLAGS.embed_size,
       )

       show_all_variables()

       stock_data_list = load_sp500(
           FLAGS.input_size,
           FLAGS.num_steps,
           k=FLAGS.stock_count,
           target_symbol=FLAGS.stock_symbol,
       )

       if FLAGS.train:
           rnn_model.train(stock_data_list, FLAGS)
       else:
           if not rnn_model.load()[0]:
               raise Exception("[!] Train a model first, then run test mode")


if __name__ == '__main__':
   tf.app.run()

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末渴逻,一起剝皮案震驚了整個濱河市疾党,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惨奕,老刑警劉巖雪位,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異梨撞,居然都是意外死亡雹洗,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門卧波,熙熙樓的掌柜王于貴愁眉苦臉地迎上來时肿,“玉大人,你說我怎么就攤上這事幽勒∈任辏” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵啥容,是天一觀的道長锈颗。 經(jīng)常有香客問我,道長咪惠,這世上最難降的妖魔是什么击吱? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮遥昧,結果婚禮上覆醇,老公的妹妹穿的比我還像新娘。我一直安慰自己炭臭,他們只是感情好永脓,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鞋仍,像睡著了一般常摧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天落午,我揣著相機與錄音谎懦,去河邊找鬼。 笑死溃斋,一個胖子當著我的面吹牛界拦,可吹牛的內容都是我干的。 我是一名探鬼主播梗劫,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼享甸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了在跳?” 一聲冷哼從身側響起枪萄,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猫妙,沒想到半個月后瓷翻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡割坠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年齐帚,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片彼哼。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡对妄,死狀恐怖,靈堂內的尸體忽然破棺而出敢朱,到底是詐尸還是另有隱情剪菱,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布拴签,位于F島的核電站孝常,受9級特大地震影響,放射性物質發(fā)生泄漏蚓哩。R本人自食惡果不足惜构灸,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岸梨。 院中可真熱鬧喜颁,春花似錦、人聲如沸曹阔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赃份。三九已至稿茉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背漓库。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留园蝠,地道東北人渺蒿。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像彪薛,于是被迫代替她去往敵國和親茂装。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容