ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多项式回归实战指南:从特征扩展到正则化调参全解析

多项式回归实战指南:从特征扩展到正则化调参全解析 总有人问我多项式回归都写到第三篇了还能聊什么。其实越往深走越发现真正值钱的东西不在PolynomialFeatures那两行调用里而在“为什么这么调”“什么时候会翻车”这些细节上。前两篇我聊了线性回归的基本姿势和评估指标今天这篇就把多项式回归的完整链路拆开讲透从原理到调参从实战代码到坑位排查用 sklearn 走一遍能直接抄作业的流程。这篇适合已经把线性回归跑通、想处理非线性关系但还没系统搞过特征扩展和正则化的朋友。读完你能搞明白三件事什么时候该上多项式回归sklearn 里标准的组合写法是什么以及怎么避免过拟合和维度爆炸。1. 为什么要引入多项式回归从线性假设到特征扩展1.1 线性回归的天花板在哪线性回归的假设是目标值 y 和特征 x 之间满足线性关系也就是 y w0 w1·x。现实数据很少这么听话温度对电力负荷的影响、学习时间与成绩提升、广告投入与销售额这些关系往往带明显的曲线特征。你硬拿线性模型去拟合截距和斜率会被“平均”掉两头都吃不好残差图上一看就是有规律的弯曲带。很多人的第一反应是换成更复杂的模型比如随机森林或 XGBoost。但如果数据量不大或者你就想保留模型的可解释性和系数含义多项式回归反而是更克制的选择。它不改变线性回归的求解框架只是先把原始特征做非线性变换再跑一遍线性回归。换句话说我们不是在模型层面加复杂度而是在特征层面加表达能力。我之前遇到一个做销量预测的需求数据只有近两年月度记录单变量、样本不到 30 条。这时候你让它跑个神经网络纯粹是找不痛快。把特征 x 扩展成 x²、x³ 之后多项式回归就能把季节性起伏的长期趋势拟合得相当好而且每个系数还能讲出实际业务含义。1.2 多项式回归的数学本质多项式回归的核心公式写出来很简单y w0 w1·x w2·x² w3·x³ ... wn·xⁿ注意这里的关键点模型对于参数 w 来说仍然是线性的只是特征从一维的 [x] 变成了多维的 [x, x², x³, ...]。所以底层仍然调用最小二乘法求解这也是为什么 sklearn 里只需要用 PolynomialFeatures 做特征变换回归器还是 LinearRegression。理解了这个区别你就明白为什么多项式回归又叫“线性模型的特征扩展”。它是一种“用特征工程的思路解决模型表达力不足”的典型手段而不是“换了一个新模型”。用生活化的例子说就相当于你想描述一段山路。线性回归只能画一条直线山多陡它都只给一个固定坡度多项式回归相当于允许你给出“先缓后陡再缓”的变化过程因为 x 的 n 次项组合起来曲线形状可以非常灵活。1.3 别把 degree 当成越大越好这是新手最爱踩的第一个坑看到 degree3 拟合效果好了直接上 degree10。表面看训练集误差一路下降甚至能完美穿过每个样本点但拿去预测新数据立刻现原形预测值疯狂震荡稍微超出训练区间就飞出天际。这个现象在数学上叫过拟合在工程上叫“记住了答案没学会规律”。degree 每加一档特征数量不是线性增加而是组合爆炸式增长。单变量情况还好degree10 只有 10 个特征一旦多变量特征数直接按组合数膨胀模型很快就变成在拟合噪声。我的经验是degree 从 2 或者 3 开始尝试最多到 5。除非有很强的领域知识说数据存在高频波动否则不要轻易追求高次项。模型越复杂越需要更多样本支撑没有足够数据时复杂度带来的只有灾难。2. sklearn 里的标准做法PolynomialFeatures 与 Pipeline 组合2.1 PolynomialFeatures 的核心参数sklearn 提供 PolynomialFeatures 类来生成多项式特征。别看它简单三个参数一定要吃透degree多项式的最高次数默认 2。interaction_only如果为 True只生成不同原始特征之间的交互项不生成各特征的幂次项。include_bias如果为 True生成一列全 1 的特征相当于线性回归里的截距项。很多人忽略 interaction_only其实它在多变量场景里非常实用。比如你有 x1、x2 两个特征degree2 时默认会生成 [1, x1, x2, x1², x1·x2, x2²]。如果业务上觉得单独平方项意义不大只关心特征之间的交互效应就可以设 interaction_onlyTrue生成 [1, x1, x2, x1·x2]特征数量少很多过拟合风险也小。include_bias 这个参数更是埋了不少坑。LinearRegression 默认会自己算截距所以你在 PolynomialFeatures 里又生成一列全 1等于把截距项重复表达了。在 Pipeline 里用 PolynomialFeatures 生成特征再接 LinearRegression建议把 include_bias 设为 False让回归器统一管理截距。这样逻辑清晰也不会出现某些口袋里参数相关的怪问题。2.2 为什么我坚持用 Pipeline 而不是手工拼接数据量小的时候手工写法也不是不能用from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression poly PolynomialFeatures(degree3, include_biasFalse) X_poly poly.fit_transform(X) model LinearRegression() model.fit(X_poly, y)这段代码跑起来没问题但有个致命问题转换器和模型是分开管理的。训练时你 fit_transform 了训练集预测时你还要记得对测试集做 transform而且绝对不能重新 fit。一旦忘记特征分布对不上预测结果全乱。推荐的做法是把两步塞进 Pipelinefrom sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression pipe Pipeline([ (poly, PolynomialFeatures(degree3, include_biasFalse)), (linear, LinearRegression()) ]) pipe.fit(X_train, y_train) y_pred pipe.predict(X_test)Pipeline 会自动在 fit 阶段对训练数据做转换在 predict 阶段用同一套转换规则处理新数据。你不用手动跟踪“现在该对谁 fit对谁 transform”极大地减少了状态管理的负担。所以我在文章里反复强调 Pipeline不是因为它听起来高级而是因为它把“特征变换的一致性”这种最容易出错的地方直接封装掉留给你的只剩业务逻辑。2.3 一个可以直接跑的示例代码我造一份带明显曲线关系的数据来演示模拟一个“学习时间与考试得分”的非线性场景import numpy as np import matplotlib.pyplot as plt from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression from sklearn.model_selection import train_test_split rng np.random.RandomState(42) X np.linspace(0, 10, 100).reshape(-1, 1) y 2 0.5 * X.ravel() ** 2 - 0.1 * X.ravel() ** 3 rng.normal(0, 1, 100) X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) degree 3 pipe Pipeline([ (poly, PolynomialFeatures(degreedegree, include_biasFalse)), (linear, LinearRegression()) ]) pipe.fit(X_train, y_train) train_score pipe.score(X_train, y_train) test_score pipe.score(X_test, y_test) print(fdegree{degree} 训练集R2{train_score:.4f} 测试集R2{test_score:.4f}) X_plot np.linspace(0, 10, 200).reshape(-1, 1) y_plot pipe.predict(X_plot) plt.scatter(X_train, y_train, s20, alpha0.6, labeltrain) plt.scatter(X_test, y_test, s20, alpha0.6, markerx, labeltest) plt.plot(X_plot, y_plot, colorred, labelfdegree{degree}) plt.legend() plt.show()跑完之后你会发现 degree1 的时候测试集 R2 很低因为直线拟合曲线误差很大提升到 degree2 或 3 之后R2 明显提高到 degree7 左右训练集 R2 继续涨但测试集 R2 开始回落这就是过拟合的经典信号。3. 调参和过拟合控制把多项式回归用稳3.1 degree、交叉验证与训练曲线判断 degree 选多少最稳的办法不是我告诉你一个固定值而是跑交叉验证。sklearn 里有 learning_curve 和 validation_curve 两个工具后者最适合解决这个问题。import numpy as np from sklearn.model_selection import validation_curve from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import LinearRegression pipe Pipeline([ (poly, PolynomialFeatures(include_biasFalse)), (linear, LinearRegression()) ]) param_range [1, 2, 3, 4, 5, 6, 7] train_scores, test_scores validation_curve( estimatorpipe, XX_train, yy_train, param_namepoly__degree, param_rangeparam_range, cv5, scoringr2 ) train_mean train_scores.mean(axis1) test_mean test_scores.mean(axis1) for d, tm, te in zip(param_range, train_mean, test_mean): print(fdegree{d}: 训练R2{tm:.4f}, 验证R2{te:.4f})关键要看训练曲线和验证曲线的“剪刀差”。随着 degree 增加训练 R2 一直上升验证 R2 先升后降那个“验证 R2 开始下降”的点就是最合适的复杂度。如果你发现两个分数差距越来越大说明模型已经进入过拟合区间。有同事问我为什么不直接挑验证分数最高的那个 degree。我说可以但要小心样本量小造成的波动。建议你在这个曲线上选择“验证分数较高、同时跟训练分数差距还不算悬殊”的位置而不一定是最高点。这样泛化能力更稳不会因为运气好而选到一个刚好在验证集上表现虚高的配置。3.2 引入正则化Ridge 与 Lasso 怎么选即使 degree 控制住了高次多项式回归的系数往往很大稍有波动就会剧烈震荡。这时候要引入正则化给系数加惩罚让模型不敢把权重拉得太大。sklearn 里常用的两个选择是 Ridge 和 Lasso对应 L2 和 L1 正则化。Ridge 适合特征是平滑变化、彼此相关性较高的情况。它会压缩系数大小但一般不会把系数压到 0适合多项式这种特征天然存在强相关关系的场景。Lasso 适合做特征选择。它会把部分系数直接变成 0让一些不重要的幂次项彻底消失。多项式特征里Lasso 经常会把高次项干掉只保留低次项效果上等于帮我们自动选了 degree。实操时我会这样组合from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge from sklearn.pipeline import Pipeline pipe_ridge Pipeline([ (poly, PolynomialFeatures(degree10, include_biasFalse)), (ridge, Ridge(alpha1.0)) ])这里有个看似矛盾的事情前面我提醒 degree 别设太大现在正则化模型里我敢用 degree10。原因就在于 Ridge 会把高次项系数压下去所以你可以先给模型更大的表达空间让正则化去约束它。这比一开始就把 degree 限死更灵活。但我的建议是正则化不是偷懒的理由。如果业务上能确定低阶关系直接用小 degree 加简单模型解释起来更舒服如果关系确实复杂再考虑“大 degree 正则化”的组合。两条路都可以但要把“为什么这样选”想清楚。3.3 GridSearchCV 一次性找最优参数实际项目中degree 和正则化强度 alpha 往往要一起调。手动一个个试太累直接用 GridSearchCV 跑网格搜索from sklearn.model_selection import GridSearchCV from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge pipe Pipeline([ (poly, PolynomialFeatures(include_biasFalse)), (ridge, Ridge()) ]) param_grid { poly__degree: [2, 3, 4, 5], ridge__alpha: [0.01, 0.1, 1.0, 10.0] } grid GridSearchCV( estimatorpipe, param_gridparam_grid, cv5, scoringr2, n_jobs-1 ) grid.fit(X_train, y_train) print(grid.best_params_) print(grid.best_score_)GridSearchCV 会在 Pipeline 内部完成特征变换每次交叉验证都严格使用训练折拟合转换器不会把测试信息泄漏进来这一点比手动循环要安全得多。选定参数后别忘了在独立的测试集上做最终评估。网格搜索过程中你已经用了验证集选参数测试集只有跑最后一次靠谱。如果测试集分数和验证集分数差得很远说明数据分布可能不稳定或者样本划分随机性太大这个信号要重视。我还会顺手看一眼 grid.cv_results_而不是只看 best_params_ 和 best_score_。因为同一分数附近的参数组合可能不止一组选一个语义上更简单、系数更小、稳定性更好的组合比强行追求最高分有意义得多。4. 实战中常见的坑与排查经验4.1 特征尺度失衡引发的数值问题多项式特征会把原始数值变成高次方如果原始特征本身很大比如上百、上千那么 x²、x³ 直接变成天文数字。特征之间的数值差距拉得极大最小二乘法在求逆矩阵时容易出现数值不稳定模型可能训练不收敛或者预测结果溢出。解决办法是在多项式转换前后加标准化。最常见的组合from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import PolynomialFeatures from sklearn.linear_model import Ridge from sklearn.pipeline import Pipeline pipe Pipeline([ (poly, PolynomialFeatures(degree3, include_biasFalse)), (scaler, StandardScaler()), (ridge, Ridge(alpha1.0)) ])为什么标准化放在多项式转换之后而不是之前因为你要标准化的是“扩展后的特征空间”。如果先标准化原始特征再算高次项其实相当于对缩放后的值求幂这时特征之间的关系已经发生改变语义不干净先扩展再统一标准化每一个新特征都能落在相对稳定的区间数学上更合理。标准化的位置很多教程都含糊带过我自己也在这里吃过亏。早期我图省事直接对原始 X 做 StandardScaler再接 PolynomialFeatures结果模型预测的曲线形状和预期差了一大截后来才意识到问题出在转换顺序上。4.2 维度爆炸和 interaction_only多变量场景下PolynomialFeatures 的特征数量按组合数增长。假设原始特征有 p 个degree 为 d特征数量大约是 C(pd, d)。如果 p10、d5特征数量瞬间破千再加上高次项之间相关性极强模型不仅慢而且容易崩。遇到这种情况优先做两件事一是把不重要的原始特征先用特征选择筛掉一部分不要让无关特征参与多项式扩展二是考虑 interaction_onlyTrue只保留交互项不要平方项、立方项特征数量会少很多。还有一个思路是改用 Lasso 或 ElasticNet。L1 正则化会在训练过程中把大量无用特征系数压成 0相当于训练过程自动做了特征选择。这样的话即便多项式扩展后特征数量很多最终有效特征不会太多过拟合风险被控制住了。4.3 预测值震荡、外推不可靠等问题高次多项式有一个很反直觉的特性在训练数据范围内它可能拟合得很好一旦超出训练数据的取值范围曲线会按照最高次项的趋势飞速冲向正无穷或负无穷。比如你用 1990 到 2020 年的数据做了 degree4 的回归预测 2025 年结果可能离谱到超出业务常识。这种问题不是 bug而是多项式模型本身的外推局限。所以如果部署模型时预测目标经常超出训练范围我会额外加业务规则做截断或者干脆换用局部模型、树模型。多项式回归适合插值不适合长距离外推这是它的边界。做项目时要心里有数别等线上预测结果离谱了才回头排查。4.4 快速排查清单把常见问题整理成一张表方便你排错时对照现象可能原因处理动作训练 R2 高测试 R2 低degree 过高过拟合降低 degree 或引入正则化结果出现 NaN 或极大数据特征尺度差异大数值不稳定多项式后加 StandardScaler预测曲线抖动剧烈高次项系数过大换 Ridge 限制系数或降 degree特征数量增长可怕多变量高次扩展筛选特征用 interaction_only 或 Lasso验证集表现忽高忽低样本量太小划分不稳定增加折数或换 RepeatedKFold训练集外预测离谱用了高次多项式外推加业务截断或换树模型排查顺序建议先看特征数量和尺度再看 degree 和正则化设置最后检查数据划分和评估方式。大部分问题不是模型选错而是特征处理前面已经埋了雷。拿我自己的经验说最耗时间的往往不是写代码而是排查这些“看起来能跑、但结果怪怪的”情况。多项式回归表面上只有几行 API 调用真正考验功力的是你知道每一步为什么这么设置。你照着上面的流程走一遍至少能避开绝大多数新手必踩的坑调起参来也更有方向感。
返回列表