线性回归:从最小二乘到生产级实践
标签:
machinelearning,datascience,python,tutorial
线性回归是大多数人接触的第一个算法,也是大多数人从未深入研究的算法。它也是在更复杂的算法失效后,仍然会在生产环境中被保留的模型,因为它快速、稳定且可解释。
本文不是一个“调用 .fit() 并读取分数”的教程。我们将涵盖数学原理、统计假设、诊断、正则化、评估、生产注意事项,以及区分初学者与工程师的面试问题。
为什么线性回归值得再次审视
线性回归是理解几乎所有其他监督模型的基础:
- 逻辑回归是带 sigmoid 的线性回归。
- Ridge 和 Lasso 是带约束权重的线性回归。
- 神经网络是带非线性激活的堆叠线性变换。
- 树模型的判断基准是:“我能击败线性模型吗?”
更重要的是,线性回归在许多业务问题中仍然是正确答案。当需要向监管机构、客户或财务团队解释预测时,一个带有可解释系数的简洁线性模型胜过黑箱模型。
数学原理:最小二乘与正规方程
给定特征 X 和目标 y,线性模型假设:
y = X * beta + epsilon
Enter fullscreen mode Exit fullscreen mode
目标是最小化残差平方和:
L(beta) = ||y - X*beta||^2
Enter fullscreen mode Exit fullscreen mode
对 beta 求导并设为零,得到正规方程:
beta = (X^T * X)^(-1) * X^T * y
Enter fullscreen mode Exit fullscreen mode
在实践中,使用伪逆(pinv)而非逆矩阵,因为当特征共线性时,X^T X 可能奇异或数值不稳定。
import numpy as np
def normal_equation(X, y):
Xb = np.c_[np.ones(X.shape[0]), X] # 添加截距
beta = np.linalg.pinv(Xb.T @ Xb) @ Xb.T @ y
return beta
Enter fullscreen mode Exit fullscreen mode
最小二乘的三种等效视角
1. 几何视角
预测值 X * beta 是 y 在 X 列空间上的投影。最小二乘在该子空间中找到最近点。这也是残差与拟合值正交的原因。
2. 最大似然视角
假设:
epsilon ~ N(0, sigma^2)
Enter fullscreen mode Exit fullscreen mode
则最大化对数似然等价于最小化平方误差和。这种联系解释了为什么残差正态性对置信区间很重要,即使在较弱假设下 OLS 系数仍然一致。
3. 优化视角
对于大数据集,计算 (X^T X)^(-1) 成本高昂。梯度下降迭代求解同一目标:
def gradient_descent(X, y, lr=0.01, epochs=500):
Xb = np.c_[np.ones(X.shape[0]), X]
n, d = Xb.shape
beta = np.zeros(d)
for _ in range(epochs):
grad = (2 / n) * Xb.T @ (Xb @ beta - y)
beta -= lr * grad
return beta
Enter fullscreen mode Exit fullscreen mode
正规方程:精确,适合中小数据集。梯度下降:可扩展,但需要特征缩放和学习率调优。
必须检查的假设
拟合后 R 平方很高的模型并不自动可靠。这些假设使系数可解释:
| 假设 | 含义 | 检查方法 |
|---|---|---|
| 线性 | 特征与目标呈线性关系 | 残差-拟合值图 |
| 独立性 | 误差不相关 | Durbin-Watson 统计量 |
| 同方差性 | 误差方差恒定 | Breusch-Pagan 检验、残差图 |
| 正态性 | 误差呈正态分布 | Q-Q 图、Jarque-Bera 检验 |
| 无多重共线性 | 特征不高度相关 | VIF、相关矩阵 |
完整诊断示例
import statsmodels.api as sm
from statsmodels.stats.stattools import durbin_watson
from statsmodels.stats.diagnostic import het_breuschpagan
from statsmodels.stats.outliers_influence import variance_inflation_factor
from scipy import stats
import pandas as pd
X_const = sm.add_constant(X_train)
model = sm.OLS(y_train, X_const).fit()
resid = model.resid
print("Durbin-Watson:", durbin_watson(resid))
# 接近 2 表示无自相关;接近 0 或 4 是警告。
lm, lm_pvalue, fvalue, f_pvalue = het_breuschpagan(resid, X_const)
print("Breusch-Pagan p-value:", f_pvalue)
# p > 0.05:无强异方差证据。
print("Jarque-Bera p-value:", stats.jarque_bera(resid).pvalue)
# p > 0.05:无强非正态证据。
vif = pd.DataFrame({
"feature": X_train.columns,
"VIF": [variance_inflation_factor(X_train.values, i)
for i in range(X_train.shape[1])],
})
print(vif)
# VIF > 10 通常视为多重共线性警告。
Enter fullscreen mode Exit fullscreen mode
正则化:偏差-方差权衡
普通最小二乘最小化训练误差,当特征较多或共线性时容易过拟合。正则化添加惩罚:
Ridge: L = MSE + alpha * sum(beta_i^2)
Lasso: L = MSE + alpha * sum(|beta_i|)
ElasticNet: L = MSE + alpha * (r * L1 + (1 - r) * L2)
Enter fullscreen mode Exit fullscreen mode
| 模型 | 惩罚 | 效果 | 适用场景 |
|---|---|---|---|
| Ridge | L2 | 收缩权重,保留所有特征 | 特征高度相关 |
| Lasso | L1 | 将部分权重精确收缩为零 | 需要特征选择 |
| ElasticNet | L1 + L2 | 稀疏且对相关特征组稳定 | 两种需求兼顾 |
关键细节:正则化前务必标准化特征,否则惩罚会不公平地压缩量纲大的特征。
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
models = {
"OLS": LinearRegression(),
"Ridge(alpha=1)": Ridge(alpha=1.0),
"Lasso(alpha=0.01)": Lasso(alpha=0.01),
"ElasticNet(alpha=0.01)": ElasticNet(alpha=0.01, l1_ratio=0.5),
}
for name, model in models.items():
pipe = make_pipeline(StandardScaler(), model)
pipe.fit(X_train, y_train)
y_pred = pipe.predict(X_test)
print(f"{name:20s} RMSE={mean_squared_error(y_test, y_pred, squared=False):.4f} "
f"R2={r2_score(y_test, y_pred):.4f}")
Enter fullscreen mode Exit fullscreen mode
评估指标
不要仅报告 R 平方。每个指标讲述不同的故事:
| 指标 | 公式含义 | 适用场景 |
|---|---|---|
| MAE | 平均绝对误差 | 不希望对误差平方,异常值影响较小 |
| RMSE | 均方根误差 | 大误差尤其有害 |
| MAPE | 平均绝对百分比误差 | 业务需要百分比解释 |
| R² | 方差解释比例 | 比较模型质量 |
| 调整 R² | 按特征数量惩罚后的 R² | 比较不同特征集的模型 |
始终与均值基线对比。如果模型仅略优于“预测平均值”,说明特征贡献不大。
baseline_pred = np.full_like(y_test, y_train.mean())
print("Baseline RMSE:", mean_squared_error(y_test, baseline_pred, squared=False))
Enter fullscreen mode Exit fullscreen mode
完整端到端示例
import numpy as np
import pandas as pd
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
data = fetch_california_housing(as_frame=True)
X = data.frame.drop(columns=["MedHouseVal"])
y = data.frame["MedHouseVal"]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# 1. 手写正规方程
beta = normal_equation(X_train.values, y_train.values)
X_test_b = np.c_[np.ones(X_test.shape[0]), X_test.values]
y_pred_manual = X_test_b @ beta
# 2. sklearn 基线
from sklearn.linear_model import LinearRegression
lr = LinearRegression().fit(X_train, y_train)
y_pred_sklearn = lr.predict(X_test)
for name, y_pred in [("manual", y_pred_manual), ("sklearn", y_pred_sklearn)]:
print(f"{name}: RMSE={mean_squared_error(y_test, y_pred, squared=False):.4f} "
f"MAE={mean_absolute_error(y_test, y_pred):.4f} "
f"R2={r2_score(y_test, y_pred):.4f}")
Enter fullscreen mode Exit fullscreen mode
手写解与 sklearn 解应产生几乎相同的结果。若不相同,则梯度下降未收敛或特征未缩放。
生产检查清单
- 特征缩放:梯度下降和正则化模型必需。
- 交叉验证:切勿在测试集上调优 alpha。
- 数据泄露:在缩放、编码或插补前先划分。
- 监控:部署后跟踪特征分布与预测漂移。
- 可解释性:记录系数、特征重要性与 SHAP 值以备审计。
- 简单优先:在尝试 XGBoost 前先用线性回归。
常见错误
1. 相关不等于因果
高系数不意味着改变特征会改变目标。遗漏变量和反向因果始终可能。
2. 外推
线性模型在训练范围外危险。训练于 300 平米以下的房屋模型,无法预测 3000 平米房屋价格。
3. 高 R 平方不等于好预测
R 平方衡量样本内拟合。模型可在训练集上 R 平方很高,在未见数据上 RMSE 却很差。
4. 忽略残差
若残差有模式,说明模型缺失结构。不要盲目添加更多线性特征来“修复”非线性模式。
5. 正则化时使用原始特征
未标准化时,Ridge 和 Lasso 会对不同量纲的特征施加不平等惩罚。
面试常见问题
- 正规方程与梯度下降的区别?
- 为什么在正态误差假设下 OLS 等价于最大似然?
X^T X奇异时会发生什么?Ridge 如何帮助?- 如何检测并处理多重共线性?
- 为什么使用调整 R 平方而非 R 平方?
- 残差-拟合值图能告诉我们什么?
- 何时优先选择 Lasso 而非 Ridge?
- 为什么 L1 或 L2 正则化前必须标准化?
- 异方差性的后果?
- 如何在回归流水线中检测数据泄露?
结论
线性回归看似简单,却包含机器学习的核心思想:目标函数、优化、统计假设、正则化、评估与部署。若能深入理解这一模型,其后的所有算法都将更容易学习。
从数学入手,验证假设,牢记生产关注点。那个“过于简单”的模型往往在生产环境中存活最久。
觉得有用?关注我获取更多实用 AI 与数据工程文章。
没 ;。l
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.