背景
本文将介绍深度学习模型 ——— 长短期记忆网络LSTM 在股票价格预测领域的应用,同时学习Python语言 Tensorflow(Keras)框架下的预测代码。
道琼斯指数全称为股票价格平均指数。作为世界上历史最为悠久的股票指数,它可以反映市场的变化,代表着市场的趋势。由于该指数历史悠久,从未间断,分析师们易于用该指数的历史指数与现实指数做出对比,以此判断经济、股市形势。利用道琼斯指数可以比较不同时期的股票行情和经济发展情况,是观察市场动态和从事股票投资的主要参考。道琼斯股价平均指数所选用的股票都很有代表性,这些股票的发行公司都是本行业具有重要影响的著名公司,其股票行情为世界股票市场所瞩目,各国投资者都极为重视。针对道琼斯指数价格进行准确合理的预测对投资行业及各个经济领域都有着不可估量的作用。
本文以1980年12月23日至2020年2月7日共9866个交易日的道琼斯指数开盘价格(并按照适当比例划分训练集、验证集、测试集)为主要数据集,利用长短期记忆循环神经网络对道琼斯指数进行价格预测研究和涨跌分类预测研究。
循环神经网络
循环神经网络是一类以序列数据为输入,在序列的演进方向进行递归且所有节点按链式连接的神经网络。循环神经网络的研究始于二十世纪80-90年代,并在二十一世纪初发展为深度学习算法之一 ,其中双向循环神经网络(Bidirectional RNN, Bi-RNN)和长短期记忆网络(Long Short-Term Memory networks,LSTM)是常见的循环神经网络。
循环神经网络具有记忆性,参数共享并且图灵完备(Turing completeness),因此在对序列的非线性特征进行学习时具有一定优势。其在自然语言处理(NLP)领域,例如语音识别、语言建模、机器翻译等领域有重要应用价值,也常被用于各类时间序列预测。
LSTM
下面我们简单介绍长短期记忆网络LSTM,详细介绍及推导过程推荐B站搜李宏毅和吴恩达的深度学习课程。
Fig1. Lstm模型结构
LSTM由重复模块链形成,每个单个模块称为细胞(元胞)。各元胞都有特定的门结构来实现选择性让信息传递,通过LSTM门结构(遗忘门、输入门、细胞状态更新、输出门)信息的传递,实现各个元胞状态的更新。元胞的结构如图。其中为Sigmoid激活函数,表示基本积运算(Hadamard),tanh表示tanh激活函数。
遗忘门的作用是选择性的更新细胞状态。LSTM的第一步是确定元胞状态的更新。它通过Sigmoid函数对(前一个输出)和(当前输入)处理,输出0-1之间的数字。1代表完全保留,而0代表完全遗忘。其中为权重矩阵,为偏置项。
输入门的作用是输入数据,决定细胞状态中储存的信息。这一部分分为两步,首先是由Sigmoid层确定输入信息在更新中所占的权重,即哪些信息需要更新。接下来通过tanh构建由此时计算形成候选向量。
接下来更新上一个细胞状态,将该矩阵与遗忘门处理后的上一个元胞状态相加形成。将上一个状态矩阵乘以,表示对上一个细胞状态的选择性记忆。之后我们将得到的值加上,得到的是新的细胞状态。
最后输出门将基于当前细胞状态进行输出,将当前输入数据通过Sigmoid层。然后将当前细胞状态通过tanh层,将矩阵的归一化到-1和1之间,并将其与Sigmoid层的输出进行基本积运算,至此,确定了输出部分的数据。
通过上述LSTM细胞单元构成的模块链网络,能通过对输入数据的不断学习进而对未来进行预测。
Lstm 编程实现
笔者对1980年12月23日至2020年2月7日共9866个交易日的道琼斯指数进行缺失值检验,发现并无缺失值,然后将输入窗口设置为50、输出窗口设置为5( 即利用前50天的道琼斯指数预测后5天的道琼斯指数),对数据进行切片。按照7:1.5:1.5的比例将数据分为训练集、验证集、测试集三部分,其中训练集用于模型训练、验证集用于超参数调优、测试集用于模型检验。
# 调用库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import layers
from sklearn.preprocessing import MinMaxScaler
from keras import regularizers
#### 数据处理部分 ####
# 读入数据
google_stock = pd.read_excel('道琼斯综合.xls', encoding='GBK')
google_stock.tail() # 查看部分数据
google_stock.head()
# 时间戳长度
time_stamp = 5 # 输入序列长度
# 划分训练集与验证集
google_stock = google_stock[['开盘价(元/点)_OpPr']]
train = google_stock[0:7000 + time_stamp]
valid = google_stock[7000 - time_stamp:8500 + time_stamp]
test = google_stock[8500 - time_stamp:]
# 归一化
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(train)
x_train, y_train = [], []
# 训练集切片
for i in range(time_stamp, len(train)-5):
x_train.append(scaled_data[i - time_stamp:i])
y_train.append(scaled_data[i: i+5])
x_train, y_train = np.array(x_train), np.array(y_train).reshape(-1,5)
# 验证集切片
scaled_data = scaler.fit_transform(valid)
x_valid, y_valid = [], []
for i in range(time_stamp, len(valid)-5):
x_valid.append(scaled_data[i - time_stamp:i])
y_valid.append(scaled_data[i: i+5])
x_valid, y_valid = np.array(x_valid), np.array(y_valid).reshape(-1,5)
# 测试集切片
scaled_data = scaler.fit_transform(test)
x_test, y_test = [], []
for i in range(time_stamp, len(test)-5):
x_test.append(scaled_data[i - time_stamp:i])
y_test.append(scaled_data[i: i+5])
x_test, y_test = np.array(x_test), np.array(y_test).reshape(-1,5)
笔者选取了Tensorflow(version:2.3)作为本次建模的深度学习框架。在进行Lstm模型建模时,笔者设置了4层网络,第一层为Lstm层(维度;64),第二层为Lstm层(维度;64),第三层为Lstm层(维度;32),第四层为dropout层(dropout=0.054,用于防止过拟合),第四层为全连接层(神经元个数为5,用于预测未来5道琼斯指数),估计参数使用Adam优化器,学习率采用LR衰减方式,并将最大迭代次数设置为70次,可得训练集、验证集loss变换趋势如下。
Fig2. loss变化趋势可视化
#### 建模部分 ####
model = keras.Sequential()
model.add(layers.LSTM(64, return_sequences=True, input_shape=(x_train.shape[1:])))
model.add(layers.LSTM(64, return_sequences=True))
model.add(layers.LSTM(32))
model.add(layers.Dropout(0.1))
model.add(layers.Dense(5))
model.compile(optimizer=keras.optimizers.Adam(), loss='mae',metrics=['accuracy'])
learning_rate_reduction = keras.callbacks.ReduceLROnPlateau(monitor='val_loss', patience=3, factor=0.7, min_lr=0.000000005)
history = model.fit(x_train, y_train,
batch_size = 128,
epochs=70,
validation_data=(x_valid, y_valid),
callbacks=[learning_rate_reduction])
# loss变化趋势可视化
plt.plot(history.history['loss'],label='training loss')
plt.plot(history.history['val_loss'], label='val loss')
plt.show()
可以看出,测试集上的预测效果还不错,且经过超参数调优,模型没有出现过拟合现象。在模型评价方面,笔者采用MAE准则作为评价标准,使用Naive估计(用前1天的道琼斯指数作为未来一天的估计值)作为基模型,最终得到Lstm模型预测结果由于基模型Naive估计(MAE:125.43)。
Fig3. 测试集预测效果
#### 预测结果分析&可视化 ####
closing_price = model.predict(x_test)
model.evaluate(x_test)
scaler.fit_transform(pd.DataFrame(valid['开盘价(元/点)_OpPr'].values))
# 反归一化
closing_price = scaler.inverse_transform(closing_price.reshape(-1,5)[:,0].reshape(1,-1)) #只取第一列
y_test = scaler.inverse_transform(y_test.reshape(-1,5)[:,0].reshape(1,-1))
# 计算预测结果
rms = np.sqrt(np.mean(np.power((y_test[0:1,5:] - closing_price[0:1,5:] ), 2)))
print(rms)
print(closing_price.shape)
print(y_test.shape)
# 预测效果可视化
plt.figure(figsize=(16, 8))
dict_data = {
'Predictions': closing_price.reshape(1,-1)[0],
'开盘价(元/点)_OpPr': y_test[0]
}
data_pd = pd.DataFrame(dict_data)
plt.plot(data_pd[['开盘价(元/点)_OpPr']],linewidth=3,alpha=0.8)
plt.plot(data_pd[['Predictions']],linewidth=1.2)
plt.savefig('D:/Software/Pycharm_project/Machine_learning/Stock_prediction_lstm_multistep.png', dpi=600)
plt.show()
思考&练习 | 股票涨跌预测
在股票涨跌分类预测中,除了预测股票开盘价、收盘价外,更多的是对股票的涨跌进行预测。实际上这个问题可以视作分类问题。计算相邻两天股票开盘价的差值,将大于等于零的记为“涨”,小于零的记为“跌”,进而计算分类的准确率。除深度学习模型外,笔者还使用了许多传统的机器学习方法如:SVM、Decision tree等对道琼斯指数进行分类研究。然而,无论使用哪种方法,预测的准确率都仅在0.53-0.60之间,预测效果虽然大于51%(即存在套利机会),但与51%的相差不大,为此笔者深入考虑了效果不好的原因。
Fig4. 股票涨跌白噪声序列
通过绘制观察股票的涨跌值序列,可以看出该序列长期呈现不规则的波动趋势。在对该序列进行了白噪声检验后,可以确定该序列为纯随机序列,这意味着股票的涨跌几乎不可预测,这也符合股票市场的实际情况:并不是所有投资者都符合经济学意义上的理性人假设。