ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

时序回归实战:预测新冠人数、特征工程与数据泄漏复盘

时序回归实战:预测新冠人数、特征工程与数据泄漏复盘

复试准备到DAY4,我把手边的回归项目实战定成了“预测新冠人数”。这个题目在一众电商销量、房价预测的复试题里不算新,但确实非常典型:它是一个连续的时序回归问题,能串起特征工程、模型选型、评估指标和数据泄漏这些复试高频考点的几乎全部内容。这套DAY4项目的复盘,我整理成一份能直接照着做的实操笔记,有我的踩坑过程,也有最后复盘时觉得值得写进复试回答的思路。如果你也在准备机器学习方向的复试,或者刚好想拿时序回归练手,这篇应该能帮你少走不少弯路。

1. 复试清单里为什么会冒出回归预测这个项目

1.1 “预测新冠人数”到底是一个什么问题

先把题目翻译成机器学习的话:给定过去若干天的每日新增确诊人数,要求预测未来某几天的新增人数。这里的目标变量是连续型数值,所以是一个回归问题;又因为数据是沿着时间顺序采集的,所以它同时也是一个时序预测问题。

这两个定语决定了后面所有操作的走向。普通的回归任务,数据之间假设是独立同分布的,随机打乱数据做交叉验证通常问题不大。但时序回归不行,第T天的数值天然依赖于第T-1天、第T-2天的结果,一旦把时间顺序打乱,就是在人为制造数据泄漏,得到的验证分数没有任何参考意义。这个点我在复习初期根本没意识到,直到把随机K折跑出来的完美分数画成图,才发现模型已经把“未来信息”偷着用了。这部分后面第3章我会专门展开。

另外,这个题目还可以拆成两种预测方式:一是单步预测,即只用历史数据预测明天一天;二是多步预测,即预测未来7天或者14天。复试项目里我更推荐先做多步中的“滚动预测”,思路更清晰,也更接近现实场景。简单说就是每预测完一天,把预测值拼回历史序列,再接着预测下一天。这样虽然误差会逐日累积,但反而能体现你对预测不确定性的理解,面试时可以大大方方讲出来。

1.2 项目目标拆解:从题目到可量化任务

我拿到这个题目后第一件事,不是急着找数据,而是把目标拆成几条能落地的任务:

  1. 构造一个监督学习数据集:每一行是一个样本,特征是当天的各项历史信息(过去7天、14天的新增数、星期几、月份等),标签是当天实际新增数。
  2. 采用时间序列切分:最后14天完全留出不做训练,只做验证,模拟“我站在今天预测未来”的真实场景。
  3. 模型选型:至少对比线性回归、正则化线性模型、随机森林、梯度提升树这四类,给出选择理由和结果对比。
  4. 评估指标:同时看RMSE、MAE、MAPE,并解释为什么在疫情类数据里相对误差指标要谨慎使用。

这套拆解出来之后,项目的骨架就清楚了。数据我用的是一个模拟构造的每日新增统计序列,字段只有date、daily_new_cases、daily_new_deaths这三列。为什么不强调真实数据集?因为复试场景下,数据来源不重要,重要的是你如何处理它。我完全可以大大方方告诉面试官:“我在一份公开统计风格的数据上完成了建模流程”,然后讲清楚流程本身,这反而更安全,也更聚焦。

1.3 数据可用性与合规边界:用什么数据、如何构造训练集

关于数据获取,我建议复试项目不要纠结于能不能找到特别权威的数据源。网上各种每日新增统计很多,字段基本都差不多,拿其中一段连续历史记录就足够。真正重要的是你在项目文档里写清楚三件事:数据覆盖的时间范围、字段定义、以及为什么要截取这段区间。

比如我最终采用的是某个平台整理的历史每日新增数,跨度大约一年,中间有几个明显的异常抬升点。我对原始数据做了两步处理:第一步只保留“每日新增感染人数”这一列作为核心变量,另外保留“每日新增治愈人数”作为可选的辅助特征;第二步手动构造一个连续日期索引,从第0天一直递增,这个特征后面会用来捕捉长期趋势。

构造训练集时有一个很关键的习惯:先做日期对齐,再做特征拼接。所有特征都必须以“某一天的信息”为粒度,不能把整段序列的统计值直接当特征加进去。比如“这14天的新增数均值”,它必须是在某个窗口内算出来的,窗口滑到哪一天,那一行特征就对应该天。如果先对整个序列算一个全局均值,再复制到每一行,这个特征就是废的,还会给模型带去误导。这个坑我踩过,后面详细说。

