news 2026/10/17 12:46:29

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SEIR+LSTM 传染病预测实战:Python 源码与避坑指南

简介:本资源面向计算机、人工智能、数据科学等专业的在校学生与教师,提供基于SEIR传染病动力学模型与LSTM神经网络实现新型冠状病毒肺炎预测的完整Python项目,可作为课程设计、期末大作业或毕业设计的参考方案。压缩包共31个文件,包含12个Python源码文件、14张结果图表、2份Excel真实疫情数据、2份Markdown项目说明及备份压缩包,整体约1.79MB,结构清晰便于按模块查阅。项目通过控制接触率模拟不同干预强度,体现防控措施对疫情走势的影响,并利用LSTM以前三天数据预测第四天数值,代码附详细注释,便于理解建模思路与调参过程。目前已有384人学习下载,适合希望入门传染病建模与时间序列预测的读者参考与二次开发。

1. 传染病动力学遇上深度学习:SEIR+LSTM 到底在预测什么

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_dim=1, hidden_dim=32, num_layers=2, output_dim=1): super().__init__() # batch_first=True 让输入形状为 (batch, seq_len, input_dim) self.lstm = nn.LSTM(input_dim, hidden_dim, num_layers, batch_first=True) 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, gamma=1/14, sigma=1/7): # df 需包含 date 和 new_cases 两列 df = df.sort_values('date').reset_index(drop=True) 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(window=7).mean().shift(1).fillna(0) df['S'] = df['cum_cases'].max() * 10 - df['cum_cases'] # 总人口按累计量级放大 return df[['date', 'S', 'E', 'I', 'R']]

这段代码的关键是 gamma 和 sigma 的取值。gamma=1/14 表示感染期约 14 天,sigma=1/7 表示潜伏期约 7 天。这两个值不是拍脑袋,而是早期流行病学调查的常见估计。实际项目里应该用网格搜索或最小二乘去拟合,而不是直接写死。

提示:S 的构造里用了「累计量级放大 10 倍」这种近似,真实项目应该用实际人口数。如果拿不到人口数据,至少要做归一化,否则 SEIR 的 β 会失去物理意义。

3. 用 Python 跑通 SEIR+LSTM 的最小闭环

3.1 环境准备与依赖安装

源码包一般要求 Python 3.8 以上。我习惯用 conda 建独立环境,避免和系统里的 numpy 版本打架:

conda create -n seir_lstm python=3.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 列 # 初始猜测:beta=0.5, sigma=1/7, gamma=1/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, window=7): X, y = [], [] for i in range(len(series) - window): X.append(series[i:i+window]) y.append(series[i+window]) return np.array(X), np.array(y) X, y = make_windows(residual, window=7) X = torch.tensor(X, dtype=torch.float32).unsqueeze(-1) # (N, 7, 1) y = torch.tensor(y, dtype=torch.float32).unsqueeze(-1) model = ResidualLSTM() optimizer = torch.optim.Adam(model.parameters(), lr=1e-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(f'epoch {epoch}, loss {loss.item():.4f}')

逻辑说明:window=7 表示用过去 7 天残差预测第 8 天。lr=1e-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:], dtype=torch.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. 避坑与排查:SEIR+LSTM 最容易翻车的 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, window=7): X, y = [], [] for i in range(len(I_seq) - window): feat = np.stack([beta_seq[i:i+window], gamma_seq[i:i+window], I_seq[i:i+window]], axis=-1) X.append(feat) y.append([beta_seq[i+window], gamma_seq[i+window]]) return np.array(X), np.array(y)

这个路线的优点是预测结果仍然满足动力学约束,不会出现负值或爆炸。缺点是参数序列的拟合本身有噪声,窗口太短会导致 β 估计不稳定。我一般会把窗口设为 14 天以上,并且对 β 序列做平滑。

验证方法上,除了看 MSE,还要看预测曲线的拐点是否和真实拐点对齐。如果拐点提前或滞后超过 3 天,说明参数序列的时序建模有问题。另一个实用技巧是画相图:把 S 和 I 的关系画出来,看预测轨迹是否落在真实轨迹的邻域内。

我自己做这类项目最大的教训是:不要一上来就堆 LSTM 层数。先把 SEIR 拟合做扎实,残差图看一眼,再决定要不要上神经网络。很多时候,一个调好参数的 SEIR 加移动平均,效果不比 LSTM 差,而且可解释性强得多。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 0:22:45

Voyager 生态系统指南:受 Voyager 启发的跨平台 AI 增强扩展全景

AI 应用前端插件系统提示工程 【免费下载链接】voyager Enhancement suite for Gemini, AI Studio, Claude, ChatGPT & DeepSeek — plus a prompt manager for any website, DeepSeek Harness included. / 面向 Gemini、AI Studio、Claude、ChatGPT 与 DeepSeek 的增强套件…

作者头像 李华
网站建设 2026/10/15 5:37:32

Linux下AX210开热点卡在200Mbps?固件、监管域与内核协同真相

1. 真实场景还原:为什么你用 AX200/AX210 在 Linux 下开热点,永远卡在 200Mbps?我第一次在某高校实验室的嵌入式开发机上尝试用 AX210 开热点时,心里是笃定的——毕竟 Intel 官方文档白纸黑字写着“支持 AP 模式”,Lin…

作者头像 李华
网站建设 2026/10/16 11:16:44

信创文件传输系统有哪些?主流形态、选型要点与避坑指南

1. 先搞清楚:信创文件传输系统和日常用的文件传输工具有什么不同?先说个我几年前的真实经历。当时帮一家制造企业做供应链系统改造,对方IT负责人对着市面上七八套文件传输方案来回比较,越比越乱。他问了我一句话:“我们…

作者头像 李华
网站建设 2026/10/16 13:05:49

Espresso与UI Automator混合测试实战:突破跨进程UI测试瓶颈

如果跑Espresso测试跑久了,你大概率遇过这种情况:应用内部逻辑全都畅通,一点击某个按钮触发系统权限弹窗、或者跳转到系统设置页,测试就在那儿干等,最终以NoMatchingViewException或者超时收场。这就是Espresso的边界—…

作者头像 李华