线性模型的正则化

正如我们在第一和第二章看到的那样,降低模型的过拟合的好方法是正则化这个模型(即限制它):模型有越少的自由度,就越难以拟合数据。例如,正则化一个多项式模型,一个简单的方法就是减少多项式的阶数。

对于一个线性模型,正则化的典型实现就是约束模型中参数的权重。 接下来我们将介绍三种不同约束权重的方法:Ridge 回归,Lasso 回归和 Elastic Net。

岭(Ridge)回归

岭回归(也称为 Tikhonov 正则化)是线性回归的正则化版:在损失函数上直接加上一个正则项 \alpha\sum_{i=1}^n\theta_i^2。这使得学习算法不仅能够拟合数据,而且能够使模型的参数权重尽量的小。注意到这个正则项只有在训练过程中才会被加到损失函数。当得到完成训练的模型后,我们应该使用没有正则化的测量方法去评价模型的表现。

提示

一般情况下,训练过程使用的损失函数和测试过程使用的评价函数是不一样的。除了正则化,还有一个不同:训练时的损失函数应该在优化过程中易于求导,而在测试过程中,评价函数更应该接近最后的客观表现。一个好的例子:在分类训练中我们使用对数损失(马上我们会讨论它)作为损失函数,但是我们却使用精确率/召回率来作为它的评价函数。

超参数 \alpha 决定了你想正则化这个模型的强度。如果 \alpha=0 那此时的岭回归便变为了线性回归。如果 \alpha 非常的大,所有的权重最后都接近于零,最后结果将是一条穿过数据平均值的水平直线。公式 4-8 是岭回归的损失函数:

公式 4-8:岭回归损失函数

J(\theta)=MSE(\theta)+\alpha\frac{1}{2}\sum\limits_{i=1}^n\theta_i^2

值得注意的是偏差 \theta_0 是没有被正则化的(累加运算的开始是 i=1 而不是 i=0)。如我定义 \mathbf{w} 作为特征的权重向量(\theta_1\theta_n),那么正则项可以简写成 \frac{1}{2}{({\parallel \mathbf{w}\parallel_2})}^2,其中 \parallel \cdot \parallel_2 表示权重向量的 \ell_2 范数。对于梯度下降来说仅仅在均方差梯度向量(公式 4-6)加上一项 \alpha\mathbf{w}

提示

在使用岭回归前,对数据进行放缩(可以使用StandardScaler)是非常重要的,算法对于输入特征的数值尺度(scale)非常敏感。大多数的正则化模型都是这样的。

图 4-17 展示了在相同线性数据上使用不同 \alpha 值的岭回归模型最后的表现。左图中,使用简单的岭回归模型,最后得到了线性的预测。右图中的数据首先使用 10 阶的PolynomialFearures进行扩展,然后使用StandardScaler进行缩放,最后将岭模型应用在处理过后的特征上。这就是带有岭正则项的多项式回归。注意当\alpha增大的时候,导致预测曲线变得扁平(即少了极端值,多了一般值),这样减少了模型的方差,却增加了模型的偏差。

对线性回归来说,对于岭回归,我们可以使用封闭方程去计算,也可以使用梯度下降去处理。它们的缺点和优点是一样的。公式 4-9 表示封闭方程的解(矩阵 \mathbf{A} 是一个除了左上角有一个 0n \times n 的单位矩,这个 0 代表偏差项。译者注:偏差 \theta_0 不被正则化的)。

线性模型的正则化 - 图23

图 4-17:岭回归

公式 4-9:岭回归的封闭方程的解

\hat{\theta} = ({\mathbf{X}}^T\cdot\mathbf{X}+\alpha\mathbf{A})^{-1}\cdot{\mathbf{X}}^T\cdot\mathbf{y}

