PSO-LSTM股票调整收盘价预测:Python源码实现与调参实战
简介这套基于PSO-LSTM神经网络的股票调整收盘价预测源码专为需要完成期末大作业或课程设计的Python学习者准备适合有一定神经网络基础但希望快速搭建完整项目的新手。资源包含10个文件以7个csv数据文件为主覆盖DJI、AAPL、000001.SS等常见股票与指数行情同时提供1个核心预测py脚本、1份说明文档与1个备注文件整体压缩包仅490KB部署简单、便于直接套用。已有171人浏览学习代码注释详尽可实现基于改进粒子群算法优化长短期记忆网络的单维单步MSE收盘价预测目录结构清晰运行即可看到预测效果。该源码功能完整、界面简洁既可作为课程设计的完整方案也能帮助读者深入理解PSO与LSTM结合的原理与实战流程。1. 拿到这份“基于PSO-LSTM神经网络的股票调整收盘价预测Python源码.zip”先别急着解压我最初看到这个标题时心里想的是又是一个把算法名堆一起的“炼丹”项目。但真正把压缩包里的代码跑通后发现它解决了一个很实际的痛点——LSTM预测股票时序最烦的不是网络结构而是调参。hidden units设多少学习率给多少时间步长选多长网格搜索跑一次要几天随机搜索又全凭运气。这份源码用粒子群算法PSO把LSTM的超参数搜索自动化让模型自己找一组相对最优的参数组合。文章面向两类人一是刚入门量化、想用深度学习预测股价的Python开发者二是已经在用LSTM但被调参折磨的从业者。我不打算复述源码里每一行注释而是按“原理—实现—调参—避坑”的顺序把这条技术路线讲透。你可以照着思路自己重写也能直接用源码改自己的数据。2. 原理与选型为什么是PSOLSTM而不是网格搜索或贝叶斯优化2.1 LSTM在股票时序预测里的定位它不是魔法是带记忆的序列建模器股票调整收盘价是一串按时间排序的数值前后有依赖关系。普通的前馈神经网络FNN假设输入之间独立拿它预测股价相当于只看了当天的几个指标完全忽略昨天、前天发生了什么。LSTM长短期记忆网络属于循环神经网络RNN它的核心是门控机制能选择性地记住长期信息、忘掉短期噪声。对于“今天的价格受过去20个交易日趋势影响”这类逻辑LSTM天然比前馈网络更契合。但LSTM的门控参数是训练出来的而“训练几个epoch、每层多少个神经元、学习率多大”这些超参数不会在网络反向传播中自动更新。你只能提前定好或者用外部搜索算法去找。这正是PSO的用武之地。2.2 PSO为什么适合做LSTM超参数搜索群体智能的性价比之选PSO粒子群优化模拟鸟群觅食每个粒子代表一组候选超参数在搜索空间里飞方向由“自己历史最优”和“群体历史最优”共同决定。和网格搜索相比网格搜索是穷举参数组合多了就是维度爆炸和贝叶斯优化相比PSO实现简单不需要对目标函数做概率建模尤其适合目标函数是“训练一次LSTM”这种黑匣子的场景。一个粒子就是一组LSTM超参数比如[hidden_units, learning_rate, time_steps, batch_size]。适应度函数通常是验证集上的均方误差MSE或平均绝对误差MAE。每次迭代粒子更新速度与位置相当于告诉LSTM“下一组试试这个配置”训练完再把误差返回给PSO。整个过程对你完全透明——你不用手动改参数PSO替你试错。2.3 调整收盘价是什么为什么预测它比预测原始收盘价更有意义股票收盘价会受拆股、送股、分红等公司行为影响这些事件会导致价格跳空但并没有真实反映市场价值变动。调整收盘价Adjusted Close把这些事件的影响剔除让价格序列在时间上连续可比较。多数免费数据源如yfinance默认返回调整收盘价但很多新手没注意直接拿原始收盘价建模结果发现模型完全学不到规律——其实是数据被“人为突变”污染了。因此拿到源码后第一件事确认它的数据加载逻辑是否用的是adclose这一列。如果用的是原始close建议你改成adjusted close。不要小看这一步它决定你的模型学的是市场规律还是除权除息规则。3. 代码结构与核心实现从解压zip到跑通第一个PSO-LSTM最小例程3.1 解压源码包先看清目录结构再动手标题里是.zip压缩包。Windows下直接右键解压macOS/Linux用unzip命令。注意如果源码包是从Windows传过来的可能有中文文件名乱码Linux下用unzip -O gbk处理。解压后典型结构包括pso_lstm_stock/ ├── data/ # 存放原始CSV数据 ├── src/ # 核心代码 │ ├── pso.py # PSO算法实现 │ ├── lstm_model.py # LSTM网络定义与训练 │ ├── data_loader.py # 数据加载与预处理 │ └── main.py # 入口串联全流程 ├── config.yaml # 全局配置 └── requirements.txt # 依赖清单先别急着看模型先看requirements.txt装依赖。常见依赖有numpy、pandas、matplotlib、scikit-learn、torch或tensorflow。我写这篇文章时用的是PyTorch版本兼容性更好调试也方便。3.2 核心代码一PSO粒子与适应度函数设计以下是精简后的核心逻辑保留了源码里的关键思路。完整代码比这长但骨架一致import numpy as np import torch from lstm_model import build_lstm, train_lstm, evaluate def fitness_function(params, X_train, y_train, X_val, y_val): 给定一组超参数训练一个LSTM并返回验证集MSE越小越好 hidden_units int(params[0]) learning_rate float(params[1]) time_steps int(params[2]) batch_size int(params[3]) # 构建模型 model build_lstm(input_sizeX_train.shape[-1], hidden_unitshidden_units, num_layers1) # 训练内部包含早停逻辑防止过拟合 train_lstm(model, X_train, y_train, lrlearning_rate, batch_sizebatch_size, epochs50) # 评估 mse evaluate(model, X_val, y_val) return mse class Particle: def __init__(self, dim, bounds): self.position np.random.uniform( bounds[:, 0], bounds[:, 1], sizedim) self.velocity np.random.uniform(-1, 1, sizedim) self.best_position self.position.copy() self.best_score float(inf)逻辑说明fitness_function接收一组粒子位置即LSTM参数训练一个短周期的LSTM返回验证集MSE。PSO每次迭代都会调用这个函数所以计算开销主要花在LSTM训练上。为了加快速度源码通常会把epoch设小30-50并用早停。参数bounds定义搜索上下界比如hidden_units在32到128之间learning_rate在1e-4到1e-2之间对数尺度更好。参数说明PSO的粒子维度dim就是你要优化的超参数个数。这里选了4个你也可以加dropout、num_layers。注意PSO对离散参数如hidden_units需要取整对连续参数如learning_rate建议做对数变换否则大范围搜索时学习率这种小量级参数会被淹没。3.3 核心代码二PSO主循环与速度更新PSO的核心是速度和位置更新公式。源码里最常见的是标准PSO不包含惯性权重的衰减但我建议加上线性递减惯性权重收敛更稳。def pso_optimize(fitness_func, dim, bounds, n_particles10, max_iter15): particles [Particle(dim, bounds) for _ in range(n_particles)] global_best_score float(inf) global_best_position None for iteration in range(max_iter): # 惯性权重线性递减从0.9降到0.4 w 0.9 - 0.5 * iteration / max_iter c1 2.0 # 个体学习因子 c2 2.0 # 社会学习因子 for p in particles: score fitness_func(p.position) # 更新个体最优 if score p.best_score: p.best_score score p.best_position p.position.copy() # 更新全局最优 if score global_best_score: global_best_score score global_best_position p.position.copy() # 更新速度和位置 for p in particles: r1, r2 np.random.rand(dim), np.random.rand(dim) cognitive c1 * r1 * (p.best_position - p.position) social c2 * r2 * (global_best_position - p.position) p.velocity w * p.velocity cognitive social # 速度限幅防止飞出搜索空间 p.velocity np.clip(p.velocity, -1, 1) p.position p.position p.velocity # 位置边界处理越界则拉回边界 p.position np.clip(p.position, bounds[:, 0], bounds[:, 1]) return global_best_position, global_best_score逻辑说明每次迭代先让所有粒子计算适应度然后更新个体历史最优和群体全局最优最后按标准速度公式移动粒子。这里有一个容易忽略的点位置更新后必须检查边界。如果不做边界处理粒子会飞到负数hidden_units直接让LSTM崩溃。参数说明n_particles10和max_iter15是源码的默认值加起来最多训练150个LSTM模型。如果你的数据量大一个模型要跑几十秒这个成本不低。建议先在小数据集上测试确认流程没问题再放大。w从0.9线性降到0.4符合“前期探索、后期收敛”的直觉。c1和c2都设为2.0是经典取值但如果你发现粒子在局部震荡可以把c1调大、c2调小增强“个体认知”分量。3.4 核心代码三数据预处理与LSTM输入格式股票预测里最常见的翻车原因就是数据预处理不对导致信息泄漏。以下是我从源码中提炼出的标准流程import pandas as pd from sklearn.preprocessing import MinMaxScaler def load_and_preprocess(csv_path, time_steps20): df pd.read_csv(csv_path) # 使用调整收盘价 data df[adj_close].values.reshape(-1, 1) # 归一化必须只用训练集拟合scaler防止未来数据泄漏 scaler MinMaxScaler(feature_range(0, 1)) data_scaled scaler.fit_transform(data) # 这里先演示实际应用要拆开 # 构造时间步样本 X, y [], [] for i in range(time_steps, len(data_scaled)): X.append(data_scaled[i - time_steps:i, 0]) y.append(data_scaled[i, 0]) X np.array(X).reshape(-1, time_steps, 1) y np.array(y).reshape(-1, 1) # 按时间顺序切分前80%训练后20%验证 split int(len(X) * 0.8) X_train, X_val X[:split], X[split:] y_train, y_val y[:split], y[split:] return X_train, X_val, y_train, y_val, scaler逻辑说明这是最基础的单变量时间步构造方式。time_steps决定了模型看多长的历史窗口来预测下一天。源码里的默认值是20代表20个交易日约一个月。如果你预测的是日线这个值算合理如果是分钟线可能需要更长的窗口。参数说明特别注意MinMaxScaler的使用。上面代码中fit_transform对整个序列做了归一化这在正规流程里是错的——因为验证集的信息被训练集“偷看”了会导致评估结果偏乐观。正确做法是先对训练集fit再分别transform训练集和验证集。这一点我会在第5章避坑中重点讲。这里保留这种写法是为了和源码里常见的简化流程对齐实际使用你要改掉。3.5 LSTM模型构建与训练import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size1, hidden_units64): super().__init__() self.lstm nn.LSTM(input_sizeinput_size, hidden_sizehidden_units, num_layers1, batch_firstTrue) self.fc nn.Linear(hidden_units, 1) def forward(self, x): # x shape: (batch, time_steps, input_size) out, _ self.lstm(x) # 取最后一个时间步的输出 last_out out[:, -1, :] return self.fc(last_out)逻辑说明这是最常见的LSTM回归结构LSTM层后接一个全连接层输出单维预测值。batch_firstTrue让输入维度变成(batch, time_steps, features)更直观。训练时损失函数用MSE优化器用Adam。一个细节是LSTM的初始隐含状态不显式指定PyTorch默认全零对短期预测影响不大但如果你做长序列滚动预测初始状态需要自己维护。训练函数里通常包含早停例如连续5个epoch验证集误差不下降就停止。源码里有一个patience参数我一般设成5。如果你发现模型训练很快结束但结果不好可以调大patience到10。4. 参数调优与结果解读PSO和LSTM的关键参数到底怎么设4.1 PSO超参数对照表种群数、迭代次数、惯性权重、学习因子PSO参数建议范围说明n_particles种群数5-20每个粒子一次LSTM训练。数据量大时种群数取小值5-8配合更多迭代次数max_iter迭代次数10-30和种群数乘积就是总训练次数。超过30次迭代收益递减因为后期粒子聚集w惯性权重0.4-0.9固定值也可线性递减更好。w大探索性强w小局部精调c1个体学习因子1.5-2.5c1大粒子更依赖自身经验适合多峰问题c2社会学习因子1.5-2.5c2大粒子更跟群体走收敛快但易早熟速度限制-1 到 1防止粒子一步飞越整个搜索空间导致位置频繁越界实际操作时我习惯先跑一次小规模PSOn_particles5, max_iter5看适应度能不能降到合理范围。如果能再调大。如果一开始就卡在某个不收敛的值多半是模型本身有问题不是PSO的锅。先把固定参数的LSTM跑通再尝试让PSO去搜。4.2 LSTM超参数搜索范围与离散化处理LSTM参数搜索范围处理方式经验说明hidden_units32-128离散取整数太小拟合不了序列模式太大容易过拟合且训练慢learning_rate1e-4 - 1e-2连续对数尺度实际使用建议转成对数值让PSO搜索即粒子位置是log(lr)time_steps5-60离散取整数与数据周期有关。日线数据20-30数据频率越高窗口越长batch_size16-64离散取2的幂过小梯度噪声大过大内存压力大。源码里默认32num_layers1-2离散整数太多层在训练集小的情况下容易忘dropout0-0.3连续正则化但会改变LSTM行为。如果训练集大可以不加一个容易忽略但很重要的点PSO内嵌的是“搜索过程”每评估一个粒子都要完整训练一次LSTM。如果你把epoch设成200那一次搜索要跑十几个小时。源码的聪明之处是限制每个粒子的训练epoch数常见30-50并用早停。这样搜出来的参数虽然不是完全最优但已经具备了合理性。拿到全局最优参数后再用大epoch去最终训练一次模型效果会更好。4.3 怎么判断PSO是否收敛看适应度曲线别只看最终MSEPSO跑完后源码通常会画出适应度随迭代次数的变化曲线包含每轮粒子的平均适应度和全局最优适应度。怎么看三条经验曲线平滑下降并最终平缓说明收敛正常。如果曲线像锯齿一样上下剧烈跳动说明粒子搜索步伐太大或者适应度函数本身方差太大LSTM训练随机性导致同一组参数多次训练误差不同。全局最优适应度几乎不再下降时说明群体已经聚集再跑迭代也没意义。这时候可以把当前最优参数拿出来重新训练一个长epoch的LSTM。如果平均适应度远高于全局最优说明粒子分布太散PSO还在探索如果两者非常接近说明粒子都聚在一起了可能出现局部最优。可以尝试调大w或c1让粒子飞出去看看。4.4 固定参数LSTM vs PSO-LSTM要对比别只跑一个验证PSO有没有用最直接的办法是拿同一份数据固定一组你“觉得合理”的参数比如hidden_units64, lr0.001, time_steps20训练LSTM记录验证集MSE。然后再跑PSO-LSTM把两组MSE和预测曲线画在一起。我见过不少项目PSO搜出来的结果比手动调参只好了2%-5%但需要付出几十倍的算力成本。如果数据量小手动调参完全够用如果数据量大、参数维度高PSO的收益才明显。这也提醒你不要神化这份源码。它把“调参”这件事自动化了但数据预处理、特征工程、模型评估的每一步仍然决定成败。5. 避坑与常见问题5个让我翻过车的细节5.1 现象归一化时用整个数据集fit导致验证集MSE极低但实盘预测一塌糊涂原因这是典型的数据泄漏。我用scaler.fit_transform(data)把训练集和验证集一起归一化了验证集的最大最小值影响了scaler的变换函数模型在验证集上看到的“分布”已经包含了未来的范围信息评估结果虚高。真实预测时新数据可能超出训练集范围模型立刻失灵。解决严格按时间序列切分只对训练集fit然后对训练集和验证集分别transform。测试集最后一段数据在整个调参流程中不能碰等模型完全确定后再使用。scaler MinMaxScaler() # 先fit训练集 X_train_flat X_train.reshape(-1, X_train.shape[-1]) scaler.fit(X_train_flat) # 再transform训练集、验证集 X_train_norm scaler.transform(X_train_flat).reshape(X_train.shape) X_val_flat X_val.reshape(-1, X_val.shape[-1]) X_val_norm scaler.transform(X_val_flat).reshape(X_val.shape)提示如果你的源码里直接用了fit_transform记得改成上面这种两段式。这是最值得动手改的一行代码。5.2 现象PSO运行时某些粒子位置导致LSTM维度不匹配直接报错原因粒子位置是连续的浮点数比如hidden_units54.7。如果直接传给nn.LSTM(hidden_size54.7)PyTorch会报TypeError。另一个常见问题是time_steps被更新成负数或0导致数据切片错误。解决在构造模型前对离散参数取整并夹紧到合法范围。写一个参数清洗函数def clean_params(params, bounds): p np.clip(params, bounds[:, 0], bounds[:, 1]) # 离散参数取整 p[0] int(round(p[0])) # hidden_units p[2] int(round(p[2])) # time_steps # 时间步至少为1 p[2] max(p[2], 1) # learning_rate转为正数且不小于1e-6 p[1] abs(p[1]) 1e-6 return p这个函数在PSO每次评估前调用能拦截80%的崩溃。血泪经验我最早没做这一步粒子飞到learning_rate-0.05导致损失变成NaN整个搜索进程报废。5.3 现象适应度函数在相同参数下多次运行MSE波动很大原因LSTM训练有随机性——权重初始化、PyTorch的随机数种子、GPU的并行计算顺序都会影响结果。同一组超参数两次训练可能得到不同的验证集MSE。PSO是基于适应度比较来更新粒子的如果适应度本身噪声大PSO可能把“运气好”当成“参数好”导致搜索方向错误。解决要么固定随机种子torch.manual_seed(42)要么对同一组参数重复训练2-3次取平均MSE。固定种子简单易行但会让搜索结果对初始随机值敏感重复训练更稳定但计算量翻倍。我一般选择固定种子并在PSO完毕后用不同种子再训练3次验证稳定性。5.4 现象zip解压后源码里引用的相对路径报错找不到data.csv原因这个坑和PSO无关但非常常见。源码运行时的当前目录是src/但数据文件在../data/如果代码里写的是pd.read_csv(data/xxx.csv)就会因路径不对报错。解决在main.py开头做路径处理用绝对路径或基于当前文件位置的相对路径import os, sys BASE_DIR os.path.dirname(os.path.abspath(__file__)) DATA_PATH os.path.join(BASE_DIR, .., data, stock.csv)注意如果源码包是下载的先检查数据文件是否存在。有些分享者为了减小包体积不附带数据文件而是给一个下载链接或生成脚本。5.5 现象PSO收敛太快最终参数和初始随机粒子差别不大原因PSO的w和速度限制设得太小粒子在初始位置附近“飞”不动。或者c1、c2设置失衡粒子要么跟着自己老路走要么被群体最优带着迅速聚集而群体最优本身可能是局部极值。解决把w初始值调到0.95速度限制放宽到-2到2。同时检查初始化粒子是否覆盖了整个搜索空间。如果初始化粒子时全部落在搜索范围的下半区就不容易发现上半区的最优解。可以在初始化时使用拉丁超立方抽样from scipy.stats import qmc sampler qmc.LatinHypercube(ddim) samples sampler.random(n_particles) # 映射到搜索空间 particles_init qmc.scale(samples, bounds[:, 0], bounds[:, 1])这个方法比纯随机均匀分布覆盖更均匀尤其在高维搜索时效果明显。6. 进阶用法与验证方法把PSO-LSTM用到真实交易之前你还差这三步首先真正上线前不要用单步预测改成滚动多步预测。源码里训练的是“用过去20天预测下一天”这只适合研究。实际交易中你要预测的是未来5天或10天的走势。实现方式是用训练好的模型预测下一天把这个预测值当作新输入追加到序列末尾然后继续预测再下一天直到达到步数。注意滚动预测会累积误差预测周期越长越不可靠。我一般把5天内的预测结果当作方向参考不追求绝对数值准确。其次评估指标不能只看MSE。股价预测里MSE低不一定意味着方向对。建议增加方向准确率DA模型预测明天较今天上涨或下跌的方向是否与实际一致。这个指标对交易更有参考价值。计算方式很简单def direction_accuracy(y_true, y_pred): true_up np.diff(y_true, axis0) 0 pred_up np.diff(y_pred, axis0) 0 return np.mean(true_up pred_up)最后模型保存与复现。PSO搜索到最优参数后要用这些参数重新初始化一个新模型并在完整训练集训练验证上训练更长epoch然后保存参数。源码如果用PyTorch保存整个模型比较方便torch.save({ model_state_dict: model.state_dict(), scaler: scaler, params: best_params }, pso_lstm_model.pth)加载时再恢复scaler和参数这样新数据来的时候你才能用和训练时完全一样的预处理方式做变换。忘了保存scaler是我犯过最可惜的错误——模型还在但不知道怎么把新数据变回0-1区间。说实话我最初对PSO-LSTM持怀疑态度认为它不过是把网格搜索换了个包装。但用了几次后发现当搜索空间从3-4个参数扩展到7-8个参数时手动调参已经完全不可行PSO确实能以更少的训练次数找到可用的参数组合。当然它也有局限如果LSTM本身结构不适合你的数据PSO再努力也没用。所以我的习惯是先不做任何调参用默认参数跑通一个基线确认时间序列本身可预测再上PSO。如果你正要开始尝试这个方向我会建议你也采取同样的流程。希望帮到你。本文还有配套的精品资源点击获取