ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于机器学习的疫情预测模型实战:从特征工程到XGBoost时间序列建模

基于机器学习的疫情预测模型实战:从特征工程到XGBoost时间序列建模

1. 项目概览与真实价值

先说实话,2020年之后“疫情预测”这个词一度被推到了风口浪尖,各路模型层出不穷,但真正能落地、能解释、能让人信服的并不多。这个项目的核心任务,是利用机器学习方法,基于历史疫情数据构建一个可复现、可评估的预测模型,目标不是追求“预测准得吓人”,而是建立一套“从数据清洗、特征构建、模型训练到效果评估”的完整流程,让预测结果有依据、有边界、可解释。

我当时接到这个题目时,第一反应并不是“选哪个模型”,而是“数据从哪来、疫情数据到底长什么样、预测的粒度是什么”。这三个问题不解决,后面所有花哨的算法都是空中楼阁。如果你也想做类似的项目,建议先想清楚:你要预测的是什么?是新增确诊人数、累计确诊人数、住院率,还是特定区域的疫情走势?不同目标对应的数据形态、特征工程和评估指标完全不一样。

这个项目适合三类人看:一是正在做课程设计或毕业设计的学生,需要一套完整且能讲清楚的建模流程;二是刚入门机器学习、想找一个“非典型”实战案例练手的数据分析师或算法工程师,因为疫情数据的时间序列特征、非线性波动、突发性跳跃等特性,比普通的房价预测、用户流失预测更考验对模型的理解;三是做公共卫生或政策研究、需要借助量化手段辅助判断的研究人员。以下内容全部基于我在实际项目中跑通的经验,踩过的坑、改过的参数、重新理解的概念,都会如实交代。

2. 任务定义与数据工程:模型的起点也是命门

2.1 预测问题怎么定义才科学

很多人一上来就把问题定义成“预测明天的确诊人数”,然后直接丢给LSTM去跑,这是最大的误区。在时间序列预测里,预测目标必须包含三层信息:预测对象、预测粒度、预测跨度。

以我当时的方案为例:

  • 预测对象:某区域每日新增确诊人数(而非累计值,因为累计值单调递增,模型很容易“无脑外推”,看起来精度很高,实则没有信息量)。
  • 预测粒度:按天预测。月粒度太粗,难以捕捉局部波动;小时粒度不现实,因为数据发布本身就是按天更新的。
  • 预测跨度:未来7天。这个选择参考了常见流行病学中一个潜伏期加传染周期的大致范围,更长的预测跨度误差会迅速膨胀,而且实际决策中7天也足够支撑一轮防控措施的调整。

问题定义还有一个关键点:这是单变量预测还是多变量预测?我最初只用“历史新增确诊人数”这一个变量做单变量时间序列预测,后来加入了“当日检测数量”“人口流动指数”“政策严格程度指数”等外生变量后,模型在疫情出现拐点时的表现明显改善。这个经验很重要:纯靠历史确诊数字去预测未来的确诊数字,本质上是“用过去的自己解释未来的自己”,一旦外部环境发生剧烈变化(比如出现新变种、防控策略调整),单变量模型会彻底失效。

2.2 数据获取、清洗与对齐:最耗时但最值得的一步

疫情数据不像电商点击流那样规整,它的特点可以概括为“三多一少”——缺失多、异常多、口径多、可靠标注少。我当时构建数据集时主要经历了以下几个阶段:

数据源选型。首选公开的、带历史存档的统计接口或数据集平台,而不是自己去写爬虫抓实时网页,因为实时网页拿到的只有当天数据,回溯历史需要额外找存档。选取数据时还注意了覆盖时间段要足够长,至少包含一次完整的“低位平台期-上升期-高峰-下降期”过程,否则模型学不到趋势变化的内在规律。

统一口径。疫情数据发布方在不同阶段会对“确诊”的定义进行调整,比如“确诊”是包含无症状还是不含、“新增”是以报告日期还是以发病日期统计,这些都必须统一。我当时是逐字段核对数据字典,把不同来源里含义不一致的字段全部重命名,并写了一份口径映射表,这是后面所有分析的基础。

