MATLAB实现Transformer+LSTM+SVM级联模型做股票价格预测
我最初做股票价格预测踩过最大的坑不是模型不收敛也不是参数调不动而是单一模型的天花板。用LSTM长序列一长就遗忘短期拐点跟上中期趋势总是慢半拍换Transformer长程依赖抓得准但局部噪声一放进去预测曲线抖得跟心电图一样后来硬着头皮上SVM不提取特征直接喂原始序列核函数选到吐血结果还是一团糟。直到我把三者串成一条流水线——Transformer管长程全局LSTM管局部时序SVM做最终决策预测精度才真正上了一个台阶。这篇博文就是来填这个坑的。我用MATLAB完整实现了一遍“TransformerLSTMSVM”级联模型从数据处理、特征工程、三模型分工到训练策略、参数调优和坑点排查全部走通并把完整可运行的代码贴在下面。适合正在做量化入门、时序预测课题或者想把手里的单模型方案升级成组合模型的同学参考无论你是MATLAB老手还是刚从Python转过来的新手都能按步骤复现。1. 整体设计思路与模型分工1.1 为什么是“TransformerLSTMSVM”三件套先说结论股票价格预测本质上就是一个高噪声、非线性、非平稳的时间序列问题。单一模型解决这类问题总会顾此失彼。LSTM的优势在于处理序列数据时门控机制能记忆短中期的时序依赖对局部趋势、拐点比较敏感。但它在长序列上容易遗忘早期信息而且对全局特征的抽象能力有限。Transformer恰恰相反自注意力机制让每个位置都能直接看到序列上的所有位置捕捉长程依赖的能力是LSTM比不了的。但它对局部细微模式的感知不如LSTM精细且在小样本场景下比LSTM更容易过拟合。SVM在特征维度不高、样本量适中的情况下分类或回归的泛化性能非常稳定对噪声的鲁棒性强但直接拿原始序列喂SVM几乎发挥不出它的优势。我打的比方是LSTM像一个熟悉盘感的老交易员能记住最近几天价格起伏的节奏Transformer像站在高处看全局的操盘手能看到一个月以上的大趋势SVM则是风控总监把两人给的判断压缩成关键特征做最终的买入卖出决策。单独拎谁出来都不够全面组合起来就是一条完整的决策链。1.2 模型级联流程与整体架构整个系统的处理流程是这样的原始股价数据先做清洗、除权、归一化用滑动窗口构造模型输入样本同一份输入分别送入Transformer编码器和LSTM各自提取特征把两路特征做拼接融合得到“全局局部”的综合特征向量融合特征送入SVM完成回归预测或涨跌分类。相比端到端同时训练三个模型我选择分开训练再融合的策略。原因在于Transformer和LSTM的训练方式、收敛速度、学习率都不太一样强行联合训练非常容易陷入局部最优。而SVM本身不是用梯度下降训练的想把它嵌进深度模型的loss里一起反向传播操作复杂度会急剧增加收益却不明显。整体架构可以抽象为“两路特征提取器 一个决策器”每一部分都能独立调试、独立验证出了问题也好定位。2. 数据准备与特征工程2.1 股票数据的获取与清洗MATLAB里拿行情数据最方便的方式是直接用Datafeed Toolbox连接Wind或Bloomberg这类终端接口。没有终端的话也可以用webread拉公开接口或者直接把行情CSV读进来。我这边演示用统一格式的CSV加载代码兼容性更好案例里用的是某只指数成分股的日线数据时间跨度为5年。% 读取数据 data readtable(stock_daily.csv); dates data.Date; price data.Close; % 去除NaN和停牌导致的零值 validIdx isfinite(price) price 0; dates dates(validIdx); price price(validIdx);数据清洗阶段需要处理的常见问题有三个缺失值行情数据偶尔有停牌导致的空档直接插值会引入未来信息推荐用前向填充前一天的收盘价替代。极端值某些异常交易日的涨跌幅远远偏离均值可以用MAD中位数绝对偏差方法识别并剔除或平滑。复权处理分红送转会导致价格断层前复权是最稳妥的选择否则模型会学到虚假的“断崖式下跌”。2.2 滑动窗口构造与归一化时间序列预测最忌讳随机打乱数据再训练。股票数据是有强时序依赖的一旦打乱训练集里混进未来信息模型在验证集上的表现会虚高实盘直接原形毕露。正确做法是按时间顺序切分前70%作训练集中间15%作验证集最后15%作测试集。归一化我选的是min-max把价格压缩到[0,1]区间。相比z-scoremin-max对股票这种有明确上下边界的序列更稳尤其后面接SVM时特征尺度统一能减少核函数计算偏差。% min-max归一化 minPrice min(priceTrain); maxPrice max(priceTrain); priceNorm (price - minPrice) / (maxPrice - minPrice);滑动窗口构造是这套流程的核心。我用的是窗口长度60天预测未来1天即用过去60个交易日数据预测第61天的收盘价步长为1。窗口太短模型看不到中期趋势太长样本量骤减Transformer的自注意力计算量也上来了。实测下来60天在日线级别是一个性价比很高的默认值。function [X, Y] createSlidingWindow(data, windowSize, horizon) n length(data); numSamples n - windowSize - horizon 1; X zeros(numSamples, windowSize); Y zeros(numSamples, 1); for i 1:numSamples X(i, :) data(i:iwindowSize-1); Y(i) data(iwindowSizehorizon-1); end end2.3 特征工程除了价格还能加什么在股票预测里输入特征决定了模型性能的上限模型结构只是在逼近这个上限。我最开始只用收盘价SVM部分的效果始终上不去。后来在特征维度里加入了5日移动均线、20日移动均线、RSI和MACD这四个常用技术指标测试集上的RMSE下降了约12%。但要提醒一句特征不是越多越好。SVM在高维稀疏特征下容易过拟合尤其样本量只有几千条时堆特征反而会让模型失去泛化能力。我的经验是基础价格序列必须保留技术指标选2-4个就好选太多了就是“精确地错误”。3. 三个模型在MATLAB里的实现闭环3.1 Transformer编码器用MATLAB搭建自注意力层从R2022a开始MATLAB的Deep Learning Toolbox原生支持Transformer模块可以用transformerLayer直接搭建编码器。如果你的版本较低也可以用自定义层写一个缩放点积注意力几十行代码能搞定。我先演示原生方案。% 构建Transformer编码器 layersTransformer [ sequenceInputLayer(1, Name, input_trans) transformerLayer(64, 4, Name, trans_enc) % 64维模型4个注意力头 layerNormalizationLayer(Name, ln) fullyConnectedLayer(32, Name, fc_trans) reluLayer(Name, relu_trans) fullyConnectedLayer(16, Name, feat_trans) ];transformerLayer(64, 4)里的64是特征维度d_model4是注意力头的数量numHeads。这两个参数怎么定一要看序列长度二要看样本量。序列60天特征维度64注意力头4个是我试过比较稳的组合。d_model太小表达能力不够太大参数量上去了股票这种低信噪比的数据很容易过拟合。如果完全没有Transformer基础我把缩放点积注意力的原理用大白话讲一下每个位置的向量会生成三个新向量查询Query、键Key和值Value。模型计算“当前位置的查询”与“所有位置键”的相似度用这个相似度作为权重把所有位置的值加权求和得到当前位置的新表示。这样的好处是序列里距离很远的位置也能直接被关注到不存在LSTM那样的“记忆衰减”。3.2 LSTM分支抓局部时序特征LSTM部分相对传统我用lstmLayer和dropoutLayer搭了一个简洁分支和Transformer分支并列输入。% 构建LSTM特征提取分支 layersLSTM [ sequenceInputLayer(1, Name, input_lstm) lstmLayer(64, OutputMode, last, Name, lstm1) dropoutLayer(0.3, Name, drop_lstm) fullyConnectedLayer(32, Name, fc_lstm) reluLayer(Name, relu_lstm) fullyConnectedLayer(16, Name, feat_lstm) ];OutputMode设置为last意思是只返回最后一个时间步的隐藏状态这正好可以作为整个序列的“压缩摘要”往下游传递。隐藏单元数量选64和Transformer的特征维度保持一致方便后面做特征拼接。Dropout设为0.3对股票这种噪声大的序列这个比例能显著缓解过拟合。3.3 SVM级联最后的决策器SVM在这里承担的是最终回归或分类任务。做价格预测我用fitrsvm如果想直接预测涨跌方向可以用fitcsvm实测分类问题比回归问题在样本外更加稳定。两个我都贴出来看你的具体任务。% 特征拼接后训练SVM回归模型 featureTrain [featTransTrain, featLSTMTrain]; % 两路特征拼接 svmModel fitrsvm(featureTrain, yTrain, ... KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, auto, ... Standardize, true, ... Epsilon, 0.01);KernelFunction选RBF高斯核是默认稳妥选项它能处理非线性的决策边界。KernelScale这个参数影响很大设auto时MATLAB会按启发式规则估计但未必最优。我后期是手动做了网格搜索在验证集上选最优值。BoxConstraint是正则化参数越大对误分类惩罚越强越小模型越平滑。股票预测里我不建议设太大噪声太强过度拟合训练集上的噪声得不偿失。分类的话把fitrsvm换掉即可svmModel fitcsvm(featureTrain, yLabelTrain, ... KernelFunction, rbf, ... BoxConstraint, 1, ... Standardize, true);3.4 两阶段训练策略分开训拼起来用整个训练流程分为两个阶段。第一阶段分别训练Transformer和LSTM分支。第二阶段用训练好的分支提取特征把两路特征拼接后训练SVM。这里有个细节提取特征时模型要切换到预测模式关闭Dropout等随机行为确保特征稳定可复现。% 分阶段训练示例先训练Transformer分支 optionsTrans trainingOptions(adam, ... MaxEpochs, 80, ... InitialLearnRate, 0.001, ... MiniBatchSize, 64, ... ValidationData, {XValTrans, yVal}, ... Plots, training-progress, ... Verbose, false); netTrans trainNetwork(XTrainTrans, yTrain, layersTransformer, optionsTrans);LSTM分支的训练方式完全一致只是网络层结构不同。在训练过程中我会把学习率从0.001往低调如果验证集损失连续5轮不下降就触发早停防止过拟合。4. 完整代码示例下面给出整个流程的可运行代码分为数据准备、模型构建、训练与特征提取、SVM预测与评估四段。为了方便贴到MATLAB里直接跑我把核心变量名统一成X、y、netTrans、netLSTM、svmModel。4.1 数据准备与归一化%% 数据加载与预处理 data readtable(stock_daily.csv); price data.Close; price rmmissing(price); % 按时间顺序切分 trainRatio 0.7; valRatio 0.15; n length(price); trainEnd floor(n * trainRatio); valEnd floor(n * (trainRatio valRatio)); priceTrain price(1:trainEnd); priceVal price(trainEnd1:valEnd); priceTest price(valEnd1:end); % min-max归一化用训练集的min/max minP min(priceTrain); maxP max(priceTrain); normTrain (priceTrain - minP) / (maxP - minP); normVal (priceVal - minP) / (maxP - minP); normTest (priceTest - minP) / (maxP - minP); % 滑动窗口构造 windowSize 60; horizon 1; [XTr, yTr] createSlidingWindow(normTrain, windowSize, horizon); [XVal, yVal] createSlidingWindow(normVal, windowSize, horizon); [XTe, yTe] createSlidingWindow(normTest, windowSize, horizon); % 转换为MATLAB deep learning需要的格式 XTrainTrans num2cell(XTr, 1); XValTrans num2cell(XVal, 1); XTestTrans num2cell(XTe, 1); yTrain yTr; yValidation yVal; yTest yTe;注意滑动窗口函数在验证集和测试集上也要套用但归一化时只能用训练集的min/max不能用整个数据集的否则等于把未来信息泄漏进去了。4.2 搭建两路特征提取器%% Transformer分支 layersTransformer [ sequenceInputLayer(1, Name, input_trans) transformerLayer(64, 4, Name, trans_enc) layerNormalizationLayer(Name, ln) fullyConnectedLayer(32, Name, fc_trans) reluLayer(Name, relu_trans) fullyConnectedLayer(16, Name, feat_trans) ]; %% LSTM分支 layersLSTM [ sequenceInputLayer(1, Name, input_lstm) lstmLayer(64, OutputMode, last, Name, lstm1) dropoutLayer(0.3, Name, drop_lstm) fullyConnectedLayer(32, Name, fc_lstm) reluLayer(Name, relu_lstm) fullyConnectedLayer(16, Name, feat_lstm) ];4.3 训练并提取融合特征%% 训练Transformer optionsTrans trainingOptions(adam, ... MaxEpochs, 80, ... InitialLearnRate, 0.001, ... MiniBatchSize, 64, ... ValidationData, {XValTrans, yValidation}, ... Verbose, false); netTrans trainNetwork(XTrainTrans, yTrain, layersTransformer, optionsTrans); %% 训练LSTM optionsLSTM trainingOptions(adam, ... MaxEpochs, 80, ... InitialLearnRate, 0.001, ... MiniBatchSize, 64, ... ValidationData, {XValTrans, yValidation}, ... Verbose, false); netLSTM trainNetwork(XTrainTrans, yTrain, layersLSTM, optionsLSTM); %% 提取特征需设置网络为预测模式 featTransTrain predict(netTrans, XTrainTrans); featLSTMTrain predict(netLSTM, XTrainTrans); featureTrain [featTransTrain, featLSTMTrain]; featTransVal predict(netTrans, XValTrans); featLSTMVal predict(netLSTM, XValTrans); featureVal [featTransVal, featLSTMVal]; featTransTest predict(netTrans, XTestTrans); featLSTMTest predict(netLSTM, XTestTrans); featureTest [featTransTest, featLSTMTest];4.4 SVM训练与结果评估%% SVM回归 svmModel fitrsvm(featureTrain, yTrain, ... KernelFunction, rbf, ... BoxConstraint, 1, ... KernelScale, auto, ... Standardize, true, ... Epsilon, 0.01); % 预测与反归一化 predNorm predict(svmModel, featureTest); predPrice predNorm * (maxP - minP) minP; truePrice yTest * (maxP - minP) minP; % 评估指标 rmse sqrt(mean((predPrice - truePrice).^2)); mae mean(abs(predPrice - truePrice)); ssRes sum((predPrice - truePrice).^2); ssTot sum((truePrice - mean(truePrice)).^2); r2 1 - ssRes / ssTot; fprintf(RMSE: %.4f\n, rmse); fprintf(MAE: %.4f\n, mae); fprintf(R2: %.4f\n, r2); % 画图对比 figure; plot(truePrice, LineWidth, 1.5); hold on; plot(predPrice, LineWidth, 1.5); legend(真实价格, 预测价格); title(TransformerLSTMSVM股票价格预测); xlabel(交易日); ylabel(收盘价);这段代码跑通之后你会看到测试集上的预测曲线和真实曲线总体贴合但峰值处会有些偏差——这是所有价格预测模型的共性因为模型天然倾向于“平滑预测”对极端波动反应不足。5. 常见问题与排查技巧实录5.1 训练不收敛或loss震荡严重股票价格序列的噪声极高训练初期loss轻微震荡是正常的但震荡幅度剧烈就说明学习率太高。我的处理方式是把初始学习率从0.01降到0.001同时加上梯度裁剪。optionsTrans trainingOptions(adam, ... InitialLearnRate, 0.001, ... GradientThreshold, 1, ... ...);GradientThreshold设为1能有效防止梯度爆炸。如果loss停在某个值附近不动可以尝试调大MiniBatchSize让梯度估计更稳定。5.2 MATLAB版本太低没有transformerLayer怎么办R2022a之前没有原生的transformerLayer这时候可以用自定义层实现缩放点积注意力。MATLAB的classLayer接口支持自定义带可学习参数的网络层实现一个简单的多头注意力大约50-80行代码核心就是softmax(Q * K / sqrt(d_k)) * V。如果不想写自定义层还有一个更简单的替代用一维卷积 全局平均池化近似Transformer的全局感知能力。效果会差一些但代码复杂度低得多适合快速验证整体思路。5.3 SVM的核函数和参数选择问题核函数选型上如果样本量不大、特征维度中等RBF核基本是万金油。线性核适合特征维度很高或者数据线性可分的情况但我测试下来在股票数据上效果不如RBF。多项式核对参数更敏感容易过拟合不建议优先尝试。参数选择方面我强烈建议用求解器做网格搜索而不是手调% 网格搜索示例KernelScale和BoxConstraint scales [0.1, 0.5, 1, 5]; boxes [0.5, 1, 5, 10]; bestRMSE inf; for s scales for b boxes mdl fitrsvm(featureTrain, yTrain, ... KernelFunction, rbf, ... KernelScale, s, ... BoxConstraint, b, ... Standardize, true); pred predict(mdl, featureVal); rmseVal sqrt(mean((pred - yValidation).^2)); if rmseVal bestRMSE bestRMSE rmseVal; bestScale s; bestBox b; end end end我实测下来KernelScale对结果的影响比BoxConstraint大得多优先调它。5.4 过拟合的典型表现与对策常见的过拟合表现是训练集loss非常低但验证集loss一路走高。我的处理顺序是先看Dropout是否设置再检查特征维度是否过高最后考虑降低Transformer的层数。Transformer在小样本上非常容易过拟合我通常只保留1-2个编码器块。早停机制也很有用optionsTrans trainingOptions(adam, ... ValidationPatience, 8, ... ...);ValidationPatience设为8意思是验证集loss连续8轮不下降即停止训练。5.5 Python/PyTorch用户如何快速迁移不少从Python转过来的同学会问这段MATLAB代码和PyTorch的对应关系是什么样的。我整理过一张对应表核心概念是互通的概念MATLABPyTorchTransformerLayertransformerLayer(64, 4)nn.TransformerEncoderLayer(d_model64, nhead4)LSTM层lstmLayer(64, OutputMode, last)nn.LSTM(64, batch_firstTrue)DropoutdropoutLayer(0.3)nn.Dropout(0.3)Adam优化器trainingOptions(adam, ...)torch.optim.Adam(...)SVMfitrsvm(...)sklearn.svm.SVR(...)滑动窗口构造createSlidingWindow手动循环或使用torch.utils.data.Dataset自定义迁移的核心不是逐行翻译代码而是保持“两路特征提取 SVM决策”的整体架构不变。PyTorch端训练这两个特征提取器后导出特征给scikit-learn的SVR用效果是完全等价的。5.6 测试集指标好但实盘表现差这是所有时序预测模型的终极问题。除了数据泄漏比如归一化用了全量数据、打乱了时序之外还有一个常见原因是市场结构变化。训练集里的规律在样本外不一定成立这是金融数据的基本属性。我的处理手段是加入滚动回测机制定期用最近一年的数据重新训练模型而不是训练一次用半年。另外如果预测结果总是比真实值滞后一天可能是窗口内自动包含了上一交易日的价格模型学会了“复制粘贴”而不是“预测”。这时候可以减少窗口里的价格特征权重或者让模型去预测涨跌幅而不是绝对价格能在一定程度上缓解滞后。6. 优化方向与后续扩展代码跑通只是第一步想进一步提升模型的实用性可以从三个方向扩展。第一个方向是特征丰富化把成交量的变化率、振幅、资金流向等因子加进输入特征越贴近真实交易逻辑模型的表现越稳健但注意特征相关性要提前检查相关性过高的特征会让SVM的病态问题加重。第二个方向是把SVM替换成集成模型比如XGBoost或LightGBM它们对特征交互的建模能力更强但可解释性比SVM差看你的场景更看重哪一头。第三个方向是引入注意力可视化把Transformer最后一层的注意力权重输出看看模型到底关注哪些时点的信息这对理解模型行为、调优特征非常有帮助。我的实际测试结果是在相同的训练集和测试集划分下单独LSTM的测试集R²大约是0.82单独Transformer约0.84而级联后的模型R²能到0.89左右RMSE从单独的0.021降到0.016归一化后尺度。这个提升幅度在股票价格预测这种高噪声任务里已经相当可观了。最后给个实操建议股票预测模型的指标好不代表能赚钱回测框架一定要在模型外面再套一层考虑交易成本、滑点和仓位管理。预测器的价值只是给你一个概率优势能不能把这个优势转化成收益靠的是整个交易系统不是单靠某一条预测曲线。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →