训练模型


在以前的描述中,咱们一般把机器学习模型和训练算法看成黑箱子来处理。

然而,若是你对其内部的工做流程有必定了解的话,当面对一个机器学习任务时候,这些理论能够帮助你快速的找到恰当的机器学习模型,合适的训练算法,以及一个好的假设集。同时,了解黑箱子内部的构成,有助于你更好地调试参数以及更有效的偏差分析。本章讨论的大部分话题对于机器学习模型的理解,构建,以及神经网络(详细参考本书的第二部分)的训练都是很是重要的。html

首先咱们将以一个简单的线性回归模型为例,讨论两种不一样的训练方法来获得模型的最优解:python

直接使用封闭方程进行求根运算,获得模型在当前训练集上的最优参数(即在训练集上使损失函数达到最小值的模型参数)web

使用迭代优化方法:梯度降低(GD),在训练集上,它能够逐渐调整模型参数以得到最小的损失函数,最终,参数会收敛到和第一种方法相同的的值。同时,咱们也会介绍一些梯度降低的变体形式:批量梯度降低(Batch GD)、小批量梯度降低(Mini-batch GD)、随机梯度降低(Stochastic GD),在第二部分的神经网络部分,咱们会屡次使用它们。算法

接下来,咱们将研究一个更复杂的模型:多项式回归,它能够拟合非线性数据集,因为它比线性模型拥有更多的参数,因而它更容易出现模型的过拟合。所以,咱们将介绍如何经过学习曲线去判断模型是否出现了过拟合,并介绍几种正则化方法以减小模型出现过拟合的风险。网络

最后,咱们将介绍两个经常使用于分类的模型:Logistic回归和Softmax回归。app

1.线性回归

The Normal Equation

多元回归中,参数的求解公式是:
在这里插入图片描述
让咱们生成一些近似线性的数据来测试一下这个方程。dom

import numpy as np
np.random.seed(42)
X = 2 * np.random.rand(100, 1) #返回大小为[100, 1]的服从标准正态分布的随机样本值
y = 4 + 3 * X + np.random.randn(100, 1)
import matplotlib.pyplot as plt
plt.scatter(X,y)
plt.show()

在这里插入图片描述
如今让咱们使用正态方程来计算 θ ^ \hat{\theta} ,咱们将使用 Numpy 的线性代数模块(np.linalg)中的inv()函数来计算矩阵的逆,以及dot()方法来计算矩阵的乘法。机器学习

X_b = np.c_[np.ones((100, 1)), X] 
theta_best = np.linalg.inv(X_b.T.dot(X_B)).dot(X_b.T).dot(y)

让咱们看一下最后结果svg

theta_best
[[4.21509616]
 [2.77011339]]

由于存在随机噪声,预测值和真实值有必定差距函数

如今咱们可以使用 θ ^ \hat{\theta} 来进行预测:

X_new = np.array([[0],[2]])
X_new_b = np.c_[np.ones((2, 1)), X_new]
y_predict = X_new_b.dot(theta_best)
print(y_predict)
[[4.21509616]
 [9.75532293]]

画出这个模型的图像

X_new = np.array([[0],[2]])
X_new_b = np.c_[np.ones((2, 1)), X_new]
y_predict = X_new_b.dot(theta_best)

在这里插入图片描述
使用下面的 Scikit-Learn 代码能够达到相同的效果:

from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X,y)
lin_reg.intercept_, lin_reg.coef_  #coef_存放回归系数,intercept_则存放截距
lin_reg.predict(X_new)
(array([4.21509616]),array([2.77011339]))
array([[4.21509616],[9.75532293]])

计算复杂度

正态方程须要计算矩阵 X T X {\mathbf{X}}^T\cdot\mathbf{X} 的逆,它是一个 n * n 的矩阵(n 是特征的个数)。这样一个矩阵求逆的运算复杂度大约在 O ( n 2.4 ) O(n^{2.4}) O ( n 3 ) O(n^{3}) 之间,具体值取决于计算方式。换句话说,若是你将你的特征个数翻倍的话,其计算时间大概会变为原来的 5.3( 2 2.4 2^{2.4} )到 8( 2 3 2^{3} )倍。

提示:
当特征的个数较大的时候(例如:特征数量为 100000),正态方程求解将会很是慢。

接下来,咱们将介绍另外一种方法去训练模型。这种方法适合在特征个数很是多,训练实例很是多,内存没法知足要求的时候使用。

梯度降低