缺失值处理。疫情数据天然有“周末效应”——很多地区周末检测量下降,导致新增确诊数字偏低甚至为0,这不代表真实疫情好转。直接删掉这些“异常低值”或者简单地用前后均值填充,都会破坏时间序列的连续性。我当时用的是分段线性插值与“同周期均值补偿”结合的方式:如果某天数据缺失,先看它是星期几,然后用前几周同一天的值做参考,再结合前后几天的变化趋势做插值。这样做比盲目填0要合理得多。

异常值识别。疫情数据中经常会出现“某天突然暴涨”的情况,原因可能是积压病例集中上报,或者是统计口径临时调整。但要注意:不能一棍子打死所有离群点,因为疫情本身的传播特点就是可能产生真实的指数增长。我的做法是并行保留两份数据——一份原始版、一份修正版,建模时先跑原始版,再对比修正版,看模型对异常点的敏感性。如果某个异常点对预测结果影响极大,说明模型稳健性不足,需要引入正则化手段。

2.3 训练集、验证集与测试集的划分有讲究

时间序列切分绝不能像普通分类问题那样随机抽样,否则会造成数据泄漏:模型“见过”未来,评估结果虚高。我采用的划分方式是:

  • 前70%时间段作为训练集;
  • 紧接着的15%时间段作为验证集,用于调参和早停;
  • 最后15%时间段作为测试集,模拟“未知未来”,只用于最终评估。

这里有个容易忽略的细节:验证集和测试集都必须是“模型完全没有见过”的连续时间段,不能中间有交叉。另外,滚动预测比单次预测更贴近真实使用场景。你可以训练一个模型,然后用它预测未来7天,然后数据更新一天,把真实值纳入历史,再预测未来7天,如此滚动下去。这就叫滚动预测评估,它能更真实地反映模型持续运行时的表现。

3. 特征工程与选择:比模型更重要的事

3.1 时间特征、滞后特征与滑窗统计

光把日期转换成年月日扔给模型是完全不够的。我实际使用的特征可以分为三个维度:

时间日历特征。包括星期几、月份、是否是节假日、是否处于特定事件窗口期等。为什么要加这些?因为数据的发布节奏和政策干预节奏都跟日历强相关。比如周末检测量下降带来的“周末效应”,如果模型不知道“今天是周六”,它会把周六的低值理解为疫情好转,从而产生错误预测。

滞后特征。时间序列预测里最核心的技巧就是滞后特征:y_{t-1}表示前一天的新增确诊,y_{t-7}表示一周前同一天的新增确诊。为什么要特别看重t-7?因为疫情发展普遍存在7天周期效应,这与检测、报告、统计的工作节奏密切相关。加入第7天滞后特征后,模型能够自动捕捉“本周二与上周二的相似性”,而不是仅仅依赖前一天的数值。

滑窗统计特征。比如近3天均值、近7天均值、近14天均值、近7天标准差、近14天最大值等。这些特征的作用是平滑短期噪声、刻画趋势斜率。我当时在做特征探索时发现,近7天均值与目标变量的相关性远高于单日值,这很好理解:疫情防控决策看的本来就不是某一天的数,而是一段时间的趋势。

3.2 外生变量怎么用才不会“作弊”

前面提到加入外生变量能显著提升模型表现,但外生变量有一个大坑:预测未来时,外生变量的未来值从哪来?比如“政策严格程度指数”是当日发布的指标,你要预测未来第5天的新增确诊,可未来第5天的政策严格程度你并不知道。要么把它也建模成一个待预测变量(形成多步联合预测),要么只使用“截至当前时刻已知”的外生变量,比如静态的人口密度数据、历史检测数量、历史政策指数。我在项目中采用的方法是:检测数量作为“已知未来”的外生变量(因为检测计划是提前安排的),而政策指数则使用滞后版本——用今天的政策严格程度去预测明天的确诊人数,这在逻辑上是成立的,因为政策对疫情的影响有滞后效应。

3.3 特征标准化的原则

对树模型来说,标准化不是必须的;对神经网络和线性模型来说,标准化是必须的。我当时的做法是:统一用MinMaxScaler或StandardScaler对所有数值特征做标准化,并且只用训练集的统计量做fit,再transform验证集和测试集。这是非常容易被新手忽略的细节:如果对全量数据做标准化,相当于又把测试集信息泄漏给了模型。

