Jupyter notebook学习笔记——线性回归、多项式回归

一、线性回归

1.线性模型

概括地说,线性模型就是对输入特征加权求和,再加上一个偏置项(也称为截距项)的常数,以此进行预测。
线性回归模型预测公式:
y ^ = θ 0 + θ 1 x 1 + θ 2 x 2 + ⋯ + θ n x n \hat{y}=\theta_0+\theta_1x_1+\theta_2x_2+\cdots+\theta_nx_n y^=θ0+θ1x1+θ2x2++θnxn

  • y ^ \hat{y} y^是预测值
  • n是特征的数量
  • x i x_i xi是第 i i i个特征值
  • θ j \theta_j θj是第 j j j个模型参数(包括偏置项 θ 0 \theta_0 θ0以及特征权重 θ 1 , θ 2 , ⋯   , θ n \theta_1,\theta_2,\cdots,\theta_n θ1,θ2,,θn

这可以用更为简洁的向量化形式表达:
y ^ = h θ ( X ) = θ T ⋅ X \hat{y}=h_\theta(X)=\theta^T\cdot X y^=hθ(X)=θTX

  • θ \theta θ是模型的参数向量,包括偏置项 θ 0 \theta_0 θ0以及特征权重 θ 1 \theta_1 θ1 θ n \theta_n θn
  • θ T \theta^T θT θ \theta θ的转置向量(为行向量,而不再是列向量)
  • X X X是实例的特征向量,包括从 x 0 x_0 x0 x n x_n xn x 0 x_0 x0永远为1
  • θ T ⋅ X \theta^T\cdot X θTX θ T \theta^T θT X X X的点积
  • h θ h_\theta hθ是使用模型参数 θ \theta θ的假设函数

2.训练线性回归模型

训练模型就是设置模型参数直到模型最适应训练集的过程。要达到这个目的,我们首先需要知道怎么衡量模型对训练数据的拟合程度是好是差。回归模型最常见的性能指标是均方根误差(RMSE)。因此,在训练线性回归模型时,我们需要找到最小化RMSE的 θ \theta θ值。在实践中,将均方误差(MSE)最小化比最小化RMSE更为简单,二者效果相同(因为使函数最小化的值,同样也使其平方根最小)。

线性回归模型的MSE成本函数:
M S E ( X , h θ ) = 1 m ∑ i = 1 m ( θ T ⋅ X ( i ) − y ( i ) ) 2 MSE(X,h_\theta)=\frac1m\sum^m_{i=1}(\theta^T\cdot X^{(i)}-y^{(i)})^2 MSE(X,hθ)=m1i=1m(θTX(i)y(i))2

  • m m m是所使用的数据集中实例的数量
  • X ( i ) X^{(i)} X(i)是数据集中,第 i i i个实例的所有特征值的向量(标签特征除外), y ( i ) y^{(i)} y(i)是标签(也就是我们期待该实例的输出值)
  • X X X是数据集中所有实例的所有特征值的矩阵(标记特征除外)。每个实例为一行。
  • h θ h_\theta hθ是系统的预测函数,也称为一个假设
  • M S E ( X , h θ ) MSE(X,h_\theta) MSE(X,hθ)是使用假设 h θ h_\theta hθ在实例上测量的成本函数。

3.标准方程

为了得到使成本函数最小的 θ \theta θ值,有一个闭式解方法——也就是一个直接得出结果的数学方程,即标准方程
θ ^ = ( X T ⋅ X ) − 1 ⋅ X T ⋅ y \hat{\theta}=(X^T\cdot X)^{-1}\cdot X^T\cdot y θ^=(XTX)1XTy

  • θ ^ \hat{\theta} θ^是使成本函数最小的 θ \theta θ值。
  • y y y是包含 y ( 1 ) y^{(1)} y(1) y ( m ) y^{(m)} y(m)的目标值向量。

我们生成一些线性数据来测试这个公式:

import numpy as np
X=2*np.random.rand(100,1)
y=4+3*X+np.random.randn(100,1)
plt.plot(X,y,'b.')
plt.axis([0,2,0,15])
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)

我们实际用来生成数据的函数是 y = 4 + 3 x 0 + 高 斯 噪 声 y=4+3x_0+高斯噪声 y=4+3x0+。来看看公式的结果:

theta_best
array([[ 4.0929349 ],
   [ 3.00811997]])

