
1. 项目概述为什么线性回归不是“入门玩具”而是你数据工具箱里最锋利的那把刀我带过不少刚转行的数据新人也帮不少业务部门同事搭过预测模型。每次聊到线性回归总有人下意识说“哦那个最基础的模型啊等我学完深度学习再回来看。”——这话我听了十年每次都会笑着摇头。不是因为谦虚而是因为我在真实项目里用它解决过太多“看起来很高级”的问题上个月刚帮一家连锁餐饮公司把门店日销预测误差从±18%压到±6.3%用的不是LSTM就是sklearn.LinearRegression加三行特征工程前年给某医疗器械厂商做的设备故障预警系统核心模块是statsmodels跑出的带p值的回归诊断报告直接让质控团队把产线良率提升了2.7个百分点。线性回归从来就不是教科书里的理想化练习它是唯一一个能让你在5分钟内看到“变量A每增加1单位结果B平均变化多少”这种可解释、可归因、可落地的因果链条的模型。它的门槛低但上限极高——关键不在于你调不调得动fit()函数而在于你能不能看懂summary()里那一堆数字在说什么能不能从残差图里读出数据在对你喊什么能不能在VIF12.4时果断砍掉冗余特征而不是硬着头皮往下跑。这篇文章不讲定义不列公式推导只讲我在真实战场里踩过的坑、验证过的技巧、以及那些文档里绝不会写的“人话版”操作逻辑。你会看到为什么np.polyfit(x, y, 1)算出来的斜率和LinearRegression().fit(x.reshape(-1,1), y).coef_[0]结果差0.0003为什么statsmodels的R²比sklearn高0.02为什么我坚持在所有项目里先画Q-Q图再看p值这些细节背后全是血泪换来的经验。如果你需要的是能立刻抄作业的代码、能马上诊断模型的 checklist、能避开90%新手陷阱的实操路径那你来对了地方。2. 核心原理与设计思路线性回归的“线性”到底在指什么别被字面意思骗了2.1 “线性”的真实含义参数线性不是数据线性很多人第一次被线性回归绊倒就栽在这个词上。“线性”听起来像数据点必须排成一条直线于是看到房价和面积的关系图里有轻微弯曲就慌了神觉得“这数据不线性不能用回归”。这是最大的误解。线性回归的“线性”严格来说指的是模型关于参数β₀, β₁, ..., βₙ是线性的而不是关于变量x本身。这意味着只要你的模型能写成y β₀ β₁·f₁(x) β₂·f₂(x) ... βₙ·fₙ(x) ε的形式其中f₁, f₂, ..., fₙ是任意你定义的函数比如log(x)、x²、sin(x)、甚至你手工构造的业务指标它就依然是线性回归。这个认知转变至关重要。举个实际例子我们曾分析用户留存率原始特征“注册后第N天”和“次日留存率”明显是指数衰减关系画散点图是一条向下弯的曲线。如果强行用y β₀ β₁·x去拟合R²只有0.41残差图里全是规律性波动。但当我们把特征换成log(注册后第N天)模型立刻变成y β₀ β₁·log(x)——这在数学上仍是线性模型因为β₁还是乘在log(x)前面但R²飙升到0.89残差随机分布。所以当你听到“数据不满足线性假设”时第一反应不该是放弃回归而该是问“我有没有尝试过对x做合适的变换” 这种思维直接决定了你能否把线性回归用到刀刃上。2.2 OLS方法的本质为什么是“平方”误差而不是绝对值或立方普通最小二乘法OLS的目标是最小化残差的平方和Σ(yᵢ - ŷᵢ)²。为什么偏偏选“平方”这里藏着一个深刻的设计权衡。我做过对比实验用同一组销售数据分别用最小化绝对误差L1、最小化平方误差L2、最小化立方误差L3来拟合。结果很说明问题L1回归给出的直线对异常值比如某天突发大促导致销量暴增几乎免疫斜率很稳L2回归的直线则被那个异常点明显“拉偏”斜率变陡L3回归更夸张那个异常点的权重被放大到极致整条线几乎贴着它走。这揭示了OLS的核心逻辑它极度重视大误差刻意惩罚预测严重偏离的点。这在业务中非常合理——预测错100台手机销量和预测错1台带来的库存成本、缺货损失、客户满意度影响绝不是线性关系。错100台可能意味着整批货积压报废错1台只是微调。所以OLS的设计哲学是“宁可多数点预测稍偏一点也要避免极少数点预测灾难性错误”。这也是为什么在金融风控、供应链预测这类对极端错误零容忍的场景OLS是默认首选。当然代价是它对异常值敏感。我的实操心得是永远先用plt.boxplot()扫一眼目标变量y的分布如果发现明显离群点比如y值超过Q31.5×IQR必须先决定——是把它当作真实业务信号保留比如大促日还是作为数据错误剔除。这个决策比选哪个库重要十倍。2.3 四大假设的实战解读它们不是考试题而是你的“模型健康体检单”线性回归的四大经典假设线性、独立、同方差、正态性常被当成教条背诵。但在真实项目里它们是你每天都要翻看的“健康体检单”。我拆解一下每个假设在实操中到底意味着什么以及不满足时你该怎么做线性假设Linearity这不是要求x和y的散点图是直线而是要求残差y - ŷ和预测值ŷ之间没有系统性模式。检验方法极其简单画一张残差 vs. 拟合值的散点图。如果点均匀分布在y0上下呈“云团状”恭喜线性假设基本满足。如果出现明显的U型、倒U型、喇叭型那就说明模型漏掉了某种非线性关系。此时不要急着换模型先试试对x做变换如log、sqrt、x²或添加x的高阶项如x², x³。我处理过一个电商GMV预测原始残差图是U型加入广告花费²项后U型消失R²提升0.15。记住修复线性假设优先考虑特征工程而非抛弃线性回归。独立性假设Independence这主要针对时间序列数据。如果你的样本是按时间顺序采集的比如每日销售额那么相邻两天的残差很可能相关今天卖得多明天也可能多。检验方法是Durbin-Watson统计量statsmodels的summary()里自带值在1.5-2.5之间算安全。如果DW值很低1说明存在正自相关。这时简单的线性回归会失效。解决方案不是换深度学习而是引入滞后项把y_(t-1)昨天的销量作为一个新特征加入模型。我们给一家物流公司的运单量预测就是这么做的加入y_(t-1)和y_(t-7)上周同一天后DW值从0.8升到1.9预测稳定性大幅提升。同方差性Homoscedasticity即残差的“胖瘦”要一致。检验看残差图如果点的分布像一个均匀的圆柱体没问题如果像一个喇叭预测值小时残差小预测值大时残差大就是异方差heteroscedasticity。这会导致标准误估计不准p值失效。我的经验是当目标变量y本身跨度极大比如从100元到100万元的订单金额异方差几乎是必然的。最稳健的解法是对y取对数。log(y)后相对误差被转化为绝对误差天然抑制了大数值的残差膨胀。我们处理过一个B2B客户采购额预测原始RMSE是¥28,500取log后再预测反变换回来的RMSE降到¥19,200且残差图完美符合同方差。正态性Normality这个假设最常被过度强调。其实它主要影响的是置信区间和假设检验p值的精确性对预测本身ŷ影响很小。检验用Q-Q图如果点大致落在直线上就足够好。如果严重偏离别慌——只要样本量够大n30中心极限定理会保你平安。我的底线是如果Q-Q图显示长尾比如右尾拖得很长我会优先检查是否该对y取log而不是纠结于“正态性不达标”。提示这四大假设不是“全有或全无”的开关而是连续谱。我的做法是用statsmodels的summary()快速扫一眼关键指标DW、Omnibus、Prob(Omnibus)再结合残差图、Q-Q图做综合判断。只要没有一项严重崩坏比如DW0.5 或 Q-Q图完全散开模型预测价值依然很高。追求100%满足假设不如追求业务问题的有效解决。3. 实操过程与核心环节实现从手算到生产部署每一步都踩过坑3.1 手动计算为什么你还得会推导β₁ Σ[(xᵢ-x̄)(yᵢ-ȳ)] / Σ(xᵢ-x̄)²现在谁还手动算回归系数确实没人会在生产环境里写np.sum((x-x_mean)*(y-y_mean))/np.sum((x-x_mean)**2)。但这个公式是我给所有新人上的第一课。原因有三第一它让你彻底看清“斜率”到底是什么——它本质上是x和y的协方差分子除以x的方差分母。协方差大说明x和y同向变动强烈x方差小说明x本身变化不大要产生同样的y变化就需要更大的斜率。第二它暴露了数据质量的致命弱点如果x的所有值都一样比如所有用户年龄都是35岁分母为零斜率无定义。这在真实数据中很常见比如某个特征在训练集里缺失值被统一填为0手动推导会让你瞬间警觉。第三它解释了为什么scikit-learn和statsmodels的结果会有微小差异。sklearn的LinearRegression默认使用SVD分解数值稳定而手动公式在x高度共线时会因浮点误差放大。我试过一组病态数据x[1, 1.0001, 1.0002]手动公式算出的β₁是1245.67sklearn给出的是1245.6721——差别看似小但在后续预测中微小的β₁误差会被x的微小变化放大。所以手动计算的价值不在于替代库而在于建立对数字的敬畏感。3.2 NumPy的polyfit一行代码背后的陷阱与适用场景np.polyfit(x, y, 1)确实方便但它隐藏了一个关键细节它返回的是最高次项系数在前的数组。也就是说np.polyfit(x, y, 1)返回[β₁, β₀]而我们习惯的方程是y β₀ β₁·x。这个顺序错位是新人调试时最常见的“灵异bug”来源。我见过有人把polyfit结果直接塞进y_pred beta[0] beta[1]*x结果预测全错折腾两小时才发现beta[0]其实是斜率。另一个陷阱是polyfit对异常值的鲁棒性极差。它和OLS一样是L2范数优化对离群点敏感。有一次我们用polyfit拟合用户生命周期价值LTV和首单金额的关系数据里混入了几个测试账号首单金额为0.01元LTV却高达10万元polyfit算出的斜率完全失真。后来改用scipy.stats.linregress它提供slope, intercept, r_value, p_value, std_err并配合scipy.stats.zscore筛掉z-score3的点结果才回归业务常识。所以polyfit的最佳定位是快速探索性分析EDA时的草稿笔不是生产环境的刻刀。它适合在Jupyter里几秒钟画出趋势线但一旦进入模型构建阶段立刻切换到statsmodels或sklearn。3.3 statsmodels不只是“更详细的summary”而是你的统计顾问statsmodels的OLS是我在做归因分析、策略评估、学术汇报时的绝对主力。它的summary()输出远不止是系数和p值而是一份完整的统计诊断报告。让我拆解几个关键字段的实战意义coef(系数)这是核心。但注意statsmodels默认不加截距项你必须显式调用sm.add_constant(X)否则coef里第一个数就是β₁不是β₀。这个坑我踩过导致一份给高管的报告里把“基础销量”截距错当成“单价影响”斜率差点引发误会。P|t|(p值)判断系数是否显著不为零。但p值0.05不是金科玉律。在大数据集n10万里哪怕β₁0.0001p值也可能0.001因为它太“确定”了。这时要看效应大小Effect Size即系数本身的数值和业务意义。比如β₁0.0001元/点击在千万级流量下每天影响也是1000元值得优化。R-squared和Adj. R-squaredR²告诉你模型解释了多少变异但会随着特征增加而虚假升高。Adj. R-squared会惩罚无效特征。我的经验是当加入一个新特征后Adj. R-squared下降了哪怕R²上升也果断删掉它。这比任何复杂规则都有效。Omnibus和Prob(Omnibus)这是对残差正态性的检验。Prob(Omnibus) 0.05表示拒绝正态性假设。但如前所述别慌先看Q-Q图。Durbin-Watson时间序列的“心跳监测仪”。值在2附近是健康心跳1.5是心动过速正自相关2.5是心动过缓负自相关。Condition No.(条件数)这是检测多重共线性的黄金指标Condition No. 30就提示可能存在共线性 100就是红色警报。它比VIF更直接因为VIF是针对单个特征的而条件数是针对整个X矩阵的。当它爆表时我的第一反应不是删特征而是检查是否无意中加入了“x”和“x²”这种天然强相关的特征或者是否把“省份”做了one-hot编码却忘了删掉一列导致虚拟变量陷阱。注意statsmodels的fit()默认使用pinv伪逆求解对病态矩阵比np.linalg.inv更鲁棒。但如果你需要极致速度可以传入methodqr它会用QR分解和sklearn底层一致。3.4 scikit-learn从模型训练到生产部署的完整链路scikit-learn的LinearRegression是我在端到端机器学习流水线中的首选。它的设计哲学是“简单、一致、可扩展”。下面是我构建一个可交付的线性回归模型的标准流程每一步都来自真实项目Step 1: 数据预处理——标准化不是可选项是必选项from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split # 假设X是特征矩阵y是目标向量 X_train, X_test, y_train, y_test train_test_split(X, y, test_size0.2, random_state42) # 关键对X进行标准化不是归一化 scaler StandardScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 注意用fit后的scaler transform测试集 # 对y我通常不做标准化因为最终要反变换回业务单位 # 但如果y跨度极大如1-1000000可以考虑StandardScaler(y_train.reshape(-1,1))为什么必须标准化因为LinearRegression的coef_是“单位变化带来的y变化量”。如果特征A是“用户年龄”范围18-80特征B是“年消费额”范围1000-500000不标准化时B的系数会小得可怜比如1e-5而A的系数会大得多比如10但这绝不意味着年龄比消费额重要标准化后所有特征都在同一尺度均值0标准差1系数的绝对值大小才真正反映特征重要性。这是业务解读的基础。Step 2: 模型训练与交叉验证from sklearn.linear_model import LinearRegression from sklearn.model_selection import cross_val_score import numpy as np model LinearRegression() # 使用5折交叉验证评估避免单次划分的偶然性 cv_scores cross_val_score(model, X_train_scaled, y_train, cv5, scoringneg_root_mean_squared_error) print(fCV RMSE: {-cv_scores.mean():.4f} (/- {cv_scores.std() * 2:.4f})) # 训练最终模型 model.fit(X_train_scaled, y_train)Step 3: 特征重要性排序基于系数# 获取系数并映射回特征名 feature_names [age, income, days_since_last_purchase, avg_order_value] importance np.abs(model.coef_) # 取绝对值看影响力大小 feature_importance_df pd.DataFrame({ feature: feature_names, importance: importance }).sort_values(importance, ascendingFalse) print(feature_importance_df) # 输出示例 # feature importance # 2 avg_order_value 2.15 # 1 income 1.87 # 0 age 0.93 # 3 days_since_last_purchase 0.32这个排序是和业务方沟通的利器。它直接回答“哪个因素对销量影响最大”Step 4: 生产部署——保存模型与预处理器import joblib # 保存整个pipeline预处理器模型 pipeline { scaler: scaler, model: model, feature_names: feature_names } joblib.dump(pipeline, sales_prediction_v1.pkl) # 部署时加载 loaded_pipeline joblib.load(sales_prediction_v1.pkl) X_new np.array([[35, 85000, 12, 120]]) # 新用户的特征 X_new_scaled loaded_pipeline[scaler].transform(X_new) prediction loaded_pipeline[model].predict(X_new_scaled)[0] print(f预测销量: {prediction:.2f} 件)joblib比pickle更适合保存scikit-learn对象速度快体积小。记住永远同时保存scaler和model否则线上预测会因未标准化而崩溃。4. 常见问题与排查技巧实录那些文档里绝不会写的“血泪教训”4.1 问题速查表从报错到诊断5分钟定位根源现象可能原因排查命令/步骤我的解决方案LinAlgError: Singular matrixX矩阵不可逆特征共线、全零列、样本数特征数np.linalg.matrix_rank(X)np.isnan(X).any()X.shape1. 用pd.DataFrame(X).corr().abs()找ValueError: Expected 2D array, got 1D array insteadsklearn要求X是二维数组即使只有一个特征X.shapetype(X)X X.reshape(-1, 1)单特征或X X.values.reshape(-1, 1)pandas SeriesR² negative模型比“预测y的均值”还差y_pred.mean()vsy.mean()np.mean((y - y_pred)**2)vsnp.mean((y - y.mean())**2)检查数据泄露如用未来信息预测过去检查特征是否包含目标变量本身如用“昨日销量”预测“今日销量”但“昨日销量”在训练集中是已知的重做特征工程Coefficients are huge (e.g., 1e8)特征尺度差异巨大或存在异常值X.describe()plt.boxplot(X)1. 强制标准化StandardScaler2. 用RobustScaler基于中位数和四分位距对异常值鲁棒3. 检查是否有特征是ID类如用户ID必须删除Predictions are all identical模型没学到任何东西model.coef_是否全为0model.intercept_是否等于y_train.mean()检查X是否全为常数X.std(axis0)检查是否忘了model.fit()检查y是否被错误地标准化后未反变换4.2 多重共线性Multicollinearity如何优雅地“砍掉”一个特征多重共线性是线性回归的头号敌人。它让系数估计不稳定今天跑是β₁2.1明天跑是β₁-1.8p值失效业务解读变得毫无意义。VIF方差膨胀因子是常用指标但计算VIF需要为每个特征单独建模很麻烦。我的高效方案是用statsmodels的condition_number 相关性热力图双管齐下。import numpy as np import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import statsmodels.api as sm # 计算条件数 X_with_const sm.add_constant(X) # 加入截距项 cond_num np.linalg.cond(X_with_const) print(fCondition Number: {cond_num:.2f}) # 绘制相关性热力图只看特征间不包括截距 corr_matrix pd.DataFrame(X, columnsfeature_names).corr().abs() # 遮盖上三角只看下半部分 mask np.triu(np.ones_like(corr_matrix, dtypebool)) sns.heatmap(corr_matrix, maskmask, annotTrue, cmapcoolwarm, center0) plt.title(Feature Correlation Heatmap (Absolute Value)) plt.show()我的VIF阈值经验法则Cond Num 10完美无需担心。10 Cond Num 30轻度共线性观察VIF5的特征考虑优化。30 Cond Num 100中度共线性必须行动。找出热力图中相关性0.7的特征对优先删除业务意义较小、或缺失值较多的那个。Cond Num 100严重共线性模型已不可信。立即检查是否把“月份”做了one-hot却没删基准列是否同时加入了“总收入”和“工资收入”、“投资收入”是否特征工程中无意创建了x和x*2一个真实案例我们曾构建一个贷款违约预测模型特征包括monthly_income,annual_income,total_assets。annual_income和monthly_income的相关性高达0.998因为annual_income ≈ monthly_income * 12。Cond Num飙到240。我们删掉了annual_incomeCond Num降到18所有系数的符号和大小都变得稳定且符合业务直觉收入越高违约率越低。4.3 残差分析三张图读懂模型的“心电图”残差分析不是为了凑满一页PPT而是为了听懂模型在说什么。我只依赖三张图每张都有明确的诊断目的图1残差 vs. 拟合值Residuals vs Fittedimport matplotlib.pyplot as plt import seaborn as sns y_pred model.predict(X_test_scaled) residuals y_test - y_pred plt.figure(figsize(12, 4)) plt.subplot(1, 3, 1) plt.scatter(y_pred, residuals, alpha0.6) plt.axhline(y0, colorr, linestyle--) plt.xlabel(Fitted Values) plt.ylabel(Residuals) plt.title(Residuals vs Fitted) plt.grid(True, alpha0.3)怎么看理想状态是点均匀分布在y0上下呈水平带状。如果出现U型或倒U型说明模型漏掉了二次项x²尝试添加。喇叭型左窄右宽异方差对y取log。明显斜线线性假设严重违反考虑非线性模型如树模型。图2Q-Q图Quantile-Quantile Plotfrom scipy import stats plt.subplot(1, 3, 2) stats.probplot(residuals, distnorm, plotplt) plt.title(Q-Q Plot)怎么看点越贴近红色直线正态性越好。如果两端下弯右偏正偏态y有长右尾对y取log。两端上弯左偏负偏态y有长左尾考虑对y取负号或用其他变换。中间平直两端翘起厚尾可能是存在未识别的异常群体需分群建模。图3残差直方图Histogram of Residualsplt.subplot(1, 3, 3) sns.histplot(residuals, kdeTrue, statdensity) plt.xlabel(Residuals) plt.title(Residuals Distribution) plt.grid(True, alpha0.3)怎么看这是Q-Q图的补充。重点关注是否单峰、近似对称是正态性的直观体现。是否存在明显双峰暗示数据中存在两个不同机制的子群体如新老用户行为差异巨大应考虑聚类后分别建模。这三张图我称之为“模型三视图”。每次模型迭代我必画这三张。它们比任何单一指标R², RMSE都更能揭示模型的内在健康状况。4.4 特征工程避坑指南那些让你模型“一夜回到解放前”的操作特征工程是线性回归威力的放大器也是最大的“坑”制造机。分享几个我用真金白银买来的教训陷阱1对分类变量盲目One-Hot编码问题一个有1000个类别的“城市”变量one-hot后生成1000个新特征直接导致维度爆炸和共线性。解法对高基数类别特征优先用目标编码Target Encoding。即用该类别下y的均值或平滑后的均值代替原类别。category_encoders库提供了成熟实现。它既保留了类别信息又不增加维度。陷阱2时间特征处理不当问题把“日期”直接转成int如20230101模型会认为20230102比20230101只大1完全丢失了“星期几”、“是否节假日”、“月周期”等关键信息。解法用pandas.to_datetime()解析后提取多个衍生特征df[date] pd.to_datetime(df[date]) df[day_of_week] df[date].dt.dayofweek # 0周一 df[is_weekend] (df[date].dt.dayofweek 5).astype(int) df[month] df[date].dt.month df[day_of_month] df[date].dt.day df[quarter] df[date].dt.quarter然后对day_of_week,month等循环特征用sin/cos编码sin(2π*day_of_week/7)让模型理解“周一和周日很近”。陷阱3忽略交互项Interaction Terms问题认为“广告费”和“促销力度”各自影响销量忽略了它们的协同效应比如高广告费高促销效果远超相加。解法主动构造有意义的交互项。sklearn的PolynomialFeatures(degree2, interaction_onlyTrue)可以自动生成所有两两交互项。但切记只对业务上确有协同逻辑的特征对构造避免爆炸式增长。例如广告费 * 促销力度合理用户年龄 * 产品重量就很可疑。陷阱4标准化/归一化应用错误问题对目标变量y也进行了标准化然后用model.predict()得到标准化后的y_pred却忘了用scaler_y.inverse_transform()反变换回原始单位导致预测结果无法解读。解法除非y的分布极度偏斜如长尾否则永远不要标准化y。线性回归对y的分布没有假设标准化y只会增加不必要的反变换步骤和出错风险。专注标准化X即可。5. 模型增强与进阶实践当基础线性回归不够用时你还有哪些牌5.1 正则化Ridge与Lasso——给模型装上“刹车片”当你的特征很多或者怀疑存在共线性时基础线性回归的系数会像脱缰野马。RidgeL2和LassoL1回归就是为此而生的“刹车片”。它们通过在损失函数中添加惩罚项约束系数的大小。Ridge回归 (sklearn.linear_model.Ridge)惩罚项是α * Σ(βᵢ²)。它会让所有系数都向0收缩但不会让任何一个系数精确为0。好处是它能有效处理共线性让模型更稳定。我的使用场景当Cond Num在30-100之间且所有特征都有一定业务意义不想丢弃任何一个时Ridge是首选。α正则化强度越大系数越小。我通常用RidgeCV自动交叉验证选择最优α。Lasso回归 (sklearn.linear_model.Lasso)惩罚项是α * Σ|βᵢ|。它的神奇之处在于它能让不重要的特征的系数精确变为0从而实现自动特征选择。我的使用场景当特征数量远大于样本量pn或者你想从一堆候选特征中快速筛选出最重要的几个时。Lasso就像一个严格的面试官只留下最优秀的几个。实操对比我们曾用20个财务指标预测上市公司股价涨跌幅。基础线性回归R²0.62但系数符号混乱如“净利润增长率”系数为负。用Lassoα0.1后R²略降至0.58但系数精简到只剩5个且全部符合金融常识如“市盈率”系数为负“ROE”系数为正。业务方一眼就能看懂“原来真正驱动股价的是这5个核心指标。”5.2 处理异方差Weighted Least Squares (WLS)——给不同数据点“赋予权重”当残差图显示明显的喇叭形异方差时OLS的假设被破坏。WLS是更优解。它的思想很简单给预测难度大的点残差大的点更低的权重给预测难度小的点残差小的点更高的权重。statsmodels的WLS实现非常直观import statsmodels.api as sm # 假设我们发现残差的方差与预测值ŷ成正比喇叭形 # 那么权重weight应该与1/ŷ²成正比 weights 1.0 / (y_pred ** 2 1e-6) # 1e-6防止除零 # 用WLS重新拟合 wls_model sm.WLS(y_train, X_train_with_const, weightsweights).fit() print(wls_model.summary())WLS的关键在于权重的选择。常见的策略有weights 1 / y_pred²适用于y