基于Matlab的贝叶斯神经网络股票多指标预测实践
2026/9/16 6:13:48 网站建设 项目流程

简介:一套基于Matlab实现的贝叶斯神经网络股票多指标预测方案,适合计算机、电子信息、数学等专业学生在课程设计、期末大作业或毕业设计中作为参考资料,需要具备一定编程基础并自行调试。压缩包共2000个文件,以1387个m源文件为核心,覆盖网络构建、训练与预测等完整流程;另有c文件用于底层辅助计算,mat、xls等文件存放样本数据与指标,htm、txt提供说明文档,整体仅6.51MB,结构较好,方便按需查阅。目前已有523人学习或下载这份素材。通过完整源码与配套数据,读者可复现贝叶斯神经网络在股票多指标预测中的建模过程,理解先验设置、证据积累与结果分析等关键环节,也可在此基础上修改网络参数或增加指标,快速搭建自己的实验框架。

1. 贝叶斯神经网络凭什么比普通MLP更适合股票多指标预测

做量化或者金融时序预测的同行应该都有体会:普通MLP把股价当确定性函数去拟合,训练集上loss压得再低,一到样本外就露怯,而且给不出"这次预测到底有多可信"的量化指标。贝叶斯神经网络(BNN)不一样,它把每个权重从固定值变成概率分布,预测输出自然带置信区间。说白了,它回答的不只是"明天涨还是跌",而是"在多大把握下涨、涨多少"。这套资源里除了Matlab源码和股票数据,还打包了init_pot.ccollect_evidence.cdistribute_evidence.c这一组贝叶斯网络推理引擎的参考实现,正好把"信念传播/变量消元"的底层机制也补齐了。适合正在做课程设计、期末大作业,或者想把手头BP神经网络升级成BNN的工程师参考。

2. 先验设计到后验推断:BNN的核心机制与Matlab实现路径

2.1 权重不确定性的数学表达

传统神经网络训练目标是找一组最优权重(w^*),而BNN的目标是求权重后验分布(p(w|D))。根据贝叶斯公式:

[ p(w|D) = \frac{p(D|w)p(w)}{p(D)} ]

其中(p(w))是先验分布,(p(D|w))是似然。真正难算的是分母(p(D))——边际似然,也叫模型证据。在股票预测这种高维连续场景下,精确后验不可解,常见做法是用变分推断,找一个简单的分布(q_\theta(w))去逼近真实后验,通过最大化证据下界(ELBO)来优化:

[ \mathcal{L}(\theta) = \mathbb{E}{q\theta(w)}[\log p(D|w)] - \mathrm{KL}(q_\theta(w) | p(w)) ]

这个式子左边是数据拟合项,右边是正则项。KL散度项会自动惩罚过度复杂的权重分布,这就是BNN不容易过拟合的根本原因。对照打包源码里的init_pot.cdistribute_evidence.c,那套HUGIN风格的因子图引擎处理的是离散证据传播;而Matlab端的BNN实现则做了连续化变体——把因子图里的势函数替换成高斯分布的均值和方差。

2.2 源文件结构与推理引擎的角色分工

解压后你会看到两个层次的代码。Matlab主程序负责数据读取、网络构建和训练;C语言文件组是辅助参考模型,用于理解证据传播机制。它们不是Matlab直接调用的,而是给你对照原理用的。各文件职责如下:

文件职责对应BNN概念
init_pot.c初始化势函数表初始化权重先验分布
collect_evidence.c收集证据到团节点数据似然项的聚合
distribute_evidence.c从团节点分发后验消息后验分布的广播更新
marg_sparse_table.c稀疏表上的边缘化计算高维权重的边缘分布近似
nrutil.c数值计算工具函数矩阵运算、随机数生成

建议先跑Matlab主程序,跑通了再回头读C代码。因为Matlab脚本里已经把init_pot对应的先验初始化封装成了函数,你不需要手动编译C文件。

2.3 Matlab端网络构建的最小可运行框架

主程序的核心结构如下(简化后便于说明参数含义):

% 构建贝叶斯神经网络主体 layers = [ featureInputLayer(10, 'Normalization', 'zscore', 'Name', 'input') fullyConnectedLayer(32, 'Name', 'fc1') reluLayer('Name', 'relu1') fullyConnectedLayer(16, 'Name', 'fc2') reluLayer('Name', 'relu2') fullyConnectedLayer(1, 'Name', 'output') ]; % 转换为贝叶斯层(关键步骤) bLayers = bayesianLayers(layers, 'Prior', 'gaussian', ... 'PriorMean', 0, 'PriorStd', 1);

这段代码里featureInputLayerNormalization参数设为zscore,要求输入特征先做标准化。fullyConnectedLayer(32)中的32是隐层神经元数,对股票日线数据来说,10个输入特征对应32个隐层节点是够用的。bayesianLayers是封装函数,内部实现是把普通全连接层的权重矩阵替换成均值矩阵加标准差矩阵,训练时每个batch重新采样。

