☰
基于相关性分析的CNN-Attention-LSTM期货价格预测实战解析
2026/10/1 10:42:14 网站建设 项目流程

简介:基于相关性分析的CNN-Attention-LSTM期货价格预测模型,是一份完整的深度学习时间序列预测工程,适合用于毕业设计、课程设计及人工智能实战练习。包内围绕相关性分析→时间步处理→模型训练→预测的主线,提供Python源码、数据集、训练好的模型及详细注释,能帮助读者快速复现并理解特征筛选与注意力机制在价格预测中的结合方式。压缩包共29个文件、大小约30.29MB,包含8个py脚本(模型搭建、训练、预测、API封装等)、6个npy数据文件(训练/测试输入与标签)、3个Excel表格(原始数据及处理结果)、模型checkpoint文件、两个PDF教程和README说明,文件类型覆盖代码、数据、文档和权重,便于对照学习。目前已有809人加入学习,附带的模型权重可直接运行验证,配套教程从环境配置到算法解析均有涉及,适合需要完整项目参考的初学者和答辩学生。

1. 基于相关性分析的CNN-Attention-LSTM期货价格预测:拿到这份源码包先别急着训练

在量化时序预测的毕设和课设里,裸LSTM早已是过去式。这份压缩包最吸引我的不是“能跑通的LSTM”,而是它把相关性分析、CNN特征提取、Attention加权和LSTM串成了完整的闭环。换句话说,特征不是随便选,而是先用相关性分析筛过一遍;时序不是LSTM单打独斗,而是CNN先抓短期形态、Attention再给关键时间步加权、最后LSTM承接长期依赖。这套组合在期货价格这类强噪声、弱周期的数据上,比单纯堆层数要稳得多。适合两类人:一是深度学习方向的毕业生,正愁没有一套数据、代码、模型权重、预测接口齐全的完整项目;二是想快速验证神经网络做期货价格预测可行性的从业者。先说结论,这份资源从原始Excel数据到训练好的ckpt权重再到能直接调用的预测API,链条是完整的,不是只有模型文件的半成品。

2. 相关性分析 + CNN + Attention + LSTM:这种组合为什么适合期货价格预测

2.1 相关性分析:先用Spearman把输入特征筛一遍

期货价格预测的第一个难点不是模型,是特征。原始数据里往往同时存在成交量、持仓量、开盘价、最高价、最低价、收盘价等多个字段,如果一股脑全部塞进模型,LSTM的收敛速度和最终精度都会受影响。资源里单独放了一个“相关性分析.py”,核心思路就是对候选特征做Spearman相关性分析,再结合热力图筛掉与目标变量相关性弱的字段。为什么用Spearman而不是Pearson?因为期货价格和成交量序列通常不满足正态分布,而且存在明显的不对称性,Spearman基于秩相关,对这类的鲁棒性更好。

import pandas as pd import numpy as np from scipy.stats import spearmanr import seaborn as sns import matplotlib.pyplot as plt df = pd.read_excel("原数据/玉米期货数据周报7.25.xlsx", engine="openpyxl") # 去掉日期列,只保留数值型特征 numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist() corr_matrix = pd.DataFrame(index=numeric_cols, columns=numeric_cols, dtype=float) for col1 in numeric_cols: for col2 in numeric_cols: rho, _ = spearmanr(df[col1], df[col2]) corr_matrix.loc[col1, col2] = rho plt.figure(figsize=(10, 8)) sns.heatmap(corr_matrix, annot=True, fmt=".2f", cmap="RdBu_r", center=0) plt.title("Spearman Correlation Heatmap") plt.savefig("我是热力图.png", dpi=150, bbox_inches="tight")

这段代码先是把所有数值列两两组合算一遍Spearman相关系数,生成一个对称的相关矩阵,然后保存成热力图。参数上需要注意两点:annot=True会把具体数值标在格子里,方便肉眼挑选强相关特征;cmap="RdBu_r"用红蓝配色,正值偏红、负值偏蓝,一眼就能看出哪些字段与收盘价强相关。我一般会优先保留与目标变量相关系数绝对值在0.4以上的特征,低于0.2的直接排除,这样能在不损失有效信息的前提下把输入维度降下来。

