☰
基于相关性分析的CNN-Attention-LSTM期货价格预测模型实战
2026/10/10 22:25:27 网站建设 项目流程

简介:本资源为基于相关性分析的CNN-Attention-LSTM期货价格预测模型完整项目包,面向计算机相关专业做课程设计、期末大作业的学生及需要项目实战练习的学习者。项目经导师指导并认可,评分98分,围绕期货价格预测这一典型时序任务,融合相关性分析进行特征筛选,再以CNN提取局部特征、Attention机制加权关键信息、LSTM建模时序依赖,形成一套可复现的深度学习预测方案。压缩包共29个文件,约30.29MB,包含8个Python源码文件、6个npy数据文件、3个xlsx表格、2个pdf教程文档、2个模型权重文件及checkpoint、sql、md等,覆盖数据预处理、相关性分析、时间步处理、模型训练与预测API等完整环节。已有260人学习下载。读者可获得可直接运行的源码、配套数据集与训练好的模型权重,以及详细注释和独立算法教程,便于快速理解模型结构、复现实验结果并迁移到其他金融时序预测场景。

1. 期货价格预测里,CNN-Attention-LSTM 到底在解决什么问题

做期货量化的人大多有过这样的经历:拿一段螺纹钢或沪铜的日线数据,直接丢进 LSTM 跑回归,训练集 loss 降得很漂亮,一到实盘就翻车。原因不复杂——期货价格序列里既有局部突变(一根大阳线、一次跳空),又有中长周期的趋势惯性,还有品种之间的联动关系。单纯靠 LSTM 的循环结构,它擅长记长依赖,但对局部形态的捕捉偏弱,注意力也容易被噪声分散。

基于相关性分析的 CNN-Attention-LSTM 期货价格预测模型,本质上是把三件事拼在一起:用相关性分析筛掉冗余输入变量,用 CNN 提取局部时序特征,用 Attention 给不同时间步加权,最后交给 LSTM 做序列建模。这套组合在 python 生态里实现成本不高,源码、数据集、模型权重和详细注释打包在一起,适合想从「跑通 demo」过渡到「理解每一步为什么这么做」的从业者。下面按选型、数据、模型、训练、避坑、进阶的顺序拆开讲。

2. 相关性分析先行:输入变量怎么筛才不白干

2.1 为什么不能把所有能拿到的因子都塞进去

期货价格预测的输入通常包括开盘、最高、最低、收盘、成交量、持仓量,再加上外部因子如美元指数、原油、相关品种价差。很多人图省事,把几十列特征直接喂给模型,结果训练慢、过拟合严重、特征重要性还说不清。

相关性分析在这里的作用不是做因果推断,而是做冗余剔除。常见做法是计算候选因子与目标价格(或收益率)的 Pearson / Spearman 相关系数,再算因子之间的相关矩阵,把高度共线的变量合并或删除。Pearson 对线性关系敏感,Spearman 对单调非线性更稳,期货数据里两者都跑一遍对比更保险。

提示:相关性高不代表预测力强,只代表信息重叠。真正要保留的是与目标相关、但彼此之间相关性低的因子组合。

2.2 用 python 做相关性筛选的最小代码

import pandas as pd import numpy as np from scipy.stats import pearsonr, spearmanr # 假设 df 已包含 OHLCV 及外部因子,target 为下一期收益率 def corr_filter(df, target_col, threshold=0.85): # 1. 计算每个因子与目标的 Pearson 和 Spearman records = [] for col in df.columns: if col == target_col: continue p, _ = pearsonr(df[col].dropna(), df[target_col].dropna()) s, _ = spearmanr(df[col].dropna(), df[target_col].dropna()) records.append({'factor': col, 'pearson': p, 'spearman': s}) corr_df = pd.DataFrame(records).set_index('factor') # 2. 保留与目标相关性绝对值大于 0.05 的因子 corr_df['abs_p'] = corr_df['pearson'].abs() candidates = corr_df[corr_df['abs_p'] > 0.05].index.tolist() # 3. 在候选因子中剔除彼此相关性过高的 selected = [] for col in candidates: keep = True for sel in selected: r = df[col].corr(df[sel]) if abs(r) > threshold: keep = False break if keep: selected.append(col) return selected, corr_df selected_factors, corr_table = corr_filter(df, 'next_ret') print(selected_factors)

这段代码做了三层过滤:先算单因子与目标的两种相关系数,再用绝对值阈值粗筛,最后用因子间相关矩阵去共线。threshold=0.85是经验值,因子少可以放到 0.9,因子多可以降到 0.8。abs_p > 0.05这个门槛看着低,但期货日频数据里单因子线性相关本来就不高,设太高会误杀。

