Aller au contenu principal

机器学习 2 — 回归

:::tip Kaggle 笔记本 本章的完整可执行代码在 Kaggle 上:打开 →

法语和英语版本可在首页查看。 :::

课程的核心从这里开始。我们攻克回归问题:根据其他数值预测一个数值。我们先手动编写线性回归以理解其内部机制,然后转向 scikit-learn 以提高效率。

为什么要学这一章?

回归是机器学习问题的第一大类。您将学到:

  • 通过最小二乘法的线性回归梯度下降
  • 用于评估数值预测的指标
  • 训练/测试划分交叉验证
  • 多项式回归Ridge 正则化
  • k-最近邻归一化的重要性

线性回归

最简单的想法:找到一条尽可能贴近散点图的直线。

y^=ax+b\hat{y} = a x + b

我们希望直线最小化残差 ei=yi(axi+b)e_i = y_i - (a x_i + b) —— 观测值和预测值之间的差距。

最小二乘法

我们选择 aabb 来最小化残差平方和:

J(a,b)=i=1n(yiaxib)2J(a, b) = \sum_{i=1}^{n} (y_i - a x_i - b)^2

为什么是平方?两个原因:它对大误差的惩罚更重,并且能得到一个清晰的解析解。

将偏导数设为零得到:

a=Cov(x,y)Var(x),b=yˉaxˉa = \frac{\mathrm{Cov}(x, y)}{\mathrm{Var}(x)}, \quad b = \bar{y} - a\,\bar{x}

直线总是经过均值点 (xˉ,yˉ)(\bar{x}, \bar{y})

梯度下降

当变量很多时(或对于您后面将看到的更复杂的模型),解析解不再存在。我们转向梯度下降:在使损失下降的方向上逐步调整参数。

wwηJww \leftarrow w - \eta \, \frac{\partial J}{\partial w}

其中 η\eta学习率。太小,学习缓慢;太大,损失发散。合适的选择通常通过试错找到。

根据每次更新使用的样本数量,有三种变体:

  • 批量(Batch):每步使用所有样本。准确但在大数据集上慢。
  • SGD随机):一个样本。快但有噪声。
  • 小批量(Minibatch):一个子集(通常 32 或 64)。实践中使用的折中方案。

评估指标

模型训练完后,如何知道它的预测质量?有几个指标:

指标公式含义
MAE$\frac{1}{n}\sumy_i - \hat{y}_i
MSE1n(yiy^i)2\frac{1}{n}\sum (y_i - \hat{y}_i)^2对大误差惩罚更重
RMSEMSE\sqrt{\mathrm{MSE}}类似 MSE,但与 yy 同单位
MAPE$\frac{1}{n}\sum(y_i - \hat{y}_i)/y_i
1(yiy^i)2(yiyˉ)21 - \frac{\sum (y_i - \hat{y}_i)^2}{\sum (y_i - \bar{y})^2}解释方差的比例

对完美模型为 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 折交叉验证在不同的划分上训练 kk 次并平均得分:

from sklearn.model_selection import cross_val_score

scores = cross_val_score(model, X, y, cv=5, scoring='r2')
print(scores.mean(), scores.std())

多项式回归和正则化

y(x)y(x) 关系不是线性时,我们将 xx 的幂作为新变量添加:

y^=β0+β1x+β2x2+β3x3+\hat{y} = \beta_0 + \beta_1 x + \beta_2 x^2 + \beta_3 x^3 + \dots

模型在系数上保持线性但变成曲线。次数越高,模型越能拟合数据——包括噪声。这就是过拟合

Ridge:惩罚大系数

为了防止系数失控,我们在损失中添加一个惩罚项:

J(w)=i(yiy^i)2+αjwj2J(w) = \sum_i (y_i - \hat{y}_i)^2 + \alpha \sum_j w_j^2

这就是 Ridge 回归。参数 α\alpha 控制正则化强度:α\alpha 越大,系数越受约束。

from sklearn.linear_model import Ridge
model = Ridge(alpha=1.0)

相近的变体:Lasso(绝对值惩罚,进行变量选择)、ElasticNet(Ridge + Lasso 的组合)。

k 近邻(k-NN)

k-NN 回归器是一种非常不同的方法:没有全局公式,我们查看训练集中 kk 个最近邻并平均它们的目标值。

y^(x)=1kiNk(x)yi\hat{y}(x) = \frac{1}{k} \sum_{i \in \mathcal{N}_k(x)} y_i

from sklearn.neighbors import KNeighborsRegressor
model = KNeighborsRegressor(n_neighbors=5)

kk 的选择是一种权衡:小 kk → 灵活模型但对噪声敏感;大 kk → 更平滑的预测但可能错过细节。

归一化: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 保证缩放器仅在训练集上拟合,即使在交叉验证时也是如此。


Kaggle 上的完整笔记本(可分叉)→