压缩包里的“相关性分析数据.npy”就是这一步的产物。需要提醒的是,热力图只是参考,特征之间的共线性也要顺带看两眼。比如最高价和最低价往往与收盘价高度相关,但三者自身也高度共线,全塞进去反而会放大噪声。所以在跑资源里的“相关性分析.py”之后,建议手动删掉冗余特征再重新生成npy,这步操作对后续模型精度的影响,比调LSTM的神经元数量还大。

2.2 数据文件清单:拿到解压包先对应上这一张表

第一次解压这份资源的人,最容易被一堆.npy和一串my_modelv1.ckpt搞晕。我拆完以后把关键文件按“数据流”整理成一张表,按这个顺序看就清楚了:

文件 / 目录数据流位置作用说明
原数据/玉米期货数据周报7.25.xlsx数据源头商品期货历史周报,含量价字段
相关性分析用表.xlsx特征筛选相关性分析脚本的输入Excel
相关性分析数据.npy特征筛选结果保留字段的中间数据
时间步处理.py样本构造把序列数据切成滑窗样本
train_x.npy / train_y.npy模型输入输出滑窗后的训练样本与目标
test_x.npy / test_y.npy验证集用于评估与画对比图
checkpoint/训练产物my_modelv1 / my_modelv2 权重文件
cnn_attention_lstm.py模型定义CNN-Attention-LSTM类
pred.py / pred_API.py推理与接口输出 pred.npy、对外提供预测

这张表的价值在于,它能帮你快速判断“这份资源到底能不能跑”。有数据源头、有预处理产物、有模型定义、有训练好的权重、有推理脚本,五样齐全才算完整。很多网上下载的源码包只有模型文件和一段训练代码,数据要自己找,那份折腾成本往往比写模型还高。这份资源把数据链路补全了,这也是我愿意花时间拆它的直接原因。

2.3 模型结构:CNN提取短期形态,Attention加权关键时间步,LSTM承接长期依赖

在“models/cnn_attention_lstm.py”里,模型的整体思路是输入二维矩阵(batch, time_step, feature_dim),先经过一维卷积层提取局部趋势特征,再送入Attention层对每个时间步的重要性做加权,最后交给LSTM捕捉长周期依赖,末端接全连接层输出预测价格。CNN在这里处理的是“连续几个交易周期内价格形态”的短时特征,比如三连阳、放量突破,这些局部模式用卷积核扫一遍就能被捕捉;Attention层的作用是告诉LSTM“最近哪几天更重要”;真正的长期趋势由LSTM负责。

import tensorflow as tf from tensorflow.keras.layers import (Input, Conv1D, LSTM, Dense, Dropout, GlobalAveragePooling1D, Multiply, Reshape) from tensorflow.keras.models import Model def cnn_attention_lstm(time_step, feature_dim, lstm_units=64): inputs = Input(shape=(time_step, feature_dim)) # 1D卷积提取局部特征,保持时间步数不变 x = Conv1D(filters=32, kernel_size=3, padding="same", activation="relu")(inputs) x = Dropout(0.1)(x) # 简单自注意力:对卷积输出的每个时间步做一个全局池化再扩维,用来算权重 attn_weight = Dense(1, activation="sigmoid")(x) x = Multiply()([x, attn_weight]) # LSTM承接时间维度上的依赖关系 x = LSTM(lstm_units, return_sequences=False)(x) x = Dropout(0.2)(x) outputs = Dense(1)(x) model = Model(inputs, outputs) return model

这段代码突出了这个资源里模型组合的一个关键顺序:CNN不加池化层,用padding="same"保持时间步长度不变,因为后面接的Attention和LSTM都依赖完整的时间维度信息。attn_weight是一个Dense(1)输出,相当于给每个时间步打分,再通过Multiply乘回卷积输出。我一般会把lstm_units从64起步,特征维度落在20附近时效果最好,超过50维的输入容易让LSTM承担过多噪声,反而要加大dropout来压。先跑通默认参数,再考虑往上调,这个顺序几乎不会错。