下面是如何使用 Scikit-Learn 来进行封闭方程的求解(使用 Cholesky 法进行矩阵分解对公式 4-9 进行变形):

  1. >>> from sklearn.linear_model import Ridge
  2. >>> ridge_reg = Ridge(alpha=1, solver="cholesky")
  3. >>> ridge_reg.fit(X, y)
  4. >>> ridge_reg.predict([[1.5]])
  5. array([[ 1.55071465]]

使用随机梯度法进行求解:

  1. >>> sgd_reg = SGDRegressor(penalty="l2")
  2. >>> sgd_reg.fit(X, y.ravel())
  3. >>> sgd_reg.predict([[1.5]])
  4. array([[ 1.13500145]])

penalty参数指的是正则项的惩罚类型。指定“l2”表明你要在损失函数上添加一项:权重向量 \ell_2 范数平方的一半,这就是简单的岭回归。

Lasso 回归

Lasso 回归(也称 Least Absolute Shrinkage,或者 Selection Operator Regression)是另一种正则化版的线性回归:就像岭回归那样,它也在损失函数上添加了一个正则化项,但是它使用权重向量的 \ell_1 范数而不是权重向量 \ell_2 范数平方的一半。(如公式 4-10)

公式 4-10:Lasso 回归的损失函数

J(\theta)=MSE(\theta)+\alpha\sum\limits_{i=1}^n\left|\theta_i \right|

图 4-18 展示了和图 4-17 相同的事情,仅仅是用 Lasso 模型代替了 Ridge 模型,同时调小了 \alpha​ 的值。

线性模型的正则化 - 图30

图 4-18:Lasso回归

Lasso 回归的一个重要特征是它倾向于完全消除最不重要的特征的权重(即将它们设置为零)。例如,右图中的虚线所示(\alpha=10^{-7}),曲线看起来像一条二次曲线,而且几乎是线性的,这是因为所有的高阶多项特征都被设置为零。换句话说,Lasso回归自动的进行特征选择同时输出一个稀疏模型(即,具有很少的非零权重)。

你可以从图 4-19 知道为什么会出现这种情况:在左上角图中,后背景的等高线(椭圆)表示了没有正则化的均方差损失函数(\alpha=0),白色的小圆圈表示在当前损失函数上批量梯度下降的路径。前背景的等高线(菱形)表示\ell_1惩罚,黄色的三角形表示了仅在这个惩罚下批量梯度下降的路径(\alpha\rightarrow\infty)。注意路径第一次是如何到达 \theta_1=0,然后向下滚动直到它到达 \theta_2=0。在右上角图中,等高线表示的是相同损失函数再加上一个 \alpha=0.5\ell_1 惩罚。这幅图中,它的全局最小值在 \theta_2=0 这根轴上。批量梯度下降首先到达 \theta_2=0,然后向下滚动直到达到全局最小值。 两个底部图显示了相同的情况,只是使用了 \ell_2 惩罚。 规则化的最小值比非规范化的最小值更接近于 \theta=0,但权重不能完全消除。

线性模型的正则化 - 图43

图 4-19:Ridge 回归和 Lasso 回归对比

提示

在 Lasso 损失函数中,批量梯度下降的路径趋向与在低谷有一个反弹。这是因为在 \theta_2=0 时斜率会有一个突变。为了最后真正收敛到全局最小值,你需要逐渐的降低学习率。

Lasso 损失函数在 \theta_i=0(i=1,2,\cdots,n) 处无法进行微分运算,但是梯度下降如果你使用子梯度向量 \mathbf{g} 后它可以在任何 \theta_i=0 的情况下进行计算。公式 4-11 是在 Lasso 损失函数上进行梯度下降的子梯度向量公式。

公式 4-11:Lasso 回归子梯度向量

g(\theta,J)=\nabla_{\theta}MSE(\theta)+ \alpha{\left(\begin{matrix} sign(\theta_1)\\ sign(\theta_2)\\ \vdots \\ sign(\theta_n)\\ \end{matrix}\right)}\ where\ sign(\theta_i)= \begin{cases} -1, &\theta_i<0 \\ 0, &\theta_i=0 \\ +1,&\theta_i>0 \\ \end{cases}

下面是一个使用 Scikit-Learn 的Lasso类的小例子。你也可以使用SGDRegressor(penalty="l1")来代替它。

  1. >>> from sklearn.linear_model import Lasso
  2. >>> lasso_reg = Lasso(alpha=0.1)
  3. >>> lasso_reg.fit(X, y)
  4. >>> lasso_reg.predict([[1.5]])
  5. array([ 1.53788174]

弹性网络(ElasticNet)

弹性网络介于 Ridge 回归和 Lasso 回归之间。它的正则项是 Ridge 回归和 Lasso 回归正则项的简单混合,同时你可以控制它们的混合率 r,当 r=0 时,弹性网络就是 Ridge 回归,当 r=1 时,其就是 Lasso 回归。具体表示如公式 4-12。

公式 4-12:弹性网络损失函数

J(\theta)=MSE(\theta)+r\alpha\sum\limits_{i=1}^n\left|\theta_i \right|+\frac{1-r}{2}\alpha\sum\limits_{i=1}^n\theta_i^2

那么我们该如何选择线性回归,岭回归,Lasso 回归,弹性网络呢?一般来说有一点正则项的表现更好,因此通常你应该避免使用简单的线性回归。岭回归是一个很好的首选项,但是如果你的特征仅有少数是真正有用的,你应该选择 Lasso 和弹性网络。就像我们讨论的那样,它两能够将无用特征的权重降为零。一般来说,弹性网络的表现要比 Lasso 好,因为当特征数量比样本的数量大的时候,或者特征之间有很强的相关性时,Lasso 可能会表现的不规律。下面是一个使用 Scikit-Learn ElasticNetl1_ratio指的就是混合率 r)的简单样本:

  1. >>> from sklearn.linear_model import ElasticNet
  2. >>> elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5)
  3. >>> elastic_net.fit(X, y)
  4. >>> elastic_net.predict([[1.5]])
  5. array([ 1.54333232])