梯度降低是一种很是通用的优化算法,它可以很好地解决一系列问题。梯度降低的总体思路是经过的迭代来逐渐调整参数使得损失函数达到最小值。

假设浓雾下,你迷失在了大山中,你只能感觉到本身脚下的坡度。为了最快到达山底,一个最好的方法就是沿着坡度最陡的地方下山。这其实就是梯度降低所作的:它计算偏差函数关于参数向量 θ \theta 的局部梯度,同时它沿着梯度降低的方向进行下一次迭代。当梯度值为零的时候,就达到了偏差函数最小值 。

具体来讲,开始时,须要选定一个随机的\theta(这个值称为随机初始值),而后逐渐去改进它,每一次变化一小步,每一步都试着下降损失函数(例如:均方差损失函数),直到算法收敛到一个最小值,以下图。
在这里插入图片描述
线性回归模型的均方差损失函数是一个凸函数,这意味着若是你选择曲线上的任意两点,它们的连线段不会与曲线发生交叉(译者注:该线段不会与曲线有第三个交点)。这意味着这个损失函数没有局部最小值,仅仅只有一个全局最小值。同时它也是一个斜率不能突变的连续函数。这两个因素致使了一个好的结果:梯度降低能够无限接近全局最小值。(只要你训练时间足够长,同时学习率不是太大 )。

事实上,损失函数的图像呈现碗状,可是不一样特征的取值范围相差较大的时,这个碗多是细长的。下图展现了梯度降低在不一样训练集上的表现。在左图中,特征 1 和特征 2 有着相同的数值尺度。在右图中,特征 1 比特征2的取值要小的多,因为特征 1 较小,所以损失函数改变时, θ 1 \theta_1 会有较大的变化,因而这个图像会在 θ 1 \theta_1 轴方向变得细长。
在这里插入图片描述

批量梯度降低

批量梯度降低SGD:每更新一个参数时,须要全部的训练样本。
以线性回归为例:
在这里插入图片描述

eta = 0.1 # 学习率
n_iterations = 1000
m = 100

theta = np.random.randn(2,1) # 随机初始值

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

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

优势:
  (1) 一次迭代是对全部样本进行计算,此时利用矩阵进行操做,实现了并行。
  (2) 由全数据集肯定的方向可以更好地表明样本整体,从而更准确地朝向极值所在的方向。当目标函数为凸函数时,BGD必定可以获得全局最优。
  缺点:
  当样本数目 m 很大时,每迭代一步都须要对全部样本计算,训练过程会很慢。

随机梯度降低

随机梯度降低是每次迭代使用一个样原本对参数进行更新。
因为每一次迭代,只须要在内存中有一个实例,这使随机梯度算法能够在大规模训练集上使用。

虽然随机性能够很好的跳过局部最优值,但同时它却不能达到最小值。解决这个难题的一个办法是逐渐下降学习率。 开始时,走的每一步较大(这有助于快速前进同时跳过局部最小值),而后变得愈来愈小,从而使算法到达全局最小值。 决定每次迭代的学习率的函数称为learning schedule

n_epochs = 50 
t0, t1 = 5, 50  #learning_schedule的超参数

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

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

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 * gradiens

优势:
  因为不是在所有训练数据上的损失函数,而是在每轮迭代中,随机优化某一条训练数据上的损失函数,这样每一轮参数的更新速度大大加快。
  缺点:
  (1) 准确度降低。因为即便在目标函数为强凸函数的状况下,SGD仍旧没法作到线性收敛。
  (2) 可能会收敛到局部最优,因为单个样本并不能表明全体样本的趋势。
  (3) 不易于并行实现。
经过使用 Scikit-Learn 完成线性回归的随机梯度降低,你须要使用SGDRegressor类,这个类默认优化的是均方差损失函数。下面的代码迭代了 50 代,其学习率 η \eta 为0.1(eta0=0.1),使用默认的learning schedule(与前面的不同),同时也没有添加任何正则项(penalty = None):

from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRregressor(n_iter=50, penalty=None, eta0=0.1)
sgd_reg.fit(X,y.ravel())
sgd_reg.intercept_, sgd_reg.coef_
(array([4.18380366]),array([2.74205299]))

小批量梯度降低

小批量梯度降低每次迭代 使用 ** batch_size** 个样原本对参数进行更新。

在迭代的每一步,批量梯度使用整个训练集,随机梯度时候用仅仅一个实例,在小批量梯度降低中,它则使用一个随机的小型实例集。

