训练softmax分类器实例_第四章.模型训练

60af141173d515842cf26735e43ea520.png

迄今为止,我们只是把机器学习模型及其大多数训练算法视为黑盒。但是如果你做了前面几章的一些练习,你可能会惊讶于你可以在不知道任何关于背后原理的情况下完成很多工作:优化一个回归系统,改进一个数字图像分类器,甚至从头开始建立一个垃圾邮件分类器,所有这些都不知道它们的实际工作原理。事实上,在很多情况下,你其实并不需要知道这些实现细节。

然而,深刻理解事物的工作原理,可以帮助你快速找到合适的模型、正确的训练算法,以及一组适合目标任务的超参数。除此之外,也会帮助你更有效地调试问题和进行错误分析。最后,本章所讨论的大部分主题将对理解、构建和训练神经网络(将在本书第二部分中讨论)至关重要。

在本章中,我们将从线性回归模型开始,因为它是最简单的模型之一。我们将讨论两种非常不同的训练方法。

  • 使用 "闭式 "方程,直接计算出最适合模型与训练集的模型参数(即在训练集上最小化成本函数的模型参数)。
  • 使用一种梯度下降(GD)的迭代优化方法,通过逐步调整模型参数,使训练集上的成本函数最小化,并最终收敛到与第一种方法相同的参数。我们来看一下梯度下降的几个变体,在第二部分学习神经网络时将会反复使用这些变体:Batch GD、Mini-batch GD和Stochastic GD。

然后,我们将讨论一下多项式回归,这是一个比较复杂的模型,可以拟合非线性的数据集。由于这个模型的参数比线性回归更多,所以容易对训练数据过拟合,我们将讨论如何使用学习曲线来检测是否存在这种情况,以及几种可以降低过拟合风险的正则化方法。

最后,我们再着重讨论两个常用于分类任务的模型:Logistic回归和Softmax回归。

线性回归

在第一章中,我们研究了一个简单的生活满意度回归模型:

life_satisfaction

GDP_per_capita.

该模型为单个输入特征GDP_per_capita的线性函数,其中

为模型的参数。

更一般地,线性模型是通过简单地计算输入特征的加权平均,并加上一个偏置项(也称为截距项)来进行预测,如公式4-1所示:

其中:
  • 是模型的预测值
  • 是模型输入的特征数量
  • 是第
    个特征的取值
  • 是第
    个模型参数

上述公式用矢量化的形式可以写得更简洁,如公式4-2所示:

其中:
  • 是模型的
    参数向量,包含了偏置项
    和权重项
  • 是实例的
    特征向量,包含了特征
    ,其中
    始终为常数

但我们应该如何训练呢?回顾一下,训练模型意味着找出在训练集上最适合模型的参数。为此,我们首先需要一个目标函数,正如我们在第二章中所看到,回归模型最常用的度量标准是均方根误差(RMSE)。为了训练线性回归模型,我们需要寻找最小化RMSE的

值,而在实践中,更简单的方法是最小化均方误差(MSE),因为它们具有一致的结果。

在线性假设

下,模型在训练集
的计算如公式4-3所示:

正规方程

为了找到最小化成本函数的

值,可以通过闭式解,这就是所谓的正规方程(方程4-4)。

其中:
  • 为最小化代价函数的模型参数的取值
  • 为标签取值的向量,包含

现在我们来生成一些类似线性关系的数据

import numpy as np

X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

dc43fcf817ec87ca84ba1bbc4a4db5ce.png

我们可以使用正规方程进行求解,只需使用NumPy的线性代数模块(np.linalg)中的inv()函数来计算矩阵的逆,并使用dot()方法进行矩阵乘法。

X_b = np.c_[np.ones((100, 1)), X]  # add x0 = 1 to each instance
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)

我们用来生成数据的函数是

。让我们看看求解得出的方程是什么。
theta_best
# array([[4.21509616],
#        [2.77011339]])

我们本来希望

,但是已经足够接近了,因为噪声的影响,无法恢复原始函数的精确参数。

我们来做些预测:

X_new = np.array([[0], [2]])
X_new_b = np.c_[np.ones((2, 1)), X_new]  # add x0 = 1 to each instance
y_predict = X_new_b.dot(theta_best)
y_predict
# array([[4.21509616],
#        [9.75532293]])

plt.plot(X_new, y_predict, "r-")
plt.plot(X, y, "b.")
plt.axis([0, 2, 0, 15])
plt.show()

ebf75837d701a0cc002451bb958617e2.png

使用Scikit-Learn进行线性回归非常简单。

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, y)
lin_reg.intercept_, lin_reg.coef_
# (array([4.21509616]), array([[2.77011339]]))
lin_reg.predict(X_new)
# array([[4.21509616],
#        [9.75532293]])

LinearRegression类是基于scipy.linalg.lstsq()函数( "最小二乘法"),你可以直接调用它。

theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
theta_best_svd
# array([[4.21509616],
#        [2.77011339]])

这个函数计算

,其中
的伪逆,你可以使用
np.linalg.pinv()直接计算。
np.linalg.pinv(X_b).dot(y)
# array([[4.21509616],
#        [2.77011339]])

