
简介面向毕业设计与课程设计场景该资源是一套完整的期货价格预测实现方案基于相关性分析结合CNN-Attention-LSTM混合模型内含Python源码、数据集、训练好的模型权重及超详细注释覆盖相关性分析、时间步处理、模型定义、训练与预测等关键环节并附带API接口与Web前端配置教程便于直接运行或按需二次扩展。压缩包共29个文件大小约30.28MB以Python脚本、NumPy矩阵、Excel数据表、模型检查点、PDF使用教程和SQL数据库文件为主各类型分别承担数据处理、模型训练、结果展示与文档说明等角色目录结构清晰。目前已有677人学习下载适合计算机、人工智能、自动化等相关专业学生用于毕设、课设或初期项目演示也可作为深度学习进阶的参考项目。代码注释细致能帮助初学者快速理解模型实现细节。1. 期货价格预测毕设项目为什么CNN-Attention-LSTM能拿高分又不容易翻车做期货价格预测的毕业设计最难的不是把模型跑通而是让导师觉得你在做“正经研究”让答辩老师挑不出硬伤。纯LSTM太老套纯Transformer又容易被质疑“是否真的理解金融时序”而基于相关性分析的CNN-Attention-LSTM组合模型恰好卡在了一个既有理论深度又能落地复现的位置上——CNN提取局部形态特征Attention给关键时间步加权LSTM承接长短期依赖再用相关性分析筛掉冗余特征。这套结构做期货预测比单用LSTM的提升通常在5%到15%之间而且每一层都能在论文里单独写出一节。这个标题指向的是一份完整的毕设项目包里面有python源码、数据集、训练好的模型和超详细注释。整篇文章我会把这个项目的设计思路、代码结构、关键参数和踩过的坑全部拆开讲清楚——不管你是打算直接基于它改还是只借鉴架构自己重写都能少走弯路。适合正在做金融时序类毕设的本科生也适合刚入门量化想搭第一个预测模型的研究生。下面从数据预处理逐步讲到模型训练全部按可复现的标准来写。2. 相关性分析选特征别让40个指标拖垮你的模型2.1 为什么必须在建模前先做特征筛选期货行情数据天然就是高维相关的。拿螺纹钢主力合约举例你手上可能有开盘价、收盘价、最高价、最低价、成交量、持仓量、结算价再叠加几个技术指标KDJ、MACD、RSI、布林带轻松凑出三四十列。这些特征之间高度共线——最高价和收盘价的相关系数经常超过0.95MACD本身就是由价格差分算出来的。如果全部塞给CNN-Attention-LSTM模型会把大量容量浪费在重复信息上训练时间拉长还特别容易过拟合。更重要的是金融时序里的噪声远多于信号。一堆无关特征比如某个跟品种基本面毫无关系的宏观指标不仅不贡献预测能力还会干扰Attention模块学到的注意力权重。做相关性分析的直接收益有两个一是把特征维度降下来让模型输入更干净二是产出correlation矩阵热力图和筛选结果表这些东西可以直接贴进论文的“数据预处理”章节答辩时非常加分。2.2 Spearman相关系数为什么比Pearson更适合期货数据Pearson相关系数假设变量之间是线性关系且服从正态分布这在期货价格序列上经常不成立——价格跳空、涨跌停、极端行情都会让分布严重偏离。Spearman秩相关系数先把数据排序成秩再算秩的Pearson相关本质上是评估单调关系而不是线性关系对异常值不敏感得多。我处理期货数据时默认先看Spearman。计算方式直接用pandas的.corr(methodspearman)做出来是一个DataFrame对角线为1其他位置是两个特征的相关系数。筛选时选定一个阈值比如0.7两两相关系数超过这个值就只保留其中一个特征。2.3 手工实现特征筛选完整Python代码与输出解读下面是这个项目中做相关性分析的完整流程。第一步读取数据集第二步算Spearman相关矩阵第三步按阈值剔除高度相关的特征对第四步结合与目标变量通常是未来N期的收益率的相关性做最终取舍。项目中用的是国内期货日线数据CSV格式列名是date,open,high,low,close,volume,hold再加若干技术指标列。import pandas as pd import numpy as np # 1. 读取数据 df pd.read_csv(future_data.csv, parse_dates[date]) df.set_index(date, inplaceTrue) # 2. 构造目标变量未来5日收益率5个交易日后收盘价相对今天的变化 df[target_5d] df[close].shift(-5) / df[close] - 1 # 3. 剔除含有缺失值的行shift导致的 df.dropna(subset[target_5d], inplaceTrue) # 4. 计算Spearman相关矩阵 corr_matrix df.corr(methodspearman) # 5. 筛选与目标变量相关性较高的特征 # 取特征列和目标列的相关系数绝对值 target_corr corr_matrix[target_5d].drop(target_5d).abs().sort_values(ascendingFalse) print(与目标变量相关性Top10) print(target_corr.head(10)) # 6. 手动设定阈值剔除高度相关的特征对 threshold 0.7 to_drop set() features [col for col in df.columns if col ! target_5d] for i in range(len(features)): for j in range(i 1, len(features)): if abs(corr_matrix.loc[features[i], features[j]]) threshold: # 剔除与target_5d相关性较低的那一个 if abs(corr_matrix.loc[features[i], target_5d]) abs(corr_matrix.loc[features[j], target_5d]): to_drop.add(features[i]) else: to_drop.add(features[j]) selected_features [col for col in features if col not in to_drop] print(f保留特征数{len(selected_features)} / {len(features)}) print(保留的特征列表) print(selected_features)这段代码有几个关键点要说明。shift(-5)让目标变量变成未来5天的收益率这是预测的本质——用今天及之前的数据预测未来的收益方向。相关矩阵中如果params和close的Spearman相关系数超过0.7说明它们几乎是同一个信息的两种表述保留其中之一就够了。我在项目里发现常见的结果是原始价格类特征open、high、low、close之间高度相关往往只保留close而技术指标里的MACD、KDJ经常和价格本身的相关性极高也被剔除最终保留下来的通常是收益率类、振幅类、量比类等经过变换的特征。2.4 相关性分析的三个隐藏坑滞后相关、多重共线性、未来函数第一个坑是只算同期相关、不算滞后相关。期货市场的因果关系经常有时间差今天放量可能对应着两三天后的价格启动。我在项目里会额外计算df[volume].shift(1)、shift(2)与target_5d的相关性如果滞后项的相关性明显高于当期就应该把滞后特征加入模型输入而不是剔除。第二个坑是多重共线性不只是两两相关还存在组合共线。三个特征各自之间的两两相关系数都不高但其中一个可以被另外两个线性表示。这种情况靠相关矩阵筛不掉只能结合业务理解或者用VIF方差膨胀因子做辅助判断。第三个坑最致命——未来函数。如果你算技术指标时用到了未来数据比如用当天的收盘价计算MACD而这个MACD又反过来预测当天的收盘价相关性分析的结果会虚高模型在测试集上会表现得异常优秀实盘或样本外测试立刻原形毕露。做特征筛选时必须保证每个特征在时间t只能用到t及之前的信息这是金融时序建模的铁律。3. CNN-Attention-LSTM模型架构每一种子模块解决什么问题3.1 为什么偏要三个模型拼接互补而非堆砌单用LSTM做期货预测的常见问题是输入是一整段连续序列模型容易被早期的噪声干扰而且LSTM的隐藏状态长度有限对非常久远的信息会遗忘。CNN的卷积核适合捕捉价格形态上的局部模式——比如连续阳线、放量突破、均线粘合——这些“形态特征”在原始序列里是局部存在的用卷积核扫一遍就能提取出来。Attention机制解决的是“哪一段历史更重要”的问题模型可以学习到特定时间位置的权重而不是一视同仁地看待所有时间步。三个组件各管一件事CNN管局部特征、Attention管重要性分配、LSTM管时序演化。这个组合在序列预测任务里被反复验证过。它强于纯LSTM的原因在于CNN把长序列压缩成了更高层的语义特征Attention弥补了LSTM的长期遗忘问题LSTM则保证了输出的内容在时间上是连贯的。3.2 模型输入张量怎么构造从原始序列到3D输入模型输入不是直接把CSV数据扔进去而是要构造成(batch, timesteps, features)这样的3D张量。timesteps是回看窗口长度项目里取60——意思是用过去60个交易日的数据预测未来5天的涨跌。数据切分在这个项目里用rolling window的方式而不是随机切分。训练数据是连续的按时间顺序组成样本假设总共有3000条日线数据取窗口长度为60那么第一条样本是第0到59条数据第二条是第1到60条依此类推。这样相邻样本之间有59行重叠数据量会被大幅扩充。下面是数据预处理时窗口切分的核心代码import numpy as np def create_sequences(data, target, seq_length60): X, y [], [] for i in range(len(data) - seq_length): X.append(data[i:i seq_length]) y.append(target[i seq_length]) return np.array(X), np.array(y) # data是标准化后的特征矩阵target是与data对齐的目标值数组 # 假设训练特征矩阵为 train_features标签为 train_labels未来5日收益率 X_train, y_train create_sequences(train_features, train_labels, seq_length60) X_test, y_test create_sequences(test_features, test_labels, seq_length60) print(f训练样本数量: {X_train.shape}) print(f测试样本数量: {X_test.shape})seq_length60决定了模型每次能“看到多远的过去”。取60在日线数据上约等于一个季度的交易日数对中短期趋势预测比较合理。太短了学不到中期趋势太长了会把太久远且不再有影响的历史也塞进来增加计算量。这里的y用的是每行数据对应的目标值即未来5日的收益率不能出现样本内泄漏。3.3 CNN层怎么设计一维卷积还是二维卷积这里有一个大多数毕设论文里都会搞混的点输入是3D张量但实际上用的是Conv1D——一维卷积在最后一个维度特征维度上滑动。卷积核覆盖时间步方向上的局部窗口滑动方向是时间轴。项目中的CNN部分由两层Conv1D加一个MaxPooling1D组成。每层的filters输出通道数设为64和128kernel_size设为3激活函数用的是ReLU。卷积层的作用是感知价格形态比如一个连续三天的放量上涨对应的是一段特定的数值模式卷积核可以学到对这些模式敏感的特征映射。3.4 Attention层实现加性注意力还是乘性注意力Attention在这个项目里用的是加性注意力Bahdanau-style它的核心工作方式是先对所有时间步计算一个score再通过softmax变成概率权重最终把所有时间步的特征加权求和。import tensorflow as tf from tensorflow.keras.layers import Layer class AttentionLayer(Layer): def __init__(self, units64): super(AttentionLayer, self).__init__() self.W tf.keras.layers.Dense(units) self.V tf.keras.layers.Dense(1) def call(self, inputs): # inputs shape: (batch, timesteps, features) score self.V(tf.nn.tanh(self.W(inputs))) # (batch, timesteps, 1) attention_weights tf.nn.softmax(score, axis1) context_vector tf.reduce_sum(inputs * attention_weights, axis1) return context_vector, attention_weightsAttentionLayer里有两个Dense层W先把输入映射到一个隐藏空间V再把隐藏向量压缩成一个标量score。score经过softmax归一化得到注意力权重context_vector是加权求和的结果。这些权重本身就是可以可视化的——把每个时间步的权重画出来能看到模型在预测时重点看了哪些天、忽略哪些天这一张图对于论文里的“模型可解释性分析”非常有用。3.5 LSTM层参数128个单元够不够dropout怎么设在Attention之后接一个LSTM层units设为128。LSTM接收的是Attention输出的上下文向量序列它的作用是建模这个“浓缩后的历史状态”随时间的演化。LSTM层内部的结构这里不展开关键是几个参数。return_sequences不需要因为我们只要最后一个时间步的输出来接全连接层。dropout0.2是输入数据的随机丢弃率recurrent_dropout0.2是循环状态的丢弃率——这两个参数是防止过拟合的主力。金融数据噪声大、信噪比低不用dropout的话训练集loss会快速降到很低但验证集早早就开始上升。3.6 完整模型定义参数表与逐层输出形状把三层结构串起来定义整个模型from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, Conv1D, MaxPooling1D, LSTM, Dense, Dropout, Flatten def build_model(input_shape, lstm_units128): inputs Input(shapeinput_shape) # CNN层 x Conv1D(filters64, kernel_size3, activationrelu, paddingsame)(inputs) x MaxPooling1D(pool_size2)(x) x Conv1D(filters128, kernel_size3, activationrelu, paddingsame)(x) x MaxPooling1D(pool_size2)(x) # Attention层 x, attn_weights AttentionLayer(units64)(x) # LSTM层 x tf.keras.layers.Reshape((-1, 1))(x) x LSTM(unitslstm_units, dropout0.2, recurrent_dropout0.2)(x) # 输出层 outputs Dense(1, activationlinear)(x) model Model(inputsinputs, outputsoutputs) model.compile(optimizeradam, lossmse, metrics[mae]) return model, attn_weights这里有一个值得说明的细节Attention输出的context_vector是2D的(batch, features)但LSTM期望输入3D的(batch, timesteps, features)所以我在中间用Reshape把它变成(batch, N, 1)。这是项目代码里最容易报错的地方原作者用这种处理方式解决了形状不匹配问题。层名输出形状核心参数Input(60, 特征数)回看窗口60Conv1D(60, 64)filters64, kernel_size3, paddingsameMaxPooling1D(30, 64)pool_size2Conv1D(30, 128)filters128, kernel_size3, paddingsameMaxPooling1D(15, 128)pool_size2Attention (输出权重)(15, 1)units64Reshape(15, 1)适配LSTM输入LSTM(128,)units128, dropout0.2Dense(1,)线性激活输出预测值4. 项目代码跑通指南从环境配置到训练出模型的完整步骤4.1 项目目录结构每个文件是干什么的拿到压缩包解压之后里面不只有代码。项目目录大概是这样的结构project/ ├── data/ │ ├── raw/ # 原始期货数据CSV │ └── processed/ # 预处理后的数据 ├── models/ # 训练好的模型文件 │ └── cnn_attention_lstm.h5 ├── notebooks/ # Jupyter调试用的notebook ├── utils/ │ ├── data_loader.py # 数据读取和预处理 │ ├── feature_selection.py # 相关性分析特征选择 │ └── plot_utils.py # 可视化工具 ├── config.py # 全局配置路径和超参数 ├── train.py # 入口训练脚本 ├── predict.py # 加载模型做预测和可视化 └── requirements.txt # 依赖库清单train.py是主入口它会调用utils里的函数完成数据加载、特征筛选、序列构造、模型训练和评估。predict.py加载models里训练好的h5文件对最新数据做预测并画出预测值和真实值的对比曲线。4.2 环境安装Python版本与依赖库清单这个项目基于TensorFlow实现安装环境是整个流程里最容易出问题的环节特别是Windows系统上。我用的建议环境是Python 3.9到3.11。TensorFlow从2.10开始Windows版本不再支持GPU所以如果你用的是Windows笔记本电脑CPU版本凑合能跑——训练几百个epoch的时间可以接受。# 创建虚拟环境推荐避免和系统Python环境冲突 python -m venv venv # 激活虚拟环境Windows venv\Scripts\activate # 激活虚拟环境Linux/Mac source venv/bin/activate # 安装依赖库 pip install tensorflow2.13.0 pip install pandas2.0.3 pip install numpy1.24.3 pip install matplotlib3.7.2 pip install scikit-learn1.3.0 pip install seaborn0.12.2requirements.txt文件里也列了同样的库清单。如果你在国内网络环境下安装速度很慢可以把pip源换成清华源或者阿里源比如pip install -i https://pypi.tuna.tsinghua.edu.cn/simple tensorflow2.13.0。特别提示TensorFlow版本不要随意升级2.16及之后的版本在某些API接口上有破坏性变更项目源码是按2.13的API写的。4.3 训练模型的完整流程命令、日志解读、训练曲线环境装好之后直接运行训练脚本python train.py训练过程中terminal会输出每个epoch的loss和mae。对于1300条左右的样本这个模型在小数据集上十几个epoch就能让训练loss降到很低的水平但这时大概率已经过拟合了。我在项目中把epoch数设为100加了早停机制EarlyStoppingpatience设为15——连续15个epoch验证集loss不下降就自动终止恢复到最佳权重。训练完成之后会生成best_model.h5到models目录同时保存一张训练和验证loss曲线图。判断模型质量的关键不是看训练loss而是看验证loss和训练loss之差。如果验证loss还在下降而训练loss已经很低说明还有继续训练的空间如果验证loss开始上升而训练loss继续降就是过拟合的信号需要增加dropout或者减少epoch。4.4 加载模型做预测predict.py的使用方法与输出解读模型训练完后用predict.py对测试集样本做预测。这段代码的路径和文件名需要注意模型h5文件必须存在数据预处理方式必须和训练时完全一致——包括标准化参数、特征列顺序、窗口长度。# predict.py 核心片段 import numpy as np import tensorflow as tf import pandas as pd from sklearn.preprocessing import StandardScaler import matplotlib.pyplot as plt # 1. 加载训练好的模型 model tf.keras.models.load_model(models/best_model.h5, custom_objects{AttentionLayer: AttentionLayer}) # 2. 重新加载数据特征列顺序要保持一致 data pd.read_csv(data/processed/processed_data.csv) features data[selected_features].values # 3. 标准化用之前训练时保存的scaler参数 # 注意fit_transform是在训练时做的预测时只用transform scaler StandardScaler() # 实际工程中需要从训练阶段保存scaler对象这里简化演示 features_scaled scaler.fit_transform(features) # 4. 构造最后一个样本取最后60行作为输入 X_last features_scaled[-60:].reshape(1, 60, len(selected_features)) # 5. 预测 pred model.predict(X_last) print(f预测未来5日收益率: {pred[0][0]:.4f})预测输出的是一个标量数值表示未来5日收益率的预测值。实际使用中不应该直接把这个数值当成“一定涨xx%”另一方面需要把它当作一个信号去看方向——如果预测值大于0则看多小于0则看空。我一般再叠加一个阈值判断只有在预测值的绝对值超过历史预测分布的80%分位数时才做交易决策否则观望。毕竟预测的数值精度很难做到足够高但方向的准确率是有参考价值的。4.5 从predict.py到论文图表画出真实值与预测值的对比曲线论文需要对比图。在predict.py末尾会生成一张预测值vs真实值的图用matplotlib画两条曲线一条是测试集的真实target一条是模型预测值。把预测序列往上平移或者做钝化处理往往能提升视觉上的拟合效果——但这只是改图不是改模型。需要说明的是不要看着图里的拟合效果好就觉得模型很准。回归类预测的可视化对比图天然有“看起来不错”的效果因为预测值和真实值都有很强的自相关性。真正评估模型要看的是方向准确率也就是符号一致性预测值和真实值同为正或同为负的样本占比以及RMSE和MAE的绝对值大小。5. 毕设项目避坑指南五个最容易踩翻车的环节5.1 特征标准化泄漏测试集信息混进训练集现象训练时验证集loss已经很低了但拿最新的真实行情数据去预测时输出的结果离谱到不可思议甚至出现预测值恒定的情况。原因调用scaler.fit_transform()时对整个数据集做了标准化包括测试集部分。fit过程会计算整个数据集的均值和标准差而这些统计量在训练时是不可知的相当于模型“偷看”了未来的数据分布。这在回测时会让指标虚高实盘立刻现原形。解决标准化必须严格分成两步。训练集上用scaler.fit(train_data)计算均值和标准差测试集只用scaler.transform(test_data)。更严谨的做法是先按时间划分训练集和测试集再做标准化而不是先标准化再切分否则会把未来样本的统计量资源泄漏进去。5.2 样本顺序被打乱时间序列不能随机划分现象模型在训练集上的表现和随机切分出的测试集表现都好但按时间顺序的样本外测试一塌糊涂。原因时间序列数据不能用train_test_split(shuffleTrue)随机打乱划分。相邻数据之间天然有强相关性如果测试集随机分布在整个时间区间内模型训练时能见到的“未来”片段泄露到了测试集里评估结果虚高。解决用时间顺序切分。最常用的是前80%的数据做训练后20%做测试如果要更严谨用时间序列交叉验证。项目源码里已经按时间顺序切分了我不建议改动它。5.3 未来函数隐患技术指标计算时引用未来数据现象相关矩阵里某些特征和目标变量的相关性高达0.9以上模型训练loss趋近于零预测准得像在作弊。但实际用的时候完全不准。原因某些技术指标的计算公式里使用了未来的数据。比如用次日的收盘价计算当日的动量指标或者数据清洗时用全序列的均值填充缺失值。数据文件本身如果不是严格按时间单向生成的也很容易在预处理步骤里引入未来函数。解决写一个检查函数把原始数据按时间正序排列再用shift(1)对特征整体做滞后变换然后重跑一遍特征对目标变量的相关性如果原来相关性和滞后后的相关性差异巨大说明原始特征里可能存在未来信息。另外所有技术指标计算必须用rolling窗口扫描实现不能用全序列向量化公式。5.4 维度不匹配报错Attention层和LSTM层的形状连接问题现象模型在model.fit()时直接报异常提示ValueError: Input 0 of layer lstm is incompatible with the layer或者expected ndim3, found ndim2。原因Attention层输出的上下文向量是2D的(batch, features)而LSTM层要求输入是3D的(batch, timesteps, features)。中间缺了一个维度扩展的步骤。解决在Attention层和LSTM层之间加一个Reshape层或者tf.expand_dims操作。代码里用的Reshape((-1, 1))是把2D的向量直接变成N行1列的2D序列满足LSTM的输入要求。这个细节非常容易忽略项目源码里的注释专门标注了这一步的原因。5.5 过拟合与早停看着loss曲线往下走就以为没问题现象训练loss持续降低模型在回测表现非常优异但换一段不同时间段的行情测试效果打对折。原因期货数据信噪比极低模型很容易把噪声当信号学进去训练集上拟合得越完美泛化能力反而越差。解决在model.fit()里加上EarlyStopping回调监控验证集loss设patience15并在训练完成后用测试集做最终评估。同时观察训练曲线如果训练loss低于验证loss很多提高LSTM层的dropout到0.3-0.4或减小LSTM单元数。我一般建议把dropout从0.2起步往0.4方向调每次调0.05观察变化。6. 进阶玩法滚动训练与动态再训练模型训练完不代表可以一直用下去期货市场行情结构经常变化——螺纹钢和豆粕的波动特征完全不同同一品种不同年份也差异很大。固定模型长期使用效果会持续衰减所以毕设之外如果要做得更扎实可以做一个滚动训练。# 简单的滚动再训练示例 # 每次新数据到来时用最近6个月的数据重新训练模型 from datetime import timedelta def rolling_retrain(data, retrain_days120, seq_length60): last_date data.index.max() start_date last_date - timedelta(daysretrain_days) train_data data[data.index start_date] # 重新构造训练集 scaler StandardScaler() train_scaled scaler.fit_transform(train_data[selected_features]) X_train, y_train create_sequences(train_scaled, train_data[selected_features].values, seq_length) # 重新训练模型保留原有结构 model build_model(input_shape(seq_length, len(selected_features))) model.fit(X_train, y_train, epochs50, batch_size32, verbose0) return model滚动训练的思路是永远用最近的数据重新拟合模型抛弃“远古数据”的影响。注意这个片段里标准化参数也是重新fit的因为新数据的分布可能已经变了。在实盘或模拟盘环境里每周或每月做一次重新训练比一个模型用半年稳妥得多。识别模型是否失效的方法也简单。记录每次预测的方向准确率如果连续20个交易日的预测准确率低于50%说明行情状态变了该触发重新训练了——这就是动态再训练的核心逻辑。我的经验是这套CNN-Attention-LSTM架构的价值不在于预测精度高到可以实盘盈利而在于它是做金融时序毕设时“能讲清楚”且“不容易被推翻”的一个强基线方案。我把这份项目的代码从数据到模型全部拆开重写了一遍之后最大的感受是模型结构没有秘密真正的坑全在数据预处理和特征工程上。相关性分析那一步做干净后面的训练过程基本不会出大问题。希望帮到你。本文还有配套的精品资源点击获取