小批量梯度降低在参数空间上的表现比随机梯度降低要好的多,尤为在有大量的小型实例集时。做为结果,小批量梯度降低会比随机梯度更靠近最小值。可是,另外一方面,它有可能陷在局部最小值中(在遇到局部最小值问题的状况下,和咱们以前看到的线性回归不同)。

优势:
  (1) 经过矩阵运算,每次在一个batch上优化神经网络参数并不会比单个数据慢太多。
  (2) 每次使用一个batch能够大大减少收敛所须要的迭代次数,同时可使收敛到的结果更加接近梯度降低的效果。(好比上例中的30W,设置batch_size=100时,须要迭代3000次,远小于SGD的30W次)
  (3) 可实现并行化。
缺点:
  (1) batch_size的不当选择可能会带来一些问题。

2.多项式回归

若是你的数据实际上比简单的直线更复杂呢? 使人惊讶的是,你依然可使用线性模型来拟合非线性数据。 一个简单的方法是对每一个特征进行加权后做为新的特征,而后训练一个线性模型在这个扩展的特征集。 这种方法称为多项式回归。

首先,咱们根据一个简单的二次方程(并加上一些噪声,如图 4-12)来生成一些非线性数据:

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

在这里插入图片描述
很清楚的看出,直线不能恰当的拟合这些数据。因而,咱们使用 Scikit-Learning 的PolynomialFeatures类进行训练数据集的转换,让训练集中每一个特征的平方(2 次多项式)做为新特征(在这种状况下,仅存在一个特征):

from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2,include_bias=False)
X_poly = poly_features.fit_transform(X)

学习曲线

若是你使用一个高阶的多项式回归,你可能发现它的拟合程度要比普通的线性回归要好的多。例如,图 4-14 使用一个 300 阶的多项式模型去拟合以前的数据集,并同简单线性回归、2 阶的多项式回归进行比较。注意 300 阶的多项式模型如何摆动以尽量接近训练实例。

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

from sklearn.linear_model import LinearRegression
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)
    train_errors, val_errors = [], []
    for m in range(1, len(X_train)):
        model.fit(X_train[:m], y_train[:m])  #用m个样本进行训练 m的范围是[1, len(X_train))
        y_train_predict = model.predict(X_train[:m])
        y_val_predict = model.predict(X_val)
        train_errors.append(mean_squared_error(y_train_predict, y_train[:m]))
        val_errors.append(mean_squared_error(y_val_predict, y_val))
    plt.plot(np.sqrt(train_errors), "r-+", linewidth=2, label="train")
    plt.plot(np.sqrt(val_errors), "b-", linewidth=3, label="val")
    plt.show()

lin_reg = LinearRegression()
plot_learning_curves(lin_reg, X, y)

在这里插入图片描述
这幅图值得咱们深究。首先,咱们观察训练集的表现:当训练集只有一两个样本的时候,模型可以很是好的拟合它们,这也是为何曲线是从零开始的缘由。可是当加入了一些新的样本的时候,训练集上的拟合程度变得难以接受,出现这种状况有两个缘由,一是由于数据中含有噪声,另外一个是数据根本不是线性的。所以随着数据规模的增大,偏差也会一直增大,直到达到高原地带并趋于稳定,在以后,继续加入新的样本,模型的平均偏差不会变得更好或者更差。咱们继续来看模型在验证集上的表现,当以很是少的样本去训练时,模型不能恰当的泛化,也就是为何验证偏差一开始是很是大的。当训练样本变多的到时候,模型学习的东西变多,验证偏差开始缓慢的降低。可是一条直线不可能很好的拟合这些数据,所以最后偏差会到达在一个高原地带并趋于稳定,最后和训练集的曲线很是接近。

上面的曲线表现了一个典型的欠拟合模型,两条曲线都到达高原地带并趋于稳定,而且最后两条曲线很是接近,同时偏差值很是大。

若是你的模型在训练集上是欠拟合的,添加更多的样本是没用的。你须要使用一个更复杂的模型或者找到更好的特征。

3.线性模型的正则化

岭(Ridge)回归

Lasso 回归

弹性网络(ElasticNet)

早期中止法(Early Stopping)

7.逻辑回归

几率估计

训练和损失函数

决策边界

8.Softmax 回归

9.参考连接

批量梯度降低(BGD)、随机梯度降低(SGD)以及小批量梯度降低(MBGD)的理解
sklearn 的 PolynomialFeatures 的用法