我们期待的是 θ 0 = 4 , θ 1 = 3 \theta_0=4,\theta_1=3 θ0=4,θ1=3得到的是 θ 0 = 4.093 , θ 1 = 3.008 \theta_0=4.093,\theta_1=3.008 θ0=4.093,θ1=3.008。非常接近,噪声的存在使其不可能完全还原为原本的函数。

现在可以用 θ ^ \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)
y_predict
array([[  4.0929349 ],
   [ 10.10917484]])

绘制模型的预测结果:

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

在这里插入图片描述

Scikit-Learn的等效代码如下所示:

from sklearn.linear_model import LinearRegression
lin_reg=LinearRegression()
lin_reg.fit(X,y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

Scikit-Learn将偏置项(intercept_)和特征权重(coef_)分离开:

lin_reg.intercept_,lin_reg.coef_
(array([ 4.0929349]), array([[ 3.00811997]]))

预测结果也是一样的:

X_new=np.array([[0],[2]])
y_predict=lin_reg.predict(X_new)
y_predict
 array([[  4.0929349 ],
   [ 10.10917484]])

4.标准方程的局限性

标准方程求逆的矩阵 X T ⋅ X X^T\cdot X XTX,是一个nXn矩阵(n是特征数量)。对这种矩阵求逆的计算复杂度通常为 O ( n 2.4 ) O(n^{2.4}) O(n2.4) O ( n 3 ) O(n^3) O(n3)之间(取决于计算实现)。换句话说,如果将特征数量翻倍,那么计算时间将乘以大约 2 2.4 = 5.3 2^{2.4}=5.3 22.4=5.3倍到 2 3 = 8 2^3=8 23=8倍之间。
特征数量比较大(例如100000)时,标准方程的计算将极其缓慢。

好的一面是,相对于训练集中的实例数量( O ( m ) O(m) O(m))来说,方程是线性的,所以能够有效地处理大量的训练集,只要内存足够。

同样,线性回归模型一经训练(不论是标准方程还是其他算法),预测就非常快速:因为计算复杂度相对于想要预测的实例数量和特征数量来说,都是线性的。换句话说,对两倍的实例(或者是两倍的特征数)进行预测,大概需要两倍的时间。

5.梯度下降算法

梯度下降是一种非常通用的优化算法,能够为大范围的问题找到最优解。梯度下降的中心思想就是迭代地调整参数从而使成本函数最小化:通过测量参数向量 θ \theta θ相关的误差函数的局部梯度,并不断沿着降低梯度的方向调整,直到梯度降为0,到达最小值!

具体来说,首先使用一个随机的 θ \theta θ值(这被称为随机初始化),然后逐步改进,每次踏出一步,每一步都尝试降低一点成本函数(如MSE),直到算法收敛出一个最小值。

梯度下降中一个重要参数是每一步的步长,这取决于超参数学习率。如果学习率太低,算法需要经过大量迭代才能收敛,这将耗费很长时间。反之,如果学习率太高,会导致算法发散,值越来越大,最后无法找到好的解决方案。

梯度下降的两个主要挑战是:如果随机初始化不好,一种可能是算法会收敛到一个局部最小值,而不是全局最小值。另一种可能是算法需要经过很长时间的迭代过程,如果我们停下得太早,将永远达不到全局最小值。

幸好,线性回归模型的MSE成本函数恰好是个凸函数,这意味着连接曲线上任意两个点的线段永远不会跟曲线相交。也就是说不存在局部最小,只有一个全局最小值。它同时也是一个连续函数,所以斜率不会产生陡峭的变化。这两件事保证的结论是:即便是乱走,梯度下降都可以趋近到全局最小值(只要等待时间足够长,学习率也不是太高)。

  • 应用梯度下降时,需要保证所有特征值的大小比例都差不多,否则收敛的时间会长很多。
5.1批量梯度下降

要实现梯度下降,我们需要计算每个模型关于参数 θ j \theta_j θj的成本函数的梯度。换言之,我们需要计算的是如果改变 θ j \theta_j θj,成本函数会改变多少。这被称为偏导数。

关于参数 θ j \theta_j θj的成本函数的偏导数:
δ δ θ j M S E ( θ ) = 2 m ∑ i = 1 m ( θ T ⋅ X ( i ) − y ( i ) ) x j ( i ) \frac{\delta}{\delta\theta_j}MSE(\theta)=\frac2m\sum^m_{i=1}(\theta^T\cdot X^{(i)}-y^{(i)})x_j^{(i)} δθjδMSE(θ)=m2i=1m(θTX(i)y(i))xj(i)
如果不想单独计算这些梯度,可以使用成本函数的梯度向量计算公式对其进行一次性计算。梯度向量,记作 ∇ θ M S E ( θ ) \nabla_\theta MSE(\theta) θMSE(θ),包含所有成本函数(每个模型参数一个)的偏导数:
∇ θ M S E ( θ ) = 2 m X T ⋅ ( X ⋅ θ − y ) \nabla_\theta MSE(\theta)=\frac2mX^T\cdot (X\cdot \theta-y) θMSE(θ)=m2XT(Xθy)
公式在计算梯度下降每一步时,都是基于完整的训练集X。这就是为什么该算法会被称为批量梯度下降:每一步都使用整批训练数据。因此,面对非常庞大的训练集时,算法会变得极慢。但是,梯度下降算法随特征数量扩展的表现比较好:如果要训练的线性模型拥有几十万个特征,使用梯度下降比标准方程要快得多。

一旦有了梯度向量,用梯度向量乘以学习率 η \eta η就可以确定下坡步长的大小:
θ ( n e x t    s t e p ) = θ − η ∇ θ M S E ( θ ) \theta^{(next\;step)}=\theta-\eta\nabla_\theta MSE(\theta) θ(nextstep)=θηθMSE(θ)
批量梯度下降算法的快速实现:

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

代码不是太难,看看产生的结果theta:

theta
array([[ 4.0929349 ],
   [ 3.00811997]])

与标准方程的结果完全一致,梯度下降表现完美。但如果使用了其他的学习率eta,算法可能不会表现得这么完美。要找到合适的学习率,可以使用网格搜索(GridSearchCV)。但是我们可能需要限制迭代次数,这样网格搜索可以淘汰掉那些收敛耗时太长的模型。

要怎么限制迭代次数呢?如果设置太低,算法可能在离最优解还很远时就停了;但是如果设置得太高,模型达到最优解后,继续迭代参数不再变化,又会浪费时间。一个简单的办法是,在开始时设置一个非常大的迭代次数,但是当梯度向量的值变得很微小时中断算法——也就是当它的范数变得低于 ε \varepsilon ε(容差)时,因为这时梯度下降已经几乎到达了最小值。

5.2随机梯度下降

批量梯度下降的主要问题是它要用整个训练集来计算每一步的梯度,所以训练集很大时,算法会特别慢。与之相反的极端是随机梯度下降,每一步在训练集中随机选择一个实例,并且仅基于该单个实例来计算梯度。显然,这让算法变得快多了,因为每个迭代都只需要操作少量的数据。它也可以被用来训练海量的数据集,因为每次迭代只需要在内存中运行一个实例即可。

另一方面,由于算法的随机性质,它比批量梯度下降要不规则得多。成本函数将不再是缓慢降低直到抵达最小值,而是不断上上下下,但是从整体来看,还是在慢慢下降。随着时间推移,最终会非常接近最小值,但是即使它到达了最小值,依旧还会持续反弹,永远不会停止。所以算法停下来的参数值肯定是足够好的,但不是最优的。

随机性的好处在于可以逃离局部最优,但缺点是永远定位不出最小值。要解决这个困境,有一个办法是逐步降低学习率。开始的步长比较大(这有助于快速进展和逃离局部最小值),然后越来越小,让算法尽量靠近全局最小值。这个过程叫作模拟退火,因为它类似于冶金时熔化的金属慢慢冷却的退火过程。确定每个迭代学习率的函数叫作学习计划。如果学习率降得太快,可能会陷入局部最小值,甚至是停留在走向最小值的半途中。如果学习率降得太慢,则需要太长时间才能跳到差不多最小值附近,如果提早结束训练,可能只得到一个次优的解决方案。

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

n_epochs=50
t0,t1=5,50 #学习计划超参数
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*gradients

前面的批量梯度下降需要在整个训练集上迭代1000次,而这段代码只迭代了50次就得到了一个相当不错的解:

theta
array([[ 4.07034981],
   [ 3.01321888]])

训练过程的前10步(虚线表示起点):
在这里插入图片描述
在Scikit-Learn里,用SGD执行线性回归可以使用SGDRegression类,其默认优化的成本函数是平方误差。下面这段代码从学习率0.1开始(eta0=0.1),使用默认的学习计划(跟前面的学习计划不同)运行了50轮,而且没有使用任何正则化(penalty=None):

from sklearn.linear_model import SGDRegressor
sgd_reg=SGDRegressor(n_iter=50,penalty=None,eta0=0.1)
sgd_reg.fit(X,y.ravel())
SGDRegressor(alpha=0.0001, average=False, epsilon=0.1, eta0=0.1,
   fit_intercept=True, l1_ratio=0.15, learning_rate='invscaling',
   loss='squared_loss', max_iter=None, n_iter=50, penalty=None,
   power_t=0.25, random_state=None, shuffle=True, tol=None, verbose=0,
   warm_start=False)

我们再次得到了一个跟标准方程的解非常相近的解决方案:

sgd_reg.intercept_,sgd_reg.coef_
(array([ 4.07553058]), array([ 3.00980982]))
5.3小批量梯度下降

一旦理解了批量梯度下降和随机梯度下降,小批量梯度下降算法就非常容易理解了:每一步的梯度计算,既不是基于整个训练集(如批量梯度下降)也不是基于单个实例(如随机梯度下降),而是基于一小部分随机的实例集也就是小批量。相比随机梯度下降,小批量梯度下降的主要优势在于可以从矩阵运算的硬件优化中获得显著的性能提升,特别是需要用到图形处理器时。

这个算法在参数空间层面的前进过程也不像SGD那样不稳定,特别是批量较大时。所以小批量梯度下降最终会比SGD更接近最小值一些。但是另一方面,它可能更难从局部最小值中逃脱。

二、多项式回归

1.多项式回归模型

如果数据比简单的直线更为复杂,该怎么办?令人意想不到的是,其实我们也可以用线性模型来拟合非线性数据。一个简单的方法就是将每个特征的幂次方添加为一个新特征,然后在这个拓展过的特征集上训练线性模型。这种方法被称为多项式回归

2.训练多项式回归模型

首先,基于简单的二次方程制造一些非线性数据(添加随机噪声):

m=100
X=6*np.random.rand(m,1)-3
y=0.5*X**2+X+2+np.random.randn(m,1)
plt.plot(X,y,'m.')
plt.axis([-3,3,0,10])
plt.show()

在这里插入图片描述
显然,直线永远不可能拟合这个数据。所以我们使用Scikit-Learn的PolynomialFeatures类来对训练数据进行转换,将每个特征的平方(二次多项式)作为新特征加入训练集(这个例子中只有一个特征):

from sklearn.preprocessing import PolynomialFeatures
poly_features=PolynomialFeatures(degree=2,include_bias=False)
X_poly=poly_features.fit_transform(X)
X_poly
array([[ -1.55313385e+00,   2.41222476e+00],
   [  2.26937536e+00,   5.15006453e+00],
   [ -2.89199723e+00,   8.36364798e+00],
   [  1.55085644e+00,   2.40515569e+00],
   [ -2.66421494e+00,   7.09804125e+00],
   [ -2.99880861e+00,   8.99285311e+00],
   [ -1.70687637e+00,   2.91342695e+00],
   [  1.45709972e+00,   2.12313958e+00],
   [ -1.05377654e+00,   1.11044499e+00],
   [  1.51733338e+00,   2.30230057e+00],
   [  1.70881718e+00,   2.92005615e+00],
   [ -1.38765055e+00,   1.92557405e+00],
   [  7.30097359e-01,   5.33042153e-01],
   [  2.89995823e+00,   8.40975774e+00],
   [ -2.85713000e+00,   8.16319184e+00],
   [ -1.79953189e+00,   3.23831502e+00],
   [  2.42766083e+00,   5.89353709e+00],
   [  1.50320219e+00,   2.25961683e+00],
   [  2.72161325e+00,   7.40717866e+00],
   [  8.50566387e-01,   7.23463179e-01],
   [  1.00878308e+00,   1.01764330e+00],
   [  7.16892551e-01,   5.13934930e-01],
   [  9.18762249e-01,   8.44124071e-01],
   [  2.39297456e+00,   5.72632725e+00],
   [ -2.15182429e+00,   4.63034775e+00],
   [  8.60003007e-01,   7.39605171e-01],
   [ -9.66919695e-01,   9.34933697e-01],
   [ -2.96605114e-01,   8.79745934e-02],
   [ -2.14945589e+00,   4.62016064e+00],
   [  1.88383675e+00,   3.54884091e+00],
   [ -1.00302248e+00,   1.00605410e+00],
   [ -1.62588277e+00,   2.64349478e+00],
   [ -7.46713312e-01,   5.57580770e-01],
   [ -1.06495365e+00,   1.13412627e+00],
   [ -5.62994869e-01,   3.16963223e-01],
   [  2.76755553e+00,   7.65936360e+00],
   [ -2.15244570e+00,   4.63302248e+00],
   [  1.74078101e+00,   3.03031854e+00],
   [ -7.81461656e-01,   6.10682319e-01],
   [  2.37770233e+00,   5.65346836e+00],
   [ -4.99466218e-01,   2.49466503e-01],
   [  2.41152004e+00,   5.81542891e+00],
   [  2.15580178e+00,   4.64748131e+00],
   [ -2.45726564e+00,   6.03815440e+00],
   [ -5.18464605e-01,   2.68805546e-01],
   [  2.38849825e+00,   5.70492389e+00],
   [ -1.54238326e+00,   2.37894611e+00],
   [ -1.73766682e+00,   3.01948599e+00],
   [ -1.51790177e+00,   2.30402579e+00],
   [ -1.15762723e+00,   1.34010080e+00],
   [ -6.34584181e-01,   4.02697083e-01],
   [  1.60671637e+00,   2.58153751e+00],
   [  9.74868991e-01,   9.50369550e-01],
   [  1.92347452e+00,   3.69975421e+00],
   [ -1.68903626e+00,   2.85284348e+00],
   [ -2.05249104e+00,   4.21271948e+00],
   [ -2.85976339e+00,   8.17824667e+00],
   [  4.24419509e-01,   1.80131920e-01],
   [  2.37807356e+00,   5.65523387e+00],
   [  1.84128950e+00,   3.39034703e+00],
   [ -1.67367133e+00,   2.80117572e+00],
   [  5.41742730e-01,   2.93485185e-01],
   [ -2.48276151e-01,   6.16410473e-02],
   [  2.69516149e+00,   7.26389546e+00],
   [  2.79606067e+00,   7.81795529e+00],
   [  1.51829156e+00,   2.30520925e+00],
   [  1.16107487e+00,   1.34809485e+00],
   [ -7.88319237e-01,   6.21447219e-01],
   [  2.47181195e+00,   6.10985433e+00],
   [ -2.69723801e+00,   7.27509286e+00],
   [  1.67410423e-01,   2.80262499e-02],
   [  1.97463507e+00,   3.89918366e+00],
   [ -1.23522556e+00,   1.52578217e+00],
   [ -2.33918268e+00,   5.47177562e+00],
   [  1.23635707e+00,   1.52857881e+00],
   [  7.85800389e-01,   6.17482252e-01],
   [  8.38700179e-01,   7.03417989e-01],
   [ -1.49906554e+00,   2.24719748e+00],
   [  8.79324615e-02,   7.73211779e-03],
   [  6.52166917e-01,   4.25321687e-01],
   [ -1.91966465e+00,   3.68511239e+00],
   [  1.74480175e+00,   3.04433316e+00],
   [  7.44510448e-01,   5.54295808e-01],
   [ -2.03029713e+00,   4.12210642e+00],
   [  4.53720792e-01,   2.05862557e-01],
   [ -3.41030006e-02,   1.16301465e-03],
   [  1.43885895e+00,   2.07031507e+00],
   [ -9.86554672e-01,   9.73290121e-01],
   [ -3.63086200e-01,   1.31831589e-01],
   [  1.13971400e+00,   1.29894801e+00],
   [ -1.27603303e+00,   1.62826028e+00],
   [ -2.41495989e+00,   5.83203125e+00],
   [ -3.25413767e-01,   1.05894120e-01],
   [  2.62006977e+00,   6.86476560e+00],
   [ -2.70899797e+00,   7.33866999e+00],
   [ -2.15429577e+00,   4.64099028e+00],
   [ -1.79334054e+00,   3.21607028e+00],
   [  5.83205776e-01,   3.40128977e-01],
   [  5.53139307e-01,   3.05963093e-01],
   [  1.80854460e+00,   3.27083359e+00]])

X_poly现在包含原本的特征X和该特征的平方。现在对这个扩展的训练集匹配一个LinearRegression模型:

lin_reg=LinearRegression()
lin_reg.fit(X_poly,y)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=1, normalize=False)