在Matlab中运行help bayesianLayers可以查看完整参数列表。如果手头版本没有这个函数,可以下载第三方BNN工具箱,或者自己实现一个自定义层——继承nnet.layer.Layer,在predict方法里用randn生成噪声乘上标准差再加到均值权重上。这部分是BNN区别于普通网络的分水岭,不能省。

3. 多指标特征工程与股票数据集的预处理实战

3.1 原始数据堆积问题与指标派生原则

股票预测里最直接的坑是原始行情字段根本不够用。OHLCV(开盘、最高、最低、收盘、成交量)五列原始数据直接喂进网络,模型很难学到有效模式。常见做法是把原始行情转换成技术指标,再经过差分处理让序列平稳化。这套资源的数据包里已经预处理了一部分,但理解指标构造逻辑仍然必要。

以常用多指标集为例,特征组分为四类:动量类(RSI、MACD、KDJ)、波动类(ATR、布林带宽度)、量能类(OBV、量比)、资金流类(主力净流入占比)。每组取1-2个代表性指标,最终拼成10维特征向量。构造指标时最需要注意窗口期参数——RSI默认14天,在A股日线数据上通常保留默认值;但ATR的窗口建议从14改成10,因为A股涨跌停制度下波动聚集效应比成熟市场更强,短窗口能更快捕捉波动变化。

3.2 数据切分的时序陷阱

股票数据不能随机打乱划分训练集和测试集,否则会造成严重的数据泄漏。时间序列必须按时间顺序切分。正确的切分策略是:

% 按时间顺序切分,避免未来函数 data = load('stock_data.mat'); features = data.features; % [样本数, 特征维度] returns = data.returns; % 下一日收益率 trainLen = floor(size(features, 1) * 0.7); valLen = floor(size(features, 1) * 0.15); trainX = features(1:trainLen, :); trainY = returns(1:trainLen, :); valX = features(trainLen+1:trainLen+valLen, :); valY = returns(trainLen+1:trainLen+valLen, :); testX = features(trainLen+valLen+1:end, :); testY = returns(trainLen+valLen+1:end, :); % 用训练集的均值和方差做标准化,验证集/测试集沿用同一组参数 mu = mean(trainX); sigma = std(trainX); trainX = (trainX - mu) ./ sigma; valX = (valX - mu) ./ sigma; testX = (testX - mu) ./ sigma;

trainLen+1:trainLen+valLen这样的区间写法保证了验证集严格跟在训练集后面,不会混入未来数据。标准化时特别注意musigma只在训练集上计算,如果对全量数据统一计算,验证集的信息就提前泄漏到训练过程了。这是我见过的初学者最常犯的错误,调试时loss曲线看着挺好,实盘一测就崩,通常就是这个原因。

3.3 标签构造与样本不均衡处理

标签取的是"下一交易日收益率"returns,而不是"涨/跌"二分类。这背后的考虑是:用连续值做回归可以保留涨跌幅度信息,且BNN输出自带置信区间,可以直接计算预测收益率的概率分布。如果一定要做分类(比如预测涨跌方向),需要对连续收益率做阈值离散化,阈值通常设为0(涨/跌)或历史分位数(大涨/大跌/震荡)。

样本不均衡问题在日线数据上不像风控场景那么严重,但如果用分类标签,可能遇到上涨天数远少于下跌天数的情况。处理办法是给少数类样本更高的损失权重。在BNN框架下,等价的做法是调整似然函数中类别对应的高斯分布方差——把少数类的方差调小,让误判这类样本产生更大的梯度信号。

4. 训练配置、ELBO监控与不确定性量化

4.1 训练超参数与优化器选择

BNN的训练目标是最小化负ELBO,等价于普通网络的损失函数加上KL散度项。Matlab端训练配置如下:

options = trainingOptions('adam', ... 'MaxEpochs', 200, ... 'MiniBatchSize', 64, ... 'InitialLearnRate', 1e-3, ... 'LearnRateSchedule', 'piecewise', ... 'LearnRateDropFactor', 0.1, ... 'LearnRateDropPeriod', 50, ... 'ValidationData', {valX, valY}, ... 'ValidationFrequency', 10, ... 'GradientThreshold', 5, ... 'Verbose', true);

各参数的实际作用:MaxEpochs=200在日线数据上足够,因为金融时序信噪比低,训练太久必然过拟合,对BNN来说过拟合表现为后验分布坍缩到先验均值附近。MiniBatchSize=64是内存和梯度噪声的折中,太小则KL项估计不稳定,太大则随机采样的优势消失。LearnRateSchedule选择piecewise而不是sgd的固定学习率,是因为BNN的ELBO曲面陡峭程度变化大,后期需要更小的步长才能稳定收敛。GradientThreshold=5做梯度裁剪,防止个别离群样本导致梯度爆炸——这在股票数据里很常见,突发利空造成的极端收益率会把网络参数推出正常工作区间。