伪逆本身的计算是通过奇异值分解(SVD)的方法进行的,该方法将训练集矩阵

分解成三个矩阵相乘
(见
numpy.linalg.svd())。伪逆的计算公式为
,为了计算矩阵
,首先将矩阵
中所有小于预先设定阈值的值都设置为
,并将非零值替换为其倒数,最后将矩阵进行转置。这种方法比计算正规方程更有效率,事实上,如果矩阵
不可逆(即奇异),如
或一些特征是多余的,正规方程可能就不起作用了,但伪逆总是存在的。

现在我们来看看一种非常不同的训练线性回归模型的方法,这种方法更适合于有大量特征或训练实例太多,无法放入内存的情况。

梯度下降

梯度下降是一种通用的优化算法,能够为各种问题找到最优解,梯度下降的一般思想是反复调整参数来最小化成本函数。

假设你由于浓雾而在山中迷失,你只能感觉到脚下地面的坡度,要想快速到达谷底,一个好的策略就是沿着坡度最陡的方向下山。这正是 "梯度下降 "的思想:通过计算误差函数关于参数向量

的局部梯度,并沿着梯度下降的方向前进,一旦梯度为零,你就达到了最低点!

具体来说,你先随机初始化参数

的取值,然后逐步调整,每次一小步,每一步都试图减少成本函数(如MSE),直到算法
收敛到最小值(见图4-3)。

e5bb85e3f565542364b9abf3772fc1de.png

梯度下降法的一个重要参数是步长的大小,由超参数学习率所决定。如果学习率太小,那么算法就必须经过多次迭代才能收敛,也就意味着需要很长的时间进行训练(见图4-4)。

2dd78466fc28afb09b1423be10a78194.png

另一方面,如果学习率太高,你可能会跳过山谷,最后到了另一边,甚至可能比之前更高了,这可能会导致算法发散(见图4-5)。

2d847afebaf91bec954f4912dea2e9eb.png

最后需要注意的是,并不是所有的代价函数都是规则的碗状结构。代价函数的曲面可能会有洞、山脊、鞍部和各种不规则的地形,这使得收敛到最小值变得困难。图4-6显示了梯度下降的两个主要挑战。如果随机初始化从左边开始,那么它将收敛到局部最小值,这显然不如全局最小值。而如果从右边开始,那么它将需要很长的时间来跨越鞍部,并且如果你过早地停止训练,将永远无法到达全局最小值。

9c7e9e6958679bbe711d4ed45b21709c.png

幸运的是,线性回归模型的MSE代价函数恰好是一个凸函数,这意味着如果你在曲线上选取任意两点,则连接它们的线段永远不会与曲线交叉,也就是说没有局部最小值,只有一个全局最小值。此外,它也是一个连续可导的函数,这两个特点可以保证梯度下降法会无限接近全局最小值。

事实上,即使代价函数具有碗的形状,如果特征具有非常不同的尺度大小,它可以是一个拉长的碗。图4-7显示了在特征1和2具有相同尺度的情况下,以及在特征1的取值比特征2小得多的情况下,梯度下降法在训练集上的迭代过程。

457a1a93f08f786dd0d5e7f095e21c92.png

正如你所看到的,在左图中,梯度下降法直接向最小值运动,从而快速到达最小值,而在右图中,先是向着与全局最小值方向几乎正交的方向运动,最后沿着一个几乎平坦的山谷移动,虽然最终依然会到达最小值,但这需要很长的时间。

当使用梯度下降法时,你应该确保所有的特征都具有相似的尺度(例如,使用 Scikit-LearnStandardScaler类),否则会花费很长的时间来收敛。

这张图也说明了训练一个模型意味着寻找一个模型参数的组合来最小化训练集上的代价函数。而这个过程是在模型的参数空间中进行的:一个模型的参数越多,意味着这个空间的维度就越多,搜索的难度也就越大(大海捞针)。幸运的是,由于在线性回归的情况下,成本函数是凸的,搜索就变得容易多了。

批量梯度下降

为了实现梯度下降,你需要计算代价函数关于每个模型参数

的梯度。换句话说,你需要计算如果改变
一点点,代价函数会有多大变化。这就是所谓的
偏导数。代价函数关于参数
的偏导数
的计算如公式4-5所示:

与其单独计算这些偏导数,不如使用公式4-6一次性计算完梯度向量,记为 n

abla_{theta} MSE(theta) ,其包含了代价函数的所有偏导数。

请注意,上述公式在梯度下降的每一步中都涉及整个训练集
的计算!这就是为什么该算法被称为 "批量梯度下降"(Batch Gradient Descent):它在每一步都使用所有训练数据(实际上,完全梯度下降可能是一个更好的名字)。因此,在非常大的训练集上,它的速度会变得非常慢,但是,梯度下降法随着特征数量的增加具有良好的可扩展性:当面对几十万个特征的时侯,使用梯度下降法训练线性回归模型要比使用正规方程或SVD分解法快得多。

一旦有了梯度向量,其指向为上坡,我们所需要做的就是反方向下坡。这意味着从

中减去
。 这就是学习率
的作用所在:将梯度向量乘以
来确定下坡的步长(公式4-7)。

