资讯详情

SEIR+LSTM 传染病预测实战:Python 源码与避坑指南

📅 2026/10/10 13:55:27 | 华诺云谱 👁 阅读
SEIR+LSTM 传染病预测实战:Python 源码与避坑指南
简介本资源面向计算机、人工智能、数据科学等专业的在校学生与教师提供基于SEIR传染病动力学模型与LSTM神经网络实现新型冠状病毒肺炎预测的完整Python项目可作为课程设计、期末大作业或毕业设计的参考方案。压缩包共31个文件包含12个Python源码文件、14张结果图表、2份Excel真实疫情数据、2份Markdown项目说明及备份压缩包整体约1.79MB结构清晰便于按模块查阅。项目通过控制接触率模拟不同干预强度体现防控措施对疫情走势的影响并利用LSTM以前三天数据预测第四天数值代码附详细注释便于理解建模思路与调参过程。目前已有384人学习下载适合希望入门传染病建模与时间序列预测的读者参考与二次开发。1. 传染病动力学遇上深度学习SEIRLSTM 到底在预测什么2020 年之后但凡做过疫情数据建模的人都会碰到同一个尴尬纯 SEIR 曲线太平滑纯 LSTM 曲线太抖动。前者把人群当成均匀混合的流体后者把每日确诊当成一串没有物理意义的数字。真正能落地的方案是把两者缝在一起——用 SEIR 提供感染机理的骨架用 LSTM 学习残差和时序波动。这个标题里的 Python 源码包本质上就是干这件事输入历史确诊、潜伏、移出数据输出未来若干天的感染人数预测。它适合谁适合手里有一份时间序列、想用神经网络做预测但又不甘心只调包的从业者也适合做公共卫生、疾控信息化、高校课程设计的人。你不需要先成为流行病学家但得能看懂微分方程和 LSTM 的输入输出形状。下面我按「先立住理论、再跑通代码、最后讲坑」的顺序把这条路线拆开讲清楚。2. SEIR 与 LSTM 的缝合逻辑为什么不是二选一2.1 SEIR 的四个仓室和三个关键参数SEIR 把人群分成四类易感者 S、潜伏者 E、感染者 I、移出者 R。它的核心是一组常微分方程dS/dt -β * S * I / N dE/dt β * S * I / N - σ * E dI/dt σ * E - γ * I dR/dt γ * I这里 β 是传染率σ 是潜伏期转为感染期的速率1/潜伏期γ 是移出率1/感染期N 是总人口。这三个参数决定了曲线的形状β 越大爆发越猛σ 越大潜伏越短γ 越大恢复越快。纯 SEIR 的问题在于它假设参数恒定、人群均匀混合。现实中政策干预、检测能力、人口流动都会让 β 和 γ 随时间变化。所以直接拿固定参数的 SEIR 去拟合真实数据往往在拐点处翻车。2.2 LSTM 补的是哪一块残差和时序依赖LSTM 擅长的是从历史序列里学出短期依赖。把它接在 SEIR 后面有两种常见接法第一种是「SEIR 先拟合LSTM 学残差」。先用 SEIR 拟合出基线曲线再用 LSTM 去预测真实值与基线值的差。这样 LSTM 不用从零学感染机理只需要学波动。第二种是「SEIR 参数序列 LSTM 联合预测」。把 β、γ 当成随时间变化的序列用 LSTM 预测未来的参数再代回 SEIR 方程推演。这种做法更符合动力学直觉但实现复杂度高。源码包里通常采用第一种因为工程上更稳。下面给一个最小可跑的残差预测结构import numpy as np import torch import torch.nn as nn class ResidualLSTM(nn.Module): def __init__(self, input_dim1, hidden_dim32, num_layers2, output_dim1): super().__init__() # batch_firstTrue 让输入形状为 (batch, seq_len, input_dim) self.lstm nn.LSTM(input_dim, hidden_dim, num_layers, batch_firstTrue) self.fc nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch, seq_len, 1) 残差序列 out, _ self.lstm(x) # 只取最后一个时间步的输出做预测 return self.fc(out[:, -1, :])逻辑说明输入是 SEIR 基线残差序列输出是下一时刻的残差修正值。hidden_dim 控制记忆容量num_layers 控制深度。参数说明input_dim 固定为 1单变量残差output_dim 为 1预测单点。如果要多步预测把 output_dim 改成预测步数或者用自回归方式滚动预测。2.3 数据预处理把确诊数变成 SEIR 能吃的格式真实数据通常只有每日新增确诊。要喂给 SEIR得先转成累计量再反推 E 和 R。常见做法是import pandas as pd def build_seir_input(df, gamma1/14, sigma1/7): # df 需包含 date 和 new_cases 两列 df df.sort_values(date).reset_index(dropTrue) df[cum_cases] df[new_cases].cumsum() # 用移出率反推累计移出近似 R df[R] (df[cum_cases] * gamma).cumsum() # I 用当前活跃病例近似 df[I] df[cum_cases] - df[R] # E 用新增的滞后项近似 df[E] df[new_cases].rolling(window7).mean().shift(1).fillna(0) df[S] df[cum_cases].max() * 10 - df[cum_cases] # 总人口按累计量级放大 return df[[date, S, E, I, R]]这段代码的关键是 gamma 和 sigma 的取值。gamma1/14 表示感染期约 14 天sigma1/7 表示潜伏期约 7 天。这两个值不是拍脑袋而是早期流行病学调查的常见估计。实际项目里应该用网格搜索或最小二乘去拟合而不是直接写死。提示S 的构造里用了「累计量级放大 10 倍」这种近似真实项目应该用实际人口数。如果拿不到人口数据至少要做归一化否则 SEIR 的 β 会失去物理意义。3. 用 Python 跑通 SEIRLSTM 的最小闭环3.1 环境准备与依赖安装源码包一般要求 Python 3.8 以上。我习惯用 conda 建独立环境避免和系统里的 numpy 版本打架conda create -n seir_lstm python3.8 conda activate seir_lstm pip install numpy pandas matplotlib scipy torch scikit-learn如果你用 pip 直接装注意 torch 的版本要和 CUDA 匹配。没有 GPU 就用 CPU 版训练慢一点但能跑通。python 安装 numpy 库的方法这里不展开pip install numpy 即可。3.2 SEIR 参数拟合用 scipy 做最小二乘在接 LSTM 之前先把 SEIR 的 β、σ、γ 拟合出来。这一步决定了基线质量from scipy.integrate import odeint from scipy.optimize import minimize import numpy as np def seir_deriv(y, t, beta, sigma, gamma, N): S, E, I, R y dS -beta * S * I / N dE beta * S * I / N - sigma * E dI sigma * E - gamma * I dR gamma * I return [dS, dE, dI, dR] def loss(params, t, y_true, N): beta, sigma, gamma params y0 y_true[0] y_pred odeint(seir_deriv, y0, t, args(beta, sigma, gamma, N)) return np.mean((y_pred[:, 2] - y_true[:, 2]) ** 2) # 只拟合 I 列 # 初始猜测beta0.5, sigma1/7, gamma1/14 res minimize(loss, [0.5, 1/7, 1/14], args(t, y_true, N), bounds[(0.01, 2), (0.01, 1), (0.01, 1)]) print(res.x)逻辑说明odeint 负责数值求解微分方程minimize 负责调参。loss 只比较 I 列因为确诊数据对应的是感染者。参数说明bounds 限制参数范围防止出现负值或爆炸。如果拟合结果 β 接近上界说明模型设定有问题可能需要加入干预项。3.3 残差提取与 LSTM 训练拟合完 SEIR 后算出基线预测和真实值的差作为 LSTM 的训练目标# 假设 seir_pred 是 SEIR 拟合出的 I 序列real 是真实 I 序列 residual real - seir_pred # 构造滑动窗口样本 def make_windows(series, window7): X, y [], [] for i in range(len(series) - window): X.append(series[i:iwindow]) y.append(series[iwindow]) return np.array(X), np.array(y) X, y make_windows(residual, window7) X torch.tensor(X, dtypetorch.float32).unsqueeze(-1) # (N, 7, 1) y torch.tensor(y, dtypetorch.float32).unsqueeze(-1) model ResidualLSTM() optimizer torch.optim.Adam(model.parameters(), lr1e-3) criterion nn.MSELoss() for epoch in range(200): model.train() optimizer.zero_grad() pred model(X) loss criterion(pred, y) loss.backward() optimizer.step() if epoch % 50 0: print(fepoch {epoch}, loss {loss.item():.4f})逻辑说明window7 表示用过去 7 天残差预测第 8 天。lr1e-3 是 Adam 的常用起点。参数说明如果 loss 下降太慢把 lr 调到 1e-2如果震荡调到 1e-4。epoch 数根据数据量调整小数据集 200 轮足够。3.4 预测与反归一化训练完后把 LSTM 输出的残差加回 SEIR 基线得到最终预测model.eval() with torch.no_grad(): last_window torch.tensor(residual[-7:], dtypetorch.float32).view(1, 7, 1) future_residual model(last_window).item() final_pred seir_pred[-1] future_residual print(f下一时刻预测感染人数: {final_pred:.0f})如果要预测多天就用自回归把预测出的残差追加到窗口末尾滚动预测。注意这种方式误差会累积一般不超过 14 天。4. 避坑与排查SEIRLSTM 最容易翻车的 5 个地方4.1 现象SEIR 拟合曲线完全贴不上真实数据原因β、σ、γ 的初始猜测离真实值太远或者 bounds 设得太窄。另一个常见原因是数据没有做平滑每日确诊波动太大最小二乘被异常值带偏。解决先用网格搜索粗定参数范围再上 minimize。数据先做 7 天滑动平均。如果还是贴不上考虑加入时变 β或者改用分段拟合。4.2 现象LSTM 训练 loss 不下降或者降到某个值就卡住原因残差序列本身没有可学习的模式或者 window 太小。如果 SEIR 拟合已经很好残差接近白噪声LSTM 自然学不到东西。解决先画残差的自相关图。如果 ACF 在滞后几阶就截尾说明残差确实没结构这时候不该硬上 LSTM。如果 ACF 有周期性把 window 调到覆盖一个周期。4.3 现象预测结果出现负值原因LSTM 输出没有约束残差修正可能把基线拉成负数。SEIR 基线本身在疫情末期也会趋近于零。解决在输出层加 ReLU或者对最终预测做 clip。更稳妥的做法是预测对数残差再取指数还原。4.4 现象换一份数据就完全跑不通原因代码里写死了总人口 N、初始 S/E/I/R或者写死了 gamma 和 sigma。不同地区、不同时间段这些值都不一样。解决把所有硬编码参数抽成配置文件。N 从数据里推断初始值用前几天的数据构造。gamma 和 sigma 至少要做敏感性分析。4.5 现象训练集表现很好测试集一塌糊涂原因时间序列不能随机划分训练测试集。如果用 train_test_split 打乱顺序会造成未来信息泄露。解决按时间切分前 80% 训练后 20% 测试。更严格的做法是滚动窗口验证每次用前 N 天预测后 M 天滚动前进。注意时间序列的交叉验证不能用 KFold要用 TimeSeriesSplit。这一点在 sklearn 文档里有明确说明但很多人第一次做时序预测都会踩。5. 进阶技巧用 SEIR 参数序列喂给 LSTM 做联合预测前面讲的是残差路线工程上稳但有个天花板SEIR 基线一旦拟合完就固定了LSTM 只能修修补补。如果你想再进一步可以试试把 SEIR 的参数当成时序特征让 LSTM 直接预测未来的 β 和 γ。具体做法是把历史数据切成多个窗口每个窗口单独拟合一组 SEIR 参数得到 β(t) 和 γ(t) 序列。然后把这个序列和确诊序列拼在一起作为 LSTM 的多变量输入。输出是未来时刻的 β 和 γ再代回 SEIR 方程推演感染人数。# 多变量输入每行是 [beta_t, gamma_t, I_t] def make_multivariate_windows(beta_seq, gamma_seq, I_seq, window7): X, y [], [] for i in range(len(I_seq) - window): feat np.stack([beta_seq[i:iwindow], gamma_seq[i:iwindow], I_seq[i:iwindow]], axis-1) X.append(feat) y.append([beta_seq[iwindow], gamma_seq[iwindow]]) return np.array(X), np.array(y)这个路线的优点是预测结果仍然满足动力学约束不会出现负值或爆炸。缺点是参数序列的拟合本身有噪声窗口太短会导致 β 估计不稳定。我一般会把窗口设为 14 天以上并且对 β 序列做平滑。验证方法上除了看 MSE还要看预测曲线的拐点是否和真实拐点对齐。如果拐点提前或滞后超过 3 天说明参数序列的时序建模有问题。另一个实用技巧是画相图把 S 和 I 的关系画出来看预测轨迹是否落在真实轨迹的邻域内。我自己做这类项目最大的教训是不要一上来就堆 LSTM 层数。先把 SEIR 拟合做扎实残差图看一眼再决定要不要上神经网络。很多时候一个调好参数的 SEIR 加移动平均效果不比 LSTM 差而且可解释性强得多。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