【数值预测案例】(6) LSTM、GRU 时间序列股票数据预测,附TensorFlow完整代码

大家好,今天和各位分享一下如何使用循环神经网络 LSTM 和 GRU 完成对股票数据的预测。GRU 是在 LSTM 基础上的简化,将 LSTM 内部的三个闸门简化成两个,往往 GRU 的计算效果会优于 LSTM


1. 导入工具包

如果没有电脑没有GPU的话就把下面那段调用GPU加速计算的代码删了

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import matplotlib.pyplot as plt

# 调用GPU加速
gpus = tf.config.experimental.list_physical_devices(device_type='GPU')
for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpu, True)

2. 获取数据集

首先,安装一下 pandas_datareader 这个能远程获取金融数据的工具包。接下来使用 web.DataReader() 指定在哪个平台获取哪个公司的股票信息。具体的函数参数请看知乎专栏:https://zhuanlan.zhihu.com/p/341254102 

本次我们获取谷歌在2000年到2021年的股票信息,把原始数据读入进来后的一定要删除数据中的空缺值,避免对后续数据处理的影响。为了方便循环神经网络的学习,需要将数据按照从旧到新的顺序排列

本节是通过一个序列来预测10天后的股票收盘价格在数据表格中新添 'label' 列保存每个序列的标签值。是对一个时间点的预测。

# pip install pandas_datareader
import pandas_datareader.data as web
import datetime  # datetime是Python处理日期和时间的标准库。

# 设置获取股票的时间范围的数据
start = datetime.datetime(2000,1,1)  # 设置开始时间
end = datetime.datetime(2021,9,1)  # 设置结束时间

# 在stooq数据源上获取googl在2000-2021年的股票数据
df = web.DataReader('GOOGL', 'stooq', start, end)
# 查看股票信息, 时间, 开盘价, 最高价, 最低价, 收盘价, 交易量
print(df)

df.dropna(inplace=True)  # 删除表格中的空值

# 根据数据的索引(时间)从小到大排序
df.sort_index(inplace=True)  # 排序完成后替换原来的df
print(df)

# 获取标签,预测10天后的收盘价
pre_days = 10
# 添加一个新的列存放标签, 相当于通过2004-08-19的特征来预测2004-08-29的收盘价
df['label'] = df['Close'].shift(-pre_days)
print(df)

 由于 'label' 列是将 'Close' 列集体向上移动10行,因此,'label' 列最后的10行会出现空缺值nan,这里要注意,后续会处理。


3. 数据预处理

导入 sklearn 标准化方法,对所有的特征数据进行标准化处理,对标签数据 'label' 列不做处理。标准化后能够避免偏差过大的数据对训练结果的影响。

from sklearn.preprocessing import StandardScaler  # 导入数据标准化方法

scaler = StandardScaler()  # 接收数据标准化方法
# 对所有的特征数据进行标准化,最后一列是标签值
sca_x = scaler.fit_transform(df.iloc[:,:-1])
# 查看标准化后的特征数据
print(sca_x)

五个特征列分别对应:开盘价,最高价,最低价,收盘价,交易量


4. 时间序列滑窗

这里使用一个很方便的队列deque,指定这个队列的最大长度maxlen=20,就代表一个时间序列的长度为20,如果队列deq的长度超过20就将第1个特征删除,将第21个特征追加到第20个特征后面,就能一直保持队列的长度是20。那么每一个时间序列的shape=[20,5],代表20行数据5列特征。

完成对所有数据的时间序列分组之后,由于特征数据sca_x最后10行是没有对应的标签值的。因此需要把最后10组时间序列删除。每一个序列对应一个标签,标签和序列的长度是相同的。

import numpy as np
from collections import deque  # 相当于一个列表,可以在两头增删元素

men_his_days = 20  # 用20天的特征数据来预测
# 创建一个队列, 长度等于记忆的天数,时间序列滑窗大小=20
deq = deque(maxlen=men_his_days)

# 创建一个特征列表,保存每个时间序列的特征
x = []
# 遍历每一行特征数据
for i in sca_x:
    # 将每行特征保存进队列
    deq.append(list(i))  # array类型转为list类型
    # 如果队列的长度等于记忆的天数(时间滑窗的的长度)就证明特征组成了一个时间序列
    # 如果队列长度大于记忆天数,队列会自动将头端的那个特征删除,将新输入追加到队列尾部
    if len(deq) == men_his_days:
        # 将这一组序列保存下来
        x.append(list(deq))  # array类型转为list类型

