【机器学习的基本思想】模型优化与评估

在这里插入图片描述

【作者主页】Francek Chen
【专栏介绍】 ⌈ ⌈ Python机器学习 ⌋ ⌋ 机器学习是一门人工智能的分支学科,通过算法和模型让计算机从数据中学习,进行模型训练和优化,做出预测、分类和决策支持。Python成为机器学习的首选语言,依赖于强大的开源库如Scikit-learn、TensorFlow和PyTorch。本专栏介绍机器学习的相关算法以及基于Python的算法实现。
【GitCode】专栏资源保存在我的GitCode仓库:https://gitcode.com/Morse_Chen/Python_machine_learning


  在前几篇文章中,我们介绍了k近邻算法线性回归两个基本的机器学习模型。或许已经注意到,除了模型本身以外,要训练一个好的机器学习模型,还有许多需要注意的地方。例如,我们将数据集分为训练集和测试集,在前者上用不同参数训练,再在后者上测试,以选出效果最好的模型参数。此外,在线性回归一文中,我们还对数据集做了预处理,把每个特征下的数据分别做归一化,放缩到同一数量级上。诸如此类的细节在机器学习中还有很多,它们虽然本身和算法关系不大,但对模型最终效果的好坏又有着至关重要的影响,而把握好这些细节的前提是深入理解机器学习的基本思想。本文就来讲解这些机器学习模型的基本思想。

一、欠拟合与过拟合

  在线性回归一文中,我们看到了在某些情况下,无论用解析方法还是梯度下降方法,线性回归都无法得到让人满意的模型,训练损失与测试损失都较大。而在另一些情况下,虽然线性回归可以在训练集上得到非常好的效果,但在测试集上表现较差。在机器学习中,从模型实验表现的视角来看,我们将训练与测试损失都较大的情况称为欠拟合(underfitting),将训练损失小而测试损失大的情况称为过拟合(overfitting)。

  在正式讨论欠拟合和过拟合之前,我们首先需要了解数据的模式和噪声。

  • 数据的模式指数据背后的规律,例如线性回归中讨论的房屋售价与该区域的居民收入、房屋年龄、房间数、卧室数等特征之间的定量关系。每个数据集(或数据源)背后都有其真实的模式。在有监督学习中,标签和数据特征之间的模式,可以记为 f ( x ) f(\boldsymbol x) f(x),机器学习任务就是构建模型和学习算法,使得模型能发现数据背后的这些模式,以至于可以在新的数据上做出相应的目标预测。

  • 数据的噪声指具体的数据点偏离其数据集模式的随机信息。真实观测到的数据点往往带有噪声。如图1所示,用高精度的游标卡尺测量工件的直径,手拿不稳会引起误差;观察温度计的读数,视线的高低会引起误差;测量汽车的速度,会因为码表的最小精度不够引起误差;判断足球是否进入球门,也会因为视角的原因产生误差。因此,每一个训练数据或测试数据实例 ( x , y ) (\boldsymbol x, y) (x,y)其实也很可能有误差,也即是 y ≈ f ( x ) y \approx f(\boldsymbol x) yf(x)

在这里插入图片描述

图1 真实的测量数据总是带有噪声

  下面,我们依次来分析出现欠拟合和过拟合这两种情况的原因。

  欠拟合指模型无法拟合数据中的重要模式,以至于模型预测精度低。欠拟合一般出现在模型复杂度小于数据本身复杂度的场景中,导致训练与测试损失都较大的情况。例如在线性回归一章中提到,该模型的有效性依赖于数据的线性分布假设。如果数据分布本身就与线性分布偏差较大,如 y = e x y=e^x y=ex,那么在大的数据范围下,任何线性模型都无法拟合输入与输出之间的关系,这时就会出现欠拟合的现象。另一种情况常常出现在梯度下降算法的过程中。如果我们的迭代次数较少,或学习率过低,那么参数离最优参数就还有一定距离,模型拟合的结果同样较差。需要注意,这两种情况虽然都表现为训练与测试误差都较大,但其成因有本质区别。后者可以通过增加迭代次数、调整学习率等方式缓解;而前者必须通过分析数据分布后,更换更合适的模型来解决。

  与欠拟合相对,过拟合指模型过度地拟合到了观测数据中噪声的部分,以至于在未观测的数据标签预测时出现较大偏差的现象。过拟合一般出现在模型复杂度大于数据复杂度的场景中,导致训练损失小而测试损失大的情况。这时,模型可能将数据中的噪声和离群点也纳入考量,学到了过于“精确”的关系。在线性回归中,如果训练集中数据的数量 N N N小于等于数据特征数量 d d d,就一定会出现过拟合现象,因为在 d d d维空间中,任意小于等于 d d d个点一定在同一个 d − 1 d-1 d1 维超平面上。我们举一个简单的例子,设真实的数据分布为 y = x y=x y=x,但是 y y y带有一定的噪声。训练集中共有两个点 ( 0 , − 0.1 ) (0,-0.1) (0,0.1) ( 1 , 1.1 ) (1,1.1) (1,1.1),测试集包含一个点 ( 10 , 10 ) (10,10) (10,10)。用线性回归进行拟合,得到的模型是 y ^ = 1.2 x − 0.1 \hat y = 1.2x-0.1 y^=1.2x0.1。该模型在测试集上的预测结果是 1.2 × 10 − 0.1 = 11.9 1.2 \times 10 - 0.1 = 11.9 1.2×100.1=11.9,已经有了很大误差。下图展示了该示例中模型的图像。

在这里插入图片描述

图2 线性模型的过拟合

  更一般的情况是,欠拟合和过拟合取决于模型本身的复杂度。我们将视角从线性模型扩展到多项式模型,即提前选定多项式的次数 n n n,用 y = a 0 + a 1 x + ⋯ + a n x n y = a_0 + a_1x + \cdots + a_nx^n y=a0+a1x++anxn 来拟合输入与输出,其中模型的参数是系数 a 0 , a 1 , … , a n a_0,a_1,\ldots, a_n a0,a1,,an。对于相同的数据,不同的模型选择会导致完全不同的结果。如图3所示,蓝色点是由模式函数 f ( x ) = x 3 − 3 x 2 + 2 x + 1 f(x)=x^3 - 3x^2 + 2x + 1 f(x)=x33x2+2x+1 再加上随机噪声生成的,其中空心点是测试集。从左到右的红色曲线,分别是用一次多项式(线性模型)、三次多项式、九次多项式拟合实心的训练集的结果。可以看出,左边线性模型由于复杂度不够,无法学到数据中的基本模式,出现欠拟合;中间的三次多项式复杂度与数据本身相匹配,整体效果最好;右边的九次多项式虽然能够完美穿过所有实心蓝色点,包括产生标签 y y y的噪声,在训练集上达到了零损失,但在同一模式函数 f ( x ) f(x) f(x)生成的测试集(空心蓝色点)上预测结果误差很大。因此,即使不考虑模型训练,在选择模型时,我们应当首先考察问题的复杂度,选择与其相匹配的模型。