2. 数据预处理和特征工程的一条龙操作

2.1 拿到每日序列后,先要处理的脏数据

时序数据的脏和周表数据的脏很不一样。表格数据里某一行缺失,可以删掉;时序数据里某一天缺失,删掉之后日期就断了,后面所有lag计算全部会错位。所以时序缺失的第一原则是:能补就不要删。

我当时拿到序列,发现中间缺了三天,后面还跟着一个明显异常的大数值。我的处理方式是分三步走:

  • 用前向填充(用前一天的值补上)先保证序列连续,避免后续滑窗断裂;
  • 再用线性插值对比一下前向填充的结果,如果两者差距不大,就说明那几天没有结构性突变,选用更稳妥的前向填充;
  • 异常大数值先不急着删,单独拉出来看前后几天。如果它符合某种节假日效应导致的抬升,就保留并在特征里加一个“是否为节假日/重大事件日”的脉冲标记;如果是明显的录入错误,才允许剔除。

这里有个容易被忽视的细节:缺失值要不要补、用什么补,本身会影响后面所有滞后特征和滚动特征。我一开始图省事,直接用了全局均值去补缺失天,结果那几天的预测曲线塌成一条水平段,因为模型学到的“正常值”变成了整个序列的平均水平。换成前向填充和线性插值之后,曲线才恢复连贯性。

2.2 日期类特征:星期、月份、节假日与趋势项

日期本身不是数值,但可以拆出一堆有效的特征。我按重要性排了个序:

  1. 星期几(weekday):很多统计序列都有星期效应,工作日、周末的数值波动规律不一样,模型能靠这个特征学到周期性。
  2. 是否节假日(is_holiday):节假日前后往往会出现数值突增或骤减,用一个0/1标记可以让模型在识别异常抬升时不用额外消耗树模型的深度。
  3. 月度编号(month):如果有跨度半年以上的数据,月份可以捕捉季节性波动。
  4. 天数序号(days_since_start):从序列第一天开始递增的整数,作为全局趋势项。这个特征对线性回归特别重要,它相当于直接给模型一根“时间轴”,否则线性模型只能靠截距预测一个常数。

还有一个容易漏掉的特征是“一阶差分”:当天的值减去前一天的值,它表达的是短期的变化速度。把差分作为特征放进模型,相当于告诉模型“昨天涨了还是跌了”,比单纯给原始值更有效。我自己做的时候,把差分值、差分后7日均值都放进特征表里,树模型的效果立马上了一个台阶。

2.3 滑窗与滞后特征:lag特征、滚动均值与波动率

这一块是整个特征工程的核心。时序预测最朴素的思路就是:历史重复自己。为了把“历史重复”变成模型能读的数字,我构造了三组特征:

第一组是滞后特征(lag)。分别取前1天、前2天、前3天、前7天、前14天的新增数作为特征。为什么要这样取而不是连续取30天?因为数据里存在明显的7天周期,滞后7天的值比滞后5天更有解释力;而滞后1到3天捕捉的是极短期的惯性。

第二组是滚动统计特征。计算过去7天和14天的滚动均值、滚动标准差、滚动最大值。滚动均值能平滑短期噪声,滚动标准差反映波动程度。这里要特别小心一点:所有滚动窗口的起点和终点都必须严格小于当前预测日。比如要预测第T天,只能用第T-1天及之前的数据算滚动均值,一旦手滑把第T天自己也算进去了,这就是标准的泄漏。

第三组是比率特征。比如“过去3天均值 / 过去14天均值”,表达短期相对中长期的水平。这类特征在树模型里经常能挖出更深层的模式,而且它天然做了归一化,不受整体数值量级影响。

2.4 特征缩放与目标变换:为什么常用对数变换

很多入门教程说到特征缩放就一句“用StandardScaler”,但在时序回归里,特征缩放有两层用意。第一层很常规:线性模型和带L2正则的模型对特征尺度敏感,把所有数值型特征缩放到均值为0、标准差为1,能让模型收敛更快。第二层是很多人忽略的:目标变量本身的分布。

