6 多项式回归与模型泛化

  • Post author:
  • Post category:其他


本文我们会通过讲解多项式回归的算法,从而引出机器学习算法中非常重要的一环,即模型泛化相关的知识

1 多项式回归算法

1.1 分析

之前我们讲过线性回归算法,但是很多情况下,线性回归可能不能更好的拟合所有的数据,此时可能使用非线性回归更好,如下图所示

我们可以将
x^2
当成一个特征,这样就可以使用线性回归的算法来求出系数了。

import numpy as np 
import matplotlib.pyplot as plt

# 模拟数据集
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, 100)

plt.scatter(x, y)
plt.show()
复制代码

数据分布如下图所示:

  1. 如果使用线性回归算法,如下:
from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, y)
y_predict = lin_reg.predict(X)

plt.scatter(x, y)
plt.plot(x, y_predict, color='r')
plt.show()
复制代码

如下图,可以看出拟合程度并不好

  1. 添加一个多项式特征
X2 = np.hstack([X, X**2]) # 添加了x^2的特征
lin_reg2 = LinearRegression()
lin_reg2.fit(X2, y)
y_predict2 = lin_reg2.predict(X2)

# 可以求出系数
lin_reg2.coef_  #array([ 0.99870163,  0.54939125])
lin_reg2.intercept_  # 1.8855236786516001

plt.scatter(x, y)
plt.plot(np.sort(x), y_predict2[np.argsort(x)], color='r')
plt.show()
复制代码

拟合之后的图形如下,多项式拟合程度更好

1.2 scikit-learn中多项式回归

scikit-learn提供了一个类PolynomialFeatures,用来多项式回归

from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2)
poly.fit(X)
X2 = poly.transform(X)
X2.shape #(100, 3)

# 使用线性回归算法
lin_reg2 = LinearRegression()
lin_reg2.fit(X2, y)
y_predict2 = lin_reg2.predict(X2)

lin_reg2.coef_ # array([ 0.        ,  0.9460157 ,  0.50420543])
lin_reg2.intercept_ # 2.1536054095953823
复制代码

PolynomialFeatures构造方法中的参数degree,表示可以构造多少个多项式,举例说明:

如果特征只有
x_1
,degree = 2, 表示
x_1
的最大多项式为2次方,分别为
1,x_1,x_1^2
,以此特征变为3个

如果特征有
x_1,x_2
两个,degree = 3, 表示
x_1,x_2
的最大多项式为3次方,分别为
1,x_1,x_2, x_1^2,x_2^2,x_1x_2,x_1^3,x_2^3,x_1^2x_2,x_1x_2^2
,以此特征变为10个

因此使用多项式的过程可以总结以下几个步骤:

  1. 使用PolynomialFeatures(degree=?)进行多项式匹配,生成包含多项式的特征X。
  2. 对数据进行归一化,因为如果degree非常的话,比如100,10和10的100次方差距非常大,因此需要对数据进行归一化。
  3. 最后使用线性回归的算法,求得系数的值,也就是最终的模型。

1.3 Pipeline

上面总结出来的步骤,scikit_learn中有一个类已经帮我们封装好了,即Pipeline

x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x**2 + x + 2 + np.random.normal(0, 1, 100)

from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

# 三步合成在一起了
poly_reg = Pipeline([
    ("poly", PolynomialFeatures(degree=2)),
    ("std_scaler", StandardScaler()),
    ("lin_reg", LinearRegression())
])

poly_reg.fit(X, y)
y_predict = poly_reg.predict(X)
复制代码

2 过拟合与欠拟合

2.1 问题引入

用几个图来说吧:

1、欠拟合:算法所训练的模型不能完整的表述数据关系

2、过拟合:算法所训练的模型过多的表达了数据间的噪音关系

如果使用多项式回归,degree为100的话,就会发生过拟合

如果使用此模型进行对测试集进行预测,产生的结果为:

2.2 测试数据的意义

对数据集分成训练集与测试集的意义就在于,测试数据集可以验证我们模型的泛化能力。如果对训练数据拟合很好,但是对测试集拟合的不好,那就可能发生了过拟合。

机器学习算法更多的解决的是过拟合的问题。

下面一张图可以清晰的表示,模型的复杂程度与模型准确率的关系。

2.3 学习曲线

随着训练样本的逐渐增多,算法训练出的模型的表现能力。

方法原理:随着样本的增多,训练误差与测试误差的分布情况