看下模型预测结果:

lin_reg.intercept_,lin_reg.coef_
(array([ 2.23279821]), array([[ 0.95930232,  0.42162001]]))

还算不错,模型 y ^ = 0.42 x 1 2 + 0.96 x 1 + 2.23 \hat{y}=0.42x_1^2+0.96x_1+2.23 y^=0.42x12+0.96x1+2.23,而实际上原来的函数是 y = 0.5 x 1 2 + 1.0 x 1 + 2.0 + 高 斯 噪 声 y=0.5x_1^2+1.0x_1+2.0+高斯噪声 y=0.5x12+1.0x1+2.0+

图形化展示:

plt.plot(X,y,'m.')
plt.plot(X_random,y_predict,'c-',label='预测结果')
plt.rcParams['font.sans-serif']=['SimHei']
plt.xlabel('x1')
plt.ylabel('y')
plt.axis([-3,3,0,10])
plt.legend()
plt.show()

在这里插入图片描述

  • 当存在多个特征时,多项式回归能够发现特征和特征之间的关系(纯线性回归模型做不到这一点)。这是因为PolynomialFeatures会在给定的多项式阶数下,添加所有特征组合。当degree=d时,PolynomialFeatures可以将一个包含n个特征的数组转换为包含 n + d d ! n ! \frac{n+d}{d!n!} d!n!n+d个特征的数组。要小心特征组合的数量爆炸!