如果每日新增数在不同阶段量级差距很大,比如早期可能是几百,后期变成几万,那么直接做回归时,模型的误差会被量级大的样本主导。这时候我习惯对目标变量做对数变换,也就是把 y 换成 log(y+1)。加1是为了防止0取对数无意义。这样处理后,模型预测的是对数量级,回算时要再做指数变换。我实测下来,对数变换对这类有指数增长趋势的数据非常有效,它把乘法关系变成近似加法关系,线性模型也能拟合出不错的趋势。

当然,对数变换不是银弹。如果数据里本身就有很多0值,或者你更关心预测值的绝对量而非相对量,那么要慎重。我当时的做法是同时训练两个版本——一个用原始值回归,一个用对数变换回归,最后在验证集上比较MAE和MAPE,选择更优的作为主模型。

3. 从线性回归到梯度提升:模型选型的真实对比

3.1 先跑线性回归:不是凑数,是找基线

很多人一上来就上XGBoost,我反而不建议在复试项目里这么做。模型不是越复杂越好,而是要能回答“你为什么选它”。最稳的做法是从线性回归开始。

线性回归在这个项目里绝不是摆设。只要把趋势项(days_since_start)和周期项(weekday)放进特征,线性回归已经能拟合出一个带上升趋势和星期波动的预测曲线。它的优势是透明、可解释,能看到每个特征的系数大小,方便你在面试时解释:“这个特征系数是正的,意味着随着时间推移预测值总体上升”。缺点是它对非线性交互和突变点不敏感,预测曲线容易偏“直”。

我在项目里把线性回归当作baseline,记下它的验证集MAE,后面所有模型都跟它比。如果没有baseline,后面模型调得好坏就失去参照物。

3.2 正则化线性模型:Ridge和Lasso的取舍

线性回归之后,下一步我会加Ridge和Lasso。这两个模型解决的是同一个问题:特征多了之后,简单的线性回归容易过拟合,尤其是滞后特征之间存在很强的共线性时,系数会变得不稳定。

Ridge是把所有特征系数的平方和加入损失函数,让系数整体收缩但不归零;Lasso则是对系数绝对值之和做惩罚,可以把不重要的特征系数直接压缩到0,相当于在做一个嵌入式特征选择。我那时特征有二十多个,用了Lasso之后,真正被保留下来的特征只有十来个,星期几、短期滞后、滚动均值都在,而一些噪声比较大的高阶交互特征被自动筛掉了。这个结果很适合写进复试回答,因为你能从特征选择的角度解释正则化背后的意义,而不是光背公式。

3.3 树模型与梯度提升:随机森林、XGBoost/LightGBM

树模型在处理这种带周期、滞后、非线性突变的特征表时,效果通常比线性模型高一截。我试了随机森林和LightGBM两种,简单说下体会:

  • 随机森林:训练快,不容易过拟合,对特征尺度不敏感。缺点是外推能力弱,如果测试期的数值比训练期整体高很多,它会倾向于预测到训练集见过的上限,在强趋势数据上容易吃亏。
  • 梯度提升树(LightGBM):拟合能力强,能捕捉线性模型学不到的交互效应,但对参数相对敏感,需要调学习率、树的深度、叶子节点数等。它的一个重要优势是能做特征重要性排序,方便我们看到“过去7天滚动均值”这类特征贡献有多大。

我不会一上来就追求把LightGBM调到极致,而是先用默认参数跑通流程,记录结果,再微调两三组参数。复试回答里讲清楚“我用默认参数跑出baseline,然后调了learning_rate和num_leaves,验证集MAPE下降了十个百分点”这种话,比“我用了某个模型效果好”要有说服力得多。

3.4 时序场景下的数据划分陷阱:随机K折会漏信息

这是全项目里我认为最重要、也最容易被问倒的一节。

普通回归任务用train_test_split随机划分,没问题。但时序预测一旦随机划分,就会发生这样的荒唐事:训练集里包含第100天的数据,验证集里包含第99天的数据,模型在训练时已经看过第100天的走势,然后要去“预测”第99天,分数当然漂亮,但没有任何意义。

正确做法有两种,我推荐都用上:

一是留出法:把时间轴上最后一段(比如最后14天)完整留出来当验证集,训练只用这之前的全部数据。 二是时序交叉验证:用sklearn的TimeSeriesSplit或自定义一个expanding window。比如第一次用前30天训练、接下来7天验证;第二次用前37天训练、再后面7天验证,窗口不断扩展。这样既能充分利用数据,又不会出现未来信息泄漏。