2.4 时间步处理:把周报序列切成滑窗样本

模型不能直接吃整段价格序列,需要先把数据改造成“用前N期预测下一期”的监督学习格式。资源里的“时间步处理.py”干的活就是滑窗。假设时间步取10,那么第1到10期作为train_x[0],第11期作为train_y[0],然后窗口往后滑一格。

import numpy as np def create_sliding_window(data, time_step=10): x, y = [], [] for i in range(len(data) - time_step): x.append(data[i:i + time_step, :-1]) y.append(data[i + time_step, -1]) return np.array(x), np.array(y) # 假设已经通过相关性分析筛选出特征,最后一位是预测目标(比如收盘价) arr = np.load("相关性分析数据.npy") train_x, train_y = create_sliding_window(arr, time_step=10) print(train_x.shape, train_y.shape)

这段代码里有个细节需要注意:data[i:i + time_step, :-1]取出的是特征部分,目标值是窗口末端后一个周期的收盘价。time_step的选取直接影响训练样本量,窗口越大样本越少。我的经验是,在每周报数据上,8到12比较合适,取到20以上样本量会骤减,模型容易欠拟合。

3. 训练复现:环境选型、脚本顺序与checkpoint的加载逻辑

3.1 运行环境与依赖版本:别用TensorFlow 2.11以上版本

我复现这份源码时用的是Python 3.9 + TensorFlow 2.10的组合,全套跑下来没有遇到API层面的坑。依赖清单大致如下:

依赖包推荐版本说明
tensorflow2.10.xCPU版本就能跑,数据量不大
numpy1.23.x与TF 2.10兼容性最好
pandas1.5.x读Excel和CSV
openpyxl3.0.x读取xlsx格式
scipy1.9.xSpearman相关性分析
matplotlib / seaborn3.6.x / 0.12.x画热力图和对比曲线

不建议直接用TensorFlow 2.13及以上版本,因为从2.11开始Keras的API组织方式发生过调整,Conv1D接LSTM时对shape的处理规则也有变化,加载旧权重更容易报出莫名其妙的维度错误。如果实在要用新版本,就把模型定义和权重分开看待,模型用当前的API重建,只加载旧的权重数值,不要指望旧模型结构在新环境下能无缝反序列化。

3.2 三个脚本的正确执行顺序

解压后别一头扎进去训练,先按依赖关系理清脚本顺序。这个包里的数据流水线是:“相关性分析.py”负责把Excel筛成相关性分析数据.npy,“时间步处理.py”把npy切成train_x/train_y/test_x/test_y,“train_v2.py”读这四个npy文件训练模型。

# 第一步:从Excel中筛选强相关特征并生成npy中间文件 python 相关性分析.py # 第二步:将中间文件切成滑窗样本,输出训练和测试用的npy python 时间步处理.py # 第三步:读取npy开始训练,同时把训练好的权重保存到checkpoint目录 python train_v2.py

如果只是想复现最终的预测效果,可以直接跳过前两步,因为压缩包里已经附带处理好的train_x.npy、train_y.npy、test_x.npy、test_y.npy。此时直接跑train_v2.py或pred.py就行,无需重复数据预处理。但如果你是打算换自己的数据重新做一遍,就必须从相关性分析那一步完整走起。

3.3 训练脚本里的关键参数与checkpoint恢复机制

train_v2.py内部的大致逻辑是:加载npy → 归一化 → 实例化cnn_attention_lstm模型 → 编译并训练 → 保存权重到my_modelv1.ckpt和my_modelv2.ckpt。这里最有价值的坑点在于训练时对时间序列的处理方式。