我们来看看该算法的快速实现:

eta = 0.1  # learning rate
n_iterations = 1000
m = 100

theta = np.random.randn(2,1)  # random initialization

for iteration in range(n_iterations):
    gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
    theta = theta - eta * gradients

theta
# array([[4.21509616],
#        [2.77011339]])

这正是正规方程所求的! 梯度下降法的效果非常好,但如果你使用了不同的学习率

呢?图4-8展示了使用三种不同学习率的梯度下降的前10步(虚线代表起始)。

5a61534c6c3ee9ea59fa5173a9d7d0a7.png
Figure 4-8. Gradient Descent with various learning rates

为了找到一个好的学习率,你可以使用网格搜索(见第2章)。但是,您可能希望限制迭代次数,以便网格搜索可以淘汰那些需要太长时间才能收敛的模型。

你可能想知道如何设置迭代次数。如果它太低,当算法停止时,你仍然会离最优解很远;但如果它太高,你会浪费时间,而模型参数却不再改变。一个简单的解决方案是设置一个非常大的迭代次数,但当梯度向量变得很小,也就是说,当它的范数变得小于一个很小的数值

(称为
容忍度)时,中断算法,因为此时梯度下降已经(几乎)达到最小值了。

随机梯度下降

批量梯度下降的主要问题是其每一步都使用整个训练集来计算梯度,所有当训练集很大时,迭代的速度就会非常慢。相反地,随机梯度下降法在每一步都会在训练集中随机挑选一个实例,并仅基于该实例来计算梯度,显然,每次只处理一个实例会使算法的速度快得多。

另一方面,由于其随机性的特点,该算法比Batch Gradient Descent的规律性要差得多:代价函数不是缓缓下降到达最小值,而是上下跳动,总体上下降。随着时间的推移,它最终会非常接近最小值,但是就算到了最小值,也会继续跳动,永远不会稳定下来(见图4-9)。所以当算法停止时,最终得到的参数值是好的,但并不是最优的。

cf645079e089fe4062a17dc49f6a71a8.png

当代价函数非常不规则时(如图4-6),这实际上可以帮助算法跳出局部最小值,所以随机梯度下降比批量梯度下降有更好的机会找到全局最小值。

因此,随机性对于跳出局部最优值是有利的,但也意味着算法永远无法在最小值处安顿下来。解决该难题的一个办法是学习率衰减。步数一开始很大(有助于加速并跳出局部最小值),然后逐渐减小,让算法在全局最小值处稳定下来。这个过程类似于模拟退火,这种算法的灵感来自于冶金学中的退火过程,即熔融的金属被缓慢冷却。决定每次迭代时学习率的函数称为学习调度。如果学习率降低得太快,可能会卡在局部最小值,甚至中途停止,但如果学习率降低得太慢,可能会在最小值附近跳动很长时间,如果此时过早地停止训练,最终得到的将是一个次优的解。

下面这段代码使用了一个简单的学习调度实现了随机梯度下降。

n_epochs = 50
m = len(X_b)
t0, t1 = 5, 50  # learning schedule hyperparameters

def learning_schedule(t):
    return t0 / (t + t1)

theta = np.random.randn(2,1)  # random initialization

for epoch in range(n_epochs):
    for i in range(m):
        random_index = np.random.randint(m)
        xi = X_b[random_index:random_index+1]
        yi = y[random_index:random_index+1]
        gradients = 2 * xi.T.dot(xi.dot(theta) - yi)
        eta = learning_schedule(epoch * m + i)
        theta = theta - eta * gradients

theta
# array([[4.21076011],
#         [2.74856079]])

根据传统惯例,我们以

次迭代为一轮,每一轮称为一个
轮次(epoch)。上述的批量梯度下降对整个训练集进行了
次迭代,而随机梯度下降只对整个训练集进行了50个轮次,就达到了一个相当不错的解。

图4-10展示了训练的前20步:

27cb9c410ed0817948de12eda5a2f510.png
Figure 4-10. The first 20 steps of Stochastic Gradient Descent

注意到,由于实例是随机选取的,所以在每个epoch中, 有些实例可能会被选取几次,而有些实例可能根本不会被选取。如果你想确保训练过程使用到每一个实例,一种方法是对训练集进行洗牌(确保将输入特征和标签一起洗牌),然后逐个实例进行迭代。不过,这种方法一般收敛比较慢。

基于Scikit-Learn的随机梯度下降进行线性回归,可以使用 SGDRegressor 类,该类默认为优化平方误差代价函数。

下面的代码运行最大epoch数为1,000,或者直到损失下降小于0.001为止(max_iter=1000, tol=1e-3),初始学习率为0.1(eta0=0.1),使用默认的学习调度(与前面的不同),不使用任何正则化(penalty=None)。

from sklearn.linear_model import SGDRegressor

sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1, random_state=42)
sgd_reg.fit(X, y.ravel())

sgd_reg.intercept_, sgd_reg.coef_
# (array([4.24365286]), array([2.8250878]))

小批量梯度下降

