算起来,用深度学习做病毒感染人数预测这个方向,我前前后后摸索了大半年。最初是在一个数据分析任务里,需要根据每天更新的新增感染人数,预测接下来一周的感染规模变化趋势。这个任务听起来就是典型的时间序列预测,但真正落地时你会发现,数据里全是噪声、统计口径切换和外部干预的痕迹,任何一个环节处理不好,模型的预测结果就废了。这篇文章我会把项目从问题定义、数据准备、模型设计到调参排错的全过程拆开聊,核心是给拿到同类任务的朋友一个可以直接参考的路线。技术栈以Python和TensorFlow为主,重点讲LSTM的实现思路,也会坦白讲深度学习模型在传染病场景里的局限,适合有Python基础、正在入门时序预测或准备做机器学习落地项目的开发者参考。
1. 项目整体设计:从问题定义到模型选型
1.1 这种预测任务到底在预测什么
病毒感染人数预测,本质上是一个回归任务,不是分类任务。我们要输入一串历史观察值,预测未来N天的新增感染人数或累计感染人数。这个“预测未来”的过程,其实是在拟合一个时间演化函数,函数输入是过去若干天的序列和外部特征,输出是未来的一个数值或一组数值。
这个任务里有一个非常容易混淆的点:单步预测和多步预测。单步预测是给定历史T天的数据,预测第T+1天的人数;多步预测是给定历史T天数据,直接预测接下来N天的人数。刚开始我做的是单步预测,把预测值拼到历史序列尾巴上再迭代预测,结果误差像滚雪球一样越滚越大,后面才改成直接预测一整段未来序列。这一步决策直接影响模型结构和训练标签的构造方式,算是整个项目里第一个关键选择。
从数据角度看,每日新增感染人数有几个明显特征。第一是非平稳性,疫情传播有上升期、平台期和回落期,均值一直在变化。第二是强自相关性,今天的人数与昨天、前七天高度相关,这种周期性和滞后性非常明显。第三是外部干预的突变性,一旦出现管控升级或假期流动高峰,曲线会突然拐弯。这些特征决定了简单模型很难胜任,必须选择能捕捉时序依赖和突变的方案。
1.2 为什么选择深度学习而不是传统模型
做感染人数预测,绕不开几个传统模型,我也都试过一遍,踩了一圈坑之后才确定用LSTM。
先看SEIR这类仓室模型,它们把人群按易感者、暴露者、感染者、康复者分类,用微分方程模拟传播过程。这种模型最大优势是有传播机制的可解释性,参数带宽有流行病学含义。但问题也很明显:参数需要人工估计,每个地区的传播特征不一样,外部干预的作用难以精确建模,拟合不好就是“过拟合式校准”。
再看统计时序方法,ARIMA和Prophet,它们对线性趋势和周期性有很好的拟合能力,尤其是Prophet自动处理节假日、周效应,用起来很方便。但这类模型对非线性突变不敏感,遇到感染人数短期内暴增的场景,预测结果往往严重滞后。XGBoost之类的树模型我也测过,加了滞后特征和滑动平均后效果不差,但外推能力弱,测试集一旦出现训练集里没见过的分布,预测值会掉得很夸张。
深度学习模型特别是LSTM的优势,在于能自动学习序列里的非线性依赖和多因子关系。我不需要手工指定滞后阶数和交互项,网络能自己决定关注过去第几天、忽略第几天。加上它天然支持外部特征输入,比如当天检测量、节假日标记、疫苗接种覆盖率,可以和序列特征拼在一起训练。
我整理了一个简单的对比表,方便理解选型逻辑:
| 模型 | 适用场景 | 核心优势 | 主要缺陷 |
|---|---|---|---|
| SEIR/SIR | 传播机制已知、早期阶段 | 可解释、有流行病学意义 | 参数需人工校准,干预难建模 |
| ARIMA | 线性、平稳时序 | 简单、可解释 | 非线性映射能力弱 |
| Prophet | 趋势+周期明显的日报 | 自动处理节假日 | 突变和干预响应慢 |
| XGBoost | 有丰富特征的表格数据 | 非线性强、训练快 | 纯外推能力差 |
| LSTM | 非线性、多因子时序 | 自动特征学习、可融合多源输入 | 数据量要求高、调参复杂 |
当然,深度学习不是万能的。它需要足够的训练样本,而我手里实际的可用数据常常只有几百天,这在时序任务里是很小的量。后面我会专门讲在小样本下怎么训练LSTM,以及用哪些技巧避免它变成“自说自话”的复读机。
1.3 整体技术方案选型与项目流程
确定用LSTM之后,我把技术栈锁定在Python和TensorFlow/Keras上。选这套组合的考虑很简单:Keras对序列模型的构建足够友好,调试方便;TensorFlow的生态成熟,资料多,遇到问题容易找到解决方案。PyTorch当然也可以,但在这个场景下Keras的API更直观,避免过多样板代码。
整个项目流程是标准的数据驱动型流水线:原始数据收集 → 清洗对齐 → 特征工程 → 滑窗切分 → 模型训练 → 滚动验证 → 结果可视化。数据层面要处理的坑特别多,比如口径变更、缺失值、节假日效应;模型层面要做结构和超参的迭代;最后还要设计一套合理的验证方案,避免“预测未来”变成“背诵过去”。
我特别想强调一个设计原则:不要把模型想得太聪明。感染人数预测和天气预报类似,本质上是不确定系统下的概率推断。模型能学到的是历史规律和当前状态的延续,但无法真正预知外部干预、病毒变异这类黑天鹅事件。所以整体方案里我特意加了一个“干预因子”输入通道,把可观测的外部影响量化成特征,让模型至少能感知到“环境发生了重大变化”,而不是在不知情的情况下硬猜。
2. 数据准备:决定预测上限的关键环节
2.1 数据来源与观察口径
这个项目里我使用的数据是某开放数据平台提供的脱敏历史感染数据,字段包括每日日期、当日新增感染人数、累计确诊人数、当日检测数量、人口流动指数、节假日标记等。这类数据的统计口径在不同阶段会发生变化,比如早期“感染人数”只统计确诊者,中期扩展到无症状感染者,这直接导致序列在切换时间点出现断崖式跳变。
我在项目里把口径变化时间点标记成一个“干预事件”特征,相当于告诉模型:这段数据的前后统计标准可能不一样,别把它们当作同一个生成过程。这一步非常重要,如果不处理,模型会试图用同一个规律去拟合两个不同口径的数据段,结果就是两头都拟合不好。
另外,数据源里的缺失值很常见,尤其是某些日期没有更新或者周末不发布。我的处理方法是先按完整日期范围重采样,把缺失日期补出来,再用前后几天的中位数或线性插值填充。千万不要用均值填充,因为序列有强趋势,均值填充会破坏局部波动信息。实际项目中我曾因为漏掉一个周末的缺失值,导致模型在第7天预测上反复出现尖峰。
2.2 清洗、对齐与异常值处理
清洗阶段要做三件事:补全日期、平滑异常尖峰、对齐外部特征。
异常尖峰是感染人数数据里的家常便饭。某天突然报出几千人,可能是当天集中检测、也可能是上报延迟。直接让模型去拟合这种脉冲,会严重影响预测稳定性。我的做法是设置一个阈值,超过前后一周中位数3倍以上的值,先用中位数替换,同时保留一个“异常标记”特征。等于告诉模型:这个点不正常,你可以忽略它。
平滑处理也很有讲究。很多人上来就用移动平均直接平滑,结果把真实的转折点也抹平了。我推荐用Savitzky-Golay滤波器或者Hodrick-Prescott滤波,它们能在保留趋势和拐点细节的同时去掉部分噪声。尤其在感染人数从平台期转向下降期的关键时刻,过度平滑会让模型看不到拐点,预测出来永远是一条平缓下行的曲线,错失反弹信号。
外部特征的对齐同样暗藏陷阱。检测量和感染人数经常是错位发布的,统计截止时间不同,直接按日期对齐会让特征标签不同步。我采用的策略是把检测量做滞后1天处理,因为当天的检测结果一般要到次日才反映到新增感染人数里。这种领域知识带来的对齐方式,比任何自动特征对齐算法都可靠。
2.3 特征工程:别把模型喂成“复读机”
特征工程直接决定了模型能学到什么。在LSTM里,虽然网络能自动提取高阶特征,但原始输入的质量仍然至关重要。我试用过几类特征:滞后特征、滑动统计特征、日期特征、领域干预特征。
滞后特征是最直接有效的,把昨天、前7天、前14天的新增人数作为输入,模型就有了“近期水平”的概念。滑动统计特征比如7日均值、7日标准差,可以刻画趋势和波动强度。日期特征包括星期几、是否节假日、是否寒暑假,用于捕捉周期性。
真正拉开效果差距的是领域干预特征。传染病传播中,“检测量”“管控强度”“疫苗接种覆盖率”等因素会剧烈影响感染人数。如果一个模型只知道历史感染人数而不知道“这几天检测量翻了三倍”,那它看到新增人数暴增就会误判成社区扩散,实际上可能只是检测覆盖扩大。这类特征甚至比模型架构更关键。我在某次迭代里只加入检测量和节假日两个外部特征,验证集MAPE就从23%降到14%。
做特征工程时最需要注意的是“特征泄漏”。比如建模目标是预测未来7天新增人数,就不能把未来7天才会发生的移动平均特征放进训练样本,更不能把未来数据用于归一化统计。我当时吃过亏,在全数据集上计算min-max归一化的上下界,训练时看着准确率极高,一上真实未知数据就崩了。正确的做法是先按时间划分训练集和测试集,再在训练集上只基于历史窗口计算归一化参数。
2.4 归一化与数据切分策略
归一化是LSTM训练中绕不开的环节。因为激活函数对输入范围敏感,原始感染人数从几十到几千波动,直接送进网络容易让梯度爆炸。推荐使用MinMaxScaler把数据缩放到0到1之间,特别是在输出层用线性激活时,归一化可以让训练初期的输出落在正确量级附近。
数据切分绝对不能随机划分,时间序列必须保持时间顺序。我采用的比例是:前70%作为训练集、中间15%作为验证集、最后15%作为测试集。这里又有一个细节:验证集和测试集必须是“时间上靠后的连续区间”,不能用随机抽取的方式选样本,否则验证集的信息会渗透进训练集,评估结果虚高。
更可靠的验证方式是滚动时间序列验证(walk-forward validation),模拟真实的预测环境。比如第一天用前200天训练,预测第201到207天,然后滑动窗口,把真实值补进历史再预测下一周。这种方式会暴露很多静态验证看不出的问题,尤其是模型的长期错误累积。我在第五部分会详细讲这个验证法如何帮我发现预测滞后问题。
3. LSTM模型设计:结构、参数与训练策略
3.1 LSTM是怎么记住“前一天”的
LSTM全称是长短期记忆网络,本质是带门控机制的循环神经网络。通俗地理解,它内部有一条“记忆传送带”,通过遗忘门、输入门、输出门决定哪些信息要忘掉、哪些信息要记住、哪些信息要输出。这正好契合感染人数序列的特点:模型需要长期记住疫情曲线的变化趋势,又需要短期记住最近几天的波动细节。
举个例子,当序列跨越一个长假节点时,感染人数的传播模式会发生明显改变。如果网络只会“短记”,它可能把长假前的趋势一路外推,导致预测偏离实际情况。LSTM的遗忘门可以决定在遇到节假日特征时,主动丢掉一部分旧记忆,输入门再把新的干预信息写入状态。归纳起来,LSTM在这类任务里最擅长的事情是“在有规律的长序列里精准捕捉周期性拐点”。
我不建议一上来就把模型搞复杂。最简单实用的结构是两到三层LSTM堆叠,加上Dropout防过拟合,最后接一个全连接层输出未来N天的预测值。实践证明,层数越多不一定效果越好,因为感染人数序列数据量有限,深层网络很容易过拟合,反而让验证集指标恶化。我的最终版本只用了两层LSTM,每层32到64个单元,已经能满足业务需求。
3.2 网络结构搭建:从简单到合适的迭代
模型搭建没有玄学,我习惯从最简单的结构开始,观察验证集表现再迭代。初始版本是单层LSTM,直接输出未来7天的预测值。训练很快,但预测曲线明显滞后,验证集MAPE偏高。随后我加了第二层LSTM,并且把输出层改成7个神经元,每个对应未来一天,情况立刻好转。
还有一个关键参数是滑窗长度。我在项目里试过7天、14天、28天三种滑窗。7天的滑窗信息量太短,遇到周期波动大的时期预测波动大;28天又太长,把太久远的信息带进来引入了噪声。最终14天是甜点,它覆盖了两周完整周期,也匹配传染病的中位潜伏期和传播间隔特征。这里想提醒的是,滑窗长度不能只靠网格搜索,还要结合业务的自然周期来定,比如疾病的潜伏期长度、管控措施生效周期等。
对输入特征的拼接,我是把所有归一化后的特征列按时间步对齐,每个时间步是一个包含滞后感染人数、检测量、节假日、干预标记的向量。注意:外部干预特征通常是整个窗口共享的,比如节假日标记是否需要按时间步拆开,取决于这个特征本身是否随时间变化。我在代码里用一个统一的特征矩阵生成器处理,保证每个时间步拿到的是该时间步当时的特征值,而不是未来值。
3.3 训练策略、损失函数与回调机制
训练LSTM时,损失函数的选择直接影响模型行为。很多人默认用均方误差MSE,但MSE对离群点惩罚过大,感染人数数据里又恰好有不少脉冲式异常点,过大的惩罚会让模型优先去拟合异常点,而不是把握整体趋势。我最终选了平均绝对误差MAE作为主损失函数,它对异常值更鲁棒,最终预测曲线的中位数误差明显下降。如果想把峰值预测得更准,可以用Huber Loss,它介于MSE和MAE之间,在后期训练也值得尝试。
优化器选Adam基本没有争议,学习率初始值设为0.001。我配合了ReduceLROnPlateau回调:验证集指标连续5个epoch不下降时,学习率乘以0.5,最低压到1e-5。这个机制让模型在进入平台期后不是直接收敛到局部最差,而是放缓步长继续微调。
EarlyStopping也要用,patience设为15,restore_best_weights设为True。尤其在数据量小的时候,训练后期很容易出现过拟合,训练loss继续下降而验证loss反弹,EarlyStopping能在验证指标不再改善时主动终止训练并恢复最佳权重。如果不设这个回调,实际跑200个epoch很常见,但最佳权重往往在第60到90轮之间。
训练时的batch_size对效果也有影响。感染人数序列样本数量不大,用16或32都行。我习惯用32,因为数据有强自相关性,batch太小会放大相邻样本间的噪声,batch太大又会降低参数的更新频率。训练轮数上限设为200,配合EarlyStopping实际一般跑80到120轮。
3.4 评价指标:MAE、RMSE与MAPE怎么用
模型好不好,不能单看一张预测曲线图。我同时监控三个指标:MAE、RMSE、MAPE。MAE衡量平均绝对误差,单位是人数,直观但有量纲;RMSE对大误差更敏感,能反映预测是否存在“灾难性偏差”;MAPE是百分比误差,方便不同地区、不同量级的数据之间比较。
在感染人数从高峰期向低位回落的阶段,MAPE会变得异常大。假设真实值是每天50人,预测值60人,MAE只有10,MAPE却是20%。更合理的做法是分阶段评估:分别计算上升期、平台期、下降期的MAE,这样才能看出模型在哪类阶段最脆弱。我的模型在平台期表现最好,MAPE约10%,但下降期会恶化到20%以上,原因在于新增人数的绝对值变小时,相对误差天然放大。
还有一点值得强调:视觉评估同样重要。画图时要把真实值和预测值画在同一坐标系,看趋势方向是否一致、拐点是否提前或滞后、置信区间是否覆盖真实值。很多指标漂亮但曲线错位的模型,一眼就能在图上看出问题。预测不是考试打分,是对业务有用的决策工具,曲线形态的可信度有时比数值误差更重要。
4. 完整代码实现与调参记录
4.1 环境准备与数据加载
我用的环境是Python 3.9,核心依赖是TensorFlow 2.10、pandas、numpy、matplotlib、scikit-learn。这里给出一段可运行的代码框架,数据使用模拟生成的序列做演示,真实场景里替换成清洗好的CSV即可。
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.preprocessing import MinMaxScaler from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau # 模拟生成60天历史数据,替换为真实数据即可 np.random.seed(42) dates = pd.date_range('2023-01-01', periods=300, freq='D') base = np.sin(np.linspace(0, 12, 300)) * 200 + 500 trend = np.linspace(0, 300, 300) noise = np.random.normal(0, 30, 300) cases = base + trend + noise df = pd.DataFrame({'date': dates, 'cases': cases}) df['cases'] = df['cases'].clip(lower=0)加载真实数据后,第一步一定是按日期排序并补全缺失日期,再检查是否有超过阈值的异常点。我在项目里封装了一个函数统一处理:缺失值用前后7天中位数填充,异常值同样用中位数替换,同时生成一列is_anomaly标记。这些处理逻辑不要散落在不同模块,集中维护能省很多事。
4.2 滑窗生成与模型训练
构造训练样本时,我定义一个create_sequences函数,输入是特征矩阵,输出是未来horizon天的目标值。这里有个容易踩的坑,如果在循环里逐条拼接数据,生成几万条样本会非常慢,尽量用向量化方法。
def create_sequences(data, seq_len, horizon): X, y = [], [] for i in range(len(data) - seq_len - horizon + 1): X.append(data[i:i+seq_len]) y.append(data[i+seq_len:i+seq_len+horizon, 0]) # 预测第一列cases return np.array(X), np.array(y) # 训练/验证/测试按时间顺序切分 scaler = MinMaxScaler() scaled = scaler.fit_transform(df[['cases', 'tests', 'holiday', 'intervention']]) X, y = create_sequences(scaled, seq_len=14, horizon=7) split1 = int(len(X) * 0.7) split2 = int(len(X) * 0.85) X_train, y_train = X[:split1], y[:split1] X_val, y_val = X[split1:split2], y[split1:split2] X_test, y_test = X[split2:], y[split2:]滑动窗口的长度取14,未来预测长度取7天。这里要注意,y取的是每个窗口结束后的第15天到第21天,而不是第1天到第7天。如果写反了,模型看到的是“未来作为输入”,训练时损失为0,验证时毫无意义。
model = Sequential([ LSTM(64, return_sequences=True, input_shape=(14, 4)), Dropout(0.2), LSTM(32, return_sequences=False), Dropout(0.2), Dense(7) ]) model.compile(optimizer='adam', loss='mae', metrics=['mae']) early_stop = EarlyStopping(patience=15, restore_best_weights=True) reduce_lr = ReduceLROnPlateau(factor=0.5, patience=5, min_lr=1e-5) history = model.fit( X_train, y_train, validation_data=(X_val, y_val), epochs=200, batch_size=32, callbacks=[early_stop, reduce_lr], verbose=1 )第一层设为return_sequences=True,因为要输出完整的隐藏状态序列给第二层LSTM;最后一层LSTM设False,输出最终状态向量。这里的units数量不是固定值,我先用64和32作为起点,再根据验证集表现调整。数据特征数量是4,对应我输入的四个字段,真实项目中可能有更多特征。
4.3 预测、反归一化与效果评估
模型训练完,预测和反归一化是关键一步。因为训练时用了MinMaxScaler对标签列做了缩放,预测出来的数值是0到1区间的小数,必须逆变换回真实的感染人数量级才能评估。
pred_scaled = model.predict(X_test) pred = np.zeros_like(pred_scaled) pred[:, 0] = pred_scaled[:, 0] # 反归一化时只取第一列 yy = scaler.inverse_transform( np.column_stack([pred_scaled[:, 0], np.zeros((len(pred_scaled), 3))]) )[:, 0] true_scaled = y_test true = scaler.inverse_transform( np.column_stack([true_scaled[:, 0], np.zeros((len(true_scaled), 3))]) )[:, 0]这里有一个实际经验:如果直接对整个样本矩阵做inverse_transform,形状必须和拟合时的列数一致。所以我在反归一化前先构造一个临时矩阵,把预测值填到第一列,其他列填0,再取第一列结果。这个细节虽然琐碎,但写错会导致预测结果全部错位,而且非常难排查。
评估指标的计算很容易,写成一个函数即可:
from sklearn.metrics import mean_absolute_error, mean_squared_error mae = mean_absolute_error(true, pred) rmse = np.sqrt(mean_squared_error(true, pred)) mape = np.mean(np.abs((true - pred) / (true + 1))) * 100 print(f'MAE: {mae:.1f}, RMSE: {rmse:.1f}, MAPE: {mape:.1f}%')MAPE计算时在分母加1,是最常用的防除零小技巧。真实感染人数可能降到个位数甚至0,不加平滑项的话一次除零错误就能毁掉整个评估结果。
画图时我习惯把真实值和预测值画成带标记的折线,并且把预测起始时间点标成竖线。这样能一眼看到模型在转折点的表现,也方便和业务方解释预测结果的可信区域。实际交付时,我还输出了一张连续7天预测的滚动图,比单次预测更有说服力。
4.4 一次典型调参记录
这里分享一次典型的调参迭代记录,过程比结果更能说明问题。初始版本用单层LSTM,units=32,滑窗长度7,损失函数MSE,训练后验证集MAE是每天约180人。
第一次改动是滑窗长度从7增加到14,MAE降到141。原因是7天的窗口没能覆盖一周周期,模型对“周末效应”不敏感。
第二次改动是增加第二层LSTM并把损失换成MAE,MAE降到98。这说明模型容量和损失函数共同限制了性能,尤其是MSE对异常点过度惩罚的问题,换成MAE后预测曲线平滑了很多。
第三次改动是加入外部特征,包括检测量、节假日标记和干预事件标记,MAE降到67。这是提升最大的一次,也验证了我前面的判断:感染人数预测的瓶颈更可能在数据特征上,而不是模型结构。
第四次改动是加入EarlyStopping并调整Dropout到0.2,最终MAE稳定在61。之后我再加一层LSTM或增大units,验证集都没有明显改善,甚至开始过拟合。最终这个配置保留下来:两层LSTM、64+32单元、滑窗14、输出7、损失MAE、Dropout 0.2。
| 迭代版本 | 改动内容 | 验证集MAE | 备注 |
|---|---|---|---|
| v1 | 单层LSTM+滑窗7+MSE | 180 | 曲线滞后明显 |
| v2 | 滑窗改为14 | 141 | 覆盖一周周期 |
| v3 | 双层LSTM+损失MAE | 98 | 对异常点更稳健 |
| v4 | 加入外部特征 | 67 | 最大提升来源 |
| v5 | EarlyStopping+Dropout | 61 | 过拟合缓解 |
这份记录想表达的核心观点是:深度学习项目的优化顺序应该是数据特征、损失函数、模型结构、训练策略,顺序调换过来往往会浪费时间。我在v1阶段就纠结过要不要上Transformer,现在回头看,如果连基础特征都没喂对,换再强力的模型也白搭。
5. 常见问题与排查技巧
5.1 预测曲线总是滞后一天
这是时序预测里最典型的问题:模型输出的预测曲线和真实曲线形状几乎一样,但整体向右平移了一天,看起来就像模型在“抄昨天的答案”。原因并不复杂,自回归式的滑窗建模让网络学到最简单的策略:直接复制最近观测值,因为感染人数日间变化幅度不大,复制昨天的值已经能获得很低的loss。
我在项目里尝试了三种有效解法。第一,把标签从相对日期改为“变化量”,让模型预测明天相对今天的增量,等于强迫网络学习趋势变化而不是记忆数值。第二,增加7日均值、14日均值这类平滑特征,给模型提供更长周期的趋势信息。第三,使用多输出直接预测未来7天,而不是迭代单步预测。结合这三种方法后,滞后基本消失了,拐点位置也更准。
5.2 预测值被“拉平”到均值附近
另一个常见现象是预测曲线整体向中间值收缩,波动幅度远小于真实数据。这是回归模型在MAE或MSE损失下的自然行为:为了最小化期望误差,模型倾向输出训练数据分布的中位数或均值,而不是冒风险预测极端值。感染人数一旦进入暴增期,这种“拉平”的预测会严重歪曲风险程度。
应对思路是提升模型对极端值的感知。一是给近期高峰样本更高的样本权重,让模型更重视高峰模式;二是引入分位数回归,比如同时输出p10、p50、p90三个分位数,用区间表达不确定性;三是在特征里显式加入“近期最大新增人数”和“翻倍速度”,帮助模型识别当前是否处于暴发曲线。实测中分位数输出的效果最好,但实现成本也最高,时间充裕的团队建议直接上。
5.3 数据量太小,模型学不到东西
传染病数据通常只有几百天,远达不到深度学习“数据越多越好”的理想条件。面对小样本,我常用的策略是降模型复杂度、加强正则化、用更简单的任务做预训练。具体来说,把LSTM单元数降到32以下,Dropout提高到0.3,训练轮数上限保持不变,让EarlyStopping尽早截断。这样虽然模型表达能力下降,但泛化性上来了。
数据增强在时序预测里的空间不大,不过可以尝试平滑扰动:给训练序列添加微小噪声,例如原始人数乘以随机数0.95到1.05,相当于在原始样本附近生成同类样本。这个方法在某些实验中能提升模型稳定性,但要注意噪声幅度不能太大,否则会破坏序列的时间一致性。
迁移学习也可以救场。比如用其他地区更长时序的数据先预训练模型,再用目标地区数据微调。前提是两个地区的数据生成机制有相似性,比如同为呼吸道传播病毒,传播动力学参数大体接近。我在项目里测试过这种方案,目标数据只有160天时,预训练微调比从零训练降低约20%的验证误差。不过跨地区迁移要谨慎,人口密度和干预强度差异太大的话,可能带来负迁移。
5.4 训练指标很好,真实预测却崩了
这是所有预测项目都会遭遇的“数据漂移”问题。训练集和验证集的分布还算接近,但真正的未来时间里,病毒可能变异、干预措施可能升级、人口行为可能改变,所有这些都会让模型在训练阶段学到的规律失效。
解决思路不是让模型更“聪明”,而是让评估更接近实战。我强烈建议使用滚动时间序列验证,而不是一次性的固定划分。每轮预测后把真实值并入历史,然后滚动一步或一周继续预测,这样模型在训练时就已经见过“预测错之后如何纠错”的场景,更接近真实使用方式。
另一个实用技巧是给模型设置置信区间,并输出干预敏感度。当模型预测值与真实值偏差开始拉大时,说明可能出现了训练数据之外的新规律,这时候更值得关注的不是调参,而是去排查外部环境发生了什么变化。把预测系统的指标监控与业务背景结合,比盲目堆模型更有价值。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 推荐排查步骤 |
|---|---|---|
| 曲线滞后 | 自回归策略让模型复制最近值 | 改预测增量、加平滑特征、多步直接预测 |
| 预测被拉平 | MAE/MSE鼓励均值输出 | 分位数回归、样本加权、加入翻倍速度特征 |
| 验证集好测试集差 | 数据漂移、固定划分不合理 | 改用滚动验证、监控干预因素 |
| 训练loss不下降 | 学习率太大或太小 | 调整初始学习率、用学习率预热 |
| 过拟合明显 | 数据量小、模型复杂 | 降单元数、加Dropout、EarlyStopping |
| 预测出现尖峰 | 异常值未处理 | 中位数替换异常点、增加异常标记 |
| 特征泄漏 | 归一化用了全数据统计 | 先划分再计算归一化参数 |
这份速查表里的问题,我在项目迭代里几乎都踩过一遍,尤其是曲线滞后和特征泄漏,排查起来非常隐蔽。遇到指标异常时不要急着调模型结构,先按表格里的顺序逐项检查,往往能更快定位到真正的根因。
我个人做下来的最大体会是,病毒感染者人数预测这个任务的难点不在深度学习的模型结构有多复杂,而在数据质量、特征设计和评估方式的细节把控。LSTM在这个场景里足够实用,但真正决定预测上限的,是你能否把检测量、节假日、干预事件这些“场外信息”有效转化为模型输入。如果你准备接手类似任务,建议先把数据清洗和滚动验证做扎实,再考虑换更大规模的模型。最后再多说一句:预测永远不可能百分百准确,与其追求单个数字的精确,不如把重心放在预测区间和趋势方向的可靠性上,这在实际决策中的价值要高得多。