参数说明:pearsonr和spearmanr都要求输入无 NaN,所以提前dropna();如果因子和目标长度不一致,要先对齐索引。跑完这一步,你会得到一张相关性表,后续模型输入维度就从几十列降到十列以内,训练速度和可解释性都会明显改善。

3. CNN-Attention-LSTM 模型结构:每一层在干什么

3.1 从局部特征到长依赖的三段式设计

CNN 部分通常用一维卷积(Conv1D)在时间轴上滑动,卷积核大小取 3、5、7,用来捕捉「连续几根 K 线」的局部形态。比如三连阳、跳空缺口、放量突破,这些模式在原始序列里就是局部窗口内的数值变化,Conv1D 比 LSTM 更擅长提取。

Attention 层加在 CNN 之后、LSTM 之前或之后都有人用。加在 LSTM 之前,是对 CNN 输出的特征序列做加权,让模型关注更重要的时间片段;加在 LSTM 之后,是对 LSTM 各时间步的隐状态加权,再聚合成一个向量做预测。两种都合理,源码里常见的是后者,因为实现更直接。

LSTM 负责把加权后的序列按时间顺序建模,输出最后一个时间步的隐状态或全序列的池化结果,再接全连接层映射到预测值。

3.2 用 Keras 搭一个可跑的模型骨架

import tensorflow as tf from tensorflow.keras import layers, Model def build_cnn_attention_lstm(input_shape, cnn_filters=64, kernel_size=3, lstm_units=64, dropout=0.2): inputs = layers.Input(shape=input_shape) # (timesteps, features) # CNN 提取局部时序特征 x = layers.Conv1D(filters=cnn_filters, kernel_size=kernel_size, padding='same', activation='relu')(inputs) x = layers.BatchNormalization()(x) x = layers.Dropout(dropout)(x) # LSTM 建模长依赖,return_sequences=True 保留每个时间步 x = layers.LSTM(lstm_units, return_sequences=True)(x) x = layers.Dropout(dropout)(x) # Attention:对 LSTM 输出做加权求和 score = layers.Dense(1, activation='tanh')(x) # (batch, timesteps, 1) weight = layers.Softmax(axis=1)(score) # 时间步权重 context = layers.Multiply()([x, weight]) # 加权 context = layers.Lambda(lambda t: tf.reduce_sum(t, axis=1))(context) # 输出层 x = layers.Dense(32, activation='relu')(context) outputs = layers.Dense(1)(x) model = Model(inputs, outputs) model.compile(optimizer=tf.keras.optimizers.Adam(1e-3), loss='mse', metrics=['mae']) return model model = build_cnn_attention_lstm((60, 8)) model.summary()

逻辑说明:Conv1D的padding='same'保证输出时间步与输入一致,方便后面接 LSTM。BatchNormalization放在卷积后、激活前还是激活后,Keras 里常见是激活后,这里按源码习惯放在激活后。Attention 用一个小 Dense 打分再 Softmax,是最轻量的实现,参数量少,不容易过拟合。

参数说明:input_shape=(60, 8)表示用过去 60 个时间步、8 个特征预测下一期。cnn_filters和lstm_units一般取 32 到 128,数据量小就取小。dropout=0.2是起点,过拟合明显就加到 0.3 到 0.5。学习率1e-3是 Adam 默认值,训练后期 loss 震荡可以降到5e-4。

注意:Attention 的 Softmax 作用在时间步维度(axis=1),不是特征维度。写错 axis 会让权重含义完全变掉,这是新手最容易翻车的地方之一。

4. 数据准备与训练流程:从原始行情到可复现结果

4.1 数据集划分与归一化顺序

期货价格预测最怕的是未来函数。归一化必须用训练集的均值和方差,再应用到验证集和测试集,不能全量归一化后再切分。常见做法是按时间顺序 7:1:2 切分,或者用滚动窗口做 walk-forward 验证。

from sklearn.preprocessing import StandardScaler import numpy as np def make_sequences(data, target, window=60): X, y = [], [] for i in range(window, len(data)): X.append(data[i-window:i]) y.append(target[i]) return np.array(X), np.array(y) # 按时间切分 train_ratio, val_ratio = 0.7, 0.1 n = len(df) train_end = int(n * train_ratio) val_end = int(n * (train_ratio + val_ratio)) scaler = StandardScaler() train_scaled = scaler.fit_transform(df.iloc[:train_end][feature_cols]) val_scaled = scaler.transform(df.iloc[train_end:val_end][feature_cols]) test_scaled = scaler.transform(df.iloc[val_end:][feature_cols]) X_train, y_train = make_sequences(train_scaled, target[:train_end], 60) X_val, y_val = make_sequences(val_scaled, target[train_end:val_end], 60) X_test, y_test = make_sequences(test_scaled, target[val_end:], 60)