最后我们要讲的梯度下降算法叫做小批量梯度下降。如果你知道了批量梯度下降和随机梯度下降,应该就容易理解了:在每一步,小批量梯度下降并不是基于完整的训练集或仅仅基于单个实例来计算梯度,而是在小批量的随机实例集上计算梯度。与随机梯度下降相比,小批量梯度下降的主要优势在于,你可以从矩阵运算的硬件优化中获得性能提升,尤其是在使用GPU时。

此外,与随机梯度下降相比,小批量梯度下降在参数空间的进展并没有那么不稳定,尤其是在相当大的小批量下。因此,小批量梯度下降最终会比随机梯度下降更接近最小值,但同时它可能会更难脱离局部最小值。图4-11展示了三种梯度下降算法在训练过程中的路径,它们最终都在集中最小值附近,但批量梯度下降的路径实际上停在了最小值处,而随机梯度下降和小批量梯度下降都继续在最小值附近摆动。但是,别忘了,批量梯度下降每走一步都需要大量的时间,并且如果你使用一个合适的学习调度,随机梯度下降和小批量梯度下降也会达到最小值。

fd218bd20ee6474334d6f3948963b79b.png

让我们比较一下到目前为止我们讨论过的线性回归的算法(回忆一下,

是训练实例的数量,
是特征的数量)。

10b56378f63ca8ad33d5c1b31d374824.png

多项式回归

如果你的数据比直线更复杂怎么办?你可以使用线性模型来拟合非线性数据,一个简单的方法是将每个特征的幂级添加为新特征,然后在这个扩展的特征集上训练一个线性模型,这就是所谓的多项式回归

我们来看一个例子,首先,我们根据一个简单的二次方程(加上一些噪声,见图4-12),生成一些非线性数据。

m = 100
X = 6 * np.random.rand(m, 1) - 3
y = 0.5 * X**2 + X + 2 + np.random.randn(m, 1)

0213c479d7442ec75b1373f5a5b8ed3d.png

显然,直线永远无法正确拟合这些数据,因此,我们使用 Scikit- LearnPolynomialFeatures类来转换我们的训练数据,并将训练集中每个特征的平方作为新的特征添加进来(本例中只有一个特征)。

from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
X[0]
# array([-0.75275929])
X_poly[0]
# array([-0.75275929,  0.56664654])

现在你可以对这个扩展的训练数据拟合一个LinearRegression模型(图4-13)。

lin_reg = LinearRegression()
lin_reg.fit(X_poly, y)
lin_reg.intercept_, lin_reg.coef_
# (array([1.78134581]), array([[0.93366893, 0.56456263]]))

c4331b748610942ae276f49f22dc96fe.png

还不错,预测模型为:

,实际模型为:
.

需要注意的是,当数据具有多个特征时,多项式回归能够找到特征之间的交互关系(这是普通线性回归模型无法做到的)。这是因为PolynomialFeatures还可以将所有特征的组合到给定的次数。例如,如果有两个特征

PolynomialFeatures中的 degree=3,则除了添加特征
外,还会添加组合特征
PolynomialFeatures(degree=d)将包含
个特征的数组转换成包含
个特征的数组。

学习曲线

如果你尝试高次的多项式回归,你可能会比普通线性回归更好地拟合训练数据。图4-14将300次的多项式模型应用于前面的训练数据,并将结果与单纯的线性模型和四元模型(二次多项式)进行比较。注意到300次的多项式模型是摇摆不定的,并尽可能接近训练实例。

4450400fa55809077be59f3eda1bd51c.png

高次多项式回归模型对训练数据严重过拟合,而线性模型则欠拟合。在这种情况下,最具泛化能力的模型是二次模型,这是合理的的,因为数据是用二次模型生成的。但是在一般情况下,你不会知道数据是由什么函数生成的,所以你该如何决定你的模型应该有多复杂?如何判断你的模型对数据是过拟合还是欠拟合?

在第2章中,我们使用交叉验证来获得模型泛化性能的估计。如果一个模型在训练数据上表现良好,但根据交叉验证的指标,它的泛化性能很差,那么你的模型就是过拟合。如果在这两方面都表现不佳,那么就是欠拟合,这是判断一个模型太简单或太复杂的一种方法。

另一种方法是观察学习曲线:这些曲线是模型在训练集和验证集上的性能相对于训练集大小(或迭代次数)的函数。下面的代码定义了一个函数,给定一些训练数据,绘制模型的学习曲线。

from sklearn.metrics import mean_squared_error
from sklearn.model_selection import train_test_split

def plot_learning_curves(model, X, y):
    X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=10)
    train_errors, val_errors = [], []
    for m in range(1, len(X_train)):
        model.fit(X_train[:m], y_train[:m])
        y_train_predict = model.predict(X_train[:m])
        y_val_predict = model.predict(X_val)
        train_errors.append(mean_squared_error(y_train[:m], y_train_predict))
        val_errors.append(mean_squared_error(y_val, y_val_predict))

    plt.plot(np.sqrt(train_errors), "r-+", linewidth=2, label="train")
    plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="val")
    plt.legend(loc="upper right", fontsize=14)   # not shown in the book
    plt.xlabel("Training set size", fontsize=14) # not shown
    plt.ylabel("RMSE", fontsize=14)              # not shown

