尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

基于相关性分析的CNN-Attention-LSTM期货价格预测模型实战解析

发布时间:2026/9/23 18:24:33

资讯中心
01
ARTICLE

基于相关性分析的CNN-Attention-LSTM期货价格预测模型实战解析

基于相关性分析的CNN-Attention-LSTM期货价格预测模型实战解析
简介基于相关性分析的CNN-Attention-LSTM期货价格预测完整项目面向深度学习、量化金融方向的毕设或课设研究也适合希望用混合神经网络做时序预测的入门进阶者。代码覆盖数据相关性分析、时间步处理、模型定义、训练与预测全流程并附带预处理后的数表和SQL原始数据可直接运行验证效果。压缩包共29个文件含8个Python源码、3个Excel数据表、已训练模型权重及两个使用说明PDF整体大小30.29MB目录结构清晰源码注释详尽便于二次开发。目前已有809人学习下载说明该方案具备一定参考价值。对于想快速搭建期货价格预测基线、理解注意力机制如何与传统LSTM融合的读者这份资源能省去大量数据清洗和调参时间适合课程答辩或论文实验环节作为核心代码基底。1. 从“相关性分析”到“CNN-Attention-LSTM”期货预测模型的第一步为什么是筛因子开盘前最常问的问题不是涨还是跌而是“凭什么”。基于相关性分析的CNN-Attention-LSTM期货价格预测模型要回答的就是这个“凭什么”先用相关性分析把几十个量价因子筛到十几个再用CNN提取局部形态、Attention给关键交易日分配权重、LSTM捕捉趋势最后输出下一交易日的预测收益。这套方案适合三类人手里有行情数据但不知道怎么组织特征的量化研究者、想复现论文但卡在数据切分的在校学生、以及跑通无数模型却始终调不出效果的一线从业者。我不承诺模型能稳定盈利但会把每一步的代码、参数和边界讲清楚让你知道模型到底在学什么、哪里容易翻车、出了问题怎么排查。2. 数据准备相关性分析选因子与滑动窗口切片别让模型吃进一堆噪音2.1 先算相关性再选特征用Spearman而不是Pearson的原因CNN-Attention-LSTM的参数量不小输入特征里如果一半是纯噪声模型会用参数去硬拟合这些噪声训练集指标好看一出训练集就现原形。所以特征筛选不是玄学是模型泛化能力的地基。import pandas as pd import numpy as np # 假设压缩包解压后数据文件叫 futures_daily.csv按日期升序排列 df pd.read_csv(dataset/futures_daily.csv, parse_dates[trade_date]) df df.sort_values(trade_date).reset_index(dropTrue) # 预测目标用下一交易日收益率而不是收盘价 df[ret] df[close].pct_change() df[target] df[ret].shift(-1) # 候选特征量价和常见技术指标 features [open, high, low, close, volume, open_interest, ma5, ma10, ma20, rsi14] # Spearman秩相关对非线性单调关系更稳健 corr df[features [target]].corr(methodspearman) target_corr corr[target].drop(target).sort_values(keyabs, ascendingFalse) print(target_corr) # 筛掉与target相关性绝对值小于0.15的特征 selected target_corr[target_corr.abs() 0.15].index.tolist() print(selected:, selected)这段代码做的事很简单把每个候选特征与目标变量算相关性按绝对值排序然后保留相关性超过阈值的特征。为什么用Spearman而不是Pearson期货量价数据普遍存在尖峰厚尾极端行情会把Pearson相关系数拉得很离谱Spearman只看秩次一组数据里最大的值无论大多少倍秩次只差一位抗异常值能力强很多。阈值0.15是我常用的起点不是固定标准如果你的特征集比较干净可以试0.1特征足够多时0.2能筛得更狠。但相关性高不代表可以全部入选特征之间高度冗余会让模型重复吸收同一份信息还会放大共线性。继续做一步冗余剔除# 检查入选特征之间的相关性去掉冗余变量 corr_selected df[selected].corr(methodspearman) # 找出相关性高于0.8的特征对 high_corr_pairs [] for i in range(len(corr_selected.columns)): for j in range(i 1, len(corr_selected.columns)): if abs(corr_selected.iloc[i, j]) 0.8: high_corr_pairs.append( (corr_selected.columns[i], corr_selected.columns[j], round(corr_selected.iloc[i, j], 3)) ) print(high_corr_pairs)这一段的逻辑是如果两个特征相关性超过0.8说明它们携带的信息高度重复留一个就行。我一般倾向保留更容易解释、跟交易决策更直接的那个比如MA5和MA10高度相关就只留MA5成交量与持仓量高度相关时优先保留成交量。这里没有标准答案关键是让最终特征集尽量正交给后面的CNN和Attention留出真正有用的信息维度。2.2 数据清洗与归一化去极值、缺失值、Z-Score的处理顺序相关性的计算结果对数据质量极其敏感。行情数据里偶尔会有异常成交、停牌导致的缺失、涨跌停引起的价格断层这些都会干扰后面的训练。清洗顺序我通常是先去极值再补缺失值最后做归一化。def mad_winsorize(s, n5): 中位数去极值用MAD而不是3-sigma med s.median() mad (s - med).abs().median() upper med n * 1.4826 * mad lower med - n * 1.4826 * mad return s.clip(lower, upper) for col in selected: if col ! target: df[col] mad_winsorize(df[col]) # 缺失值处理target缺失出现在最后一个交易日因为没有下一日收益 print(df[selected [target]].isna().sum()) df[target] df[target].fillna(methodbfill) df df.dropna(subsetselected)为什么去极值用MAD而不是3-sigma因为期货收益率的尾部比正态分布厚得多用均值加减三倍标准差会把真实的极端行情误杀。MAD基于中位数和绝对中位差天然抗异常值n5意味着把偏离中位数5个MAD的值压回来这个参数可以按波动性调螺纹钢和国债期货的分布差异很大样本数据多的时候我通常会分品种单独标定。归一化放在清洗后、建序列前。注意这里有个隐蔽的坑归一化只能用训练集的统计量不能在整份数据上直接fit。也就是说一定要先把训练集切出来再做scaler.fit(train)然后对验证集和测试集只做transform。这个我在第4章展开但第2章你就要有这个意识否则后面测试集指标虚高都是白高兴。from sklearn.preprocessing import StandardScaler # 注意这里只演示思路真正的切分和归一化顺序见第4章 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train_raw) X_val_scaled scaler.transform(X_val_raw) X_test_scaled scaler.transform(X_test_raw)Z-Score归一化把每个特征变成均值为0、标准差为1的分布对LSTM这类依赖梯度传播的结构非常重要。如果特征尺度差太多比如成交量是几千手、RSI是几十梯度更新会被大尺度特征主导模型训练极不稳定。2.3 滑动窗口切片窗口大小和预测步长怎么搭配相关性分析做完、数据清洗完下一步就是把表格数据切成长短一致的样本让模型每次看一段连续行情输出一个预测值。def create_sequences(data, window30, pred_len1, step1): X, y [], [] for i in range(window, len(data) - pred_len 1, step): X.append(data[i - window:i]) y.append(data[i:i pred_len]) return np.array(X), np.array(y)window30对应日线数据的30个交易日大概一个半自然月既覆盖月线级别的动量又不会把太久远的市场状态混进来。window太短模型只看得到两三根K线学不到趋势window太长样本里混进不同行情阶段模型反而被平均掉。pred_len1表示每次只预测下一个交易日这是最稳的设定多步预测的误差会随时间步长累积不建议一上来就预测未来5天。step1表示每次滑动一个交易日样本之间高度重叠这会带来信息泄漏吗会有但这是时间序列预测的典型做法行业内普遍接受。真正确保泛化的是训练集、验证集、测试集按时间顺序切分而不是样本不重叠。把注意力放在切分上不要在这个问题上过度纠结。3. 模型搭建CNN-Attention-LSTM每一层在期货序列里到底在干什么3.1 输入张量与CNN层为什么先用Conv1D抓局部形态模型名字叫CNN-Attention-LSTM但不是说把原始K线直接扔进LSTM就完事。价格序列里的有效信号往往在局部形态上连续放量、三连阳、突破后回踩这些都是几个交易日内的模式。LSTM擅长建模长程依赖但要它自己从原始特征里发现这些局部模式需要大量样本小数据集根本不够。CNN在这里起的作用是把局部形态先提炼出来让LSTM不用从头学起。import tensorflow as tf from tensorflow.keras import layers def build_cnn_attention_lstm(window30, n_featureslen(selected), lstm_units64): inputs tf.keras.Input(shape(window, n_features)) # Conv1D沿时间轴卷积kernel_size3代表每次看3个交易日 x layers.Conv1D(filters32, kernel_size3, paddingsame, activationrelu)(inputs) x layers.BatchNormalization()(x) x layers.Dropout(0.2)(x) # 这里先不接LSTM先让Attention重新校准CNN的输出 ...参数说明filters32表示卷积核数量也就是提取32种不同的局部形态这个值可以随数据量加大但日线数据几万条样本量的话32到64够用。kernel_size3是时间维度的感受野只看三个交易日paddingsame保证输出和输入长度一致后面接LSTM时不需要脑补序列长度。BatchNormalization放在卷积层后面把每批数据的分布拉回原点附近。卷积层的输出经过ReLU激活后分布会偏如果不归一化下一层输入分布一直在漂LSTM对这种漂移特别敏感。Dropout(0.2)是防止CNN层学到局部噪声。3.2 Attention层让模型自己决定哪几个交易日更重要LSTM读完整段序列后理论上能记住关键信息但现实中长序列的信息往往被中间过程稀释。Attention的作用是给每个时间步分配一个权重让模型在聚合信息时重点看那些对预测结果贡献最大的交易日。有些代码把Attention实现成直接把序列压缩成一个向量但我更推荐另一种做法让Attention输出的是加权后的序列而不是压缩后的一条向量。class AttentionLayer(layers.Layer): 对时间步做加权输出保持序列结构权重可以取出来检查 def __init__(self, **kwargs): super().__init__(**kwargs) def build(self, input_shape): # input_shape: (batch, time, features) self.W self.add_weight( nameatt_weight, shape(input_shape[-1], 1), initializerglorot_uniform, trainableTrue ) super().build(input_shape) def call(self, inputs): # 把每个时间步的特征向量压缩成一个标量分数 score tf.squeeze(tf.matmul(inputs, self.W), axis-1) # softmax归一化成权重所有时间步权重和为1 attention_weights tf.nn.softmax(score, axis1) # 保持序列维度逐时间步乘权重再交给LSTM context inputs * tf.expand_dims(attention_weights, -1) return context, attention_weights这段代码里需要理解两个关键点。第一打分机制每个时间步的特征向量与可训练的向量W做点积得到一个标量分数这个分数经过softmax变成归一化权重。第二输出方式不是把序列压缩成一个context向量而是让每个时间步乘以自己的权重序列结构保持不变。这样做的好处是LSTM依然能处理完整的时间序列Attention做的事情是调整每个时间步的“音量”重要的交易日放大无关的交易日缩小。为什么不用keras内置的Attention层那个更适合seq2seq解码器的场景而且拿不到直观的注意力权重。自己做这个层训练结束后可以任意取一个样本看权重分布模型是不是真的把注意力放在突破日、放量日上这比把模型当黑匣子踏实得多。3.3 LSTM与输出层输出维度、损失函数和预测目标怎么定Attention加权后的序列进入LSTMLSTM的输出再经过全连接层压缩成预测值。这一层的设计直接影响模型最终行为的稳定性。def build_cnn_attention_lstm(window30, n_featureslen(selected), lstm_units64): inputs tf.keras.Input(shape(window, n_features)) x layers.Conv1D(filters32, kernel_size3, paddingsame, activationrelu)(inputs) x layers.BatchNormalization()(x) x layers.Dropout(0.2)(x) # Attention重新校准时间步权重输出仍为序列 x, att_weights AttentionLayer()(x) # LSTM建模序列依赖只返回最后一个时间步 x layers.LSTM(lstm_units, return_sequencesFalse, dropout0.2, recurrent_dropout0.2)(x) x layers.Dense(16, activationrelu)(x) x layers.Dropout(0.2)(x) outputs layers.Dense(1, activationlinear)(x) model tf.keras.Model(inputs, outputs) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] ) return modellSTM_units64是隐状态维度这个值影响模型容量。数据量大可以加到128但期货日线数据通常没那么充裕64起步比较稳。return_sequencesFalse表示只保留最后一个时间步的输出因为我们的任务是预测一个值而不是生成一段序列。Dense(16)做一次非线性映射把LSTM的64维输出先压缩到16维再接输出层这样可以让LSTM输出的信息被充分汇总后再进入最终回归。输出层activationlinear因为这是回归任务预测的是下一交易日收益率而不是分类概率。lossmse对大的预测偏差惩罚更重配合mae一起监控可以避免只看单一指标被带偏。如果想更抗异常值可以换HuberLoss但mse在调参阶段更直接反馈更敏感。4. 训练与评估时间序列验证集怎么切、损失函数怎么选、早停怎么设4.1 数据切分训练集/验证集/测试集必须按时间顺序表格数据随机切分是常规操作时间序列不行。期货数据天然存在自相关性今天的行情和昨天高度相关如果随机打乱训练集里会出现“未来”的样本验证集里会出现“过去”的样本模型等于提前偷看了答案。这是时间序列项目里最隐蔽也最致命的错误。# 假设X和y是create_sequences的输出形状为(样本数, window, 特征数)和(样本数, pred_len) train_end int(len(X) * 0.7) val_end int(len(X) * 0.85) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] # 归一化统计量只从训练集算验证集和测试集不能参与 from sklearn.preprocessing import StandardScaler scaler StandardScaler() n_train, window, n_features X_train.shape X_train_2d X_train.reshape(-1, n_features) scaler.fit(X_train_2d) X_train scaler.transform(X_train_2d).reshape(n_train, window, n_features) n_val, _, _ X_val.shape X_val scaler.transform(X_val.reshape(-1, n_features)).reshape(n_val, window, n_features) n_test, _, _ X_test.shape X_test scaler.transform(X_test.reshape(-1, n_features)).reshape(n_test, window, n_features)切分比例70/15/15是常见做法但要注意前三年的行情和后三年的行情可能来自完全不同的市场状态单纯按比例切不一定公平。我一般会看样本的总时间跨度如果数据跨越牛熊周期测试集至少留出最近一年验证集留最近半年之前的半年。这个切法会让训练集变短但换来的是对最近行情的真实反馈。归一化这块的reshape很多人第一次会晕StandardScaler只接受二维输入三维的(样本数, window, 特征数)必须先压平成(样本数 * window, 特征数)fit之后transform再reshape回三维。验证集和测试集用的都是训练集fit出来的scaler这保证模型在训练时看不到验证集和测试集的任何统计信息。4.2 评估指标RMSE、MAE、MAPE怎么配合看很多人训练完只盯着loss但loss是训练过程的反馈评估模型真实水平要用独立的指标。收益率的分布有尖峰厚尾单一指标容易被极端值主导所以我习惯三个一起看。from sklearn.metrics import mean_squared_error, mean_absolute_error y_pred model.predict(X_test) rmse np.sqrt(mean_squared_error(y_test, y_pred)) mae mean_absolute_error(y_test, y_pred) # 收益率可能接近0加小常数防止除零 mape np.mean(np.abs((y_test - y_pred) / (y_test 1e-6))) * 100 print(fRMSE: {rmse:.5f}, MAE: {mae:.5f}, MAPE: {mape:.2f}%)RMSE对大的预测偏差特别敏感一次大的误判会让RMSE显著上升所以RMSE能暴露模型的极端错误。MAE更稳健反映平均的绝对误差水平。MAPE把误差转换成百分比但这组指标在收益率接近0时会爆炸所以代码里加了1e-6的常数。实际操作中如果RMSE和MAE差距过大说明模型存在少数极端误差需要回去看是不是某些行情片段让模型完全崩溃而不是整体预测不准。4.3 训练参数与早停patience、学习率和batch_size怎么定模型训练不是epoch越多越好。LSTM类模型在验证集上的表现通常先下降后上升过了某个点就开始过拟合。早停就是在这个拐点附近自动停下来的机制。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stopping EarlyStopping( monitorval_loss, patience15, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6 ) model.fit( X_train, y_train, validation_data(X_val, y_val), epochs200, batch_size64, callbacks[early_stopping, reduce_lr], verbose1 )patience15表示验证集loss连续15个epoch不下降就停止restore_best_weights会把模型权重回滚到验证集loss最低的那个epoch避免把过拟合状态的权重带回来。ReduceLROnPlateau在验证集loss连续5个epoch不降时把学习率减半给训练过程第二次机会在大学习率下困住的参数减半后可能继续下降。batch_size64是综合考虑样本量和显存后的折中如果样本量在几万级别64到128都合理。需要注意batch_size太大会让每个batch的梯度方向过于平均模型难以捕捉序列中的细节模式太小则梯度震荡太大训练不稳定。训练过程要盯两条曲线训练loss平稳下降验证loss也下降且差距不大这是健康状态如果训练loss继续降、验证loss掉头向上过拟合已经开始早停的patience就是最后一道防线。5. 避坑指南相关性分析、数据泄漏与训练复现的五个翻车现场5.1 相关性筛选全是“高相关”但模型训练出来一塌糊涂现象特征和目标变量的相关性矩阵看起来非常漂亮几乎所有特征都和target有0.5以上的相关性但模型训练完在测试集上一塌糊涂。原因如果直接用收盘价作为预测目标价格序列是非平稳的几乎所有量价特征都跟价格水平高度相关这个“高相关”反映的只是大家都在随时间上涨而已不是真正可泛化的预测信号。模型学到的只是“昨天的价格决定今天的价格”换个时间段全部失效。解决预测目标必须换成收益率、差分收益率或对数收益率这类平稳化后的序列。在特征筛选阶段相关性分析的输入和输出都要基于同样的变换否则筛选结果没有意义。5.2 归一化用了全样本统计量测试集指标虚高现象模型在测试集上RMSE和MAE都很好看但一拿去模拟回测表现跟测试集预测完全对不上。原因在切分数据之前就对全样本做了scaler.fit数据集的均值和标准差包含了验证集和测试集的信息模型等于提前知道了未来数据的分布范围测试集指标被污染这就是数据泄漏。解决一定先切分再fit训练集验证集和测试集只transform不能用它们自己的统计量更不能混进训练集的fit过程。检查方法很简单在训练前打印scaler.mean_和scaler.scale_确认是在训练集上计算的。5.3 训练集random shuffle验证集惊艳回测完全崩盘现象训练时shuffleTrue验证集loss非常低测试集预测曲线和真实值高度重合结果一到实盘或滚动回测就完全乱套。原因时间序列样本之间存在顺序依赖样本i和样本i1可能只差一个交易日数据重叠度极高。随机打乱后训练集里混进了大量“未来临近”的样本模型实际上是在预测已经见过的相邻片段。解决训练时shuffleFalse严格按时间顺序喂数据。如果一定要shuffle至少要按时间块切分比如每10个交易日的样本作为一个块块之间再打乱。但最稳妥的还是保持原始顺序训练。5.4 Attention权重全部均匀分布模型不知道在看什么现象把训练好的模型拿来查看attention_weights发现所有时间步的权重几乎相等完全没有重点。原因可能有两个原因。一是训练不充分Attention层还没学到有效的打分模式就停了二是CNN输出的特征区分度太低每个时间步的特征向量长得差不多打分自然拉不开差距。解决先把Attention层的可训练参数冻结训练CNN-LSTM基线等loss下降稳定后再解冻Attention层继续微调。也可以在打分函数里加一个温度参数把score除以一个小于1的温度值后再做softmax拉大权重差异。查权重时要注意不是每个样本都要有明显重点市场混沌时模型确实可能给所有时间步接近均匀的权重这本身就是一种信号。5.5 训练loss持续下降验证loss第20轮开始回升现象训练集loss一直往下走验证集loss先降后升两条曲线中间劈叉越来越宽。原因模型参数太多样本量不足以支撑这么大的容量LSTM的循环结构比全连接网络更容易过拟合它会把训练集里的噪声当作规律记下来。解决把Dropout从0.2提到0.3或0.5调低lstm_units从64降到32在Dense层加L2正则。同时确认EarlyStopping的patience设置合理restore_best_weightsTrue务必开着让它把权重回滚到验证loss最低点而不是停在最后一个epoch。不要为了追求训练loss好看而牺牲验证集表现这个模型最终要在没见过的行情上验证不是在训练集上复述历史。6. 模型训练完别急着用先做参数敏感性分析再拿去模拟回测模型在测试集上跑出一个不错的RMSE只说明它在这个固定时间段内预测得准还不能说明它可以用。我最后的习惯是先做参数敏感性分析再做带手续费的模拟回测。参数敏感性分析就是把window、lstm_units、filters这些关键参数各跑几组看测试集指标的变化幅度。如果window从20改到50RMSE波动在0.5%以内说明模型对窗口不敏感结构稳定如果RMSE大幅跳动说明模型根本没收敛或者数据里信号太弱当前的好结果是运气。windowlstm_unitsRMSEMAE20320.01870.013230640.01840.013050640.01980.0141301280.02050.0148上表是典型结果模式window30附近表现最好过大或过小都退化lstm_units加到128反而变差说明容量已经过剩。这种分析能帮你判断模型是“学到了规律”还是“碰巧在某个参数组合下拟合得不错”。回测时一定要把交易成本算进去。期货有手续费和滑点手续费每手几块钱滑点按最小变动价位估算看起来都不大但频繁交易时累加起来非常可观。预测信号一般设置阈值比如预测收益率超过0.5%才开仓低于-0.5%才平仓中间状态不操作这能大幅减少无效交易。回测结果里扣除成本后如果还能稳定跑赢基准这个模型才算真正有了落地的基本盘否则只是统计数字上的空欢喜。我现在养成的习惯是任何模型训练完都先问三个问题换一段行情还能不能稳住、扣掉成本还剩多少、Attention权重有没有讲出我能看懂的故事。这三个问题都过关才敢把模型的输出拿去做更深度的验证。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。