StandardScaler的fit_transform只在训练集上调用,验证和测试只transform。make_sequences的window=60要和模型输入形状一致。如果预测目标是收益率而不是价格,target也要做相应处理,收益率本身量纲小,可以不再标准化。

4.2 训练回调与早停

callbacks = [ tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True), tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-5) ] history = model.fit(X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=32, callbacks=callbacks, verbose=1)

EarlyStopping的patience=10表示验证 loss 连续 10 轮不降就停,restore_best_weights=True保证拿回最优权重而不是最后一轮。ReduceLROnPlateau在 loss 停滞时把学习率减半,min_lr防止降到太小。batch_size=32是日频数据的常见起点,数据量大可以加到 64 或 128。

训练完用model.evaluate(X_test, y_test)看测试集指标,再画预测值和真实值的对比曲线。如果测试集 MAE 比验证集高出一大截,多半是过拟合或数据分布漂移,需要回头检查特征筛选和正则化强度。

5. 避坑与排查:这套模型最容易翻车的 5 个地方

5.1 现象:训练 loss 正常下降,验证 loss 从第一轮就很高

原因:归一化用了全量数据,或者训练集和验证集的时间顺序被打乱。期货数据时间顺序一乱,验证集就泄露了未来信息,但模型学到的模式又对不上,表现就是验证 loss 异常。

解决:严格按时间切分,归一化只在训练集 fit。切分前先按时间排序,确认索引单调递增。

5.2 现象:Attention 权重几乎均匀,看不出重点

原因:Softmax 前的打分 Dense 层初始化太小,或者 LSTM 输出本身方差很小,导致打分区分度不够。

解决:把打分 Dense 的初始化改成glorot_uniform,或者在打分前加一层 LayerNormalization。也可以先不加 Attention 跑一版,对比加了之后验证 loss 有没有改善,没改善说明 Attention 没起作用。

5.3 现象:预测曲线整体滞后于真实价格

原因:用收盘价做目标且没有做差分,模型学到的是「上一期价格约等于这一期价格」,输出自然滞后。

解决:改成预测收益率或价格差分,或者用多步预测并评估方向准确率而不是 MSE。滞后是价格预测的常见现象,不要只看 MSE 低就以为模型好。

5.4 现象:换一个品种或换一段时间,效果大幅下降

原因:相关性筛选是在全量数据上做的,引入了未来信息;或者模型对特定波动率区间过拟合。

解决:相关性分析也要在训练集内做,或者用滚动窗口重新筛选。品种切换时重新跑一遍因子筛选,不要直接套用。

5.5 现象:GPU 显存够但训练速度很慢

原因:batch_size太小,或者数据管道没有用tf.data做预取。

解决:把数据转成tf.data.Dataset,加.batch(32).prefetch(tf.data.AUTOTUNE)。如果用了Lambda层做 reduce_sum,确认没有在 Python 层面做循环。

6. 进阶技巧:用方向准确率和滚动验证判断模型值不值得上

MSE 和 MAE 低不代表能赚钱。期货预测里更值得看的是方向准确率:预测涨跌方向与实际一致的比例。可以在测试集上算:

pred = model.predict(X_test).flatten() direction_acc = np.mean((pred > 0) == (y_test > 0)) print(f'方向准确率: {direction_acc:.4f}')

如果方向准确率长期在 50% 附近,说明模型没有抓到有效信号,MSE 再低也没用。另一个技巧是滚动验证:每次用过去 N 天训练,预测接下来 M 天,滚动前进。这样得到的指标更接近实盘表现。

def walk_forward(df, train_days=500, test_days=60, window=60): results = [] for start in range(0, len(df) - train_days - test_days, test_days): train = df.iloc[start:start+train_days] test = df.iloc[start+train_days:start+train_days+test_days] # 重新筛选因子、归一化、训练、预测 # 记录方向准确率和 MAE results.append(...) return results

滚动验证的代价是训练次数多,但能暴露模型在不同市场阶段的稳定性。我自己的习惯是:任何期货预测模型,先跑滚动验证,方向准确率能稳定在 53% 以上再考虑进一步调参,否则优先回去检查特征和标签构造。这套 CNN-Attention-LSTM 结构本身不复杂,难的是数据切分和评估方式不走样。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询