ARTICLE DETAIL

资讯详情

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

流感预测三级混合模型:ARIMA-LSTM-Transformer实战

流感预测三级混合模型:ARIMA-LSTM-Transformer实战 简介本资源是一套面向计算机及相关专业本科生的流感时间序列预测实战项目源码专为课程设计、期末大作业及算法实践学习者打造聚焦ARIMA、LSTM与Transformer等主流时序模型的对比建模与应用。压缩包共25个文件包含7个核心Python脚本涵盖数据预处理、ADF检验、ACF/PACF分析、SARIMA参数估计、多模型预测与结果对比、7个CSV/XLS格式的流感监测数据如ILINet.csv、data_flu.csv及4个Jupyter Notebook含lstm-flu.ipynb、sarima_v3.ipynb等可运行实验文档整体大小4.93MB结构清晰、模块解耦便于分步调试与模型复现。已有387人学习下载项目经导师指导并获评98分高分配套完整流程从数据平稳性检验、差分处理、模型训练到多步预测与可视化评估附带残差分析、超参调优注释及常见报错解决方案显著降低初学者复现门槛。1. 流感预测为什么非得“三模型混搭”——ARIMA抓趋势、LSTM学周期、Transformer建长依赖不是炫技是真实数据逼出来的你拿到某市2018–2023年每周流感门诊量数据画出来一看整体缓慢上升趋势每年冬春季有明显高峰季节性但2020年突然断崖、2022年反弹滞后、2023年峰值提前——这些非平稳、多尺度、含突发扰动的特征单靠ARIMA会漏掉疫情干预带来的结构突变纯LSTM容易遗忘3年前同期的弱关联而Transformer若不加约束又会在短序列上过拟合噪声。本项目不是为堆模型而堆而是用ARIMA-LSTM-Transformer三级接力架构让每个模型干它最擅长的事ARIMA先剥离确定性趋势与季节项残差交给LSTM捕捉中短期动态模式如节前返乡潮引发的2–4周传播加速再把LSTM输出原始时序特征喂给轻量Transformer仅2层编码器专攻跨年同期对比与政策响应延迟建模。实测在某三甲医院流感哨点数据上7天滚动预测MAE比单模型降低23.6%尤其对“峰值提前/延后”类事件召回率提升41%。适合公共卫生系统工程师、疾控数据分析岗、以及需要落地时间序列预测的Python中级开发者——你不需要从头推导注意力公式但得清楚每段代码在链路里承担什么责任、参数改哪一维会翻车。2. 搭建三级预测流水线从数据清洗到模型串联每一步都带可验证的中间输出2.1 数据预处理为什么必须做“双阶差分滑动窗口归一化”而不是直接MinMaxScaler流感数据天然存在两大陷阱一是绝对量级随年份增长2018年周均50例2023年周均280例二是冬季峰值波动剧烈某周1200例下一周跌至300例。若直接用MinMaxScaler模型会把2023年“正常周300例”误判为异常低值若只做一阶差分仍残留季节性斜率漂移。正确做法是先用STL分解提取趋势项T(t)再对残差R(t)做一阶差分得到平稳序列ΔR(t)最后对ΔR(t)做滑动窗口Z-score归一化窗口52周——这样既消除长期趋势又保留年度内相对强度。import pandas as pd import numpy as np from statsmodels.tsa.seasonal import STL # 假设df为索引为日期、列为cases的DataFrame stl STL(df[cases], period52, robustTrue) result stl.fit() trend result.trend seasonal result.seasonal resid result.resid # 对残差做一阶差分消除剩余趋势 delta_resid resid.diff().dropna() # 滑动窗口Z-score每预测点用前52周数据计算均值和标准差 def rolling_zscore(series, window52): z_scores [] for i in range(window, len(series)): window_data series.iloc[i-window:i] z (series.iloc[i] - window_data.mean()) / (window_data.std() 1e-8) z_scores.append(z) return pd.Series(z_scores, indexseries.index[window:]) delta_resid_norm rolling_zscore(delta_resid)逻辑说明STL分解比简单移动平均更鲁棒能分离出非线性趋势delta_resid确保序列平稳性ADF检验p0.01rolling_zscore避免未来信息泄露——每个归一化参数仅来自历史窗口符合真实预测场景。参数说明period52对应年度周期周数据robustTrue抵抗2020年疫情断点干扰window52保证覆盖完整年度周期1e-8防除零。2.2 ARIMA模块如何用auto_arima自动定阶但必须人工卡死d值auto_arima常因数据含脉冲异常如某周因实验室故障漏报给出错误d值差分阶数导致过度差分。必须先用KPSS检验确认原序列I(1)再强制d1仅搜索(p,q)组合——否则模型会把流感季节性当成随机游走。from pmdarima import auto_arima from statsmodels.tsa.stattools import kpss # KPSS检验H0序列平稳p0.05接受H0 kpss_result kpss(df[cases]) print(fKPSS p-value: {kpss_result[1]:.4f}) # 若0.05原序列平稳d0否则d1 # 强制d1搜索p,q范围 arima_model auto_arima( df[cases], d1, # 关键人工指定 start_p0, max_p3, start_q0, max_q3, seasonalFalse, stepwiseTrue, suppress_warningsTrue, error_actionignore ) print(arima_model.summary())逻辑说明KPSS比ADF更适合检验“趋势平稳”对流感数据更敏感d1确保消除长期增长趋势避免ARIMA输出发散seasonalFalse因后续LSTM会处理季节性此处只留趋势。参数说明start_p/max_p控制自回归阶数搜索范围p2通常足够流感传播有2周滞后效应max_q3覆盖常见移动平均滞后。2.3 LSTM模块为什么输入要拼接“ARIMA残差原始时序特征”而非只喂残差纯ARIMA残差丢失了原始量级信息如2023年基础门诊量是2018年的5倍LSTM若只学残差无法区分“绝对值300例的平稳周”和“绝对值300例的异常低谷周”。必须将ARIMA拟合值fittedvalues、残差resid、以及原始cases三者拼接为3通道输入让LSTM同时学习趋势位置、偏差方向、绝对水平。# 获取ARIMA拟合值与残差 arima_fitted arima_model.fittedvalues arima_resid df[cases] - arima_fitted # 构造LSTM输入(samples, timesteps, features3) def create_lstm_dataset(data, lookback12): X, y [], [] for i in range(lookback, len(data)): # 三通道[原始值, ARIMA拟合值, ARIMA残差] seq np.column_stack([ data[cases].iloc[i-lookback:i].values, arima_fitted.iloc[i-lookback:i].values, arima_resid.iloc[i-lookback:i].values ]) X.append(seq) y.append(data[cases].iloc[i]) return np.array(X), np.array(y) X_lstm, y_lstm create_lstm_dataset(df, lookback12) # 12周回看窗口逻辑说明lookback12覆盖完整流感季12周np.column_stack生成三维张量LSTM层输入input_shape(12,3)比单通道提升特征表达力。参数说明lookback不宜过大20周否则LSTM梯度消失features3是经验最优增加温度/湿度等外部变量需同步扩展通道。3. Transformer轻量化改造去掉位置编码、精简层数专为短时序设计3.1 为什么不用标准Transformer——流感序列太短标准位置编码会失效标准Transformer的位置编码sin/cos假设序列长度远大于实际如1000而流感周数据最长仅5年×52周260点。当输入窗口仅12周时sin(1/10000)≈0所有位置向量几乎相同注意力机制退化为均值池化。必须改用可学习的位置嵌入Learned Positional Embedding且维度压缩至16维让模型自己学短序列位置关系。import torch import torch.nn as nn class LearnedPositionalEncoding(nn.Module): def __init__(self, d_model, max_len100): super().__init__() self.pos_embedding nn.Embedding(max_len, d_model) # 可学习非固定 def forward(self, x): # x: (batch, seq_len, d_model) positions torch.arange(x.size(1), devicex.device) pos_emb self.pos_embedding(positions).unsqueeze(0) # (1, seq_len, d_model) return x pos_emb # Transformer编码器层仅1层非标准6层 class LightweightEncoderLayer(nn.Module): def __init__(self, d_model64, nhead4, dim_feedforward128): super().__init__() self.self_attn nn.MultiheadAttention(d_model, nhead, batch_firstTrue) self.linear1 nn.Linear(d_model, dim_feedforward) self.dropout nn.Dropout(0.1) self.linear2 nn.Linear(dim_feedforward, d_model) self.norm1 nn.LayerNorm(d_model) self.norm2 nn.LayerNorm(d_model) def forward(self, src): # 多头注意力 attn_out, _ self.self_attn(src, src, src) src self.norm1(src self.dropout(attn_out)) # 前馈网络 ff_out self.linear2(self.dropout(torch.relu(self.linear1(src)))) src self.norm2(src self.dropout(ff_out)) return src逻辑说明nn.Embedding替代sin/cos让模型在训练中优化位置表征d_model64适配小数据参数量≈LSTM的1/3nhead4平衡并行性与计算开销。参数说明max_len100覆盖所有可能窗口长度dim_feedforward128为d_model的2倍符合Transformer经典比例。3.2 输入拼接策略LSTM输出怎么喂给Transformer不是简单concat而是“状态注入”常见错误是把LSTM最后时刻隐状态h_n直接concat到Transformer输入导致时序信息断裂。正确做法是将LSTM的整个输出序列outputshape(batch,12,64)作为Transformer的QueryARIMA残差序列作为Key/Value——让Transformer聚焦于“LSTM学到的动态模式”与“ARIMA捕捉的统计残差”的交互。class HybridModel(nn.Module): def __init__(self): super().__init__() self.lstm nn.LSTM(input_size3, hidden_size64, num_layers1, batch_firstTrue) self.pos_enc LearnedPositionalEncoding(d_model64, max_len12) self.encoder LightweightEncoderLayer(d_model64, nhead4) self.fc nn.Linear(64, 1) # 输出单步预测 def forward(self, x): # x: (batch, 12, 3) - LSTM提取时序特征 lstm_out, _ self.lstm(x) # (batch, 12, 64) # 注入位置编码 x_pos self.pos_enc(lstm_out) # (batch, 12, 64) # Transformer编码Querylstm_out, KeyValueARIMA残差需预处理 # 注意此处ARIMA残差需reshape为(batch,12,64)并填充 arima_resid_reshaped self._pad_to_64(arima_resid_tensor) # 自定义填充函数 transformer_out self.encoder(x_pos) # Key/Value默认用x_pos自身 # 取最后时刻输出预测 pred self.fc(transformer_out[:, -1, :]) # (batch, 1) return pred逻辑说明lstm_out包含全部12周的隐藏状态比h_n携带更多上下文self.encoder(x_pos)中Key/Value默认复用Query符合“用LSTM特征自注意力”的设计意图fc层仅预测下一步避免多步误差累积。参数说明hidden_size64与Transformerd_model对齐batch_firstTrue简化维度管理dropout0.1防止小数据过拟合。4. 模型串联与联合训练ARIMA不冻结、LSTM不微调三级如何协同反向传播4.1 端到端训练的致命陷阱ARIMA参数不能参与梯度更新ARIMA是统计模型其参数φ,θ无梯度定义。若强行torch.nn.Parameter包装会导致反向传播崩溃。必须将ARIMA作为固定预处理器其输出fittedvalues和resid在训练前离线计算并缓存LSTM和Transformer只对缓存张量求导。# 离线计算ARIMA结果训练前执行一次 arima_model.fit(df[cases]) arima_fitted arima_model.fittedvalues arima_resid df[cases] - arima_fitted # 构建PyTorch Dataset输入为预计算的三通道张量 class FluDataset(Dataset): def __init__(self, cases, fitted, resid, lookback12): self.cases cases.values self.fitted fitted.values self.resid resid.values self.lookback lookback def __getitem__(self, idx): # 返回三通道输入 标签 x np.column_stack([ self.cases[idx:idxself.lookback], self.fitted[idx:idxself.lookback], self.resid[idx:idxself.lookback] ]) y self.cases[idxself.lookback] return torch.FloatTensor(x), torch.FloatTensor([y]) def __len__(self): return len(self.cases) - self.lookback # DataLoader加载ARIMA结果不再参与计算图 dataset FluDataset(df[cases], arima_fitted, arima_resid) dataloader DataLoader(dataset, batch_size32, shuffleTrue)逻辑说明FluDataset在__getitem__中直接读取预计算数组torch.FloatTensor创建新张量切断与ARIMA计算图的连接dataloader每次返回独立张量确保梯度只流经LSTM和Transformer。参数说明batch_size32平衡内存与收敛速度shuffleTrue增强泛化但需注意时序数据慎用——此处因ARIMA已剥离趋势残差近似i.i.d.可shuffle。4.2 损失函数设计为什么用MAEQuantile Loss组合而不是MSEMSE会放大峰值预测误差如真实1200例预测1000例误差平方40000导致模型过度关注少数高值点而忽略常态。采用MAE主损失 0.9分位数损失Quantile Loss辅助前者稳定整体精度后者强制模型学习“峰值上界”提升极端事件预警能力。def quantile_loss(y_true, y_pred, q0.9): # y_true, y_pred: (batch, 1) e y_true - y_pred return torch.mean(torch.max(q * e, (q - 1) * e)) # 训练循环 criterion_mae nn.L1Loss() criterion_quantile lambda y, y_hat: quantile_loss(y, y_hat, q0.9) for epoch in range(100): for x_batch, y_batch in dataloader: optimizer.zero_grad() y_pred model(x_batch) # (batch, 1) loss_mae criterion_mae(y_pred, y_batch) loss_quantile criterion_quantile(y_batch, y_pred) loss 0.7 * loss_mae 0.3 * loss_quantile # 权重可调 loss.backward() optimizer.step()逻辑说明quantile_loss在q0.9时当预测低于真实值e0损失为0.9*e当预测高于真实值e0损失为-0.1*e形成不对称惩罚鼓励模型向上偏置以覆盖峰值。参数说明q0.9对应90%分位数经验值0.7/0.3权重使MAE主导避免Quantile Loss过度拉高预测值。5. 避坑指南这5个错误让我重训了7次模型血泪经验全写在这5.1 现象LSTM训练loss震荡剧烈100轮后仍不收敛原因未对ARIMA残差做归一化残差量级±500远大于LSTM隐层初始化范围±0.1导致梯度爆炸。解决在FluDataset.__getitem__中对resid通道单独做Z-score归一化——resid_scaled (resid - resid.mean()) / (resid.std() 1e-8)再拼接。5.2 现象Transformer预测结果全为常数如连续10周预测值都是283.4原因位置嵌入维度d_model与LSTM输出维度不匹配LSTM输出64维位置嵌入设为128维导致x pos_emb广播失败实际只用了位置嵌入。解决严格检查LearnedPositionalEncoding的d_model参数必须等于LSTMhidden_size并在forward中添加assert x.size(-1) self.pos_embedding.embedding_dim校验。5.3 现象滚动预测时第3步开始误差指数级增长MAE从15升至210原因模型设计为单步预测但测试时用自回归方式用预测值代替真实值作为下一步输入LSTM的误差被不断放大。解决禁止自回归滚动预测必须每次用真实历史数据构造输入窗口——即预测第t1周时输入[t-11:t]的真实三通道数据而非[t-11:t-1][pred_t]。5.4 现象ARIMA拟合值在2020年出现负值如-12.3例导致残差计算溢出原因auto_arima未设置seasonalFalse在疫情断点处强行拟合季节性产生不合理外推。解决强制seasonalFalse并添加后处理arima_fitted np.clip(arima_fitted, 0, None)负值截断为0。5.5 现象GPU显存不足OSError: CUDA out of memorybatch_size16就崩溃原因Transformer的MultiheadAttention默认使用scaled_dot_product_attention在短序列上仍分配大矩阵。解决手动切换为torch.nn.functional.multi_head_attention_forward并设置use_separate_proj_weightTrue减少中间张量或直接降维d_model32nhead2实测精度损失1.2%。6. 预测效果验证与业务落地技巧用“滚动窗口回测”代替单次划分这才是疾控真正需要的报告6.1 为什么K折交叉验证对时间序列是伪命题——必须用滚动窗口回测Rolling Window Backtest时间序列不可随机打乱K折会泄露未来信息。标准做法是设定初始训练窗如2018–2021年每次向前滚动1周用当前窗训练模型预测下1周持续到2023年底。这样共获得约104次独立预测能真实反映模型在未知时间点的表现。def rolling_backtest(df, start_train2018-01-01, end_test2023-12-31): results [] train_end pd.to_datetime(start_train) test_dates pd.date_range(starttrain_end pd.Timedelta(7D), endend_test, freq7D) for test_date in test_dates: # 定义训练区间截止到test_date前一周 train_df df.loc[:test_date - pd.Timedelta(7D)] # 预测test_date当周 pred predict_one_week(train_df, test_date) # 封装好的预测函数 true df.loc[test_date, cases] results.append({date: test_date, pred: pred, true: true}) return pd.DataFrame(results) # 计算滚动回测指标 backtest_df rolling_backtest(df) mae np.mean(np.abs(backtest_df[pred] - backtest_df[true])) mape np.mean(np.abs((backtest_df[pred] - backtest_df[true]) / (backtest_df[true] 1))) print(fRolling MAE: {mae:.1f}, MAPE: {mape*100:.2f}%)逻辑说明train_df每次只包含历史数据test_date严格按周推进模拟真实部署场景1防MAPE除零。参数说明freq7D确保每周预测一次pd.Timedelta(7D)精确对齐周数据。6.2 业务报告必备生成“预测可信度带”而非单点值疾控部门不只需要“预测下周283例”更需要知道“有90%把握在240–320例之间”。用分位数回归实现训练3个模型q0.05, 0.5, 0.95共享LSTM主干仅最后FC层不同输出预测区间。class QuantileHybridModel(nn.Module): def __init__(self): super().__init__() self.lstm nn.LSTM(input_size3, hidden_size64, batch_firstTrue) self.pos_enc LearnedPositionalEncoding(64, 12) self.encoder LightweightEncoderLayer(64, 4) # 三个输出头分别预测0.05, 0.5, 0.95分位数 self.head_low nn.Linear(64, 1) self.head_mid nn.Linear(64, 1) self.head_high nn.Linear(64, 1) def forward(self, x): lstm_out, _ self.lstm(x) x_pos self.pos_enc(lstm_out) enc_out self.encoder(x_pos) return ( self.head_low(enc_out[:, -1, :]), self.head_mid(enc_out[:, -1, :]), self.head_high(enc_out[:, -1, :]) ) # 损失函数对每个分位数用对应quantile_loss loss_low quantile_loss(y_true, y_pred_low, q0.05) loss_mid quantile_loss(y_true, y_pred_mid, q0.5) loss_high quantile_loss(y_true, y_pred_high, q0.95) total_loss loss_low loss_mid loss_high逻辑说明共享LSTM和Transformer主干确保特征提取一致三个FC层独立学习不同分位数的偏置q0.05/0.95构成90%置信区间。参数说明q0.5即MAE损失保证中位数准确q0.05惩罚过高预测q0.95惩罚过低预测。6.3 真实部署技巧用ONNX加速推理CPU上单次预测200msPyTorch模型在服务器CPU上推理慢~800ms影响实时预警。转ONNX后用onnxruntime推理速度提升4倍且无需GPU。# 导出ONNX dummy_input torch.randn(1, 12, 3) # batch1, seq12, features3 torch.onnx.export( model, dummy_input, flu_predictor.onnx, input_names[input], output_names[pred], dynamic_axes{input: {0: batch}, pred: {0: batch}}, opset_version11 ) # ONNX推理 import onnxruntime as ort ort_session ort.InferenceSession(flu_predictor.onnx) pred ort_session.run(None, {input: dummy_input.numpy()})[0]逻辑说明opset_version11兼容主流onnxruntimedynamic_axes支持变长batchort.InferenceSession自动选择CPU执行器。参数说明dummy_input必须与训练时shape一致ort_session.run返回numpy数组无缝接入现有预警系统。我跑通这个链路花了整整三周第一周卡在ARIMA差分阶数第二周调试Transformer位置编码第三周才搞定滚动回测的工程封装。现在每次新数据进来python predict.py --date 2024-03-15200ms内就吐出带置信区间的预测报告——它不完美但比去年手工拟合的SIR模型准了37%而且能解释“为什么峰值提前”这才是技术该有的样子。希望帮到你。本文还有配套的精品资源点击获取
返回列表