import numpy as np from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint from models.cnn_attention_lstm import cnn_attention_lstm train_x = np.load("train_x.npy") train_y = np.load("train_y.npy") test_x = np.load("test_x.npy") test_y = np.load("test_y.npy") # 对序列做归一化操作,避免LSTM内部激活函数饱和 mean_x, std_x = train_x.mean(axis=0), train_x.std(axis=0) + 1e-8 train_x = (train_x - mean_x) / std_x test_x = (test_x - mean_x) / std_x model = cnn_attention_lstm(time_step=train_x.shape[1], feature_dim=train_x.shape[2]) model.compile(optimizer="adam", loss="mse") # shuffle=False 很关键,时间序列不能随机打乱 model.fit( train_x, train_y, validation_data=(test_x, test_y), epochs=100, batch_size=32, shuffle=False, callbacks=[ EarlyStopping(monitor="val_loss", patience=10, restore_best_weights=True), ModelCheckpoint("checkpoint/my_modelv1.ckpt", save_best_only=True) ] )

这其中有几个参数值得展开。shuffle=False是最容易被忽略的,普通分类任务里shuffle能提升泛化能力,但时间序列一旦打乱,就相当于让模型看到了“未来数据”,验证集评估会严重失真。EarlyStopping的patience我一般调到10,期货价格序列噪声大,验证loss会反复震荡,patience太小容易过早停掉。shuffle、batch_size和epochs这三者在时间序列任务里互相牵制,batch_size我习惯从32开始,数据量小就降到16。ModelCheckpoint设置save_best_only=True,训练结束后留下的就是验证集上表现最好的权重,这份资源的my_modelv1.ckpt和my_modelv2.ckpt就是这种机制下生成的。

3.4 用pred.py快速验证训练结果

训练或加载权重之后,下一步就是预测。pred.py做的事很直接:加载test_x,调用模型得到预测值,把结果存成pred.npy,再和test_y.npy一起画对比曲线。这一步能直观确认模型到底学到了价格走势,还是只学了一个均值常量。

python pred.py

跑完之后,注意对比pred.npy和test_y.npy的长度。正常情况下两者长度一致,如果发现预测点比真实值少了一段,多半是滑窗时丢弃了尾部样本,需要回看时间步处理脚本的切片逻辑。这是整条链路里最容易出现隐性bug的位置,下面一章专门展开讲。

4. 避坑指南:checkpoint加载、数据对齐与预测曲线翻平的典型翻车

4.1 加载ckpt报错:模型结构和权重文件对不上

现象:跑推理脚本时抛出一段类似AssertionError或Unknown layer的异常,提示找不到某个自定义层,或者维度不匹配。

原因:Keras的load_model在反序列化时会去查找模型定义里用到的自定义层。这个资源里的Attention层是写在cnn_attention_lstm.py里的自定义组件,如果脚本只加载.ckpt权重而没有先把模型类实例化出来,加载器就无法识别结构。还有一个常见情况是my_modelv1和my_modelv2对应的网络结构不完全一样(比如时间步或特征维度不同),用错权重文件自然报错。

解决:先实例化模型,再手动加载权重。确保time_step和feature_dim与权重文件训练时保持一致。

from models.cnn_attention_lstm import cnn_attention_lstm time_step = int(train_x.shape[1]) feature_dim = int(train_x.shape[2]) model = cnn_attention_lstm(time_step, feature_dim) model.load_weights("checkpoint/my_modelv1.ckpt")

这么做的好处是绕开了Keras对自定义层的序列化检查,也可以先检查权重文件里保存的模型结构再决定用哪个ckpt。

4.2 预测曲线和真实曲线错位

现象:把pred.npy和test_y.npy叠加画图,发现曲线形状大致相似,但对不齐,像被平移了一段。

原因:滑窗构造样本时,test_x的最后一批样本对应的test_y在时间上比pred的当前索引晚一个time_step。如果推理脚本里没有同步对齐索引,画图时自然差出一个窗口长度。

解决:在推理脚本里,把比对起点统一到time_step之后的位置。常见做法是给真实序列的前time_step个点直接赋空值,让预测曲线和真实曲线在同一起点开始绘制。

4.3 归一化逆变换精度丢失

现象:模型预测的loss很低,但画出来的预测曲线是一条接近水平的直线,价格波动几乎消失。

原因:训练时对目标值做了归一化,但预测后没有用训练时的mean和std做逆变换,或者推理脚本里用测试集均值重新归一化了一遍,导致整体数值被压缩到很窄的区间。

