Skip to content

元数据卡

  • 前置知识:第5章(统计推断)、数学基础 C(线性代数)
  • 预计时间:55 分钟
  • 核心难度:进阶
  • 阅读模式:高度专注
  • 完成标志:能构建一个线性回归模型,做完整的模型诊断,并在必要时使用正则化

你的进度

上一章你学会了从样本推断总体——A/B 测试发现灯塔改版后失误率下降了 2.3 个百分点,p 值 0.03,结论可靠。

但情报官又问了:'能不能预测?如果补给增加 10%,任务完成率会提升多少?'这不只是推断,是建模——找出变量之间的关系,然后用它来预测。

你想起了数学塔里学过的线性代数。那些向量和矩阵,在这里找到了它们的用武之地。

你的任务

你想预测一个任务的成功率。你知道一些因素:执行时间、团队规模、资源投入。直觉告诉你可以用这些因素拟合一个模型。线性回归是你第一个应该学的建模工具——它不仅做预测,还能告诉你每个因素有多大影响。


从相关到回归

在第 3 章里你画了 duration vs success_rate 的散点图,看到它们大致呈线性关系。线性回归把这条"大致"的线变成精确公式:

success_rate = b0 + b1 * duration + noise

b0 是截距,b1 是斜率——duration 每增加一个单位,success_rate 预期变化多少。

python
import pandas as pd
import numpy as np
import statsmodels.api as sm
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score

df = pd.read_csv("missions_clean.csv")

# 准备特征和目标
X = df[["duration_minutes", "team_size", "resources_used"]]
y = df["success_rate"]

# statsmodels 方法 —— 可以看到完整统计报告
X_sm = sm.add_constant(X)  # 添加截距项
model_sm = sm.OLS(y, X_sm).fit()
print(model_sm.summary())

输出中的关键字段:

  • R-squared:模型解释了多少比例的目标变化。0.85 意味着 85% 的成功率变化可以用这些特征解释。
  • coef:每个特征的系数。正值表示正相关,负值表示负相关。
  • P>|t|:每个系数的 p 值。如果 p > 0.05,这个特征可能不显著。
  • F-statistic:整体模型的 p 值,判断模型是否有统计意义。

模型诊断

拿到模型只是第一步。你必须检查模型是否合理。最常见的诊断检查四个问题:

1. 残差是否随机分布?

残差 = 真实值 - 预测值。好的模型中,残差应该围绕 0 随机分布,没有明显模式。

python
predictions = model_sm.predict(X_sm)
residuals = y - predictions

plt.figure(figsize=(12, 4))

# 残差 vs 拟合值
plt.subplot(1, 3, 1)
plt.scatter(predictions, residuals, alpha=0.3)
plt.axhline(y=0, color="r", linestyle="--")
plt.xlabel("Predicted")
plt.ylabel("Residuals")
plt.title("Residuals vs Fitted")

如果这个图出现"喇叭形"(残差随预测值增大而扩大),说明方差不齐——需要使用加权最小二乘或对目标变量做变换。

2. 残差是否正态分布?

python
# Q-Q 图
plt.subplot(1, 3, 2)
sm.qqplot(residuals, line='s', ax=plt.gca())
plt.title("Q-Q Plot")

如果点在红线附近,残差近似正态。如果端点偏离明显,可能是长尾分布。

3. 是否存在强影响的异常点?

python
# Cook's distance
from statsmodels.stats.outliers_influence import OLSInfluence
influence = OLSInfluence(model_sm)
cooks_d = influence.cooks_distance[0]

plt.subplot(1, 3, 3)
plt.stem(range(len(cooks_d)), cooks_d)
plt.title("Cook's Distance")
plt.tight_layout()
plt.show()

Cook's distance 超过 4/n(n 为样本数)的点被认为是高影响力点。

4. 多重共线性

如果两个特征高度相关,模型无法区分它们各自的影响,系数会变得不稳定。

python
from statsmodels.stats.outliers_influence import variance_inflation_factor

vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
print(vif_data)

VIF > 10 说明存在严重的多重共线性。解决方案:删除其中一个高度相关的特征,或使用正则化回归。

正则化回归

当特征太多、特征相关、或你怀疑过拟合时,在损失函数中加入正则项。

python
from sklearn.linear_model import Ridge, Lasso

# 岭回归(L2)—— 缩小系数但不归零
ridge = Ridge(alpha=1.0)
ridge.fit(X, y)

# Lasso(L1)—— 可以把不重要的特征系数压到 0
lasso = Lasso(alpha=0.01)
lasso.fit(X, y)

# 弹性网络 —— 结合 L1 和 L2
from sklearn.linear_model import ElasticNet
elastic = ElasticNet(alpha=0.01, l1_ratio=0.5)
elastic.fit(X, y)

print("Lasso 系数:", dict(zip(X.columns, lasso.coef_)))

正则化回归在你特征数量超过样本量、或特征间高度相关时特别有用。

选择特征的原则

特征越多不代表模型越好。加入更多特征会:

  • 提高训练集 R²(甚至到 1.0,过拟合)
  • 降低测试集表现
  • 增加模型解释难度

使用 adjusted R²(调整后 R²)或 AIC/BIC 来平衡拟合度和复杂度。

python
print("Adjusted R-squared:", model_sm.rsquared_adj)
print("AIC:", model_sm.aic)
print("BIC:", model_sm.bic)

常见陷阱

  • 只训练不诊断。你拟合了一个 R²=0.99 的模型,但残差图显示明显的非线性模式——模型实际上是错的。
  • 把高 R² 当作好模型。R²=0.999 可能是过拟合,或者变量间有数据泄露。
  • 忽略共线性。两个特征高度相关时,模型会给出"看起来合理但实际毫无意义"的系数。
  • 重复使用测试集做选择。你在测试集上调了一遍参数,再用测试集评估——你的评估是有偏的。记住要留验证集。

通关挑战

  • 热身:加载一个数据集,用 statsmodels 拟合线性回归,解释每个系数的含义。
  • 挑战:做完整的模型诊断——残差图、Q-Q 图、VIF、Cook's Distance——找出并修复至少一个问题。
  • 排障:你的模型 R²=0.99,但测试集表现差。通过诊断找出过拟合的原因。

验收标准

  • 能构建 OLS 回归模型并解释输出报告
  • 能做四项基本模型诊断(残差随机性、正态性、影响点、共线性)
  • 知道什么时候该用正则化回归
  • 理解 R² 和 Adjusted R² 的区别

旅人笔记

拟合模型只是开始。真正的工作在诊断阶段——检验你的模型是否犯了愚蠢的错误。


下一站预告

模型建好了。但你能用什么样的特征输入到模型里?原始数据几乎从不是直接可用的——下一章,特征工程教你从原始数据中提取有用特征。

Built with VitePress | Software Systems Atlas