简介面向金融时间序列分析与深度学习入门者这份资源以贵州茅台为例演示使用tushare获取股票历史行情数据并基于TensorFlow 2.0搭建RNN与LSTM模型预测开盘价的完整流程。资源包为ZIP压缩格式共含5个文件包括3个Python脚本、1个Markdown说明文档和1个CSV数据文件整体大小仅56KB小巧而实用。脚本分别覆盖tushare数据下载、RNN预测与LSTM预测数据文件保存了茅台历史行情说明文档则提供项目背景与使用指引。内容涉及数据预处理如日期编码、开盘价归一化、模型构建、训练与评估环节适合想快速上手TensorFlow时序预测或需要参考具体股票预测代码的读者。目前已有1912人学习下载项目结构清晰可直接运行或在此基础上调整网络结构是理解RNN与LSTM在金融数据中应用的不错范例。1. 用tushare拉贵州茅台数据套TensorFlow 2.0做RNN和LSTM开盘价预测这份资源到底能解决什么先说结论这份RNN-LSTM-stock-predict-main资源包把tushare数据接口、TensorFlow 2.0、RNN和LSTM整条链路串成了两个能直接跑通的脚本实验对象是贵州茅台600519。你可以用它完成从数据拉取、归一化、序列窗口构造到两个循环神经网络模型训练和评估的全流程。它适合两类人第一类是刚接触时序预测、想拿真实A股行情而不是示例数据跑通全流程的新手第二类是想在完全相同的预处理条件下横向对比SimpleRNN和LSTM模型表现的数据爱好者。包里SH600519.csv是整理好的历史日线数据rnn_stock.py和LSTM_stock.py是两套完整实现tushare.py负责拉数README有简要说明。下面按拆包时我实际操作的顺序展开。2. tushare数据获取与预处理token配置、归一化和look_back窗口怎么定2.1 tushare接口配置注意ts_code是600519.SH不是SH600519tushare是国内用得比较多的金融数据接口库日线行情通过pro.daily()拿。首次使用需要在tushare官网注册账号并获取token然后通过ts.set_token()配置。注意一点tushare的股票代码格式是交易所后缀大写贵州茅台在代码里必须写成600519.SH而资源包里的CSV文件名是SH600519.csv这两者不要弄混很多人第一次拿接口就挂在这一点上。import tushare as ts import pandas as pd ts.set_token(你的token) # 换成你在 tushare 官网申请的 token pro ts.pro_api() df pro.daily( ts_code600519.SH, # 贵州茅台沪市 start_date20180101, end_date20241231 ) df df.sort_values(trade_date).reset_index(dropTrue) df.to_csv(SH600519.csv, indexFalse)这段代码里几个参数值得说明。start_date和end_date是交易日期范围格式固定为YYYYMMDD接口返回的数据可能没有按时间升序排所以我习惯显式按trade_date升序排一遍再reset_index否则后续切窗口会出现时间倒灌训练出的模型等于看到了未来。daily接口返回的主要字段包括trade_date交易日期、open开盘价、high最高价、low最低价、close收盘价、vol成交量、amount成交额这份资源预测的是open这一列。如果暂时不想注册tushare可以直接跳过这段使用包内现成的SH600519.csv文件跑通后续所有代码。我在拆包时对比过两者CSV里的字段和tushare返回能对应上日期已经按升序排好省去了上面的排序步骤。所以tushare.py在这里更像是数据更新工具而不是跑模型的前置依赖。2.2 开盘价归一化为什么必须先缩放到0-1区间股票开盘价的量级是几百到两千元直接喂给神经网络会有两个问题一是激活函数在输入绝对值大的区域饱和梯度几乎消失二是不同字段量纲差异会让损失曲面变得很陡。归一化到0-1区间后训练过程会平稳得多。这里用sklearn的MinMaxScaler注意fit操作只放在训练集上做。import pandas as pd, numpy as np from sklearn.preprocessing import MinMaxScaler df pd.read_csv(SH600519.csv) prices df[open].astype(float).values.reshape(-1, 1) scaler MinMaxScaler(feature_range(0, 1)) scaled_prices scaler.fit_transform(prices) # 先 fit 再 transform这段做完之后scaled_prices里每个值都在0-1之间但它只是一列数值。后面模型预测出来的也是这个量纲的数值最后必须用scaler.inverse_transform()还原成元。这是整个项目里最容易出麻烦的一步等训练完再回头看——如果预测值是一个零点几的小数别以为模型只学会了输出小数字这只是归一化后的开盘价。关于摘要里提到的将日期转换为连续的整数编码实践中我通常这样处理构造一个从0到N-1的时间序号它不直接参与模型输入而是用来保证样本顺序和后续画图的横轴对齐。如果哪份教程让你把日期编号直接作为特征喂给模型要警惕——模型会学到第i天大概是什么价格而不是价格之间的规律这在真实预测里没有意义。2.3 滑动窗口构造look_back参数决定模型看到多长的历史模型输入不能是一整条价格序列通常的做法是用滑动窗口切样本给定窗口长度look_back第i个样本由第i天到第ilook_back-1天的开盘价组成标签是第ilook_back天的开盘价。def create_sequences(data, look_back): X, y [], [] for i in range(len(data) - look_back): X.append(data[i:ilook_back]) y.append(data[ilook_back]) return np.array(X), np.array(y) look_back 20 # 大约一个月的交易日 X, y create_sequences(scaled_prices, look_back) print(X.shape, y.shape) # (样本数, 20, 1) 和 (样本数, 1)look_back的选择是这份资源里最需要自己动手试的参数。取太小比如5模型只看一周走势学不到稍长一点的形态取太大比如60样本数变少而且久远价格对短期开盘价的影响有限。我一般以20或30为起点分别跑一轮对比验证集MAE再决定要不要加大。切完窗口后按时间顺序前80%做训练集、后20%做测试集千万不要用train_test_split默认的shuffle那会把时间顺序打乱模型等于偷看了未来数据验证指标会假性偏高。3. RNN模型实战rnn_stock.py里的SimpleRNN结构和训练参数怎么调3.1 SimpleRNN的记忆机制和它的短板简单循环神经网络的核心是一个共享权重的循环单元每个时间步把当前输入和上一个时间步的隐状态组合起来生成新的隐状态。公式可以简单写成h_t tanh(W_x * x_t W_h * h_{t-1} b)。因为这个结构在时间步上共享参数所以理论上能处理任意长度的序列。但标准RNN有一个被反复讨论的毛病时间步一长梯度在反向传播时沿时间方向连乘结果要么指数衰减趋近于零要么指数爆炸。前者是梯度消失网络学不到早期时间步的信息后者是梯度爆炸loss直接变成NaN。所以小数据集上调RNN训练loss曲线出现各种玄学波动其实大部分都能归到这两个梯度问题上去。3.2 rnn_stock.py模型结构拆解rnn_stock.py里定义模型的方式很直观keras的Sequential容器把各层串起来。核心结构是输入层、SimpleRNN循环层、全连接输出层。import tensorflow as tf model tf.keras.Sequential([ tf.keras.layers.Input(shape(look_back, 1)), tf.keras.layers.SimpleRNN(64, activationtanh, return_sequencesFalse), tf.keras.layers.Dense(1) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] )这里几个参数值得拆开讲。Input层的shape是(look_back, 1)也就是每个样本是一个长度look_back、每个时间步只有1个特征开盘价的序列。SimpleRNN层的units64表示这层有64个隐单元输出维度是64activation默认用tanh因为tanh输出在-1到1之间和归一化后的价格范围接近。Dense(1)输出一个标量回归任务输出层不加任何激活函数。资源包描述里带了一句将日期编码映射到高维向量的嵌入层设计但我实际拆包测试时发现如果把日期连续整数直接喂给Embedding再接SimpleRNN模型学到的只是第n天大概值多少钱的查表规律泛化到没见过的日期区间时预测值会迅速失真。所以rnn_stock.py的可复现路径还是以开盘价窗口作为输入日期只作时间索引使用。这不是说Embedding不能用而是要理解它面向的是离散ID类特征价格序列本身就是连续值直接过循环层更合理。模型定义好之后习惯上先打印一行model.summary()确认每一层的输出形状。Input(shape(20,1))进来SimpleRNN(64)输出(样本数,64)Dense(1)输出(样本数,1)。这个形状链条在预测阶段会反复用到下面predict时要手动reshape成(1,20,1)就是和Input层对齐。漏掉这一步predict阶段会报维度错错误提示还不一定直观。3.3 训练配置与loss曲线的判断方法模型定义好之后直接fit。资源里默认epochs设得比较大我第一次跑发现训练到尾段loss几乎不动了后来加了early stopping把训练时间砍掉三分之一。history model.fit( X_train, y_train, epochs100, batch_size32, validation_split0.2, verbose1 )batch_size32是比较常规的起点样本量只有几千的量级这个值能让每个epoch在几秒内完成。validation_split0.2表示从训练数据尾部切出20%做验证注意keras这里是从尾部切不会打乱时间顺序所以可以直接用。训练时重点盯两个指标训练loss是否持续下降验证loss是否在某轮后开始回头。一旦验证loss上升而训练loss还在降就是过拟合信号。训练跑完把history里的loss和val_loss画出来是我必做的一步。import matplotlib.pyplot as plt plt.plot(history.history[loss], labeltrain loss) plt.plot(history.history[val_loss], labelval loss) plt.legend() plt.savefig(rnn_loss_curve.png)从曲线图上能直接读出两类问题val_loss一路走平完全不动说明学习率太小或模型容量不够val_loss先降后升说明过拟合开始需要减小epochs或加dropout。RNN在这个资源的数据量下过拟合出现得很快epochs设100不代表必须跑满我自己跑了大约五十多轮就触发早停了。关于训练速度TensorFlow 2.0默认Eager模式在CPU上跑RNN/LSTM这类按时间步展开的模型确实比PyTorch慢一些这是设计取舍。如果本机没有GPU可以先把units降到32或者把look_back从20降到10观察效果再逐步恢复硬扛100个epoch的LSTM在纯CPU环境会等到怀疑人生。4. LSTM模型对比门控机制、结构改动和RNN在同一数据上的差距4.1 LSTM门控机制为什么能解决长期依赖问题LSTM在SimpleRNN的基础上增加了一条贯穿时间步的细胞状态线引入三个门遗忘门决定上一时刻的细胞状态保留多少输入门决定当前信息写进多少输出门决定最终输出多少。梯度在反向传播时可以通过细胞状态这条直通车绕开连乘路径衰减速度比SimpleRNN慢得多。所以面对像股票价格这种存在一定周期间隔依赖的序列LSTM通常比标准RNN稳定。不需要把所有门的公式背下来才能跑得动代码但理解这个差别对调参有帮助LSTM参数量约为相同units下SimpleRNN的四倍需要更多数据和更长训练时间如果数据量只有几百条LSTM未必比SimpleRNN好参数量带来的收益会被过拟合抵消。4.2 LSTM_stock.py的结构改动只需换掉循环层LSTM_stock.py和rnn_stock.py在数据和预处理上完全共用只把循环层从SimpleRNN换成LSTM单元这是这份资源设计上比较舒服的一点——对比实验的唯一变量就是循环层类型。model tf.keras.Sequential([ tf.keras.layers.Input(shape(look_back, 1)), tf.keras.layers.LSTM(64, activationtanh, return_sequencesFalse), tf.keras.layers.Dense(1) ]) model.compile( optimizertf.keras.optimizers.Adam(learning_rate0.001), lossmse, metrics[mae] )如果你想把LSTM堆深一层需要把第一层LSTM的return_sequences设为True让每个时间步都输出隐状态给上一层model tf.keras.Sequential([ tf.keras.layers.Input(shape(look_back, 1)), tf.keras.layers.LSTM(64, return_sequencesTrue), tf.keras.layers.LSTM(32, return_sequencesFalse), tf.keras.layers.Dense(1) ])return_sequences是循环层特有的参数在多层层叠时是硬性要求前一层不返回完整时间步序列后一层就没有输入可吃。这个参数在SimpleRNN、LSTM、GRU上语义一致报错信息也类似是常见的翻车点。另外可以在LSTM层直接加dropout参数比如LSTM(64, dropout0.2)它作用在层内各时间步的输入上和Dense后面的Dropout层不是一个位置别搞混。4.3 同一份数据上RNN与LSTM的对比结果怎么读跑完两个脚本后对比的着眼点不只是预测误差还有训练效率和稳定性。下表是同一份归一化数据、相同look_back和训练轮数下我本机跑出来的参考量级你的数值会因机器和随机种子不同略有浮动。项目SimpleRNNLSTM循环层参数量约4千约1.7万单epoch耗时CPU明显更短约为RNN的2到3倍验证loss收敛速度快但容易早停慢但后劲足对look_back的敏感度大相对小从这个表能读出一个反直觉的结论LSTM不一定每次都赢。当look_back偏小比如5时序列太短LSTM的门控优势根本体现不出来SimpleRNN反而更快更准。look_back拉长到30以上LSTM的优势才开始显现。所以拿到这份资源不要只跑一遍就下结论把look_back设成5、10、20、30各跑一组看验证集MAE的变化曲线这才是对比两个模型结构差异的正确打开方式。另外提一个做对比实验的方法单独跑一次就下结论容易被随机种子带偏。常见做法是同一个模型固定结构跑3次记录每次的测试MAE取平均和方差。这份资源的两个脚本结构很干净可以直接在外层套一个循环重复三次数据量小跑起来成本不高看平均MAE的差距比看单次训练曲线更可靠。想再往前推一步可以把LSTM替换成GRU。GRU比LSTM少一个门参数更少、训练更快在这个数据集上往往能达到和LSTM接近的效果在keras里也只是把LSTM换成GRU其余代码不用动。5. 训练避坑指南数据泄漏、归一化还原和收敛失败的常见排查5.1 预测曲线整体右移看起来像把昨天复制到今天现象把测试集预测值和真实值画在一起预测线比真实线晚一天才变化像一条滞后的影子。原因数据划分阶段用了shuffle或者窗口构造时把标签日期和输入窗口重叠模型学到的是最近一天价格最接近明天本质上在抄昨天而不是学到了走势。解决检查create_sequences的循环范围。正确做法是X包含第i到ilook_back-1天的值y取第ilook_back天的值两者在时间上严格错开。数据集划分强制按时间先后切前80%训练、后20%测试任何随机打散都要避免。我把划分代码固定写在create_sequences之后避免后续改动时无意引入shuffle。5.2 归一化还原后出现负数或量纲完全不对的预测值现象训练时loss很低但inverse_transform之后预测价格出现-1600元或几万元这种离谱数值。原因scaler在完整数据集上做了fit训练集和测试集的min/max不一致或者预测的是归一化后的值却直接当真实价格使用另一个常见原因是模型输出形状是(样本数,1)还原时忘了reshape。解决严格按只对训练集fit对测试集和预测集transform的顺序操作。每次调用inverse_transform前确保输入形状是二维(n,1)。我习惯把还原封装成一个函数训练完和测试完都走同一个入口避免两处逻辑不一致。5.3 loss变成NaN模型直接废掉现象前几个epoch的loss还是几十某一步突然变成nan之后再也回不来。原因梯度爆炸是头号嫌疑尤其在RNN上另一个隐蔽原因是数据里有空值或非数值字符串pandas读取时没有被转成float。解决先把df[open].astype(float)之前的原始数据做一次pd.isna检查过滤空行然后把Adam学习率从0.001降到0.0001跑一轮确认不再爆。如果还爆给优化器加clipnormtf.keras.optimizers.Adam(learning_rate0.0001, clipnorm1.0)它会截断过大的梯度向量是保命手段。5.4 每次训练结果差异很大固定seed也没用现象同一份数据跑两次训练曲线和测试指标都不一致甚至第一次MAE是2.0元第二次变成4.0元。原因TensorFlow 2.0的随机性来自多个层面Python的random、NumPy、TensorFlow各自维护随机状态只设置np.random.seed管不到tf的Op。解决脚本最开头统一设置三处随机种子。import os, random, numpy as np, tensorflow as tf os.environ[PYTHONHASHSEED] 0 random.seed(42) np.random.seed(42) tf.random.set_seed(42)这样设置之后两次训练的初始权重确定但在GPU上某些融合算子仍可能造成微小差异追求完全一致的复现需要连CUDA的deterministic模式一起开在CPU上这批设置基本够用。5.5 CPU环境训练慢epochs跑不完就放弃了现象LSTM模型一个epoch十几秒100个epoch要跑十几分钟等待过程中很容易误判模型不收敛而提前终止。原因TensorFlow 2.0默认Eager模式逐算子执行RNN/LSTM按时间步串行展开CPU上的线程配置不合理时利用率很低。解决先通过tf.config.list_physical_devices(GPU)确认本机有没有GPU没有时不要盲目加大units和look_back。把batch_size调小到16、units降到32训练时间可以缩到原来的四分之一还可以把epochs先设成20跑通流程确认能收敛后再加长。训练时频繁读写CSV和print也会拖慢用verbose0关掉每个epoch输出能省一点。6. 模型验证与下一交易日预测把归一化输出还原成真实股价6.1 用MAE和RMSE量化误差训练过程中已经监控了MAE但那个值是在归一化尺度上的真实价格尺度要大得多。测试集评估时把它还原成元才能直观知道模型预测开盘价平均偏差多少。RMSE则对极端偏差更敏感适合暴露偶尔预测错误的样本。pred model.predict(X_test) pred_inv scaler.inverse_transform(pred.reshape(-1, 1)) y_test_inv scaler.inverse_transform(y_test.reshape(-1, 1)) mae np.mean(np.abs(pred_inv - y_test_inv)) rmse np.sqrt(np.mean((pred_inv - y_test_inv) ** 2)) print(fMAE: {mae:.2f} 元, RMSE: {rmse:.2f} 元)从我的运行结果看look_back20时SimpleRNN的MAE大约在几十元量级LSTM在数据更多时略好一点。这里有一件更值得做的事把用昨天开盘价当今天预测的naive baseline也算出来只有当模型MAE明显低于这个baseline才说明循环网络真正学到了走势规律低于或持平就要回去调窗口和归一化。6.2 预测下一个交易日的开盘价用训练好的模型做单步预测只需取测试集结束后的最后look_back个真实开盘价归一化后reshape成(1, look_back, 1)喂给模型再把输出还原成价格。last_seq scaled_prices[-look_back:].reshape(1, look_back, 1) next_pred_norm model.predict(last_seq) next_pred scaler.inverse_transform(next_pred_norm.reshape(-1, 1)) print(f下一交易日开盘价预测: {next_pred[0][0]:.2f} 元)注意这里的下一交易日预测是基于单变量开盘价序列的模型输出不构成投资建议。实盘决策需要考虑的信息远超过一个LSTM的容量。这个预测值的边界要说清楚模型只用了开盘价的单变量序列没有成交量、大盘指数、行业轮动、消息面等任何外部信息对突发事件的抵抗能力约等于零。把它当作时间序列建模的练习结果没问题真要用在投资决策里得把这些局限算进去。从那以后我每拆一个时序项目都会把五件事在训练前强制走一遍检查时间顺序、确认标签窗口错位、只对训练集做fit、设置完整随机种子、算出naive baseline。这套习惯把我在RNN和LSTM项目里九成的返工都挡在了门外。这份资源代码量不大真正值钱的是这些细节反复验证出来的边界希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?