尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CNN-Attention-LSTM期货价格预测模型详解与Python实战

CNN-Attention-LSTM期货价格预测模型详解与Python实战 简介这是一份基于相关性分析的 CNN-Attention-LSTM 期货价格预测完整项目面向计算机相关专业课程设计、期末大作业及实战练习人群。项目由导师指导并获 98 分覆盖从数据相关性分析、时间步处理、模型构建到预测 API 发布的完整流程提供 8 个 Python 源码文件、6 个 numpy 处理数据、3 个 Excel 表格、预训练模型 checkpoint、使用教程 PDF 及 README共 29 个文件压缩包约 30MB。代码中附详细注释便于理解 CNN 特征提取、Attention 加权与 LSTM 时序建模的结合方式同时包含训练与预测脚本支持直接运行和二次开发。目前已有 258 人学习下载适合需要完整项目参考、快速上手深度学习方法完成期货价格预测任务的读者。1. 为什么期货价格预测要用CNN-Attention-LSTM组合期货价格是典型的高噪声、非平稳、含长短期依赖的时间序列。单独用LSTM做预测长序列输入时早期信息容易被遗忘门缓慢稀释且所有时间步被等同看待价格启动点、放量点这类关键信号很难被突出。CNN在这个场景里不是用来做图像识别而是用一维卷积在时间轴上提取局部形态特征比如三连阳、突破前高、量价背离这些短模式Attention层再对这些模式打分让LSTM后续建模时知道该把注意力放在哪一段。三部分串联后特征提取、权重分配、时序推理各管一段比纯LSTM在玉米这类单品种连续合约上更容易拟合出趋势拐点。这个项目是课程设计用途适合需要把深度学习模型落地到真实金融数据的计算机专业学生源码、数据集、模型权重都齐可以直接复现并修改成自己的实验。2. 相关性分析预处理从热力图筛选玉米期货强相关特征拿到源码包后数据处理链路很清晰原数据里有data(1).sql、玉米期货数据周报7.25.xlsx还有一个处理后的数据表.xlsx。我一般会先跑相关性分析.py对处理后数据做特征筛选再跑时间步处理.py构造监督样本。2.1 三种数据文件的关系与选择包里数据分三个层次data(1).sql是从数据库导出的原始行情快照玉米期货数据周报7.25.xlsx是带周度统计的Excel报表处理后的数据表.xlsx是清洗对齐后的宽表。处理后的数据表每一行代表一个交易日列包含开盘价、最高价、最低价、收盘价、成交量、持仓量、部分技术指标。这个宽表就是相关性分析的直接输入SQL文件通常只在你想回溯原始字段或补充其他品种时才用得上。2.2 皮尔森相关系数计算与热力图阈值相关性分析.py做的事是计算每个候选特征与目标列一般是收盘价的皮尔森相关系数再输出热力图。核心代码如下import pandas as pd import numpy as np from scipy.stats import pearsonr import matplotlib.pyplot as plt import seaborn as sns df pd.read_excel(处理后的数据表.xlsx, index_col0) target close features [c for c in df.columns if c ! target] corr_matrix df.corr(methodpearson) # 筛选与目标列强相关的特征阈值取0.4 selected [] for feat in features: r corr_matrix.loc[feat, target] if abs(r) 0.4: selected.append(feat) print(入选特征:, selected) plt.figure(figsize(10, 8)) sns.heatmap(corr_matrix.loc[selected [target], selected [target]], annotTrue, fmt.2f, cmapRdBu_r, center0) plt.savefig(热力图.png, dpi150, bbox_inchestight)逻辑说明先用df.corr(methodpearson)一次性算完全部两两相关系数再取目标列与其他特征的交叉值做阈值筛选。热力图叠加选中特征之间的相关性是为了防冗余因为两个特征与目标都相关不等于两个都该进模型它们彼此强相关时保留一个即可。annotTrue, fmt.2f让格子里的相关系数保留两位小数方便答辩时直接截图。皮尔森系数适合线性关系明显的数据期货价格序列里开盘价、最高价、最低价与收盘价天然高度线性相关按0.4阈值能保留一批强特征但对于成交量这类非线性影响变量即使相关系数不高也可以先留着后续让CNN自动提取关系。2.3 时间步处理构造监督学习样本相关性分析之后是时间步处理.py这一步把宽表切成有监督样本也就是训练集里出现的那几个.npy文件的来源import numpy as np import pandas as pd df pd.read_excel(处理后的数据表.xlsx, index_col0) WINDOW 30 # 用过去30个交易日预测下一个交易日收盘价 data df[selected [target]].values # Min-Max归一化注意先fit再transform避免信息泄漏 min_val data.min(axis0) max_val data.max(axis0) data_norm (data - min_val) / (max_val - min_val 1e-8) X, y [], [] for i in range(len(data_norm) - WINDOW): X.append(data_norm[i: i WINDOW]) y.append(data_norm[i WINDOW, -1]) # 最后一列是close X np.array(X) # shape: (样本数, 30, 特征数) y np.array(y) # shape: (样本数,) # 按时间顺序切分前80%做train后20%做test split int(len(X) * 0.8) train_x, train_y X[:split], y[:split] test_x, test_y X[split:], y[split:] np.save(train_x.npy, train_x) np.save(train_y.npy, train_y) np.save(test_x.npy, test_x) np.save(test_y.npy, test_y)逻辑说明滑动窗口代码里X.append(data_norm[i: i WINDOW])取的是连续30行y.append(data_norm[i WINDOW, -1])取的窗口后第一天的收盘价两者错开一个时间步模型的任务就是用前30天的多变量序列预测未来一天的价格。归一化必须先用训练数据计算出min和max再应用到全部数据。窗口大小WINDOW是可调参数30在期货日线上约等于一个半月的交易日数量对玉米这类趋势性品种来说能覆盖一段完整的回调或上涨波段。2.4 数据切分时注意时序泄漏切分时按时间顺序切而不是随机切这一点对时间序列模型非常关键。随机切分会让训练集混入未来样本模型在验证集上表现虚高真实交易时却完全失效。相关性分析数据.npy在这个流程里起缓存作用跑过一次相关性分析后把选中的特征索引保存下来后面时间步处理直接读取避免每次重复计算。提示如果打开train_x.npy发现shape是(样本数, 30, 特征数)说明滑动窗口逻辑没有问题如果shape只有两维说明特征维度没展开检查是否在构造窗口时用了values.reshape(-1, 1)。3. CNN_Attention_LSTM模型结构与张量在各层的变换模型定义集中在cnn_attention_lstm.py。这个文件是整套源码的核心理解了张量怎么流动后面调参和改动才有方向。3.1 三个子模块的职责划分CNN部分是一维卷积在时间维度上滑过30个时间步提取局部特征模式比如连续几天的上涨斜率、放量区间的形态Attention部分对卷积输出的每个时间步学习一个权重突出对最终预测贡献大的时间段LSTM部分接收加权后的特征序列建模长短期依赖并输出最后一个隐藏状态再接全连接层映射成价格值。简单说CNN负责找模式Attention负责分配注意力LSTM负责按时间顺序理解这些模式。3.2 用Keras实现组合模型import tensorflow as tf from tensorflow.keras import layers, Model def build_cnn_attention_lstm(input_shape, lstm_units64, dropout0.2): inputs layers.Input(shapeinput_shape) # (batch?, time_step30, features?) # 1) Conv1D提取局部形态特征paddingsame保持时间步数不缩减 x layers.Conv1D( filters32, kernel_size3, paddingsame, activationrelu )(inputs) x layers.BatchNormalization()(x) # 2) Attention对每个时间步学习重要性权重 score layers.Dense(1, activationrelu)(x) # (batch, 30, 1) score layers.Flatten()(score) # (batch, 30) attn_weights tf.nn.softmax(score, axis-1) # 归一化到和为1 attn_weights tf.expand_dims(attn_weights, axis-1) # (batch, 30, 1) # 按时间步加权广播到每个特征通道 x layers.Multiply()([x, attn_weights]) # 3) LSTM建模时间依赖 x layers.LSTM(lstm_units, return_sequencesFalse, dropoutdropout)(x) x layers.Dense(32, activationrelu)(x) x layers.Dropout(dropout)(x) # 单步预测输出一个标量价格值 outputs layers.Dense(1)(x) model Model(inputs, outputs) return model逻辑说明Conv1D(filters32, kernel_size3)的卷积核只在时间轴滑动每个核学习一种局部模式。paddingsame保证输出仍然是30个时间步否则卷积后序列变短Attention对齐会变麻烦。Attention用Dense(1)对每个时间步打分后接softmax把分数转成和为1的概率分布。Multiply层把权重广播到特征维度上重要时间步被放大次要时间步被压缩。Linear层输出1个节点因为任务是回归出下一日收盘价不是分类。3.3 张量维度逐层变化层输出维度说明Input(None, 30, 7)30天、7个特征筛选后Conv1D(None, 30, 32)卷积核提取32种局部模式BatchNormalization(None, 30, 32)稳定分布加速收敛Dense score(None, 30, 1)每个时间步一个原始得分Softmax(None, 30, 1)得分归一化为权重Multiply(None, 30, 32)特征按时间步权重缩放LSTM(None, 64)最后一个隐藏状态聚合全序列信息Dense(None, 1)输出预测价格3.4 关于Attention放LSTM前后的问题这个项目把Attention放在CNN和LSTM之间而不是经典的Encoder-Decoder式后置注意力。区别在于后置Attention是在LSTM输出后根据各时间步的隐藏状态再加权更适合Seq2Seq任务这个场景是单步回归把Attention放在LSTM之前等于先筛选时间步、再让LSTM只处理被强调的信息训练更稳也更容易观察到模型在关注哪些时间段。若要改成后置Attention需要把LSTM设为return_sequencesTrue再接Attention层但输入输出维度处理会复杂一些。3.5 训练时Loss与优化器选择回归任务用均方误差MSE优化器用Adam是稳妥的组合。MSE对大误差样本惩罚重期货价格预测里这通常是期望的因为预测偏差大意味着实际交易风险高。Adam配合0.001的初始学习率在这个规模的数据集上收敛速度合适不需要手工调整学习率衰减策略。4. train_v2.py训练与pred.py预测实战import numpy as np import tensorflow as tf from cnn_attention_lstm import build_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) input_shape (train_x.shape[1], train_x.shape[2]) model build_cnn_attention_lstm(input_shape, lstm_units64) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] ) early_stop tf.keras.callbacks.EarlyStopping( monitorval_loss, patience15, restore_best_weightsTrue ) ckpt tf.keras.callbacks.ModelCheckpoint( filepathcheckpoint/my_modelv2.ckpt, monitorval_loss, save_best_onlyTrue, save_weights_onlyFalse ) ### 4.1 关键训练参数与batch_size设置 history model.fit( train_x, train_y, validation_data(test_x, test_y), epochs100, batch_size32, callbacks[early_stop, ckpt], verbose1 )逻辑说明ModelCheckpoint只保存val_loss最低的那个版本restore_best_weights确保训练结束后模型权重回滚到验证集表现最好的状态不会被最后一轮过拟合的权重覆盖。batch_size32适合当前数据量级如果训练集样本只有几千条batch_size可以设为16或32如果数据量大到几万条Conv1D加LSTM的组合显存占用会明显增加batch_size需要下调。训练过程中的几个典型现象loss下降但val_loss长期不动说明模型过拟合优先调大dropout或减小LSTM单元数val_loss在某个epoch后突然跳变通常是因为学习率过大或BatchNormalization在验证阶段统计量不稳定可以降低学习率再跑一遍。4.2 checkpoint中两个模型版本的区别checkpoint目录里同时存在my_modelv1.ckpt和my_modelv2.ckpt对应项目迭代的两个版本。v1一般是基础CNN-LSTM结构v2加入了Attention机制或者v2调整了卷积核数量。我的判断依据是文件名train_v2.py训练脚本对应v2版本。实际使用建议优先加载v2如果v1和v2结构不同但不能混用结构定义必须与checkpoint匹配用错误的模型结构加载权重会直接报shape不匹配。4.3 加载本地模型进行预测import numpy as np import tensorflow as tf from sklearn.metrics import mean_absolute_error, mean_squared_error model tf.keras.models.load_model(checkpoint/my_modelv2.ckpt, compileFalse) test_x np.load(test_x.npy) test_y np.load(test_y.npy) pred model.predict(test_x) # shape: (样本数, 1) mae mean_absolute_error(test_y, pred) rmse np.sqrt(mean_squared_error(test_y, pred)) print(fMAE{mae:.4f}, RMSE{rmse:.4f}) np.save(pred.npy, pred)逻辑说明预测完成后要把归一化后的预测值反变换回真实价格区间才能和行情对比否则算出的MAE没有业务意义。如果处理脚本里做了Min-Max归一化反变换用pred * (max_val - min_val) min_val其中max/min是目标列close的统计值。这里注意反变换只对目标列做不要用整个特征矩阵的min/max。pred.npy保存下来是为了画预测曲线把真实收盘价和预测价画在同一张图里是课程设计里最直观的可视化证据。4.4 pred_API.py把预测包装成HTTP接口pred_API.py在这个项目里承担的是接口封装的作用配合PDF里的Web前端配置教程使用让训练好的模型接受外部请求并返回预测结果。常见做法是用Flask起一个轻量服务接收前端传来的最近30天特征序列加载checkpoint调用model.predict后返回预测价格。from flask import Flask, request, jsonify import numpy as np import tensorflow as tf app Flask(__name__) model tf.keras.models.load_model(checkpoint/my_modelv2.ckpt, compileFalse) app.route(/predict, methods[POST]) def predict(): data request.get_json() seq np.array(data[features]).reshape(1, 30, -1) pred model.predict(seq)[0][0] return jsonify({pred_price: float(pred)}) if __name__ __main__: app.run(host0.0.0.0, port5000)逻辑说明Flask接收JSON格式输入features字段是至少30天、含全部特征维度的二维数组reshape成(1, 30, 特征数)后喂给模型。需要确保前端传过来的特征顺序和训练时selected [target]的顺序一致特征列错位会导致预测结果完全失真。接口返回的pred_price仍是归一化后的数值前端拿到后应配合归一化参数做逆变换展示或者在后端直接完成逆变换再返回。5. 滚动验证把预测误差拆开看一次性model.predict(test_x)只能说明模型在测试集上的整体误差看不出预测值是否只是延迟了一天的真实值。LSTM做价格预测最常见的假象是预测曲线紧紧贴着真实曲线但整体右移了一天这种情况下MAE依然很低实际却没有预测能力。我的验证方法是做滚动预测每次只取最近30天数据预测下一天得到预测值后把真实值追加到窗口尾部丢掉最前面一天再预测下一天模拟真实交易中的逐日滚动。def rolling_predict(model, init_window, true_future, n_steps, feat_dim): window init_window.copy() # shape: (30, feat_dim) preds [] for i in range(n_steps): p model.predict(window.reshape(1, 30, feat_dim), verbose0)[0][0] preds.append(p) # 真实值推进把窗口前移一天用真实值更新末尾 next_row true_future[i] window np.concatenate([window[1:], next_row.reshape(1, -1)], axis0) return np.array(preds)逻辑说明true_future是测试集里每天的完整特征向量不只是收盘价。每次迭代窗口向前滚动一天window[1:]丢掉最早的一天next_row补上最新一天。这样每一步的输入都包含真实历史信息与实际交易场景一致。运行后把preds和真实收盘价画在一张图里如果预测曲线明显滞后于真实曲线说明模型主要依赖最近一两天的价格做惰性预测没有学到趋势信息需要调整特征或窗口长度。Attention权重在这个验证场景里也有用处。把测试集某条样本的attention权重取出来按时间步画折线图看模型是否把权重集中到价格启动点附近的历史日期上。如果权重几乎均匀分布说明Attention层没有学到有效模式可以考虑换维度更大的attention打分结构。另外一个需要排除的问题是数据泄漏。检查处理后的数据表里是否出现了未来函数也就是某列使用了当天收盘价之后才知道的数据比如第二天的开盘价或周度汇总字段。如果存在这列且被选入特征集模型在训练时等于提前看到了未来信息测试集MAE会低得不真实。用手动滞后检查最直接把数据表按时间排列逐列确认每个字段在时间戳上是否早于或等于目标列当日收盘数据。本文还有配套的精品资源点击获取
返回列表