lin_reg = LinearRegression()
plot_learning_curves(lin_reg, X, y)
plt.axis([0, 80, 0, 3])                         # not shown in the book
plt.show()                                      # not shown

7f9e109c9138b571011b5c16fef899fb.png

这是个欠拟合的模型,值得解释一下。首先,我们来看看在训练数据上的表现:当训练集中只有一两个实例时,模型可以完美地拟合它们,这就是为什么曲线从零开始。但是随着新的实例加入到训练集中,模型就不可能完美地拟合训练数据了,这既是因为数据是有噪声的,也是因为数据根本不是线性的。所以训练数据上的误差会一直上升,直到达到一个平稳的状态,这时向训练集添加新的实例并不会使平均误差有多大的变化。现在我们来看看模型在验证数据上的表现,当模型在极少的训练实例上进行训练时,它无法进行正确的泛化,这就是为什么最初的验证误差相当大。然后,当模型被展示更多的训练实例时,它就会学习,因此验证误差会慢慢下降。然而,直线又不能很好地对数据进行建模,所以误差也最终会达到一个平稳状态,非常接近另一条曲线。

这些学习曲线是典型的模型欠拟合,两条曲线都达到了一平缓,很接近,而且相当高。

如果你的模型对训练数据欠拟合,添加更多的训练实例也无济于事,你需要使用更复杂的模型或添加更好的特征。

我们来看看10次多项式模型在相同数据上的学习曲线(图4-16)。

from sklearn.pipeline import Pipeline

polynomial_regression = Pipeline([
        ("poly_features", PolynomialFeatures(degree=10, include_bias=False)),
        ("lin_reg", LinearRegression()),
    ])

plot_learning_curves(polynomial_regression, X, y)
plt.axis([0, 80, 0, 3])           # not shown
plt.show()            

075ce1ff0ccc0cc1e8872e6e006e27de.png

这些学习曲线看起来有点像之前的曲线,但有两个非常重要的区别。

  • 训练数据的误差比线性回归模型低得多
  • 曲线之间存在差距。这意味着模型在训练数据上的表现明显好于验证数据,这是过拟合的标志,然而,如果你使用更大的训练集,两条曲线会继续变得更接近。
偏差/方差权衡
改进过拟合模型的一种方法是给它输入更多的训练数据,直到验证误差逼近训练误差。
统计学和机器学习的一个重要理论成果是,一个模型的泛化误差可以用三个不同的误差之和来表示。 偏差:这部分泛化误差是由于错误的假设造成的,比如假设数据是线性的,而实际上是二次元的。高偏差的模型很有可能是对训练数据欠拟合。 方差:这部分是由于模型对训练数据的微小变化过于敏感。一个具有高自由度的模型(如高次多项式模型)很可能具有较高的方差,从而对训练数据过拟合。
随机误差:这部分是由于数据本身的噪声。减少这部分误差的唯一方法是清理数据(如修复数据源,如破损的传感器,或检测并去除异常值)。
增加一个模型的复杂性通常会增加其方差,减少其偏差。相反,降低一个模型的复杂性会增加它的偏差,减少它的方差。这就是为什么它被称为权衡。

正则化线性回归

正如我们在第1章和第2章中所看到的,减少过拟合的一个方法是对模型进行正则化(即约束模型):模型的自由度越少,它就越难对数据过拟合。正则化多项式模型的一个简单方法是减少多项式的次数。

对于线性模型来说,正则化通常是通过约束模型的权重来实现的。我们现在来看一下岭回归、拉索回归和弹性网络三种不同的权重约束方式。

岭回归

岭回归(也叫Tikhonov正则化)是线性回归的正则化版本:在代价函数中加入

的正则化项,迫使学习算法不仅要拟合数据,而且要使模型权重尽可能小。需要注意的是,正则化项只在训练期间添加到代价函数中,当模型训练完成后,你要使用非正则化的性能度量来评估模型的性能。
训练时使用的代价函数与测试时使用的性能度量标准往往是不同的。除了正则化之外,另一个原因是,一个好的代价函数应该具有良好优化的导数,而用于测试的性能度量应该尽可能地接近最终目标。例如,分类模型通常使用对数损失等代价函数进行训练,但是使用精度/召回率进行评估。

超参数

控制了对模型的正则化程度,如果
,则岭回归就是线性回归,如果
非常大,则所有的权重最终都会非常接近于零,结果就是一条平坦的线穿过数据的平均值。公式4-8给出了岭回归的代价函数:

注意到偏置项

是不需要正则化的。
在进行岭回归之前,对数据进行缩放(例如,使用 StandardScaler)是很重要的,因为它对输入特征的尺度很敏感,实际上大多数正则化模型都是如此。

图4-17展示了使用不同的惩罚系数

对一些线性数据进行训练的岭回归模型。在左图中,使用简单的Ridge模型,进行线性拟合;在右图中,首先使用
PolynomialFeatures(degree=10)对数据进行扩展,然后使用 StandardScaler对数据进行缩放,最后对得到的特征应用岭回归模型。注意到,增加
会导致曲线更平坦,从而减少模型的方差,但相应地会增加其偏差。

