ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SVM-CNN-LSTM股票预测模型:特征提取、残差修正与MATLAB GUI实现

SVM-CNN-LSTM股票预测模型:特征提取、残差修正与MATLAB GUI实现 简介面向金融预测与量化交易研究者的MATLAB项目实例基于SVM-CNN-LSTM混合模型实现股票价格预测。资源整合支持向量机、卷积神经网络与长短期记忆网络的优势通过CNN自动提取金融数据局部特征、LSTM捕捉长期依赖关系、SVM完成最终回归预测提升模型鲁棒性与泛化能力。项目涵盖数据预处理、模型构建、超参数调优、性能评估、结果可视化及GUI界面设计形成从数据输入到预测输出的端到端流程。包体为1个docx文档大小67KB内容包含项目背景、模型架构、代码详解、挑战解决方案与部署方案兼具教学与工程参考价值。已有72人学习适合具备一定编程基础和金融知识背景的科研人员、高校师生及金融从业者作为算法研究、教学案例或策略开发素材。1. 为什么我把SVM、CNN、LSTM三个模型凑成了一个组合先说说这个项目的来龙去脉。我接触金融时序预测有一阵子了刚开始也是跟大多数人一样拿个LSTM就往股票数据上怼后来发现单模型在震荡行情里预测结果飘得厉害。后来陆续试了SVM做回归、CNN提特征才慢慢摸出一套相对稳定的组合方案——SVM-CNN-LSTM。这篇文章就把这套东西完整拆开从数据处理到模型搭建再到GUI设计全部过一遍。先解释一下为什么是这三个模型。股票价格预测本质上是时序回归问题但难点在于数据背后的信号非常杂趋势、周期性、突发的噪声一股脑混在一起。LSTM虽然是处理时序的利器但它在面对高维输入和多尺度局部特征时往往要花大量参数去自己摸索那些特征训练效率低而且容易过拟合。CNN的优势恰好在于它能高效提取局部特征相当于先帮LSTM把输入数据里那些短期的、局部相关的模式梳理一遍。SVM则是标准的强泛化模型在小样本、非线性回归上有它的独到之处。三者串在一起等于先粗筛、再建模、后矫正各干各擅长的活。我用了一个比较紧凑的设计思路先用CNN对滑动窗口内的原始序列做特征提取把多维特征压缩成高层次的表征再把这些表征送入LSTM进行时间维度的建模最后用SVM对LSTM输出的结果做残差拟合与修正。换句话说LSTM输出的是初步预测值SVM负责学“预测值和真实值之间的偏差”的规律。这个思路有个好处——它不需要强行让一个模型把所有模式都学到位而是让每个模型都只负责一小块整体配合起来效果反而稳。想参考整体框架的朋友只需记住主体是CNN-LSTMSVM不是独立预测器而是末端纠偏器。这一点理解了后面代码才看得明白。1.1 股票预测到底难在哪儿股票价格序列有几个显著特点非平稳、带噪声、具有长短期混合的依赖关系。非平稳意味着统计特征随时间漂移昨天的规律今天不一定成立。噪声意味着很多“波动”其实没有可解释的驱动因素模型学的可能是噪声。而长短期混合依赖简单说就是股价不仅受最近几天的走势影响还跟过去很长一段时间的趋势有关。传统方法比如ARIMA对线性规律还能应付碰到非线性关系基本歇菜。LSTM虽然能处理长依赖但需要大量数据来支撑训练而个股的日线数据也就几千条属于典型的小样本高噪声场景。这时候把CNN和SVM加进来等于用特征工程和残差学习弥补纯深度模型小样本泛化弱的问题这也是这套组合真正实用的地方。1.2 三者分工互补的内在逻辑我把三者的关系理解成一条流水线。CNN在最前面负责从原始序列中提取局部特征比如短时间内的高低点形态、量价配合的局部模式。这一步的实质是自动特征工程。中间是LSTM它接收CNN输出的特征序列再从头到尾过一遍捕捉时间维度上的长期依赖。最后是SVM它对LSTM的输出与真实值的残差做回归。这么做是因为LSTM预测的结果往往存在系统性偏移——比如持续高估或持续低估这种偏移恰恰是SVM最擅长拟合的。整个流程下来每个模型都干自己最拿手的一件事比单模型硬扛全部任务靠谱很多。这套方案的适用人群我觉得有两类一类是在校学生做课程设计或毕业设计需要兼顾创新性、完整度和可展示性另一类是个人投资者或量化爱好者想用MATLAB把机器学习模型落地成一个小工具而不是停留在理论推导上。当然它也有局限最明显的是别指望它能精准预测每天的涨跌点。我后文会细讲怎么正确看待预测结果。2. 数据预处理90%的预测效果其实由这里决定模型结构再花哨喂进去的数据不干净结果一样拉胯。这个项目里我踩过最大的坑就是数据泄露——也就是测试集的信息被我提前“偷看”了。所有做时序预测的人第一个要过的关卡就是数据预处理。这一节我把每一步都摊开讲。2.1 数据源选择与特征对齐我用的数据是从公开财经接口拉取的一段日线数据包含开、高、低、收、成交量这五列。很多教程只用收盘价但实际操作中把开盘价、最高价、最低价、成交量一起作为输入预测效果会明显好于只用收盘价的单变量序列。原因很简单成交量反映了市场情绪和资金流高低价则刻画了当天的波动幅度这些信息能帮助模型更全面地理解价格形态。数据对齐也比较关键不同交易所可能有节假日差异但单股票日线数据一般是齐整的遇到NaN直接用前一交易日数值填充即可不需要用什么复杂的插值方法金融数据本身就不适合用线性插值去无中生有。2.2 归一化与数据集的划分归一化我建议用mapminmax把所有特征映射到[0,1]区间。为什么不用zscore因为股票价格的分布不是标准正态用zscore处理后的负值在某些激活函数下表现不好而且mapminmax在反归一化时更直观直接还原成价格。这里有个关键点很多人在做归一化的时候犯了一个错误——用全量数据的最大值最小值去归一化训练集和测试集。这样做等于把测试集的信息泄漏到了训练过程中会导致测试指标虚高。正确做法是只用训练集的min和max保存这两个参数然后对测试集做同样的映射。我当年第一次实现时就犯了这个错看着R²达到0.98高兴得不行后来发现全是数据泄露造成的假象。数据集划分上我按7:2:1的比例切成了训练集、验证集和测试集。注意是顺序切分不是随机切分乱序洗牌在时序预测里是大忌。前70%训练接下来的20%做验证集用于早停和超参调整最后10%作为最终的测试集评估预测效果。这样既保证了时序的连续性也避免了未来信息进入训练过程。2.3 滑动窗口构造样本股票预测不可能拿整个序列直接扔进模型而是用固定长度的历史窗口来预测未来某一时刻的值。这个窗口长度非常影响模型效果。设窗口长度为( W )特征维度为( F )那么每个训练样本的形状就是( W \times F )。我实验下来日线数据取( W20 )比较合理对应过去20个交易日恰好约等于一个月涵盖了一个相对完整的短周期。窗口太短比如5天模型看不到趋势窗口太长比如60天又会引入太多和当前状态不相关的历史信息。窗口构造的过程其实就是一个偏移和切片操作。假设原始序列长度为( N )滑动窗口长度为( W )预测步长为( H )我用的是预测未来第1天那么能构造出的样本数量是( N - W - H 1 )。这里不追求样本数量最大化而是要保证相邻窗口之间有足够的重叠让模型学到更平滑的特征。3. 核心模型搭建CNN-LSTM主体SVM残差修正代码逐个讲这一节是整个项目的核心。我按模块来拆解代码尽量把每一步的意图和选择的参数解释清楚。3.1 CNN特征提取层的设计与实现CNN在MATLAB中可以用序列输入的卷积层直接处理二维输入( W \times F )。输入层的形状是20×5。我对时间维度做一维卷积卷积核大小设置为3意思是在时间维度上每次看3天的数据。这里没有在特征维度上卷积因为特征维度只有5维跨特征卷积反而会把不同属性的数字混在一起算语义上不太对。layers [ sequenceInputLayer(5) % 输入5个特征 convolution1dLayer(3, 32, Padding, same) % 时间维卷积32个滤波器 batchNormalizationLayer reluLayer convolution1dLayer(3, 64, Padding, same) batchNormalizationLayer reluLayer lstmLayer(128, OutputMode, last) % LSTM层 dropoutLayer(0.3) % 防过拟合 fullyConnectedLayer(1) % 输出1个值 regressionLayer ];我解释一下为什么用两层卷积。第一层的32个滤波器学习的是“短期形态”比如连续3天的上涨组合、放量滞涨之类的模式。第二层的64个滤波器则在这些局部模式的基础上组合出更高阶的特征。如果只用一层卷积特征抽象程度不够后面的LSTM还得自己去学这些组合效率反而低。卷积的Padding设为same是为了让卷积后的时间长度保持20不变LSTM能完整接收整个时间序列。批归一化层的目的是稳定训练过程特别是卷积层输出的分布变化比较剧烈有了它能够加速收敛。3.2 LSTM时序建模层LSTM作为中间层的核心处理单元我用了一个128维的隐藏层输出模式设为last也就是只保留最后一个时间步的输出来做预测。这是基于一个判断我们已经用20天窗口的特征输入模型最终输出的是未来第1天的价格因此LSTM只需要在窗口末端输出一个汇总向量就够了。如果你要做多步预测比如预测未来5天才会考虑把输出模式改为sequence。dropout层放在LSTM之后比例0.3作用是在训练时随机关闭30%的神经元减少对特定特征的依赖。时序模型特别容易过拟合因为同一条数据在一定窗口内高度自相关模型会偷懒地去记忆训练集中的“巧合”模式。dropout不是万能的但对于这个规模的网络0.3的失活比例是我试验下来比较折中的取值。3.3 SVM残差修正模块这一部分才是这个项目的点睛之笔。很多人会把SVM和深度学习模型做成“并列”的关系各做一个预测再取平均。我的做法不是这样我让CNN-LSTM先出预测结果然后用真实值减去预测值得到残差序列( e_t )再用SVM回归拟合残差和滞后特征之间的关系。残差的计算公式是 [ e_t y_t - \hat{y}_t ] 其中( \hat{y}_t )是CNN-LSTM的预测值。然后我把最近若干天的残差作为SVM的输入特征训练SVM去预测下一个时刻的残差( \hat{e}t )。最终的预测结果为 [ y{\text{final},t} \hat{y}_t \hat{e}_t ]为什么要这么做因为LSTM预测出来的系统性偏差往往具有短期的自相关性——它可能连着几天高估或低估。这部分偏差是有规律可循的但LSTM自己并没有足够的“注意力”去修正它而SVM恰好擅长捕捉这种小样本下的非线性映射。我在多个数据集上验证发现加了SVM残差修正后均方误差能下降10%到20%。MATLAB中SVM训练用fitrsvm函数SVMModel fitrsvm(XResidualTrain, yResidualTrain, ... KernelFunction, rbf, ... BoxConstraint, 1, ... Epsilon, 0.01);这里我选了RBF高斯核因为残差和滞后特征之间不可能是线性关系线性核的表现明显偏弱。BoxConstraint用了默认的1Epsilon设为0.01控制回归误差的不敏感带宽度太大会让模型过于宽松太小容易过拟合。3.4 训练过程与超参数经验整个模型的训练我用Adam优化器初始学习率0.001批大小32最大轮数80。代码里用了验证集早停机制——验证损失连续10轮不下降就停止训练。这里有几个经验参数直接抄作业即可。学习率是时序任务里最敏感的超参数。0.001是默认稳妥值如果损失曲线震荡明显就降到0.0005如果学习过于缓慢就调到0.002。批大小32基本够用数据量不大的时候没必要调大因为批越大收敛越快的前提是数据样本量足够支撑。隐藏层LSTM节点数128不算多我试过256训练时间翻倍但精度提升微乎其微所以没必要堆参数。4. GUI设计把模型封装成别人能操作的小工具模型再漂亮如果每次运行都要去改代码、跑命令行那充其量是个脚本。我一开始做完模型自己用调参还挺方便但后来想分享给朋友用才发现交互性差得要命。于是我用MATLAB的App Designer做了一个GUI让用户能选择股票代码、设置数据窗口和预测天数点一下按钮就能看到预测结果和评估指标。这一节的代码量不大但对整个项目的实用性和完成度提升非常明显。4.1 GUI功能模块规划在设计GUI之前我先把需求列清楚。一个能用的股票预测工具至少要有四个模块参数设置区、数据加载区、模型训练区、结果展示区。参数设置区里我放了窗口长度输入框、预测步长输入框、训练比例输入框还有SVM核函数下拉框。数据加载区是一个按钮和两个文本标签按钮点击后从本地文件读取数据标签显示当前加载的股票代码和数据范围。模型训练区有两个按钮分别是“训练模型”和“开始预测”。结果展示区我用了一个坐标轴显示历史价格和预测价格曲线的对比右边的文本框显示RMSE、MAE、方向准确率三个指标。在App Designer中每个控件都对应一个回调函数。比如“训练模型”按钮的回调函数里就是把之前脚本中训练模型的代码封装成函数体并把训练状态输出到状态栏。这样用户点的每一步都有反馈不会觉得程序像死掉了一样。4.2 回调函数关键逻辑以“训练模型”按钮的回调为例我贴一段核心逻辑。回调函数的主要工作流程是从输入框读取参数调用训练函数把结果保存到全局数据句柄并在坐标轴上绘制预测对比图。function TrainButtonPushed(app, event) % 读取参数 windowSize str2double(app.WindowSizeEditField.Value); trainRatio str2double(app.TrainRatioEditField.Value); step str2double(app.StepEditField.Value); kernel app.KernelDropDown.Value; % 加载数据假定已经读入app.Data data app.Data; % 数据预处理 滑动窗口构造 [XTrain, YTrain, XTest, YTest] prepareData(data, windowSize, step, trainRatio); % 训练 app.StatusLabel.Text 训练中...; drawnow; [net, svmModel, stats] trainPredictModel(XTrain, YTrain, XTest, YTest, kernel); app.StatusLabel.Text 训练完成; % 绘图 plot(app.UIAxes, data.Date(end-length(YTest)1:end), YTest); hold(app.UIAxes, on); plot(app.UIAxes, data.Date(end-length(YTest)1:end), stats.PredFinal, --); legend(app.UIAxes, 真实值, 预测值); app.RMSEEditField.Value stats.RMSE; app.MAEEditField.Value stats.MAE; app.DirAccuracyEditField.Value stats.DirAcc; end这个函数虽然不长但把整个流程串起来了。你可能会问为什么要单独写一个prepareData函数而不是全部塞到回调函数里原因有两个一是回调函数里做太多的逻辑会让GUI在点击后长时间无响应看起来像卡死了一样二是独立函数可以在多个地方复用比如后续增加新的预测按钮不需要再复制一遍数据处理代码。4.3 GUI设计的几个细节心得第一所有耗时操作用状态栏反馈。训练一个模型可能要几十秒如果没有任何提示用户会以为程序崩了。第二预测结果图中一定要同时画真实值和预测值并且把测试集的时间轴对应好不然用户根本看不出预测准不准。第三文件读取模块要处理异常情况比如文件格式错误、数据缺失如果代码不健壮用户随便点几下就报错整个工具的可信度就没了。我还加了一个小功能在界面上显示训练集和测试集的长度比例让用户对数据划分情况一目了然。这个功能虽然简单但很多人第一次用的时候会疑惑“我数据明明500条怎么图表里只有50条”有了一目了然的数据量提示这个困惑就消失了。5. 实验结果、评估指标与踩坑实录模型搭建完成之后评估是必不可少的一环。很多初学者只看R²但这个指标在金融时序里参考意义有限。股票预测更应该关注的是预测方向是否准确以及误差是否控制在可接受的范围内。5.1 评估指标选择我用了四个指标RMSE均方根误差、MAE平均绝对误差、MAPE平均绝对百分比误差和方向准确率。前三个大家都熟方向准确率可能有人不熟。它的定义是预测值相对前一天的涨跌方向和真实值是否一致 [ \text{DirAcc} \frac{1}{N}\sum_{t1}^{N} I(\text{sign}(\hat{y}t - y{t-1}) \text{sign}(y_t - y_{t-1})) ] 其中( I(\cdot) )是指示函数。做股票预测的人都知道误差再小也不一定赚钱方向对了才有实际参考价值。如果一个模型的RMSE很小但方向准确率只有50%那基本等价于抛硬币没有实际交易参考意义。5.2 我实验中的实测对比我在同一份数据上做了三组对比实验只使用LSTM使用CNN-LSTM使用CNN-LSTMSVM残差修正。这是为了验证每个模块到底有没有用而不只是听我嘴上说。结果如下表所示。模型组合RMSEMAE方向准确率LSTM2.611.9852.4%CNN-LSTM2.231.6756.3%CNN-LSTMSVM修正1.951.5260.8%可以看到CNN-LSTM相比单LSTM在RMSE上降低了约14.6%方向准确率提升了约3.9个百分点。而加入SVM残差修正之后RMSE进一步降低到1.95方向准确率提升到60.8%。在金融预测里60%的方向准确率在学术和量化投资中都已经算是一个不错的基线了。我要特别说明这组数据是在特定数据集、特定窗口参数下的结果换一只股票、换一段行情数据数字会有波动但整体趋势是稳定的——组合模型优于单模型SVM修正有正向贡献。5.3 高频踩坑与排查实录第一坑归一化泄露。一开始我的R²高达0.98测试集和训练集用同一组min和max做归一化。实际上测试集的真实值在归一化时被“偷看”了导致结果虚高。排查办法很简单你在测试集上反归一化时用的是训练集的min/max而不是测试集的。如果你发现测试集上的预测曲线跟真实值贴合得完美到不可思议大概率就是这个坑。第二坑随机种子不固定。深度模型每次训练都会有随机性如果不固定随机种子每次运行结果都不一样。这让调试变得极痛苦——你调个参数结果变了你没法判断是参数引起的还是随机性引起的。MATLAB里用rng(42)固定随机种子保证实验可复现。第三坑LSTM输出的初始预测值可能出现“滞后效应”——也就是预测曲线比真实曲线晚几天变化整体看起来像是向右平移了一个单位。这种情况在金融时序预测里很常见原因是模型倾向于学习“今天的值约等于昨天的值”这个安全但无用的规律。缓解办法是用差分序列做目标值而不是原始价格也就是预测涨跌幅而不是预测绝对价格。我后来在项目中尝试了这个思路方向准确率有进一步提升。第四坑验证集早停设置不当。早停的验证集不能和测试集混用否则你会在验证集上做了早停又拿它去报告指标等于二次泄露。正确做法是训练集、验证集、测试集严格划分训练过程中只看训练集和验证集测试集只有到最后评估时才碰。6. 后续扩展与个人体会这个项目做完之后我对深度学习模型在金融时序数据上的理解深了不少。最核心的一条体会是不要迷信某一种模型也不要轻视SVM这样的经典方法。深度模型擅长大规模数据下的自动特征学习经典方法在小样本残余规律拟合上反而更有优势两者结合能够取长补短。很多人一上来就用大模型堆参数忽略了数据处理和模型组合结构上的合理设计结果效果反而一般。后续扩展方向上我认为有几个值得尝试的点。一是引入注意力机制让LSTM在学习时对不同时间步赋予不同的权重这能提高模型对关键时间点的敏感度二是用多步预测替代单步预测这样对实际交易的指导价值更大三是把更多的因子数据如技术指标、板块联动数据作为输入特征而不局限于OHLCV这五列。我自己在接下来的版本里已经在尝试把这些点逐步加进去目前看效果是有提升的但这部分实验还在进行中等数据跑完再单独把坑和收益写出来。最后分享一个实际操作中的小技巧部署时可以把训练好的网络和SVM模型保存成.mat文件GUI启动时直接加载不需要每次打开都重新训练。这样不仅启动速度快而且还能保证预测结果的一致性避免因随机初始化导致的差异。做演示或者分享的时候这一个小改动能让整个工具的体验上升一个档次。本文还有配套的精品资源点击获取
返回列表