ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Keras搭建DNN/LSTM预测股票涨跌:从数据准备到回测验证

Keras搭建DNN/LSTM预测股票涨跌:从数据准备到回测验证 简介一份基于Keras搭建DNN与LSTM网络的股票涨跌预测实战课程设计资源适合机器学习初学者、数据科学相关专业学生完成课程作业或入门时间序列预测。资源包共5个文件包含3个Python脚本、1个Markdown说明文档和1个CSV数据集整体仅182KB结构紧凑。其中Python脚本分别对应数据获取、特征工程与模型构建CSV文件提供带日期、开盘价、收盘价、最高最低价及交易量的历史行情数据Markdown文档则说明运行流程与结果解读。整个项目围绕数据清洗、序列化处理、DNN与LSTM组合建模展开并涉及移动平均、RSI等技术指标计算可帮助理解神经网络处理金融时间序列的完整链路。目前已有256人学习下载适合希望动手实践Keras和LSTM、完成课程设计并提升数据分析与机器学习技能的学习者。需注意股市预测受多重因素影响模型结果仅供学习参考。1. 基于keras搭DNN/LSTM做股票涨跌预测先搞清能预测的是哪一段用keras同时搭DNN和LSTM来预测股票涨跌是课程设计里最常被点名的组合也是把python量化交易策略代码从“回测框架”往前推一步的最小闭环。方案本身不复杂拿一支股票的历史日线切出“过去20天、未来1天涨不涨”的样本分别喂给全连接网络和LSTM各自输出上涨概率。对课程设计和期末项目来说它最大的价值是把数据获取、特征构造、模型搭建、训练调参、回测验证串联成一条完整路径交得出代码和数据也交得出实验对比。接下来我按“拿数据→建模型→调参→踩坑→验证”往下走每个环节都给能直接跑起来的代码。2. 数据准备与标签构造拉数据、归一化、切窗口这一步决定模型“视力”先说明一个基调所有和预测质量相关的坑追到最后往往不是模型问题而是数据准备阶段埋下的雷。股票预测里样本本身就不独立同一只股票相邻两天行情高度相关这一条会直接影响后面验证集怎么切也会决定你的准确率是真实水平还是数字幻觉。2.1 用tushare拉日线数据免费接口够课程设计用了课程设计最常用的是tusharepip install tushare就能装。pro_bar接口能拿到带日期的OHLCV数据不用自己处理复权。下面的代码把贵州茅台从2018年至今的日线存成CSVimport tushare as ts import pandas as pd ts.set_token(你的token) # token在tushare官网个人主页 pro ts.pro_api() df ts.pro_bar(ts_code600519.SH, start_date20180101, end_date20231231, adjhfq) df.sort_values(trade_date, inplaceTrue) df.to_csv(stock_data.csv, indexFalse) print(df.head())这段代码做了三件事设置token并初始化接口抓取日线数据落盘。sort_values按交易日升序排列模型训练极度依赖时间顺序这一步不能省。to_csv存到本地后后续所有实验都从CSV重新读取避免反复请求接口触发频率限制。参数说明里最需要关注的是adjhfq。后复权价格会随分红配送持续调整长期训练不会因为除权除息出现假下跌。如果只想跑通流程用不复权也能出数但答辩时容易被问复权问题直接说“我用后复权避免分红导致的跳空”是明显的加分项。数据列里至少要有 trade_date、open、high、low、close、vol、amount其中最高最低价和成交量会和收盘价一起作为特征输入close列单独用来构造涨跌标签。2.2 特征选择与归一化MinMaxScaler要在训练段上fit数据落地后先做特征子集然后归一化。股票日线的价量特征量纲差异很大股价可能是几十到上千元成交量可能是几十万手直接喂网络绝对值大的特征会主导梯度方向所以必须压缩到0到1之间。import numpy as np from sklearn.preprocessing import MinMaxScaler df pd.read_csv(stock_data.csv, parse_dates[trade_date]) df.sort_values(trade_date, inplaceTrue) features [open, high, low, close, vol] raw_feat df[features].values raw_close df[close].values scaler MinMaxScaler(feature_range(0, 1)) scaled_feat scaler.fit_transform(raw_feat)这段代码的逻辑是把需要用到的5列整成numpy二维数组用MinMaxScaler做归一化。这里的fit_transform是演示写法严格讲应该只对训练段fit测试段用同一组参数做transform留到第5章讲数据泄漏时展开。raw_close单独存了一份因为构造标签时比较的是原始收盘价而不是归一化之后的值。MinMax是单调变换拿两者比较结果一样但直接拿scaled比较答辩时容易被追问属于不必要的风险。窗口长度是另一个值得纠结的点。常见做法是20天约一个自然月能覆盖短中期形态。5天窗口噪声极大准确率很难突破0.5260天窗口样本量大幅缩水LSTM在这种数据量下几乎必过拟合。我用过的对比大致是这样窗口长度样本量学到的东西主要问题5天多短期动量噪声大准确率接近抛硬币20天中月度级别形态相邻样本重叠切分时不能洗牌60天少季度趋势样本太少LSTM严重过拟合2.3 用滑动窗口构造样本20天输入1天标签模型要学的是“过去20个交易日的情况决定下一个交易日涨不涨”。这个任务本质是二分类标签只有0和1不需要预测具体涨跌幅度。WINDOW 20 FUTURE 1 X, y [], [] for i in range(WINDOW, len(scaled_feat) - FUTURE): X.append(scaled_feat[i - WINDOW:i]) # 形状 (20, 5) y.append(1 if raw_close[i FUTURE] raw_close[i] else 0) X np.array(X) # (样本数, 20, 5) y np.array(y) # (样本数,)切窗逻辑是从第20天开始取它之前20天的5维特征作为X标签看第i1天的收盘价比第i天高还是低高记1低记0。如果想改成“3日预测”把FUTURE 3即可后面的回测评估口径要跟着变。得到的X形状是三维的LSTM直接拿来用。DNN需要把它压平成(样本数, 100)这一步放在第3章建模之前。还有一个天然的小尾巴数据末尾最后FUTURE天因为没有未来价格没法构造标签循环会自动排除这是正常的不需要特意处理。3. DNN和LSTM网络搭建两个keras模型跑通再调参装好tensorflow 2.x之后keras就随包装好了直接from tensorflow import keras即可。课程设计里用tf.keras写最省事编译、训练、保存模型全在一套API里。两个网络解决的是同一个二分类问题但学习方式完全不同DNN吃的是摊平后的截面特征LSTM按时间顺序逐窗口读入。3.1 先跑一个DNN把20×5压平成100维向量DNN的输入是“过去20天、每天5个特征”的扁平化结果形状是(None, 100)。用三层全连接加Dropout搭一个基线网络from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Dense, Dropout, Input import numpy as np X_dnn X.reshape(len(X), -1) # (样本数, 100) model_dnn Sequential([ Input(shape(WINDOW * len(features),)), Dense(64, activationrelu), Dropout(0.2), Dense(32, activationrelu), Dropout(0.2), Dense(1, activationsigmoid) ]) model_dnn.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model_dnn.summary()逻辑说明sigmoid输出一个0到1的概率值正好对应“上涨概率”。用binary_crossentropy作为loss是因为标签就是0/1二分类这是和回归任务最本质的区别。Dropout(0.2)在每一层全连接后面随机丢弃20%的神经元防止网络把训练样本的噪声细节背下来。参数上第一层64、第二层32是一个课程设计足够用的配置。如果你的数据量很大可以升到12864数据量小就保持3216否则收敛极慢且抖动明显。optimizeradam是sigmoid二分类最稳妥的默认选择学习率没写就是默认的0.001后面第4章会专门讲什么时候需要动它。3.2 LSTM模型return_sequences从True到FalseLSTM的输入保持三维(样本数, 20, 5)时间步是20天每个时间步的特征维度是5。它和DNN最大的区别是DNN把100个数值一次性看全LSTM按顺序一天一天读记忆会跨时间步传递。from tensorflow.keras.layers import LSTM model_lstm Sequential([ Input(shape(WINDOW, len(features))), LSTM(50, return_sequencesTrue), Dropout(0.2), LSTM(50, return_sequencesFalse), Dropout(0.2), Dense(1, activationsigmoid) ]) model_lstm.compile(optimizeradam, lossbinary_crossentropy, metrics[accuracy]) model_lstm.summary()这里最需要理解的是return_sequences参数。第一层LSTM设True意思是把每个时间步的隐藏状态都输出给下一层相当于输出一个完整的序列第二层设False只输出最后一个时间步的隐藏状态这个状态浓缩了整个20天窗口的信息再接全连接层做分类。如果两层都设False第二层会报shape错误如果两层都设True最后接Dense时维度对不上需要手动展平。单元数50是一个平衡点。LSTM的参数量大约是4 * (input_dim units) * units级别50个单元参数量已经不小再往上加单元数训练时间成倍增长而准确率提升往往不到1个百分点。课程设计阶段5050是性价比最高的配置。3.3 训练配置shuffleFalse和validation_split的坑模型搭完后开始训练。这里有个容易翻车的细节keras的fit默认在每轮epoch之前打乱训练数据顺序对股票数据这是致命的。history_dnn model_dnn.fit( X_dnn, y, # 演示用正式跑换成训练集切片 epochs30, batch_size64, validation_split0.2, shuffleFalse, # 股票数据按时间排列不能乱洗 verbose1 )shuffleFalse的意义在于股票样本是时间序列相邻样本高度相关。如果每轮都打乱网络在训练时会反复遇到“上一轮见过它邻居”的情况验证集和训练集之间的独立假设被破坏准确率虚高。课程设计里经常有人忽略这个参数导致验证准确率0.58实盘却是0.48。validation_split0.2也是需要警惕的它是在训练集末尾直接切出20%作为验证集但由于我们没有洗牌这20%其实是时间上最晚的数据天然避免了一部分泄漏。但它仍不是真正的样本外测试只能用来观察训练曲线是否平稳。batch_size64在几千样本量下够用。样本数少于5000时batch_size降到32梯度更新更频繁收敛更稳样本数过两万batch_size提到128能明显加快训练。epochs给30只是上限实战里很少能跑满30通常会搭配第4章的EarlyStopping提前停下。4. 训练过程与过拟合控制早停、学习率、类别不平衡三个调参点模型不是搭完就结束了训练阶段才是课程设计真正花时间的地方。DNN和LSTM在这三个问题上表现不同但调参思路一致先保证收敛再看过不过拟合最后处理样本不平衡。4.1 EarlyStopping和ReduceLROnPlateau给训练装一个自动刹车课程设计里最常犯的错误是把epochs设成50或100直接跑完。网络在前20轮就已经收敛后面30轮就是纯过拟合验证loss一路上扬。正确做法是用回调函数让训练自己停下来。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping( monitorval_loss, patience8, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience3, min_lr1e-5 ) history_dnn model_dnn.fit( X_dnn, y, epochs50, batch_size64, validation_split0.2, shuffleFalse, callbacks[early_stop, reduce_lr], verbose1 )EarlyStopping监视验证集loss连续8轮没变好就停止训练同时restore_best_weightsTrue会把网络权重回滚到验证loss最低的那一轮。ReduceLROnPlateau是学习率衰减器验证loss连续3轮不下降就把学习率减半最低减到1e-5。两者配合的效果是前期用0.001快速下降后期用更小的学习率在局部最优点附近精细搜索。这里的参数选择的经验是patience不能设太小股票数据噪声大验证loss连续两轮不降是常态立刻停掉会欠拟合但也不能超过10否则早停失去意义。8是我的常用起点。4.2 学习率与优化器的选择Adam默认值不总是最优adam的默认学习率0.001在多数场景表现不错但股票涨跌预测的标签噪声极大loss曲面非常崎岖0.001往往导致训练初期loss剧烈震荡怎么看都像没收敛。from tensorflow.keras.optimizers import Adam opt Adam(learning_rate0.0003) model_lstm.compile(optimizeropt, lossbinary_crossentropy, metrics[accuracy])把学习率从0.001降到0.0003后训练曲线明显平滑验证集准确率能稳定提升。这是我对时间序列预测类任务最常用的配置不是模型不行是学习率步子太大在loss曲面边缘反复横跳。另一个替代方案是换rmsprop它对RNN类模型更友好但收敛速度通常比adam慢课程设计时间紧张时不推荐。判断学习率是否合适的技巧打印前5轮loss。如果loss在0.69附近震荡二分类的随机猜测loss是-ln(0.5)说明学习率太高模型不学如果loss每轮只降一点点且验证集完全不变说明学习率太低训练白跑。4.3 涨跌样本失衡用class_weight硬掰回来股票日线数据里涨跌天数天然不平衡。拉长到五年周期上涨天数通常占55%上下下跌占45%左右这个比例看起来还能接受但在某些震荡行情段局部可能出现“70%上涨、30%下跌”的严重失衡。模型学到的策略变成“永远预测上涨”准确率虚高到0.70实际毫无意义。from sklearn.utils.class_weight import compute_class_weight classes np.array([0, 1]) weights compute_class_weight(balanced, classesclasses, yy) class_weight {0: weights[0], 1: weights[1]} history_lstm model_lstm.fit( X, y, epochs50, batch_size64, validation_split0.2, shuffleFalse, class_weightclass_weight, callbacks[early_stop, reduce_lr], verbose1 )compute_class_weight会根据y中0和1的占比自动计算权重样本量少的类别获得更高权重。传给fit的class_weight参数后loss计算时会按权重放大少数类的误差逼着网络不只学“多数类”这一条路。需要注意权重矫正后预测概率不再是真实概率直接拿0.5作阈值决策可能不划算。此时可以降低阈值到0.4甚至0.35让模型在“少预测”和“猜得准”之间重新平衡。这一步属于策略层调整课程设计里能做到这里已经比绝大多数只跑通的作业高一个档次。5. 避坑指南5个踩过才回头的低级错误这一章全是血泪经验。下面五个错误我在实际跑数据时全踩过前三个是数据泄漏层面后两个是训练细节层面每一个都直接决定最终数字能不能看。5.1 train_test_split默认shuffle把未来数据泄进训练集现象用sklearn.model_selection.train_test_split切分数据后验证准确率0.58LSTM甚至跑到0.62换个时间段重跑就掉回0.50。原因train_test_split默认shuffleTrue会把时间序列打乱。股票相邻样本高度相关2019年的样本和2020年的样本混进了同一个集合网络学到的是“时间相近”的信号而不是真正的涨跌规律。解决永远用切片切分按时间顺序取前80%为训练集后20%为测试集。代码只有两行split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:]切完后检查一下X_train[-1]和X_test[0]的日期确保两者之间没有时间重叠。这一步是回测可信度的底线。5.2 MinMaxScaler在全量数据上fit测试集被“预看”现象归一化时直接在全部数据上scaler.fit_transform(raw_feat)模型测试集准确率看起来很好但实盘崩盘。原因MinMaxScaler需要知道数据的最小值和最大值才能做缩放。如果它在全量数据上fit测试集的价格区间已经提前暴露给了训练过程。测试集中的极端价格不会被真实“看到”但这个信息先被缩放器记录再在训练时通过特征分布间接参与了模型参数更新。解决先切分数据再在训练集上fit测试集只做transform。scaler MinMaxScaler() scaled_train scaler.fit_transform(raw_feat[:split]) scaled_test scaler.transform(raw_feat[split:])顺便一提tuhare拉的数据里偶尔会有停牌日或极端跳空fit阶段看下min和max值如果某天价格是0或负值大概率是脏数据先过滤再缩放。5.3 标签构造错误把归一化后的close拿来做涨跌比较现象训练过程完全正常但报告里的涨跌比例严重偏离直觉比如上涨占比超过70%。原因有人在构造y时写的是y.append(1 if scaled_close[i1] scaled_close[i] else 0)也就是拿归一化后的值做比较。MinMax缩放是单调变换正常情况下不影响比较结果但一旦原始数据里有极端值或缺失填充缩放后的序列可能在一些位置产生平台区间导致标签失真。解决始终保留一份原始close数组专门用于标签构造归一化只用于特征输入。比较用原始值输入用缩放值两者分开逻辑清晰答辩也不会被问倒。5.4 LSTM窗口开太长样本量撑不住现象把WINDOW设成60天LSTM参数量增大训练时间翻倍验证准确率反而比20天窗口低2到3个百分点。原因窗口越长每个样本覆盖的时间段越长有效样本数急剧下降。假设五年日线约1200个交易日20天窗口能切出约1180个样本60天窗口只剩下约1140个样本看似差异不大但LSTM需要学习的参数随着输入步长增加样本量不足以支撑过拟合是必然结果。解决课程设计阶段固定20天窗口最安全。如果你非要用60天窗口必须先扩充数据源比如拿多只股票拼接或者改用tushare的周线数据做降频让每个时间步的信息密度更高。5.5 loss在0.69附近横盘不是模型问题而是学习率问题现象训练10轮后loss仍然在0.69附近震荡准确率一直卡在0.50左右第一反应是模型写错了。原因0.69是二分类随机猜测的交叉熵loss卡在这里说明模型根本没在学。常见原因有两个一是学习率太高导致loss在陡峭区域反复横跳二是输入特征没有归一化直接被网络吞掉。解决先把学习率降到0.0003重跑一次再看前5轮loss是否开始缓慢下降。如果依然横盘打印X_train的数值范围确认特征都在0到1之间别把原始价格数据直接喂进LSTM。这两个检查做完90%的“模型不收敛”问题都能解决。6. 用样本外回测验证模型判断值不值得投入的最后一道闸6.1 按时间顺序切样本外用最后20%数据模拟“未来”训练完成后最自然的问题是“这个模型放在今天值不值得信”。答案是看它在从未参与训练的那段时间里的表现。我一般会把数据集按8:2切成训练段和样本外测试段训练段内部再分训练和验证测试段只跑一次不回头调参。split int(len(X) * 0.8) X_train, X_test X[:split], X[split:] y_train, y_test y[:split], y[split:] # LSTM 直接用三维输入 lstm_prob model_lstm.predict(X_test) # DNN 需要先 reshape 成二维 X_test_dnn X_test.reshape(len(X_test), -1) dnn_prob model_dnn.predict(X_test_dnn)这段代码的输出是两个概率数组形状都是(len(X_test), 1)。回测时可以用0.5作为阈值把概率转成0/1预测也可以直接拿概率值比较两个模型的排序能力。特别提醒测试段应该只被评估一次。如果根据测试结果回去调学习率、改窗口、换层数然后再测测试段就已经被污染了最终报告的AUC会比真实水平偏高。6.2 用AUC和盈亏比而不是只盯准确率课程设计报告里最常见的败笔是只写“验证准确率0.55”。股票涨跌预测里准确率是最容易被欺骗的指标样本不平衡时它虚高纯随机时它也有0.50区分不出模型是否真的学到规律。我习惯用三个指标一起评价from sklearn.metrics import roc_auc_score, accuracy_score dnn_pred (dnn_prob.flatten() 0.5).astype(int) print(DNN AUC:, roc_auc_score(y_test, dnn_prob)) print(DNN ACC:, accuracy_score(y_test, dnn_pred))指标含义判断标准AUC随机抽一对涨跌样本模型给上涨样本更高分的概率0.5是随机0.55以上算有微弱信号准确率预测正确的比例参考值不能单独依赖盈亏比信号触发后的平均盈利除以平均亏损大于1.1才值得投入我的个人判断标准是AUC低于0.52的模型基本等于白做高于0.55已经具备课程设计的展示价值盈亏比要到1.1以上才值得往策略方向深入。最早做这个题目时我在准确率0.56上自嗨了三天直到用样本外算AUC才发现模型排序能力约等于随机那种失落也让我记住了验证指标该怎么选。后来每次实验只做两个记录第一是样本外AUC第二是测试段盈亏比其余指标都只作参考。希望帮到你。本文还有配套的精品资源点击获取
返回列表