3.学习曲线

高阶多项式回归对训练数据的拟合,很可能会比简单线性回归要好,但是也可能对训练数据过度拟合。那么怎么才能判断模型是过度拟合还是拟合不足呢?

如果使用交叉验证来评估模型的泛化性能。如果模型在训练集上表现良好,但是交叉验证的泛化表现非常糟糕,那么模型就是过度拟合。如果在二者上的表现都不佳,那就是拟合不足。这是判断模型太简单还是太复杂的一种方法。

还有一种方法是观察学习曲线:这个曲线绘制的是模型在训练集和验证集上,关于“训练集大小”的性能函数。要生成这个曲线,只需要在不同大小的训练子集上多次训练模型即可。下面这段代码,在给定训练集下定义了一个函数,绘制模型的学习曲线:

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])
        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='训练集')
    plt.plot(np.sqrt(val_errors),'b-',linewidth=3,label='验证集')
    plt.legend()
    plt.xlabel('训练集大小')
    plt.ylabel('RMSE')
    plt.show()

看看纯线性回归模型(一条直线)的学习曲线:

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

在这里插入图片描述
这条学习曲线是典型的模型拟合不足。两条曲线均到达高地,非常接近,而且相当高。

如果模型对训练数据拟合不足,添加更多训练示例也于事无补。我们需要使用更复杂的模型或者找到更好的特征。