d97af2be6d3dd79bbfa7574a01ecf5e1.png

与线性回归一样,我们也可以通过计算闭式解或梯度下降来训练岭回归模型。方程4-9为对应的闭式解,其中

单位矩阵,只是左上角取值为0,因为其对应偏置项。

下面使用闭式解来训练Scikit-Learn的岭回归:

from sklearn.linear_model import Ridge
ridge_reg = Ridge(alpha=1, solver="cholesky", random_state=42)
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])
# array([[1.55071465]])

也可以使用随机梯度下降法:

ridge_reg = Ridge(alpha=1, solver="sag", random_state=42)
ridge_reg.fit(X, y)
ridge_reg.predict([[1.5]])
# array([[1.5507201]])

# SGD
sgd_reg = SGDRegressor(penalty="l2", max_iter=1000, tol=1e-3, random_state=42)
sgd_reg.fit(X, y.ravel())
sgd_reg.predict([[1.5]])
# array([1.47012588])

其中超参数penalty设置要使用的正则化项的类型,指定 "l2 "表示希望SGD在代价函数中添加一个正则化项,取值为权重向量的

范数的一半。

拉索回归

拉索回归是线性回归的另一个正则化版本:就像岭回归一样,它在代价函数中增加了一个正则化项,但它使用的是权重向量的

范数(见公式4-10)。

与图4-17类似,将岭模型替换为拉索模型,并使用较小的

值,结果如图4-18所示:

697ae21262759af0d3e106c048415cce.png

拉索回归的一个重要特点是它倾向于淘汰不重要的特征的权重(即将其设置为零)。例如,图4-18中右图中的虚线(

)看起来是二次的,而且几乎是线性的,即所有高次的特征的权重都等于零。换句话说,拉索回归会自动进行特征选择,并输出一个
稀疏模型

你可以通过看图4-19来了解为什么会这样:轴代表模型的两个参数,背景轮廓代表不同的损失函数。在左上角的图中,等高线代表了

损失(
),当接近任何一个轴时,其取值都会线性下降。例如,如果你初始化模型参数为
,使用 "梯度下降 法"将使两个参数均等地递减(用黄色虚线表示),因此,
将首先达到
(因为它一开始就接近
),之后,梯度下降会顺着轴滚下去,直到达到
(会有一点跳动,因为
范数的梯度永远不会接近0:每个参数的梯度不是
就是
)。在右上角的图中,等高线代表了拉索回归的代价函数(即MSE代价函数加上
损失),白色的小圆圈显示了梯度下降法优化模型参数的路径,这些参数初始化在
附近。我们再一次注意到路径快速到达
,然后沿着轴滚动,并围绕全局最优值(红色方块)跳动。如果我们增大
,全局最优值将沿黄色虚线向左移动,而如果我们减小
,全局最优值将向右移动(本例中,无正则化MSE的最优参数为
)。

7b0b998d0b6c9cf390af7d9961181e99.png

底部的两张图显示的是同样的情况,只是惩罚项换成了

损失。在左下角的图中,可以看到
损失会随着距离原点的距离而减少,所以梯度下降走了一条直达原点的路径。在右下角的图中,等高线代表了岭回归的代价函数(即MSE代价函数加上
损失)。这与拉索回归有两个主要区别:首先,当参数接近全局最优时,梯度会变小,所以梯度下降自然会变慢,这有助于收敛(因为没有反弹);其次,当你增大
时,最佳参数(红色方块)会越来越接近原点,但它们永远不会被完全消除(即其中一个取值为
)。
在使用拉索回归时,为了避免梯度下降在最后绕着最优值跳动,你需要在训练中逐渐降低学习率(它仍然会绕着最优值跳动,但步子会越来越小,所以会收敛)。

拉索回归的代价函数在

时不可导,但是如果我们在
时使用
次梯度向量
代替的话,梯度下降仍然可以正常工作。公式4-11给出了一个次梯度的计算:

from sklearn.linear_model import Lasso
lasso_reg = Lasso(alpha=0.1)
lasso_reg.fit(X, y)
lasso_reg.predict([[1.5]])
# array([1.53788174])

弹性网络

弹性网络是介于岭回归和拉索回归之间,正则化项是它们正则化项的简单混合,并通过混合比

进行控制,当
时,弹性网络相当于岭回归,当
时,相当于拉索回归(见公式4-12)。

那么,什么时候应该使用普通的线性回归(即不进行任何正则化)、Ridge、Lasso和Elastic Net?事实上最好还是要有一点正则化,即尽量避免使用纯线性回归。Ridge是一个很好的初始模型,但如果你怀疑只有几个特征是有用的,你应该更偏向于Lasso或Elastic Net,因为它们倾向于将无用特征的权重降到零。一般来说,Elastic Net比Lasso更适用,因为当特征的数量大于训练实例的数量,或者有几个特征具有很强的相关性时,Lasso可能会表现得很不稳定。

from sklearn.linear_model import ElasticNet
elastic_net = ElasticNet(alpha=0.1, l1_ratio=0.5, random_state=42)
elastic_net.fit(X, y)
elastic_net.predict([[1.5]])
# array([1.54333232])

早停