在这里插入图片描述

图3 不同次数的多项式拟合数据的结果,其中蓝色实心点为训练数据,蓝色空心点为测试数据

  欠拟合与过拟合在模型训练时也有迹可循,我们通常可以根据训练集上模型的损失和测试集上模型的损失来判断。如图4所示,从左向右依次是模型欠拟合、恰好拟合(well fitting)和过拟合时训练损失和测试损失的曲线。

  • 在欠拟合时,训练损失和测试损失都未收敛,还有明显的下降趋势,说明模型还没有完全捕捉到数据中的主要模式。这时,我们可以增加学习率或者训练轮数,让模型充分拟合。如果一个模型的训练损失和测试损失都收敛,但其相对大小很大,说明模型或许本身复杂度较低,即使穷尽了模型的能力,也无法捕捉到数据中的主要模式,例如上面用线性模型拟合三次多项式数据。

  • 在恰好拟合时,训练损失和测试损失先下降后收敛到一个比较低的位置,说明模型基本达到了其自身能力的极限,很好地完成了学习任务。

  • 在过拟合时,虽然训练损失还在下降或已经收敛,测试损失却在下降后重新上升,说明在学习过程中模型学到了很多训练集中独有的模式,拟合到了训练集上的噪声信号,其泛化能力反而降低了。此外,在判断模型是否拟合良好时,损失的相对大小也是一个重要指标。

在这里插入图片描述

图4 模型训练损失和测试损失曲线示例

  总之,在实践中我们可以将训练曲线和损失大小综合起来判断模型训练的情况,由此调整模型的类型或者超参数。

二、正则化约束

  当我们确定了要使用的模型后,一方面,很多情况下我们无法直接确定该模型的复杂度是否仍然过高;另一方面,梯度下降的迭代次数太大也会导致过拟合现象,而反复调整迭代次数又非常消耗时间和计算资源。本质上,过拟合是由于模型的参数过于复杂所引起的。因此,我们希望对模型的参数引入某种限制,在训练的过程中就防止其向过拟合的方向发展。像这样对参数的复杂度进行约束的方法,就称为正则化(regularization)。下面,我们以线性规划问题为例,介绍正则化的思想。

  前面已经讲过,在线性规划中,如果数据集的数据个数 N N N小于等于数据特征数 d d d,就会出现过拟合现象。从理论上分析,线性规划模型的解析解为 θ = ( X T X ) − 1 X T y \boldsymbol{\theta} = (\boldsymbol{X}^\mathrm{T}\boldsymbol{X})^{-1}\boldsymbol{X}^\mathrm{T}\boldsymbol{y} θ=(XTX)1XTy N ≤ d N≤d Nd 时,矩阵 X T X \boldsymbol{X}^\mathrm{T}\boldsymbol{X} XTX的逆矩阵不存在,因此上式无法计算。为了解决这一问题,在考虑所有样本的平方误差总和的情况下,我们将线性规划的损失函数改为 J ( θ ) = 1 2 ( y − X θ ) T ( y − X θ ) + λ 2 ∥ θ ∥ 2 J(\boldsymbol{\theta}) = \frac12 (\boldsymbol{y} - \boldsymbol{X\theta})^\mathrm{T}(\boldsymbol{y} - \boldsymbol{X\theta}) + \frac{\lambda}{2} \lVert \boldsymbol\theta \lVert^2 J(θ)=21(y)T(y)+2λθ2 其中的 λ 2 ∥ θ ∥ 2 \frac\lambda2 \lVert\boldsymbol\theta \lVert^2 2λθ2 就称为正则项, λ > 0 \lambda > 0 λ>0 表示正则化约束的强度。相比于原本的损失函数,新的损失函数多了与 θ \boldsymbol\theta θ L 2 L_2 L2范数的平方有关的项。当我们最小化损失函数时,这一正则项的存在就要求参数 θ \boldsymbol\theta θ L 2 L_2 L2范数较低,从而限制了其复杂度。对于那些对模型预测作用不大的特征、甚至完全是噪声的特征,正则化会使得与这些特征相关的系数缩减到接近 0 0 0,让这些特征失效,降低模型的复杂度。

  从理论角度分析,我们用新的损失函数重新求解线性回归问题,可以得到 ∇ J ( θ ) = − X T y + X T X θ + λ θ = ( X T X + λ I ) θ − X T y \nabla J(\boldsymbol\theta) = -\boldsymbol{X}^\mathrm{T}\boldsymbol{y} + \boldsymbol{X}^\mathrm{T}\boldsymbol{X\theta} + \lambda \boldsymbol\theta = (\boldsymbol{X}^\mathrm{T}\boldsymbol X + \lambda\boldsymbol I)\boldsymbol\theta - \boldsymbol{X}^\mathrm{T}\boldsymbol{y} J(θ)=XTy+XT+λθ=(XTX+λI)θXTy 令其等于 0 \boldsymbol 0 0,解出 θ = ( X T X + λ I ) − 1 X T y \boldsymbol\theta = (\boldsymbol{X}^\mathrm{T}\boldsymbol X {\color{red}{+ \lambda\boldsymbol I}})^{-1}\boldsymbol{X}^\mathrm{T}\boldsymbol{y} θ=(XTX+λI)1XTy

  与原本的解对比可以发现,加入正则化后,新的解多出了 λ I \lambda\boldsymbol I λI一项。根据矩阵相关的理论,形如 X T X + λ I \boldsymbol{X}^\mathrm{T}\boldsymbol X + \lambda\boldsymbol I XTX+λI 的矩阵,其中 X T X \boldsymbol{X}^\mathrm{T}\boldsymbol X XTX为半正定矩阵,其特征值皆为非负数,无论它是否可逆,只要 λ \lambda λ为正数,那么矩阵 X T X + λ I \boldsymbol{X}^\mathrm{T}\boldsymbol X + \lambda\boldsymbol I XTX+λI 的所有特征值皆为正数,该矩阵必定可逆。因此,只要正则化约束存在, θ \boldsymbol\theta θ在理论上就是有解的。像这样使用了 L 2 L_2 L2范数的正则化方法就称为 L 2 L_2 L2正则化,又称为岭回归(ridge regression)。

   L 2 L_2 L2范数的计算方式是将向量的所有元素平方相加,因此限制的是向量整体的规模。除了 L 2 L_2 L2范数,我们还可以用其他范数来进行正则化约束。例如,我们有时希望得到的模型参数更稀疏,即参数中 0 0 0的数量尽可能多。由于 L 0 L_0 L0范数衡量的是向量中非零元素的个数,因此,我们可以选用 L 0 L_0 L0范数来进行正则化,最小化 L 0 L_0 L0范数就会使向量中的非零元素尽可能少。然而, L 0 L_0 L0范数中含有示性函数 I \mathbb{I} I,并不可导,无论是解析求解还是梯度下降都比较困难。因此,我们常用 L 1 L_1 L1范数代替 L 0 L_0 L0范数作为约束。理论上可以证明, L 1 L_1 L1范数是对 L 0 L_0 L0范数的非常好的近似。

  加入 L 1 L_1 L1正则化后,线性回归的损失函数变为 J ( θ ) = 1 2 ( y − X θ ) T ( y − X θ ) + λ ∥ θ ∥ 1 J(\boldsymbol{\theta}) = \frac12 (\boldsymbol{y} - \boldsymbol{X\theta})^\mathrm{T}(\boldsymbol{y} - \boldsymbol{X\theta}) + \lambda \lVert \boldsymbol\theta \lVert_1 J(θ)=21(y)T(y)+λθ1 其梯度为 ∇ J ( θ ) = − X T y + X T X θ + λ s g n ( θ ) \nabla J(\boldsymbol{\theta}) = -\boldsymbol{X}^\mathrm{T}\boldsymbol{y} + \boldsymbol{X}^\mathrm{T}\boldsymbol{X\theta} + \lambda \mathrm{sgn}(\boldsymbol\theta) J(θ)=XTy+XT+λsgn(θ) 其中, s g n ( x ) \mathrm{sgn}(x) sgn(x)是符号函数,定义为