我当时分别做了这两种验证,发现结果差距非常明显:随机K折的MAE比时序切分低了百分之三十,但那是假象;时序切分下的分数虽然没那么好看,却和最后盲测的感觉更一致。这个对比本身就是很好的复试素材。

3.5 评估指标怎么选:RMSE、MAE、MAPE各自适用场景

评估这一步,我同时算了三个指标,但解释的时候各有侧重:

  • RMSE(均方根误差):对大误差非常敏感。如果某一天预测差得离谱,RMSE会剧烈上升。它适合用来发现模型的“极端失误”。
  • MAE(平均绝对误差):更稳健,能反映平均的绝对偏差。但它是一个绝对量,要看真实值的量级才有意义。
  • MAPE(平均绝对百分比误差):把误差除以真实值得到百分比,直观好用。但真实值很小时会爆表。比如某天真实值只有5,预测值是25,误差百分比就是400%,会拉高整体MAPE。所以数据里有大量接近0的取值时,我会标注说MAPE只作为参考,不以它为主指标。

我在项目最终的表格里把三个指标都列出来,再附一句“以MAE和MAPE为主,RMSE作为稳健性参考”。这种细节问题,面试官很容易追问,提前准备准没错。

4. 实战过程中踩过的坑与排查链路

4.1 坑一:滞后特征不小心把未来的数放进训练集

第一个坑是构造lag特征时对齐错位。我的目标是把第T天的数据作为标签,那么第T天的特征只能来自第T-1天及之前。结果我在写代码时图省事,直接用pandas的shift(0)去取“当天值当作滞后一天的特征”,等于特征里包含了当天的真实值。训练时每一行都自带标准答案,RMSE直接逼近0。

排查链路是这样的:我发现训练集R2高达0.999,第一反应不是开心而是慌,因为验证集表现远没有那么好。我先打印了前几行特征和标签,发现x1那一列和标签一模一样,立刻定位到是shift参数写错了。修复方法是把shift(0)改成shift(1),再重新检查每一列特征的来源日期确实早于预测日。

这个坑让我养成一个习惯:特征工程代码里,全部写成显式的日期偏移,能用日期做索引对齐就绝不用默认的行号。因为行号一旦排序、去重,错位的概率非常高。

4.2 坑二:补缺失值时用了全局均值而不是滑动窗口

第二个坑在预处理阶段。序列中间缺了几天,我用整个序列的均值填补,导致那几天预测曲线变得异常平缓。当时我还以为是模型问题,花了好几个小时调参,结果无济于事。

后来我把填补后的序列可视化,才发现那座“平桥”太刺眼。全局均值补进去,相当于把周围正常波动抹平成同一个常数,滞后特征和滚动特征都会跟着失真,模型自然学到一条异常平滑的段。

正确的修复方式很简单:先用ffill(前向填充)保证连续,再用interpolate对个别空洞做线性插值,最后把填补的位置打一个标记特征“is_imputed”,让模型自己判断这一段信息量不足。加了这个标记之后,整体误差又降了一点。

4.3 坑三:目标变量非平稳,模型预测永远落后半拍

第三个坑是很多初学时序预测都会遇上的。直接用原始值训练线性回归,得到的预测曲线总是“慢半拍”:真实值上涨,预测还在上一阶段的位置,真实值到顶之后,预测才开始跟着往上冲。这是典型的非平稳序列问题——目标变量有长期的上升趋势,均值在随时间变化,模型没法用有限的历史去外推趋势。

我的排查方法是画出训练期的数据走势,发现序列不但有趋势,而且波动幅度也随量级变大。修复方法分两步:第一步把目标变量取对数,让高量级时期的波动幅度被压缩;第二步在特征里加入差分项,即“今天比昨天多了多少”,让模型学会看增量而不是只看绝对水平。

这样改造之后,线性回归那条“慢半拍”的曲线明显改善,树模型的效果也有进一步提升。这一步我强烈建议在复试题里主动提,因为它考察的是你对时间序列平稳性概念的理解。

4.4 坑四:评估时用随机采样导致分数虚高

第四个坑是我对比评估方式时发现的。本来我在评估阶段用的就是TimeSeriesSplit,但为了试验,我顺手跑了一次随机K折,结果验证集MAE只有时序切分的七成。一开始我差点想把随机K折的结果写进报告,幸好多画了一张图:把所有预测值和真实值按时间顺序排开,能看到验证期的预测曲线和真实曲线几乎重合到诡异,因为验证集里的样本和训练集的样本在时间上交错,模型等于见过答案。

