ARTICLE DETAIL

资讯详情

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

粒子群优化神经网络做股票预测:从原理到代码避坑指南

粒子群优化神经网络做股票预测:从原理到代码避坑指南 简介这份资源面向金融数据分析、机器学习入门与量化投资方向的学习者聚焦用粒子群优化算法PSO改进神经网络以提升股票价格预测精度。内容围绕网络结构、学习率、动量因子与初始权重等关键参数的自动寻优展开并给出MSE、RMSE、MAE等误差评价思路适合作为课程设计、毕业设计或算法复现的参考方案。压缩包共12个文件约1.9MB包含4个docx训练过程与数据问题记录、3个csv股票历史数据、2个pdf相关论文、2个py实现脚本及1个说明txt覆盖数据读取、模型训练与结果分析环节。目前已有130人学习下载。读者可据此搭建基础神经网络预测流程理解PSO迭代更新粒子位置以搜索最优参数配置的完整逻辑并借助论文与训练文档排查数据与调参问题形成可复用的股票预测实验框架。1. 粒子群优化神经网络做股票预测为什么你的模型总在测试集上翻车股票价格预测这件事十个做量化的人里有八个试过神经网络但真正能跑出稳定结果的不到一个。问题往往不出在网络结构本身而是出在权重和阈值的初始化上——BP 神经网络对初始参数极度敏感随机初始化十次可能得到十个完全不同的预测曲线。粒子群优化算法PSO就是来解决这个问题的它把神经网络的权重和偏置当作粒子群搜索空间中的维度通过群体迭代找到一组更优的初始参数再交给神经网络做梯度下降精调。这套组合在股票价格预测场景下的核心价值是降低模型对初始值的依赖让预测结果更可复现。适合已经跑通过 BP 神经网络、但被“每次训练结果都不一样”折磨过的从业者也适合想入门群智能优化与深度学习结合方向的新手。下面从原理到代码到踩坑把这条链路完整走一遍。2. PSO 优化神经网络权重原理、选型与最小可跑通方案2.1 为什么是 PSO 而不是遗传算法或模拟退火粒子群优化算法最早由 Kennedy 和 Eberhart 在 1995 年提出核心思想模拟鸟群觅食行为。每个粒子代表一组候选解在这里就是神经网络的全部权重和偏置粒子根据自身历史最优位置和群体历史最优位置更新速度和位置。相比遗传算法PSO 没有交叉和变异操作参数更少、收敛更快相比模拟退火PSO 天然支持并行搜索适合权重维度较高的场景。在股票价格预测这个具体任务里一个典型的 BP 神经网络结构是“输入层 10 个节点 → 隐藏层 8 个节点 → 输出层 1 个节点”总权重和偏置数量为 10×8 8 8×1 1 97 个参数。PSO 的搜索空间就是 97 维粒子数一般取 20 到 50 就够用。维度不算高PSO 的收敛优势能体现出来。选 PSO 的另一个实际理由是代码量小。遗传算法要写选择、交叉、变异三套逻辑PSO 只需要速度更新和位置更新两个公式调试成本低很多。我一般会在项目初期用 PSO 快速验证“优化初始权重”这个思路是否有效再决定要不要换更复杂的策略。2.2 适应度函数怎么定MSE 还是方向准确率适应度函数决定了 PSO 往哪个方向搜索。最常见的做法是用均方误差MSE作为适应度粒子位置对应的权重代入神经网络后在训练集上计算预测值与真实值的 MSEMSE 越小适应度越高。但股票预测有个特殊之处涨跌方向的准确率往往比绝对价格误差更重要。如果你只优化 MSE模型可能预测出一个平滑但方向全错的曲线。我的做法是混合适应度def fitness_function(particle_position, X_train, y_train, input_size, hidden_size): 适应度函数MSE 方向惩罚 particle_position: 展平后的权重和偏置向量 # 将粒子位置还原为权重矩阵和偏置向量 W1 particle_position[:input_size*hidden_size].reshape(input_size, hidden_size) b1 particle_position[input_size*hidden_size : input_size*hidden_sizehidden_size] W2 particle_position[input_size*hidden_sizehidden_size : input_size*hidden_sizehidden_sizehidden_size].reshape(hidden_size, 1) b2 particle_position[-1:] # 前向传播 hidden np.tanh(np.dot(X_train, W1) b1) pred np.dot(hidden, W2) b2 # MSE 部分 mse np.mean((pred.flatten() - y_train) ** 2) # 方向准确率惩罚预测涨跌方向与真实方向不一致的比例 pred_direction np.sign(np.diff(pred.flatten())) true_direction np.sign(np.diff(y_train)) direction_error np.mean(pred_direction ! true_direction) # 混合适应度方向误差权重 0.3 return mse 0.3 * direction_error这段代码的关键在于direction_error的计算用np.diff取相邻两天的价格变化方向再比较预测方向和真实方向。权重 0.3 是我在几个数据集上试出来的经验值方向误差占比太高会导致 MSE 收敛太慢太低则方向纠正效果不明显。你可以根据自己数据里价格波动的剧烈程度调整这个系数波动大的股票可以适当提高到 0.4。注意适应度函数里的方向误差计算要求输入数据已经按时间顺序排列不能打乱。股票数据的时间顺序是硬约束。2.3 用 PSO 优化 BP 神经网络的最小可跑通代码下面是一个完整的、可以直接复制运行的 PSO 优化神经网络流程。数据用模拟生成的正弦波叠加噪声代替真实股票数据方便你验证流程正确性后再替换成自己的数据。import numpy as np # 1. 生成模拟股票数据 np.random.seed(42) t np.linspace(0, 100, 500) price 100 10 * np.sin(t * 0.3) 5 * np.sin(t * 0.7) np.random.normal(0, 1.5, 500) # 构造滑动窗口样本用前 10 天预测第 11 天 window 10 X, y [], [] for i in range(len(price) - window): X.append(price[i:iwindow]) y.append(price[iwindow]) X np.array(X) y np.array(y) # 归一化股票数据必须做 X_min, X_max X.min(), X.max() y_min, y_max y.min(), y.max() X (X - X_min) / (X_max - X_min) y (y - y_min) / (y_max - y_min) # 划分训练集和测试集按时间顺序前 80% 训练 split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] # 2. PSO 参数设置 input_size window # 10 hidden_size 8 output_size 1 dim input_size * hidden_size hidden_size hidden_size * output_size output_size # 97 n_particles 30 # 粒子数 max_iter 100 # 最大迭代次数 w 0.7 # 惯性权重 c1 1.5 # 个体学习因子 c2 1.5 # 群体学习因子 v_max 0.5 # 速度上限 # 初始化粒子位置和速度 positions np.random.uniform(-1, 1, (n_particles, dim)) velocities np.random.uniform(-0.1, 0.1, (n_particles, dim)) # 个体最优和群体最优 pbest positions.copy() pbest_fitness np.array([fitness_function(p, X_train, y_train, input_size, hidden_size) for p in positions]) gbest pbest[np.argmin(pbest_fitness)].copy() gbest_fitness np.min(pbest_fitness) # 3. PSO 主循环 for iteration in range(max_iter): for i in range(n_particles): # 速度更新 r1, r2 np.random.rand(dim), np.random.rand(dim) velocities[i] (w * velocities[i] c1 * r1 * (pbest[i] - positions[i]) c2 * r2 * (gbest - positions[i])) # 速度限制 velocities[i] np.clip(velocities[i], -v_max, v_max) # 位置更新 positions[i] positions[i] velocities[i] # 计算适应度 fit fitness_function(positions[i], X_train, y_train, input_size, hidden_size) # 更新个体最优 if fit pbest_fitness[i]: pbest[i] positions[i].copy() pbest_fitness[i] fit # 更新群体最优 if fit gbest_fitness: gbest positions[i].copy() gbest_fitness fit # 惯性权重线性递减前期探索后期收敛 w 0.9 - 0.5 * (iteration / max_iter) if iteration % 20 0: print(fIter {iteration}: gbest_fitness {gbest_fitness:.6f}) # 4. 用 PSO 最优解初始化神经网络并精调 # 还原权重 W1 gbest[:input_size*hidden_size].reshape(input_size, hidden_size) b1 gbest[input_size*hidden_size : input_size*hidden_sizehidden_size] W2 gbest[input_size*hidden_sizehidden_size : input_size*hidden_sizehidden_sizehidden_size].reshape(hidden_size, 1) b2 gbest[-1:] # 简单梯度下降精调学习率 0.01迭代 500 次 lr 0.01 for epoch in range(500): hidden np.tanh(np.dot(X_train, W1) b1) pred np.dot(hidden, W2) b2 error pred.flatten() - y_train # 反向传播 dW2 np.dot(hidden.T, error.reshape(-1, 1)) / len(X_train) db2 np.mean(error) dhidden np.dot(error.reshape(-1, 1), W2.T) * (1 - hidden ** 2) dW1 np.dot(X_train.T, dhidden) / len(X_train) db1 np.mean(dhidden, axis0) W2 - lr * dW2 b2 - lr * db2 W1 - lr * dW1 b1 - lr * db1 # 测试集预测 hidden_test np.tanh(np.dot(X_test, W1) b1) pred_test np.dot(hidden_test, W2) b2 # 反归一化 pred_test_real pred_test.flatten() * (y_max - y_min) y_min y_test_real y_test * (y_max - y_min) y_min # 计算测试集指标 mse_test np.mean((pred_test_real - y_test_real) ** 2) mae_test np.mean(np.abs(pred_test_real - y_test_real)) print(fTest MSE: {mse_test:.4f}, MAE: {mae_test:.4f})这段代码分四块数据构造、PSO 初始化、PSO 主循环、神经网络精调。几个关键参数说明一下。n_particles30是粒子数股票预测这种百维以下的问题 20 到 50 都合理粒子太少搜索不充分太多计算量线性增长。max_iter100是 PSO 迭代次数配合惯性权重从 0.9 线性降到 0.4前期让粒子大胆探索后期收敛到局部最优附近。v_max0.5限制单步移动距离防止粒子飞出有意义的搜索区域。精调阶段的学习率lr0.01和迭代次数500是保守设置因为 PSO 已经给了一个不错的起点梯度下降只需要微调。如果你发现精调后测试误差反而变大说明 PSO 找到的解已经在局部最优附近梯度下降把它推歪了这时候可以把学习率降到 0.001 或者直接跳过精调。3. 数据预处理与参数调优股票预测里最容易翻车的三个环节3.1 滑动窗口长度怎么选10 天还是 20 天滑动窗口长度决定了神经网络输入层的节点数也直接影响 PSO 的搜索维度。窗口太短模型看不到足够的趋势信息窗口太长维度爆炸PSO 收敛变慢而且引入太多噪声。我的经验是日频数据用 5 到 15 天周频数据用 4 到 8 周。A 股市场一个完整的短期波动周期大约在 8 到 12 个交易日所以窗口取 10 是一个比较稳的默认值。你可以用下面的代码快速测试不同窗口长度对测试集 MSE 的影响def evaluate_window(window_size, price_series): 测试不同滑动窗口长度下的测试集 MSE X, y [], [] for i in range(len(price_series) - window_size): X.append(price_series[i:iwindow_size]) y.append(price_series[iwindow_size]) X, y np.array(X), np.array(y) # 归一化 X (X - X.min()) / (X.max() - X.min()) y (y - y.min()) / (y.max() - y.min()) split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] # 这里调用前面的 PSO 优化流程返回测试集 MSE # 为简洁省略具体调用实际使用时把 PSO 主循环封装成函数 return mse_test # 测试窗口 5 到 20 for w in [5, 8, 10, 12, 15, 20]: mse evaluate_window(w, price) print(fWindow {w}: Test MSE {mse:.4f})跑完这个循环你会看到 MSE 先降后升的 U 型曲线。最低点对应的窗口长度就是适合你这份数据的值。注意不要用测试集来选窗口然后报告测试集指标正确做法是划出验证集来选窗口测试集只在最后用一次。3.2 PSO 参数调优惯性权重和学习因子的配合PSO 本身也有超参数最核心的是惯性权重w、个体学习因子c1、群体学习因子c2。这三个参数的配合决定了搜索行为。参数作用常用范围股票预测建议值w控制探索与开发平衡0.4 ~ 0.9线性递减 0.9→0.4c1粒子向自身历史最优靠拢1.0 ~ 2.01.5c2粒子向群体最优靠拢1.0 ~ 2.01.5v_max单步最大移动距离0.1 ~ 1.00.5n_particles粒子数量20 ~ 5030c1和c2的经典设置是都取 1.5 到 2.0 之间。如果c1远大于c2粒子倾向于在自身附近搜索群体协作弱收敛慢如果c2远大于c1粒子过早向群体最优聚集容易陷入局部最优。股票数据噪声大我一般让c1略大于c2比如c11.6, c21.4给粒子更多自主探索空间。惯性权重w用线性递减是最简单有效的策略。前 30% 迭代用 0.9 附近的值让粒子充分探索后 70% 逐步降到 0.4 附近让群体收敛。如果你发现适应度曲线震荡厉害可以把w的下降速度加快比如从 0.8 开始降到 0.3。3.3 归一化与反归一化别让量纲毁了你的模型股票价格动辄几十上百而神经网络激活函数tanh、sigmoid的有效区间在 -1 到 1 或 0 到 1 之间。不做归一化直接喂原始价格梯度会爆炸或者消失PSO 的适应度值也会大到无法比较。标准做法是 min-max 归一化到 [0, 1] 区间。但这里有个容易忽略的坑归一化的最大最小值必须用训练集计算然后应用到测试集。如果你用全量数据算 min/max测试集的信息就泄露到了训练过程中测试指标会虚高。# 正确做法只用训练集计算归一化参数 train_min, train_max X_train_raw.min(), X_train_raw.max() X_train_norm (X_train_raw - train_min) / (train_max - train_min) X_test_norm (X_test_raw - train_min) / (train_max - train_min) # 用训练集的 min/max # 反归一化预测结果 pred_real pred_norm * (train_max - train_min) train_min反归一化时也要用训练集的 min/max而不是测试集的。这个细节在实盘预测里尤其重要因为实盘时你根本不知道未来数据的 min/max。4. 避坑与排查PSO 优化神经网络做股票预测的五个血泪教训4.1 适应度曲线不下降粒子在原地打转现象PSO 迭代 100 次gbest_fitness几乎不变粒子位置更新幅度极小。原因最常见的是速度初始化太小。如果velocities初始化为np.random.uniform(-0.001, 0.001, ...)粒子第一步移动距离微乎其微后续速度更新也带不动。另一个原因是v_max设得太小比如 0.01粒子被限制在初始位置附近。解决速度初始化范围设为位置初始化范围的 10% 到 20%。位置在 [-1, 1] 时速度初始化用 [-0.2, 0.2]。v_max设为位置范围的 30% 到 50%即 0.3 到 0.5。如果还是不下降检查适应度函数是不是返回了常数——比如归一化后所有 y 值相同MSE 恒为零。4.2 测试集误差远大于训练集过拟合严重现象训练集 MSE 0.001测试集 MSE 0.05差了两个数量级。原因PSO 在训练集上过度优化把噪声也拟合进去了。粒子数太多比如 100或者迭代次数太多比如 500都会加剧这个问题。隐藏层节点数过多也是常见原因8 个节点可能就够了有人设 50 个。解决减少粒子数到 20 到 30减少迭代次数到 50 到 100。隐藏层节点数从 8 开始试不要超过输入层节点数。更有效的做法是在适应度函数里加正则项# L2 正则化lambda 取 0.001 到 0.01 l2_penalty 0.005 * np.sum(particle_position ** 2) return mse 0.3 * direction_error l2_penalty正则项让 PSO 偏好权重绝对值较小的解这些解通常泛化更好。4.3 预测曲线整体滞后于真实价格现象预测价格走势和真实价格形状相似但总是慢一两天拐点处尤其明显。原因滑动窗口构造样本时用前 10 天预测第 11 天模型学到的是“昨天的价格约等于今天的价格”这个平凡解。股票价格自相关性很强模型偷懒直接输出接近输入的值就能获得不低的适应度。解决改预测目标。不要预测绝对价格改预测价格变化量收益率# 用收益率代替价格作为预测目标 returns np.diff(price) / price[:-1] # 构造样本时 y 用收益率 y.append(returns[iwindow-1])预测收益率后模型必须真正学到方向信息才能降低误差滞后问题会明显改善。代价是收益率噪声更大PSO 需要更多迭代才能收敛。4.4 PSO 每次运行结果都不一样现象同一份数据同一套参数跑两次 PSO 得到的gbest_fitness差很多。原因PSO 是随机搜索算法粒子初始位置和速度都是随机的每次运行搜索路径不同。这是算法固有特性不是 bug。解决固定随机种子np.random.seed(42)可以让单次调试可复现。但实盘应用时不能依赖固定种子正确做法是跑 10 次 PSO取适应度最好的那次结果或者把 10 次的最优解做平均。这样虽然计算量乘以 10但结果稳定性大幅提升。4.5 用未来数据做归一化回测指标虚高现象回测夏普比率 3.0实盘一跑就亏。原因归一化时用了全量数据的 min/max测试集的信息泄露到了训练阶段。更隐蔽的是滑动窗口构造样本时如果先打乱数据再划分训练测试时间顺序就丢了模型实际上在用未来预测过去。解决永远按时间顺序划分训练集和测试集前 80% 训练后 20% 测试。归一化参数只用训练集计算。如果你要做交叉验证用时间序列交叉验证TimeSeriesSplit不要用普通的 KFold。5. 进阶技巧用 PSO 的群体最优轨迹做预测置信区间PSO 优化完神经网络后大多数人只取gbest这一组权重去预测得到一个点估计。但 PSO 的整个粒子群在迭代过程中探索了大量权重组合这些组合的预测结果分布本身就包含了不确定性信息。我习惯用粒子群最后一代的预测分布来构造置信区间比单纯的点估计实用得多。具体做法PSO 迭代结束后取最后一代所有粒子的位置分别代入神经网络得到 N 个预测值计算这些预测值的均值和标准差。均值作为最终预测标准差作为不确定性度量。# PSO 结束后用最后一代所有粒子做预测 final_predictions [] for i in range(n_particles): W1_i positions[i][:input_size*hidden_size].reshape(input_size, hidden_size) b1_i positions[i][input_size*hidden_size : input_size*hidden_sizehidden_size] W2_i positions[i][input_size*hidden_sizehidden_size : input_size*hidden_sizehidden_sizehidden_size].reshape(hidden_size, 1) b2_i positions[i][-1:] hidden_i np.tanh(np.dot(X_test, W1_i) b1_i) pred_i np.dot(hidden_i, W2_i) b2_i final_predictions.append(pred_i.flatten()) final_predictions np.array(final_predictions) # shape: (n_particles, n_test) # 均值和标准差 pred_mean final_predictions.mean(axis0) pred_std final_predictions.std(axis0) # 95% 置信区间 lower_bound pred_mean - 1.96 * pred_std upper_bound pred_mean 1.96 * pred_std # 反归一化 pred_mean_real pred_mean * (y_max - y_min) y_min lower_real lower_bound * (y_max - y_min) y_min upper_real upper_bound * (y_max - y_min) y_min这个置信区间有个直观解释如果粒子群收敛得好最后一代粒子位置集中pred_std就小说明模型对这次预测比较确定如果粒子群还在四处分散pred_std大说明模型不确定这时候的预测应该谨慎对待。我在实盘里会加一条规则当pred_std超过训练集预测标准差的 1.5 倍时这次预测信号直接丢弃。这个规则帮我过滤掉了不少高不确定性的假信号。当然阈值 1.5 是根据我的策略调出来的你可以用验证集数据自己标定。还有一个细节最后一代粒子的预测分布不一定服从正态分布用 1.96 倍标准差算 95% 区间是近似。如果你要更精确的区间直接取final_predictions的 2.5% 和 97.5% 分位数lower_bound np.percentile(final_predictions, 2.5, axis0) upper_bound np.percentile(final_predictions, 97.5, axis0)分位数方法不依赖分布假设代码也就多一行我后来都直接用这个。这套 PSO 优化神经网络加置信区间的方案我从两年前开始用在几个中低频策略上最大的体会是PSO 带来的提升不在于预测精度提高了多少而在于结果可复现了。以前跟同事讨论策略同样的代码跑出不同的曲线根本没法对齐认知。现在固定流程后大家看到的是同一组权重、同一条预测曲线讨论效率高了很多。另外别迷信 PSO 能找到全局最优它就是个更聪明的随机搜索接受这一点把精力放在数据质量和适应度函数设计上回报更大。希望帮到你。本文还有配套的精品资源点击获取
返回列表