PSO-LSTM优化股票调整收盘价预测:超参数搜索与源码实践
2026/9/24 0:19:00 网站建设 项目流程

简介:基于PSO-LSTM神经网络的股票调整收盘价预测Python源码,面向金融数据分析、深度学习方向的课程设计与期末大作业场景,适合需要完成预测类项目但缺乏完整代码参考的高校学生与初学者。资源利用粒子群算法优化LSTM超参数,实现对多只股票调整收盘价的单步预测,代码附有详细注释,结构清晰,下载后简单配置环境即可运行。压缩包含10个文件,其中7个CSV为主要股票与指数的历史行情数据集,1个PY脚本为预测主程序,另含TXT与MD说明文档,整体约490KB,便于快速理解数据预处理、模型训练与评估流程。目前已有171人学习与浏览,项目功能完整、界面直观,既可作为课程设计或期末大作业的完整模板,也可为后续扩展多步预测或引入更多优化算法提供坚实基础。

1. PSO-LSTM预测股票调整收盘价:一份能直接跑的期末大作业源码

先说结论:把LSTM直接丢给股票序列,十有八九预测结果和掷骰子差不多。LSTM本身是个黑匣子,隐藏层数、学习率、时间步长这些超参数对结果的影响远大于网络结构本身,而教科书里从不告诉你怎么选一组靠谱参数。这份源码把粒子群优化(PSO)塞进LSTM的超参数搜索里,用比特币、道琼斯、上证指数、苹果、黄金等多组数据集跑单维单步的MSE预测,代码注释齐全,适合做课程设计和期末大作业。我拆完最直观的感受是:不需要你有优化算法基础,改改数据集路径和迭代次数就能复现完整训练流程。下面按原理、数据流、落地步骤、踩坑记录、验证技巧依次讲。

2. 为什么是PSO-LSTM:LSTM的调参困境与粒子群寻优思路

2.1 LSTM对股票序列的短板不在结构,在超参数

LSTM处理时间序列的机制本身没有问题:输入门、遗忘门、输出门三个门控结构配合记忆单元,能按时间步选择性保留信息,这比前馈神经网络和BP神经网络更适合序列数据。问题是LSTM的每个组件都带超参数,常见的就有学习率、隐藏层神经元数量、网络层数、时间步长、batch大小、dropout比例。这六个参数的组合空间是连续且高维的,互相之间还有耦合关系。比如学习率从0.01改到0.001,loss曲线可能从剧烈震荡变成收敛缓慢;时间步长从10改成30,模型看到的“记忆长度”变了,预测结果完全是另一种形态。

股票调整收盘价数据本身是非平稳、带噪声的时间序列,叠加这种参数敏感度,结果就是:同一份代码、同一份数据,只改一个参数,预测精度可以从均方误差0.002恶化到0.05。这种问题在期末大作业场景里尤其致命——老师不会因为你网络结构设计精巧就给分,他要看到的是可复现的实验结果和合理的误差指标。所以关键在于把“人工试参数”变成“机器自动找参数”。

2.2 PSO怎么把调参变成寻优

粒子群优化的思路很直观:把一组超参数组合看作一个粒子,粒子的位置就是参数向量,速度是参数更新的方向和步长。每个粒子根据两个记忆来更新自己:自身历史最优位置(pbest)和整个群体的历史最优位置(gbest)。标准更新公式是:

[ v_{i,d}^{t+1} = \omega v_{i,d}^{t} + c_1 r_1 (pbest_{i,d} - x_{i,d}^{t}) + c_2 r_2 (gbest_{d} - x_{i,d}^{t}) ]

[ x_{i,d}^{t+1} = x_{i,d}^{t} + v_{i,d}^{t+1} ]

其中 (\omega) 是惯性权重,控制粒子对上一时刻速度的继承程度;(c_1) 是自我认知系数,(c_2) 是社会认知系数,(r_1)、(r_2) 是[0,1]的随机数。当 (\omega) 较大时粒子探索性强,容易跳出局部区域;(\omega) 较小时粒子收敛快,适合后期精细搜索。

这份源码里,PSO要寻优的参数通常是学习率、LSTM隐藏层神经元数、时间步长、batch大小。每个粒子的“适应度”就是用它对应的参数组合训练一次LSTM后,在验证集上得到的MSE。MSE越低,粒子越优秀。这和网格搜索、随机搜索的最大区别是:PSO不是盲目撒点,每个粒子都在往群体最优的方向移动,同时保留自身探索能力,所以在10到20次迭代内就能逼近一个较优区域。

2.3 适应度评估的成本结构与预算控制