s g n ( x ) = { 1 ( x > 0 ) 0 ( x = 0 ) − 1 ( x < 0 ) \mathrm{sgn}(x) = \begin{cases} 1 & (x > 0) \\ 0 & (x = 0) \\ -1 & (x < 0) \end{cases} sgn(x)= 101(x>0)(x=0)(x<0)

   L 1 L_1 L1正则化的解析求解较为困难,但是可以利用梯度下降法得到数值解。带有 L 1 L_1 L1约束的线性回归方法又称作最小绝对值收敛和选择算子(least absolute shrinkage and selection operator,LASSO)回归,简称LASSO回归。

  下面,我们用图像来说明 L 1 L_1 L1 L 2 L_2 L2正则化起作用的原理。图5展示了参数 θ \boldsymbol\theta θ为二维的场景,原始的损失函数 J 0 J_0 J0 θ \boldsymbol \theta θ与平面上一点 θ ∗ \boldsymbol \theta^* θ之间的距离有关。左图绘制了不带有正则化的损失函数 J 0 ( θ ) J_0(\boldsymbol\theta) J0(θ)的等值线,在每个椭圆上,损失函数的值相等。颜色越偏蓝的地方损失越小,越偏红的地方则越大。椭圆中心的 θ ∗ \boldsymbol\theta^* θ是损失函数 J 0 ( θ ) J_0(\boldsymbol\theta) J0(θ)的最小值点,也就是最优参数。当不加正则化约束时,对损失函数做梯度下降,最终就会收敛到 θ ∗ \boldsymbol\theta^* θ

  中图和右图分别描绘了带有 L 2 L_2 L2正则化和 L 1 L_1 L1正则化时损失函数等值线的变化。可以明显看出,引入正则化后,由于零向量的范数最小,损失函数较小的区域向原点偏移了,且形状也有变化。作为参考,图中的蓝色椭圆是原始损失函数 J 0 J_0 J0的等值线,而红色的圆形或方形是相应范数的等值线。图中的 θ λ ∗ \boldsymbol\theta_\lambda^* θλ是带正则化的最优参数。可以证明,整体损失函数的最小值会在某两条等值线相切的地方取到,而具体的位置受正则化强度 λ \lambda λ的控制。 λ \lambda λ越小,能容忍的范数值就越大,最优参数就离原点就越远。

在这里插入图片描述

图5 L 1 L_1 L1 L 2 L_2 L2正则化约束的对比

  而从两幅图正则化约束对应等值线的形状,我们也能看出 L 1 L_1 L1 L 2 L_2 L2正则化的区别。对 L 1 L_1 L1正则化产生的正方形区域来说,切点更容易在正方形的顶点处取到,或者非常接近顶点。此时, θ λ ∗ \boldsymbol\theta_\lambda^* θλ的某些维度很接近零 0 0 0,变得更加稀疏。对 L 2 L_2 L2正则化产生的圆形区域来说,不同方向是对称的,因此它衡量参数的整体规模。

  正则化的思想不仅在线性规划中有作用,即使在更一般的机器学习模型中,它也是防止过拟合的有效手段。我们回顾本章开始用多项式拟合数据点的例子,九次多项式出现了严重的过拟合问题。现在,我们为其加上不同强度的 L 2 L_2 L2约束再进行拟合,结果如图6所示。可以看出,当添加强度合适的 L 2 L_2 L2正则化约束后,模型过拟合的现象得到了明显改善。然而,当 λ \lambda λ越来越大时,模型的复杂度被大大限制,反而过于简单,拟合结果也由过拟合转而变为欠拟合。因此,正则化约束的强度并非越大越好,而是应当根据模型复杂度和实验结果逐步调整为合适的值。

在这里插入图片描述

图6 带正则化约束的九次多项式拟合结果

  上述例子也展示了机器学习模型选择的一个基本思想,即模型复杂度决定了其能够拟合到的模式的复杂程度,如果所选模型的复杂度不够,那么再什么调试也无法使其拟合到重要的数据模式,欠拟合问题无法解决;而如果所选模型的复杂度较高,为了防止其过拟合到数据噪声,可以加入一定程度的正则化约束。因此,一般选择模型时,我们可以选择具有一定复杂度的模型,首先杜绝欠拟合问题,再通过调整正则化的强度来控制过拟合的程度。

下面是岭回归和LASSO回归的代码示例:

# 岭回归
import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Ridge
from sklearn.metrics import mean_squared_error, r2_score

# 加载数据集
data = load_boston()
X = data.data
y = data.target

# 拆分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 创建岭回归模型
ridge = Ridge(alpha=1.0)  # alpha 是正则化强度

# 训练模型
ridge.fit(X_train, y_train)

# 训练集预测
y_train_pred = ridge.predict(X_train)
# 测试集预测
y_test_pred = ridge.predict(X_test)

# 计算训练集和测试集的 R² 和 MSE
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)

# 打印结果
print("Ridge Regression Coefficients:", ridge.coef_)
print("Ridge Regression Intercept:", ridge.intercept_)
print("训练集R²: {:.2f}".format(train_r2), end=', ')
print("训练集MSE: {:.2f}".format(train_mse))
print("测试集R²: {:.2f}".format(test_r2), end=', ')
print("测试集MSE: {:.2f}".format(test_mse))

在这里插入图片描述

# LASSO回归
import numpy as np
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.linear_model import Lasso
from sklearn.metrics import mean_squared_error, r2_score

# 加载数据集
data = load_boston()
X = data.data
y = data.target

# 拆分数据集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 创建 LASSO 回归模型
lasso = Lasso(alpha=1.0)  # alpha 是正则化强度

# 训练模型
lasso.fit(X_train, y_train)

# 训练集预测
y_train_pred = lasso.predict(X_train)
# 测试集预测
y_test_pred = lasso.predict(X_test)