def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
    train_score = [] #训练误差
    test_score = [] #测试误差
    for i in range(1, len(X_train)+1):
        algo.fit(X_train[:i], y_train[:i])
    
        y_train_predict = algo.predict(X_train[:i])
        train_score.append(mean_squared_error(y_train[:i], y_train_predict))
    
        y_test_predict = algo.predict(X_test)
        test_score.append(mean_squared_error(y_test, y_test_predict))
    
    # 分别绘制训练误差与测试误差    
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(train_score), label="train")
    plt.plot([i for i in range(1, len(X_train)+1)], 
                               np.sqrt(test_score), label="test")
    plt.legend()
    plt.axis([0, len(X_train)+1, 0, 4])
    plt.show()
复制代码

线性回归的学习曲线:

plot_learning_curve(LinearRegression(), X_train, X_test, y_train, y_test)

degree=2时的多项式回归的学习曲线:

degree=20时的多项式回归的学习曲线:

我们对比一下欠拟合、过拟合 与 最佳情况的对比

1、欠拟合 对于训练和测试数据集,误差都偏大

2、过拟合 训练数据集误差与最佳差距不大 对于测试数据集误差偏大,并且两个误差之间的差距比较大。

2.4 交叉验证

我们将数据分为训练数据与测试数据,通过测试数据集判断模型的好坏。 如果发生了过拟合,我们来进行调整参数,然后再通过测试数据集判断模型的好坏。

问题1:

我们一直围绕着测试数据集打转,针对测试数据集进行多次调参,如果测试数据集过拟合了,改怎么办?

解决办法:分为训练数据、验证数据、测试数据三部分: 其中训练数据与验证数据都参与了调参的过程,而测试数据是不参与调参过程的,相当于黑盒子数据。

问题2:随机?

这三部分的数据都是随机产生的?那么如果验证数据集里有极端的数据,那么调参后得到的模型可能有问题

解决办法:交叉验证,对数据分成K份,对K个模型的均值作为结果调参

from sklearn.model_selection import cross_val_score

knn_clf = KNeighborsClassifier()
cross_val_score(knn_clf, X_train, y_train, cv=5) # 5份的准确率分别为array([ 0.99543379,  0.96803653,  0.98148148,  0.96261682,  0.97619048])
复制代码

回顾网格搜索,GridSearchCV,其中CV表示的就是交叉验证:

from sklearn.model_selection import GridSearchCV

param_grid = [
    {
        'weights': ['distance'],
        'n_neighbors': [i for i in range(2, 11)], 
        'p': [i for i in range(1, 6)]
    }
]

grid_search = GridSearchCV(knn_clf, param_grid, verbose=1)
grid_search.fit(X_train, y_train)

grid_search.best_score_ #0.98237476808905377,找到的是交叉验证平均后的数值
grid_search.best_params_ # {'n_neighbors': 2, 'p': 2, 'weights': 'distance'}

best_knn_clf = grid_search.best_estimator_ # 获取交叉验证后最好的模型
best_knn_clf.score(X_test, y_test) # 0.98052851182197498,平均后的模型
复制代码

缺点:每次训练k个模型,相当于整体性能慢了k倍

2.5 偏差方差权衡

偏差:偏离中心点 方差:在中心点分散

模型误差= 偏差+方差+不可避免的误差

导致偏差主要原因:欠拟合,对问题本身的假设不正确 如:非线性数据使用线性回归

导致方差的主要原因:过拟合,数据的一点点扰动都会较大地影响模型。 比如学习了过多的噪音,通常原因是模型太复杂。

  • 有一些算法天生是高方差的算法:如kNN

  • 非参数学习算法通常都是高方差的算法。因为不对数据进行假设

  • 有一些算法天生是高偏差算法。如线性回归

  • 参数学习算法多次是高偏差算法。因为对数据具有极强的假设

  • 大多数算法具有相应的参数,可以调整偏差和方差

如kNN的k,k越大模型就越简单,偏差越大,方差越小;k越小模型越复杂,偏差就越小,方差就越大。

如线性回归中多项式回归,degree越小,偏差越大,方差越小。degree越大,模型越复杂,偏差越小,方差越大。

  • 偏差与方差通常是矛盾的,降低偏差就会提高方差,降低方差就会提高偏差

机器学习算法的主要挑战,来自于方差,就是解决过拟合的问题。 偏差主要是来自于对数据来源的认知,比如特征数据不合理,就会导致偏差很大。

解决高方差的通常手段有:

  1. 降低模型复杂度
  2. 减少数据维度;降噪(PCA)
  3. 增加样本数(神经网络、深度学习,只有在数据规模足够大,效果才会好)
  4. 使用验证集
  5. 模型正则化

3 模型正则化

模型正则化就是:限制参数的大小

如下图中,某些斜率会非常大。

3.1 目标

