首页 / 资讯中心 / 文章详情

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南 ★ FEATURED ARTICLE
简介基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码面向计算机相关专业课程设计与期末大作业学生以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程适合系统掌握有监督建模思路的读者。压缩包共 25 个文件含 7 个 Python 脚本、7 个 CSV 数据、4 个 XLS 表格、2 个 Jupyter Notebook 及 5 个衍生 zip大小约 4.93MB脚本按预处理、检验、残差、预测等模块组织Notebook 提供 LSTM 与 SARIMA 可交互示例。已有 387 人学习下载。附带流感历史数据集便于核对格式、验证结果曾获导师认可并评为 98 分代码清晰、注释完整可直接作为课程设计高分参考也能迁移到其他疾病或业务时序预测场景。1. 流感预测不是调包就能交差这份 98 分项目的真实拆解期末大作业拿到“传染病预测”这类题目时最容易翻车的不是模型跑不通而是交上去的代码只有一堆调包和一张图导师问两句就露馅。这份基于 ARIMA、SARIMA、LSTM并预留 Transformer 扩展位的流感时间序列预测项目价值不在于它被评了 98 分而在于它把一套完整的时间序列建模流程拆成了按顺序执行的脚本和 Notebook——从平稳性检验、定阶、残差验证到多模型对比每一步都有对应的.py或.ipynb文件而不是把模型糊在一个文件里。它适合正在做课程设计、需要向导师演示“我理解了建模流程”而不是“我会 import”的计算机相关专业学生。我能用到的经验是拿到资源后先用数据脚本找回建模的主线比直接跳进模型看代码重要得多。2. 从 ILINet.csv 到能建模的时间序列差分、ADF 与 ACF/PACF 的实操顺序2.1 原始数据的形状ILINet.csv 里到底存了什么打开ILINet.csv它存的是美国流感监测网络按周上报的流感样病例百分比ILI%字段通常包括年份、周次、地区、未加权 ILI 百分比、加权 ILI 百分比等。这种数据的特点是维度多、可能包含多个地区和多年记录不能直接塞进模型。data_flu.csv则是预处理后的单变量序列只有日期和 ILI 值两列才是真正喂给 ARIMA 和 LSTM 的输入。我一般拿到这类数据会先确认两件事一是序列的时间跨度是否连续流感数据经常缺周次比如当年第 52 周之后跳过几周二是单位是百分比还是率这两个在模型里差一个数量级的方差。检查方式是在读入后用df.info()和df.isna().sum()扫一遍不要跳过这一步直接做可视化。import pandas as pd df pd.read_csv(data_flu.csv, parse_dates[date], index_coldate) print(df.info()) print(df.isna().sum()) # 检查时间索引是否连续流感数据常见跳周问题 full_range pd.date_range(startdf.index.min(), enddf.index.max(), freqW) print(缺失周数:, len(full_range.difference(df.index)))逻辑说明parse_dates把日期列转成DatetimeIndex这是后续所有时间序列操作的前提。freqW按周生成完整日期范围和实际索引做差集就能定位缺周的位置。参数说明freqW是周频率如果数据是月度频率就改成MS或M。这里重要的是确认频率和你后续建模的周期假设一致缺周择直接补NaN还是填充取决于缺的数量缺得少就前向填充缺得多则建议按年同期均值插补否则会在残差里留下周期性尖刺。2.2 三个平稳性脚本差分到底该做几次ADF 的 p 值别只看小于 0.051_1_Stationarity_differencing.py、1_2_Stationarity_ADF.py、1_3_Stationarity_ACF_PACF.py这三个脚本是按顺序执行的递进关系。第一次跑1_1时脚本会对原始序列做一阶差分并绘制差分前后的对比图。流感 ILI 数据有明显年度季节性所以一阶差分通常不足以消除周期性还要做季节性差分周期 52 周这一步不能省。1_2里的 ADF 检验输出三行关键信息ADF 统计量、p 值、临界值。很多新手看到 p 值小于 0.05 就认为序列平稳了这里有一个常见的翻车点ADF 检验对样本长度敏感流感序列若只覆盖两三年p 值本身可信度有限要同时看统计量是否小于 1% 临界值才算稳。from statsmodels.tsa.stattools import adfuller def check_stationarity(series, name): result adfuller(series.dropna()) print(f--- {name} ---) print(fADF: {result[0]:.4f}) print(fp-value: {result[1]:.4e}) print(临界值:, {k: round(v, 4) for k, v in result[4].items()}) return result[1] 0.05 and result[0] result[4][1%] # 对原始序列和季节性差分后的序列分别做检验 check_stationarity(df[ili], 原始序列) seasonal_diff df[ili].diff(52).diff().dropna() check_stationarity(seasonal_diff, 季节差分后)逻辑说明diff(52)剔除年度季节性再做一次普通diff()消除剩余趋势这是流感数据建模的常规操作。函数返回值同时卡了 p 值和统计量比较能避免只盯 p 值带来的误判。参数说明这里seasonal_diff是把季节性差分和一阶差分串在一起写的对应 SARIMA 模型的D1和d1。如果你跑出来的 ADF 在原始序列上就通过了也不必高兴太早——大概率是数据窗口太短做差分后的模型的残差反而更干净。2.3 ACF/PACF 图的读法拖尾、截尾和你在作业里要怎么描述它1_3脚本会绘制 ACF 和 PACF 两条带置信区间的棒图。ARIMA 定阶的规则是ACF 拖尾、PACF 截尾走 AR 模型ACF 截尾、PACF 拖尾走 MA 模型两个都拖尾就上 ARMA。实际流感序列基本都落在“两个都拖尾”的情况意味着纯靠看图定阶很玄学所以这份资源里才会有2_estimate_pq.py用信息准则自动寻优。你在作业里描述这张图时不要只写“由图可知需要差分”而要写清楚“ACF 在滞后 52 周处仍显著说明存在年度季节性PACF 在滞后 1 阶截尾因此倾向 AR(1) 项”。这样写导师能确认你看懂了图而不是贴了张图充数。from statsmodels.graphics.tsaplots import plot_acf, plot_pacf import matplotlib.pyplot as plt # 在差分平稳后的序列上做图不要用原始数据 fig, axes plt.subplots(2, 1, figsize(12, 6)) plot_acf(seasonal_diff, axaxes[0], lags60) plot_pacf(seasonal_diff, axaxes[1], lags60) plt.show()逻辑说明lags60保证能看到滞后 52 周的季节性尖峰如果只画默认的 20 阶季节性特征会被截断看图定阶就会漏掉关键信息。参数说明lags60不是固定值你的序列如果只有两年左右画到 40 就够了超过 100 周的数据开 80 也不过分。关键是让图上至少出现两个完整年度周期。3. ARIMA 定阶与残差验证p 值、BIC 和“过拟合”的边界在哪里3.1 自动定阶脚本BIC 惩罚项在流感序列上怎么用2_estimate_pq.py做的事情是遍历p和q的组合通常各取 0 到 5用 AIC 或 BIC 排序输出得分最低的几组参数。BIC 比 AIC 对复杂模型惩罚更重流感序列往往有弱自相关用 BIC 选出的阶数通常比 AIC 小更不容易过拟合。这份脚本的实用之处在于它把寻优结果打印成了表格包括每组参数对应的 AIC/BIC 和残差方差。我建议你把它改成输出预测误差比如用最后 10 周做验证集否则只看信息准则选出的模型可能在预测段表现不好。ARIMA 定阶没有绝对正确只有相对稳健。import warnings from statsmodels.tsa.arima.model import ARIMA from itertools import product warnings.filterwarnings(ignore) def select_arima(series, max_p5, max_q5): results [] for p, q in product(range(max_p 1), range(max_q 1)): try: model ARIMA(series, order(p, 1, q)).fit() results.append((p, q, model.aic, model.bic)) except Exception: continue results.sort(keylambda x: x[3]) # 按 BIC 排序 for row in results[:5]: print(fp{row[0]}, q{row[1]}, AIC{row[2]:.2f}, BIC{row[3]:.2f}) return results[0][:2]逻辑说明脚本内部捕获了拟合异常因为低阶组合在小样本上经常报收敛错误不能用 try-except 包住就不管了——报错的组合往往意味着参数空间不适合当前数据是有效信息。参数说明order(p, 1, q)里的1是我们前面做的一阶差分次数。如果你的季节性差分也算进去了这里应该改写成 SARIMA 的order(p,1,q), seasonal_order(P,1,Q,52)但这份脚本只处理非季节部分季节部分的定阶在sarima_v3.ipynb里单独完成。3.2 残差分析正态性、自相关和 Ljung-Box 的“要过三个关”3_residual.py的作用不是看预测误差有多大而是验证模型是否已经把信息提取干净。它通常输出三样东西残差 Q-Q 图、残差 ACF 图、Ljung-Box 检验 p 值。好的残差应该接近白噪声也就是说残差的 ACF 不应该有任何显著的棒子。Ljung-Box 检验有个易踩坑的地方滞后期数选择。脚本里如果默认用lags20但你做的是周数据且有季节性20 期可能不够覆盖一个年度周期导致季节性残差没被检测到。我会习惯性把lags调到 52 或 min(52, len(resid)//5)宁可多做几组也不要漏掉隐藏的周期模式。from statsmodels.stats.diagnostic import acorr_ljungbox import scipy.stats as stats resid model_fit.resid.dropna() lb_test acorr_ljungbox(resid, lags[12, 24, 52], return_dfTrue) print(lb_test) # 正态性检验shapiro 适合小样本但流感残差常右偏不必强行追求正态 shapiro_stat, shapiro_p stats.shapiro(resid) print(fShapiro p-value: {shapiro_p:.4f})逻辑说明多个滞后期的检验结果并列输出比单一lags更有说服力。如果 12 期通过而 52 期没通过说明短周期自相关被消除了但年度季节性还留在残差里此时不该换模型而该考虑补季节项。参数说明lags[12, 24, 52]分别对应月度、半年度、年度滞后这个组合基本覆盖了流感数据的典型周期。Shapiro 检验在残差接近 400 个样本时对微小偏离也敏感p 值小于 0.05 时先看 Q-Q 图再说不要机械地判死刑。3.3 预测脚本的边界动态预测和静态预测为什么结果不一样5_forecast.py里通常提供两种预测方式一种是单步预测循环每步都喂真实值另一种是动态多步预测用上一步预测值作为下一步输入。动态预测才是 ARIMA 的正常推演方式但预测值会随着步数增加快速收敛到均值这是 ARIMA 的固有限制不是 bug。流感预测里有个特殊的点动态预测在跨过年度高峰通常在第 52 周附近之后可能会把峰值拉平因为模型学到的季节性成分在均值回归。这不是脚本写错而是你要在答辩时能解释的模型边界问题。所以脚本里预测步数超过一个季节周期时结果图里曲线尾部变平是正常的你需要做的是在作业文档里写清楚“本模型适用于未来 4 至 8 周的短期预测更长期受季节突变影响显著”。# 动态预测用模型递归生成未来 n 步的预测值 forecast_steps 12 forecast_res model_fit.get_forecast(stepsforecast_steps) pred_mean forecast_res.predicted_mean conf_int forecast_res.conf_int() print(pred_mean)逻辑说明get_forecast返回一个结果对象predicted_mean是点预测conf_int是置信区间。多步预测的置信区间会随步数扩大这是模型对不确定性累积的正常表达画图时应画出区间带而不是只画中点线。4. LSTM 和 Transformer 做流感预测数据切分、序列长度与训练稳定性4.1 lstm-flu.ipynb 的实现思路把周数据改造成监督学习格式流感的 LSTM 预测和股票预测有个高度相似的坑把时间序列切成滑动窗口后训练集和验证集如果直接从中间断开会造成数据泄露。这个 Notebook 里做得对的地方是把最后一段时间完整留出做验证而不是随机抽样——流感数据是周期性极强的时间序列随机切分的验证集误差会被季节性峰值干扰。在改造成监督学习样本时常用的方式是用lookback大小的窗口逐步滑动生成X和y。设置lookback8约两个月的周数据是流感预测的常见起步值因为流感潜伏期和就医周期基本以月为单位过长会引入太多噪声。import numpy as np from sklearn.preprocessing import MinMaxScaler def create_sequences(data, lookback): X, y [], [] for i in range(len(data) - lookback): X.append(data[i:i lookback]) y.append(data[i lookback]) return np.array(X), np.array(y) scaler MinMaxScaler(feature_range(0, 1)) scaled scaler.fit_transform(df[ili].values.reshape(-1, 1)) X, y create_sequences(scaled, lookback8) # 留出最后 12 周做验证不做随机切分 train_size len(X) - 12 X_train, X_valid X[:train_size], X[train_size:] y_train, y_valid y[:train_size], y[train_size:] print(X_train.shape, X_valid.shape)逻辑说明create_sequences用固定窗口平移生成样本窗口长度对应lookback参数。切分时用最后 12 周作为验证集是保证验证集里的样本不参与训练防止模型“偷看”未来信息。参数说明feature_range(0,1)是最常用归一化区间LSTM 对数据尺度敏感。lookback8是经验值如果验证集误差偏大可以尝试 4 或 12不要盲目加大窗口越长样本越少流感公开数据集本身不长。4.2 单变量 LSTM 的模型结构为什么说它和手写 Transformer 是同一条路lstm-flu.ipynb里的 LSTM 结构通常是LSTM层加Dense输出层输入形状是(batch_size, lookback, 1)。这个结构在处理单变量流感序列时已经够用但为什么项目标题里还挂着 Transformer因为这两者的输入输出接口完全一致——把LSTM层替换成TransformerBlock即可扩展而这份资源里的 LSTM 代码实际上是 Transformer 改造的脚手架。如果你想把 LSTM 升级成 Transformer常见做法是写一个包含多头自注意力加前馈网络的TransformerBlock内部需要位置编码。流感预测是逐点回归不需要像 NLP 那样处理变长序列所以位置编码用简单的可学习矩阵就行。import tensorflow as tf from tensorflow.keras.layers import Input, Dense, LayerNormalization, MultiHeadAttention, Dropout def transformer_block(x, head_size4, num_heads2, ff_dim32, dropout0.1): attn_output MultiHeadAttention( num_headsnum_heads, key_dimhead_size )(x, x) attn_output Dropout(dropout)(attn_output) out1 LayerNormalization(epsilon1e-6)(x attn_output) ffn_output Dense(ff_dim, activationrelu)(out1) ffn_output Dense(x.shape[-1])(ffn_output) ffn_output Dropout(dropout)(ffn_output) return LayerNormalization(epsilon1e-6)(out1 ffn_output)逻辑说明这个实现参考了标准 Transformer 编码器块先多头注意力再残差连接加层归一化再接前馈网络。x attn_output和out1 ffn_output的残差结构是训练稳定的关键删掉它模型很容易梯度爆炸。参数说明head_size是注意力头的维度num_heads2在小数据集上够用ff_dim32是前馈网络隐层宽度。这四个参数在流感单变量任务上不需要很大过大反而容易过拟合训练集。4.3 训练时的早停和学习率LSTM 的“玄学”收敛问题时间序列的 LSTM 训练收敛问题不是玄学而是两个明确原因一是学习率设置不合理二是没有早停机制。这个 Notebook 里如果用固定学习率 0.001 加 100 个 epoch极有可能出现验证 loss 在某个 epoch 后开始回升的训练曲线。我建议你把训练部分改成ReduceLROnPlateau加EarlyStopping的组合前者在 loss 停滞时自动降低学习率后者防止最后几个 epoch 的过拟合。这份资源里 LSTM 代码的主要改进空间就在这里也是答辩时导师最爱问的“你怎么防止过拟合”的加分答案。from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) reduce_lr ReduceLROnPlateau( monitorval_loss, factor0.5, patience5, min_lr1e-6 ) history model.fit( X_train, y_train, validation_data(X_valid, y_valid), epochs100, batch_size16, callbacks[early_stop, reduce_lr], verbose1 )逻辑说明EarlyStopping在验证 loss 连续 10 个 epoch 不下降时停止并把权重恢复到验证 loss 最低的 epoch这是时间序列任务的标准操作。ReduceLROnPlateau在连续 5 个 epoch 无改善时把学习率减半给模型二次收敛的机会。参数说明patience10在某些小数据集上偏大如果验证集只有 12 个样本可以改成 5。min_lr1e-6是学习率下限防止衰减到零后无法跳出局部极小值。batch_size16对应样本量较少的场景样本上万时可调到 32 或 64。5. 避坑指南时间序列预测项目里的五条血泪经验5.1 现象ADF 检验显示序列平稳但模型预测结果像一条直线原因是数据窗口太短ADF 在小样本下功效低把非平稳序列误判为平稳。解决方法是结合 ACF/PACF 图的视觉判断如果差分后的 ACF 在滞后 52 周处仍显著就必须做季节性差分不能只看 p 值。5.2 现象LSTM 训练时 loss 在某个 epoch 后突然跳到 NaN原因是学习率过大或数据未归一化。检查归一化是否用了MinMaxScaler且作用在(-1, 1)区间如果归一化没问题就把学习率从 0.001 改成 0.0003 重新训练。NaN 出现后不要只重跑要把model.optimizer.lr.numpy()打印出来确认学习率没有因为某些回调异常变大。5.3 现象SARIMA 的sarima_v3.ipynb拟合耗时极长甚至半小时不出结果原因是seasonal_order里的P和Q设置过大网格搜索组合爆炸。流感数据有年度季节性seasonal_order(1,1,1,52)通常足够如果你把P、Q都设成 2模型要拟合的参数数量会翻倍。解决方法是先固定seasonal_order(1,1,1,52)只调整p、q确认收敛后再放宽季节参数。5.4 现象4_compare.py绘制对比图时显示NaN值导致图像断开原因是原始流感数据存在缺周不同模型对缺值的处理路径不一致——ARIMA 用NaN跳过LSTM 用前向填充对比脚本拼接结果时错位。解决方法是在每个模型训练前对数据做统一的缺失值处理推荐用df[ili].interpolate(limit_directionboth)不要卸载 STOP 在各自 Notebook 里单独处理。5.5 现象动态预测曲线在后期剧烈偏离真实值验证集误差远大于训练集原因是模型本身没有捕捉到流感爆发的突变特征流感序列在某些年份由新型毒株引发尖峰这类行为在统计上不可预测。解决方法是把预测窗口限制在 4 到 8 周并在报告中明确说明“长周期预测结果仅供趋势参考不适用于突发公共卫生事件的早期预警”。这个坑不是代码问题是模型能力边界写清楚反而加分。6. 从 98 分到能答辩的检查清单用 4_compare.py 跑完的最后一公里拿到这份资源后你至少要做两次完整的运行。第一次是照搬原样按顺序跑完1_1到1_3再看2_estimate_pq.py选出的p、q然后跑3_residual.py验证残差最后用5_forecast.py输出 ARIMA 的预测结果。第二次是修改参数后重跑重点把 LSTM 的lookback从 8 改成 4 和 12对比同一验证集上的误差变化把这三组结果放进作业文档里。4_compare.py的内容是汇总各模型的预测误差指标通常包括 MAE、RMSE、MAPE 三个列。这是答辩时最直接的得分点——它告诉你 ARIMA 和 LSTM 在流感数据上各有什么取舍。我跑这类对比有一个固定习惯先把验证集的真实值、两种模型的预测值落成一张表按周对比而不是只看汇总指标流感数据有强季节性MAE 相同但峰值周误差可能差一倍这个细节在你回答“为什么选这个模型”时非常有用。from sklearn.metrics import mean_absolute_error, mean_squared_error import numpy as np def evaluate_model(y_true, y_pred, name): mae mean_absolute_error(y_true, y_pred) rmse np.sqrt(mean_squared_error(y_true, y_pred)) mape np.mean(np.abs((y_true - y_pred) / y_true)) * 100 print(f{name:10s} MAE{mae:.4f} RMSE{rmse:.4f} MAPE{mape:.2f}%) return {name: name, MAE: mae, RMSE: rmse, MAPE: mape} # y_arima 和 y_lstm 分别对应 4_compare.py 输出的预测结果 eval_results [] eval_results.append(evaluate_model(y_true, y_arima, ARIMA)) eval_results.append(evaluate_model(y_true, y_lstm, LSTM))逻辑说明MAPE对接近零的真实值会输出极大误差流感 ILI 百分比很少接近零所以在这个场景下可用如果换到病例数这类含零的数据用smape更稳妥。RMSE放大了大误差样本的影响这也是刻意为之——流感预测对低估爆发峰值更敏感。答辩时导师问“ARIMA 和 LSTM 哪个更好”不要回答“LSTM 因为 MAE 更低”而要拆开说如果验证集正好落在流感季上升段LSTM 对非线性拐点的拟合更好如果验证集落在下降段或平台期ARIMA 的线性均值回归反而更稳。这份资源里完全有足够的数据支撑你跑出这种结论你需要做的是多跑几次不同验证窗口把结果截屏放到文档里。做完这些之后我每次拿到这类项目都会强制走一遍从数据检查到残差验证的完整流程而不是只看模型输出。把data_flu.csv当成一个没有答案的问题重新做一遍你会发现 ARIMA 定阶脚本选出的参数和你自己通过 ACF/PACF 推断出的参数基本一致——那种感觉才算是真正掌握了这份资源。希望这些拆解能帮你在答辩前把项目的每一行都变成自己的东西。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站