简介:这份资源是面向计算机、人工智能、自动化等专业学生与教师的期货价格预测毕业设计项目,核心为基于相关性分析的CNN-Attention-LSTM组合模型实现,可用于课程设计、毕设、作业或项目初期立项演示。压缩包共29个文件,约30.28MB,包含8个Python源码文件、6个npy数据文件、3个xlsx表格、2个模型checkpoint文件及2份PDF教程,覆盖数据预处理、相关性分析、时间步处理、模型训练与预测推理等完整流程,并附有配置教程与详细注释。项目代码经过测试运行成功,读者可据此掌握从特征筛选到深度学习建模的完整链路,理解CNN提取局部特征、Attention加权与LSTM时序建模的融合思路,也可在现有代码基础上修改以扩展其他功能。目前已有677人学习,适合需要完整赛题方案与排错参考的学习者。
1. 期货价格预测毕设项目:相关性分析加 CNN-Attention-LSTM 到底能跑出什么
做期货价格预测的毕设,最怕两件事:一是数据拿不到,二是模型跑出来一条直线。这个标题里的方案,核心思路是用 Spearman 相关性分析先筛因子,再把筛选后的多变量序列喂给 CNN-Attention-LSTM 做预测。它解决的不是"预测明天涨跌"这种玄学问题,而是"给定一段历史量价数据,能不能比单变量 LSTM 更稳地拟合下一根 K 线的收盘价"。适合谁?适合正在做量化方向毕设、手里有 Python 基础、但不知道从哪套模型下手的同学。整套流程在普通笔记本上就能跑,不需要 GPU 集群。下面我把从数据到模型到评估的完整路径拆开讲,包括我踩过的坑和参数怎么调。
2. 相关性分析筛因子:Spearman 为什么比 Pearson 更适合期货数据
2.1 期货收益率序列的非正态性决定了相关系数的选择
期货价格序列有两个显著特征:一是非平稳,二是收益率分布尖峰厚尾。Pearson 相关系数假设变量线性相关且服从正态分布,直接套在期货收益率上会严重低估尾部关联。Spearman 秩相关系数不要求正态假设,只关心排序一致性,对异常值也更稳健。我一般会先对原始价格做一阶差分得到收益率,再算 Spearman 矩阵。
实际操作中,把主力连续合约的收盘价、成交量、持仓量、以及几个宏观因子(比如螺纹钢对应的铁矿石价格)一起纳入候选池,算完 Spearman 矩阵后按绝对值排序,取前 60% 作为入模特征。阈值不要卡太死,0.3 以上就可以保留,因为后续 CNN 层本身有特征提取能力。
import pandas as pd import numpy as np from scipy.stats import spearmanr # 假设 df 的列是各因子,行是时间序列 # 先做一阶差分转收益率,避免伪相关 returns = df.pct_change().dropna() # 计算 Spearman 相关系数矩阵 corr_matrix, p_values = spearmanr(returns) # 转成 DataFrame 方便筛选 corr_df = pd.DataFrame(corr_matrix, index=returns.columns, columns=returns.columns) # 假设目标变量是 'close',取与它相关性绝对值大于 0.3 的因子 target_corr = corr_df['close'].abs().sort_values(ascending=False) selected_features = target_corr[target_corr > 0.3].index.tolist() print("入选因子:", selected_features)这段代码的关键点:pct_change()做差分是必须的,否则价格序列之间的高相关性只是共同趋势造成的伪相关。spearmanr返回的是矩阵和 p 值矩阵,p 值可以用来做显著性过滤,但毕设场景下样本量通常够大,p 值普遍很小,重点看相关系数绝对值。selected_features里会包含 'close' 自身,后续建模时要把它从特征列里去掉,只保留其他因子作为输入。
2.2 滚动窗口相关性:静态矩阵会骗你
静态 Spearman 矩阵算的是全样本相关性,但期货市场结构会变。2020 年之前螺纹钢和铁矿石的联动关系和现在完全不同。我建议用滚动窗口算时变相关性,窗口长度取 60 或 120 个交易日。具体做法是每个窗口算一次 Spearman,然后看目标因子相关性的稳定性——如果某个因子在多个窗口里相关性忽高忽低,直接剔除。
window_size = 60 rolling_corr = [] for i in range(window_size, len(returns)): window = returns.iloc[i-window_size:i] corr, _ = spearmanr(window) corr_df = pd.DataFrame(corr, index=window.columns, columns=window.columns) rolling_corr.append(corr_df['close']) rolling_corr_df = pd.concat(rolling_corr, axis=1) # 计算每个因子相关性的均值和标准差 stability = rolling_corr_df.T.describe().T[['mean', 'std']] # 保留均值绝对值大于 0.25 且标准差小于 0.15 的因子 stable_features = stability[(stability['mean'].abs() > 0.25) & (stability['std'] < 0.15)].index.tolist()参数说明:window_size取 60 是因为期货大约三个月换一次主力合约,60 个交易日覆盖了一个完整的主力周期。std阈值 0.15 是我试出来的经验值,太严会筛掉所有因子,太松等于没筛。这一步做完,入模特征通常从十几个降到五六个,模型训练速度明显提升。
3. CNN-Attention-LSTM 模型搭建:从输入形状到注意力权重的完整实现
3.1 为什么是 CNN 在前、LSTM 在后、Attention 夹中间
这个架构的顺序不是随便排的。CNN 做一维卷积,负责从原始序列里提取局部模式,比如连续三天的放量上涨。LSTM 处理时序依赖,记住更长周期的趋势。Attention 放在 LSTM 之后,对 LSTM 每个时间步的输出加权,让模型自己决定哪些历史时刻对当前预测更重要。如果反过来先 LSTM 再 CNN,卷积会破坏时序结构,效果通常更差。
输入数据的形状是(样本数, 时间步长, 特征数)。时间步长我一般取 20,也就是用过去 20 天的数据预测第 21 天。特征数就是上一步筛选后的因子数量。标签是下一日的收盘价收益率,注意是收益率不是价格,这样模型学的是变化量,泛化更好。
import numpy as np 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): inputs = layers.Input(shape=input_shape) # CNN 层:提取局部特征 x = layers.Conv1D(filters=cnn_filters, kernel_size=kernel_size, padding='same', activation='relu')(inputs) x = layers.BatchNormalization()(x) x = layers.MaxPooling1D(pool_size=2)(x) # LSTM 层:捕捉时序依赖,return_sequences=True 保留每个时间步输出 x = layers.LSTM(lstm_units, return_sequences=True)(x) x = layers.LSTM(lstm_units // 2, return_sequences=True)(x) # Attention 层:对 LSTM 输出加权 attention = layers.Dense(1, activation='tanh')(x) attention = layers.Flatten()(attention) attention = layers.Activation('softmax')(attention) attention = layers.RepeatVector(lstm_units // 2)(attention) attention = layers.Permute([2, 1])(attention) # 加权求和 x = layers.Multiply()([x, attention]) x = layers.Lambda(lambda z: tf.reduce_sum(z, axis=1))(x) # 输出层 x = layers.Dense(32, activation='relu')(x) x = layers.Dropout(0.2)(x) outputs = layers.Dense(1)(x) model = Model(inputs=inputs, outputs=outputs) model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', metrics=['mae']) return model model = build_cnn_attention_lstm(input_shape=(20, 6)) model.summary()逻辑说明:Conv1D的padding='same'保证输出长度不变,MaxPooling1D把时间步从 20 降到 10,减少 LSTM 的计算量。两层 LSTM 的return_sequences=True是必须的,否则 Attention 拿不到每个时间步的输出。Attention 的实现方式是先用一个 Dense 层算每个时间步的分数,softmax 归一化成权重,再和 LSTM 输出逐元素相乘后求和。RepeatVector和Permute是为了把权重维度对齐到 LSTM 输出。
参数说明:cnn_filters=64是起点,数据量大可以加到 128。kernel_size=3对应三天的局部模式,改成 5 会捕捉更长的模式但参数更多。lstm_units=64配合两层结构,第二层减半是为了压缩表示。学习率 0.001 是 Adam 的默认值,如果 loss 震荡明显降到 0.0005。
3.2 训练集划分与早停策略:别让模型在验证集上过拟合
期货数据不能随机打乱划分,必须按时间顺序切。我一般用 70% 训练、15% 验证、15% 测试。验证集用来做早停,测试集只在最后评估一次。早停的patience设 10,意思是验证集 loss 连续 10 个 epoch 不下降就停。
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau # 假设 X_train, y_train, X_val, y_val 已经准备好 early_stop = EarlyStopping(monitor='val_loss', patience=10, restore_best_weights=True) reduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=5, min_lr=1e-6) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=100, batch_size=32, callbacks=[early_stop, reduce_lr], verbose=1 )restore_best_weights=True是关键,它保证训练结束后模型权重是验证集 loss 最低的那个 epoch,而不是最后一个 epoch。ReduceLROnPlateau在 loss 停滞时自动降学习率,比手动调省事。batch_size=32是常规选择,数据量少于 5000 条可以降到 16。
4. 避坑与排查:相关性分析和模型训练里最容易翻车的五个地方
4.1 现象:Spearman 矩阵全是 0.9 以上,模型却完全不收敛
原因:没有做差分,直接用价格算相关性。所有期货价格都有共同趋势,算出来当然高度相关。模型输入的是价格绝对值而不是收益率,量纲差异巨大,梯度爆炸。
解决:建模前强制做pct_change(),并且检查returns.describe()确认均值在 0 附近。如果某个因子差分后全是 NaN,说明原始数据有缺失,先做前向填充。
4.2 现象:训练 loss 降到 0.001,测试集 loss 是 0.5
原因:时间序列泄露。划分训练集和测试集时随机打乱了,或者用未来数据做了标准化。期货预测里这是最致命的错误。
解决:用train_test_split(shuffle=False)或者手动按索引切。标准化参数只能用训练集的均值和方差,然后应用到验证集和测试集。我一般写一个StandardScaler只在训练集上fit,再transform其他集。
4.3 现象:Attention 权重全是均匀的 1/20
原因:Attention 的 Dense 层初始化太小,或者 LSTM 输出本身没有区分度。常见于 LSTM 层数太少或return_sequences没开。
解决:检查 LSTM 是否设置了return_sequences=True。把 Attention 的 Dense 初始化改成glorot_uniform,或者加一个BatchNormalization在 Attention 之前。如果还是均匀,说明数据本身没有时序模式,考虑换因子。
4.4 现象:验证集 loss 比训练集低
原因:Dropout 在验证集不生效,训练集有 Dropout 导致 loss 偏高。这是正常的,不是 bug。但如果验证集 loss 持续低于训练集很多,说明验证集太简单或者太小。
解决:把验证集比例提高到 20%,或者用 K 折交叉验证(时间序列要用TimeSeriesSplit)。不用纠结这个现象,重点看测试集表现。
4.5 现象:预测曲线比真实曲线滞后一天
原因:模型学到了y[t] = y[t-1]这个恒等映射。因为期货收益率自相关性很弱,模型找不到其他模式,只能复制前一天。
解决:在损失函数里加一个惩罚项,或者把标签改成y[t+1] - y[t]的差分。更直接的办法是检查输入特征里是否包含了当天的收盘价——如果包含了,模型直接抄就行,必须把当天收盘价从特征里去掉。
5. 评估与调参:用 IC 和方向准确率替代 MSE
MSE 只能告诉你预测值离真实值多远,但期货交易更关心方向对不对。我习惯加两个指标:IC(Information Coefficient)和方向准确率。IC 是预测收益率和真实收益率的 Spearman 相关系数,方向准确率是预测涨跌符号和真实符号一致的比例。
from scipy.stats import spearmanr y_pred = model.predict(X_test).flatten() y_true = y_test.flatten() # IC ic, _ = spearmanr(y_pred, y_true) print(f"IC: {ic:.4f}") # 方向准确率 direction_acc = np.mean(np.sign(y_pred) == np.sign(y_true)) print(f"方向准确率: {direction_acc:.4f}")IC 在 0.05 以上就算有效因子,方向准确率超过 0.52 就有实用价值。如果 MSE 很小但 IC 接近 0,说明模型只是拟合了均值,没有预测能力。
调参顺序我一般这样:先调 LSTM 层数和 units,再调 CNN 的 filters 和 kernel_size,最后调学习率和 dropout。每次只动一个参数,记录验证集 IC。Attention 层本身没有太多超参,Dense 的 units 固定为 1 就行。
最后一个技巧:把训练好的模型在测试集上跑 10 次,每次用不同的随机种子,取 IC 的均值和标准差。如果标准差大于均值,说明模型不稳定,需要简化结构或增加数据量。这个习惯帮我省了很多后悔药,希望帮到你。
本文还有配套的精品资源,点击获取