早期停止法(Early Stopping)

对于迭代学习算法,有一种非常特殊的正则化方法,就像梯度下降在验证错误达到最小值时立即停止训练那样。我们称为早期停止法。图 4-20 表示使用批量梯度下降来训练一个非常复杂的模型(一个高阶多项式回归模型)。随着训练的进行,算法一直学习,它在训练集上的预测误差(RMSE)自然而然的下降。然而一段时间后,验证误差停止下降,并开始上升。这意味着模型在训练集上开始出现过拟合。一旦验证错误达到最小值,便提早停止训练。这种简单有效的正则化方法被 Geoffrey Hinton 称为“完美的免费午餐”

线性模型的正则化 - 图54

图 4-20:早期停止法

提示

随机梯度和小批量梯度下降不是平滑曲线,你可能很难知道它是否达到最小值。 一种解决方案是,只有在验证误差高于最小值一段时间后(你确信该模型不会变得更好了),才停止,之后将模型参数回滚到验证误差最小值。

下面是一个早期停止法的基础应用:

  1. from sklearn.base import clone
  2. sgd_reg = SGDRegressor(n_iter=1, warm_start=True, penalty=None,learning_rate="constant", eta0=0.0005)
  3. minimum_val_error = float("inf")
  4. best_epoch = None
  5. best_model = None
  6. for epoch in range(1000):
  7. sgd_reg.fit(X_train_poly_scaled, y_train)
  8. y_val_predict = sgd_reg.predict(X_val_poly_scaled)
  9. val_error = mean_squared_error(y_val_predict, y_val)
  10. if val_error < minimum_val_error:
  11. minimum_val_error = val_error
  12. best_epoch = epoch
  13. best_model = clone(sgd_reg)

注意:当warm_start=True时,调用fit()方法后,训练会从停下来的地方继续,而不是从头重新开始。