回归项目实战:感染人数预测,从线性回归到LightGBM

发布时间:2026/10/8 4:16:04
回归项目实战:感染人数预测,从线性回归到LightGBM 1. 从热搜词看回归模型这个实战项目到底在练什么如果把“3回归项目实战新冠病毒感染人数预测”这个标题扔进技术社区点进来的大概分三类人一是刚学完线性回归、想找个完整案例练手的初学者二是被各种热搜词比如lightgbm回归模型、xgboost回归模型、随机森林回归算法轰炸过、想搞明白它们之间到底有什么区别的进阶者三是纯粹对“预测疫情数字”这件事好奇、想看看机器学习能不能派上用场的路人。这个项目的价值恰恰在于它把所有这些需求都串起来了。先说回归本身。回归是监督学习里最基础也最实用的一类任务核心就一句话给定一堆历史数据学出一个函数让这个函数能对连续数值做预测。预测房价、预测销量、预测气温本质上都是回归。而“感染人数预测”这个场景比房价预测更有挑战性数据有强的时间依赖性早期数据少后期波动大还可能有政策干预带来的突变点。这些特性让简单的线性模型不够用必须引入更复杂的算法于是就有了热搜词列表里那串名字线性回归、岭回归、Lasso、决策树回归、随机森林、XGBoost、LightGBM。这个项目能练到的东西非常实在特征怎么构造、数据怎么划分、模型怎么选型、参数怎么调、过拟合怎么防、结果怎么评估。这些都是面试里常问、工作中常用的硬功夫。你可能会说直接用真实疫情数据不就行了吗问题在于真实数据的获取和清洗本身就有门槛而且不同国家、不同时期的统计口径差异很大很容易把时间花在数据清洗上而不是模型学习上。所以更聪明的做法是构造一套基于真实趋势的模拟数据来做项目把重心放在“建模”本身。这个项目的目标读者我建议是已经学过Python基础、了解pandas和sklearn基本用法的朋友。如果你连DataFrame是什么都还没搞清建议先补一下基础如果你已经会跑通一个简单的线性回归那这个项目就是帮你从“会跑demo”进阶到“会做完整项目”的关键一步。2. 方案选型为什么这个项目要把经典模型和集成模型都过一遍2.1 从Excel到Python理解工具的边界在哪里热搜词里有一条“如何在excel制作加乘回归模型”这个搜索意图很典型说明很多人最开始是想用熟悉的工具解决问题。Excel确实能做回归分析它内置了线性回归、指数趋势线等功能画个散点图、勾选“显示公式”一条趋势线和R²就出来了。对于简单的一元线性回归Excel完全够用而且非常直观。但Excel的边界很快就能碰到一是它对多维特征的支持很弱你没法方便地在Excel里构建一个包含历史滞后值、移动平均、星期几、节假日标记等多维特征的数据集二是它没有内置的交叉验证和超参数搜索机制模型好不好全靠肉眼判断三是遇到随机森林、XGBoost这类需要大量迭代计算的模型Excel完全无能为力。所以这个项目虽然不排斥Excel做初步探索但真正的建模环节必须用Python。这也是为什么我在后面的实操部分会给出完整的sklearn和LightGBM代码。工具没有高低之分关键是知道在什么阶段用哪个工具。2.2 为什么先跑经典回归再看集成模型我在设计这个项目的算法链路时刻意让经典回归算法和集成学习算法各占一半不是没有理由的。线性回归、岭回归、Lasso这些经典模型它们的优势在于可解释性强、计算速度快、代码实现简单。线性回归的coef_直接告诉你每个特征对目标的贡献方向和大小岭回归通过L2正则化解决特征共线性问题Lasso通过L1正则化自动做特征选择。这些特性让它们成为理解回归问题的“第一站”。而随机森林、XGBoost、LightGBM这些集成模型它们的优势在于能捕捉非线性关系、能处理特征交互、对异常值和缺失值更鲁棒。在感染人数预测这个任务中感染人数的增长往往不是线性的早期可能近似指数增长后期因为防控措施出现回落这种复杂的动态关系用线性模型很难刻画而树模型天然适合。把它们放在同一个项目里对比你会发现一个很有意思的现象在这个数据集上随机森林的默认参数可能就比线性回归调参后的效果还好而XGBoost和LightGBM在这个中等规模的数据集上可能优势不明显甚至因为数据量不够大而出现过拟合。通过这样的对比你就会明白“模型不是越复杂越好而是越合适越好”这句话的实际含义。2.3 数据集构建背后的真实逻辑我一直强调这个项目要用模拟数据不是为了偷懒而是为了把精力放在建模上。但模拟数据也不是凭空编我参照了2020年春季某地区疫情走势的真实形状先是一段缓慢增长期然后进入指数快速上升期最后在强力干预下进入下降平台期整体呈单峰曲线。模拟数据的构造逻辑是这样的用一组数学函数生成基础曲线然后叠加合理的噪声和可控的随机性。这样做的最大好处是你心里对“正确答案”有一个预期。比如你知道真实规律是“第20天前后进入快速增长期”那你就能直观地检验模型有没有捕捉到这个拐点。如果用真实数据你很难判断模型预测偏差到底来自数据噪声还是模型本身的问题。这个“先构造已知规律的数据再验证模型能否还原规律”的思路是机器学习教学中非常经典的练习方式。它让你从“黑盒调参”转变为“验证假设”这是质的区别。3. 核心细节解析回归模型的损失函数、评估指标与调参要点3.1 回归模型的损失函数到底在优化什么热搜词里有“逻辑回归损失函数头歌”和“逻辑回归”这里必须澄清一个常见的概念混淆。逻辑回归虽然名字里带“回归”但它实际上是一个分类模型输出的是样本属于某个类别的概率因此它的损失函数是交叉熵损失log loss而非回归任务里的均方误差。举个例子逻辑回归适合回答“明天感染人数会不会超过1000”这种是/否问题而线性回归适合回答“明天感染人数大约是多少”这种数值问题。这个项目的任务是预测具体的感染人数所以用的是回归模型家族的损失函数不包括逻辑回归。回归任务的损失函数主要有这么几个初学者一定要搞清楚它们的区别均方误差MSE对误差取平方意味着大误差会被放大。它是回归模型最常用的损失函数对异常值非常敏感。均绝对误差MAE对误差取绝对值对异常值的容忍度更高但它的导数在零点不连续优化时不如MSE平滑。Huber损失综合了MSE和MAE的优点误差小时用平方损失误差大时用绝对值损失有一个超参数delta来控制切换阈值。在感染人数预测这种可能有异常波动比如某天突然大量上报的数据上Huber损失是很实用的选择。在sklearn的LinearRegression中默认就是在最小化MSE而在XGBoost和LightGBM中默认也是回归平方误差损失但你可以通过objective参数改成Huber或其它自定义损失。我实际跑下来的体会是当数据里存在明显异常值时用Huber损失训练的XGBoost模型比用默认MSE损失的表现更稳定泛化误差也更低。3.2 评估指标不能只看一个R²“这个模型效果怎么样”是每个做回归项目的人都要回答的问题。如果只盯着一个R²很容易被误导。R²决定系数的含义是模型能解释目标变量多大比例的方差取值越接近1越好。但它有一个问题在测试集上做预测时R²的计算方式决定了它对尺度敏感而且当测试集的均值与训练集差异较大时R²会变得很奇怪。比如未来几天感染人数整体抬升即使模型预测的方向是对的R²也可能很低。所以这个项目里我建议同时看三个指标MAE直观反映平均预测偏差的绝对值单位跟原始数据一致比如“平均偏差1234人”非常好理解。RMSE对大误差更敏感如果模型在某几天预测严重偏差RMSE会明显变大用来捕捉“不可接受的错误”。MAPE平均绝对百分比误差反映相对误差比如“平均偏差12%”。这个指标在疫情预测场景里特别直观因为它直接告诉你预测偏离真实值几个百分点。实操里我会这样用这些指标先用MAE看整体偏差水平用RMSE检查有没有大偏差点用MAPE对比不同模型间的相对优劣。三个指标一起看才能对模型能力形成完整认知。3.3 树模型参数那么多真正需要调的没几个随机森林、XGBoost、LightGBM这三个模型光参数就有几十个新手一上来容易懵。我在跑完整个项目之后的体会是在这类中小规模数据集上真正需要重点关注的参数就那么几个。对于随机森林最核心的参数有两个n_estimators树的数量和max_depth树的最大深度。n_estimators太少容易欠拟合太多则训练变慢且收益递减我一般先用100棵树看基线效果再通过学习曲线观察是否继续增加树的数量能带来提升。max_depth控制单棵树的复杂度如果深度过大模型会把训练集里的噪声也记住导致过拟合。特征随机采样的比例max_features也需要试一下默认是特征数的平方根但在这个项目里我试过全部用sqrt反而不如手动设置0.5到0.7的效果稳定。对于XGBoost和LightGBM核心参数可以归成三组树的结构参数max_depth、num_leaves、正则化参数reg_alpha、reg_lambda、随机性参数subsample、colsample_bytree。其中LightGBM的num_leaves比max_depth更重要因为它控制的是每棵树的叶子节点数量直接决定模型的复杂度。在感染人数预测的数据集上我建议把num_leaves控制在16到31之间太大必过拟合。学习率learning_rate我一般设为0.05到0.1配合早停法early stopping来防止过拟合。不要一上来就搞网格搜索。正确的做法是先固定一组粗参数跑通全流程然后针对最重要的两三个参数做小范围搜索重点观察验证集上的表现而不是训练集上的表现。4. 实操全流程用Python从数据到模型对比的一次完整演练4.1 环境准备与模拟数据构造我假设你的电脑上已经装好了Python 3.8以上版本以及pandas、numpy、matplotlib、scikit-learn这些常用库。如果还没装强烈建议直接用Anaconda一步到位避免环境问题。本项目的核心依赖是scikit-learn和LightGBM前者提供线性回归、岭回归、随机森林等模型后者提供LightGBM模型。下面是模拟数据的构造代码我特意把数据生成函数写成了可调参数的形式方便你控制曲线的形状、噪声的大小和数据的长度import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression, Ridge, Lasso from sklearn.ensemble import RandomForestRegressor from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import lightgbm as lgb np.random.seed(42) # 构造模拟数据单峰曲线前20天缓慢增长中间20天指数上升后20天回落 days np.arange(0, 60) base 100 5 * days # 基础线性趋势 peak np.exp((days - 20) / 6.0) * 20 # 指数上升部分 peak np.clip(peak, 0, 6000) # 限制峰值 noise np.random.normal(0, 80, sizelen(days)) # 随机噪声 cases base peak noise df pd.DataFrame({day: days, cases: cases}) plt.figure(figsize(10, 4)) plt.plot(days, cases, o-, label模拟感染人数) plt.xlabel(天) plt.ylabel(人数) plt.legend() plt.title(模拟感染人数时序曲线) plt.show()如果只把日期当天数、人数当目标变量做回归模型能学到的信息非常有限。真正的技巧在于做特征工程把“天”这个一维变量扩展成一个高维特征空间。惯用的做法包括把天数的二次项和三次项加入捕捉非线性趋势、把前5天的滞后值加入捕捉自相关关系、把7天移动平均加入平滑短期波动、把星期几和是否节假日的标记加入捕捉周期性。# 特征工程构造滞后特征、移动平均、多项式特征 for lag in [1, 3, 5]: df[flag_{lag}] df[cases].shift(lag) df[rolling_7] df[cases].rolling(window7).mean() df[day_sq] df[day] ** 2 df[day_cub] df[day] ** 3 # 丢掉缺失行 df df.dropna().reset_index(dropTrue) feature_cols [c for c in df.columns if c ! cases] X df[feature_cols] y df[cases] X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, shuffleFalse)注意train_test_split这里设置了shuffleFalse这是有意的。时间序列数据不能随机打乱后20天的数据必须留出来当测试集模拟的是“拿历史预测未来”的真实场景。这是新手最容易踩的坑之一。4.2 经典回归模型基线效果先把最简单的线性回归跑通当作基线后面所有复杂模型都要跟它对比看看到底值不值得引入额外的复杂度。model_lr LinearRegression() model_lr.fit(X_train, y_train) y_pred_lr model_lr.predict(X_test) print(线性回归 MAE:, round(mean_absolute_error(y_test, y_pred_lr), 2)) print(线性回归 RMSE:, round(np.sqrt(mean_squared_error(y_test, y_pred_lr)), 2)) print(线性回归 R2:, round(r2_score(y_test, y_pred_lr), 4))紧接着试岭回归和Lasso。岭回归跟线性回归的区别就在于多了L2正则化惩罚系数alpha控制惩罚力度。alpha太小跟线性回归几乎没区别alpha太大则会把系数压缩得过小导致欠拟合。在这个小数据集上alpha在1到10之间比较合理。model_ridge Ridge(alpha1.0) model_ridge.fit(X_train, y_train) y_pred_ridge model_ridge.predict(X_test) model_lasso Lasso(alpha1.0) model_lasso.fit(X_train, y_train) y_pred_lasso model_lasso.predict(X_test)Lasso这里有个坑Lasso默认用的是坐标下降法对特征的量纲非常敏感如果特征没有标准化结果可能很不稳定。建议在Lasso之前用StandardScaler对特征做标准化但这样一来线性回归和岭回归的系数可解释性就会被破坏因为它们基于的是标准化后的特征。实际项目里我一般会把特征标准化放在流水线里做用Pipeline把StandardScaler和模型串起来方便统一管理和交叉验证。4.3 随机森林、XGBoost、LightGBM的登场树模型不需要做特征标准化这省了很大力气。但它们有另外一套问题超参数多、训练结果有随机性。我在跑随机森林时把n_estimators设成了200max_depth设成了5max_features设成了0.6然后观察它在测试集上的表现。model_rf RandomForestRegressor( n_estimators200, max_depth5, max_features0.6, random_state42 ) model_rf.fit(X_train, y_train) y_pred_rf model_rf.predict(X_test) print(随机森林 MAE:, round(mean_absolute_error(y_test, y_pred_rf), 2)) print(随机森林 RMSE:, round(np.sqrt(mean_squared_error(y_test, y_pred_rf)), 2)) print(随机森林 R2:, round(r2_score(y_test, y_pred_rf), 4))XGBoost的用法跟LightGBM很像但XGBoost更慢一些在小数据集上尤甚。我用XGBoost时踩过最典型的坑是忘了设置early_stopping导致训练轮数一大就过拟合。正确的做法是用sklearn的API配合eval_set和early_stopping_rounds让模型在验证集上不再提升时自动停止训练。xgb_model lgb.LGBMRegressor( objectiveregression, n_estimators500, learning_rate0.05, num_leaves16, max_depth5, subsample0.8, colsample_bytree0.8, random_state42 ) xgb_model.fit( X_train, y_train, eval_set[(X_test, y_test)], callbacks[lgb.early_stopping(50)], ) y_pred_lgb xgb_model.predict(X_test)LightGBM默认打印一堆训练日志看起来有点吓人其实没有什么神秘的就是在不断报告每一轮在验证集上的损失。看到验证损失先降后升说明过拟合开始了早停就触发在这里。4.4 统一评估与模型对比所有模型跑完后我习惯把指标整理成一张表格一目了然地对比模型MAERMSER²线性回归1220.341456.780.87岭回归1156.211398.100.88Lasso1180.451420.330.87随机森林823.161045.520.93LightGBM876.091105.230.92以上数值仅为演示实际取决于你的随机种子和数据构造参数但趋势基本是一致的集成模型在这个非线性数据上明显优于线性模型。你可能会问随机森林和LightGBM的排名能不能再提升当然可以但需要更系统的调参。我建议用GridSearchCV或者Optuna做超参数搜索重点搜max_depth、num_leaves、learning_rate这三个参数。但一定要记住调参的目标是测试集表现不是训练集表现。5. 实操中的高频问题与避坑指南5.1 训练集拟合得很好测试集一塌糊涂这个问题我在带新手做项目时见到过无数次。原因无外乎两个一是数据划分时用了shuffleTrue把未来数据混进了训练集模型直接“偷看”了答案二是模型复杂度过高树模型深度太大或者XGBoost训练轮数太多把训练集里的随机噪声也当成了规律。解决办法说起来简单时间序列数据用时间顺序划分给树模型加深度限制和正则化用早停让模型在验证集效果不再提升时停止训练。真正操作的时候很多人忘了在交叉验证里同样保持时间顺序如果用TimeSeriesSplit代替普通的KFold这个问题就能从机制上避免。5.2 LightGBM的训练日志刷屏怎么快速判断训练是否正常LightGBM的verbose参数是关键。设置verbose-1可以关闭所有日志设置verbose100表示每100轮打印一次。在调参阶段我建议把verbose设成50或100观察验证集上mae的变化趋势如果验证集误差在连续50轮以上都没有改善早停就会被触发然后模型自动停在最佳迭代轮数上。另外还有一个细节在LGBMRegressor的sklearn接口中早停回调的写法是lgb.early_stopping(stopping_rounds)这个API我只在较新的版本上见到过如果你的版本比较老可能需要换用lgb.early_stopping(50)这种带位置参数的老写法。遇到API报错的时候先升级lightgbm到最新版然后看官方文档别硬记网上的老代码。5.3 特征重要性结果靠不靠谱随机森林和LightGBM都能输出feature_importances_新手看到排序结果就奉为圭臬这其实是有局限性的。树模型的特征重要性衡量的是特征在分裂时带来的不纯度下降总量它偏向取值种类多的特征而且高相关特征之间会“分摊”重要性。举个例子lag_1前一天的感染人数和rolling_77天移动平均之间高度相关如果同时放进模型两者的特征重要性都会低于真实的贡献水平。这并不代表它们不重要只是重要性被分散了。所以特征重要性适合用来做粗筛和模型解释不适合作为特征取舍的唯一依据。遇到高相关特征我建议的做法是先跑一次基线然后做特征的逐个剔除实验或者用SHAP值做更细粒度的归因分析。SHAP库给出的shap值能告诉你每个样本上每个特征对预测值的具体贡献方向是正向拉高还是负向压低这个信息比单纯的feature_importances_丰富得多。5.4 预测结果出现负值怎么办线性回归模型在预测时完全没有边界约束如果特征组合的结果偏小线性预测值完全可能是一个负数。而“感染人数为负数”显然是荒谬的。最简单的处理办法是把负值截断为0可以用numpy的np.maximum(0, y_pred)一步到位。但是截断只是保底背后的问题模型对边界情况估计不准并没有解决。在真实项目中我建议对目标变量做log1p变换也就是对y做log(y1)后再建模预测完再用expm1变换回来。这样做的理由是感染人数的分布是右偏的对数变换能压缩数量级的差异让模型更容易捕捉相对变化而不是绝对变化而且对数变换天然保证了预测值在变换回原空间时非负。对数变换对树模型的影响不大但对线性回归有实质帮助如果你发现线性模型频繁预测出负值优先考虑log变换而不是硬截断。6. 进阶方向这个项目还能怎么延伸这个项目做到这里已经覆盖了一个完整回归项目从数据到建模再到评估的全流程。如果还想继续深入有几个方向我非常推荐第一加入真实世界的数据源。可以找一个公开的传染病数据接口把真实数据按同样的流程跑一遍。真实数据的挑战在于缺失值、异常点、统计口径变化这些是模拟数据完全体会不到的“真实感”。我建议先用模拟数据把代码调试通顺再切换到真实数据这样排查问题时能有个确定性的参考。第二引入时序模型做对比。ARIMA、Prophet、LSTM这些时序模型本质上是回归的变体和延伸把它们放进同一个评估框架里对比你对“模型选型”的理解会上一个台阶。尤其是在短序列预测上Prophet和简单回归模型的表现差距会改变你对“复杂模型一定更好”这个印象的判断。第三做端到端的可视化产物。把模型的预测曲线、置信区间、未来7天外推线画在一起再加一段文字结论就是一份可以直接拿去汇报的分析报告。这个能力在实际工作中价值很大因为大多数业务方想看的不是模型参数而是“未来走势是什么”以及“该不该做决策”。我个人在实际操作中的体会是这个项目最值得回味的不是最终的数字而是整个过程中“假设不断被打破、方法不断被迭代”的那种节奏。第一次跑出线性回归那么差的预测值你会意识到数据里非线性的成分有多强第一次把LightGBM跑通并且看到它在验证集上持续下降的损失曲线你会真正理解“模型学习到了规律”这句话的分量。如果你能从这篇文章里带走一样东西我希望是面对一个回归问题时先搭一个最简单的基线再逐步引入复杂度每一步都问自己“加了复杂度之后收益是多少、代价是什么”。带着这个问题去做项目比记住任何模型参数都管用。