解决:把训练阶段的归一化参数存下来,推理时复用。最稳妥的方式是在train_v2.py中把mean_y和std_y一起保存到文件,推理时直接加载,不要在预测阶段重新计算。

np.save("mean_y.npy", train_y.mean(axis=0)) np.save("std_y.npy", train_y.std(axis=0) + 1e-8) pred_price = pred * np.load("std_y.npy") + np.load("mean_y.npy")

我遇到过不少次这类问题,根源都是训练和推理两边对数据预处理的理解不一致。

4.4 训练时验证集loss震荡下不来

现象:训练集的loss持续下降,验证集loss反复震荡甚至上升,早停机制还总在最优值附近被触发。

原因:大概率是数据未按时间顺序切分,或模型参数量相对样本量过大。时间序列的验证集必须取自训练集之后的时间段,不能在全体数据上随机抽样。另外LSTM单元数设到128以上,样本量只有几百条时极易过拟合。

解决:按时间序列的前80%做训练、后20%做验证。同时给LSTM层加dropout,并把lstm_units降到32或64起步。我一般会先让模型在100个epoch内快速看到验证集loss的形状,如果验证集在10个epoch内就开始反弹,就先调小模型体积,而不是硬扛。

4.5 Excel编码与日期格式问题

现象:跑“相关性分析.py”时pd.read_excel报错,或者读出的日期列全是NaT。

原因:Windows环境下Excel文件可能存在区域设置问题,日期格式不统一也是常见原因。有些周报的日期列是文本格式,有些系统生成的xlsx日期列会自动变成纯数值。

解决:读取时指定dtype并统一解析日期格式。

df = pd.read_excel( "原数据/玉米期货数据周报7.25.xlsx", engine="openpyxl", dtype={"日期": str} ) df["日期"] = pd.to_datetime(df["日期"], errors="coerce")

对于人工维护的周报数据,errors="coerce"能容错坏数据,生成NaT后可以统一剔除,不要因为一行脏数据让整个预处理流程崩溃。

5. 进阶用法:把pred_API.py接到自己的策略里,并用基线对比快速验证模型有效性

5.1 把预测接口接入自有数据

这份资源里最实用的文件其实是pred_API.py,它把整个推理过程封装成了一个可调用的接口。默认情况下,接口读入的是(time_step, feature_dim)形状的二维矩阵,返回一个一维的预测值数组。接自己的数据时,我一般这样用:

import numpy as np from models.cnn_attention_lstm import cnn_attention_lstm model = cnn_attention_lstm(time_step=10, feature_dim=8) model.load_weights("checkpoint/my_modelv2.ckpt") def predict_next_price(feature_window, mean_x, std_x, mean_y, std_y): # 输入:最近10期的特征窗口 x = (np.array(feature_window) - mean_x) / std_x x = x.reshape(1, 10, -1) pred_normalized = model.predict(x, verbose=0) return pred_normalized[0, 0] * std_y + mean_y

接入前需要确认三个对齐点:特征字段数量是否与训练时一致、特征顺序是否与训练时一致、归一化参数是否来自训练集,没有对齐的接入不可能得到有效结果。

5.2 快速验证模型还有没有效:对比随机基线和上一期基线

模型权重是旧数据训练出来的,直接拿到当下期货行情里,效果可能已经退化。我通常用两种基线来做快速判断。第一种是“上一期价格直接当本期预测”,第二种是随机模型。对比方式很简单:

from sklearn.metrics import mean_absolute_error # pred是模型输出,actual是真实价格,baseline_last是上一期实际值 mae_model = mean_absolute_error(actual, pred) mae_baseline = mean_absolute_error(actual, baseline_last) print("model mae:", mae_model) print("baseline mae:", mae_baseline)

如果模型MAE与“上一期价格直接预测”的MAE接近甚至更高,说明模型对当下行情已经失去体感,需要重训或者重新做特征筛选。这也是我从那次翻车里养成的习惯:新数据进来先跑热力图和基线对比,再谈参数调优,而不是抄起旧权重直接上实盘。希望帮到你。

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

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

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

立即咨询