4.2 ELBO曲线读法与收敛判据

普通网络训练看loss,BNN训练除了看损失,更要看ELBO的分解。建议在训练循环里手动记录两项指标:

% 每一轮记录ELBO分解项 history.kl = kl_divergence(net, prior); % 近似后验与先验的KL history.likelihood = log_likelihood(net, trainX, trainY); history.elbo = history.likelihood - history.kl;

观察三个现象:第一,KL项如果快速冲到非常小的数值(接近0),说明后验退化成了先验,模型没学到数据信息,典型原因是学习率过大把优化过程带偏;第二,似然项如果持续下降但KL不变,说明模型只在记忆数据噪声,没有压缩出有效特征;第三,理想曲线是训练前期似然快速上升、KL缓慢增长,后期两者都趋于平台期。如果训练到100轮后KL项还在快速上升,说明先验强度设置过低(PriorStd太大),权重分布过度自由。

4.3 从预测分布提取交易信号

训练完成后,预测不再是一次前向传播,而是对权重分布做多次采样,得到预测分布:

numSamples = 200; predSamples = zeros(numSamples, length(testY)); for s = 1:numSamples % 每次前向传播重新采样权重,得到一条预测轨迹 predSamples(s, :) = predict(net, testX)'; end predMean = mean(predSamples, 1); predStd = std(predSamples, 0, 1); % 95%置信区间 lowerBound = predMean - 1.96 * predStd; upperBound = predMean + 1.96 * predStd;

numSamples=200是经验值,太少则均值和方差的估计波动大,太多则计算成本高。predStd就是每个样本点上的预测不确定性——如果测试集某天的predStd显著高于历史平均水平,说明模型对该时点的预测信心不足,可能是出现了训练分布之外的极端行情模式。实盘应用时可以用这个标准差做仓位控制:标准差大时降低仓位,标准差小时提高仓位。这正是BNN比普通网络多出来的实用价值。

5. 先验灵敏度分析与收敛性验证:三个实用排错技巧

5.1 先验标准差扫描法

BNN的第一个排错步骤是检查先验设置是否合理。很多人在课程设计里直接套用高斯先验PriorStd=1,得到的后验分布往往过于分散或过于集中。正确做法是做一组先验灵敏度扫描:

priorStdList = [0.1, 0.5, 1.0, 2.0]; valMetrics = zeros(length(priorStdList), 1); for p = 1:length(priorStdList) bLayers = bayesianLayers(layers, 'Prior', 'gaussian', ... 'PriorMean', 0, 'PriorStd', priorStdList(p)); net = trainNetwork(trainX, trainY, bLayers, options); yPred = predict(net, valX); valMetrics(p) = mse(valY, yPred); end

观察验证集MSE随PriorStd的变化曲线。如果最佳先验标准差出现在两端(要么极小要么极大),说明数据分布和先验设定有明显错位。PriorStd=0.1附近最优意味着数据噪声很小,模型应该更窄;PriorStd=2.0附近最优则说明数据中可能有未被特征工程捕获的异常波动。正常情况是曲线呈U形,最优值在中间。这个扫描本身也能作为课程设计报告里的一张关键图表。

5.2 预测校准误差(Calibration Error)检查

BNN给出的95%置信区间是否真的覆盖了95%的实际样本?这个检查往往能暴露隐性bug。计算经验覆盖率:

coverage = mean(testY >= lowerBound' & testY <= upperBound'); % 期望覆盖率是95%,如果实际只有70%,说明不确定性被低估 % 如果实际是99%,说明模型过度保守

经验覆盖率与期望值偏差超过5个百分点,优先怀疑似然函数设置问题。如果覆盖率偏低,试着把似然的高斯方差调大(相当于容忍更大的数据噪声);如果偏高,调小方差。这个参数在bayesianLayers的配置项里对应LikelihoodVariance,需要在训练时就设定好,训练完成后无法修复。

5.3 边际似然作为模型选择的最终裁决

最后说边际似然。训练结束后,可以用拉普拉斯近似估算边际似然:

% 用验证集上的残差估计噪声方差 residuals = valY - predict(net, valX); noiseVar = var(residuals); % 对ELBO做BIC类修正,近似边际似然 logML = history.likelihood(end) - 0.5 * numParams * log(trainLen);

numParams是网络总参数量,trainLen是训练样本数。对比不同结构(层数、节点数)的logML值,选择最大的那个,而不是单纯比较验证集MSE。这两个指标经常给出不同答案——MSE最低的模型可能复杂度太高,在金融时序上泛化能力有限;而边际似然天然包含复杂度惩罚,给出的选择更稳健。这个判断逻辑同样适用于对比BNN和普通MLP:普通MLP没有概率解释,无法计算边际似然,也就没法做这种等价的模型选择。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询