# 由于原特征中最后10条数据没有标签值, 在x特征数据中将最后10个序列删除
x = x[:-pre_days]
# 查看有多少个序列
print(len(x))  # 4260

# 数据表格df中最后一列代表标签值, 把所有标签取出来
# 例如使用[0,1,2,3,4]天的特征预测第20天的收盘价, 使用[1,2,3,4,5]天的特征预测第21天的收盘价
# 而表格中索引4对应的标签就是该序列的标签
y = df['label'].values[men_his_days-1: -pre_days]
print(len(y))  # 序列x和标签y的长度应该一样

# 将特征和标签变成numpy类型
x, y = np.array(x), np.array(y)

5. 数据集划分

我们已经获得了处理后的时间序列和对应的标签,接下来就按比例划分训练集、验证集、测试集即可。对于训练集需要使用 .shuffle() 随机打乱数据行排列顺序,避免偶然性。设置迭代器 iter(),结合 next() 函数从训练集中取出一个batch的数据

total_num = len(x)  # 一共多少组序列和标签
train_num = int(total_num*0.8)  # 80%的数据用于训练
val_num = int(total_num*0.9)  # 80-90%的数据用于验证
# 剩余的数据用于测试

x_train, y_train = x[:train_num], y[:train_num]  # 训练集
x_val, y_val = x[train_num:val_num], y[train_num:val_num]  # 验证集
x_test, y_test = x[val_num:], y[val_num:]  # 测试集

# 转为tensor类型
batch_size = 128  # 每个step训练多少组序列数据
# 训练集
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_ds = train_ds.batch(batch_size).shuffle(10000)  # 随机打乱
# 验证集
val_ds = tf.data.Dataset.from_tensor_slices((x_val, y_val))
val_ds = val_ds.batch(batch_size)
# 测试集
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_ds = test_ds.batch(batch_size)

# 查看数据集信息
sample = next(iter(train_ds))  # 取出一个batch的数据
print('x_train.shape:', sample[0].shape)  # (128, 20, 5)
print('y_train.shape:', sample[1].shape)  # (128,)

6. 构造网络模型

这里以 GRU 网络为例,LSTM 只需要将下面代码中的 layers.GRU() 换成 layers.LSTM() 即可。

要注意参数 return_sequences,代表返回输出序列中的最后一个值,还是所有值。默认False一般是下一层还是 LSTM 的时候才用 return_sequences=True

input_shape = sample[0].shape[-2:]  # [20,5] 输入维度不需要写batch维度

# 构造输入层
inputs = keras.Input(shape=input_shape)  # [None,20,5]

# 第一个GRU层, 如果下一层还是LSTM层就需要return_sequences=True, 否则就是False
x = layers.GRU(8, activation='relu', return_sequences=True, kernel_regularizer=keras.regularizers.l2(0.01))(inputs)
x = layers.Dropout(0.2)(x)  # 随机杀死神经元防止过拟合

# 第二个GRU层
x = layers.GRU(16, activation='relu', return_sequences=True, kernel_regularizer=keras.regularizers.l2(0.01))(x)
x = layers.Dropout(0.2)(x)  

# 第三个GRU层
x = layers.GRU(32, activation='relu')(x)
x = layers.Dropout(0.2)(x)  

# 全连接层, 随机权重初始化, l2正则化
x = layers.Dense(16, activation='relu', kernel_initializer='random_normal', kernel_regularizer=keras.regularizers.l2(0.01))(x)
x = layers.Dropout(0.2)(x)  

# 输出层, 输入序列的10天后的股票,是时间点。保证输出层神经元个数和y_train.shape[-1]相同
outputs = layers.Dense(1)(x)

# 构造网络
model = keras.Model(inputs, outputs)

# 查看网络结构
model.summary()

查看网络结构

Model: "model_3"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
input_4 (InputLayer)         [(None, 20, 5)]           0         
_________________________________________________________________
gru_3 (GRU)                  (None, 20, 8)             360       
_________________________________________________________________
dropout_12 (Dropout)         (None, 20, 8)             0         
_________________________________________________________________
gru_4 (GRU)                  (None, 20, 16)            1248      
_________________________________________________________________
dropout_13 (Dropout)         (None, 20, 16)            0         
_________________________________________________________________
gru_5 (GRU)                  (None, 32)                4800      
_________________________________________________________________
dropout_14 (Dropout)         (None, 32)                0         
_________________________________________________________________
dense_6 (Dense)              (None, 16)                528       
_________________________________________________________________
dropout_15 (Dropout)         (None, 16)                0         
_________________________________________________________________
dense_7 (Dense)              (None, 1)                 17        
=================================================================
Total params: 6,953
Trainable params: 6,953
Non-trainable params: 0
_________________________________________________________________