现在我们再来看看在同样的数据集上,一个10项多阶式模型的学习曲线:

from sklearn.pipeline import Pipeline
polynomial_regression=Pipeline((
    ('poly_features',PolynomialFeatures(degree=10,include_bias=False)),
    ('sgd_reg',LinearRegression()),
 ))
plt.ylim(0,3)
plot_learning_curves(polynomial_regression,X,y)

在这里插入图片描述
这条学习曲线看起来跟眼前一条差不多,但是有两个非常大的区别:

  • 训练数据的误差远低于线性回归模型。
  • 两条曲线之间有一定差距。这意味着该模型在训练数据上的表现比验证集上要好很多,这正是过度拟合的标志。但是,如果我们使用更大的训练集,那么这两条曲线将会越来越近。

改进模型过度拟合的方法之一是提供更多的训练数据,直到验证误差接近训练误差。

4.偏差/方差权衡

在统计学习和机器学习领域,一个重要的理论结果是,模型的泛化误差可以被表示为三个截然不同的误差之和:

  • 偏差:这部分泛化误差的原因在于错误的假设,比如假设数据是线性的,而实际上是二次的。高偏差模型最有可能对训练数据拟合不足
  • 方差:这部分误差是由于模型对训练数据的微小变化过度敏感导致的。具有高自由度的模型(例如高阶多项式模型)很可能也有高方差,所以很容易对训练数据过度拟合
  • 不可避免的误差:这部分误差是因为数据本身的噪声所致。减少这部分误差的唯一方法就是清理数据(例如修复数据源,或者是检测并移除异常值)。

增加模型的复杂度通常会显著提升模型的方差,减少偏差。反过来,降低模型的复杂度则会提升模型的偏差,降低方差。这就是为什么称其为权衡。

  • 4
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值