对迭代学习算法(如梯度下降)进行正则化的另一个方法是:当验证误差达到最小值时,立即停止训练,这就是所谓的早停。图4-20展示了一个复杂的模型(在本例中,是一个高阶多项式回归模型)使用批量梯度下降法进行训练,随着时间的推移,模型在训练集和验证集的预测误差(RMSE)都在下降。然而过了一段时间,验证误差不再减少,开始回升,这说明模型已经开始对训练数据过拟合,你只要在验证误差达到最小值时就停止训练即可。就是这样一种简单高效的正则化技术,Geoffrey Hinton称其为 "美妙的免费午餐"。

c9526982be5d65b1e4d99f1271e33133.png
对于随机和小批量的梯度下降,曲线并不是那么平滑,可能很难知道你是否已经达到了最小值。一个解决方案是,只有在验证误差超过最小值一段时间后(即当你确信模型不会有更好的表现时)才停止,然后返回验证误差最小的模型参数。
from sklearn.base import clone

poly_scaler = Pipeline([
        ("poly_features", PolynomialFeatures(degree=90, include_bias=False)),
        ("std_scaler", StandardScaler())
    ])

X_train_poly_scaled = poly_scaler.fit_transform(X_train)
X_val_poly_scaled = poly_scaler.transform(X_val)

sgd_reg = SGDRegressor(max_iter=1, tol=-np.infty, warm_start=True,
                       penalty=None, learning_rate="constant", eta0=0.0005, random_state=42)

minimum_val_error = float("inf")
best_epoch = None
best_model = None
for epoch in range(1000):
    sgd_reg.fit(X_train_poly_scaled, y_train)  # continues where it left off
    y_val_predict = sgd_reg.predict(X_val_poly_scaled)
    val_error = mean_squared_error(y_val, y_val_predict)
    if val_error < minimum_val_error:
        minimum_val_error = val_error
        best_epoch = epoch
        best_model = clone(sgd_reg)

注意到热重启参数warm_start=True,当调用fit() 函数时,会接着上次训练的结果继续训练,而不是重新训练。

逻辑回归

正如我们在第一章中所讨论的那样,一些回归算法可以用于分类(反之亦然)。逻辑回归通常用于估计一个实例属于特定类别的概率(例如,这封邮件是垃圾邮件的概率是多少?)如果估计的概率大于50%,那么该模型就预测该实例属于该类(称为正类,标签为 "1"),否则就预测它不属于该类(即属于负类,标签为 "0")。

概率估计

那么,逻辑回归是如何工作的呢?如线性回归模型一样,逻辑回归模型计算的是输入特征的加权和(加上一个偏置项),但它并不像线性回归模型那样直接输出结果,而是输出这个结果的logistic(见公式4-13)。

是输出在
之间的
函数(即S形)。 它的定义如公式4-14和图4-21所示:

437dd6cd364fcd2b88f00746f79abb98.png

只要逻辑回归模型估计出一个实例

属于正类的概率
,就可以很容易地进行预测(见公式4-15):

代价函数及其训练

我们知道了逻辑回归模型如何估计概率并进行预测了,但是如何训练呢?训练的目的是寻找参数向量

,使得模型对正的实例(
)估计出较高的概率取值,而对负的实例(
)估计出较低的概率取值。公式4-16给出了单个训练实例
的代价函数:

这个代价函数是合理的,因为当

接近
时,
会急剧增大,所以对于一个正例,如果模型估计的概率接近
,代价会很大;类似地,对于一个负例,如果模型估计的概率接近
,代价也会很大。另一方面,当
接近于
时,
会接近于
,所以对于估计概率接近于
的负例或估计概率接近于
的正例,其代价将接近于
,这正是我们想要的。

整个训练集的代价函数是所有训练实例的代价的平均。它可以写成一个单一的表达式,即对数损失,如式4-17所示:

坏消息是,并没有已知的闭式方程来求解最小化这个代价函数的

值。好消息是,这个代价函数是凸的,所以梯度下降法(或其他任何优化算法)能够保证找到全局最小值。代价函数关于模型参数
的偏导数由式4-18给出:

这个式子看起来很像式4-5:对于每个实例,计算其预测误差,并将其乘以

个特征的取值,最后计算所有训练实例的平均值。只要有了包含所有偏导数的梯度向量,就可以在批量梯度下降算法中使用它。

决策边界

我们用鸢尾花数据集来了解Logistic回归,该数据集包含了150朵三种不同品种(Iris setosa, Iris versicolor, Iris virginica)的鸢尾花的萼片和花瓣的长度与宽度(见图4-22)。

17ce87b6393c2695c270e0b6a22dccaf.png

我们先尝试建立一个仅根据花瓣宽度来检测花朵是否为Iris virginica的二分类器,首先我们来加载数据:

from sklearn import datasets
iris = datasets.load_iris()
list(iris.keys())
# ['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename']
X = iris["data"][:, 3:]  # petal width
y = (iris["target"] == 2).astype(np.int)  # 1 if Iris virginica, else 0

然后我们训练一个逻辑回归模型:

from sklearn.linear_model import LogisticRegression
log_reg = LogisticRegression(solver="lbfgs", random_state=42)
log_reg.fit(X, y)