我们不能求J(θ),也要顾及后面这项。注意此时不需要θ0,因为截距不影响整体的模型效果

其中α也是一个超参数

3.2 岭回归

# 绘制图形方法
def plot_model(model):
    X_plot = np.linspace(-3, 3, 100).reshape(100, 1)
    y_plot = model.predict(X_plot)

    plt.scatter(x, y)
    plt.plot(X_plot[:,0], y_plot, color='r')
    plt.axis([-3, 3, 0, 6])
    plt.show()

np.random.seed(42)
x = np.random.uniform(-3.0, 3.0, size=100)
X = x.reshape(-1, 1)
y = 0.5 * x + 3 + np.random.normal(0, 1, size=100)
X_train, X_test, y_train, y_test = train_test_split(X, y)

poly_reg = PolynomialRegression(degree=20)
poly_reg.fit(X_train, y_train)
y_poly_predict = poly_reg.predict(X_test)
mean_squared_error(y_test, y_poly_predict) # 167.94010867293571
plot_model(poly_reg)
复制代码

过拟合时,得到的曲线如下图所示

使用岭回归Ridge之后:

from sklearn.linear_model import Ridge

def RidgeRegression(degree, alpha):
    return Pipeline([
        ("poly", PolynomialFeatures(degree=degree)),
        ("std_scaler", StandardScaler()),
        ("ridge_reg", Ridge(alpha=alpha))
    ])

ridge1_reg = RidgeRegression(20, 0.0001)
ridge1_reg.fit(X_train, y_train)

y1_predict = ridge1_reg.predict(X_test)
mean_squared_error(y_test, y1_predict) # 1.3233492754051845
plot_model(ridge1_reg)
复制代码

曲线平滑了不少,误差也减少了不少

如果α非常大的情况

ridge4_reg = RidgeRegression(20, 10000000)
ridge4_reg.fit(X_train, y_train)

y4_predict = ridge4_reg.predict(X_test)
mean_squared_error(y_test, y4_predict) # 1.8408455590998372
plot_model(ridge4_reg)
复制代码

最后变成了一条直线,因为此时所有θ都为0

3.3 LASSO回归

那么和岭回归相比,区别与好处是什么呢 我们先看下使用LASSO的效果如何

from sklearn.linear_model import Lasso

def LassoRegression(degree, alpha):
    return Pipeline([
        ("poly", PolynomialFeatures(degree=degree)),
        ("std_scaler", StandardScaler()),
        ("lasso_reg", Lasso(alpha=alpha))
    ])
lasso1_reg = LassoRegression(20, 0.01)
lasso1_reg.fit(X_train, y_train)

y1_predict = lasso1_reg.predict(X_test)
mean_squared_error(y_test, y1_predict) # 1.1496080843259966
plot_model(lasso1_reg)
复制代码

相比岭回归,α值无需取那么小,也可以得到更平滑的曲线

而当α = 0.1时

而当α = 1时

比较Ridge与LASSO:

  • 使用Ridge有很多系数,值很小,趋向于0,但不为0

  • 使用LASSO趋向于是一部分θ=0,所以可以作为特征选择器使用。

解释: Ridge只对后一项进行梯度下降法,是如下图所示,每一步θ都是有值的

而LASSO对后一项进行梯度下降法,如下图所示,先沿着某一方向,找到一个轴,然后沿着这个轴走向原点。而在某个轴为0点,就会使得很多个θ=0.

但是LASSO可能会错误的把某些有用的特征给变为0 所以从准确率角度讲,Ridge会更准确一些。 如果特征非常多,LASSO可以把特征变小。而Ridge计算量会非常大。

3.4 L1、L2弹性网

实际上还存在L0正则项 —— 即尽量让θ的个数少

实际用L1取代,因为L0正则的优化是一个NP难的问题

弹性网:结合L1与L2

如果特征很大,使用岭回归计算量会非常大,而LASSO又急于使得某些参数为0,因此优先使用弹性网,因为结合了两者的优点

4 总结

本章我们从多项式回归的讲解中,引出了机器学习中最重要的两个问题,欠拟合与过拟合的问题。

接着为了防止测试数据过拟合,说明了数据集分割的重要性。并且用交叉验证的方式来验证模型。

为了降低方差,引出正则化的概念,并分别说明了Ridge与LASSO正则化的区别与优缺点。



声明:此文章为本人学习笔记,课程来源于慕课网:python3入门机器学习经典算法与应用。在此也感谢bobo老师精妙的讲解。

如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、AI资料、以及感悟,欢迎留言,与大家一起探索AI之路。

转载于:https://juejin.im/post/5cfe5624e51d45772a49ad16