# 计算训练集和测试集的 R² 和 MSE
train_r2 = r2_score(y_train, y_train_pred)
test_r2 = r2_score(y_test, y_test_pred)
train_mse = mean_squared_error(y_train, y_train_pred)
test_mse = mean_squared_error(y_test, y_test_pred)

# 打印结果
print("LASSO Regression Coefficients:", lasso.coef_)
print("LASSO Regression Intercept:", lasso.intercept_)
print("训练集R²: {:.2f}".format(train_r2), end=', ')
print("训练集MSE: {:.2f}".format(train_mse))
print("测试集R²: {:.2f}".format(test_r2), end=', ')
print("测试集MSE: {:.2f}".format(test_mse))

在这里插入图片描述

三、输入特征与相似度

  上节中讲到,在引入正则化后,矩阵 X T X + λ I \boldsymbol X^\mathrm{T} \boldsymbol X +\lambda\boldsymbol I XTX+λI λ \lambda λ足够大时一定可逆。因此,我们可以对线性回归的结果进行化简。模型用最优参数对训练数据预测的结果为 f θ ( X ) = X θ = X ( X T X + λ I ) − 1 X T y f_{\boldsymbol{\theta}}(\boldsymbol X) = \boldsymbol X\boldsymbol\theta = \boldsymbol X (\boldsymbol X^\mathrm{T} \boldsymbol X +\lambda\boldsymbol I)^{-1}\boldsymbol X^\mathrm{T}\boldsymbol y fθ(X)=Xθ=X(XTX+λI)1XTy

  利用矩阵中的贯穿恒等式(push-through identity),对于矩阵 P ∈ R n × m \boldsymbol P \in \mathbb{R}^{n\times m} PRn×m Q ∈ R m × n \boldsymbol Q \in \mathbb{R}^{m\times n} QRm×n,有 ( λ I + P Q ) − 1 P = P ( λ I + Q P ) − 1 (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q)^{-1}\boldsymbol P = \boldsymbol P(\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P)^{-1} (λI+PQ)1P=P(λI+QP)1

  贯穿恒等式证明如下,设矩阵 P ∈ R n × m \boldsymbol P \in \mathbb{R}^{n\times m} PRn×m Q ∈ R m × n \boldsymbol Q \in \mathbb{R}^{m\times n} QRm×n,那么 P ( λ I + Q P ) = λ P + P Q P = ( λ I + P Q ) P \boldsymbol P(\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P) = \lambda\boldsymbol P + \boldsymbol P \boldsymbol Q \boldsymbol P = (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q) \boldsymbol P P(λI+QP)=λP+PQP=(λI+PQ)P 如果 λ \lambda λ足够大,那么 λ I + P Q \lambda\boldsymbol I + \boldsymbol P \boldsymbol Q λI+PQ λ I + Q P \lambda\boldsymbol I + \boldsymbol Q \boldsymbol P λI+QP 都可逆。在上式两边分别乘以其逆矩阵,就得到
P ( λ I + Q P ) = ( λ I + P Q ) P ⇒ ( λ I + P Q ) − 1 P ( λ I + Q P ) ( λ I + Q P ) − 1 = ( λ I + P Q ) − 1 ( λ I + P Q ) P ( λ I + Q P ) − 1 ⇒ ( λ I + P Q ) − 1 P = P ( λ I + Q P ) − 1 \begin{aligned} && \boldsymbol P(\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P) &= (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q) \boldsymbol P \\ \Rightarrow && (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q)^{-1}\boldsymbol P(\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P)(\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P)^{-1} &= (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q)^{-1}(\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q) \boldsymbol P (\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P)^{-1} \\ \Rightarrow && (\lambda\boldsymbol I + \boldsymbol P \boldsymbol Q)^{-1}\boldsymbol P &= \boldsymbol P (\lambda\boldsymbol I + \boldsymbol Q \boldsymbol P)^{-1} \end{aligned} P(λI+QP)(λI+PQ)1P(λI+QP)(λI+QP)1(λI+PQ)1P=(λI+PQ)P=(λI+PQ)1(λI+PQ)P(λI+QP)1=P(λI+QP)1 当我们应用贯穿恒等式时,需要注意两边括号内的矩阵阶数是不同的, P Q \boldsymbol P \boldsymbol Q PQ n n n阶方阵,而 Q P \boldsymbol Q \boldsymbol P QP m m m阶方阵,对应的单位矩阵的阶数也分别是 n n n m m m。矩阵求逆是一个非常复杂的运算,对一般的 n n n阶方阵求逆的时间复杂度大致是 O ( n 3 ) O(n^3) O(n3)。因此,如果 n ≫ m n \gg m nm,贯穿恒等式就把 n n n阶方阵求逆转化为了更加简单的 m m m阶方阵求逆。

  令 P = X T \boldsymbol P = \boldsymbol X^\mathrm{T} P=XT Q = X \boldsymbol Q = \boldsymbol X Q=X,我们得到 f θ ( X ) = X ( X T X + λ I ) − 1 X T y = X X T ( X X T + λ I ) − 1 y = K ( K + λ I ) − 1 y f_{\boldsymbol{\theta}}(\boldsymbol X) = \boldsymbol X (\boldsymbol X^\mathrm{T} \boldsymbol X +\lambda\boldsymbol I)^{-1}\boldsymbol X^\mathrm{T}\boldsymbol y = \boldsymbol X\boldsymbol X^\mathrm{T} (\boldsymbol X \boldsymbol X^\mathrm{T} +\lambda\boldsymbol I)^{-1}\boldsymbol y = \boldsymbol K (\boldsymbol K + \lambda \boldsymbol I)^{-1} \boldsymbol y fθ(X)=X(XTX+λI)1XTy=XXT(XXT+λI)1y=K(K+λI)1y 其中 K = X X T \boldsymbol K = \boldsymbol X \boldsymbol X^{\mathrm{T}} K=XXT。可以发现, K \boldsymbol K K的第 i i i j j j列的元素恰好等于数据集中第 i i i个样本和第 j j j个样本的内积,即 K i j = x i T x j \boldsymbol K_{ij} = \boldsymbol x_i^\mathrm{T} \boldsymbol x_j Kij=xiTxj。为什么最后的结果会包含样本之间的内积形式呢?考虑两个向量 a \boldsymbol a a b \boldsymbol b b之间的夹角 α \alpha α的余弦值,它可以由内积计算得到: cos ⁡ α = a ⋅ b ∥ a ∥ ∥ b ∥ = a ∥ a ∥ ⋅ b ∥ b ∥ \cos \alpha = \frac{\boldsymbol a \cdot \boldsymbol b}{\| \boldsymbol a \|\|\boldsymbol b\|} = \frac{\boldsymbol a}{\|\boldsymbol a\|} \cdot \frac{\boldsymbol b}{\|\boldsymbol b\|} cosα=a∥∥bab=aabb

  上式表明,如果向量 a \boldsymbol a a b \boldsymbol b b的模长都是1,它们之间夹角的余弦值就等于其内积。而当两个向量重合时,夹角为 0 0 0,内积的最大值就是1。沿着这一思路,即使我们不对模长做归一化,而是直接将两个向量做内积,得到的结果同样可以在一定程度上反映它们之间的相似性。当然,这样的相似性在向量的模长相差不大时更准确一些。对于有实际意义的样本向量来说,向量的每个维度代表样本的一个特征,不同样本的同一维度代表同一个特征。这样,样本 a \boldsymbol a a和样本 b \boldsymbol b b的内积就体现了它们在各个特征下的关联程度。

  机器学习的归根到底还是基于样本的统计信息。在k近邻算法中讲过,“近朱者赤,近墨者黑”,所有的模型都基于“相似的样本应当具有相似的标签”这一基本假设。例如在分类问题中,相似的样本大概率具有相似的类别;在线性回归这样的连续值回归问题中,相似的样本大概率具有相似的 y y y值。这样,在线性回归的解中出现样本间的相似度就是相当自然的事情了。而当我们要用训练好的模型预测新样本 x \boldsymbol x x的类别或者目标值 y y y时,实际上仍然是在通过训练集中与新样本 x \boldsymbol x x较为相似的那些样本进行推测。还以线性回归为例,解出模型参数 θ \boldsymbol \theta θ后,对新样本 x \boldsymbol x x的预测值为
