ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

粒子群优化BP神经网络在股票价格预测中的实战方法详解

粒子群优化BP神经网络在股票价格预测中的实战方法详解 简介面向股票预测研究的压缩包聚焦粒子群优化算法与神经网络的结合用来解决传统BP网络在网络结构、学习率、初始权重等参数上难以确定、预测精度波动大的问题适合金融数据分析、机器学习方向的学生、研究者以及入门开发者参考。包内共12个文件大小1.9MB组成覆盖完整实验链路docx文档记录模型训练过程与遇到的数据问题py脚本完成数据预处理和预测主流程csv提供多只股票的行情样本pdf收录粒子群优化在证券投资组合、岩爆预测等场景的应用论文。目前已有130人学习。通过该包读者能直观理解粒子群迭代寻优如何驱动神经网络参数更新直接运行Python脚本即可复现股票价格预测的基本流程配套文档和论文还有助于掌握数据清洗、模型评估、误差指标等要点适合作为课程设计、毕业设计或赛前研究的参考工程。1. 股票价格预测为什么要用粒子群优化算法找出神经网络参数大多数做股票价格预测的入门模板拿起 BP 神经网络第一件事就是构造三五个技术指标然后把样本倒进去跑反向传播。结果一般雷同训练集均方误差降得漂亮验证集方向准确率勉强在 52% 上下换个时间窗口参数就得从头调。问题的根源不完全在网络结构而是神经网络的权重寻优被梯度方向限制住了。粒子群优化算法给这个场景提供了一个不同的选项它不计算梯度只靠一群粒子在参数空间中不断比较位置好坏用个体历史最优和全局最优两个约束更新速度。这篇文章围绕“PSO神经网络”讲一套可以复现的做法从速度-位置更新公式、适应度函数构造到数据时序划分、种群规模和惯性权重的调节最后落在滚动验证的实战技巧上。适合想用元启发式算法替代局部寻优的算法工程师、量化研究新手以及对“为什么 PSO 能配神经网络”这件事想弄清楚边界的人。2. 粒子群优化算法的更新规则与神经网络权重寻优的适配2.1 速度与位置更新PSO 核心公式在解决什么问题粒子群优化算法核心是维护一个由若干候选解组成的群体。设第 i 个粒子位置为 x_i它代表参数空间中的一个 d 维点速度为 v_i。每次迭代粒子根据两个记忆调整运动方向一个是它自己历史最优 pbest_i另一个是整个群体当前的全局最优 gbest。常见更新公式为v_i ω·v_i c1·r1·(pbest_i − x_i) c2·r2·(gbest − x_i) x_i x_i v_i公式里r1 和 r2 是 [0,1] 均匀分布的随机数为搜索引入随机性。ω 是惯性权重控制上一时刻速度对当前速度的影响ω 偏大时粒子惯性大、更容易飞出当前区域做广域探测ω 偏小时粒子更愿意围绕局部位置精细移动。c1、c2 分别是自我认知系数和社会认知系数c1 让粒子往自己走过的良好像靠近c2 让粒子向群体最优靠拢。两个系数配平就得到“既有独立探索、又不过度分散”的搜索轨迹。把这两行公式用到神经网络上逻辑非常直接神经网络优化目标是非凸、含噪声且没有解析梯度的PSO 不要求目标函数可导它只需要你给每个候选解算一个适应度值。一个粒子就是一组完整网络权重一次迭代同时评估几十组权重天然带上了并行采样的效果。2.2 从“位置”到“一组神经网络权重”的映射把粒子映射成网络权重需要定义一个固定不变的顺序。常见做法是先把输入层到隐藏层的权重矩阵按行展开再接隐藏层偏置然后接隐藏层到输出层的权重和输出层偏置全部拼成一个一维浮点数组。计算适应度时再按原来的形状还原。维度计算公式是dim n_input × n_hidden n_hidden n_hidden × n_output n_output举个例子输入特征有 5 个隐藏层 10 个神经元输出 1 个那么粒子维度为 5×10 10 10×1 1 71。一个 71 维的优化问题用 2040 个粒子足够了不需要像其他元启发式演示那样开 5001000 个粒子。粒子越多单次迭代的前向计算成本越高而股票数据样本量本身有限种群过大的收益会递减。权重的取值边界建议初始化在 [-1, 1] 或 [-0.5, 0.5]不要直接给 [-10, 10]。过大的初值会让隐藏层激活值落入饱和区前向传播输出趋于一致粒子群在适应度上几乎无法分辨彼此的差异收敛速度会明显变慢。2.3 适应度函数股价预测中的常用选择与复合改进粒子群优化算法只认适应度。适应度函数的选取直接决定粒子在搜索时会优先寻找哪种类型的解。股票价格预测里最省事的适应度是验证集上的均方误差但它有一个容易被忽视的缺陷网络很容易学到“预测值约等于当前价格”的平庸解此时均方误差可能仍然很小但方向完全没被抓住。常见做法是构造复合适应度把回归误差和方向准确率放在同一个式子里。方向准确率 DA 是预测收益符号与真实收益符号一致的样本比例。一种可用的复合指标是fitness RMSE × (1 − DA)RMSE 越大惩罚越大DA 越高折扣越多。这个式子不额外引入权重超参比较适合快速基准测试。如果希望更精细可以写成fitness RMSE × sqrt(1 − DA) λ × mean(theta²)后一项是权重参数的 L2 惩罚λ 取 0.00010.001 即可。它防止粒子为了拟合个别极端样本而把权重推到离谱的数值。股票数据噪声重过拟合通常不是发生在训练精度上而是发生在权重的陡峭程度上。评估口径计算公式在股票预测中的作用MSEmean((y − ŷ)²)连续值回归对偏差大的点惩罚高RMSEsqrt(MSE)与 y 同量纲便于对比模型间差异MAPEmean(|(y − ŷ)/y|)价格幅度差异大时数据分母不稳少用DAmean(sign(y) sign(ŷ))判断模型有没有抓住涨跌方向复合适应度RMSE × (1 − DA)幅度和方向兼顾PSO 常用提示适应度函数是粒子之间的唯一比较标尺也是神经网络训练方向的隐性定义。设计适应度时把“误差大小”和“方向对不对”拆开看比只追低 MSE 更接近股票预测的真实需求。2.4 位置边界与速度限幅的初始化PSO 在高维空间里最常见的失控现象是速度爆炸某个维度的速度不断累加粒子直接跑到十万八千里外。初始化时就把速度上限 vmax 固定住一般取位置边界宽度的 10%20%。比如位置范围 [-1,1]跨度是 2vmax 取 0.20.4。位置边界可以全局统一用 [lb, ub]所有权重共享一个边界。这不影响隐藏节点间的差异因为粒子会在迭代中自动把不同维度落到不同区间位置而不是靠预先给每个维度单独划范围。每次更新完位置后把超边界的维度直接拉回到边界也就是“吸收”策略。反射和随机重置策略在低维可视化里看着漂亮但在数百维权重空间中容易破坏粒子已经积累的搜索动量股票预测场景下我一般直接选吸收。3. 用粒子群优化算法训练 BP 神经网络的编码、前向计算与迭代流程3.1 网络结构与粒子群的初始化骨架这一节给出一个不依赖深度学习框架的 NumPy 实现。网络选单隐藏层前馈神经网络隐藏层用 tanh 激活输出层用线性激活因为预测目标是收益等连续实数输出层不需要压缩到 [0,1]。网络结构由输入特征数、隐藏节点数和输出节点数决定。import numpy as np class PsoBP: def __init__(self, n_input, n_hidden, n_output1, pop_size20, lb-1.0, ub1.0): self.n_input n_input self.n_hidden n_hidden self.n_output n_output self.shapes [ (n_input, n_hidden), # 输入到隐藏层权重 (n_hidden,), # 隐藏层偏置 (n_hidden, n_output), # 隐藏层到输出权重 (n_output,), # 输出层偏置 ] self.dim (n_input * n_hidden n_hidden n_hidden * n_output n_output) self.pop_size pop_size self.lb lb self.ub ub span ub - lb self.V np.random.uniform(-0.2 * span, 0.2 * span, (pop_size, self.dim)) self.X np.random.uniform(lb, ub, (pop_size, self.dim)) self.pbest self.X.copy() self.pbest_score np.full(pop_size, np.inf) self.gbest None self.gbest_score np.inf初始化时把每个粒子都放到 [-1,1] 的置信区间内速度初始化为边界跨度正负 20% 以内的随机值。pbest_score 全部初始化为无穷大这样第一次评估后任意有限适应度都能替换掉初始值。gbest 先置为 None等第一次迭代结束后再赋值。3.2 前向传播与适应度计算要计算粒子的适应度必须先把一维参数还原成权重矩阵和偏置向量再做一次完整前向传播。为了避免维度索引出错可以维护一个游标依次切开每段参数。def unpack(self, theta): idx 0 w1_size self.n_input * self.n_hidden w1 theta[idx:idx w1_size].reshape(self.n_input, self.n_hidden) idx w1_size b1 theta[idx:idx self.n_hidden] idx self.n_hidden w2_size self.n_hidden * self.n_output w2 theta[idx:idx w2_size].reshape(self.n_hidden, self.n_output) idx w2_size b2 theta[idx:idx self.n_output] return w1, b1, w2, b2 def predict(self, theta, x): w1, b1, w2, b2 self.unpack(theta) z1 np.tanh(x w1 b1) y z1 w2 b2 return y.ravel()predict 里使用矩阵乘法x 的 shape 是 (样本数, 输入特征数)z1 经过 tanh 后是 (样本数, 隐藏节点数)。第二层是线性加权不套激活函数。这里之所以不用 Sigmoid 而用 tanh是因为 tanh 的输出以 0 为中心不会让隐藏层激活值全部偏向正值在收益类数据的回归任务里更稳。适应度函数在训练集和验证集上分开处理。粒子之间的排序用训练误差比较验证集只用来记录分数避免粒子群直接去拟合验证集。def fitness(self, theta, x_tr, y_tr, x_va, y_va): y_pred self.predict(theta, x_tr) rmse float(np.sqrt(np.mean((y_pred - y_tr) ** 2))) y_pred_va self.predict(theta, x_va) da float(np.mean(np.sign(y_pred_va) np.sign(y_va))) return rmse * (1.0 - da) 1e-4 * float(np.mean(theta ** 2))fitness 返回一个标量越小越好。RMSE 度量误差DA 度量方向L2 惩罚项防止个别权重过大。需要注意股票收益的符号分布通常是不均衡的DA 接近 0.5 并不一定是失败重点是与基线模型对比后的相对提升。3.3 含早停和线性递减惯性权重的 PSO 训练主循环主循环按“评估-更新 pbest/gbest-更新速度-更新位置-处理边界”的顺序执行。惯性权重从 0.9 线性下降到 0.4前中期偏向全局探索后期偏向局部精调。def fit(self, x_tr, y_tr, x_va, y_va, max_iter200, c11.6, c21.8, patience10): prev_best self.gbest_score no_improve 0 for t in range(max_iter): scores np.array([ self.fitness(p, x_tr, y_tr, x_va, y_va) for p in self.X ]) better scores self.pbest_score self.pbest[better] self.X[better] self.pbest_score[better] scores[better] idx_g np.argmin(scores) if scores[idx_g] self.gbest_score: self.gbest_score scores[idx_g] self.gbest self.X[idx_g].copy() no_improve 0 else: no_improve 1 w 0.9 - 0.5 * (t / max_iter) r1 np.random.rand(self.pop_size, self.dim) r2 np.random.rand(self.pop_size, self.dim) self.V (w * self.V c1 * r1 * (self.pbest - self.X) c2 * r2 * (self.gbest - self.X)) span self.ub - self.lb self.V np.clip(self.V, -0.2 * span, 0.2 * span) self.X np.clip(self.X self.V, self.lb, self.ub) if no_improve patience: break return self.gbest, self.gbest_score三个关键参数是 c1、c2、patience。c1 取 1.6、c2 取 1.8 时粒子不会过早完全倒向全局最优个体探索还保留一定权重这对高维神经网络的参数空间是必要的。patience 表示连续多少轮 gbest 不动就提前停止默认 10 轮即可股票预测中代间波动大设太小的 patience 会误杀还在探索前期的算法。3.4 为什么股价预测场景里 PSO 迭代次数不宜过大粒子群优化算法没有反向传播那样的天然加速机制它每一代都要做完整的群体前向计算。假设 30 个粒子、5 个特征、8 个隐藏节点训练样本 600 条每次迭代就是 30 次前向传播200 代就是 6000 次每次还包含 600 条样本的矩阵乘法。成本虽不夸张但一旦叠加滚动回测的多个窗口时间会快速膨胀。常见的抵消方案有两个。一是限制迭代次数在 100200 代之间靠粒子数取得搜索多样性。二是做“PSO 预热 梯度精调”先用 PSO 跑 100 代把得到的 gbest 作为神经网络的初始权重再交给反向传播训练 50 个 epoch。这样 PSO 负责跳出局部盆地BP 负责在已经比较优良的区域内拿到更精确的权重解两边各取所长。4. 股票价格预测的数据特征、时序划分与评估指标4.1 用对数收益而不是绝对价格做训练目标股票收盘价是非平稳序列有长期趋势和明显自相关。直接把绝对价格作为神经网络预测目标模型很容易学到“预测值接近历史均值”的惰性解。更基础的隐患是价格序列的方差随时间增大不同训练窗口的统计口径完全不同模型迁移性会很差。常见做法是把价格转换成对数收益r_t ln(P_t / P_{t−1})预测目标改成下一个交易日的对数收益 r_{t1}而输入特征用过去若干天的收益与统计量。这样能消除价格中大部分非平稳因素也方便通过累乘还原未来价格P_{t1} P_t × exp(r̂)。但还原只能在单步预测有效多步预测必须把每一步预测结果作为输入重新代入特征误差会随步数指数累积。所以股票价格预测任务里评估指标以单步收益预测为准多步预测只做压力观察。4.2 收益序列的特征构造这里给一个最精简的特征构造函数大约含 5 个滞后收益、窗口均值和窗口标准差共 7 个特征。如果你想加入 RSI、MACD 等指标可以在这个函数的输出后面拼接但建议先用这套基准特征验证 PSO 的可行性。def build_features(close, window5): ret np.diff(np.log(close)) X, y [], [] for i in range(window, len(ret)): x_i list(ret[i - window:i]) x_i.append(np.mean(ret[i - window:i])) x_i.append(np.std(ret[i - window:i])) X.append(x_i) y.append(ret[i]) return np.array(X), np.array(y)x_i 的前 5 个数是过去 5 个交易日的收益率即滞后特征最后两个是窗口内收益的均值和波动率。y 是第 i 天的收益也就是根据前 window 天预测下一个交易日。特征里不包含当天的收益这是防止前视偏差的最小要求。标准化必须用训练窗口的均值方差mu X_train.mean(axis0) std X_train.std(axis0) 1e-8 X_train (X_train - mu) / std X_test (X_test - mu) / std如果事先把全部数据一起算均值方差再切训练测试测试集信息等于已经进入训练特征。这种错误很隐蔽一两次滚动重训看不出问题但到了连续窗口就会导致指标虚高。标准化的正确做法是永远只在训练窗口内估计 mu 和 std测试窗口只消费这两个数值。4.3 Walk-Forward 滚动划分避免随机打乱股票数据是时间序列随机打乱样本等于让模型同时看到未来和过去这会让仿针验证失去意义。最接近实盘的划分是滚动重训每次用最近一段固定长度的数据训练预测下一小段然后把窗口向后滑动。def walk_forward_split(length, train_len250, step20, horizon1): folds [] start 0 while start train_len horizon length: train_end start train_len test_end train_end horizon folds.append((start, train_end)) start step return foldstrain_len 取 250 约等于一年交易日step 取 20 表示每重训一次就向前滚动一个月horizon 是预测的交易日个数只取 1 最稳妥。每个 fold 都会产生一个测试预测值最后把这些预测值拼接起来构成覆盖整段测试期的连续预测序列。划分方式是否推荐主要原因随机 90%/10% 打乱不推荐未来样本泄漏指标失真按时间顺序单次划分可以适合快速验证代码正确性Walk-Forward 滚动推荐贴近实盘重训节奏可控时间序列交叉验证进阶需要用 Blocked 方法避免重叠窗口4.4 评估指标与方向准确率的基线判断测试集上的核心指标有三个RMSE、DA、以及方向提升率。DA 的计算代码很短def direction_accuracy(y_true, y_pred): pred_sign np.sign(y_pred) true_sign np.sign(y_true) return float(np.mean(pred_sign true_sign))但 DA 不能单独看数值。如果测试期内上涨样本占比是 62%那么模型 DA 只有 56% 时其实跑输了“永远看涨”的朴素策略。此时更合理的基准是方向提升率lift DA − max(上涨样本占比, 下跌样本占比)lift 为正值才说明模型提供了超越样本概率的方向信息。RMSE 则用来检验预测值幅度是否接近真实收益DA 再高如果 RMSE 明显大于 baseline说明模型只是靠运气碰方向收益预测整体偏差过大。这四个指标一起看才能判断一个 PSO 优化出来的神经网络是不是真的有可用的预测结构。5. 粒子群优化神经网络的种群规模、粒子维度与惯性权重调节5.1 粒子维度与网络结构如何折中粒子维度由输入特征数、隐藏节点数和输出节点数共同决定。股票预测任务里隐藏节点数并不是越多越好。特征维度低、样本量小隐藏节点一旦超过输入特征数的两倍网络就有能力记住单个样本的噪声PSO 在搜索中会很容易找到那种“在训练集上拟合到小数点后五位”的尖刺解。此时验证集 DA 反而会掉回 50% 附近说明模型已经过拟合。输入特征数隐藏节点数粒子维度建议种群规模78732030101214520401516289306020204614080维度超过 300 后速度更新会产生更多维度间的交互震荡这时粒子的种群规模可以适当提高但 vmax 要下调到位置边界跨度的 10% 左右。超过这个范围高维空间里很容易出现某个粒子在某几个维度大幅震荡但整体适应度几乎不动的情况。5.2 种群规模与迭代次数的最优组合在 PSO 中总计算量约等于种群规模 × 迭代次数 × 前向计算样本数。总计算量固定的前提下增加种群规模比增加迭代次数更容易带来多样性收益。股票数据噪声高目标函数表面存在大量陡峭的尖峰单一粒子沿自己的轨迹长期搜索容易卡在尖峰附近更多粒子则能覆盖更多不同的局部盆地。推荐的起步组合是pop_size 取 20max_iter 取 150。跑通后如果验证集 DA 偏低先加 pop_size 到 30 或 40而不是直接把 max_iter 加到 500。当继续加 pop_size 后 gbest 分数没有明显变化说明数据本身的预测上限到了再调迭代次数意义也不大。还可以做一次群体收敛观测如果最后 20 代里所有粒子几乎聚集在同一个位置附近说明搜索已经收敛如果 gbest 还在频繁变换说明适应度景观过于振荡此时要检查特征构造而不是继续加大迭代。5.3 惯性权重、加速系数与速度限幅的协同调节线性递减惯性权重是最常用的设置ω(t) 0.9 − 0.5 × (t / max_iter)前期 ω ≈ 0.9 时粒子大范围游走后期降到 0.4 时则细致收敛。如果你发现 gbest 在 30 代内就早早停滞说明下降速度太快可以换成恒定的 0.7如果 100 代后还在震荡说明 ω 后段太大了需要调高下降斜率。c1 和 c2 不必机械地取 2.0。股价预测里我一般用 c11.6、c21.8个体认知略低于社会认知粒子会更快靠拢全局最优但又不至于在第一轮就被 gbest 拽走。随机数 r1、r2 必须和粒子维度同形状也就是每个维度都抽一次随机数而不是整个粒子共用一个标量。共用标量会让粒子朝单一方向整齐移动多样性损失非常大。速度限幅的处理方式对高维权重空间有直接影响。调整代码可以这样写span ub - lb vmax 0.15 * span V np.clip(V, -vmax, vmax)vmax 大粒子速度快能越过大尺度盆地但权重在迭代后期也容易来回震荡vmax 小收敛稳定但容易卡在局部。股票数据中0.15×span 是个合理的起步值。如果你的适应度曲线是锯齿状就把 vmax 降到 0.1×span如果 gbest 长期不动再把 vmax 提升到 0.2×span。5.4 早停判定靠 gbest 分数变化而不是固定迭代次数PSO 在股票预测里最大的风险不是收敛太慢而是收敛太快但收敛到错误区域。早停判定要同时看 gbest 分数和验证集分数。验证分数不再下降时即使 gbest 还在训练集上缓慢下降也应停止迭代因为这个阶段大概率开始了过拟合。我在滚动回测中通常会同时记录 gbest_score 曲线和验证集 DA 曲线。g醒est_score 连续 10 代下降幅度小于初始值的 2%就提前退出。验证集 DA 在最近 10 代内没有刷新最高值也会触发提前退出。两个条件任一触发都停止这比硬性跑满迭代次数更可靠。6. 滚动回归里验证粒子群优化神经网络预测结果的实战技巧6.1 训练参数通过命令行传入固定的随机种子滚动回测中每个窗口都会重跑 PSO。如果每个窗口不固定随机种子粒子群每次初始位置不同最终预测结果会被随机性干扰你很难判断提升是来自算法改动还是来自采样运气。常见做法是把随机种子写进命令参数每个窗口用相同种子运行基线和新模型。python pso_bp.py --train 250 --step 20 --particles 30 --iterations 150 --seed 2024同一组参数下重跑结果要能保持一致。滚动窗口多时可以用 fold 序号和 seed 组合成派生种子保证窗口之间结果可复现又能避免完全相同的粒子初始分布。6.2 用“零收益预测”作为必要基准股票收益预测的基线不是随机猜测而是“下一日收益为 0”。如果 PSO 网络在这个基线上没有正向提升说明模型只是引入了噪声。实际操作时我把零收益预测的 RMSE 和 DA 打印在结果第一行然后再打印 PSO-BP 的结果。DA 的提升需要按 4.4 节的上涨样本占比计算 lift而不是直接看 DA 是否大于 50%。对比至少要有这三个模型随机初始化 BP 梯度训练、PSO 直接训练权重、PSO 预热后 BP 精调。随机初始化 BP 代表局部优化的效果PSO 代表无梯度全局搜索的效果混合训练代表两者结合的效果。三者只有同一数据划分和同一评估代码下才有比较价值。6.3 第一步检查标准化是否串窗滚动回测中暴露出的很多“指标暴增”其实不是模型强而是标准化泄漏。跑完第一个窗口后我会立即检查测试集预测分布和训练集预测分布是否相似。如果测试集预测方差只有训练集的三分之一多半是测试集被训练集的均值方差压到了过于狭窄的范围。再检查一下 mu 和 std 是否在循环里每次重算以及是否把测试数据直接并入了全量标准化。对每个滚动窗口记录下验证集 DA 序列。这个序列如果前后稳定说明模型在不同时间段都有类似表现如果某几个窗口特别高或特别低优先检查该区间是否出现了样本异常再考虑粒子群参数调整。股票序列本身非平稳不同窗口指标波动是正常的你要找的是“大多数窗口都不明显低于基线”而不是偶然的几个高 DA 窗口。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表