tushare+TensorFlow 2.0:RNN与LSTM股票开盘价预测实战

发布时间:2026/10/26 7:31:03
tushare+TensorFlow 2.0:RNN与LSTM股票开盘价预测实战

简介:面向金融时间序列分析与深度学习入门者,这份资源以贵州茅台为例,演示使用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不是SH600519

tushare是国内用得比较多的金融数据接口库,日线行情通过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_code='600519.SH', # 贵州茅台,沪市 start_date='20180101', end_date='20241231' ) df = df.sort_values('trade_date').reset_index(drop=True) df.to_csv('SH600519.csv', index=False)

这段代码里几个参数值得说明。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天到第i+look_back-1天的开盘价组成,标签是第i+look_back天的开盘价。

def create_sequences(data, look_back): X, y = [], [] for i in range(len(data) - look_back): X.append(data[i:i+look_back]) y.append(data[i+look_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, activation='tanh', return_sequences=False), tf.keras.layers.Dense(1) ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', metrics=['mae'] )

这里几个参数值得拆开讲。Input层的shape是(look_back, 1),也就是每个样本是一个长度look_back、每个时间步只有1个特征(开盘价)的序列。SimpleRNN层的units=64表示这层有64个隐单元,输出维度是64;activation默认用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, epochs=100, batch_size=32, validation_split=0.2, verbose=1 )

batch_size=32是比较常规的起点,样本量只有几千的量级,这个值能让每个epoch在几秒内完成。validation_split=0.2表示从训练数据尾部切出20%做验证,注意keras这里是从尾部切,不会打乱时间顺序,所以可以直接用。训练时重点盯两个指标:训练loss是否持续下降,验证loss是否在某轮后开始回头。一旦验证loss上升而训练loss还在降,就是过拟合信号。

训练跑完,把history里的loss和val_loss画出来是我必做的一步。

import matplotlib.pyplot as plt plt.plot(history.history['loss'], label='train loss') plt.plot(history.history['val_loss'], label='val 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, activation='tanh', return_sequences=False), tf.keras.layers.Dense(1) ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001), loss='mse', 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_sequences=True), tf.keras.layers.LSTM(32, return_sequences=False), tf.keras.layers.Dense(1) ])

return_sequences是循环层特有的参数,在多层层叠时是硬性要求:前一层不返回完整时间步序列,后一层就没有输入可吃。这个参数在SimpleRNN、LSTM、GRU上语义一致,报错信息也类似,是常见的翻车点。另外可以在LSTM层直接加dropout参数,比如LSTM(64, dropout=0.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到i+look_back-1天的值,y取第i+look_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跑一轮,确认不再爆。如果还爆,给优化器加clipnorm=tf.keras.optimizers.Adam(learning_rate=0.0001, clipnorm=1.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也会拖慢,用verbose=0关掉每个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(f'MAE: {mae:.2f} 元, RMSE: {rmse:.2f} 元')

从我的运行结果看,look_back=20时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项目里九成的返工都挡在了门外。这份资源代码量不大,真正值钱的是这些细节反复验证出来的边界,希望帮到你。

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