简介:时间序列预测是机器学习的重要分支,股票价格作为典型非线性、非平稳序列,其预测任务极具挑战性。传统方法如ARIMA对数据平稳性要求苛刻,而LSTM凭借其独特的门控机制与记忆单元,能够有效捕捉长短期依赖关系,成为金融时序预测的主流模型。在实际工程中,特征工程的质量往往决定模型上限,从OHLCV到技术指标MA、RSI,再到归一化与滑窗切分,每一步都直接影响预测精度。此外,正确处理训练集与测试集的归一化、防止数据泄漏,以及合理评估MAE、RMSE、方向准确率等指标,是构建可靠模型的关键。本文以LSTM为核心,系统梳理了股票预测课程设计的完整流程,从数据获取到模型评估,为深度学习实践提供了一份可复用的技术路线。
1. 为什么是LSTM:先把模型的选型逻辑讲清楚
做股票预测这个题目,最怕的不是写不出代码,而是交上去的作业一眼看上去就是“拼凑的demo”。老师翻代码的时候,重点看的不是你调了多少个包,而是你能不能讲清楚每个环节的选择理由。LSTM之所以成为这类课程设计的主流选择,核心原因就一句话:股票数据是典型的时间序列,而LSTM天生就是处理时间序列的结构。
很多人一开始想的是用普通全连接网络去预测,或者用ARIMA这种传统统计模型。全连接网络的问题在于,它把每个时刻的数据当成独立的样本,完全丢掉了时间上的先后依赖关系。今天的收盘价对明天的收盘价有影响,这周的走势对下周的趋势有牵引,这种“记忆”能力是普通网络不具备的。ARIMA虽然是时间序列的经典方案,但它对数据的平稳性要求很苛刻,股票价格这种非平稳、带噪声、有波动聚集效应的序列,ARIMA做起来非常吃力,差分阶数、滞后阶数都要反复调,而且预测长序列的能力有限。
LSTM走的是另一条路。它内部有输入门、遗忘门、输出门三个门控结构,配合一个贯穿时间步的记忆单元,让网络能够在长序列中保留需要的信息、丢弃无关的信息。对股票这种“短期波动大、长期有趋势”的数据,LSTM能在一定程度上捕捉到均值回归效应和趋势延续效应。用大白话讲,它学会了“该记的记,该忘的忘”。
我见过不少同学在答辩的时候被问“你为什么不用GRU”“LSTM和RNN什么区别”,如果只是照本宣科背定义,很容易被追问到卡壳。我的建议是,这个项目里你用LSTM做主力模型,报告里再补一组对比实验——拿简单的线性回归或者单层LSTM做对照——不需要做得太复杂,但“对比”这两个字,在评分标准里往往直接对应“工作量充足”和“有独立思考”两个加分项。后面我会详细讲怎么设计对比实验。
2. 从数据到特征:90%的分数藏在数据准备里
2.1 数据源怎么选:免费稳定好解释是第一原则
数据是整份作业的地基。我见过太多人卡在数据获取这一步,原因无外乎三种:接口没权限、字段看不懂、数据格式不统一。
以我实测过的体验来说,推荐优先用Tushare Pro或者AkShare。Tushare Pro需要注册账号拿一个token,部分接口有积分要求,但基础日线数据注册后就能用;AkShare完全免费,不需要token,直接pip安装就能拉数据。如果你只是做课程设计,不需要上亿条数据,这两个都够用。
选数据源的时候有一个重要的考量维度:可复现性。你的老师打开你的代码重新跑一遍,能不能顺利拿到同样的数据?如果用的是某个需要付费权限的接口,老师没有token,代码直接报错,印象分会掉很多。所以我的建议是:把拉取到的数据本地存一份CSV,代码里写清楚“优先读本地,缺失时再从接口拉”,把数据快照放在项目目录里一起提交。这样既保证了复现,又展示了工程意识。
单只股票还是多只股票?课程设计一般不需要做全市场预测,选一只流动性好的股票即可,比如贵州茅台、招商银行、平安银行之类的。有波动才有预测空间,天天横盘的股票预测了也没意义。时间范围建议取最近三到五年,覆盖至少一段完整的上涨和下跌周期,这样模型能学到不同市场环境下的特征。
2.2 特征工程:价格之外还能加什么
原始数据拉下来一般有这几列:日期、开盘价、最高价、最低价、收盘价、成交量、成交额。初学者最容易犯的错误就是只用收盘价一个字段去预测收盘价,信息量太少,模型很难学到东西。
常用特征至少包括以下几类:
- OHLCV原始序列:开盘、最高、最低、收盘、成交量
- 衍生特征:涨跌幅、振幅、换手率、量比
- 技术指标:MA5、MA10、MA20、MACD、RSI、布林带位置
- 时间特征:星期几、月份,用来捕捉周内效应和月度效应
技术指标不建议堆太多,选五六个有代表性的就够了。特征太多会引入噪声,训练速度变慢,还容易过拟合。以我个人的经验,MA5、MA20、RSI、MACD这四个技术指标配合原始OHLCV,已经能拿到不错的效果。
有一个细节很多人不知道:特征选择的时候,一定要想清楚“这个特征在预测时能不能拿到”。比如你用“当日成交量”去预测“次日收盘价”,这是合理的,因为当日数据是已知的;但如果你不小心把“次日开盘价”也放进特征里,那就发生了数据泄漏(data leakage),训练时指标很好,实际预测时发现根本拿不到这个特征。老师最喜欢问的就是“你的特征里有没有未来数据”,这个问题答不好,分数档次直接掉一级。
2.3 归一化:必须只对训练集拟合,这是最容易扣分的点
LSTM用的是激活函数,对输入数据的尺度非常敏感。股票价格是几千甚至几万的数量级,如果直接塞进网络,梯度计算很容易出问题,训练不稳定。所以归一化是必需的,通常用MinMaxScaler把所有特征缩放到0到1之间。
但这里有一个极其关键、期末作业里最常见的扣分点:MinMaxScaler必须只在训练集上fit,然后再用同一套参数去transform验证集和测试集。很多人的代码写成先对整个数据集做归一化,再划分训练测试集,这属于典型的“信息泄漏”——测试集的信息在训练阶段就已经被模型看到了,评估结果会虚高。真实场景中你没有未来数据,不可能拿未来的最大值最小值来归一化过去的数据。
正确做法是先按时间顺序划分训练集和测试集,再在训练集上fit scaler,然后transform所有集合。我建议把这段逻辑写在代码注释里,答辩的时候主动提一句“我特意避免了归一化泄漏”,这个细节能让你从八十分档跳到九十分档。
2.4 滑窗切分:如何把时间序列变成模型能吃的样本
LSTM的输入是三维的,形状是[样本数, 时间步长, 特征数]。时间步长就是“用过去多少天的数据去预测未来”,比如用过去20天的数据预测明天的收盘价,那时间步长就是20。
滑窗的原理很好理解:假设有1000天的数据,窗口长度20,那么第1到20天生成第一个样本,第2到21天生成第二个样本,依此类推,总共生成约980个样本。这个"滑动"的过程需要自己写,或者用keras.preprocessing.timeseries_dataset_from_array来生成。
窗口长度怎么选?太短,模型看不到趋势;太长,样本数量变少,训练效率低,而且股票市场的“记忆”本身也有限,十年前的价格对明天的影响微乎其微。我的经验是5到60天之间,具体取值可以做一个小实验:分别比较窗口为5、10、20、30天时的验证集误差,把对比结果写进报告里,又是一个加分项。
预测目标怎么定?最常规的设定是“预测明天的收盘价”,这是回归任务。除此之外还可以做二分类——预测明天涨还是跌,甚至多分类——预测涨幅区间。课程设计首选回归,因为回归的误差指标更直观,画图也更漂亮;分类可以作为扩展实验写进第四章。
3. LSTM模型搭建:从架构设计到训练细节
3.1 网络结构:几层LSTM、多少个神经元才算合理
网络结构没有标准答案,但有一个合理的起点。以我的经验和多轮实测来看,单层LSTM加一个Dense输出层,在大部分股票数据上已经能取得不错的效果。堆两层LSTM往往能提升一点点精度,但训练时间翻倍,而且更容易过拟合。课程设计建议先从单层开始,如果效果不理想再加深。
具体参数我推荐这样起步:
- 第一层LSTM:50个神经元,
return_sequences=True - 第二层LSTM:50个神经元(可选,加了就
return_sequences=False) - Dropout层:0.2,防止过拟合
- Dense输出层:1个神经元,线性激活
输入形状不用在LSTM层里显式指定,Keras会根据输入数据自动推断。但如果你写了Input层,记得把形状写成(time_step, feature_dim)。
神经元数量怎么调?一个简单原则:先定一个小值比如32,看训练集loss能不能降下去,能降说明模型容量够用;如果验证集loss高而训练集loss很低,那是过拟合,需要加Dropout或减小模型;如果训练集loss也降不下去,说明模型容量不够,需要加大。
3.2 损失函数和优化器:这些参数为什么这么选
股票预测是回归任务,最常用的损失函数是均方误差(MSE)。为什么不用MAE?因为MSE对大误差的惩罚更大,梯度更平滑,训练更稳定。但MSE对异常值敏感,股票数据里偶尔会有极端行情,如果你发现模型被个别极端值带偏了,可以换成Huber Loss,它是MSE和MAE的折中。答辩的时候能说清楚MSE和Huber的区别,非常加分。
优化器直接用Adam,学习率默认0.001即可。注意,不要自己瞎改学习率策略,除非你明确知道为什么要改。Adam自带自适应学习率,对新手非常友好。
编译代码我习惯这样写:
from keras.losses import MeanSquaredError from keras.optimizers import Adam model.compile( optimizer=Adam(learning_rate=0.001), loss=MeanSquaredError(), metrics=["mae"] )除了loss,一定要加mae这个指标。理由是MSE是平方量纲,直观上不好理解,而MAE直接从预测值和真实值的平均绝对误差出发,人话就是“平均每天预测错了多少钱”,老师和同学都能一眼看懂。
3.3 训练细节:EarlyStopping和模型保存不能少
训练轮数(epochs)不是越大越好。我见过有人直接训500轮,训到后面验证集loss不降反升,典型的过拟合。正确做法是配合EarlyStopping使用:监控验证集loss,如果连续多轮没有改善就停止训练,同时恢复到最佳权重。
代码示例:
from keras.callbacks import EarlyStopping, ModelCheckpoint early_stop = EarlyStopping( monitor="val_loss", patience=20, restore_best_weights=True ) checkpoint = ModelCheckpoint( "best_model.h5", monitor="val_loss", save_best_only=True ) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=150, batch_size=64, callbacks=[early_stop, checkpoint], verbose=1 )batch_size的选择也有讲究。股票数据样本量一般几千到几万,batch_size设64或128比较合适。太小训练不稳定,太大训练太慢而且容易陷入局部最优。
训练完之后,画loss曲线是必须的——训练loss和验证loss两条曲线画在同一张图上。如果训练loss下降而验证loss先降后升,就是过拟合;如果两条线都降不下去,就是模型容量不够或学习率不合适。这张图是报告里的标配,也是老师判断“你确实动手训练过”的重要依据。
3.4 反归一化:预测值必须还原成真实价格再评估
模型输出的预测值是0到1之间的归一化数值,必须用之前那个scaler做inverse_transform才能回到真实价格量纲。这里要注意一个坑:如果训练时随机选了多个特征一起归一化,inverse_transform需要传入和之前相同维度的输入。所以实践中常见做法是:预测目标(收盘价)单独用一个scaler,其余特征用另一个scaler,这样反归一化时不容易出错。
代码逻辑大致是这样:
pred_scaled = model.predict(X_test) pred_price = target_scaler.inverse_transform(pred_scaled.reshape(-1, 1)) real_price = target_scaler.inverse_transform(y_test.reshape(-1, 1))反归一化之后,再算误差指标、画预测对比图,结果才是真实可读的价格数字。
4. 评估与可视化:用跌宕起伏的曲线让老师眼前一亮
4.1 误差指标:MAE、RMSE、MAPE都要会解释
评估指标是报告里必备的内容,但很多人的通病是光贴数值不解释含义。每个指标都要一句话说清楚“它到底在衡量什么”。
MAE是平均绝对误差,单位是元,意思是预测值和真实值平均差多少钱。RMSE是均方根误差,它比MAE对大误差更敏感,如果RMSE明显大于MAE,说明预测里存在一些特别离谱的样本。MAPE是平均绝对百分比误差,它消除了价格量纲的影响,方便和其他股票对比,比如MAPE=2%意味着平均每天预测偏了2%。R²决定系数用来衡量模型对数据的解释程度,越接近1越好。
对于课程设计,我建议报告里用表格列出这几个指标,同时加一句“从数据可以看出,模型在测试集上MAPE为X%,说明预测误差在可接受范围内”。注意,不建议说“预测很准”这种绝对化的话,股市本身带有随机性,老师也清楚这一点。你的目标是展示“我能科学评估一个模型的效果”,而不是宣称“我找到了股市提款机”。
代码示例:
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np mae = mean_absolute_error(real_price, pred_price) rmse = np.sqrt(mean_squared_error(real_price, pred_price)) mape = np.mean(np.abs((real_price - pred_price) / real_price)) * 100 r2 = r2_score(real_price, pred_price)4.2 预测曲线图:这张图画好了,答辩就成功了一半
可视化是课程设计里最直观的评分依据。我见过很多代码写得一般、但图特别漂亮的作业拿到高分,也有代码很完整但图一塌糊涂的作业被压分。图是你的脸面,值得花时间打磨。
核心图至少要有四张:
第一张是原始价格走势图,让读者对数据总体情况有认识。第二张是训练集真实价与预测价的对比图,主要证明模型学习了训练数据。第三张是测试集真实价与预测价的对比图,这是最有说服力的一张图,用来证明模型的泛化能力。第四张是误差分布图,可以画预测残差的直方图,直观呈现误差是否符合正态分布。
测试集对比图我建议只画最后200到300个交易日,否则整条曲线挤在一起看不出细节。画法示例:
import matplotlib.pyplot as plt plt.figure(figsize=(12, 6)) plt.plot(real_price, label="True Price", color="black", linewidth=2) plt.plot(pred_price, label="Predicted Price", color="red", linestyle="--") plt.title("LSTM Stock Price Prediction on Test Set") plt.xlabel("Time Step") plt.ylabel("Price (CNY)") plt.legend() plt.grid(alpha=0.3) plt.tight_layout() plt.savefig("prediction_result.png", dpi=200)注意图片要设置dpi=200以上,清晰度直接影响印象分。坐标轴要有明确标签,图例一定要加,这些都是基本的学术作图素养。
4.3 方向准确率:股票预测里更贴近实际场景的指标
价格预测的误差再小,如果预测涨跌的方向经常反了,实际参考价值就不大。所以在评估环节,我强烈建议加一个“方向准确率(Directional Accuracy)”指标,计算方式很简单:判断模型预测的明日价格相对于今日是涨还是跌,跟真实方向是否一致,统计一致的比例。
这个指标不需要额外写复杂代码,几行就能算出来:
true_direction = np.sign(np.diff(real_price.flatten())) pred_direction = np.sign(np.diff(pred_price.flatten())) accuracy = np.mean(true_direction == pred_direction) * 100为什么这个指标重要?因为股票预测的终极目标不是预测精确价格——那几乎不可能——而是预测趋势。方向准确率超过55%就已经有实际参考价值了,这个数字在答辩时非常能说明问题。
4.4 策略回测:把预测结果转化为模拟收益
如果想让作业再往上一个档次,加一个策略回测模块。思路很简单:如果模型预测明天上涨,就模拟持有;如果预测下跌,就模拟空仓或卖出。对比“模型策略”和“一直持有不动”的累计收益,画两条净值曲线。
这是很多课程设计里没有的内容,一旦出现就属于“加分项中的加分项”。代码也不复杂,核心逻辑是根据预测方向生成每日持仓状态,然后乘以当日的实际收益率。注意,这里是为了展示模型的实用价值,不是让你真金白银去炒股,报告中要加一句“策略模拟仅用于学术研究,不构成投资建议”。
5. 课程设计拿高分的四个隐藏技巧
5.1 报告结构:要让老师五分钟内看懂所有亮点
95分以上的期末作业,报告结构通常遵循一个黄金公式:先交代问题和意义,再介绍数据来源和预处理方法,然后用图表呈现模型结构,接着展示实验结果和对比分析,最后写不足与展望。
报告里要有至少三张图、两张表。图分别是数据走势图、预测对比图、loss曲线图;表分别是模型参数表和指标对比表。这三图两表就是报告的主干,其他文字都是围绕他们展开。有一个常见错误:一上来就贴大段代码。老师要看的不是代码,是思路和结果。代码放附录,正文只放核心代码片段。
5.2 对比实验:同一模型少参数就不如多参数,这是最有效的论证
“没有对比就没有伤害”这句话在学术场景里完全成立。一份只说“我做了LSTM预测模型”的作业,和一份说“我对比了单层LSTM、双层LSTM、不同时间步长、是否加技术指标的效果”的作业,评分差距往往是两个档次。
我在前面提过,对比实验不需要多复杂,三条线就够:
- 基准线:单层LSTM + 只用收盘价,相当于最简方案
- 主力方案:单层LSTM + 完整特征集(OHLCV + 技术指标)
- 扩展方案:双层LSTM + 完整特征集
把三个方案在测试集上的MAE、RMSE、MAPE放进同一个表格,配合一段分析文字,说明“增加特征能够有效降低预测误差,但增加LSTM层数对效果的提升有限,说明当前瓶颈在特征而非模型复杂度”。这段话展示了你对模型的深度理解,比任何代码都能打动老师。
5.3 答辩预演:把最可能被问到的6个问题准备成问答稿
答辩环节决定最终印象分。我按多年经验整理出最常被问的问题,提前准备就不会慌。
第一个问题是“为什么选LSTM而不选RNN或GRU”。回答要点是LSTM的门控机制能缓解梯度消失,GRU虽然结构更简单速度更快,但在长序列上LSTM的经验表现更稳定。第二个问题是“你的模型有哪些局限性”。诚恳认错反而加分,可以说模型没有考虑市场消息面因素、宏观经济指标,无法应对极端行情。第三个问题是“滑动窗口长度怎么定的”。你要能说出是做了实验对比,不是随便拍的。第四个问题是“怎么防止过拟合”。答Dropout、EarlyStopping、数据量控制。第五个问题是“如果预测不准怎么办”。答这个预测只作为参考,方向准确率比精确价格更有意义。第六个问题是“这套方法能直接用来炒股吗”。答案是明确的“不能”,股市受太多未知因素影响,做作业是证明建模能力,不是创造永动机。
5.4 代码规范:命名清晰、注释到位、结构分层
对于一个直接读你代码的老师来说,代码结构比代码本身更值钱。我建议把所有代码分成四个文件:data_fetcher.py管数据获取,data_preprocess.py管清洗和滑窗生成,model.py管模型搭建和训练,evaluate.py管评估和画图。再加一个main.py串联整个流程,一个README.md说明运行环境和步骤。
注释要写中文,关键步骤写清楚“为什么”而不是“做了什么”。比如归一化那行注释写“只在训练集上拟合,防止测试集信息泄漏”,这句话的价值远超代码本身。变量命名用英文,不要用拼音缩写。这些工程上的细节,虽然不影响预测结果,但直接影响老师对你专业素养的判断。
6. 常见问题与调试技巧实录
6.1 维度报错:LSTM输入必须是三维的,这是新手第一大坑
报错信息里最容易遇到的是Input 0 of layer "lstm" is incompatible with the layer: expected ndim=3, found ndim=2。这个错误就是在告诉你,LSTM需要三维输入(batch_size, time_step, n_features),而你给的是二维数据。
解决办法很简单:在特征矩阵准备好之后,用reshape把数据变成三维。比如你有1000个样本,窗口长度20,特征数10,那形状应该是(981, 20, 10),因为最后一个样本的起点是第981个样本。如果你发现数据的形状对不上,优先回看滑窗切分的实现。
6.2 过拟合:训练集loss一直降,验证集loss在后半段不降反升
这是LSTM做股票预测时最典型的问题。股票数据本身就带噪声,模型容易把噪声中的偶然规律也学进去。解决手段按优先级排列:加Dropout(0.2起步,可以试0.3)、调低LSTM神经元数量、EarlyStopping的patience调小一些、增加训练数据量。还有一个被忽视的手段是降低batch_size,小batch引入的随机性有时反而能帮助模型跳出局部最优。
如果试遍以上手段还是过拟合,检查一下是否用了过多的技术指标特征。特征越多,模型可拟合的维度越高,但股票数据没有那么多稳定的规律可学,特征多到一定程度就是在帮噪声建模。
6.3 loss不降:检查两点,学习率和数据归一化状态
loss一直不降或者剧烈震荡,第一反应不是调模型结构,而是检查两件事。第一,数据是否归一化。我见过一次真实案例,同学忘了对数据做归一化直接训练,loss在几十万上下震荡了100轮毫无改善。第二,学习率是否过大或过小。默认的0.001一般没问题,但你如果改到0.1可能梯度爆炸,改到0.00001可能训练速度慢到你怀疑人生。
还有一个容易忽略的点:检查标签列是否也做了归一化。有时候特征归一化了,但y忘了归一化,模型输出范围是0到1,而真实价格是几十块钱,损失函数爆炸。这个细节非常常见。
6.4 预测结果是一条水平线:模型输出几乎不变,重点检查这三点
这种情况我也踩过坑。如果预测曲线几乎是一条平线,说明模型学到了“输出均值最优”,也就是预测结果基本贴近历史均值的简单策略。原因往往有三个:特征完全没有预测力、模型容量太小或者特征归一化包含未来信息然后又反归一化出错。还有一个低级的坑:用MinMaxScaler归一化后,预测值反归一化时传入了错误的特征维度,导致输出全部被拉伸到同一个值。
检验方法很简单:把预测值在归一化之前打印出来,看看是否在0到1之间变化。如果归一化之前的预测值有波动那问题出在反归一化;如果归一化之前就是平的,那问题出在模型或特征。
7. 代码压缩包交付的注意事项
题目里带了.zip后缀,说明这份作业最终是以压缩包形式提交的。我拿到过很多份课程设计,发现压缩包内的组织方式本身就是隐形评分项。
包里建议至少包含这些内容:完整的源代码目录、一份requirements.txt环境依赖文件、一份README.md运行说明、一份项目报告PDF和Word双版本、核心结果图(预测曲线、loss曲线)、如果数据集不大的话把本地数据CSV也放进去。
requirements.txt这个文件特别提一下,很多同学会忽略。老师在一台新电脑上跑你的代码,如果少了环境依赖说明,光是装tensorflow、pandas就能折腾半小时,如果恰好版本不对还报一堆错,体验分直接崩。写清依赖文件,至少保证老师能在二十分钟内跑通全流程。
numpy==1.24.3 pandas==2.0.3 scikit-learn==1.3.0 matplotlib==3.7.2 tensorflow==2.13.0压缩包命名也要注意,如果学校支持,文件名尽量包含学号姓名和项目名,方便老师归档。
在实际操作中我还发现一个非常实用的小技巧:在README.md里写清楚“先运行main.py一键训练,再运行evaluate.py查看结果”,并附上实测运行环境的说明,比如Python版本3.10、操作系统Windows 11。这种细节看起来不起眼,但老师打开压缩包那一刻的体验,会直接影响他对整个项目的心理预判。
本文还有配套的精品资源,点击获取