元数据卡
- 前置知识:第5章(统计推断)、数学基础 C(线性代数)
- 预计时间:55 分钟
- 核心难度:进阶
- 阅读模式:高度专注
- 完成标志:能构建一个线性回归模型,做完整的模型诊断,并在必要时使用正则化
你的进度
上一章你学会了从样本推断总体——A/B 测试发现灯塔改版后失误率下降了 2.3 个百分点,p 值 0.03,结论可靠。
但情报官又问了:'能不能预测?如果补给增加 10%,任务完成率会提升多少?'这不只是推断,是建模——找出变量之间的关系,然后用它来预测。
你想起了数学塔里学过的线性代数。那些向量和矩阵,在这里找到了它们的用武之地。
你的任务
你想预测一个任务的成功率。你知道一些因素:执行时间、团队规模、资源投入。直觉告诉你可以用这些因素拟合一个模型。线性回归是你第一个应该学的建模工具——它不仅做预测,还能告诉你每个因素有多大影响。
从相关到回归
在第 3 章里你画了 duration vs success_rate 的散点图,看到它们大致呈线性关系。线性回归把这条"大致"的线变成精确公式:
success_rate = b0 + b1 * duration + noiseb0 是截距,b1 是斜率——duration 每增加一个单位,success_rate 预期变化多少。
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 随机分布,没有明显模式。
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. 残差是否正态分布?
# Q-Q 图
plt.subplot(1, 3, 2)
sm.qqplot(residuals, line='s', ax=plt.gca())
plt.title("Q-Q Plot")如果点在红线附近,残差近似正态。如果端点偏离明显,可能是长尾分布。
3. 是否存在强影响的异常点?
# 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. 多重共线性
如果两个特征高度相关,模型无法区分它们各自的影响,系数会变得不稳定。
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 说明存在严重的多重共线性。解决方案:删除其中一个高度相关的特征,或使用正则化回归。
正则化回归
当特征太多、特征相关、或你怀疑过拟合时,在损失函数中加入正则项。
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 来平衡拟合度和复杂度。
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² 的区别
旅人笔记
拟合模型只是开始。真正的工作在诊断阶段——检验你的模型是否犯了愚蠢的错误。
下一站预告
模型建好了。但你能用什么样的特征输入到模型里?原始数据几乎从不是直接可用的——下一章,特征工程教你从原始数据中提取有用特征。