ARTICLE DETAIL

资讯详情

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

疫情预测代码实战:从数据清洗到GRU模型避坑指南

疫情预测代码实战:从数据清洗到GRU模型避坑指南 简介这份资源面向数据分析初学者、公共卫生方向学生及希望练习真实数据项目的开发者提供一套完整的新冠疫情分析与预测代码方案解决从数据获取到建模预测的全流程学习需求。包内共1089个文件以513个Python源码和508个编译文件为主体另含17个CSV数据集、17个可执行程序及少量配置与说明文件压缩包约19.3MB结构清晰便于按模块查阅。目前已有3562人学习下载具备一定参考热度。读者可从中获得疫情数据抓取与预处理脚本、基于matplotlib与PyEcharts的柱形图、折线图、地图、玫瑰图及动态条形图可视化实现以及使用SIR模型对美国疫情数据进行模拟预测的完整代码适合作为课程设计、竞赛练习或数据分析入门的实战素材。1. 疫情预测代码到底在算什么从一份 COVID-19 数据集说起2020 年春天我接到一个需求用公开的 COVID-19 数据做未来 7 天的累计确诊预测。当时我以为这是个标准的时序回归问题拉个 LSTM 跑一遍就完事。结果第一版模型在测试集上的 MAE 是 3400而数据本身的日均新增才 800 左右——模型比瞎猜还离谱。后来花了整整两周排查才发现问题根本不在模型结构上而在数据对齐和评估口径上。这份 COVID-19 prediction 代码要解决的核心问题其实很具体给定约翰斯·霍普金斯大学JHUCSSE 发布的全球确诊、死亡、恢复时间序列构建一个能预测未来若干天累计确诊的模型并给出可复现的评估流程。它适合两类人一是想拿真实公共卫生数据练手时序建模的算法工程师二是需要快速搭建预测基线、再逐步迭代的研究者。数据集通常包含全球各国从 2020 年 1 月 22 日起的逐日累计值字段包括 Province/State、Country/Region、Lat、Long 以及按日期展开的确认数。代码层面常见做法是用 Python Pandas 做数据清洗用 PyTorch 或 TensorFlow 搭建 LSTM/GRU/Transformer 类模型最后用 MAE、RMSE、MAPE 三个指标做评估。但真正决定成败的是下面这几件事你有没有做对。2. 数据加载与清洗JHU 原始表为什么不能直接喂给模型2.1 原始数据的三个结构性问题JHU 的 CSSE 数据集以宽表形式发布每一列是一个日期每一行是一个地区。直接读进来会碰到三个麻烦第一列名是字符串日期Pandas 默认当 object 处理做时间切片时容易翻车第二部分国家有 Province/State 层级比如中国有省份、美国有州、澳大利亚有州而另一些国家该字段为空导致同一国家出现多行第三累计值在早期存在回退现象——某些地区某天报的累计数比前一天少这不是数据错误而是统计口径调整或补报修正。我一般会先做一次“最小可用数据集”的构建把宽表转成长表按国家聚合再统一日期索引。下面这段代码是我反复用过的一个清洗模板import pandas as pd import numpy as np def load_jhu_confirmed(filepath: str) - pd.DataFrame: 读取 JHU confirmed 宽表返回按 (Country, Date) 聚合的长表。 filepath: 例如 time_series_covid19_confirmed_global.csv df pd.read_csv(filepath) # 1. 丢弃 Lat/Long保留地区标识 df df.drop(columns[Lat, Long], errorsignore) # 2. 宽表转长表日期列全部 melt 成行 date_cols [c for c in df.columns if c not in [Province/State, Country/Region]] long_df df.melt( id_vars[Province/State, Country/Region], value_varsdate_cols, var_nameDate, value_nameConfirmed ) # 3. 日期解析 按国家聚合忽略省份层级 long_df[Date] pd.to_datetime(long_df[Date], format%m/%d/%y) long_df long_df.groupby([Country/Region, Date], as_indexFalse)[Confirmed].sum() long_df long_df.rename(columns{Country/Region: Country}) # 4. 处理累计值回退用前向填充 单调修正 long_df long_df.sort_values([Country, Date]) long_df[Confirmed] long_df.groupby(Country)[Confirmed].cummax() return long_df这段代码的关键在最后一行cummax()。累计确诊理论上只增不减任何回退都视为统计修正用累积最大值抹平。参数上format%m/%d/%y是 JHU 早期文件的日期格式如果你用的是更新版数据可能是%Y-%m-%d需要先看一眼原始列名再改。groupby时我选择忽略 Province/State因为大多数预测任务只到国家粒度如果你要做美国州级预测把Province/State加回 groupby 键即可。2.2 训练集/验证集/测试集怎么切才不泄露未来信息时序预测最忌讳随机切分。我见过有人用train_test_split(shuffleTrue)切疫情数据结果验证集里混入了未来日期的样本模型在验证集上表现极好上线后直接崩掉。正确做法是按时间切比如用 2020-01-22 到 2021-06-30 做训练2021-07-01 到 2021-09-30 做验证2021-10-01 到 2021-12-31 做测试。更严格一点验证集和测试集之间留一个 gap防止滑动窗口把边界样本泄露过去。def temporal_split(long_df: pd.DataFrame, train_end: str, val_end: str): train long_df[long_df[Date] train_end].copy() val long_df[(long_df[Date] train_end) (long_df[Date] val_end)].copy() test long_df[long_df[Date] val_end].copy() return train, val, test参数说明train_end和val_end是字符串日期建议用2021-06-30这种 ISO 格式避免解析歧义。切完之后每个国家的序列长度可能不同后续做滑动窗口时需要按国家分别处理不能直接 concat 后统一 reshape。2.3 缺失值与零值不是所有空值都该填JHU 数据里早期很多国家没有报告表现为空值或 0。这里有个血泪经验不要用全局均值填充。疫情数据里0 和缺失是两回事——0 表示当天确实没有新增缺失表示没有上报。我一般会先按国家做前向填充再对仍然缺失的头部用 0 填充因为一个国家在首次报告之前累计确诊就是 0。long_df[Confirmed] long_df.groupby(Country)[Confirmed].ffill().fillna(0)这行代码的顺序不能反先ffill再fillna(0)。如果先填 0会把中间断报的日期也变成 0导致累计值出现平台期模型会学到错误的“停滞”模式。3. 模型选型与训练LSTM、GRU 还是 Transformer3.1 为什么我最终选了 GRU 而不是 LSTM在 COVID-19 预测这个任务上我试过 LSTM、GRU、Temporal Fusion Transformer 和简单的 ARIMA。结论可能反直觉在数据量有限全球 200 多个国家每个国家约 700 天的情况下GRU 的验证集 MAE 比 LSTM 低约 8%训练速度快 30%。原因不复杂——GRU 参数更少在中小规模时序上过拟合风险更低。Transformer 类模型需要更长的序列和更多数据才能发挥优势直接套用在这里注意力权重很难学到有意义的模式。我的基线模型结构是这样的输入是过去 14 天的累计确诊经过 log1p 变换输出是未来 7 天的累计确诊。中间两层 GRU隐藏维度 64dropout 0.2最后接一个全连接层映射到 7 维输出。import torch import torch.nn as nn class GRUPredictor(nn.Module): def __init__(self, input_dim1, hidden_dim64, output_dim7, num_layers2, dropout0.2): super().__init__() self.gru nn.GRU( input_sizeinput_dim, hidden_sizehidden_dim, num_layersnum_layers, batch_firstTrue, dropoutdropout ) self.fc nn.Linear(hidden_dim, output_dim) def forward(self, x): # x: (batch, seq_len14, input_dim1) out, _ self.gru(x) # 取最后一个时间步的隐藏状态 last out[:, -1, :] return self.fc(last)参数说明input_dim1是因为我只用了累计确诊一个通道如果你要加入死亡数、恢复数、人口密度等协变量把input_dim改成对应维度并在数据管道里做多列拼接。output_dim7对应预测未来 7 天如果你想预测 14 天改这个数就行。dropout0.2是我在验证集上网格搜索出来的再高会欠拟合再低会过拟合。3.2 log1p 变换为什么你的损失曲线会爆炸累计确诊的数值范围从 0 到几千万直接做 MSE 损失梯度会被大国主导小国的预测完全被忽略。我一开始没做变换结果模型对所有国家都预测成“美国模式”小国误差巨大。后来加了log1p损失曲线立刻平稳了。def preprocess_series(series: np.ndarray) - np.ndarray: return np.log1p(series) def inverse_transform(pred: np.ndarray) - np.ndarray: return np.expm1(pred)注意log1p和expm1是配对使用的不要用log和exp否则 0 值会变成 -inf。评估的时候一定要先做逆变换再算 MAE/RMSE否则你报的指标是 log 空间的没有实际意义。3.3 训练循环里的三个必调参数训练循环本身不复杂但有三个参数直接决定你能不能复现出合理结果。第一个是学习率我一般从 1e-3 开始配合ReduceLROnPlateau耐心值设 5因子 0.5。第二个是 batch size不要设太大32 或 64 就够了因为每个国家的序列长度不同padding 之后有效样本比例会下降。第三个是梯度裁剪clip_grad_norm_设 1.0防止早期梯度爆炸。optimizer torch.optim.Adam(model.parameters(), lr1e-3) scheduler torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, patience5, factor0.5) criterion nn.MSELoss() for epoch in range(100): model.train() for x_batch, y_batch in train_loader: optimizer.zero_grad() pred model(x_batch) loss criterion(pred, y_batch) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0) optimizer.step() # 验证集评估后 step scheduler scheduler.step(val_loss)这段代码里clip_grad_norm_的位置在backward()之后、step()之前顺序不能错。scheduler.step(val_loss)需要传入验证损失所以你要在每个 epoch 结束后先跑一遍验证集。4. 避坑与排查疫情预测代码里最容易翻车的五个地方4.1 现象验证集损失比训练集低很多原因时序切分时验证集和训练集有重叠日期或者滑动窗口的 stride 设得太小导致验证集样本和训练集样本高度相似。解决检查切分边界确保train_end val_start并且在构建 Dataset 时验证集的窗口起点必须大于训练集的最大日期。4.2 现象预测曲线是一条直线原因模型输出被 log1p 变换后的均值主导或者学习率太低导致模型没学到任何东西。解决先检查输入数据是否已经做了 log1p再检查模型最后一层的 bias 是否初始化为 0。我一般会把fc.bias初始化为训练集最后一天的平均值这样模型至少从一个合理起点开始。4.3 现象某些国家预测误差极大原因小国或早期数据稀疏的国家序列长度不足 14 天padding 之后全是 0模型无法学到有效模式。解决在数据管道里过滤掉序列长度小于 21 天的国家或者对短序列做上采样。我一般会设一个阈值少于 30 天数据的国家直接不参与训练但在评估时单独报告。4.4 现象MAE 很小但 MAPE 巨大原因MAPE 在真实值接近 0 时会爆炸而疫情早期很多国家累计确诊就是 0 或个位数。解决不要用 MAPE 作为主指标改用 MAE 和 RMSE。如果一定要报 MAPE先过滤掉真实值小于 10 的样本并在论文或报告中注明。4.5 现象训练 loss 震荡不收敛原因batch 内不同国家的数值尺度差异太大即使做了 log1p小国和大国的梯度仍然不平衡。解决按国家做分层采样或者用WeightedRandomSampler给每个国家一个权重权重和该国最后一天累计确诊的倒数成正比。这样小国在 batch 里的出现频率会提高。5. 进阶技巧用多步滚动预测替代单次输出5.1 为什么直接输出 7 天不如滚动预测稳我一开始的模型是一次性输出未来 7 天后来发现一个问题第 7 天的误差总是比第 1 天大很多而且误差会累积。改成滚动预测后每一步只预测下一天然后把预测值拼回输入序列再预测下一天。这样模型在每一步都能看到“最新”的输入误差累积更慢。代价是推理速度变慢7 天预测需要跑 7 次前向传播。def rolling_predict(model, initial_seq, steps7): initial_seq: (1, 14, 1) 的初始输入 返回: (steps,) 的预测序列 model.eval() seq initial_seq.clone() preds [] with torch.no_grad(): for _ in range(steps): next_val model(seq) # 输出维度为 1 时 preds.append(next_val.item()) # 把预测值拼到序列末尾去掉最早的一天 seq torch.cat([seq[:, 1:, :], next_val.view(1, 1, 1)], dim1) return np.array(preds)注意这个函数假设模型输出维度是 1。如果你用的是前面那个输出 7 维的模型需要改一下每次只取第一个输出或者重新训练一个单步预测模型。我一般会单独训一个单步模型专门用于滚动预测效果比复用多步模型好。5.2 评估滚动预测的两种口径滚动预测的评估有两种做法。第一种是“开环”每一步都用真实值拼回输入只评估单步预测能力。第二种是“闭环”每一步用模型自己的预测值拼回输入评估多步累积误差。我一般两个都报开环看模型单步精度闭环看实际部署效果。如果闭环误差远大于开环说明模型对自身误差的鲁棒性不够需要加入噪声训练。评估口径输入来源适用场景典型 MAE 差距开环真实值诊断模型单步能力基准闭环模型预测值模拟真实部署比开环高 20%40%5.3 一个我常用的验证习惯每次改完模型或数据管道我会先跑一个“最小复现测试”只取 5 个国家、100 天数据训练 10 个 epoch看 loss 是否下降。如果这个最小测试都跑不通说明代码有结构性 bug不用浪费算力跑全量。这个习惯帮我省了至少几十个小时的 GPU 时间。另外我习惯把每次实验的配置文件、随机种子、验证集 MAE 记在一个 CSV 里方便回溯。疫情预测这个方向数据更新频繁模型迭代快没有实验记录两周后你根本记不清哪个版本对应哪个结果。希望帮到你。本文还有配套的精品资源点击获取
返回列表