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

BP神经网络预测股票价格:从数据预处理到防坑指南

发布时间:2026/9/26 21:13:42

资讯中心
01
ARTICLE

BP神经网络预测股票价格:从数据预处理到防坑指南

BP神经网络预测股票价格:从数据预处理到防坑指南
简介这是一份面向股票价格预测学习者的BP神经网络实战源码包提供MATLAB脚本与配套CSV历史行情数据覆盖从数据读取、归一化处理到网络训练和预测的核心流程适合初学者快速入门也便于相关课程设计与实验复现。压缩包共2个文件以.m脚本和.csv数据为主整体仅23KB结构精炼适合直接对照研究BP网络的权重调整与反向传播机制。资源已有5759人学习下载实用性获得一定认可。借助其中的示例读者可以了解开盘价、最高价、最低价、收盘价等关键特征如何作为输入变量并通过MATLAB神经网络工具箱完成模型搭建和结果验证为后续迁移到Python环境或扩展更多股票指标提供参考。1. BP神经网络预测股票价格为什么入门代码跑通就废了第一次跑网上拷贝的“BP神经网络预测股票价格 Python 代码”时我盯着那根紧贴真实收盘价、却永远晚一天的预测曲线一度以为是数据对齐写错了。后来把所有常见写法拆开才发现不是某一行代码出了问题而是整个“用BP单点预测下一日股价”的目标定义就不够严谨。这篇笔记想用一个可复现的技术方案说清楚——如何用Python和MATLAB分别搭建BP神经网络预测股票价格数据怎么预处理、代码怎么写、调参看什么以及哪些坑会让你的测试指标看起来完美但实际不能用。适合刚接触神经网络的量化初学者也适合给赛事课题做基线的工程师。2. 把价格序列变成监督学习滑窗、归一化与按时间切分BP神经网络从结构上看学习的是输入 X 到输出 Y 的映射关系。股票价格是一维时间序列直接喂给网络没有意义必须先把它转化成一组“已知过去、预测未来”的样本对。这一步做得对不对直接决定后面所有代码是涨是跌。2.1 用滑窗构造样本lookback 和 horizon 是两个核心旋钮常见做法是滑窗用过去 lookback 个交易日的收盘价作为输入用未来 horizon 个交易日后的收盘价作为标签。我一般会写一个通用函数避免在每次实验里重复切片逻辑。import numpy as np def make_window(data, lookback5, horizon1): X, y [], [] for i in range(lookback, len(data) - horizon 1): X.append(data[i - lookback:i]) y.append(data[i horizon - 1]) return np.array(X), np.array(y)逻辑说明data 是归一化后的一维收盘价序列。for 循环从 lookback 开始保证每个样本都有完整的 lookback 个历史值标签取的是第 i horizon - 1 个值也就是未来第 horizon 个交易日的价格。参数 lookback 决定看多少天历史horizon 决定预测多远。很多入门代码把 horizon 固定为 1这样模型学到的往往是“今天的价格接近昨天”预测曲线严重滞后。把 horizon 调成 3 或 5预测的是未来第 3 或第 5 个交易日的价格滞后现象会明显改善但代价是误差也会跟着变大。要注意这个函数生成的样本量是 len(data) - lookback - horizon 1不是 len(data)。后面切分训练集时如果直接用原始长度切会少几个样本容易顺手就把最后几个测试样本切没了。2.2 MinMaxScaler 的拟合位置最容易让测试集作弊的一步股票价格数值往往从几十到几百变化不归一化直接喂给 BP 网络隐藏层的 tanh 激活函数很容易饱和梯度直接消失。常见做法是用 MinMaxScaler 把价格压缩到 0 到 1 之间但真正要命的是 scaler 放在哪里拟合。from sklearn.preprocessing import MinMaxScaler train_size int(len(close) * 0.8) scaler MinMaxScaler(feature_range(0, 1)) scaler.fit(close[:train_size]) # 只拟合训练段 train_scaled scaler.transform(close[:train_size]) test_scaled scaler.transform(close[train_size:])逻辑说明scaler.fit 的参数必须是训练集切片不是全量 close。如果先对全量数据 fit再切分那么测试集的最小值和最大值已经被训练阶段看到了相当于你手里的考卷在考试前就印了参考答案。注意 transform 的时候训练集和测试集都要用同一个训练集上拟合出来的 scaler不能分别 fit。参数说明MinMaxScaler 的 feature_range 默认就是 (0, 1)一般不用改。真正要改的是训练集比例0.8 是常见起点样本量少时可以提到 0.9但要确保测试集还有至少几十个交易日的数据否则测试误差不具备参考价值。2.3 按时间顺序切分训练集绝不能随机打乱BP 网络的训练代码很多人习惯直接对全量样本做随机打乱这在普通分类问题里没问题但股票数据一旦随机打乱训练集里会出现未来样本测试集里会出现历史样本模型等于开卷考试。正确做法是严格按时间顺序切片。X, y make_window(close_scaled[:, 0], lookback5, horizon1) cut train_size - lookback X_train, X_test X[:cut], X[cut:] y_train, y_test y[:cut], y[cut:]逻辑说明这里 cut 必须等于 train_size - lookback因为 make_window 从第 lookback 个位置才开始有样本前 lookback 个位置没有对应输入。如果直接用 train_size 去切训练集会多出几个未来样本虽然影响不大但严谨起见还是减去 lookback。测试集一旦切出来整个实验期间就不能再动它所有调参和训练都只看训练集和验证集。3. Python 手写 BP 神经网络从 numpy 反向传播到 Keras 替换很多帖子直接贴 Keras 代码但如果你不懂反向传播遇到 loss 变成 NaN 或者预测曲线完全不动时会非常被动。我的建议是先用手写 numpy 版本跑通理解每一步在干什么然后再切换到 Keras 版本提升训练效率。3.1 最小手写 BP 网络30 行代码跑通股价预测下面这个类是标准的三层 BP 网络输入层、单隐藏层、线性输出层。隐藏层用 tanh输出层不用激活函数因为回归任务需要输出连续价格。class BPNet: def __init__(self, n_in, n_hidden, lr0.005, seed42): rng np.random.default_rng(seed) # 权重初始化太大会让tanh饱和0.5是一个安全的起始量级 self.W1 rng.normal(0, 0.5, (n_in, n_hidden)) self.b1 np.zeros((1, n_hidden)) self.W2 rng.normal(0, 0.5, (n_hidden, 1)) self.b2 np.zeros((1, 1)) self.lr lr def forward(self, X): self.h np.tanh(X self.W1 self.b1) self.y self.h self.W2 self.b2 return self.y def backward(self, X, y_true): m X.shape[0] dout (self.y - y_true) / m dW2 self.h.T dout db2 np.sum(dout, axis0, keepdimsTrue) dh dout self.W2.T dz dh * (1 - self.h ** 2) dW1 X.T dz db1 np.sum(dz, axis0, keepdimsTrue) self.W2 - self.lr * dW2 self.b2 - self.lr * db2 self.W1 - self.lr * dW1 self.b1 - self.lr * db1 def fit(self, X, y, epochs300): for _ in range(epochs): self.forward(X) self.backward(X, y) def predict(self, X): return self.forward(X)逻辑说明forward 里先算隐藏层 tanh 输出再算输出层线性结果。backward 基于 MSE 损失求导dout 是输出层误差dW2 和 dW1 是两层权重梯度。关键是 tanh 的导数等于 1 减去隐藏层输出的平方所以代码里写 (1 - self.h ** 2)。整个类没有引入任何深度学习框架只要 numpy 能跑。参数说明n_in 必须等于 lookback 大小n_hidden 先给 10 到 20 之间lr 给 0.005 比较保守太高容易发散太低收敛慢epochs 给 300 到 500 足够看趋势。我习惯固定随机种子这样每次跑出的结果一致排查问题才方便。3.2 完整训练流程归一化、切分、预测、逆变换单独一个类还不够需要把前面所有步骤串在一起。下面这段代码可以直接放进脚本里跑前提是 df 里有一列叫 close 的收盘价。close df[close].values.reshape(-1, 1) scaler MinMaxScaler(feature_range(0, 1)) train_size int(len(close) * 0.8) scaler.fit(close[:train_size]) close_scaled scaler.transform(close) X, y make_window(close_scaled[:, 0], lookback5, horizon1) cut train_size - 5 X_train, y_train X[:cut], y[:cut] X_test, y_test X[cut:], y[cut:] model BPNet(n_in5, n_hidden10, lr0.005) model.fit(X_train, y_train, epochs500) pred_test scaler.inverse_transform(model.predict(X_test))逻辑说明这里先对价格做 MinMax 归一化然后用 make_window 生成样本。注意切分时 cut 减去了 5因为 lookback 等于 5。训练完成后model.predict 输出的是 0 到 1 之间的归一化预测值必须用同一个 scaler 的 inverse_transform 还原成真实价格否则你画的曲线和真实价格不在一个量纲上。参数说明lookback 为 5 表示用一周交易数据预测未来这是一个性价比很高的起点。horizon 为 1 时预测的就是下一交易日收盘价此时滞后几乎不可避免如果换成 horizon 为 5预测的是未来第 5 天价格曲线形态会平滑很多也更有预测意义。3.3 切换 Keras 版本三行代码获得更强默认值手写版本适合学习但实际做批量回测时我一般会切到 Keras。因为手写 BP 只能梯度下降而 Keras 自带 Adam 优化器收敛速度和鲁棒性都要好一个档次。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense keras_model Sequential([ Dense(10, activationtanh, input_shape(5,)), Dense(1) ]) keras_model.compile(optimizeradam, lossmse) keras_model.fit(X_train, y_train, epochs100, batch_size32, verbose0) pred_keras scaler.inverse_transform(keras_model.predict(X_test, verbose0))逻辑说明这个模型结构和手写 BPNet 几乎一样同样是隐藏层 10 个神经元加 tanh输出层线性。Keras 的默认初始化方式对 tanh 更友好Adam 会自动调整每个参数的学习率实际使用中往往比固定 lr 的梯度下降更平滑。参数说明epochs 从 300 降到 100因为 Adam 收敛快得多batch_size 为 32如果训练样本只有几百条改成 16 或者直接用全量训练效果会更稳定。需要注意Keras 里 Dense(1) 默认没有激活函数这就是线性输出不用额外加 linear。4. MATLAB 实现feedforwardnet 与手写反向传播的取舍MATLAB 的 Deep Learning Toolbox 保留了经典的神经网络函数处理股票价格这类小样本回归任务feedforwardnet 比 Python 里的 Keras 更容易上手尤其适合已经习惯 MATLAB 矩阵操作的人。这里给出一套可以直接替换数据的代码。4.1 用 feedforwardnet 搭 BP 网络divideblock 保住时间顺序closePrices data.Close; % 列向量收盘价 n length(closePrices); lookback 5; X zeros(n - lookback, lookback); Y zeros(n - lookback, 1); for i lookback1:n X(i - lookback, :) closePrices(i-lookback:i-1); Y(i - lookback, :) closePrices(i); end muX mean(X); sdX std(X); X (X - muX) ./ sdX; muY mean(Y); sdY std(Y); Y (Y - muY) ./ sdY; net feedforwardnet(10); net.trainFcn traingd; net.trainParam.lr 0.01; net.trainParam.epochs 1000; net.trainParam.goal 1e-5; net.divideFcn divideblock; net.divideParam.trainRatio 0.8; net.divideParam.valRatio 0.1; net.divideParam.testRatio 0.1; [net, tr] train(net, X, Y); predNorm net(X); predClose predNorm * sdY muY;逻辑说明for 循环把过去 5 天的收盘价拼成特征矩阵 X把当前收盘价作为标签 Y这和 Python 里的 make_window 完全等价。归一化时 X 和 Y 要分开做因为它们的量纲不同。feedforwardnet(10) 创建一个隐藏层 10 个神经元的 BP 网络divideblock 是这里最关键的一行它按数据块的顺序划分训练集、验证集和测试集而不是随机抽取这保证了时间顺序不被破坏。train 函数的输入必须转置成“特征数乘样本数”的格式所以 X 和 Y 是标准写法。参数说明net.trainFcn 设置训练算法traingd 是标准梯度下降适合教学复现如果想快速看到结果可以把 trainFcn 改成 trainlm也就是 Levenberg-Marquardt 算法收敛极快但内存占用更高小样本上更容易过拟合。trainParam.lr 只对 traingd 和 traingdm 生效对 trainlm 没有意义。4.2 trainFcn 怎么选traingd、trainlm、trainscg 的对比MATLAB 的 BP 网络训练函数很多但股票预测任务主要看下面三个。我把它们的取舍整理成表格。trainFcn收敛速度内存占用适用场景traingd慢低教学、需要逐次观察收敛曲线trainlm极快高小样本快速实验但容易过拟合trainscg中等中数据量中等、想避免手调 lr我一般先拿 traingd 配 lr0.01 跑 500 轮确认网络结构和归一化没问题然后切到 trainscg 做最终训练。trainlm 虽然快但在几百条股票样本上经常出现验证集损失上涨反而不如慢一点的算法可控。4.3 手写 MATLAB 反向传播什么时候值得自己造轮子如果你需要把成交量、技术指标、文本情绪一起拼进输入特征工具箱的方式会不够灵活。这时可以用矩阵运算手写反向传播代码其实很短。X X_train; Y Y_train; [nIn, m] size(X); nHidden 8; lr 0.01; W1 randn(nIn, nHidden) * 0.5; b1 zeros(1, nHidden); W2 randn(nHidden, 1) * 0.5; b2 0; for epoch 1:500 h tanh(X * W1 b1); yPred h * W2 b2; dout (yPred - Y) / m; W2 W2 - lr * (h * dout); b2 b2 - lr * sum(dout, 1); dhidden (dout * W2) .* (1 - h.^2); W1 W1 - lr * (X * dhidden); b1 b1 - lr * sum(dhidden, 1); end逻辑说明这版完全照着 BP 公式推隐藏层 tanh输出层线性。关键在于 dhidden 的广播写法dout 是 1 乘 m 的行向量dout * W2 得到 1 乘 nHidden 的行向量再与 (1 - h.^2) 逐元素相乘。代码可以直接跑不需要额外工具箱。缺点是少了验证集监控需要自己在循环里每隔几十轮记录一次误差。5. BP 预测股票价格避坑滞后、过拟合、数据泄露、NaN 的四次翻车这一章写我实际踩过的坑。每个都是“现象、原因、解决”的结构方便你直接对照排查。5.1 预测曲线总慢半拍模型把“昨天收盘价”当成了标准答案现象训练集 MSE 很低但把预测曲线和真实价格画在一起预测永远在真实价格转弯之后才动整个曲线像被向右平移了一天。原因当 horizon1 时模型要用过去 5 天收盘价预测明天收盘价而股票价格近似随机游走明天价格和今天价格高度相关网络学到的其实就是“预测值接近最后一个输入值”。这不是代码 bug而是目标定义带来的必然结果。解决把 horizon 从 1 改成 5 或 10预测未来第 5 或第 10 个交易日的价格滞后效应会明显减弱。另一个办法是改变训练目标不预测收盘价而是预测未来 5 日的涨跌幅这样输出值在 0 附近震荡网络更容易学到方向信息。5.2 训练损失很低、测试却是一条直线过拟合和神经元数量现象训练集 loss 一路降到很小测试集预测几乎不变画出来是一条平线RMSE 甚至比直接用昨天收盘价预测还差。原因BP 网络拟合能力太强隐藏层神经元给到 20 个以上训练样本只有几百条网络直接背下了训练集噪声碰到没见过的测试数据只能输出一个“平均值”。解决先把隐藏层降到 8 个左右epochs 控制在 200 到 300观察验证集 loss 变化。还不行就给输出层加 L2 正则Keras 里直接在 Dense 层加 kernel_regularizer手写版本则可以在权重更新时额外减去一个小的衰减项。注意观察训练 loss 和测试 loss 的差距差距越大过拟合越明显。5.3 随机打乱样本测试集偷看了未来现象测试集 RMSE 低得离谱甚至比训练集还低但把模型放到后续一段真实数据上立刻失效。原因数据切分时用了 train_test_split(X, y, shuffleTrue)或者自己加了一句 np.random.shuffle。股票样本之间有时间依赖一旦打乱测试集里混入未来价格信息模型在测试集上等于开卷考试。解决强制使用顺序切分。Python 里不要用默认 shuffle直接用切片MATLAB 里不要用 dividedata 默认的随机划分必须设置 divideFcndivideblock。我还会把测试区间选在训练区间之后的一段独立行情里例如训练 2018 到 2020测试只放 2021 全年。5.4 MinMaxScaler 用全量数据拟合测试集统计信息提前暴露现象训练时正常测试时误差偏小但把同一套代码换到另一只股票上效果大幅下降。原因写入 scaler.fit(close) 而不是 scaler.fit(close[:train_size])导致测试集的最小值和最大值已经被模型见过。虽然这不是直接看到价格但归一化参数本身也是数据信息属于温和的数据泄露。解决严格先切分再 fit。代码里我习惯把归一化、切分写成同一个函数任何脚本都不允许先执行全量 scaler.fit。MATLAB 里如果早期版本没有现成的 MinMaxScaler就自己算 mu 和 sd并且只从训练段计算。5.5 loss 变成 NaN学习率、梯度爆炸与脏数据现象训练到第几次迭代时 loss 突然变成 NaN预测值全部变成 nan。原因最常见是学习率太大梯度一步跨出太远其次是输入数据里有空值或除零产生的 Inf再就是权重初始化太大tanh 进入饱和区后梯度消失数值不稳定。解决把 lr 从 0.01 降到 0.001或者换成 Adam 优化器。数据喂进去之前先检查 close 值有 NaN 或 0 时直接用 fillna 处理。初始化权重从 rng.normal(0, 0.5) 改成更小的 0.3能减少前几次迭代的爆炸概率。如果手写代码一直出 NaN可以在 backward 里加一句 np.clip 截断梯度但这只是兜底根因还是 lr。6. 让预测结果接受拷问方向准确率、滚动回测与自动重训MSE 是BP网络训练时最自然的损失函数但股票预测场景下一张标准的回归指标表会骗人。预测值只要紧贴前一天价格MSE 就能很好看交易价值却几乎为零。所以我最后一段建议所有结果都要过三关。关一是方向准确率。不要只看 RMSE把预测序列和真实序列做一阶差分比较符号一致的比例。def direction_acc(y_true, y_pred): return np.mean(np.sign(np.diff(y_true.flatten())) np.sign(np.diff(y_pred.flatten())))方向准确率 50% 说明模型做工等于抛硬币55% 以上才算有一点预测意义。建议把 MSE、方向准确率、最大回撤三行指标打在同一张表里否则特别容易高估模型。关二是滚动回测。一次性把测试集跑完属于事后诸葛更扎实的做法是 walk-forward每次只用测试区间前一段已经发生过的数据训练逐步向后滚动每 5 天或 10 天重训一次。这个过程会让你看清模型在行情风格切换时会不会崩掉。我一般会保留最近两年的数据只做样本外验证任何参数调整都必须先跑完一轮滚动回测才作数。关三是自动重训阈值。我现在的习惯是设置一个方向准确率底线例如 0.52滚动预测期间一旦低于这个值就自动把当前窗口内最新数据并入训练集重训而不是继续使用旧模型。这个规则比手动看预报曲线省力得多也能避免长时间跑批时模型悄悄失效。这套流程做完你再看网上那些单次训练后的漂亮预测图就不会轻易被带偏了。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

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

03
WHY YAOTU

想打造同款高转化官网?

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

◈

场景化定制

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

◐

营销型架构

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

▲

全周期服务

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

免费获取你的建站方案

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