理解了PSO之后要有一个清醒认识:每次适应度评估都要训练一次LSTM,哪怕只训练很少的epoch,20个粒子、10次迭代也意味着200次训练。这是这份源码在真实使用中最大的成本约束。源码里的“单维单步mse预测”就是为了控制成本——单维输入(只用收盘价),单步输出(只预测下一天),网络规模小,训练很快,所以一次完整的PSO寻优在你的笔记本上也能跑完。

常见做法是设定粒子数20到30,迭代次数10到15次,每次适应度评估只训练5到10个epoch,因为PSO寻优阶段只需要比较相对优劣,不需要训练到完全收敛。找到最优参数后再用这些参数从头训练一个完整的模型,训练到真正的收敛条件。这个“粗训练选参数、精训练出结果”的两段式流程,是这份源码的设计精髓。

# PSO适应度评估核心骨架(与源码逻辑一致) def fitness_function(params, X_train, y_train, X_val, y_val): # params = [learning_rate, hidden_units, time_step, batch_size] lr, hidden, time_step, batch = params # 每次评估前重建一个轻量LSTM,不保留历史状态 model = build_lstm(time_step=time_step, hidden_units=hidden) model.compile(optimizer=Adam(learning_rate=lr), loss='mse') # 只训练少量epoch,比较相对优劣即可 model.fit(X_train, y_train, batch_size=int(batch), epochs=5, verbose=0) mse = model.evaluate(X_val, y_val, verbose=0) return mse

这段代码的逻辑是:把PSO传来的参数向量拆包,构建一个轻量级LSTM,训练5个epoch后用验证集MSE作为粒子的适应度。注意time_step既是LSTM输入形状的一部分,也是滑动窗口长度,改它必须同步重建数据集,所以真正实现时要放在窗口构建之后。

参数说明:lr建议在[0.0001, 0.01]范围搜索,用对数尺度取随机值更合理;hidden_units在[32, 128];time_step在[5, 30];batch_size在[16, 64]。这四个参数是这份源码寻优的核心对象。

3. 源码结构与数据流:从CSV到单步MSE预测的完整路径

3.1 文件地图:每个文件是干什么的

解压后的目录结构很清晰,核心文件是一个Python脚本加一个datasets文件夹:

文件/目录角色说明
会议PSO-LSTM单维单步mse预测.py主脚本,包含数据加载、PSO寻优、LSTM训练、结果评估全流程
README.md使用说明和参数说明
Datasets/BTC-USD.csv比特币美元价格日线数据
Datasets/DJI.csv道琼斯工业指数日线数据
Datasets/Gold_daily.csv黄金日线数据
Datasets/000001.SS.csv上证指数日线数据
Datasets/IXIC.csv纳斯达克综合指数日线数据
Datasets/AAPL.csv苹果公司股价日线数据
Datasets/GSPC.csv标普500指数日线数据
Datasets/1.txt可能是数据来源说明或备用数据

这个数据集设置覆盖了加密货币、股指、商品、个股,目的是验证PSO-LSTM在不同波动特征序列上的泛化表现。期末答辩时这一点很加分:同一个模型框架在多个数据集上稳定运行,远比在一个数据集上调出漂亮数字更有说服力。

3.2 数据加载与归一化:先统一列名再谈训练

第一步是读取CSV。不同数据集来源不同,列名可能不一样:有的叫Close,有的叫Adj Close,有的带Price。源码里用的是调整收盘价(Adjusted Close),这是分红送股修正后的价格,比单纯收盘价更能反映真实收益变化。加载时要先打印columns确认列名,再做映射。

import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_data(csv_path, price_col='Adj Close'): df = pd.read_csv(csv_path) # 统一列名:如果CSV里没有Adj Close,退回Close if price_col not in df.columns: price_col = 'Close' data = df[price_col].values.astype(float) return data def normalize(data): scaler = MinMaxScaler(feature_range=(0, 1)) scaled = scaler.fit_transform(data.reshape(-1, 1)) return scaled, scaler

逻辑说明:load_data做的事情是容错读取,BTC-USD.csv和AAPL.csv这类数据通常有Adj Close列,但某些黄金数据可能只有Close,所以加了退化逻辑。normalize用MinMaxScaler把价格压缩到[0,1]区间,这一步不能省略——LSTM用tanh和sigmoid激活函数,输入不归一化会导致梯度消失或梯度爆炸。

参数说明:feature_range=(0,1)是常见设置,如果你的数据有明显离群值(比如比特币某天暴涨暴跌),建议改成(-1,1),配合tanh输出层效果更稳定。保存好scaler对象,后面预测结果还原价格要用,这是新手最容易丢的。

