本文介绍了如何使用LSTM(长短期记忆网络)构建模型,通过社交媒体StockTwits上的评论来预测股票市场是熊市还是牛市。首先通过数据预处理和编码,然后将数据输入LSTM网络进行训练,使用dropout防止过拟合。最后,测试模型性能,结果表明模型在测试集上达到了72%的准确率。文章还提供了模型的建立、优化和测试的具体实现代码,并探讨了进一步提高模型性能的方法。
(本文独家发布在金蝶云社区上)
上一篇主要介绍了LSTM网络的基本架构,这一篇将用LSTM作为building Blocks搭建模型来预测股票市场是熊市还是牛市。
模型建立
你可以通过github的rep来访问相关源代码。所有的操作指令都在这个repo下的README上。
StockTwits $SPY消息数据集
我们将基于对股票市场的评论来训练我们的LSTM模型来预测个人情绪。
我们将从StockTwits.com网站上面抓取包含$SPY标签(即”S&P” 500指数基金)的评论数据。StockTwits是为交易员,投资者提供的分享关于股票行情观点的社交网站。当一个用户发信息的时候,他可以贴上相关的股票标签(我们的例子里是$SPY),还可以选择另外一个标签,即当他认为这个股票将要上涨,他将选择“bullish”标签,反之,他将选择“bearish”表掐吧,图三就是就显示了一个实例。
为建模处理数据
在我们建立模型前,我们必须先做一点数据预处理的工作。 项目的Github上的util.py模块专门用来预处理和批处理数据。这些函数抽象出了我们文章中大量的数据工作。但是我还是建议你通读一下util.py的代码,以更好的理解数据预处理具体是如何实现的。
首先,我们需要预处理消息数据以规范化已知的StockTwits实体,例如对股票代码,用户,链接或对数字的引用的引用。 在这里,我们还删除标点符号。
下一步,我们再对消息和情绪数据编码。情绪可以简单得为bearish(熊市)消息编码为0,为bullish(牛市)消息编码为1。至于为了编码消息数据,我们首先需要有一个包括消息里所有单词的词典库,然后把消息里的每一个单词映射成单词在词典里的索引值,这个索引值起始位置是1。这里请注意,在实践中,你只应该使用训练数据中的词典,但是现在为了简化和演示,我们将从整个数据集中创建词典。一旦我们有了这个映射,我们将用适当的索引替换每个消息中的每个单词。
我们需要所有的输入数据的长度是一致的,因此我们需要找到数据集里最长的那条数据并且将其他短一点的数据用0补齐。在我们的例子中,最长的序列是244。
最后,我们将数据集分割成训练集,验证集和测试集。
网络输入
当建立一个神经网络的时候,我们首先要做的就是定义我们的网络输入的数据。在这里,我们简单地定义一个函数来为我们的消息序列和标签定义TensorFlow Placeholders,再建立一个叫做”keep_prob_”变量来实现dropout层(这个dropout待会儿再详细说一下)。
def model_inputs(): """ Create the model inputs """ inputs_ = tf.placeholder(tf.int32, [None, None], name='inputs') labels_ = tf.placeholder(tf.int32, [None, None], name='labels') keep_prob_ = tf.placeholder(tf.float32, name='keep_prob')
简单来说,TensorFlow placeholders是一种数据管道,用来在实际运行系统的再输入数据。
Embedding layer(嵌入层)
下一步,我们定义一个函数来实现Embedding layer(嵌入层)。在TensorFlow中,word embeddings是一个行为字典,列为embeddings的矩阵(看图4)。矩阵中的每一个值都是在训练过程中需要被学习的。
Emdeding查询只是一个基于当前单词的索引的矩阵的简单查询。
def build_embedding_layer(inputs_, vocab_size, embed_size): """ Create the embedding layer """ embedding = tf.Variable(tf.random_uniform((vocab_size, embed_size), -1, 1)) embed = tf.nn.embedding_lookup(embedding, inputs_)
得到的word embeding向量是我们的分布式表示 - 即,将要传递给LSTM层的当前单词的多维向量量化表示。
LSTM层
我们将建立一个函数来动态得处理层数和规模。这个函数输入一个list,list的长度决定了LSTM网络中的层数。(比如没,我们的例子中的list的长度是2,分别是128和64,表明了这是一个两层的LSTM网络,分别的规模是128和64, 即第一层有128个隐藏单元,第二层有64个隐藏单元)
首先,我们用TensorFlow contrib API的BasicLSTMCell来简历LSTM层,并将每一层包裹在dropout层中。
请注意,我们在这里使用BasicLSTMCell仅用于说明的目的。在实际开发中有更多高效的方法可以定义我们的网络,例如tf.contrib.cudnn_rnn.CudnnCompatibleLSTMCell,它比BasicLSTMCell快20倍,并且使用的内存减少3到4倍。
Dropout是深度学习中使用的正则化技术,其中任何单个节点在给定的训练迭代期间具有“退出”网络的概率。 在解决实际问题时使用这些正则化是一种很好的做法。这些技术通过确保模型不太依赖于任何给定节点,使模型更加通用于训练数据以外的数据,即解决过拟合的问题。
def build_lstm_layers(lstm_sizes, embed, keep_prob_, batch_size): """ Create the LSTM layers """ lstms = [tf.contrib.rnn.BasicLSTMCell(size) for size in lstm_sizes] # Add dropout to the cell drops = [tf.contrib.rnn.DropoutWrapper(lstm, output_keep_prob=keep_prob_) for lstm in lstms] # Stack up multiple LSTM layers, for deep learning cell = tf.contrib.rnn.MultiRNNCell(drops) # Getting an initial state of all zeros initial_state = cell.zero_state(batch_size, tf.float32) lstm_outputs, final_state = tf.nn.dynamic_rnn(cell, embed, initial_state=initial_state)
然后将这个包裹着dropout层的LSTM网络传递给TensorFlow的MultiRNN单元,来将他们堆叠起来。最后,我们创建一个初始零状态并传递我们的堆叠LSTM层,以及来自先前定义的embeding层输入数据,还有初始状态来创建网络。然后Tensorflow dynamic_rnn返回模型输出和最终状态,我们需要在训练期间在批次之间传递这些状态。
损失函数,优化器以及准确度
最后,我们将为模型编写损失函数,相应的优化器以及精确度显示方面的代码。尽管损失量化和准确性只是基于结果的计算,但是TensorFlow中所有的内容都是计算图(Computational Graph)的一部分。因此,我们需要在计算图的上下文中定义损失函数,优化器和精度节点。
def build_cost_fn_and_opt(lstm_outputs, labels_, learning_rate): """ Create the Loss function and Optimizer """ predictions = tf.contrib.layers.fully_connected(lstm_outputs[:, -1], 1, activation_fn=tf.sigmoid) loss = tf.losses.mean_squared_error(labels_, predictions) optimzer = tf.train.AdadeltaOptimizer (learning_rate).minimize(loss) def build_accuracy(predictions, labels_): """ Create accuracy """ correct_pred = tf.equal(tf.cast(tf.round(predictions), tf.int32), labels_) accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))
首先,我们通过TensorFlow完全连接层(Full Connected Layer)将LSTM层的最终输出传递给sigmoid激活函数来得到我们的预测。回想一下前面的内容,LSTM层可以输出序列中所有单词的结果,即序列中每个单词都有个相应的模型输出。 但是,我们只想要最终输出来进行预测。我们使用上面演示代码的[:,-1]索引来解决这个问题,并通过一个完全连接的层将其传递给sigmoid激活以获得我们的预测。 这些预测被馈送到我们的均方误差损失函数里,并使用Adadelta优化器来最小化损失函数。最后,我们定义了相应的准确度指标,用于评估我们的训练集,验证集和测试集的对于模型的性能。
建立计算图和训练
现在我们已经定义了很多函数来创建计算图的各个部分,我们再定义一个函数来将计算图统一起来并训练模型。首先,我们调用我们之前定义的每个函数来构建网络,并调用TensorFlow sess以使用小批量的训练数据在预定义的训练周期中训练模型。在每个周期结束时,我们将打印当前损失数值,以及训练集的准确性和验证集的准确性,以便在我们训练模型时监控结果。
def build_and_train_network(lstm_sizes, vocab_size, embed_size, epochs, batch_size, learning_rate, keep_prob, train_x, val_x, train_y, val_y): # Build Graph with tf.Session() as sess: # Train Network # Save Network
下一步,我们来定义模型的超参数。我们已经建立了两层的LSTM网络,相应的每一层的隐藏单元有128个,以及64个。我们用了有300个单元的embeding层,训练周期为50个,小的批量(mini-batches)是256。初始的学习率(learning rate)为0.1,虽然我们的Adadelta optimizer将会50%的概率随着时间去改变它。
下面的输出显示我们的LSTM网络刚开始的时候学习速度非常快,从第一个epoch后的58%的验证集的准确度上升到第10个epoch后的66%的验证集准确度。 然后学习开始逐渐变慢,在仅仅50个训练周期后,最终达到75%的训练集准确率和72%的验证集准确度。当模型完成训练时,我们使用TensorFlow Saver来存储训练出来的模型参数供以后使用。
Epoch: 1/50... Batch: 303/303... Train Loss: 0.247... Train Accuracy: 0.562... Val Accuracy: 0.578 Epoch: 2/50... Batch: 303/303... Train Loss: 0.245... Train Accuracy: 0.583... Val Accuracy: 0.596 Epoch: 3/50... Batch: 303/303... Train Loss: 0.247... Train Accuracy: 0.597... Val Accuracy: 0.617 Epoch: 4/50... Batch: 303/303... Train Loss: 0.240... Train Accuracy: 0.610... Val Accuracy: 0.627 Epoch: 5/50... Batch: 303/303... Train Loss: 0.238... Train Accuracy: 0.620... Val Accuracy: 0.632 Epoch: 6/50... Batch: 303/303... Train Loss: 0.234... Train Accuracy: 0.632... Val Accuracy: 0.642 Epoch: 7/50... Batch: 303/303... Train Loss: 0.230... Train Accuracy: 0.636... Val Accuracy: 0.648 Epoch: 8/50... Batch: 303/303... Train Loss: 0.227... Train Accuracy: 0.641... Val Accuracy: 0.653 Epoch: 9/50... Batch: 303/303... Train Loss: 0.223... Train Accuracy: 0.646... Val Accuracy: 0.656 Epoch: 10/50... Batch: 303/303... Train Loss: 0.221... Train Accuracy: 0.652... Val Accuracy: 0.659
测试
最后,我们将在测试集上验证我们的模型是否能够达到训练时候的在验证集上的准确率。
def test_network(model_dir, batch_size, test_x, test_y): # Build Network with tf.Session() as sess: # Restore Model # Test Model
我们像以前一样构建计算图,但现在不是训练阶段,而是从检查点目录恢复保存的模型,然后通过模型运行测试集数据。如下显示,测试集的精度为72%。 这基本符合我们之前的验证集的准确性,并表明我们通过数据拆分获得了比较理想的数据分布。
INFO:tensorflow:Restoring parameters from checkpoints/sentiment.ckpt Test Accuracy: 0.717
结论
最后,我们已经有一个良好的开始,但从目前的成果出发我们可以继续做一些其他事情来提高当前模型的性能。比如我们可以花费更长时间去训练模型,构建具有更多隐藏单元和更多LSTM层的更大网络,并继续调整我们的超参数。
总之,LSTM(Long short-term memory)网络是RNN(Recurrent Neural Networks)的扩展,主要用来解决序列数据长期依赖性的学习问题。 LSTM尤其在处理序列型数据方面具有非常广泛的应用,通常用于NLP, 即自然语言处理。 我们通过训练具有word embeding的多层LSTM网络来显示LSTM的表达能力,通过社交媒体上的评论来预测股市情绪。