SEIR+LSTM混合模型实现疫情预测完整指南

发布时间:2026/9/23 5:47:23
SEIR+LSTM混合模型实现疫情预测完整指南 简介这套源码项目围绕COVID-19疫情数据预测融合SEIR传染病动力学模型与LSTM神经网络两种技术路线适合计算机、数据科学、人工智能等相关专业学生作为课程设计、毕业设计或入门进阶项目。资源共31个文件包含12个Python脚本SEIR基础模型、不同日期干预策略、新增/活跃/累计病例预测等、14张结果可视化图片、2份Excel真实数据、2份Markdown项目说明及1个源码备份压缩包整体仅1.79MB结构紧凑、注释详细便于快速运行与二次开发。目前已有383人学习下载学员可借助完整代码和说明理解模型构建、参数调整及LSTM时序预测流程也可基于现有框架扩展干预输入或优化序列长度用于防控效果评估或疫情趋势分析。1. SEIRLSTM预测新冠一个值得复现的混合建模方案把传染病动力学模型和神经网络放在同一个项目里是很多人在疫情数据预测上尝试过的一条技术路线先用SEIR把传播过程的物理框架搭起来再用LSTM去补上SEIR拟合不了的那部分实际波动。这套方案的吸引力在于它既不是纯数理模型也不是纯黑匣子拟合而是让两个方向互补。一个典型的开源项目命名方式会带上“python源码详细注释项目说明.zip”说明作者已经把工程化的工作做完了拿到压缩包解压就能跑。这个方向能解决什么问题对研究人员和量化分析从业者来说它提供了一个不需要手动调超参数就能完成疫情短期预测的基线方案对刚接触时序预测的Python开发者来说它是理解SEIR参数估计和LSTM输入输出格式之间差异的绝佳练习项目。适合人群很明确有Python基础、懂一点pandas和matplotlib、想弄清楚“机理模型数据驱动模型”怎么协作的入门到中级从业者。下面从模型原理开始一步步拆到这个项目里每一段代码到底在干什么。2. SEIR动力学模型怎么落地微分方程、参数估计与最小可运行代码2.1 先把SEIR公式写清楚四个仓室和两个关键传递项SEIR把人群分成四类易感者S、潜伏者E、感染者I、康复者R。核心假设是潜伏者不传染或传染力极弱感染者按一定速率转为康复者康复后不再被感染。这个模型最贴近新冠早期传播特征因为它多了一个E仓室能描述“接触病毒后还没出现症状但已经带毒”的窗口期比SIR模型更接近真实情况。四个微分方程如下dS/dt -beta * S * I / N易感者因接触感染者而减少。dE/dt beta * S * I / N - sigma * E新增暴露者减去转为感染者的部分。dI/dt sigma * E - gamma * I潜伏期结束转为感染者再按康复速率转出。dR/dt gamma * I感染者康复或隔离后不再参与传播。其中beta、sigma、gamma是三个核心参数分别表示有效接触率、潜伏期倒数、康复期倒数。实际做预测时绝大多数项目不直接去查这三者的医学文献值而是在已知每日新增确诊数据的基础上做参数反演。这也是SEIR项目里代码量最集中的部分把微分方程写对只是第一层把参数调到和真实曲线吻合才是关键。2.2 用odeint写SEIR求解器最小代码与参数说明在Python里求解SEIR最常用的是scipy.integrate.odeint。你不需要自己写龙格库塔法odeint内部已经用LSODA做了自适应步长。最小可运行代码如下import numpy as np from scipy.integrate import odeint def seir_model(y, t, N, beta, sigma, gamma): S, E, I, R y dSdt -beta * S * I / N dEdt beta * S * I / N - sigma * E dIdt sigma * E - gamma * I dRdt gamma * I return [dSdt, dEdt, dIdt, dRdt] # 参数设置N为总人口beta有效接触率sigma潜伏期倒数gamma康复期倒数 N 100000 beta, sigma, gamma 0.3, 1/5.2, 1/14.0 I0, E0, R0 1, 10, 0 S0 N - I0 - E0 - R0 y0 [S0, E0, I0, R0] t np.linspace(0, 160, 160) ret odeint(seir_model, y0, t, args(N, beta, sigma, gamma)) S, E, I, R ret.T这段代码的逻辑很简单seir_model返回四个仓室的导数odeint按时间序列t逐步积分。关键参数里beta0.3表示每个感染者每天平均有效接触0.3个易感者sigma1/5.2意味着平均5.2天潜伏期结束gamma1/14表示从发病到隔离或康复平均14天。这些数值只是初始猜测真实项目中需要结合具体疫情阶段调整。2.3 参数估计的常规做法先人口、再病程、后反演SEIR落地时的第一道坎是参数估计。多数项目不会把beta当作固定值而是用最小二乘或scipy.optimize.curve_fit去拟合每日新增确诊曲线。做法是把每日新增近似为sigma * E也就是当天从潜伏者转为感染者的数量然后让模拟值和真实值做误差最小化。from scipy.optimize import curve_fit def fit_seir(t, beta, sigma, gamma): y0 [N - 10, 5, 1, 0] ret odeint(seir_model, y0, t, args(N, beta, sigma, gamma)) return ret[:, 2] # 返回I曲线 # 假设obs是真实每日在院或新增感染序列t是日期序号 # popt, pcov curve_fit(fit_seir, t, obs, p0[0.3, 1/5.2, 1/14.0], bounds(0, 1))注意curve_fit拟合的目标选择是I还是sigmaE对结果影响非常大。直接用I做目标拟合的是存量曲线用sigmaE拟合则是对齐新增曲线。大多数时候新增数据比存量数据更容易获得所以按sigma*E对齐更常见。这里建议把三个参数都设置合理边界beta在0到1之间sigma和gamma在0.05到0.5之间避免优化器跑到负值或超物理范围。SEIR模型本身有它的边界它假设人群均匀混合没有空间结构也没有把医疗资源占用、方舱隔离、疫苗接种等干预措施显式建模。所以纯SEIR在疫情中期预测上会逐渐偏离真实曲线。这就是为什么项目里要引入LSTM——让神经网络去捕捉SEIR的残差。3. 从真实疫情数据到LSTM训练集清洗、滑动窗口与归一化3.1 数据来源与字段选择为什么只看新增确诊不够做LSTM预测前先要把原始数据整理成监督学习格式。很多项目使用公开的每日新增确诊、累计确诊、累计治愈、累计死亡四个字段。直接拿“累计确诊”建模是最常见的错误因为累计值有单调递增且波动幅度随时间放大的特性LSTM对这类趋势的拟合能力有限更容易在拐点处产生滞后。我的习惯是优先使用“每日新增确诊”作为主预测目标同时把“累计确诊”的差分结果作为额外特征。如果要让模型感知到防控强度变化可以把“治愈率”或“死亡率”也加进去。字段数量不是越多越好LSTM输入特征维度太高而样本量有限时反而容易过拟合。import pandas as pd df pd.read_csv(covid_daily.csv, parse_dates[date]) df[new_cases] df[confirmed].diff().fillna(0) df[new_recovered] df[recovered].diff().fillna(0) df[active] df[confirmed] - df[recovered] - df[deaths] # 只保留模型需要的列 feature_cols [new_cases, new_recovered, active] data df[feature_cols].values.astype(float32)这段代码把累计值转成每日新增并计算在院活跃数。活跃数这个特征很重要它反映的是当前正在传播的感染存量和SEIR中的I仓室直接对应。用活跃数做输入特征相当于让LSTM感知到SEIR的I曲线形态。3.2 构造监督学习样本时间步长、预测步长与特征列LSTM不能直接吃一维时间序列需要把数据切成“过去若干天预测未来若干天”的样本对。常见的做法是用过去14天或21天的数据预测未来7天的数据这正好对应一个潜伏期加一个传染期的量级。import numpy as np def create_sequences(data, input_days14, output_days7): X, y [], [] for i in range(len(data) - input_days - output_days 1): X.append(data[i : i input_days]) y.append(data[i input_days : i input_days output_days, 0]) # 只预测new_cases return np.array(X), np.array(y) X, y create_sequences(data) print(输入形状:, X.shape, 输出形状:, y.shape)input_days是回看窗口output_days是预测长度。输出部分只取第0列也就是new_cases因为是预测新增确诊数。X的形状是(samples, input_days, features)LSTM要求输入必须是三维张量这个写法已经满足要求。序列构造有一个容易忽略的细节如果数据量只有一百多天滑动窗口切分后样本量会很小这时候通常需要把output_days调小或者回看窗口缩短到10天以内否则训练集太少。3.3 归一化的坑归一化对象和反归一化方式LSTM用的是tanh和sigmoid激活函数输入数据尺度差异太大会让梯度不稳定。归一化几乎是必须做的。我惯用的方案是MinMaxScaler把所有特征压缩到(0,1)区间但有两个坑要避开。from sklearn.preprocessing import MinMaxScaler scaler MinMaxScaler(feature_range(0, 1)) scaled_data scaler.fit_transform(data) # 训练集和测试集要用同一个scaler禁止分别fit第一个坑训练集和测试集不能分别调用fit_transform。如果分别归一化实际值域不同模型看到的分布就是两套预测结果会整体偏掉。正确做法是先用训练集fit_scaler.fit(data_train)再对测试集只用transform。第二个坑反归一化时用同一scaler变换回原始尺度。pred_inverse scaler.inverse_transform( np.concatenate([pred, np.zeros((pred.shape[0], 2))], axis1) )[:, 0]这里pred是LSTM输出的归一化预测值但scaler是在三维特征上训练的所以要用np.concatenate补回两个零列再反变换最后只取第0列。如果数据里有极端值MinMaxScaler会把大部分数据压到很窄的区间里导致训练时梯度变化不明显这时改用RobustScaler或者对原始数据取对数后再归一化更稳妥。4. 用LSTM做时序预测网络结构、训练流程与损失曲线判断4.1 为什么SEIR和LSTM能结合残差修正与参数时序化理解了SEIR和LSTM各自的特性两者的结合方式就清晰了。常见做法有两种一是把SEIR模型的输出比如I曲线和R曲线作为额外特征拼到LSTM的输入里让LSTM学习“真实值和SEIR预测值之间的差异”二是在SEIR框架内让beta、sigma随时间变化变化规律由LSTM预测相当于让神经网络去做参数估计器。两种方案我在项目中都试过。残差修正方案更稳实现起来也更简单把SEIR算出的I(t1)和I(t)作为特征向量的一部分输入到LSTM中。这样一来LSTM不需要从零学习疫情传播的物理规律只需要学习SEIR模型的误差模式比如政策干预后的下降斜率偏差这种偏差在时间序列上是有一定规律的。参数时序化方案的上限更高但训练不稳定对数据量要求也更大建议在数据超过200天后再尝试。4.2 LSTM模型搭建PyTorch实现与关键参数用PyTorch写一个最基本的LSTM回归模型代码比Keras版本稍微多几行但胜在可控性强断点调试也方便。import torch import torch.nn as nn class LSTMPredictor(nn.Module): def __init__(self, input_size, hidden_size64, num_layers2, output_size7): super().__init__() self.lstm nn.LSTM( input_sizeinput_size, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropout0.2 if num_layers 1 else 0.0 ) self.regressor nn.Sequential( nn.Linear(hidden_size, 32), nn.ReLU(), nn.Linear(32, output_size) ) def forward(self, x): out, _ self.lstm(x) # out: (batch, seq_len, hidden_size) out out[:, -1, :] # 只取最后一个时间步的隐状态 return self.regressor(out)几个关键参数说清楚hidden_size64是LSTM隐状态的维度太小拟合能力不足太大容易在样本量少时过拟合num_layers2表示堆叠两层LSTM可以学到更高层的时间抽象但两层以上在几百条数据量级上没有明显收益反而让训练变慢batch_firstTrue表示输入形状是(batch, seq_len, features)和前面create_sequences构造的X形状直接匹配。forward里只取最后一个时间步的隐状态因为目标是预测未来7天需要用当前时刻的隐状态代表序列的信息摘要。4.3 训练与验证训练集、验证集划分和早停训练集和验证集的划分要按时间顺序切不能随机打乱。时序数据里任何随机切分都等于让模型偷看未来数据这会让验证损失失真。通常做法是前70%到80%作训练、后20%作验证。split_idx int(len(X) * 0.8) X_train, X_val X[:split_idx], X[split_idx:] y_train, y_val y[:split_idx], y[split_idx:] def to_tensor(x, y): return torch.FloatTensor(x), torch.FloatTensor(y) def train(model, X_tr, y_tr, X_va, y_va, epochs200, lr0.001): optimizer torch.optim.Adam(model.parameters(), lrlr) loss_fn nn.MSELoss() best_val float(inf) best_state None for epoch in range(epochs): model.train() optimizer.zero_grad() pred model(X_tr) loss loss_fn(pred, y_tr) loss.backward() optimizer.step() model.eval() with torch.no_grad(): val_pred model(X_va) val_loss loss_fn(val_pred, y_va) if val_loss best_val: best_val val_loss best_state {k: v.clone() for k, v in model.state_dict().items()} if (epoch 1) % 20 0: print(fEpoch {epoch1}: train_loss{loss.item():.6f}, val_loss{val_loss.item():.6f}) model.load_state_dict(best_state)训练里最重要的两个习惯一是用验证集上的最优损失来保存模型参数而不是用最后一轮训练结果因为在训练后期模型很可能已经过拟合二是每次epoch开始前要清空梯度optimizer.zero_grad()否则梯度会在多轮间累积。学习率lr0.001是Adam的默认值对这类小规模时序预测任务通常是足够稳的不需要先上学习率调度器如果损失震荡再考虑降到0.0005。值得提醒的是疫情数据通常只有几十到几百天LSTM的样本量是很有限的。模型层数和隐藏单元数不要贪多否则很容易在训练集上损失降到很低、验证集上完全跑偏。这个时候看损失曲线比看预测图更重要。训练集损失一路下降而验证集损失先降后升就是过拟合的典型信号需要减小hidden_size、增加dropout或提前停止。5. SEIRLSTM项目避坑清单四个翻车点与排查思路5.1 现象预测曲线是一条平直线这是最常见的翻车现场。原因有两类一类是归一化时对全量数据做了fit_transform测试集的信息泄漏到了训练里模型看起来“表现很好”实际上预测结果被削平另一类是LSTM输出层用了sigmoid而归一化后的标签范围超出了(0,1)输出被截断到区间边界。解决思路是先检查数据划分和归一化代码确认scaler只对训练数据拟合再检查输出层的激活函数回归任务最后一层应该用线性激活而不是sigmoid。用我上面的LSTMPredictor代码regressor最后一层没有激活函数默认就是线性不会有截断问题。5.2 现象SEIR模拟曲线和真实数据完全对不上SEIR曲线和真实数据的差异主要有三个方面真实新增数据有周期性波动这是报告延迟和检测能力变化导致的SEIR里完全没这个概念真实数据中段会出现斜率突变对应的是防控政策的直接干预SEIR的beta是常数模拟不出来这种突变真实数据在疫情后期的拖尾非常长原因是境外输入、复工导致的二次传播这是SEIR里没有引入新暴露源的假设造成的。解决思路是分段拟合。把数据按政策节点切成两到三段每段单独估计beta比如第一波严格防控前beta可能是0.35全面管控后beta直接降到0.05。然后把分段后的I曲线作为LSTM的额外特征让神经网络去拟合每段内部的误差模式。这比强行用一组参数贯穿全程要靠谱得多。5.3 现象LSTM训练损失不下降训练损失在几十轮内纹丝不动首先要怀疑梯度消失了。LSTM虽然比RNN缓解了长程梯度消失但层数太深或输入序列太长时依然会出问题。排查顺序是先确认输入数据里没有NaN或inf再确认归一化后的数据尺度在(0,1)附近最后确认学习率没有设在0.1这种过大值上。疫情数据通常只有几百个样本损失不降大概率是代码层的数值问题。测试一下梯度是否正常可以在训练循环里手动打印参数梯度for name, param in model.named_parameters(): if param.grad is not None: print(name, param.grad.abs().mean().item())如果某一层的梯度全为零说明流程在反向传播前就中断了检查网络输出和损失函数之间的数据流是否匹配。另外还要确认X的形状如果X变成了二维而不是三维LSTM会直接报维度错误但有些情况下batch_first设置不当输入被静默地广播成错误形状损失曲线表现为剧烈震荡。5.4 现象预测值出现负数和极端值新增确诊预测出负数在LSTM里并不罕见。原因主要是训练样本太少且分布极度不均匀大多数时间是低值偶尔一两天出现峰值模型为了压低均方误差会倾向于输出一个中间偏小的值在峰值之后就出现负值调整。另外一个可能原因是输出层没有夹逼也就是没有对输出做非负约束。处理方式上我一般在后处理阶段把预测值负值截断为0同时在损失函数里加上一个小惩罚项让输出偏离非负区间时受到额外的损失。更根本的做法是转换预测目标不去预测原始新增数而是预测新增数的对数log1p这样把数据从右偏分布拉向接近正态分布预测结果指数还原后不会出现负值。6. 让预测更接近真实参数校准、滚动预测与置信区间6.1 用滚动窗口验证代替一次性划分一次性把数据按80%和20%切分评估结果只有一次运气成分很大。更稳的验证方式是滚动窗口验证每次用过去90天数据训练预测未来7天然后把真实数据吞进来延长窗口再预测下一个7天。这样重复N次最后把N次预测结果拼接起来和真实序列对比得到一份连续的、接近真实部署场景的评估指标。def rolling_evaluate(model_fn, data, window90, step7): preds [] for start in range(0, len(data) - window - step, step): train_data data[start : start window] X, y create_sequences(train_data, input_days14, output_daysstep) model model_fn(X.shape[-1]) train(model, X, y, X, y, epochs100) last_seq data[start window - 14 : start window] pred model(torch.FloatTensor(last_seq.reshape(1, 14, -1))) preds.append(pred.detach().numpy().ravel()) return np.concatenate(preds)滚动评估最大的价值在于暴露模型的稳定性问题——如果某几个窗口的预测误差突然增大通常对应数据本身发生结构变化比如正好跨越了防控政策节点。这种位置正是SEIRLSTM这类混合模型最薄弱的环节也是你需要介入调参的地方。6.2 用实际数据做SEIR参数校准别迷信单一参考值参数校准这一步很多项目做成“从论文里抄一个beta和gamma就用”的方式。这在复现demo时没问题但如果你想让预测结果作为决策参考就必须正面对参数不确定性。SEIR参数估计在多轮优化下常常收敛到局部最优一个保险的做法是多次随机初始化p0保留拟合残差最小的一组。同时把beta在不同阶段的变化用阶梯函数表示每一段单独估计这往往对预测准确率的提升比调LSTM结构更明显。另外一个工程习惯是每天固定时间拉取最新数据对SEIR参数做一次滚动更新然后带着最新参数预测未来14天。这套流程看上去比训练LSTM朴素但在真实疫情预测中参数更新的价值远大于模型结构的调整。6.3 代码里的设计细节从跑通到能给别人用项目说明文件如果写得好通常会在前面列出三样东西运行环境、数据文件格式说明、执行顺序。我自己拿到任何预测类项目源码先确认两件事一是程序里有没有设置随机种子这决定所有实验是否可复现二是模型参数保存路径和日志路径有没有写死在别人的机器上跑会不会因为路径不存在直接报错。# 设置所有可能的随机种子来源 import os, random, torch os.environ[PYTHONHASHSEED] 0 random.seed(42) np.random.seed(42) torch.manual_seed(42) torch.cuda.manual_seed(42) torch.backends.cudnn.deterministic True如果项目源码里没有这五行我一般会顺手补上否则同一份代码两次运行结果不一致后面所有分析都缺少可信度。我在接这类项目时常遇到的坑是作者在GPU环境里跑通代码没有设置设备为CPU到了没有CUDA的机器上直接报找不到显存。加上torch.device(cuda if torch.cuda.is_available() else cpu)这行就能避免这个问题。最后说一个习惯性动作把每天的预测结果和真实值同时存下来追加到同一张表里。跑一个月后再回看误差大的时段基本都是数据口径变化和政策调整的时间点这比争论哪个模型更先进要实在得多。做预测项目不追求单次预测多准而是持续跟踪误差并修正输入特征。希望这个方案的技术细节能帮到你少走我踩过的弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询