这一段踩坑经历我最后写进了答辩稿里,作为“如何避免数据泄漏”的实战案例。我甚至觉得,这个坑比“正确方法本身”更有教学价值,因为大多数人就是栽在“看上去分数很好”上面。

5. 如果把这次实战写进复试回答,我会怎么组织

5.1 一句话说清项目:目标、数据、模型、结果

复试被要求介绍项目时,最忌讳的就是长篇流水账。我给自己准备了一个一句话模板,照着填就行:这是一个用于预测某地每日新增感染人数的回归项目,基于约一年历史数据,我构造了包含滞后、滚动统计和日期编码的20多个特征,对比了线性回归、Ridge、随机森林和LightGBM,最后用时序交叉验证,验证集相对误差约在百分之十左右。

这句话里包含四个要素:任务、数据、方法、结果,缺一不可。而且每一点后面都要准备好三分钟的展开细节,但开头这30秒一定干净利落。

接下来再花一分钟讲关键决策:为什么不用随机划分,为什么MAE比RMSE更适合作为主指标,为什么需要对目标做对数变换。这三个问题基本能把深度学习之外的传统回归基础考察到。

5.2 项目亮点与创新点

复试项目最怕平庸,但它也不需要多么花哨的创新,只要体现出“我想过实验结果背后的原因”就行。我给自己提炼了三个亮点:

第一,把数据泄漏问题当成一个正式对比案例来做,在报告中同时列出随机K折和时序切分两组结果,并解释为什么随机K折分数虚高。这很能体现工程严谨性。

第二,对目标变量做变换的选择不是拍脑门,而是基于对非平稳趋势和波动异方差的观察,并在验证集上比较了变换前后的差别。

第三,用特征重要性解释了模型行为。比如LightGBM的排序显示滚动均值特征贡献最大,这说明了历史惯性在短期预测中是主导因素。讲模型结果时多讲“为什么”,而不是只报数字。

5.3 面试官可能追问的3个问题与应对思路

我大概模拟了三个容易被追问的问题,提前准备了回答思路。

问题一:如果让你预测未来30天,方案要改吗? 回答方向:要改。未来30天属于中期预测,直接滚动30步误差会累积得很快。我会考虑三种改进方向:一是加入更多外部特征,如人口流动、季节因素;二是改用序列到序列的深度学习模型,比如简单LSTM作为扩展对比;三是报告预测区间而不是只给点预测,用多次模拟或分位数回归表达不确定性。

问题二:你的特征里有星期几,如果某个地区没有星期效应,这个特征会不会有害? 回答方向:可能无害但可能引入噪声。树模型能在分裂时自动忽略不重要的特征,但线性模型会被无关特征稀释部分信号。所以我会在特征选择阶段关注系数值,若是星期特征的系数始终在0附近,会把它移除再训练一次,比较误差变化。

问题三:MAPE如果某个真实值接近0会发生什么? 回答方向:MAPE会爆炸。我采取的应对是同时报告MAE和RMSE,而且在计算MAPE时给真实值加一个小平滑常数,或者改用sMAPE,用对称误差缓解分母过小的问题。这样回答既显示了数学直觉,也显示了工程处理经验。

5.4 复盘总结:这次实战最值钱的收获不是模型

准备复试到现在,这个回归项目让我明白一个道理:复试不是一个刷题比赛,它考察的是“你会不会认真做一件小事”。预测新冠人数这个题目,难度并不高,任何一本机器学习教材都能教你跑通一个回归模型。但真正拉开差距的,是你有没有在每一步里追问一句为什么:为什么这样补缺失值,为什么这个特征有意义,为什么验证分数可信。

我自己的实操体会是,把上面那些坑一个个踩过来之后,再回去看书本上的交叉验证、数据泄漏、正则化,感觉完全不一样了。每个概念都有了具体的对应场景,而不是一行行的数学符号。

最后再分享一个小技巧:每次调完一组参数,我都把结果存成一个带时间戳的CSV,标注当时的特征版本、模型版本和验证分数。这个习惯特别朴素,但在项目复盘、写报告的时候帮了大忙。复试如果有可能被问到“你迭代过哪些版本”,你可以直接从记录里挑两三个典型版本出来讲,那种真实感远胜于临场编造。这一步,建议你也试试。

RELATED READING

延伸阅读

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