7. 网络训练

使用预测值和标签值之间的平均绝对误差mae作为损失函数,使用均方对数误差msle作为网络的监控指标。history中保存训练时的每次迭代的mae损失和msle指标

# 网络编译
model.compile(optimizer = keras.optimizers.Adam(0.001),  # adam优化器学习率0.001
              loss = tf.keras.losses.MeanAbsoluteError(),  # 标签和预测之间绝对差异的平均值
              metrics = tf.keras.losses.MeanSquaredLogarithmicError())  # 计算标签和预测之间的对数误差均方值。

epochs = 10  # 网络迭代次数

# 网络训练
history = model.fit(train_ds, epochs=epochs, validation_data=val_ds)

训练过程如下

Epoch 1/10
27/27 [==============================] - 8s 214ms/step - loss: 395.9859 - mean_squared_logarithmic_error: 32.9226 - val_loss: 1164.5131 - val_mean_squared_logarithmic_error: 46.3883
Epoch 2/10
27/27 [==============================] - 5s 198ms/step - loss: 404.5123 - mean_squared_logarithmic_error: 28.0247 - val_loss: 1153.9086 - val_mean_squared_logarithmic_error: 20.9722
----------------------------------------------------
----------------------------------------------------
Epoch 9/10
27/27 [==============================] - 5s 200ms/step - loss: 111.9984 - mean_squared_logarithmic_error: 0.1729 - val_loss: 174.2481 - val_mean_squared_logarithmic_error: 0.0213
Epoch 10/10
27/27 [==============================] - 5s 199ms/step - loss: 101.5161 - mean_squared_logarithmic_error: 0.1041 - val_loss: 54.0906 - val_mean_squared_logarithmic_error: 0.0028

8. 查看训练过程信息

绘制每次迭代的训练集损失和验证机损失、训练集监控指标和验证集监控指标

#(10)查看训练信息
history_dict = history.history  # 获取训练的数据字典
train_loss = history_dict['loss']  # 训练集损失
val_loss = history_dict['val_loss']  # 验证集损失
train_msle = history_dict['mean_squared_logarithmic_error']  # 训练集的百分比误差
val_msle = history_dict['val_mean_squared_logarithmic_error']  # 验证集的百分比误差
 
#(11)绘制训练损失和验证损失
plt.figure()
plt.plot(range(epochs), train_loss, label='train_loss')  # 训练集损失
plt.plot(range(epochs), val_loss, label='val_loss')  # 验证集损失
plt.legend()  # 显示标签
plt.xlabel('epochs')
plt.ylabel('loss')
plt.show()

#(12)绘制训练百分比误差和验证百分比误差
plt.figure()
plt.plot(range(epochs), train_msle, label='train_msle')  # 训练集损失
plt.plot(range(epochs), val_msle, label='val_msle')  # 验证集损失
plt.legend()  # 显示标签
plt.xlabel('epochs')
plt.ylabel('msle')
plt.show()


9. 预测阶段

使用 evaluate() 函数对整个测试集计算损失和监控指标,获取每个真实值的时间刻度

#(13)测试集评价, 计算损失和监控指标
model.evaluate(test_ds)

# 预测
y_pred = model.predict(x_test)

# 获取标签值对应的时间
df_time = df.index[-len(y_test):]

# 绘制对比曲线
fig = plt.figure(figsize=(10,5))  # 画板大小
axes = fig.add_subplot(111)  # 画板上添加一张图
# 真实值, date_test是对应的时间
axes.plot(df_time, y_test, 'b-', label='actual')
# 预测值,红色散点
axes.plot(df_time, y_pred, 'r--', label='predict')
# 设置横坐标刻度
axes.set_xticks(df_time[::50])
axes.set_xticklabels(df_time[::50], rotation=45)
 
plt.legend()  # 注释
plt.grid()  # 网格
plt.show()

绘制真实值和预测值之间的对比曲线


10. 对比 LSTM 和 GRU

使用相同方法训练的LSTM和GRU的预测曲线图如下,两种方法差别不大,如果有实际需要时可对比使用。

 训练过程比较图

猜你喜欢

转载自blog.csdn.net/dgvv4/article/details/124386024