机器学习 2 — 回归
:::tip Kaggle 笔记本 本章的完整可执行代码在 Kaggle 上:打开 →
法语和英语版本可在首页查看。 :::
课程的核心从这里开始。我们攻克回归问题:根据其他数值预测一个数值。我们先手动编写线性回归以理解其内部机制,然后转向 scikit-learn 以提高效率。
为什么要学这一章?
回归是机器学习问题的第一大类。您将学到:
- 通过最小二乘法的线性回归和梯度下降;
- 用于评估数值预测的指标;
- 训练/测试划分和交叉验证;
- 多项式回归和 Ridge 正则化;
- k-最近邻及归一化的重要性。
线性回归
最简单的想法:找到一条尽可能贴近散点图的直线。
我们希望直线最小化残差 —— 观测值和预测值之间的差距。
最小二乘法
我们选择 和 来最小化残差平方和:
为什么是平方?两个原因:它对大误差的惩罚更重,并且能得到一个清晰的解析解。
解
将偏导数设为零得到:
直线总是经过均值点 。
梯度下降
当变量很多时(或对于您后面将看到的更复杂的模型),解析解不再存在。我们转向梯度下降:在使损失下降的方向上逐步调整参数。
其中 是学习率。太小,学习缓慢;太大,损失发散。合适的选择通常通过试错找到。
根据每次更新使用的样本数量,有三种变体:
- 批量(Batch):每步使用所有样本。准确但在大数据集上慢。
- SGD(随机):一个样本。快但有噪声。
- 小批量(Minibatch):一个子集(通常 32 或 64)。实践中使用的折中方案。
评估指标
模型训练完后,如何知道它的预测质量?有几个指标:
| 指标 | 公式 | 含义 |
|---|---|---|
| MAE | $\frac{1}{n}\sum | y_i - \hat{y}_i |
| MSE | 对大误差惩罚更重 | |
| RMSE | 类似 MSE,但与 同单位 | |
| MAPE | $\frac{1}{n}\sum | (y_i - \hat{y}_i)/y_i |
| R² | 解释方差的比例 |
R² 对完美模型为 1,对于不比预测均值好的模型为 0,对于比均值还差的模型可能为负。
:::warning MAE vs RMSE 如果 MAE 和 RMSE 差异很大,说明存在离群值。RMSE 因为平方而爆炸;MAE 处理得更好。 :::
训练/测试和交叉验证
在用于训练的数据上评估模型,就像让学生在他背过的真题上考试。总是为测试保留一部分数据。
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
为了不依赖单一划分,k 折交叉验证在不同的划分上训练 次并平均得分:
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print(scores.mean(), scores.std())
多项式回归和正则化
当 关系不是线性时,我们将 的幂作为新变量添加:
模型在系数上保持线性但变成曲线。次数越高,模型越能拟合数据——包括噪声。这就是过拟合。
Ridge:惩罚大系数
为了防止系数失控,我们在损失中添加一个惩罚项:
这就是 Ridge 回归。参数 控制正则化强度: 越大,系数越受约束。
from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)
相近的变体:Lasso(绝对值惩罚,进行变量选择)、ElasticNet(Ridge + Lasso 的组合)。
k 近邻(k-NN)
k-NN 回归器是一种非常不同的方法:没有全局公式,我们查看训练集中 个最近邻并平均它们的目标值。
from sklearn.neighbors import KNeighborsRegressor
model = KNeighborsRegressor(n_neighbors=5)
的选择是一种权衡:小 → 灵活模型但对噪声敏感;大 → 更平滑的预测但可能错过细节。
归一化:k-NN 必不可少
k-NN 完全依赖于距离。如果一个变量的尺度比另一个大 1000 倍,它会主导距离计算。
解决方案:将所有变量放在相同的尺度上。
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
:::warning 数据泄漏陷阱
仅在训练集上拟合缩放器(fit_transform),然后应用到测试集(transform)。否则,您会让测试信息泄漏到训练过程中。
:::
scikit-learn Pipeline
为了避免错误并干净地链接多个步骤:
from sklearn.pipeline import Pipeline
pipe = Pipeline([
('scaler', StandardScaler()),
('model', KNeighborsRegressor(n_neighbors=5)),
])
pipe.fit(X_train, y_train)
y_hat = pipe.predict(X_test)
Pipeline 保证缩放器仅在训练集上拟合,即使在交叉验证时也是如此。