f θ ( x ) = x T θ = x T ( X T X + λ I ) − 1 X T y = x T X T ( X X T + λ I ) − 1 y = ( x T x 1 , … , x T x n ) ( X X T + λ I ) − 1 y \begin{aligned} f_{\boldsymbol{\theta}}(\boldsymbol x) &= \boldsymbol x^\mathrm{T}\boldsymbol \theta = \boldsymbol x^\mathrm{T} (\boldsymbol X^\mathrm{T}\boldsymbol X + \lambda\boldsymbol I)^{-1} \boldsymbol X^\mathrm{T} \boldsymbol y \\[1ex] &= \boldsymbol x^\mathrm{T}\boldsymbol X^\mathrm{T} (\boldsymbol X\boldsymbol X^\mathrm{T} + \lambda\boldsymbol I)^{-1}\boldsymbol y \\[1ex] &= (\boldsymbol x^\mathrm{T} \boldsymbol x_1, \ldots, \boldsymbol x^\mathrm{T} \boldsymbol x_n) (\boldsymbol X\boldsymbol X^\mathrm{T} + \lambda\boldsymbol I)^{-1}\boldsymbol y \end{aligned} fθ(x)=xTθ=xT(XTX+λI)1XTy=xTXT(XXT+λI)1y=(xTx1,,xTxn)(XXT+λI)1y 结果中的第一个向量的每个元素恰好是 x \boldsymbol x x与训练集中的样本 x i \boldsymbol x_i xi的内积,而最后一项是训练集中的标签 y \boldsymbol y y。这就说明,线性回归的预测其实是在用 x \boldsymbol x x与训练集中样本的相似度 x T x i \boldsymbol x^\mathrm{T} \boldsymbol x_i xTxi,经过某个变换(乘以 ( X X T + λ I ) − 1 (\boldsymbol X\boldsymbol X^\mathrm{T} + \lambda\boldsymbol I)^{-1} (XXT+λI)1)后作为权重,对训练集的标签计算加权平均。不同模型有不同的利用相似度的方式,比如KNN就直接把相似度作为直接依据进行分类,而更复杂的模型可能要对相似度进行更加复杂的变换。

  在考虑样本间的相似度时,又产生了另外一个问题:为什么直接内积作为相似度是合理的?事实上,在大多数情况下,我们不会直接用原始输入中的特征作为判断相似度的依据,而是会先将输入经过某种变换 ϕ  ⁣ : R d → R h \phi \colon \mathbb{R}^d \to \mathbb{R}^h ϕ:RdRh,将 d d d维的特征变为 h h h维。函数 ϕ \phi ϕ就称为特征映射函数,它的主要目的就是把原始的特征经过某种筛选或者变换,得到更能反映样本本质的特征。比如样本 x \boldsymbol x x是一些长方形,其原始特征中包含样本的长和宽,记为 x ( l ) x^{(l)} x(l) x ( w ) x^{(w)} x(w),而样本的标签只和其面积有关。那么,直接用长、宽两个特征做内积,就不能合适地反映在该问题下样本的相似情况。这时,我们可以引入特征映射 ϕ ( x ( l ) , x ( w ) , … ) = ( x ( l ) x ( w ) , … ) \phi(x^{(l)}, x^{(w)}, \ldots) = (x^{(l)} x^{(w)}, \ldots) ϕ(x(l),x(w),)=(x(l)x(w),),把长、宽两个特征映射成面积这一个特征,再用面积去作为计算相似度的特征。我们还以线性回归为例,设映射后的样本组成的矩阵为