3.3 滑动窗口构建:单维单步的数据样本长什么样

单维单步预测的含义是:用过去time_step天的调整收盘价,预测未来1天的调整收盘价。数据集的构建方式是在时间轴上滑动取值:

def create_sequences(data, time_step): X, y = [], [] for i in range(len(data) - time_step - 1): X.append(data[i:(i + time_step), 0]) y.append(data[i + time_step, 0]) return np.array(X), np.array(y) # 以time_step=20为例 X, y = create_sequences(scaled_data, time_step=20) print('X_shape:', X.shape) # 期望输出:[样本数, 20] print('y_shape:', y.shape) # 期望输出:[样本数, 1] # LSTM要求输入是3D: [样本数, 时间步, 特征数] X = X.reshape(X.shape[0], X.shape[1], 1)

逻辑说明:create_sequences是在时间轴上滑窗取样。第i个样本的X是第i到第i+time_step-1天的价格,y是第i+time_step天的价格。注意循环终止条件是len(data) - time_step - 1,少了最后一天,目的是保证y有值,常见边界错误是把-1漏掉。

参数说明:time_step是这份源码的核心超参数之一,由PSO负责搜索。它本质是“模型记忆长度”:值太小,模型看不到趋势;值太大,会把噪声也学进去,而且样本数减少。AAPL这类长期趋势数据适合20到30,比特币这类高波动数据10到15更合适。shape是深度学习框架最常见的报错点,LSTM需要[样本数, 时间步, 特征数]三维输入,这里特征数是1(只用收盘价),所以最后reshape成[样本数, 20, 1]

3.4 为什么要用MSE作为预测误差

源码标题里“mse预测”指的就是用MSE(均方误差)作为训练损失和评估指标。MSE对预测值与真实值之差取平方,有两个特性:一是放大大误差的惩罚,模型会优先修正那些偏离严重的预测;二是误差量纲是价格的平方,解释性不如RMSE(开根号后回到价格量纲)。

在股票预测场景里,MSE是课程设计的默认选择,因为损失函数和评估指标一致,训练过程中可以直接观察验证集MSE来判断模型有没有过拟合。如果你的老师要求看到“价格误差”,可以在评估阶段加一行rmse = np.sqrt(mse)。但如果预测的是原始价格而非归一化价格,RMSE可能在几十甚至上百美元,答辩时要说明你的误差是在归一化空间内计算的,否则会被质疑结果规模。

4. 让脚本跑起来:环境配置、训练参数与结果解读

4.1 运行环境与依赖安装

这份源码基于深度学习框架编写,主脚本需要TensorFlow或Keras环境。我的建议是Python 3.8搭配TensorFlow 2.7,稳定性和兼容性最好。安装命令:

pip install tensorflow==2.7.0 pip install pandas numpy scikit-learn matplotlib

如果你用的是TensorFlow 2.7以上版本,keras已经内置在tensorflow包里,不需要单独pip安装。但要注意numpy版本,TensorFlow 2.7要求numpy版本在1.19到1.21之间,直接装最新numpy(1.24+)会在import时报module 'numpy' has no attribute 'float'错误。跑之前先检查一遍:

python -c "import tensorflow as tf; print(tf.__version__)" python -c "import numpy; print(numpy.__version__)"

这是我复现这类源码固定的第一步,先把框架版本卡死,再改业务代码,可以省掉大量底层兼容性的踩坑时间。

4.2 启动训练:一行命令跑起来的完整流程

源码结构是单脚本执行,下载后定位到目录,直接运行:

cd PSO-LSTM-for-Prediction-main python 会议PSO-LSTM单维单步mse预测.py

脚本正常启动后会依次做这些事情:读取默认数据集(一般是AAPL.csv)→ 归一化 → 按时间步构建窗口 → 划分训练集和验证集 → 初始化PSO粒子群 → 迭代寻优 → 输出最优参数 → 用最优参数重建LSTM并训练 → 绘制预测对比图。第一次跑通建议不要改任何参数,用默认配置跑一遍,确认环境没问题。

4.3 PSO关键参数对照表与调整建议

参数源码常见默认值调整方向影响
粒子数(n_particles)20增大到30-40提升搜索覆盖,但训练时间线性增长粒子太少容易漏掉最优区域
最大迭代次数(n_iter)10观察适应度曲线,若还在下降就增大到20迭代过多后期只做局部微调
惯性权重 w0.5-0.9大值前期探索、小值后期收敛,线性递减效果最好固定过大收敛慢,过小易早熟
学习率搜索范围[0.0001, 0.01]高波动数据往小了调学习率是LSTM最敏感的参数
隐藏层神经元数[32, 128]数据量大可放宽到[32, 256]神经元过多过拟合,过少欠拟合
时间步长搜索范围[5, 30]趋势型数据15-30,噪声型5-15影响模型记忆长度和样本数量
batch size[16, 64]数据量小时用16影响训练稳定性和收敛速度

