ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

LSTM时间序列预测实战:双色球数据建模与毕业设计全解析

LSTM时间序列预测实战:双色球数据建模与毕业设计全解析 简介时间序列预测是深度学习中极具工程实践价值的应用方向从股票行情到气象预报LSTM凭借门控机制有效建模序列中的长期依赖关系成为处理时序数据的核心技术之一。当前许多研究者和开发者将LSTM应用于各类随机序列的建模与分析探索其在复杂场景下的能力边界。本文以经典的双色球历史数据作为实验对象系统性介绍从数据清洗、滑窗构造、归一化处理到LSTM模型设计、训练调参与结果评估的完整流程。详细讲解红球与蓝球拆分建模的思路并对比随机基线与历史频率基线的评估方法帮助读者理解在低信噪比数据上如何科学衡量模型效果。无论是准备毕业设计还是希望快速上手时间序列预测项目都能从中获得可复现的代码结构与实践经验。1. 毕业设计选题为什么双色球预测LSTM是一个能打能讲的题目每年毕业设计选题季计算机专业的同学基本都会遇到同一个情况题目太虚的写不出东西题目太实的没有创新点题目太难的三个月做不完。我当时在选题表上看到基于深度学习的LSTM算法双色球预测这个方向时第一反应是——这玩意儿真的能预测吗会不会被答辩老师认为是搞迷信但真正做下来之后我发现自己想错了。这个题目被选作毕业设计靠的是它独特的教学价值而不是预测准确率本身。先说清楚双色球是典型的随机事件任何模型都不可能从中稳定找到中奖规律这一点在开题报告里就必须明确表态。但正因为它是随机序列反而让LSTM这种时间序列模型有了一个非常标准的实验场你可以在完全公开的数据上验证模型的效果、对比不同网络结构的差异、讨论数据量对预测结果的影响。这类课题的评分核心在于你的模型为什么这样设计、数据是怎么处理的、结果该怎么解读而不是你预测出了几等奖。从难度梯度上看这个题目也非常适合本科毕业设计。它的技术栈是Python TensorFlow/Keras Pandas Matplotlib全部是深度学习入门必备工具数据获取不需要爬复杂的网站历史开奖数据在公开渠道都有模型结构不用自己设计复杂的网络标准的LSTM堆叠就能跑出结果。相比基于深度学习的某某图像识别这类动辄需要大规模数据集和高性能显卡的题目双色球预测项目的数据量只有几千条普通笔记本CPU就能完成训练这大大降低了环境准备的门槛。另一个容易被忽视的点是这个题目天然自带完整链路。从数据爬取、数据清洗、特征工程、模型搭建、训练调参到结果可视化和论文写作每一个环节都有明确的任务产出学生不会出现没东西可写的困境。我给自己的要求是把这个链路彻底跑通不能只停留在跑通demo的层面而是要能回答每一步为什么这么做。接下来这篇博文我从头到尾梳理一遍这个项目的完整实现过程包括数据怎么处理、模型怎么设计、训练有哪些坑、论文怎么组织以及最终交付的代码结构应该长什么样。如果你也在准备这个题目或者想找一个能快速上手的时间序列预测练手项目这篇文章应该能帮你节省不少试错时间。2. 双色球数据到底该怎么处理先搞清楚你的数据长什么样2.1 数据源的获取与基础清洗双色球数据本质上是一个典型的多变量时间序列。每期开奖产生7个数字6个红球范围01-33和1个蓝球范围01-16红球不重复且按大小排序公布。数据源一般从公开的彩票历史开奖信息接口获取保存成CSV格式字段包括期号、开奖日期、红球1到红球6、蓝球。拿到原始数据后第一件事不是处理格式而是观察数据分布。我当时把1993年至今双色球游戏本身从2003年开始销售的数据拉下来后先做了一轮基础检查期号是否连续有没有遗漏期日期字段是否完整有没有重复记录红球号码是否都在01-33范围内蓝球是否都在01-16范围内每一期的6个红球是否互不重复。这些检查看着琐碎但在实际写代码时需要特别注意。比如有的数据源会把开奖日期格式写成2023-01-03和2023/01/03混排有的在早期年份会把号码写成1而不是01如果不统一格式化后面特征工程时会出现难以排查的类型错误。数据清洗完成后还需要思考一个关键问题数据从中奖公告页面拿到的原始格式和模型需要的格式之间差了一个滑窗操作。这一步是所有时序预测任务的核心放在下一节讲。2.2 滑窗构造把开奖序列变成监督学习样本LSTM处理时间序列时不能直接把这一期的号码作为输入直接预测下一期的号码。模型的输入必须是一个固定长度的历史窗口输出是窗口之后那期的目标值。这种从纯序列到监督学习样本的转换在时间序列领域叫滑窗sliding window构造。假设我们用前10期开奖数据预测第11期那么数据集的组织方式如下输入X第1期到第10期的全部开奖号码输出y第11期的红球和蓝球。接着窗口滑动一期输入变成第2期到第11期输出是第12期以此类推。理论上窗口越宽模型能看到的历史规律越多但双色球总共也就3000多期数据窗口太大会导致训练样本数量急剧下降。window_size10是我实测下来一个比较合适的平衡点既能保留一定的历史长度又不会把样本量砍得太狠。在实现滑窗时另外一个需要处理的问题是红球和蓝球要不要放在同一个模型里一起预测我见过不少实现选择把红球6个号码和蓝球1个号码拼成7维向量直接作为LSTM的输出。这样做的好处是代码简单模型输出层只需7个神经元劣势是红球和蓝球在语义上完全独立蓝球是从16个数字里选1个红球是从33个数字里选6个且不能重复它们的内在分布特征差异很大强行绑在同一个模型里其实是不合理的。我最终采用的是分而治之的思路红球和蓝球各建一个LSTM子模型共享相同的输入窗口但输出层和损失函数分开。在每个子模型内部红球输出6个值蓝球输出1个值。这样训练时各子模型可以针对自身的数据分布优化评估时也能分别统计命中情况毕业答辩时讲这个设计理由也非常加分。2.3 数据归一化不归一化的后果你真的体验过吗时间序列输入到LSTM之前一个很容易被忽略但影响巨大的操作是归一化。双色球号码的取值范围是1到33量级本身不大很多同学觉得这点数值直接喂给网络也没问题于是跳过归一化。我在初版实现里也这么干过结果训练过程中的损失曲线在后期出现明显的震荡验证集上预测出来的数字经常超出合法范围比如红球预测出37、蓝球预测出18。归一化在这里的真正作用是配合LSTM的激活函数。LSTM内部大量使用tanh激活函数输出范围是[-1,1]如果你的输入数值是不归一化的33经过tanh后已经被压到了接近饱和区梯度在反向传播时非常容易消失。所以标准的做法是使用MinMaxScaler把所有号码压缩到[0,1]区间训练完成后再用inverse_transform把预测结果还原成真实的号段。关于MinMaxScaler有一个细节必须使用训练集的scaler去transform验证集和测试集而不是对全量数据统一fit。否则测试集的信息会被泄露到训练过程中导致指标虚高这在论文写作中被认为是不严谨的做法。我当时在答辩前自查时专门核对了这一点确认所有评估环节都用的是训练集得到的scaler才敢在论文里写评估数据。3. LSTM模型设计堆几个LSTM层输出层怎么定全靠实验说话3.1 为什么是LSTM而不是RNN或者Transformer既然是做毕业设计选择LSTM需要有一个能说服答辩老师的理由。我在开题报告中把LSTM和传统RNN做了对比RNN在处理长序列时存在梯度消失问题而LSTM通过遗忘门、输入门、输出门三个门控机制让信息可以选择性地跨时间步传递更适合捕捉数据中潜在的长期依赖关系。那双色球数据有这个长期依赖吗严格说开奖号码是独立随机事件理论上模型捕捉不到规律。但LSTM在随机序列上仍然能学到一些统计特征比如号码分布的边缘概率、冷热号的变化趋势等。我在论文里是这样措辞的本项目尝试从历史数据中挖掘统计层面的相关性并检验LSTM对低维随机序列的拟合能力而非试图寻找确定性的中奖规律。这个表述既如实反映了研究性质又体现了你对所使用方法的理解深度。3.2 网络结构两层LSTMDense的经典组合我的最终模型结构是一个经典配置在很多时序预测项目中都验证过稳定效果输入层形状为(batch_size, window_size, feature_dim)其中window_size10feature_dim76个红球1个蓝球进行一维嵌入后输入。第一层LSTM128个单元return_sequencesTrue保证输出仍保留时间步维度。第二层LSTM64个单元return_sequencesFalse只输出最后一步的状态作为全连接层的输入。Dropout层rate0.2防止过拟合。Dense层红球子模型输出6个神经元激活函数用linear蓝球子模型输出1个神经元同样用linear。这里有两个细节值得展开。第一LSTM的return_sequences这个参数。第一层如果不设True它只会输出最后一个时间步的隐藏状态序列的时间信息被压缩成一个向量后续层就无法再沿时间轴建模。而双层LSTM堆叠的意义在于第一层在时间步级别提取特征第二层把整个序列信息聚合为一个全局表示这种层次化的特征提取方式在序列任务中通常优于单层。第二激活函数选择linear而不是sigmoid或tanh。因为输出是回归值经过归一化后的号码我期望模型输出的是一个连续的实数再用四舍五入转换成合法的号码。如果用sigmoid限制在[0,1]范围内也可以但因为存在边界饱和问题训练后期的梯度更新会非常缓慢。用linear反而让损失函数MSE的梯度直接传递收敛速度明显更快。我实测下来两层LSTM12864在双色球数据上已经能接近模型能力的上限继续增加到3层或加大到256单元验证集指标几乎不变训练时间却成倍上涨。所以在毕业设计场景里我建议优先用一个结构简单但训练充分的模型把时间花在特征处理和结果分析上而不是追求花哨的网络结构。3.3 损失函数与评估指标MSE到底够不够回归任务最常用的损失函数是MSE均方误差用在双色球预测上的意义是衡量预测号码与真实号码的数值偏差。但MSE有一个问题它把预测值为17真实值为18和预测值为17真实值为25看成不同的错误程度前者损失小很多。而从预测的角度看只要没命中差1和差8没有本质区别——都是没中。所以除了MSE之外我加了另一个维度的评估指标命中率。具体做法是把模型输出的连续值四舍五入取整然后判断每个号码是否落在真实开奖号码集合中统计测试集上的命中数量。这种指标的优点是直观、可向非专业人解释缺点是模型输出的舍入处理会折断梯度所以它只作为评估指标不参与训练时梯度的计算。训练时我用的是Adam优化器学习率初始设为0.001并配合ReduceLROnPlateau回调当验证集损失连续5轮不下降时学习率乘以0.5。最终模型大约在第80个epoch趋于收敛验证集MSE在0.02左右。如果你复现时发现收敛慢首先要检查是不是学习率太大导致loss震荡其次看数据归一化有没有做对。4. 训练过程中的关键细节与调参心得4.1 数据集划分不能按比例随机切必须按时间切这个点非常关键几乎每年都有同学踩坑。做图像分类时我们可以把照片随机打乱按7:2:1分成训练集、验证集、测试集但时间序列数据绝对不能这么干。如果我把2018年的数据放进训练集、把2015年的数据放进测试集模型相当于用未来的数据预测过去训练时信息已经泄露测试指标虚高得毫无意义。正确的做法是按时间顺序切割前80%的期数作为训练集接下来的10%作为验证集最后10%作为测试集。这样测试集中的每一个样本其输入窗口中的所有历史期都在训练数据之后模拟的是真正用已知预测未知的场景。我当时用的总期数是2860期不含早期数据质量不高的部分训练集2288期验证集286期测试集286期。滑窗之后训练样本量大约是2000多组这个体量对LSTM来说偏小但配合Dropout和早停法模型没有出现严重的过拟合。需要注意的是这里的数据量指的不是原始期数而是滑窗后生成的样本对数两者差距很大写论文时要说清楚。4.2 早停与模型保存不要手动盯loss让代码帮你决定训练神经网络时一个朴素的做法是设定固定的epoch数跑完200轮取最后一轮的模型。但这一步在小数据集上容易出问题模型可能在70轮时已经达到验证集最优后面的训练只是在过拟合训练集验证损失不降反升。我用EarlyStopping回调解决这个问题。核心参数是monitorval_loss和patience10意思是当验证集损失连续10轮没有降低到新的最低值时训练自动停止并恢复最佳权重。ModelCheckpoint回调同时会把验证集损失最优的那次模型参数保存为HDF5文件方便后续加载测试。这两个回调配合使用基本不需要人工介入判断什么时候该停。在训练代码里还有一个容易忽略的操作需要在每个epoch结束后对打乱的batch训练数据重新洗牌。LSTM对数据顺序敏感如果不打乱batch模型可能学到的是期号相邻的样本被连续训练的虚假顺序。我在每个epoch开始前用np.random.shuffle对训练集的索引做一次随机排列保证每个batch内的样本分布足够随机。4.3 训练过程中碰到过的两个具体问题第一个问题是损失值一开始就非常低低到让我怀疑模型写错了。排查后发现是因为输出层的bias初始化恰好让所有预测值偏向训练集标签的均值附近MSE天然就低。这个问题的本质是模型还没有学到任何特征只是输出均值就取得了低的初始损失。解决方法是观察训练过程中的预测输出而不是只看loss数值在第10个epoch左右把模型跑出来的预测值和真实值打印对比一下确认模型真的在学着改变而不是学会偷懒。第二个问题是红球和蓝球子模型分开训练后蓝球模型的损失波动明显比红球大。原因是蓝球取值范围只有1到16训练样本中蓝球每个号码的分布相对稀疏模型容易受到个别异常样本的干扰。后来我给蓝球模型单独设置了更高的Dropout比例0.3并增加了一倍的训练轮次上限波动得到明显缓解。5. 结果评估与可视化论文的实验与结果章节应该怎么写5.1 怎么设定评价预测效果的基准线很多做类似题目的同学会在论文里写模型预测准确率达到百分之多少但面对彩票数据时这个表达是有争议的。更稳妥、更学术的做法是先设定一个基准线再把模型的效果和基准线对比。合理的基准线有两种。第一种是历史频率基线统计训练集中每个红球号码出现的频率预测时直接把出现频率最高的6个红球和1个蓝球作为预测结果。第二种是随机基线用随机数生成器从合法范围内随机选取号码作为预测结果。把LSTM模型的命中率与这两个基线放在同一个表里对比如果LSTM略优于随机基线可以说模型捕捉到了某些统计层面的信号如果差异不大也要如实说明——这本身就是对这个课题研究价值的一种客观回答。我在测试集上跑出来的结果是LSTM的红球平均命中数为0.87个/期蓝球命中率大约是6.4%历史频率基线红球命中数为0.83个/期蓝球命中率约6.1%两者差异很小。这个结果说明LSTM在这个任务上没有表现出超越基线统计方法的优势。在论文里我把这个结论作为重要章节来写从学术角度看反而让课题更加严谨可信。5.2 可视化图表怎么做最能让答辩老师满意论文和答辩PPT中的可视化是整个项目的门面。我建议至少画四类图第一类是训练过程损失曲线图。横轴epoch纵轴loss同时画训练集和验证集两条曲线直观展示模型收敛情况和是否过拟合。这张图放在模型训练章节用来支撑你选择epoch数和早停策略的依据。第二类是预测值与真实值的对比散点图。选取测试集最后100期把红球的预测结果和真实结果用不同颜色的散点画在同一个坐标系中横轴为期号纵轴为号码值。即使预测效果不理想这张图也能展示模型输出在合法范围内波动这一事实说明网络结构本身没有问题。第三类是命中数分布的直方图。统计测试集每一期的红球命中数量0到6个分布对比随机基线的命中分布。这张图最能直观说明模型能力的边界。第四类是热力图。在33x33的矩阵红球或者16x16的矩阵蓝球上画出预测号码分布的热力图能看出模型预测结果是否存在偏好性。比如当前模型对号码1-10的预测频率明显偏高说明数据不平衡的影响被模型学到了这也是一个值得在论文中讨论的现象。5.3 论文中的结论部分别写预测很准毕业设计论文是学术写作不是软文。结论部分要围绕三点来写完成了什么、发现了什么、局限在哪里。完成了什么指的是搭建了一个完整的数据处理LSTM训练评估的流程发现了什么指的是模型在双色球预测任务中与随机基线无明显差异说明该数据本身的可预测性极低该结果可作为随机序列建模的参考案例局限在哪里指的是数据量小、没有引入外部特征、LSTM对独立同分布数据的学习能力有限等。这样写既不会让人觉得你在夸大结果又能体现你对深度学习模型的本质理解。答辩老师最怕学生把预测彩票说出花来你用客观、严谨、实验驱动的方式把结论呈现出来反而容易拿高分。6. 核心代码拆解把完整流程变成能直接跑的工程6.1 项目文件结构与运行环境在做毕业设计时提交的不仅仅是代码还应该有一个清晰的项目结构方便评审老师快速复现。我的项目按下面的方式组织lstm_ssq/ ├── data/ │ ├── raw_ssq.csv # 原始开奖数据 │ └── processed_ssq.npz # 清洗并滑窗后的numpy数据 ├── src/ │ ├── data_preprocess.py # 数据清洗与滑窗构造 │ ├── train.py # 模型训练主脚本 │ ├── evaluate.py # 测试集评估与可视化 │ └── model.py # LSTM网络结构定义 ├── output/ │ ├── model_red.h5 # 红球模型 │ ├── model_blue.h5 # 蓝球模型 │ └── figures/ # 训练曲线、预测对比图等 ├── requirements.txt └── README.md运行环境是Python 3.8 TensorFlow 2.10 Keras Pandas scikit-learn Matplotlibrequirements.txt里要固定版本号。当时踩过一个坑是TensorFlow 2.10以上版本在Windows下的GPU配置比较复杂而CPU版本同样可以用不到3分钟跑完一个epoch总共100个epoch以内就能完成训练所以毕业设计环境下直接用CPU版本完全够用不必在GPU环境上浪费时间。6.2 数据预处理核心代码下面是数据清洗和滑窗构造的核心代码我加了详细注释。在实际使用时需要把原始CSV的列名和你的数据源对齐其他部分基本可以通用。import pandas as pd import numpy as np from sklearn.preprocessing import MinMaxScaler def load_and_clean_data(csv_path): # 读取原始数据 df pd.read_csv(csv_path) # 统一列名期号、日期、红球1-6、蓝球 df.columns [issue, date, r1, r2, r3, r4, r5, r6, blue] # 去掉有缺失值的行 df df.dropna().reset_index(dropTrue) # 把号码统一格式化为两位字符串避免 1 和 01 混用 for col in [r1, r2, r3, r4, r5, r6, blue]: df[col] df[col].apply(lambda x: str(x).zfill(2)) return df def create_sliding_window(data, window_size10): 输入 data: shape (样本数, 7) 的numpy数组每行是[红球1..6, 蓝球] 返回 X: shape (样本数-window_size, window_size, 7) y: shape (样本数-window_size, 7) X, y [], [] for i in range(len(data) - window_size): X.append(data[i:i window_size]) y.append(data[i window_size]) return np.array(X), np.array(y) if __name__ __main__: df load_and_clean_data(data/raw_ssq.csv) # 提取号码列并转为float类型数组 feature_cols [r1, r2, r3, r4, r5, r6, blue] raw_data df[feature_cols].astype(float).values # 归一化用训练集部分的scaler scaler MinMaxScaler() # 先取前80%作为训练集进行scaler fit注意滑窗后再切也可以 # 但用样本前的数据fit更保险避免用测试集信息影响scaler train_size int(len(raw_data) * 0.8) scaler.fit(raw_data[:train_size]) scaled_data scaler.transform(raw_data) X, y create_sliding_window(scaled_data, window_size10) # 再按时间顺序切分训练集/验证集/测试集 total_samples len(X) train_end int(total_samples * 0.8) val_end int(total_samples * 0.9) X_train, y_train X[:train_end], y[:train_end] X_val, y_val X[train_end:val_end], y[train_end:val_end] X_test, y_test X[val_end:], y[val_end:] np.savez_compressed(data/processed_ssq.npz, X_trainX_train, y_trainy_train, X_valX_val, y_valy_val, X_testX_test, y_testy_test)这里有一个细节值得强调scaler的fit操作放在构建滑窗之前的训练集原始序列上而不是放在滑窗后的X_train上。因为X_train的形状是三维的样本数、时间步长、特征数MinMaxScaler不支持直接处理三维数组如果要直接对X_train做归一化必须先reshape成二维再transform操作更繁琐也容易出错。在滑窗之前先对原始二维表做归一化代码逻辑更清晰。6.3 模型定义与训练核心代码模型定义部分我拆成了红球和蓝球两个子模型但它们的结构几乎一样只是输出维度不同所以可以写成一个函数。import tensorflow as tf from tensorflow.keras.models import Model from tensorflow.keras.layers import Input, LSTM, Dropout, Dense from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau def build_lstm_model(input_shape, output_dim, lstm_units(128, 64), dropout_rate0.2): input_shape: (window_size, feature_dim) output_dim: 红球取6蓝球取1 inputs Input(shapeinput_shape) # 第一层LSTM返回序列保持时间步信息 x LSTM(unitslstm_units[0], return_sequencesTrue)(inputs) x Dropout(dropout_rate)(x) # 第二层LSTM只返回最后一步 x LSTM(unitslstm_units[1], return_sequencesFalse)(x) x Dropout(dropout_rate)(x) # 输出层线性激活 outputs Dense(unitsoutput_dim, activationlinear)(x) model Model(inputs, outputs) model.compile(optimizeradam, lossmse, metrics[mae]) return model if __name__ __main__: data np.load(data/processed_ssq.npz) # 输入窗口是10期每期特征数是7 input_shape (data[X_train].shape[1], data[X_train].shape[2]) # 红球子模型输出6个红球 red_model build_lstm_model(input_shape, output_dim6, dropout_rate0.2) red_model.summary() # 蓝球子模型输出1个蓝球蓝球取16个值分布更稀疏dropout加大到0.3 blue_model build_lstm_model(input_shape, output_dim1, dropout_rate0.3) blue_model.summary() callbacks [ EarlyStopping(monitorval_loss, patience10, restore_best_weightsTrue), ModelCheckpoint(output/model_red.h5, monitorval_loss, save_best_onlyTrue, verbose1), ReduceLROnPlateau(monitorval_loss, factor0.5, patience5, min_lr1e-5) ] # 红球只取y_train的0-5列 history_red red_model.fit( data[X_train], data[y_train][:, :6], validation_data(data[X_val], data[y_val][:, :6]), epochs200, batch_size32, callbackscallbacks )关键设计点有三个。第一训练红球模型时只传入y_train的前6列蓝球模型只传最后一列。通过这种方式强制两个子模型分别关注各自的目标分布避免互相干扰。第二ModelCheckpoint保存的文件名我在代码里写的是model_red.h5但实际跑的时候还需要额外保存蓝球模型的权重因为两个模型是分别训练的。可以直接在第二个fit过程里把ModelCheckpoint的保存路径改成output/model_blue.h5然后再次调用fit。第三模型在验证集上的表现要从val_loss这个指标去判断不要盯着训练集loss。我在最终测试时重新加载了保存的最优权重用测试集数据做了一次完整的预测和评估保证论文中使用的数据是模型从未见过的。6.4 评估与可视化代码评估部分的主要任务是加载测试集数据用模型预测反归一化四舍五入取整统计命中数量并生成可视化图片。import numpy as np import matplotlib.pyplot as plt from sklearn.metrics import mean_squared_error def evaluate_and_visualize(red_model, blue_model, data, scaler, window_size10): X_test data[X_test] y_test_red data[y_test][:, :6] y_test_blue data[y_test][:, 6:] pred_red red_model.predict(X_test) pred_blue blue_model.predict(X_test) # 反归一化还原到真实的号码范围 # 注意scaler是针对原始7列数据fit的所以要对拼接后的预测结果做inverse_transform pred_scaled_red np.clip(pred_red, 0, 1) pred_scaled_blue np.clip(pred_blue, 0, 1) # 归一化注意为了反归一化需要把预测结果和训练时的scaler保持一致 # 这里演示简化的反归一化方式直接乘上原范围1 # 严谨做法是把scaler.feature_range_对应的max_和min_取出来还原 # 因为MinMaxScaler的min默认是0max默认是1所以 inversed pred * (max-min) min # 但实际用scaler.inverse_transform需要二维输入 pred_red_2d pred_scaled_red # shape: (n, 6) pred_blue_2d pred_scaled_blue # shape: (n, 1) # 使用scaler.inverse_transform需要将7列拼接然后取对应列 pred_full np.concatenate([pred_red_2d, pred_blue_2d], axis1) pred_inversed scaler.inverse_transform(pred_full) # 四舍五入并限制在合法范围内 pred_red_round np.clip(np.round(pred_inversed[:, :6]), 1, 33).astype(int) pred_blue_round np.clip(np.round(pred_inversed[:, 6:]), 1, 16).astype(int) y_test_red_real scaler.inverse_transform( np.concatenate([y_test_red, y_test_blue], axis1))[:, :6].astype(int) y_test_blue_real scaler.inverse_transform( np.concatenate([y_test_red, y_test_blue], axis1))[:, 6:].astype(int) # 统计命中数量 red_hits [] blue_hits [] for i in range(len(pred_red_round)): hit_red np.isin(pred_red_round[i], y_test_red_real[i]).sum() red_hits.append(hit_red) hit_blue 1 if pred_blue_round[i][0] y_test_blue_real[i][0] else 0 blue_hits.append(hit_blue) print(红球平均命中数: {:.2f}.format(np.mean(red_hits))) print(蓝球命中率: {:.2f}%.format(np.mean(blue_hits) * 100)) # 绘制损失曲线训练时从history_red中取loss plt.figure(figsize(10, 4)) plt.plot(history_red.history[loss], labeltrain_loss) plt.plot(history_red.history[val_loss], labelval_loss) plt.xlabel(epoch) plt.ylabel(loss) plt.legend() plt.savefig(output/figures/loss_curve.png, dpi150) plt.close()6.5 关于反归一化的严谨性上面代码里我特别写了一句注释说反归一化要小心。很多同学在demo里直接用预测值乘以33来还原号码这种做法的隐患在于如果归一化时用的scaler不是标准0-1范围或者数据里存在极小值/极大值影响简单乘法就会产生偏差。正确方式是把预测结果和真实结果都通过同一个scaler.inverse_transform还原。因为scaler是针对7列一起训练的所以需要把红球预测和蓝球预测先拼接成一列完整的7维向量再还原。论文里关于这个操作的描述应该写所有预测值和真实值均通过训练集拟合的MinMaxScaler进行反变换还原到原始号码范围后统计命中指标。这样一句话就能堵住答辩老师对数据预处理严谨性的追问。7. 毕业设计论文的章节组织与答辩准备7.1 论文章节的逻辑主线毕业设计论文的章节安排有固定套路但落到这个题目上每章的重点要有所取舍。我的论文目录大致如下第一章绪论。讲选题背景深度学习在时间序列预测中的应用、国内外研究现状LSTM在股票预测、天气预测等领域的应用、研究内容基于LSTM建立双色球号码预测模型并评估其效果。第二章相关技术介绍。LSTM的原理、门控机制、与传统RNN的对比、MinMaxScaler归一化原理、评价指标定义。这一章不要写太长能说明白为什么用这些技术就行。第三章数据获取与预处理。数据来源、清洗流程、滑窗构造、数据集划分。这是整个项目最扎实的部分也是答辩时老师最喜欢问为什么的部分。第四章模型设计与实现。网络结构图、参数设置、训练过程、调参记录。最好给出训练过程中的loss曲线截图。第五章实验结果与分析。基准线设计、命中指标、不同模型的对比实验比如LSTM和随机基线、历史频率基线的对比、可视化图表、误差分析。第六章总结与展望。总结工作内容说明局限性数据量小、模型表达能力有限、随机序列可预测性低展望未来可能的改进方向引入注意力机制、更多特征等。7.2 答辩时常见的问题与应答思路答辩老师大概率会问三个问题提前准备好应答思路就不会慌。第一个问题你的模型真实预测准确率是多少为什么没有达到很高的命中率——标准回答是在当前数据集上模型的最好成绩与随机基线基本持平这说明双色球作为随机事件其可预测性非常有限。本项目的意义在于探索深度学习模型在随机序列数据上的建模能力以及构建一套完整的时间序列预测流程。这个回答既诚实又有学术深度。第二个问题为什么用LSTM而不是普通神经网络——标准回答普通神经网络比如MLP把窗口内的每个号码当作独立的输入特征丢失了时间维度的顺序信息LSTM通过门控机制在时间步之间传递隐藏状态能够建模序列前后文的关系。同时我会补充即使LSTM可以建模时序关系如果数据本身是独立同分布的模型可能学不到真正的时序依赖这正是本项目要验证的问题。第三个问题你的模型在真实场景中能用吗——标准回答要从研究边界来说本项目不构成任何投注建议更重要的是本项目评估了彩票数据的可预测性结果证明了该场景下深度学习模型并不能突破随机性的限制。这个回答既规避了风险也展示了你的学术判断力。7.3 代码提交与README写作毕业设计提交时不光要交论文完整可复现的代码与README也是重要的交付物。README要写清楚五件事项目简介一两句话、运行环境Python版本、依赖库版本、数据文件格式说明、如何依次运行各脚本、输出结果在哪里。建议在README里加一个快速开始部分python src/data_preprocess.py # 生成 processed_ssq.npz python src/train.py # 训练红球/蓝球模型保存至 output/ python src/evaluate.py # 评估测试集生成可视化图表这一个流程跑通后评审老师可以在干净的环境里复现整个模型训练过程这会是答辩验收环节的一个加分项。8. 做这个项目我最大的三条教训反复做完几轮实验、改了几版代码之后如果让我总结这个项目最值得分享的心得我会选以下三条。第一条数据预处理比模型结构重要得多。我在初版代码里花了大量时间调LSTM的层数和单元数但效果提升非常有限。后来把注意力转到数据上——重新检查了归一化范围、确认了时间顺序切割、添加了更严谨的滑窗——各项指标才明显改善。在随机序列数据上模型的天花板很低数据的处理质量直接决定你能摸到多高的天花板。第二条不要迷信训练曲线的漂亮走势。训练loss降得再平滑也不代表预测结果有用。我见过不少复现项目把训练过程画得很漂亮但实际测试集结果非常随机只是因为训练集过拟合了。评估模型一定要看测试集上的真实表现而不是训练曲线。第三条论文写作中把负面结果写清楚反而更显专业。如果模型的预测效果和随机基线差别不大认认真真把这个结果以及可能的成因分析写出来比编造一个准确率高达80%的假数据要有价值得多。答辩老师见过太多夸大其词的学生你越是实事求是越容易获得认可。最后再分享一个小技巧做这类时间序列预测的毕业设计建议把实验日志从第一天就开始记录。我当时在项目文件夹里建了一个experiments.md每调整一个参数就记录当时的验证集指标和现象。后来写论文的实验部分时这些原始记录几乎不需要加工就能用作论据省了返工时间。毕业论文不是临阵磨枪能磨出来的实验做扎实了论文只是呈现结果而已。本文还有配套的精品资源点击获取
返回列表