
前两年整理回归模型笔记的时候我特意挑了一个实战性很强的数据集做压力测试——公开的COVID-19每日感染人数记录。这个选题放到今天依然有讨论度因为它几乎就是检验回归模型能力的“压力测试题”趋势非线性、周几效应明显、噪声密集、还时不时来一个无法解释的突变点。我能把这个数据集拟合到业务可用水平再把它迁移到电商、运营类预测项目里心里基本都有底。这篇文章是这次实战的完整复盘从数据清洗到特征工程从线性回归一路跑到LightGBM总共对比了8个模型还埋了不少只有亲手做过才懂的坑。如果你正在学回归模型或者做完教程想找一个真实业务场景练手这份复盘可以直接当参考。1. 项目定位先把“预测什么”定义清楚1.1 把时间序列问题转换成回归问题的关键一步很多人看到“感染人数预测”第一反应是这是时间序列任务要用ARIMA或者Prophet这类专门的时序模型。我之前也这么想但认真做完之后发现如果一上来就套现成时序库整个建模过程会变成一个“调参黑盒”对理解回归模型帮助不大。所以我刻意把这个问题转换成一个监督学习回归任务构造滞后特征和滚动统计特征作为X未来第T天的新增人数作为y。一句话总结就是用过去14天的数据告诉模型明天会发生什么。这个转换听起来简单实际操作中却决定了模型上限。直接输入日期列模型只能学到“时间越靠后数值越大”这种粗糙规律而输入滞后值和统计量之后模型才真正学习“增长惯性”“周内周期”“波动程度”这些结构化的模式。这其实就是时间序列分析里的自回归思想——你看“自回归”这个热词本质就是过去值的回归。把AR(p)看成“用滞后p步的值预测当前值”的线性回归是不是好理解多了我建议所有准备做时序预测的同学都先走一遍这个流程哪怕最后还是要上专门的时序模型你也能看清它底层到底在拟合什么。1.2 疫情数据作为回归样本的三个“坏脾气”这个数据集的第一个特性是趋势非平稳。感染人数在快速爬坡期完全不符合均值平稳假设直接用原始数值建模线性回归很容易被几个极端值带偏。更聪明的做法是对目标变量做log1p变换把指数型增长拉成近似线性。第二个特性是强自相关。昨天的值对今天的影响极大这意味着特征工程如果漏掉滞后项模型几乎跑不动。第三个特性是异方差波动幅度会随着数值升高而增大。低峰期误差几百高峰期误差几万这时候如果只盯着R²看你会得到一种虚假的安全感。上面三点不是新冠数据独有的很多业务数据都有类似性质。例如电商销售额在大促期间会出现尖峰服务器请求量在晚高峰波动剧烈甚至股价数据也同时具备趋势、均值回归和波动聚集三种特征。你在这次实践里学到的特征构造方法和评估思路完全可以迁移过去。2. 数据准备与特征工程真正的胜负手2.1 数据源、清洗和规范化数据我选的是公开的COVID-19日更数据集字段包含日期、区域、累计确诊、日新增、人口数等。网上整理好的版本不少选一个字段相对完整的即可。为了把注意力集中在方法本身我随机选取某个区域作为预测对象所有区域名称统一做了匿名化处理不影响模型效果也能避免一些不必要的连带解读。预处理有几步需要特别留意。第一步是缺失值补齐。有些日期没有上报直接删除会让时间序列出现断层影响了滞后特征的连续性。我采用前向填充把缺口补上同时额外加了一列“是否为补全值”的哑变量让模型能感知到这些特殊样本。第二步是异常值修正。个别日期的数据出现明显的报送口径变化比如某天新增从100突然跳到2万这不一定是真实感染变化更像统计口径调整。我没有修改原始值而是用滑动窗口的3倍标准差把异常点标记出来。第三步是数据变换目标变量做log1p变换。这一步几乎决定了所有线性类模型的表现不换的话预测高值段会严重失真。2.2 特征工程滞后、滚动、日历与交互项特征工程是整个项目投入时间最多的部分前后构造了30多个特征最终保留约20个。我把它们分成四组来管理每一组都对应一种业务直觉。第一组是滞后特征我构造了lag1、lag3、lag7、lag14分别代表前一天、前三天、一周前和两周前的新增人数。为什么选这四个因为疫情数据存在明显的周内报告周期和潜伏周期lag7能抓住上周同期的基准量lag14能反映一个完整潜伏周期前的趋势。第二组是滚动统计包括过去7天和14天新增人数的均值、最大值、标准差。滚动均值可以平滑掉日度噪声标准差传递的是波动风险。项目做完回头看滚动7日均值是所有特征里对预测贡献最大的一项没有之一。第三组是日历特征主要是星期几。不要小看这个特征公开数据里周初和周末的报送节奏经常不一样模型学会“报告节奏”之后预测准确率明显提升。第四组是交互项我加了“周末哑变量 × 滞后7日均值”这个乘积特征。这里补充一个Excel用户常问的“加乘回归模型”。Excel里的回归分析工具可以通过“对数-线性”转换或者添加乘积列来构造加乘模型本质上就是我刚才说的交互项做法。如果你暂时不想写Python代码把滞后7日均值那一列和周末哑变量那一列相乘生成新列再用Excel的回归工具跑一遍预测效果也会有明显提升。不过样本量一大、特征一多还是Python这套流程更利索。在这个项目里加入交互项后线性回归的验证集MAE下降了约14%效果非常明显。还有一个小技巧对最终模型稳定性帮助特别大我给所有训练样本加了一个“是否大于前7日均值5倍”的异常标记。当时反复调试发现统计口径突变会造成短期极端值如果模型不做任何感知这些点会严重干扰拟合。有了这一列做标记集成模型的峰值预测和低谷期表现都更稳健。2.3 训练集和验证集的划分方式不少回归项目教程会用train_test_split随机划分数据但时间序列数据绝对不能用。一旦随机打乱模型就“偷看”了未来数据验证集分数虚高到离谱。我用两套划分做了对比随机划分的R²比按时间顺序划分高出0.12左右看起来模型很棒但一旦做真正的滚动预测就完全失灵这就是典型的数据泄漏。正确做法是按时间顺序切分前70%做训练后30%做验证。如果还想再严谨一点可以用Walk-Forward验证初始训练窗口逐步向后推进每次都只预测下一段时间。我在最终评估阶段采用的就是扩窗验证每轮把训练窗口扩充一段新数据重新训练然后预测下一周这样得到的误差指标更接近模型未来真实表现。后续调参时也一直遵守这条规则没有踩过随机验证的雷。3. 八个回归模型的实战全过程下面按模型复杂度排列把每个模型的思路、关键代码和实际表现都过一遍。重点关注模型之间的差异点这比单独看某个模型更有收获。3.1 多元线性回归先建立一个透明的baseline线性回归是必须跑的基准模型不是因为效果好而是因为它最透明、最容易排查问题。如果连线性回归都训练不起来就先别上集成模型。import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score model LinearRegression() model.fit(X_train, y_train) y_pred model.predict(X_valid) print(MAE:, mean_absolute_error(y_valid, y_pred)) print(RMSE:, mean_squared_error(y_valid, y_pred, squaredFalse)) print(R2:, r2_score(y_valid, y_pred))这里需要注意y_valid是经过log1p变换后的目标值所以算出来的误差都要再逆变换回去才能得到“真实人数误差”否则数字会让人摸不着头脑。我第一版就没做逆变换看着MAE只有0.3还以为模型无敌了逆变换回来才发现误差仍然很大幸好及时发现。线性回归在验证集上的R²大约0.86看着还行但残差图有明显的周期性波纹而且高峰期残差特别大。这说明模型学会了平均水平却没学会“周一低、周六高”的节奏也没学会爆发期的斜率切换。这个结果指引我去补特征而不是急着换模型。3.2 多项式回归给线性模型开外挂线性模型处理不了非线性关系一个快速补救方案是加多项式特征。我把滞后特征和滚动统计量做二次多项式展开并且只保留交互项这样既能加入非线性弯曲又不至于让维度爆炸。from sklearn.preprocessing import PolynomialFeatures from sklearn.pipeline import make_pipeline from sklearn.preprocessing import StandardScaler pipe make_pipeline( PolynomialFeatures(degree2, include_biasFalse), StandardScaler(), LinearRegression() ) pipe.fit(X_train, y_train)这里有个重要经验多项式次数千万不要太高。我当时试过degree5训练集几乎完美验证集直接崩溃典型过拟合。degree2和degree3效果最好配合StandardScaler做标准化之后数值稳定性也好了很多。多项式回归在这个项目里把R²从0.86提到了0.91MAE下降约23%是线性模型里最大的一次提升。3.3 逻辑回归损失函数与有序分类视角很多教程会强调逻辑回归是分类模型不属于回归这个说法没有问题但我在项目里发现一个更实用的用法把“明天新增人数落在哪个区间”当成有序分类问题。疫情场景下业务方真正关心的可能不是精确数字而是“明天要不要启动应急预案”这种场景用区间预测比数值预测更直接。我做了三分类版本新增人数小于100定义为低风险100到1000定义为中风险大于1000定义为高风险。逻辑回归的损失函数是交叉熵也叫对数损失本质上就是极大似然估计的反面。模型输出概率分布后用交叉熵衡量预测分布与真实one-hot标签的差距训练过程中梯度下降不断修正参数。很多在线实训平台喜欢拿逻辑回归损失函数出题比如在某平台上就有专门的手推损失和梯度的单元练习我当时刷那些题时觉得偏理论直到这个项目里实际跑了一遍多分类逻辑回归才算真正理解每条公式为什么长那个样子。在这个三分类任务里初期模型严重偏向高频的“高风险”类别验证集准确率只有62%。我设置了class_weightbalanced让少数类样本获得更高损失权重准确率提高到78%。这个结果说明回归模型给出的是连续数值而逻辑回归给出的是概率区间两种信息可以服务不同决策场景。3.4 CART回归树理解每一次分裂才能真正理解集成模型CART回归树是树模型家族的老祖宗核心是递归二分。每次选择一个特征和一个阈值把样本一分为二目标是让分裂后两个子节点的均方误差之和最小。这里和分类树的最大区别在于分裂指标分类树用基尼系数或熵回归树用均方误差。理解了这一点后面的随机森林和梯度提升就是水到渠成的事。from sklearn.tree import DecisionTreeRegressor tree DecisionTreeRegressor( max_depth6, min_samples_leaf10, ccp_alpha0.005 ) tree.fit(X_train, y_train)单独一棵树的验证集表现并不好甚至比线性回归还差主要问题在于方差太大。但它有一个线性模型给不了的价值可解释性。我打印了树结构发现第一刀的分裂特征是“过去14天滚动均值”说明这个特征携带的信息量最强第二刀开始有“星期几”出现周内节奏确实客观存在。这些证据反过来指导特征工程形成正反馈。CART有个关键参数ccp_alpha用于代价复杂度剪枝。我一开始忽略了这个参数树特别深训练集误差几乎为零验证集一团糟。加上剪枝之后模型才变得可用。3.5 随机森林回归多棵树的平均值为什么更稳随机森林是在CART树上加了一个Bagging流程。它从训练集里bootstrap采样出多个子集每个子集独立训练一棵树最终预测值取所有树的平均值。这个机制可以直观理解为“一人一票然后取平均”——单独一个人容易出错但100个人平均之后正确答案的占比会显著提升。随机森林引入第二个随机化机制是列采样也就是每个节点只考虑部分特征。这个机制是随机森林表现好的关键。在我这个数据集上max_features设置为特征总数的1/3到1/2之间效果最好。原因在于时间序列特征之间相关性很强如果每次都看全部特征树之间的差异就会变小Bagging降低方差的优势会被削弱。from sklearn.ensemble import RandomForestRegressor rf RandomForestRegressor( n_estimators400, max_features0.4, min_samples_leaf20, n_jobs-1, random_state42 ) rf.fit(X_train, y_train)还有一个参数值得单独说min_samples_leaf。我默认用1的时候模型过拟合明显调到20以后每片叶子至少包含20个训练样本树的颗粒度变粗泛化能力反而上升。最终随机森林的MAE比线性回归低约27%效果非常稳健在平稳段几乎不会出现灾难性的离群预测。3.6 XGBoost回归梯度提升的工程巅峰XGBoost是GBDT的一种高效工程实现核心是串行训练一系列弱学习器每棵新树负责拟合前面所有树的残差。普通梯度提升只用一阶导数XGBoost把目标函数做了二阶泰勒展开同时使用一阶和二阶导数收敛更快更稳。做回归任务时目标函数设置为平方损失即可。import xgboost as xgb xgb_model xgb.XGBRegressor( n_estimators500, max_depth4, learning_rate0.03, subsample0.8, colsample_bytree0.7, early_stopping_rounds30, random_state42 ) xgb_model.fit( X_train, y_train, eval_set[(X_valid, y_valid)], verboseFalse )这里有一个非常容易踩的坑早停必须基于时间序列后段的验证集而不是随机切分的验证集。我在中途用random split做过一次然后看到early stopping的评估分数大幅提升整个项目差点被带到沟里。换成时序验证之后分数虽然没那么亮眼却更接近真实场景。在疫情数据这类小而噪声强的数据集上XGBoost的优势是能刻画非线性交互。比如滚动均值和高风险标记特征的交互逻辑回归需要手动构造XGBoost在分裂过程中就能自动学。它的最终MAE和LightGBM几乎持平处于所有模型里的第一梯队。3.7 LightGBM回归换一种更快的提升方式LightGBM是微软开源的高性能梯度提升库最大的变化是引入了直方图算法。它把连续特征离散化为固定数量的桶然后在桶上寻找最优分裂点训练速度大幅提升。另一个特点是leaf-wise生长策略每次从当前所有叶子中选择增益最大的叶子进行分裂而不像传统按层生长因此同等迭代次数下通常精度更高但也更容易过拟合所以要用num_leaves限制叶子数量。import lightgbm as lgb lgb_model lgb.LGBMRegressor( n_estimators500, num_leaves31, learning_rate0.05, subsample0.8, colsample_bytree0.7, random_state42 ) lgb_model.fit(X_train, y_train)在这个几千条样本的数据集上LightGBM与XGBoost的最终预测精度非常接近但训练速度快了近3倍。如果你的数据量更大或者需要频繁调参与滚动重训从XGBoost迁移到LightGBM几乎是零成本的选择。而且LightGBM对分类特征有原生支持如果后续要加入星期几、节假日等离散变量可以直接声明特征类型非常省事。3.8 最小角回归一个常被忽略的特征筛选工具最小角回归Least Angle RegressionLARS是求解Lasso的一种高效路径算法。把它理解为“走到离目标最近的路上逐步纳入特征”就好。每一步从当前特征集合里找出与残差相关性最强的方向沿着该方向前进到下一个特征与残差相关性相等为止如此往复逼近Lasso解。它天生适合高维稀疏数据在特征维度较高时计算效率远高于直接梯度下降。我当时的特征数量在20个左右并不算高维但最小角回归的价值在于自动特征排序。跑LarsCV之后它的特征重要性排序是滚动7日均值、滞后14天新增、星期几、异常标记这和之前几个模型手工筛选的结果高度一致给了我很大的信心。用它作为最终预测模型效果一般但作特征筛选器很称职尤其在特征工程阶段可以节约大量时间。4. 评估方法、时间序列陷阱与调参技巧4.1 回归指标怎么选才不骗自己回归模型评估常用四个指标MAE、RMSE、MAPE和R²。它们在稳定数据集上表现差不多但疫情数据方差极大各指标讲的故事完全不同。指标关注点这个项目里的表现MAE平均绝对误差量纲一致容易被少量极值拉高RMSE对大误差加倍惩罚数值明显大于MAE说明存在大幅预测偏差MAPE相对误差占比低谷期分母太小MAPE会被异常放大R²模型解释方差比例直观但不适合单独支撑决策我最终重点关注MAE和RMSE的比值。如果RMSE是MAE的1.3倍以内说明模型预测误差分布比较均匀如果超过2倍说明有少数极端预测错得离谱。早期我的模型比值一度到2.5后来加了异常点标记和log1p变换降到1.4左右模型的稳定性肉眼可见地提高了。建议你在自己的回归项目里也算一下这个比值能快速判断模型是否存在经常性大的偏差。4.2 时间序列回归的三条铁律第一条禁止随机划分。只要数据带时间顺序就必须按时间戳切分训练集和验证集。第二条禁止未来信息。用未来时段的数据构造特征等于告诉模型正确答案这种信息泄漏在真实预测中不会被发现但在线上部署后模型会立刻现原形。第三条禁止用验证集反复调参而不做滚动更新。正确做法是把数据切成多段每一段都是一次Walk-Forward窗口训练时只使用该窗口之前的样本。我在最后评估时用了五折滚动窗口每折预测约30天最终误差取五折平均值这样得到的结果比我最初一张测试集得分更有说服力。4.3 调参过程与最终模型横向对比集成模型的参数调节优先级值得说说。第一次调参时我总盯着n_estimators从100调到1000耗时很长提升却很小。后来意识到这一堆模型里最重要的参数其实是“树的复杂度约束”XGBoost里的max_depth、LightGBM里的num_leaves、随机森林里的min_samples_leaf。把树变简单再配合学习率提升效果立刻体现。这也解释了为什么前沿模型都强调“防止过拟合比拼命拟合更重要”。最终八个模型在验证集上的关键指标对比如下模型MAERMSER²训练耗时线性回归基准值偏高0.86秒级多项式回归比基准降23%明显改善0.91秒级逻辑回归三分类区间准确率78%不适用不适用秒级CART单树较差较差约0.72秒级随机森林比基准降27%稳定约0.93低XGBoost比基准降38%较低约0.95中LightGBM比基准降39%最低约0.95快最小角回归仅做特征筛选不参与排名不参与排名快结果排位很清晰LightGBM和XGBoost并列第一梯队随机森林紧随其后多项式回归在传统模型里拔尖纯线性回归在爆发期明显滞后CART单树作为基类模型确实不够看。还有一个值得记录的细节对目标做log变换之后所有树模型的效果都有明显上升而对线性模型来说log变换几乎是“必须步骤”不是可选项。5. 复盘这些经验对普通回归项目同样有效5.1 五条实操心得第一特征是爹模型是儿子。这个项目里特征工程带来的提升远大于从线性回归切换成XGBoost带来的提升。如果你时间有限优先做滞后特征、滚动统计和交互项模型哪怕用最简单的线性回归也能交出不错的成绩。第二先做log变换再看分布尤其是目标值跨多个数量级时。第三无论如何先跑一个简单的baseline不但能验证数据管道是否顺畅还能给后面的复杂模型提供参比基准。第四残差分析比指标数值更能发现问题。我几次重大的特征改进都源于残差图上的模式和残差尖峰。第五认真记录实验。训练数据版本、特征列表、参数、分数都要写进表格否则模型一多几天后你根本分不清哪个配置对应哪个结果。5.2 后续可以这样扩展这个项目还留了很多扩展空间。可以从单步预测改成多步预测训练模型分别预测第1天、第3天、第7天或者用滚动方式连续预测未来一周对比一下误差累积曲线。也可以引入更多外部特征人口流动数据、搜索指数、天气数据等能明显提高LightGBM这种树模型的预测上限。你还可以把这份数据和ARIMA、Prophet甚至LSTM再做一轮对比有了滞后特征和滚动特征这套基础你会很容易看懂那些时序模型内部在做什么。最后分享一个我自己的体会做完这个项目之后我对“预测”这件事谨慎了很多。模型输出的数字再精确充其量也只是对过去规律的外推。一旦外部条件发生结构性变化任何回归模型都会瞬间失灵。所以现在做类似预测任务时我更愿意把结果展示成区间判断配合一个概率分布让使用方既看到最可能的值也知道它的不确定性边界。这种思维转变比跑通八个模型更有价值。