Φ = ( ϕ ( x 1 ) T ϕ ( x 2 ) T ⋮ ϕ ( x N ) T ) = ( ϕ 1 ( x 1 ) ϕ 2 ( x 1 ) … ϕ h ( x 1 ) ϕ 1 ( x 2 ) ϕ 2 ( x 2 ) … ϕ h ( x 2 ) ⋮ ⋮ ⋮ ϕ 1 ( x N ) ϕ 2 ( x N ) … ϕ h ( x N ) ) \boldsymbol\Phi = \begin{pmatrix} \phi(\boldsymbol x_1)^\mathrm{T} \\ \phi(\boldsymbol x_2)^\mathrm{T} \\ \vdots \\ \phi(\boldsymbol x_N)^\mathrm{T} \end{pmatrix} = \begin{pmatrix} \phi_1(\boldsymbol x_1) & \phi_2(\boldsymbol x_1) & \ldots & \phi_h(\boldsymbol x_1) \\ \phi_1(\boldsymbol x_2) & \phi_2(\boldsymbol x_2) & \ldots & \phi_h(\boldsymbol x_2) \\ \vdots & \vdots & & \vdots \\ \phi_1(\boldsymbol x_N) & \phi_2(\boldsymbol x_N) & \ldots & \phi_h(\boldsymbol x_N) \\ \end{pmatrix} Φ= ϕ(x1)Tϕ(x2)Tϕ(xN)T = ϕ1(x1)ϕ1(x2)ϕ1(xN)ϕ2(x1)ϕ2(x2)ϕ2(xN)ϕh(x1)ϕh(x2)ϕh(xN) 称为特征映射矩阵。其中 ϕ k ( x j ) \phi_k(\boldsymbol x_j) ϕk(xj)表示样本 x j \boldsymbol x_j xj经映射后的第 k k k个特征。该矩阵完全可以替代原始推导中样本矩阵 X \boldsymbol X X的位置,因此,经过映射的线性回归的解析解为 θ = ( Φ T Φ + λ I ) − 1 Φ T y = Φ T ( Φ Φ T + λ I ) − 1 y \boldsymbol \theta = (\boldsymbol\Phi^\mathrm{T}\boldsymbol\Phi + \lambda\boldsymbol I)^{-1}\boldsymbol\Phi^\mathrm{T}\boldsymbol y = \boldsymbol \Phi^\mathrm{T} (\boldsymbol\Phi\boldsymbol\Phi^\mathrm{T} + \lambda\boldsymbol I)^{-1}\boldsymbol y θ=(ΦTΦ+λI)1ΦTy=ΦT(ΦΦT+λI)1y

  同样,对训练数据的预测结果为 f θ ( X ) = Φ θ = Φ Φ T ( Φ Φ T + λ I ) − 1 y = K ( K + λ I ) − 1 y f_{\boldsymbol{\theta}}(\boldsymbol X) = \boldsymbol \Phi\boldsymbol \theta = \boldsymbol \Phi\boldsymbol \Phi^\mathrm{T} (\boldsymbol\Phi\boldsymbol\Phi^\mathrm{T} + \lambda\boldsymbol I)^{-1}\boldsymbol y = \boldsymbol K(\boldsymbol K + \lambda\boldsymbol I)^{-1} \boldsymbol y fθ(X)=Φθ=ΦΦT(ΦΦT+λI)1y=K(K+λI)1y 其中 K i j = K ( x i , x j ) = ϕ ( x i ) T ϕ ( x j ) \boldsymbol K_{ij} = K(\boldsymbol x_i, \boldsymbol x_j) = \phi(\boldsymbol x_i)^\mathrm{T}\phi(\boldsymbol x_j) Kij=K(xi,xj)=ϕ(xi)Tϕ(xj) 称为核矩阵(kernel matrix),里面的 K ( ⋅ , ⋅ ) K(\cdot, \cdot) K(,)称为核函数(kernel function)。容易发现,如果我们不做特征映射,或者说 ϕ ( x ) = x \phi(\boldsymbol x) = \boldsymbol x ϕ(x)=x 是恒等映射,这里得到的结果就和本节开始时得到的结果完全一样了。

  大家可能会疑惑,既然要计算两个向量的内积,为什么要多此一举引入核函数 K K K呢?仔细观察可以看出,在最终的预测式中,映射函数 ϕ \phi ϕ并没有单独出现过,化简后留下的只有两个特征向量的内积。这提示我们,可以设计合适的核函数 K K K来绕过 ϕ ( x i ) \phi(\boldsymbol x_i) ϕ(xi) ϕ ( x j ) \phi(\boldsymbol x_j) ϕ(xj)的计算,而是一步到位,直接算出 K ( x i , x j ) = ϕ ( x i ) T ϕ ( x j ) K(\boldsymbol x_i, \boldsymbol x_j) = \phi(\boldsymbol x_i)^\mathrm{T}\phi(\boldsymbol x_j) K(xi,xj)=ϕ(xi)Tϕ(xj)。例如,设核函数 K ( x , y ) = ( x T y ) 2 K(\boldsymbol x, \boldsymbol y) = (\boldsymbol x^\mathrm{T}\boldsymbol y)^2 K(x,y)=(xTy)2,其中 x , y ∈ R n \boldsymbol x, \boldsymbol y \in \mathbb{R}^n x,yRn。它对应的映射函数 ϕ \phi ϕ可以通过如下过程计算:
K ( x , y ) = ( ∑ i = 1 n x i y i ) ( ∑ j = 1 n x j y j ) = ∑ i = 1 n ∑ j = 1 n x i y i x j y j = ∑ i = 1 n ∑ j = 1 n ( x i x j ) ( y i y j ) \begin{aligned} K(\boldsymbol x, \boldsymbol y) &= \left(\sum_{i=1}^n x_iy_i\right)\left(\sum_{j=1}^n x_jy_j\right) \\ &= \sum_{i=1}^n \sum_{j=1}^n x_iy_ix_jy_j \\ &= \sum_{i=1}^n \sum_{j=1}^n (x_ix_j)(y_iy_j) \end{aligned} K(x,y)=(i=1nxiyi)(j=1nxjyj)=i=1nj=1nxiyixjyj=i=1nj=1n(xixj)(yiyj) n = 3 n=3 n=3 时,上式给出的映射函数为 ϕ ( x ) = ϕ ( x 1 , x 2 , x 3 ) = ( x 1 2 , x 1 x 2 , x 1 x 3 , x 2 x 1 , x 2 2 , x 2 x 3 , x 3 x 1 , x 3 x 2 , x 3 2 ) T \phi(\boldsymbol x) = \phi(x_1, x_2, x_3) = (x_1^2, x_1x_2, x_1x_3, x_2x_1, x_2^2, x_2x_3, x_3x_1, x_3x_2, x_3^2)^\mathrm{T} ϕ(x)=ϕ(x1,x2,x3)=(x12,x1x2,x1x3,x2x1,x22,x2x3,x3x1,x3x2,x32)T 可以自行验算 K ( x , y ) = ϕ ( x ) T ϕ ( y ) K(\boldsymbol x, \boldsymbol y) = \phi(\boldsymbol x)^\mathrm{T}\phi(\boldsymbol y) K(x,y)=ϕ(x)Tϕ(y)。在本例中,计算 K K K需要计算两个 n n n维向量的内积,再将得到的标量平方,时间复杂度是 O ( n ) O(n) O(n)。但映射函数 ϕ \phi ϕ是从 n n n维到 n 2 n^2 n2维的映射,计算映射要先花费 O ( n 2 ) O(n^2) O(n2)的时间,再将两个映射后的向量做内积又要花费 O ( n 2 ) O(n^2) O(n2)的时间,远远超出了用核函数 K K K计算的消耗。进一步,在用模型预测新样本 x \boldsymbol x x时,得到的结果也能用核函数表示: f θ ( x ) = ϕ ( x ) T Φ T ( Φ Φ T + λ I ) − 1 y = ( K ( x , x 1 ) , … , K ( x , x N ) ) ( K + λ I ) − 1 y f_{\boldsymbol{\theta}}(\boldsymbol x) = \phi(\boldsymbol x)^\mathrm{T}\boldsymbol \Phi^\mathrm{T} (\boldsymbol\Phi\boldsymbol\Phi^\mathrm{T} + \lambda\boldsymbol I)^{-1}\boldsymbol y = (K(\boldsymbol x, \boldsymbol x_1), \ldots, K(\boldsymbol x, \boldsymbol x_N)) (\boldsymbol K + \lambda\boldsymbol I)^{-1} \boldsymbol y fθ(x)=ϕ(x)TΦT(ΦΦT+λI)1y=(K(x,x1),,K(x,xN))(K+λI)1y

  这一结果中同样不需要显式地出现映射函数 ϕ \phi ϕ。因此,大多数时候我们直接用核函数进行计算即可,不需要再推算映射函数的具体形式。这样的方法称为核技巧(kernel trick),它的更多应用会在支持向量机中介绍。

  上面讲述的思路大致有两个。第一,机器学习先计算样本之间的相似度,再用统计的思想利用相似度来给出模型对样本的预测。第二,为了让相似度的计算更加符合任务要求,可以先用特征映射把样本中的有用信息提取出来,从而降低后续特征利用的难度。这两个思想是几乎所有机器学习模型都会用到的基本思想。需要注意,这两个思想并不一定是两个“步骤”,在许多情况下可以合二为一,把特征提取和相似度的计算与应用统一起来,包装在更复杂的模型里。例如在现代深度学习的神经网络中,它们很难再被严格区分,但是其基本思路仍然是一致的。