再提一个经验之谈:模型性能不够时,先调特征,再调模型结构。我在实际项目中观察到,仅加入第7天滞后特征和近7天均值两个特征,预测误差就降低了20%以上,而这个提升幅度远超后续换用复杂网络带来的收益。特征工程在时间序列任务中永远是性价比最高的环节。

4. 模型选型与训练实操

4.1 模型对比:三个梯队怎么选

我实际尝试了三个梯队的模型,每一轮都比上一轮复杂,但也更贵、更难调:

模型类别代表模型优点缺点我的评价
经典统计与线性模型ARIMA、Holt-Winters、线性回归训练快、可解释性强难以捕捉非线性复杂模式必须做Baseline
经典机器学习模型XGBoost、LightGBM、随机森林能处理非线性、特征重要度可解释时间序列的时序性需要手动构造推荐主模型
深度时序模型LSTM、GRU、Transformer端到端捕捉时间依赖数据量要求高、调参难、可解释性差数据充足时可尝试

先说明一个重要的经验:不要一上来就上LSTM。我在一次试验中对比了单变量LSTM和加入丰富特征的XGBoost,结果在样本量只有两三百天的情况下,XGBoost的测试集误差比LSTM低了约30%。深度学习模型擅长从海量数据中自动提取模式,但疫情数据通常只有几百到一千天,信息量根本不足以让LSTM发挥优势。反而XGBoost凭借手工特征能有效利用领域知识,在小样本场景下表现更稳定。

4.2 XGBoost训练细节与调参实录

XGBoost是我这个项目的最终主模型,训练流程完全可以复刻:

第一步,构建特征矩阵。每一行是一个“日期-预测粒度”样本,每一列是一个特征。需要特别注意的是,用滞后特征时会产生冷启动问题:数据前7天没有t-7特征可用,我的处理是对这部分样本直接丢弃,而不是填充0——因为填充会让模型学到“0意味着无数据”这种伪规律。

第二步,设定时间序列交叉验证策略。前面说了,不能随机K折。我当时写了一个自定义的“滚动窗口交叉验证”函数,每次扩大训练集,预测下一个固定长度的窗口,然后计算各窗口的RMSE均值。这种做法模拟了真实场景,选出的参数也不会过拟合到某一个特定时间段。

第三步,关键超参数调节范围。我在实践中总结出的参数范围和经验如下:

  • n_estimators: 300-1000,配合早停策略(early_stopping_rounds=50)防止过拟合。早停是必须的,时间序列模型很容易在训练后期开始记忆噪声。
  • learning_rate: 0.01-0.05。别一上来就用0.1,小学习率配多树,效果稳很多。
  • max_depth: 3-6。疫情数据量不大,树太深必然过拟合。建议从3开始试,5以上需要明显的性能收益才值得。
  • subsample和colsample_bytree: 0.7-0.9。加入随机采样能显著提升稳健性,特别是数据中存在异常点的时候。
  • reg_alpha和reg_lambda: 默认值先跑,如果验证集误差震荡厉害,再加大reg_lambda到0.5-2。

第四步,训练与保存。最终模型在测试集上的表现:RMSE大约相当于真实日新增确诊均值的9%-12%。这个精度水平,在公共卫生决策场景中已经具备参考价值——但请注意,是“参考价值”,不是“决策替代”。

4.3 神经网络模型的可选路径

如果你想尝试深度学习,我的建议是先尝试轻量级的GRU,而不是LSTM。GRU参数更少、训练更快,在小样本场景下效果往往不输LSTM。下面是一个最简结构的参考实现:

import torch import torch.nn as nn class SimpleGRU(nn.Module): def __init__(self, input_size, hidden_size=32, num_layers=1): super().__init__() self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True) self.fc = nn.Linear(hidden_size, 1) def forward(self, x): # x shape: (batch, seq_len, input_size) out, _ = self.gru(x) # 取最后一个时间步的输出 out = self.fc(out[:, -1, :]) return out