调整原则:先固定迭代次数和粒子数,只调搜索范围。如果你在作业里复现,建议设置n_iter=15n_particles=25,这个组合在精度和耗时之间比较平衡。如果训练一轮下来超过半小时,把粒子数降回15,比降低迭代次数更能节约时间。

4.4 结果指标与预测图解读

训练结束后,源码会输出最优参数、训练集MSE、验证集MSE,并绘制真实价格曲线和预测价格曲线的对比图。看这张图时重点关注三点:

第一,预测曲线(一般是红色)是否跟着真实曲线的整体形态走。如果形态一致、略有滞后,说明模型学到了有效规律;如果完全不对称,大概率是数据划分或反归一化出错。第二,验证集MSE和训练集MSE的比值。如果验证集MSE是训练集的10倍以上,过拟合,要加大dropout或减少神经元数。第三,预测曲线的末端是否发散。单步预测是滚动一步、预测一步,误差不会累计,所以末端发散说明模型的最后一个时间步输入有问题。

import matplotlib.pyplot as plt # 反归一化还原真实价格 y_true_orig = scaler.inverse_transform(y_test.reshape(-1, 1)) y_pred_orig = scaler.inverse_transform(y_pred.reshape(-1, 1)) plt.figure(figsize=(12, 5)) plt.plot(y_true_orig, label='True Price', color='#333333') plt.plot(y_pred_orig, label='Pred Price', color='#d62728', linestyle='--') plt.title('Stock Adjusted Close Price Prediction (PSO-LSTM)') plt.xlabel('Time Step') plt.ylabel('Price') plt.legend() plt.grid(alpha=0.3) plt.show()

逻辑说明:模型输出的是归一化空间的预测值,必须用训练时保存的scalerinverse_transform才能还原成真实价格。这里最容易犯的错误是对y_test和y_pred分别fit一个新的scaler,那样还原出来的价格量级会偏离实际。参数说明:figsize=(12,5)适合单交易日序列展示,如果你的验证集有500个以上样本,可以适当加长宽度。颜色用深灰和深红的对比在PPT里投影也能看清。

5. 避坑与排查:我在复现PSO-LSTM时踩过的五个坑

5.1 预测曲线贴着0轴,反归一化还原后全是小数点后的小数

现象:训练完画图,预测值全部在0到0.05之间波动,还原成真实价格后数值比实际小了上千倍。

原因:反归一化时使用了错误的scaler。最常见的情况是对y_test自己重新fit_transform了一个新的MinMaxScaler,这个scaler的min和max来自归一化后的数据(0到1之间),反向还原时自然对不回去。

解决:全程只保留最初fit在完整训练数据上的那个scaler,对验证集和测试集只调用transforminverse_transform,绝对不要在测试集上重新fit。

# 错误写法:对测试集重新fit scaler_test = MinMaxScaler() y_test_scaled = scaler_test.fit_transform(y_test.reshape(-1, 1)) # 错! # 正确写法:复用训练时的scaler y_test_orig = scaler.inverse_transform(y_test.reshape(-1, 1)) # 对

5.2 PSO迭代没几次就停住,所有粒子挤在一起不再更新

现象:打印每次迭代的gbest MSE,发现前3次在下降,第4次之后完全不变化,并且每个粒子的参数向量几乎一样。

原因:这是粒子群优化的经典“早熟收敛”。惯性权重固定为常数时,粒子速度逐渐衰减到接近零,群体失去探索能力,被一个局部最优困住。

解决:把惯性权重改成线性递减,从0.9逐渐降到0.4,迭代前期大权重保持探索、后期小权重加速收敛。另外给每个粒子的位置和速度加边界约束,超出范围就随机重置,避免所有粒子收敛到同一组参数。

5.3 切换数据集时报维度错误:X_train.shape[1]不匹配

现象:用AAPL.csv跑通后,把csv_path改成BTC-USD.csv,训练时报expected shape (None, 20, 1) but found (None, 15, 1)

原因:不同数据集的长度不同,但源码里time_step如果在PSO搜索范围内,数据集的构建是在PSO循环外还是循环内有讲究。如果窗口是用固定time_step构建的,而PSO在改变time_step参数,那每次评估都需要重新构建数据集。

解决:把create_sequences放进适应度函数内部,每次评估按当前粒子的time_step值重新滑窗。代价是每次评估多一次数据构建开销,但换来的是time_step真正参与寻优。手动切换数据集时,也先打印X.shape确认维度。