四、参数与超参数

  机器学习的目标是学习输入与输出之间的映射关系。在计算机中,我们通常用一些参数来表示模型,例如线性回归中的 θ \boldsymbol\theta θ。而在此之外,还有一类参数对于模型的训练效果也有很大影响,例如线性回归中的学习率 η \eta η,或是KNN中的 K K K,以及上面讲解的正则化系数 λ \lambda λ。这两类参数虽然都是模型的一部分,但是它们调整和优化的方式并不一样。回顾带有正则化的线性回归算法可以发现,无论我们用解析方式还是梯度下降算法,求解的都是 θ \boldsymbol\theta θ。而 λ \lambda λ则以“已知数”的身份出现,在开始训练前就由我们提前定好,在训练过程中不再改变。像 λ \lambda λ这样,不通过模型训练优化、需要人为指定的参数称为超参数(hyperparameter)。而调整超参数、寻找表现最好的模型的过程,就称为“调参”。

  图7简单描述了一个开发者为完成某项任务而建立模型的过程。假设我们现在的身份就是机器学习模型开发者,首先,我们应当根据任务的特点和数据分布设计合适的模型,比如线性回归模型。这时,我们还无法直接开始优化模型,因为其训练轮数、批量大小等等超参数还未确定。因此,我们需要先指定各个超参数的值,用 h 1 \boldsymbol{h}_1 h1表示。在超参数确定后,我们就可以初始化模型参数 θ 1 \boldsymbol\theta_1 θ1,并用数据集不断优化模型,得到最终的优化结果 θ 1 ∗ \boldsymbol\theta_1^* θ1,并在验证集或者测试集上观察模型的表现。接下来,我们可以调整超参数的值为 h 2 \boldsymbol{h}_2 h2,重新初始化模型参数 θ 2 \boldsymbol\theta_2 θ2,重复优化流程,直到得到我们满意的训练结果。这样下来,对每一组超参数 h i \boldsymbol{h}_i hi,我们都得到一组优化后的模型参数 θ i ∗ \boldsymbol\theta^*_i θi及其测试结果。我们可以根据实际需要,选出最符合我们要求的超参数 h k \boldsymbol{h}_k hk和相应的 θ k ∗ \boldsymbol\theta^*_k θk。进一步,如果有一个新的类似的任务需要解决,我们既可以直接把参数为 θ k ∗ \boldsymbol\theta^*_k θk的模型拿来直接使用或在新数据上微调参数,也可以用相同的超参数 h k \boldsymbol{h}_k hk在新数据上训练一个新的模型。如果重新训练的效果还是不好,我们就只好回到最开始设置超参数的步骤,尝试其他超参数的取值,寻找最适合新任务的超参数了。

在这里插入图片描述

图7 完整的机器学习模型建立流程

  参数与超参数的数量都会影响模型的复杂度。参数通常影响模型在训练时消耗的时间,而超参数由于需要我们提前设置,无法在训练中优化,其影响的是我们寻找最优模型的时间,过多的超参数会使得调参的过程非常困难。因此,优秀的机器学习模型通常需要减少其中超参数的数量,或者让部分超参数对模型的影响较小,不需要太多调整就可以达到较好的效果。

  针对一个给定机器学习任务,选择模型和调整超参数是一个机器学习算法工程师的“家常便饭”,这能决定最终训练出来的机器学习模型的性能。好的机器学习算法工程师能根据自己的经验,快速确定使用哪类模型和一个调试超参数的范围,然后通过少量实验就可以快速确定最终选择的模型和超参数,开始模型的正式训练。

  超参数也并非完全不能自动优化。在机器学习中,自动机器学习(AutoML)领域的研究内容就是自动优化机器学习模型的结构和超参数。其具体内容较为复杂,可查阅相关资料进行学习。

  Gridsearchcv()函数是用于网格搜索的函数,它通过遍历给定的参数组合来寻找最佳的模型超参数。该函数可以帮助我们系统地搜索不同的超参数组合,并使用交叉验证来评估每个超参数组合的性能,从而找到最优的模型配置。以下是GridSearchCV()函数的一些常用参数:

  • estimator:指定要优化的机器学习模型或者管道对象。
  • param_grid:一个字典或列表,包含要搜索的超参数及其取值范围。可以使用字典表示多个参数的组合,也可以使用列表表示单个参数的不同取值。
  • scoring:指定模型性能评估的指标,可以是字符串(如 ‘accuracy’)或者可调用函数。
  • cv:用于交叉验证的折数,默认为 5,可以是一个整数或者交叉验证生成器对象。
  • refit:布尔值,表示是否在搜索结束后重新训练最佳模型,并将其保存在 estimator 中。

以下是一个示例,展示了如何使用GridSearchCV()函数进行超参数调优:

# 加载数据集
from sklearn.datasets import load_breast_cancer
cancer = load_breast_cancer()

# 对数据集进行预处理,实现数据标准化
from sklearn.preprocessing import StandardScaler
X = StandardScaler().fit_transform(cancer.data)
y = cancer.target

# 将数据集划分为训练集和测试集(要求测试集占25%,随机状态random state设置为33)
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=33) 

# 创建模型估计器 estimator
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()

# 用训练集训练模型估计器 estimator
knn.fit(X_train, y_train)

# 用模型估计器对测试集数据做预测
y_pred = knn.predict(X_test)

# 对模型估计器的学习效果进行评价
# 最简单的评估方法:就是调用估计器的 score(),该方法的两个参数要求是测试集的特征矩阵和标签向量
print("测试集的分类准确率为:", knn.score(X_test, y_test))

from sklearn import metrics
# 对于多分类问题,还可以使用 metrics 子包中的 classification_report
print(metrics.classification_report(y_test, y_pred, target_names=cancer.target_names))

# 网格搜索超参数的 kNN 算法分类
from sklearn.model_selection import GridSearchCV
params_knn = {'algorithm': ['auto', 'ball_tree', 'kd_tree', 'brute'],
              'n_neighbors': range(3, 10, 1),
              'weights': ['uniform', 'distance']}
grid_search_knn = GridSearchCV(knn, params_knn, cv=5)  # 直接使用整数作为 cv 参数
grid_search_knn.fit(X_train, y_train)
grid_search_y_pred = grid_search_knn.predict(X_test)

print("Accuracy:", grid_search_knn.score(X_test, y_test))
print("Best params:", grid_search_knn.best_params_)

在这里插入图片描述

  为了评估不同超参数组合配置下的模型性能,可以采用网格搜索的方法,并且常常与训练数据集的交叉验证进行搭配。网格搜索的目的是为了优化超参数配置,交叉验证的目的是为了更准确的评估特定超参数配置下的模型性能的可信度,两者作用不同。