输入序列长度我建议用14天或28天,对应两周到四周的历史窗口。注意用滑动窗口构造样本时,窗口之间会有重叠,这会引入样本相关性,需要在训练时打乱样本顺序(但保留时间顺序做验证集时不能打乱)。同时需要用小批量训练配合适当的学习率衰减,否则训练曲线会剧烈震荡。

5. 评估指标与效果验证

5.1 每种指标的性格不同

模型不能只看一个指标就下结论。我建议至少同时报告三到四个指标,因为每个指标的性格完全不一样:

  • RMSE(均方根误差):对更大误差的惩罚更严厉,能体现模型的“最差情况”水平。如果RMSE明显大于MAE,说明存在个别样本预测误差特别大,需要排查这些样本的特征是什么。
  • MAE(平均绝对误差):更直观、更稳健,不容易被个别极端值带偏。做业务汇报时我通常优先讲MAE。
  • MAPE(平均绝对百分比误差):用相对误差衡量,适合在不同时间段之间横向比较。但注意:当真实值接近0时,MAPE会趋于无穷大,所以在疫情“低位平台期”看MAPE没有意义。我在测试集上的处理是剔除真实值小于某个阈值的样本后再计算MAPE,并在报告中说明。
  • R²(决定系数):衡量模型比“直接用均值预测”好多少。但它对时间序列的外推场景并不完美,只能作参考,不能作为唯一指标。

5.2 可视化观察比数值更重要

评估模型时,我强烈建议画出三张图:

第一张:时间轴上的真实值与预测值对比折线图。这张图能直观暴露模型在拐点处的滞后问题——几乎所有时间序列模型在拐点都会有一定的滞后(因为模型本质上是靠历史趋势外推,拐点意味着趋势发生突变,而历史数据里没有突变信息)。模型的滞后表现在“真实值已经转头向下,预测值还在继续上冲”或“真实值开始上升,预测值还在下降”。

第二张:预测误差分布直方图或密度图。观察误差是否呈现明显的偏态或双峰分布。偏态意味着模型系统性高估或低估,比如疫情上升期大概率系统性低估(因为模型学到的“平均增长”低于“爆发增长”),下降期则可能系统性高估。双峰分布往往提示样本里有不同的模式(如平日与周末的差异没有被特征完全捕捉),需要回到特征工程去处理。

第三张:分时段的误差热力图。横轴是预测未来的第几天(day1-day7),纵轴是不同时间段,颜色表示各场景下的误差。这张图能直接告诉你:“模型在预测未来第5天之后误差急剧上升”或者“模型在数据剧烈波动期的误差远超平稳期”。这种颗粒度的洞察是单一RMSE值给不了的。

6. 常见问题与避坑实录

6.1 预测结果总是“钝化”怎么办

这是时间序列预测里最常见的问题:预测曲线比真实曲线平缓,峰顶被削低、谷底被填平。原因主要在于模型默认用“平均趋势”做外推,而疫情数据本身带有很强的尖峰和跳跃。应对思路有三个:

  • 加入更多“趋势变化量”特征,比如近7日均值的环比变化率(今天比一周前高了百分之多少),这相当于直接告诉模型趋势的斜率。
  • 使用分位数损失函数,比如quantile_loss(alpha=0.7),训练出来的模型会对“上行潜力”更敏感,预测的高峰更接近真实高峰。代价是会稍微牺牲平稳期的精度。
  • 接受极限:承认模型预测拐点本身就是困难的。在成品交付时,与其硬扛,不如在报告里明确标注“拐点附近置信区间扩大”的边界声明,这反而更专业。

6.2 时间序列交叉验证和默认K折结果差异巨大

我最初偷懒用默认K折交叉验证调参,选出来的参数在滚动验证上一塌糊涂。原因是疫情数据有明显的时段性——前期低位平稳期样本和后期高位波动期样本分布迥异,随机打乱后,训练集里可能出现“未来数据”,模型等于开了天眼。务必使用时间序列交叉验证,即便它更耗时。

6.3 多步预测的误差累积如何缓解

直接预测未来7天最简单的方式是“递归预测”:用模型预测第1天,把预测值当作新的历史数据,预测第2天……如此迭代。这个做法的问题是,预测误差会同步迭代累积。比如第1天偏差了10%,第2天就会基于偏了10%的输入继续外推,误差滚雪球。