我们来看看模型对于花瓣宽度从0到3厘米不等的花朵的预测概率分布(图4-23)。

X_new = np.linspace(0, 3, 1000).reshape(-1, 1)
y_proba = log_reg.predict_proba(X_new)
decision_boundary = X_new[y_proba[:, 1] >= 0.5][0]

plt.figure(figsize=(8, 3))
plt.plot(X[y==0], y[y==0], "bs")
plt.plot(X[y==1], y[y==1], "g^")
plt.plot([decision_boundary, decision_boundary], [-1, 2], "k:", linewidth=2)
plt.plot(X_new, y_proba[:, 1], "g-", linewidth=2, label="Iris virginica")
plt.plot(X_new, y_proba[:, 0], "b--", linewidth=2, label="Not Iris virginica")
plt.text(decision_boundary+0.02, 0.15, "Decision  boundary", fontsize=14, color="k", ha="center")
plt.arrow(decision_boundary, 0.08, -0.3, 0, head_width=0.05, head_length=0.1, fc='b', ec='b')
plt.arrow(decision_boundary, 0.92, 0.3, 0, head_width=0.05, head_length=0.1, fc='g', ec='g')
plt.xlabel("Petal width (cm)", fontsize=14)
plt.ylabel("Probability", fontsize=14)
plt.legend(loc="center left", fontsize=14)
plt.axis([0, 3, -0.02, 1.02])
plt.show()

a7b04bbaf56e67206561f4b519d2c43c.png

Iris virginica(三角形)的花瓣宽度从

厘米不等,而其他鸢尾花(正方形)的花瓣宽度一般较小,从
厘米不等。对于花瓣宽度,如果在
厘米以上,则分类器高度确信该花是
Iris virginica,而如果在
厘米以下,则它高度确信该花不是
Iris virginica。对于两者之间的情况,分类器是不确定的,但是,如果你让它预测类别(使用 predict()方法而不是 predict_proba()方法),它将返回最有可能的类。因此,在
厘米左右有一个决策边界,该处两个概率都等于50%:如果花瓣宽度高于
厘米,分类器就会预测这朵花是Iris virginica,否则就会预测它不是。
log_reg.predict([[1.7], [1.5]])
# array([1, 0])

图4-24展示了相同的数据集,但这次展示的是两个特征:花瓣宽度和长度。训练完成后,逻辑回归分类器就可以根据这两个特征来估计新样本是Iris virginica的概率。虚线代表模型估计概率为50%的点:这是模型的决策边界,并注意到,这是一个线性边界。每条平行线代表模型输出特定概率的点,从15%(左下)到90%(右上),根据模型,右上角线以外的所有花都有90%以上的概率是Iris virginica

38cfaabe2fef3f384d730365476b2e08.png

如同其他线性模型一样,逻辑回归模型可以使用

惩罚来进行正则化,事实上,
Scikit-Learn默认会增加一个
的惩罚项。
控制 Scikit-LearnLogisticRegression模型正则化强度的超参数不是
,而是其倒数:
的值越大,模型的正则化强度
越低

Softmax

逻辑回归模型可以推广为支持多分类的情形,这就是所谓的Softmax回归。

其思想很简单:当给定一个实例

时,Softmax回归模型首先计算出每个类
的分数
,然后通过对分数应用
softmax函数来得到每个类的概率。计算
的方程应该很熟悉,因为它和线性回归的方程一样(见方程4-19):

注意到,每个类都有自己的专用参数向量

,所有这些向量通常以行的形式存储在一个参数矩阵
中。

只要计算出实例

的每一类对应的分数,就可以通过softmax函数来计算实例属于类
的概率(公式4-20)。该函数计算每个分数的指数,然后将其标准化(除以所有指数的总和)。

其中:

  • 为类别数
  • 为实例
    的所有类别的得分向量
  • 为实例
    属于类别
    的概率

与逻辑回归一样,Softmax回归也是预测估计概率最高的类(简单来说就是得分最高的类),如公式4-21所示:

我们的目标是训练一个对目标类具有高概率取值的模型。最小化式4-22所示的代价函数,即交叉熵,是可以达到目标的,因为当模型对目标类的估计概率较低时,它会对模型进行惩罚。交叉熵经常用来衡量预测类概率与目标类的匹配程度。

其中:
  • 表示第
    个实例是否属于类别
    ,其取值为

该代价函数关于

的梯度向量由式4-23给出:

现在,你可以计算每个类的梯度向量,然后使用梯度下降(或任何其他优化算法)找到最小化代价函数的参数矩阵

X = iris["data"][:, (2, 3)]  # petal length, petal width
y = iris["target"]

softmax_reg = LogisticRegression(multi_class="multinomial",solver="lbfgs", C=10, random_state=42)
softmax_reg.fit(X, y)

图4-25展示了由此产生的决策边界,用背景颜色表示。注意,任何两个类之间的决策边界都是线性的。图中还显示了Iris versicolor的概率,用曲线表示(例如,标有0.450的线代表45%的概率边界)。

ffd4c87b02e3d2a505425e12f5bd7953.png
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值