五、数据集划分与交叉验证

  除了正则化约束之外,我们还可以从数据集的角度来防止过拟合产生。在前面几篇文章中,我们已经采用了最基础的手段,将数据集分为训练集和验证集两部分。通过上面对欠拟合与过拟合现象的讲述,相信读者已经明白,在训练模型时,只观察训练集上损失函数的变化情况是远远不够的。但是在现实场景中,测试数据又是未知的,无法在训练时获得。因此,我们通常采用人为构造“测试集”的方法,将数据集随机划分为训练集和验证集(validation set)两部分,用验证集来代替测试集的作用。一个完整的模型训练流程如图8所示。当我们用不同的超参数(如KNN中的 K K K、正则化约束中的约束强度 λ \lambda λ)在训练集上训练出不同的模型后,可以观察这些模型在验证集上的效果,选出表现最好的模型。由于模型在训练时完全没有用到验证集中数据的任何信息,因此对于模型来说,如果真实的测试集和验证集内的数据分布相同,验证集与测试集就是等效的。我们可以期望,按照验证集上的表现所选择的模型,在测试集上也有接近的效果。

在这里插入图片描述

图8 数据集的划分与训练、验证的流程

  这一思路中还隐含着两个关键假设。第一,模型在训练时没有用到验证集的信息。由于我们常常会对数据集进行预处理,如果在预处理的过程中,用到了训练集和验证集一同计算的统计信息,就会造成验证集的信息泄露。例如,假设我们要对数据集中的数据归一化,正确的做法是先划分训练集和验证集,仅用训练集计算均值和方差等信息,再用这些信息处理验证集的数据。虽然我们目前已介绍过的任务和所用模型对信息泄露并不敏感,但读者仍然应当对此保持警惕,养成良好习惯。第二,训练集与真实的测试集中的数据分布相同。如果两者分布不相同,那么训练集与测试集里的样本很可能满足不同的关系,从而在训练集上训练得到的模型并不适用于测试集,通过验证集来挑选模型也就无从谈起了。现实中,训练集与测试集数据不同分布的任务确实有很多。例如,训练集中包含上海的天气数据,而测试场景在北京。由于地理位置差异,上海的天气数据与北京的数据很可能分布不同。但是,这两者肯定都服从气象学的普遍规律,仍然存在对两方都适用的机器学习模型。设计适用于训练集和测试集数据分布不同的任务的模型与算法属于机器学习中迁移学习(transfer learning)的范畴。在本书涉及的较为基础的范围内,我们都假设训练集与测试集的数据分布是相同的。

  在实践中,为了进一步消除数据分布带来的影响,我们在划分训练集和验证集时,通常采用随机划分的方式。如果数据集中的数据原本是有序的,随机划分就可以防止训练集中的样本只来自于样本空间中的一小部分。如果再考虑到随机划分也可能因为运气不好,恰好得到有偏差的分布,我们还可以采用交叉验证(cross validation)的方法来再加一重保险。如图9所示,首先,将数据集随机分成 k k k份,记其编号为 1 , … , k 1, \ldots, k 1,,k。接下来进行 k k k次独立的训练,第 i i i次训练时,将第 i i i份作为验证集,其他 k − 1 k-1 k1 份合起来作为训练集。最后,将 k k k次训练中得到的验证集上误差的平均值,作为模型最终的误差。下图展示了这一过程的思路,其中 L v a l ( i ) \mathcal{L}_{\mathrm{val}}^{(i)} Lval(i)表示第 i i i次训练时在验证集上计算的损失, L v a l \mathcal{L}_{\mathrm{val}} Lval表示模型的最终损失。交叉验证虽然增加了训练模型需要的时间,但一方面降低了数据集划分时数据分布带来的误差,另一方面也将所有的数据都利用了起来,每个样本都会在某次训练时出现在训练集中。而在 k k k值的选择上, k k k越小时,我们受到随机性的影响就越大,最终的 L v a l \mathcal{L}_{\mathrm{val}} Lval作为对真实期望值的估计,其偏差也就越大。但是,由于验证集中的样本更多,不同训练结果之间的方差也就比较小。反过来, k k k值越大, L v a l \mathcal{L}_{\mathrm{val}} Lval的估计就越准确,但方差也会增大。此外,我们还要考虑 k k k对总的训练时间的影响。经验上,我们一般取 k ∈ [ 5 , 10 ] k \in [5, 10] k[5,10]

在这里插入图片描述

图9 交叉验证示意

  KFold()函数是用于将数据集进行K折交叉验证的函数,它可以帮助我们评估模型的性能并减小因数据集划分不同而引起的偏差。在使用KFold()函数时,可以指定将数据集划分为多少个折(即K的取值),然后对每一折进行训练和验证。以下是KFold()函数的一些常用参数:

  • n_splits:指定将数据集划分为多少个折(即K的取值)。
  • shuffle:布尔值,表示是否在划分之前对数据进行洗牌,以确保数据的随机性。
  • random_state:整数或者 random_state 实例,用于控制数据的洗牌过程。
  • stratified:是否进行分层抽样,默认值为False。如果设置为True,则会进行分层抽样,即保证每个子集中的样本类别比例与原始数据集中的样本类别比例相同。stratified通常用于处理分类问题。

KFold()函数通常配合交叉验证函数(如 cross_val_score())一起使用,以评估模型的性能。以下是一个示例,展示了如何使用KFold()函数。将【机器学习基础】线性回归 中的训练集-验证集划分改为交叉验证,选出最好的模型在测试集上测试。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score, KFold
 
# 从源文件加载数据,并输出查看数据的各项特征
lines = np.loadtxt('USA_Housing.csv', delimiter=',', dtype='str')
header = lines[0]
lines = lines[1:].astype(float)
print('数据特征:', ', '.join(header[:-1]))
print('数据标签:', header[-1])
print('数据总条数:', len(lines))
 
# 划分输入和标签
X, y = lines[:, :-1], lines[:, -1]
 
# 设置交叉验证
num_folds = 5
kf = KFold(n_splits=num_folds, shuffle=True, random_state=0)
 
# 初始化线性模型
linreg = LinearRegression()
 
# 交叉验证选择最佳模型
best_model = None
best_rmse = float('inf')
for train_index, val_index in kf.split(X):
    X_train, X_val = X[train_index], X[val_index]
    y_train, y_val = y[train_index], y[val_index]
 
    # 数据归一化
    scaler = StandardScaler()
    X_train_scaled = scaler.fit_transform(X_train)
    X_val_scaled = scaler.transform(X_val)
 
    linreg.fit(X_train_scaled, y_train)
    y_pred = linreg.predict(X_val_scaled)
    rmse = np.sqrt(((y_pred - y_val) ** 2).mean())
    # if rmse < best_rmse:
    #     best_rmse = rmse
    #     best_model = linreg
    print(rmse)

在这里插入图片描述

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Francek Chen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值