赞
踩
前言
系列专栏:【深度学习:算法项目实战】✨︎
涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对抗网络、门控循环单元、长短期记忆、自然语言处理、深度强化学习、大型语言模型和迁移学习。
近来,机器学习得到了长足的发展,并引起了广泛的关注,其中语音和图像识别领域的成果最为显著。本研究论文分析了深度学习方法–长短期记忆神经网络(LSTM)–在A股市中的表现。论文显示,虽然这种技术在语音识别等其他领域取得了不错的成绩,但在应用于金融数据时却表现不佳。事实上,金融数据的特点是噪声信号比高,这使得机器学习模型难以找到模式并预测未来价格。
本文不对LSTM模型过多介绍,只探讨Stacked LSTM在A股中的表现,以及模型调参与性能优化。本研究文章的结构如下。第一节介绍金融时间序列数据。第二部分介绍金融时间数据的特征过程。第三部分是构建模型、定义参数空间、损失函数与优化器。第四部分是模型评估与结果可视化。第五部分是预测下一个时间点的收盘价。
金融时间序列数据是指按照时间顺序记录的各种金融指标的数值序列,这些指标包括但不限于股票价格、汇率、利率等。这些数据具有以下几个显著特点:
import numpy as np
import pandas as pd
from pytdx.hq import TdxHq_API
import plotly.graph_objects as go
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import TimeSeriesSplit
from sklearn.model_selection import ParameterSampler
from keras.models import Sequential
from keras.layers import Input, Dense, LSTM, Dropout
from keras.metrics import RootMeanSquaredError
from keras.optimizers import Adam
首先,让我们使用 TdxHq_API()
函数获取股票价格
api = TdxHq_API()
with api.connect('119.147.212.81', 7709):
df = api.to_df(api.get_security_bars(9, 1, '600584', 0, 800))
print(df)
open close high low vol amount year month day \ 0 41.01 39.60 41.29 39.60 432930.0 1.735115e+09 2021 2 25 1 38.63 39.56 39.56 38.51 398273.0 1.553533e+09 2021 2 26 2 39.71 40.47 40.59 39.15 382590.0 1.530225e+09 2021 3 1 3 41.17 40.30 41.26 39.90 334984.0 1.358569e+09 2021 3 2 4 40.20 40.80 40.92 39.38 325774.0 1.306173e+09 2021 3 3 .. ... ... ... ... ... ... ... ... ... 795 29.15 29.05 29.29 28.68 489791.0 1.418345e+09 2024 6 14 796 29.05 31.28 31.90 28.75 980331.0 2.989219e+09 2024 6 17 797 31.20 31.40 31.41 30.81 580956.0 1.811908e+09 2024 6 18 798 31.30 31.75 32.02 31.05 739795.0 2.341768e+09 2024 6 19 799 31.30 31.08 31.88 30.93 530154.0 1.660881e+09 2024 6 20 hour minute datetime 0 15 0 2021-02-25 15:00 1 15 0 2021-02-26 15:00 2 15 0 2021-03-01 15:00 3 15 0 2021-03-02 15:00 4 15 0 2021-03-03 15:00 .. ... ... ... 795 15 0 2024-06-14 15:00 796 15 0 2024-06-17 15:00 797 15 0 2024-06-18 15:00 798 15 0 2024-06-19 15:00 799 15 0 2024-06-20 15:00 [800 rows x 12 columns]
接下来,使用 go.Scatter()
函数绘制股票价格趋势
fig = go.Figure([go.Scatter(x=df['datetime'], y=df['close'])])
fig.update_layout(
title={'text': 'Close Price History', 'font_size': 24, 'font_family': 'Comic Sans MS', 'font_color': '#454545'},
xaxis_title={'text': '', 'font_size': 18, 'font_family': 'Courier New', 'font_color': '#454545'},
yaxis_title={'text': 'Close Price CNY', 'font_size': 18, 'font_family': 'Lucida Console', 'font_color': '#454545'},
xaxis_tickfont=dict(color='#663300'), yaxis_tickfont=dict(color='#663300'), width=900, height=500,
plot_bgcolor='#F2F2F2', paper_bgcolor='#F2F2F2',
)
fig.show()
# 设置时间窗口大小
window_size = 180
若在收盘之前运行,则最后一个测试price不准确,range中长度最好再减1
# 构造序列数据
def create_dataset(dataset, look_back=1):
X, Y = [], []
for i in range(len(dataset)-look_back):
a = dataset[i:(i+look_back), 0]
X.append(a)
Y.append(dataset[i + look_back, 0])
return np.array(X), np.array(Y)
MinMaxScaler()
函数主要用于将特征数据按比例缩放到指定的范围。默认情况下,它将数据缩放到[0, 1]区间内,但也可以通过参数设置将数据缩放到其他范围。在机器学习中,MinMaxScaler()
函数常用于不同尺度特征数据的标准化,以提高模型的泛化能力
# 归一化数据
scaler = MinMaxScaler(feature_range=(0, 1))
scaled_data = scaler.fit_transform(df['close'].values.reshape(-1, 1))
# 创建数据集
X, y = create_dataset(scaled_data, look_back=window_size)
# 重塑输入数据为[samples, time steps, features]
X = np.reshape(X, (X.shape[0], X.shape[1], 1))
TimeSeriesSplit()
函数与传统的交叉验证方法不同,TimeSeriesSplit 特别适用于需要考虑时间顺序的数据集,因为它确保测试集中的所有数据点都在训练集数据点之后,并且可以分割多个训练集和测试集。
# 使用TimeSeriesSplit划分数据集,根据需要调整n_splits
tscv = TimeSeriesSplit(n_splits=3, test_size=30)
# 遍历所有划分进行交叉验证
for i, (train_index, test_index) in enumerate(tscv.split(X)):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
# print(f"Fold {i}:")
# print(f" Train: index={train_index}")
# print(f" Test: index={test_index}")
这里我们使用最后一个 fold
X_train.shape, X_test.shape, y_train.shape, y_test.shape
((590, 180, 1), (30, 180, 1), (590,), (30,))
Stacked LSTM,即堆叠长短期记忆网络,是一种深度学习的模型架构,由多个LSTM层堆叠而成。这种架构使得模型能够学习并提取输入序列数据的不同级别的特征,从而提高预测的准确性。
# 定义模型构建函数 def LSTMRegressor(lstm_units, dropout_rate, learning_rate): model = Sequential() model.add(Input(shape=(X_train.shape[1], X_train.shape[2]))) model.add(LSTM(lstm_units, return_sequences=True)) model.add(Dropout(dropout_rate)) model.add(LSTM(lstm_units, return_sequences=True)) model.add(Dropout(dropout_rate)) model.add(LSTM(lstm_units)) model.add(Dropout(dropout_rate)) model.add(Dense(60)) model.add(Dropout(dropout_rate)) model.add(Dense(1)) # 线性回归层 opt = Adam(learning_rate=learning_rate) model.compile(optimizer=opt, loss='mean_squared_error') return model
使用 ParameterSampler
可以为随机搜索定义参数的分布,却不像网格搜索那样指定所有可能的参数组合。
# 定义参数空间 param_grid = { 'lstm_units': [32, 64, 128], 'dropout_rate': [0.2, 0.3, 0.4], 'learning_rate': [0.001, 0.0001] } # 使用ParameterSampler生成参数组合 param_list = list( ParameterSampler( param_grid, n_iter=len( param_grid['lstm_units'] ) * len( param_grid['dropout_rate'] ) * len( param_grid['learning_rate'] ), random_state=42 ) )
# 初始化最佳验证损失和最佳模型
best_val_loss = float('inf')
best_model = None
# 调参循环 for params in param_list: print(f"Trying parameters: {params}") model = LSTMRegressor(**params) # 训练模型(这里仅使用一部分epoch作为示例) history = model.fit(X_train, y_train, epochs=10, batch_size=32, validation_data=(X_test, y_test), verbose=0) # 计算验证集上的损失 val_loss = history.history['val_loss'][-1] # 如果当前模型的验证损失比之前的好,则更新最佳模型和最佳验证损失 if val_loss < best_val_loss: best_val_loss = val_loss best_model = model print(f"Found better model with validation loss: {best_val_loss}") # 输出最佳模型的参数 print(f"Best model parameters: {param_list[param_list.index(params)]}")
Trying parameters: {'lstm_units': 32, 'learning_rate': 0.001, 'dropout_rate': 0.2} Found better model with validation loss: 0.007122963201254606 Trying parameters: {'lstm_units': 64, 'learning_rate': 0.001, 'dropout_rate': 0.2} Found better model with validation loss: 0.006877976469695568 Trying parameters: {'lstm_units': 128, 'learning_rate': 0.001, 'dropout_rate': 0.2} Found better model with validation loss: 0.003105488372966647 Trying parameters: {'lstm_units': 32, 'learning_rate': 0.0001, 'dropout_rate': 0.2} Trying parameters: {'lstm_units': 64, 'learning_rate': 0.0001, 'dropout_rate': 0.2} Trying parameters: {'lstm_units': 128, 'learning_rate': 0.0001, 'dropout_rate': 0.2} Trying parameters: {'lstm_units': 32, 'learning_rate': 0.001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 64, 'learning_rate': 0.001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 128, 'learning_rate': 0.001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 32, 'learning_rate': 0.0001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 64, 'learning_rate': 0.0001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 128, 'learning_rate': 0.0001, 'dropout_rate': 0.3} Trying parameters: {'lstm_units': 32, 'learning_rate': 0.001, 'dropout_rate': 0.4} Trying parameters: {'lstm_units': 64, 'learning_rate': 0.001, 'dropout_rate': 0.4} Trying parameters: {'lstm_units': 128, 'learning_rate': 0.001, 'dropout_rate': 0.4} Trying parameters: {'lstm_units': 32, 'learning_rate': 0.0001, 'dropout_rate': 0.4} Trying parameters: {'lstm_units': 64, 'learning_rate': 0.0001, 'dropout_rate': 0.4} Trying parameters: {'lstm_units': 128, 'learning_rate': 0.0001, 'dropout_rate': 0.4} Best model parameters: {'lstm_units': 128, 'learning_rate': 0.0001, 'dropout_rate': 0.4}
best_model.summary()
Model: "sequential_2" ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩ │ lstm_6 (LSTM) │ (None, 180, 128) │ 66,560 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dropout_8 (Dropout) │ (None, 180, 128) │ 0 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ lstm_7 (LSTM) │ (None, 180, 128) │ 131,584 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dropout_9 (Dropout) │ (None, 180, 128) │ 0 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ lstm_8 (LSTM) │ (None, 128) │ 131,584 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dropout_10 (Dropout) │ (None, 128) │ 0 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dense_4 (Dense) │ (None, 60) │ 7,740 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dropout_11 (Dropout) │ (None, 60) │ 0 │ ├──────────────────────────────────────┼─────────────────────────────┼─────────────────┤ │ dense_5 (Dense) │ (None, 1) │ 61 │ └──────────────────────────────────────┴─────────────────────────────┴─────────────────┘ Total params: 1,012,589 (3.86 MB) Trainable params: 337,529 (1.29 MB) Non-trainable params: 0 (0.00 B) Optimizer params: 675,060 (2.58 MB)
使用 .save()
函数保存最佳模型
# 保存最佳模型
# best_model.save('best_model.h5')
使用均方误差 mean_squared_error()
评估模型性能
from sklearn.metrics import mean_squared_error
# 使用最佳模型进行预测
trainPredict = best_model.predict(X_train)
testPredict = best_model.predict(X_test)
mse = mean_squared_error(y_test, testPredict)
print(f"Test MSE: {mse}")
19/19 ━━━━━━━━━━━━━━━━━━━━ 2s 105ms/step
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 94ms/step
Test MSE: 0.0031054877469655923
# 反归一化预测结果
trainPredict = scaler.inverse_transform(trainPredict)
y_train = scaler.inverse_transform([y_train])
testPredict = scaler.inverse_transform(testPredict)
y_test = scaler.inverse_transform([y_test])
# 计算绘图数据
train = df[:X_train.shape[0]+X_train.shape[1]]
valid = df[X_train.shape[0]+X_train.shape[1]:]
valid = valid.assign(predictions=testPredict)
# 可视化数据
fig = go.Figure([go.Scatter(x=train['datetime'], y=train['close'],name='Train')])
fig.add_trace(go.Scatter(x=valid['datetime'],y=valid['close'],name='Test'))
fig.add_trace(go.Scatter(x=valid['datetime'],y=valid['predictions'], name='Prediction'))
fig.update_layout(
title={'text': 'Close Price Validation', 'font_size': 24, 'font_family': 'Comic Sans MS', 'font_color': '#454545'},
xaxis_title={'text': '', 'font_size': 18, 'font_family': 'Courier New', 'font_color': '#454545'},
yaxis_title={'text': 'Close Price CNY', 'font_size': 18, 'font_family': 'Lucida Console', 'font_color': '#454545'},
xaxis_tickfont=dict(color='#663300'), yaxis_tickfont=dict(color='#663300'), width=900, height=500,
plot_bgcolor='#F2F2F2', paper_bgcolor='#F2F2F2',
)
fig.show()
从上图我们可以观察到预测价格存在滞后性,关于如何缓解滞后性请参考连接。1
# 使用模型预测下一个时间点的收盘价
# 假设latest_closes是一个包含最新window_size个收盘价的列表或数组
latest_closes = df['close'][-window_size:].values
latest_closes = latest_closes.reshape(-1, 1)
scaled_latest_closes = scaler.fit_transform(latest_closes)
latest_closes_reshape = scaled_latest_closes.reshape(1, window_size, 1)
next_close_pred = best_model.predict(latest_closes_reshape)
next_close_pred = scaler.inverse_transform(next_close_pred)
next_close_pred
1/1 ━━━━━━━━━━━━━━━━━━━━ 0s 31ms/step
array([[30.284128]], dtype=float32)
本文仅用于深度学习科学实验和教育目的,并非投资建议
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。