5.4 训练MSE在下降,但预测曲线整体比真实曲线滞后一天

现象:预测曲线和真实曲线的趋势完全一致,但整体向右平移了一个时间步,MSE还很小。

原因:单步预测任务里,如果时间步长设置得太小(比如time_step=3),模型学到的最优策略就是“预测值约等于上一个时刻的值”,因为股票价格本身有强自相关性。MSE会因为这个滞后而显得很小,但这并不是有意义的预测。

解决:至少把时间步长设置到10以上;同时计算误差时对比滞后0步、1步、2步的MSE,如果滞后1步的MSE反而更小,说明预测没有实际价值。作业答辩时可以主动提这个问题,并展示你用时间步长15以上的结果,老师会认为你理解了模型的局限。

5.5 TensorFlow版本兼容性:安装即报float错误

现象:按README装完依赖,import tensorflow时报module 'numpy' has no attribute 'float'

原因:numpy在1.24版本移除了float别名,而老版本TensorFlow(2.6及以下)的代码还在用np.float。这是Python生态里典型的版本漂移问题,跟你的代码逻辑无关。

解决:固定版本安装pip install numpy==1.21.0 tensorflow==2.7.0。如果你装的是TensorFlow 2.10以上,numpy可以用1.23.x。检查顺序是先看numpy.__version__,再看tf.__version__,确认这两个版本匹配再跑脚本。

6. 效果验证与进阶技巧:让预测结果更可信

6.1 用滚动预测代替一次性测试集

源码默认的评估方式是把数据尾部一段作为测试集,一次性输入所有历史时间步得到预测值。这在单步预测里其实偏乐观。更贴近真实场景的做法是滚动预测:每次只预测未来一天,然后把真实观测值加入窗口继续预测下一天。这样更接近实盘中的操作逻辑,因为实盘里每一天都能拿到真实价格。

def rolling_predict(model, initial_seq, scaler, n_steps): current_seq = initial_seq.copy() preds = [] for _ in range(n_steps): # 输入形状: [1, time_step, 1] cur_input = current_seq[-time_step:].reshape(1, time_step, 1) pred = model.predict(cur_input, verbose=0)[0, 0] preds.append(pred) # 把预测值滚动进窗口,丢掉最早一天 current_seq = np.append(current_seq, pred) return scaler.inverse_transform(np.array(preds).reshape(-1, 1)).flatten()

这段代码的价值在于验证模型的“自反馈稳定性”。如果滚动多步后预测曲线发散成直线或指数曲线,说明模型对自身输出的误差没有鲁棒性,实际部署价值存疑。期末作业里加上这段代码,比单次预测的对比图更有说服力。

6.2 维度对齐检查:训练前打印shape是唯一后悔药

我复现源码的习惯是:在任何训练开始之前,强制打印X_train、y_train、X_val、y_val的shape,并且心里先算出预期值再对比。这个习惯救了我太多次:

print(f'X_train: {X_train.shape}, y_train: {y_train.shape}') print(f'X_val: {X_val.shape}, y_val: {y_val.shape}') print(f'time_step: {time_step}, hidden: {hidden_units}')

一旦看到y_train是一维而LSTM输出是二维,或者X_train少了一维,当场就能定位是滑窗还是reshape的问题。等训练跑到一半再报维度错,浪费的十分钟足以让你怀疑人生。

6.3 怎么证明PSO真的比手动调参强

作业答辩时最容易被问到的问题就是:“你怎么证明PSO找的参数比你自己试的好?”准备一个参数对比表就能回答:固定同一个数据集和同一次数据划分,跑三组实验。第一组用PSO找出的最优参数,第二组用随机网格搜索的参数,第三组用你手动估的参数,每组记录验证集MSE和训练耗时。三行数据摆出来,PSO的优势和成本一目了然。

另外保留PSO每一代gbest的MSE曲线,画成折线图放进PPT,展示收敛过程。如果曲线在大约第6次迭代后趋于平缓,说明收敛正常;如果是断崖式下降,说明初始粒子质量太差,需要扩大粒子数。

我从拿到这份源码第一次跑通,到把它改成多数据集批量预测,踩过最深的坑就是把scaler在测试集上重新fit,画出来的预测曲线和真实价格差了两个数量级,却愣是查了半天没找到原因。从那以后,我每次复现这类预测源码都强制走一遍流程——先打印shape,再确认scaler复用,最后用滚动预测验证稳定性,三步走完才敢说这个模型真的能用来做实验。希望帮到你。

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

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

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

立即咨询