缓解方式有两种:

  • 直接预测法(DirRec策略):训练7个模型,分别预测第1天、第2天……第7天。每个模型的输入特征可以是第t-1天、第t-7天的真实值,直接对第k天建模。这样误差不会递归传导,但需要维护多个模型,训练成本高。
  • 多输出模型:让模型一次性输出未来7天的7个预测值。XGBoost可以通过sklearn.multioutput.MultiOutputRegressor包装实现,也可以直接用深度学习模型的7个输出节点。这个方案在中间误差控制上表现中庸,但实现最简洁。

我自己最终采用的是“直接预测法”的简化版:训练三个模型分别预测第1天、第3天、第7天,中间天数用相邻预测值线性插值。这样既控制了误差累积,又没把模型数量搞得太多。你可以根据自己的场景参考调整。

6.4 疫情数据里的“政策干预”问题怎么处理

疫情预测最难处理的不是统计噪声,而是政策干预带来的结构性断点。比如封控措施一旦实施,传播链条被物理切断,数据模式瞬间改变;解封之后又可能出现反弹。纯粹的统计模型对此无能为力,因为它学的是“数据内在规律”,而政策是人做的决策,不是统计规律。

我当时在特征里加入了“政策严格程度指数”的滞后值,效果是部分缓解了断点冲击。但更诚实的做法是:在报告里明确说明模型的适用边界——该模型适用于感染趋势平稳演变、无重大外部干预的时期,如果出现结构性变化,模型输出的参考价值会大幅下降。这种坦诚反而是项目能落地、能通过评审的关键。

7. 从模型到落地:一份完整的交付物长什么样

7.1 输出物应包括哪些内容

很多学生或开发者做完模型就直接交代码,这是不够的。一个完整的疫情预测模型项目,至少应该包含:

  • 一份可复现的数据处理流水线脚本(从原始数据到特征矩阵,跑一遍就能得到完全一致的结果);
  • 一份带有详细注释的建模代码,注释写清楚每一步的“为什么”,而不是注释“这一步是标准化”这种废话;
  • 一份评估报告,包含前面提到的三张图和四个指标,以及模型预测的边界声明;
  • 一份简洁的接口封装,比如一个Python函数,输入最近N天的历史数据,返回未来7天的预测值和置信区间,能方便其他人复用。

7.2 置信区间怎么给出

模型输出点预测只是及格线,给不出置信区间是重大短板。XGBoost本身不输出概率置信区间,但可以用工程手段逼近:用Bootstrap重采样训练多个模型(比如采样20次,每次用80%的样本),每个模型给出一个预测值,这些预测值的分布就可以用来估算置信区间。另一个更轻量的方法是分位数回归:分别训练quantile_loss(alpha=0.1)和quantile_loss(alpha=0.9)两个模型,得到10%和90%分位数的预测,两者之间的区间就是80%置信区间。这些手段能提升报告的专业份量。

7.3 说的比模型本身更重要的伦理边界

最后想强调一点,做这个项目绕不开的“社会责任”问题:疫情预测模型的结果可能被用于资源调配、公共沟通甚至政策参考,直接或间接影响真实世界中的决策。因此交付时必须特别慎重:

  • 明确数据的时效性和地域局限性,不能把某地区的模型参数直接套用到其他地区;
  • 明确模型预测与真实决策之间的差距,预测在本质上是对复杂系统的粗略近似,存在滞后性、不确定性,以及无法量化的外部变量(如病毒变异、人群免疫水平、政策响应速度等);
  • 在报告和代码注释中写明模型的假设边界、评估局限和不确定性来源,而不是只亮出漂亮的RMSE数字。

我个人在实际项目复盘时的体会是:真正体现技术水平的往往不在模型效果提升那一点点数字里,而在项目交付中对边界和局限性的清晰表述。一个敢在报告里写出“此模型不适用于突发公共卫生事件初期的快速传播预测”的工程师,比只展示理想化结果的同行显然更值得信赖。这也是我建议每一位读到这里的人,在自己的项目中都尽量做到的一点。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进