首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python3入门机器学习(八)- 多项式回归

Python3入门机器学习(八)- 多项式回归

作者头像
Meet相识
发布2018-09-12 16:47:27
2K0
发布2018-09-12 16:47:27
举报
文章被收录于专栏:技术专栏技术专栏

1.多项式回归简介

考虑下面的数据,虽然我们可以使用线性回归来拟合这些数据,但是这些数据更像是一条二次曲线,相应的方程是y=ax2+bx+c,这是式子虽然可以理解为二次方程,但是我们呢可以从另外一个角度来理解这个式子:

如果将x2理解为一个特征,将x理解为另外一个特征,换句话说,本来我们的样本只有一个特征x,现在我们把他看成有两个特征的一个数据集。多了一个特征x2,那么从这个角度来看,这个式子依旧是一个线性回归的式子,但是从x的角度来看,他就是一个二次的方程

1-1

以上这样的方式,就是所谓的多项式回归

相当于我们为样本多添加了一些特征,这些特征是原来样本的多项式项,增加了这些特征之后,我们们可以使用线性回归的思路更好的我们的数据

2.编程实验多项式回归

1.模拟多项式回归的数据集

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,size=100)

plt.scatter(x,y)

<matplotlib.collections.PathCollection at 0x114f17160>

[图片上传失败...(image-46be02-1527345377921)]

2.使用线性回归拟合

很明显,我们用一跟直线来拟合一根有弧度的曲线,效果是不好的

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')

[<matplotlib.lines.Line2D at 0x1a1c90ecf8>]

[图片上传失败...(image-e9e717-1527345377922)]

3.解决方案,添加一个特征

原来所有的数据都在X中,现在对X中每一个数据都进行平方, 再将得到的数据集与原数据集进行拼接, 在用新的数据集进行线性回归

(X**2).shape

(100, 1)

X2 = np.hstack([X,X**2])
lin_reg2 = LinearRegression()
lin_reg2.fit(X2,y)
y_predict2 = lin_reg2.predict(X2)

plt.scatter(x,y)
# 由于x是乱的,所以应该进行排序
plt.plot(np.sort(x),y_predict2[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1c691198>]

[图片上传失败...(image-414310-1527345377922)]

从上图可以看出,当我们添加了一个特征(原来特征的平方)之后,再从x的维度来看,就形成了一条曲线,显然这个曲线对原来数据集的拟合程度是更好的

# 第一个系数是x前面的系数,第二个系数是x平方前面的系数
lin_reg2.coef_

array([1.08043759, 0.52423752])

lin_reg2.intercept_

1.9427736300237914

3.总结

多线性回归在机器学习算法上并没有新的地方,完全是使用线性回归的思路 他的关键在于为原来的样本,添加新的特征。而我们得到新的特征的方式是原有特征的多项式的组合。 采用这样的方式,我们就可以解决一些非线性的问题

与此同时需要主要,我们在上一章所讲的PCA是对我们的数据进行降维处理,而我们这一章所讲的多项式回归显然在做一件相反的事情,他让我们的数据升维,在升维之后使得我们的算法可以更好的拟合高纬度的数据


2.scikit-learn中的多项式回归于pipeline

1.使用scikit-learn中的多项式对数据进行预处理

# sklearn中对数据进行预处理的函数都封装在preprocessing模块下,包括之前学的归一化StandardScaler
from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly.fit(X)
X2 = poly.transform(X)
# 第一列是sklearn为我们添加的X的零次方的特征
# 第二列和原来的特征一样是X的一次方的特征
# 第三列是添加的X的二次方的特征
X2[:5]

array([[ 1. , 2.5980174 , 6.74969443], [ 1. , 2.07484052, 4.30496317], [ 1. , -1.74999096, 3.06246837], [ 1. , -2.74141103, 7.51533441], [ 1. , -1.3420996 , 1.80123135]])

2.调用LinearRegression对X2进行预测

lin_reg2 = LinearRegression()
lin_reg2.fit(X2,y)
y_predict2 = lin_reg2.predict(X2)

plt.scatter(x,y)
# 由于x是乱的,所以应该进行排序
plt.plot(np.sort(x),y_predict2[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1c4e8860>]

[图片上传失败...(image-80f622-1527345377922)]

lin_reg2.coef_

array([0. , 1.08043759, 0.52423752])

3.关于PolynomialFeatures

# 测试多维的数据集
X = np.arange(1,11).reshape(5,2)
X.shape

(5, 2)

X

array([[ 1, 2], [ 3, 4], [ 5, 6], [ 7, 8], [ 9, 10]])

poly = PolynomialFeatures(degree=2)
poly.fit(X)
X2 = poly.transform(X)
X2

array([[ 1., 1., 2., 1., 2., 4.], [ 1., 3., 4., 9., 12., 16.], [ 1., 5., 6., 25., 30., 36.], [ 1., 7., 8., 49., 56., 64.], [ 1., 9., 10., 81., 90., 100.]])

将52的矩阵进行多项式转换后变成了56

  • 第一列是1 对应的是0次幂
  • 第二列和第三列对应的是原来的x矩阵,此时他有两列一次幂的项
  • 第四列是原来数据的第一列平方的结果
  • 第六列是原来数据的第二列平方的结果
  • 第五列是原来数据的两列相乘的结果

可以想象如果将degree设置为3,那么将产生一下10个元素

  • 1,X1,X2
  • X21,X22,X1*X2
  • X13,X23,X12X2,X22X1

也就是说PolynomialFeatures会穷举出所有的多项式组合

3.Pipline

pipline的英文名字是管道,那么 我们如何使用管道呢,先考虑我们多项式回归的过程

1.使用PolynomialFeatures生成多项式特征的数据集 2.如果生成数据幂特别的大,那么特征直接的差距就会很大,导致我们的搜索非常慢,这时候可以进行数据归一化 3.进行线性回归 pipline 的作用就是把上面的三个步骤合并,使得我们不用一直重复这三步

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,size=100)
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
# 传入每一步的对象名和类的实例化
poly_reg = Pipeline([
('poly',PolynomialFeatures(degree=2)),
('std_scaler',StandardScaler()),
('lin_reg',LinearRegression())
])
poly_reg.fit(X,y)
y_predict = poly_reg.predict(X)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1ccc15c0>]

[图片上传失败...(image-4c2d99-1527345377922)]


3.过拟合与前拟合

1.什么是过拟合和欠拟合

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression

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,size=100)

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')

0.5406237455773699

[图片上传失败...(image-f3daf8-1527345377922)]

# 直接使用线性回归,显然分数太低
lin_reg.score(X,y)

0.5406237455773699

使用均方误差来看拟合的结果,这是因为我们同样都是对一组数据进行拟合,所以使用不同的方法对数据进行拟合 得到的均方误差的指标是具有可比性的,(但是对于多项式回归来说,使用r2score进行衡量是没有问题是)

from sklearn.metrics import mean_squared_error

y_predict = lin_reg.predict(X)
mean_squared_error(y,y_predict)

2.6112077267395803

使用多项式回归
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline

# 使用Pipeline构建多项式回归
def PolynomialRegression(degree):
poly_reg = Pipeline([
('poly',PolynomialFeatures(degree=degree)),
('std_scaler',StandardScaler()),
('lin_reg',LinearRegression())
])
return poly_reg
poly_reg2 = PolynomialRegression(2)
poly_reg2.fit(X,y)
y2_predict = poly_reg2.predict(X)
# 显然使用多项式回归得到的结果是更好的
mean_squared_error(y,y2_predict)

1.000151338154146

plt.scatter(x,y)
plt.plot(np.sort(x),y2_predict[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1d536908>]

[图片上传失败...(image-6e5e74-1527345377922)]

使用更多的维度进行多项式回归
# 使用10个维度
poly_reg10 = PolynomialRegression(10)
poly_reg10.fit(X,y)
y10_predict = poly_reg10.predict(X)
mean_squared_error(y,y10_predict)

0.9394112675409493

plt.scatter(x,y)
plt.plot(np.sort(x),y10_predict[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1d78c198>]

[图片上传失败...(image-97cb38-1527345377922)]

poly_reg100 = PolynomialRegression(100)
poly_reg100.fit(X,y)
y100_predict = poly_reg100.predict(X)
# 显然使用多项式回归得到的结果是更好的
mean_squared_error(y,y100_predict)

0.5431979125088253

plt.scatter(x,y)
plt.plot(np.sort(x),y100_predict[np.argsort(x)],color='r')

[<matplotlib.lines.Line2D at 0x1a1da201d0>]

[图片上传失败...(image-463c7c-1527345377922)]

这条曲线只是原来的点对应的y的预测值连接起来的曲线,不过有很多地方可能没有那个数据点,所以连接的结果和原来的曲线不一样 下面尝试真正还原原来的曲线(构造均匀分布的原数据集)

X_plot = np.linspace(-3,3,100).reshape(100,1)
y_plot = poly_reg100.predict(X_plot)

plt.scatter(x,y)
plt.plot(X_plot,y_plot,color='r')
plt.axis([-3 , 3 , -1,10 ])

[-3, 3, -1, 10]

[图片上传失败...(image-44edb2-1527345377922)]

说明总有一条曲线,他能拟合所有的样本点,使得均方误差的值为0 degree从2到10到100的过程中,虽然均方误差是越来越小的,从均方误差的角度来看是更加小的 但是他真的能更好的预测我们数据的走势吗,例如我们选择2.5到3的一个x,使用上图预测出来的y的大小(0或者-1之间)显然不符合我们的数据

换句话说,我们使用了一个非常高维的数据,虽然使得我们的样本点获得了更小的误差,但是这根曲线完全不是我们想要的样子 他为了拟合我们所有的样本点,变的太过复杂了,这种情况就是过拟合【over-fitting】

相反,在最开始,我们直接使用一根直线来拟合我们的数据,也没有很好的拟合我们的样本特征,当然他犯的错误不是太过复杂了,而是太过简单了 这种情况,我们成为欠拟合-【under-fitting】

对于现在的数据(基于二次方程构造),我们使用低于2项的拟合结果,就是欠拟合;高于2项的拟合结果,就是过拟合

2.为什么要使用训练数据集和测试数据集

模型的泛化能力

使用上小节的过拟合结果,我们可以得知,虽然我们训练出的曲线将原来的样本点拟合的非常好,总体的误差非常的小, 但是一旦来了新的样本点,他就不能很好的预测了,在这种情况下,我们就称我们得到的这条弯弯曲曲的曲线,他的泛化能力(由此及彼的能力)非常弱

image.png

训练数据集和测试数据集的意义

我们训练的模型目的是为了使得预测的数据能够尽肯能的准确,在这种情况下,我们观察训练数据集的拟合程度是没有意义的 我们真正需要的是,我们得到的模型的泛化能力更高,解决这个问题的方法也就是使用训练数据集,测试数据集的分离

image.png

测试数据对于我们的模型是全新的数据,如果使用训练数据获得的模型面对测试数据也能获得很好的结果,那么我们就说我们的模型泛化能力是很强的。 如果我们的模型面对测试数据结果很差的话,那么他的泛化能力就很弱。事实上,这是训练数据集更大的意义

from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y)

lin_reg = LinearRegression()
lin_reg.fit(X_train,y_train)
y_predict = lin_reg.predict(X_test)
# 训练模型使用的X_train,是预测的模型使用X_test,以计算模型的泛化能力
mean_squared_error(y_test,y_predict)

2.7714817137686794

poly_reg2 = PolynomialRegression(2)
poly_reg2.fit(X_train,y_train)
y2_predict = poly_reg2.predict(X_test)
mean_squared_error(y_test,y2_predict)

0.7922037464116539

poly_reg10 = PolynomialRegression(10)
poly_reg10.fit(X_train,y_train),
y10_predict = poly_reg10.predict(X_test)
mean_squared_error(y_test,y10_predict)

1.336192585265726

使用degree=10的时候得到的均方误差要大于degree=2的时候,说明当degree等于10的时候,他的模型泛化能力变弱了

poly_reg100 = PolynomialRegression(100)
poly_reg100.fit(X_train,y_train)
y100_predict = poly_reg100.predict(X_test)
mean_squared_error(y_test,y100_predict)

4.192433747323001e+21

刚刚我们进行的实验实际上在实验模型的复杂度,对于多项式模型来说,我们回归的阶数越高,我们的模型会越复杂,在这种情况下对于我们的机器学习算法来说,通常是有下面一张图的。横轴是模型复杂度(对于不同的算法来说,代表的是不同的意思,比如对于多项式回归来说,是阶数越高,越复杂;对于KNN来说,是K越小,模型越复杂,k越大,模型最简单,当k=n的时候,模型就简化成了看整个样本里,哪种样本最多,当k=1来说,对于每一个点,都要找到离他最近的那个点),另一个维度是模型准确率(也就是他能够多好的预测我们的曲线)

image.png

通常对于这样一个图,会有两根曲线:

  • 一个是对于训练数据集来说的,模型越复杂,模型准确率越高,因为模型越复杂,对训练数据集的拟合就越好,相应的模型准确率就越高
  • 对于测试数据集来说,在模型很简单的时候,模型的准确率也比较低,随着模型逐渐变复杂,对测试数据集的准确率在逐渐的提升,提升到一定程度后,如果模型继续变复杂,那么我们的模型准确率将会进行下降(欠拟合->正合适->过拟合)
欠拟合和过拟合的标准定义

欠拟合:算法所训练的模型不能完整表述数据关系 过拟合:算法所训练的模型过多的表达了数据间的噪音关系


4.学习曲线

1. 什么是学习曲线

随着训练样本的主键增多,算法训练出的模型的表现能力

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(666)
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,size=100)

plt.scatter(x,y)

<matplotlib.collections.PathCollection at 0x107369240>

[图片上传失败...(image-de5417-1527345377922)]

2.实际编程实现学习曲线

from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test = train_test_split(X,y,random_state=10)
X_train.shape

(75, 1)

2.1观察线性回归的学习曲线:观察线性回归模型,随着训练数据集增加,性能的变化
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error

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.axis([0,len(X_train)+1,0,4])
plt.legend()

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

[图片上传失败...(image-7c3927-1527345377922)]

从趋势上看:

  • 在训练数据集上,误差是逐渐升高的。这是因为我们的训练数据越来越多,我们的数据点越难得到全部的累积,不过整体而言,在刚开始的时候误差变化的比较快,后来就几乎不变了
  • 在测试数据集上,在使用非常少的样本进行训练的时候,刚开始我们的测试误差非常的大,当训练样本大到一定程度以后,我们的测试误差就会逐渐减小,减小到一定程度后,也不会小太多,达到一种相对稳定的情况
  • 在最终,测试误差和训练误差趋于相等,不过测试误差还是高于训练误差一些,这是因为,训练数据在数据非常多的情况下,可以将数据拟合的比较好,误差小一些,但是泛化到测试数据集的时候,还是有可能多一些误差
2.2 观察多项式回归的学习曲线
from sklearn.preprocessing import StandardScaler
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline

# 使用Pipline构建多项式回归模型
def PolynomialRegression(degree):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])

# 使用二阶多项式回归
poly2_reg = PolynomialRegression(2)
plot_learning_curve(poly2_reg,X_train,X_test,y_train,y_test)

[图片上传失败...(image-ec1d61-1527345377922)]

首先整体从趋势上,和线性回归的学习曲线是类似的 仔细观察,和线性回归曲线的不同在于,线性回归的学习曲线1.5,1.8左右;2阶多项式回归稳定在了1.0,0.9左右,2阶多项式稳定的误差比较低,说明 使用二阶线性回归的性能是比较好的

# 使用20阶多项式回归
poly20_reg = PolynomialRegression(20)
plot_learning_curve(poly20_reg,X_train,X_test,y_train,y_test)

[图片上传失败...(image-db9bf9-1527345377922)]

在使用20阶多项式回归训练模型的时候可以发现,在数据量偏多的时候,我们的训练数据集拟合的是比较好的,但是测试数据集的误差相对来说增大了很多,离训练数据集比较远,通常这就是过拟合的结果,他的泛化能力是不够的

3.总结

image.png

对于欠拟合比最佳的情况趋于稳定的那个位置要高一些,说明无论对于训练数据集还是测试数据集来说,误差都比较大。这是因为我们本身模型选的就不对,所以即使在训练数据集上,他的误差也是大的,所以才会呈现出这样的一种形态

image.png

对于过拟合的情况,在训练数据集上,他的误差不大,和最佳的情况是差不多的,甚至在极端情况,如果degree取更高的话,那么训练数据集的误差会更低,但是问题在于,测试数据集的误差相对是比较大的,并且训练数据集的误差和测试数据集的误差相差比较大(表现在图上相差比较远),这就说明了此时我们的模型的泛化能力不够好,他的泛化能力是不够的


5.验证数据集与交叉验证

使用分割训练数据集和测试数据集来判断我们的机器学习性能的好坏,虽然是一个非常好的方案,但是会产生一个问题:针对特定测试数据集过拟合

我们每次使用测试数据来分析性能的好坏。一旦发现结果不好,我们就换一个参数(可能是degree也可能是其他超参数)重新进行训练。这种情况下,我们的模型在一定程度上围绕着测试数据集打转。也就是说我们在寻找一组参数,使得这组参数训练出来的模型在测试结果集上表现的最好。但是由于这组测试数据集是已知的,我们相当于在针对这组测试数据集进行调参,那么他也有可能产生过拟合的情况,也就是我们得到的模型针对测试数据集过拟合了

image.png

那么怎么解决这个问题呢? 解决的方式其实就是:我们需要将我们的问题分为三部分,这三部分分别是训练数据集,验证数据集,测试数据集。 我们使用训练数据集训练好模型之后,将验证数据集送给这个模型,看看这个训练数据集训练的效果是怎么样的,如果效果不好的话,我们重新换参数,重新训练模型。直到我们的模型针对验证数据来说已经达到最优了。 这样我们的模型达到最优以后,再讲测试数据集送给模型,这样才能作为衡量模型最终的性能。换句话说,我们的测试数据集是不参与模型的创建的,而其他两个数据集都参与了训练。但是我们的测试数据集对于模型是完全不可知的,相当于我们在模型这个模型完全不知道的数据

image.png

这种方法还会有一个问题。由于我们的模型可能会针对验证数据集过拟合,而我们只有一份验证数据集,一旦我们的数据集里有比较极端的情况,那么模型的性能就会下降很多,那么为了解决这个问题,就有了交叉验证。

1.交叉验证 Cross Validation

交叉验证相对来说是比较正规的、比较标准的在我们调整我们的模型参数的时候看我们的性能的方式

交叉验证:在训练模型的时候,通常把数据分成k份,例如分成3份(ABC)(分成k分,k属于超参数),这三份分别作为验证数据集和训练数据集。这样组合后可以分别产生三个模型,这三个模型,每个模型在测试数据集上都会产生一个性能的指标,这三个指标的平均值作为当前这个算法训练处的模型衡量的标准是怎样的。 由于我们有一个求平均的过程,所以不会由于一份验证数据集中有比较极端的数据而导致模型有过大的偏差,这比我们只分成训练、验证、测试数据集要更加准确

2.编程实现

import numpy as np
from sklearn import datasets
digits = datasets.load_digits()
X = digits.data
y = digits.target

训练train_test_spilt

from sklearn.model_selection import train_test_split

X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.4,random_state =666)
from sklearn.neighbors import KNeighborsClassifier

best_score,best_k,best_p = 0,0,0
# k为k近邻中的寻找k个最近元素
for k in range(2,10):
# p为明科夫斯基距离的p
for p in range(1,5):
knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
knn_clf.fit(X_train,y_train)
score = knn_clf.score(X_test,y_test)
if score > best_score:
best_score,best_k,best_p = score,k,p
print("Best_score = ",best_score)
print("Best_k = ",best_k)
print("Best_p = ",best_p)

Best_score = 0.9860917941585535 Best_k = 3 Best_p = 4

使用交叉验证

# 使用sklearn提供的交叉验证
from sklearn.model_selection import cross_val_score

knn_clf = KNeighborsClassifier()
# 返回的是一个数组,有三个元素,说明cross_val_score方法默认将我们的数据集分成了三份
# 这三份数据集进行交叉验证后产生了这三个结果

# cv默认为3,可以修改改参数,修改修改不同分数的数据集
cross_val_score(knn_clf,X_train,y_train,cv=3)

array([0.98895028, 0.97777778, 0.96629213])

# 使用交叉验证的方式来进行调参的过程
best_score,best_k,best_p = 0,0,0
# k为k近邻中的寻找k个最近元素
for k in range(2,10):
# p为明科夫斯基距离的p
for p in range(1,5):
knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
scores = cross_val_score(knn_clf,X_train,y_train)
score = np.mean(scores)
if score > best_score:
best_score,best_k,best_p = score,k,p
print("Best_score = ",best_score)
print("Best_k = ",best_k)
print("Best_p = ",best_p)

Best_score = 0.9823599874006478 Best_k = 2 Best_p = 2

通过观察两组调参过程的结果可以发现 1.两组调参得出的参数结果是不同的,通常这时候我们更愿意详细使用交叉验证的方式得出的结果。 因为使用train_test_split很有可能只是过拟合了测试数据集得出的结果 2.使用交叉验证得出的最好分数0.982是小于使用分割训练测试数据集得出的0.986,因为在交叉验证的 过程中,通常不会过拟合某一组的测试数据,所以平均来讲这个分数会稍微低一些

但是使用交叉验证得到的最好参数Best_score并不是真正的最好的结果,我们使用这种方式只是为了拿到 一组超参数而已,拿到这组超参数后我们就可以训练处我们的最佳模型

knn_clf = KNeighborsClassifier(weights='distance',n_neighbors=2,p=2)
# 用我们找到的k和p。来对X_train,y_train整体fit一下,来看他对X_test,y_test的测试结果
knn_clf.fit(X_train,y_train)
# 注意这个X_test,y_test在交叉验证过程中是完全没有用过的,也就是说我们这样得出的结果是可信的
knn_clf.score(X_test,y_test)

0.980528511821975

回顾网格搜素

我们上面的操作,实际上在网格搜索的过程中已经进行了,只不过这个过程是sklean的网格搜索自带的一个过程

# GridSearchCV里的cv实际上就是交叉验证的方式
from sklearn.model_selection import GridSearchCV

param_grid = [
{
"weights":['distance'],
"n_neighbors":[i for i in range(2,10)],
"p":[i for i in range(1,6)]
}
]
knn_clf = KNeighborsClassifier()
# cv默认为3,可以修改改参数,修改修改不同分数的数据集
grid_search = GridSearchCV(knn_clf,param_grid,verbose=1,cv=3)
grid_search.fit(X_train,y_train)

Fitting 3 folds for each of 40 candidates, totalling 120 fits

[Parallel(n_jobs=1)]: Done 120 out of 120 | elapsed: 1.0min finished

GridSearchCV(cv=None, error_score='raise', estimator=KNeighborsClassifier(algorithm='auto', leaf_size=30, metric='minkowski', metric_params=None, n_jobs=1, n_neighbors=5, p=2, weights='uniform'), fit_params=None, iid=True, n_jobs=1, param_grid=[{'weights': ['distance'], 'n_neighbors': [2, 3, 4, 5, 6, 7, 8, 9], 'p': [1, 2, 3, 4, 5]}], pre_dispatch='2*n_jobs', refit=True, return_train_score='warn', scoring=None, verbose=1)

Fitting 3 folds for each of 40 candidates, totalling 120 fits 的意思就是交叉验证中分割了三组数据集,而我们的参数组合为8*5=40中组合 3组数据集,30种组合,一共要进行120次的训练

grid_search.best_score_
# 0.9823747680890538 和我们上面得到的best_score 是吻合的

0.9823747680890538

grid_search.best_params_

{'n_neighbors': 2, 'p': 2, 'weights': 'distance'}

best_knn_clf = grid_search.best_estimator_
best_knn_clf.fit(X_train,y_train)
best_knn_clf.score(X_test,y_test)

0.980528511821975

3.总结

image.png

虽然整体速度慢了,但是这个结果却是可信赖的 极端情况下,K-folds cross validation可以叫做留一法

image.png


6.偏差方差均衡

模型误差=偏差(Bias)均差(Variance)+不可避免的误差

偏差

image.png

方差

模型没有完全的学到数据的中心,而学习到了很多噪音

image.png

  • 有一些算法天生是高方差算法。如KNN(过于依赖数据,一点选取的数据点有多数是不正确的,那么预测的结果就是错误的。导致有的很准确,有的非常不准确,方差非常大)
  • 非参数学习通常都是高方差的算法。因为不对数据做任何假设
  • 有一些算法天生是高偏差算法。如线性回归(用一条直线去拟合一条曲线,导致整体预测结果都距离真实数据查很大,偏差非常大)
  • 参数学习通常都是高偏差的算法。因为对数据具有极强的假设
  • 大多数算法具有相应的参数,可以调整偏差和方差
  • 如KNN中的k,线性回归中使用多项式回归
  • 偏差和方差是互相矛盾的。降低方差会提高偏差,降低偏差会提高方差

机器学习的主要调整来源于方差(这是站在算法的角度上,而不是问题的角度上,比如对金融市场的理解,很多人尝试用历史的数据预测未来的金融走势,这样的尝试通常都不太理想。很有可能因为历史的金融趋势不能很好的反应未来的走向,这种预测方法本身带来的非常大的偏差)换句话说,我们很容易让模型变的很复杂,从而降低模型的偏差,但是由于这样的模型的方差非常的大,最终也没有很好的性能。

解决高方差的通常手段:

1.降低模型复杂度 2.减少数据维度;降噪 3.增加样本数(模型太过复杂,模型中的参数非常多,而样本数不足以支撑计算出这么复杂的参数) 4.使用验证集 5.模型正则化

image.png


7.模型正则化-Regularization

1.什么是模型正则化

下图是我们之前使用多项式回归过拟合一个样本的例子,可以看到这条模型曲线非常的弯曲,而且非常的陡峭,可以想象这条曲线的一些θ系数会非常的大。 模型正则化需要做的事情就是限制这些系数的大小

image.png

模型正则化基本原理

image.png

一些需要注意的细节:

  1. 对于θ的求和i是从1到n,没有将θ0加进去,因为他不是任意一项的系数,他只是一个截距,决定了整个曲线的高低,但是不决定曲线每一部分的陡峭和缓和
  2. θ求和的系数二分之一是一个惯例,加不加都可以,加上的原因是因为,将来对θ2>求导的时候可以抵消系数2,方便计算。不要也是可以的
  3. α实际上是一个超参数,代表在我们模型正则化下新的损失函数中,我们要让每一个θ尽可能的小,小的程度占我们整个损失函数的多少,如果α等于0,相当于没有正则化;如果α是正无穷的话,那么我们主要的优化任务就是让每一个θ尽可能的小

岭回归 Ridge Regression

image.png

2.编程实现岭回归

import numpy as np
import matplotlib.pyplot as plt

# 模型样本
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)

# 绘制样本曲线
plt.scatter(x,y)

<matplotlib.collections.PathCollection at 0x1a159a6c88>

image.png

y.shape

(100,)

from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline

# 定义多项式回归函数
def PolynomialRegression(degree):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("lin_reg",LinearRegression())
])
from sklearn.model_selection import train_test_split

# 分割数据集
np.random.seed(666)
X_train,X_test,y_train,y_test = train_test_split(X,y)
from sklearn.metrics import mean_squared_error

# 多项式回归对样本进行训练,使用20个维度
poly20_reg = PolynomialRegression(20)
poly20_reg.fit(X_train,y_train)

y20_predict = poly20_reg.predict(X_test)
mean_squared_error(y_test,y20_predict)

167.94010860151894

# 定义绘图模型
def plot_module(module):
X_plot = np.linspace(-3,3,100).reshape(100,1)
y_plot = module.predict(X_plot)

plt.scatter(x,y)
plt.plot(X_plot[:,0],y_plot,color='r')
plt.axis([-3,3,0,6])
# 绘制模型曲线--过拟合(非常的完全,两段有极端的情况)
plot_module(poly20_reg)

image.png

使用岭回归

from sklearn.linear_model import Ridge

def RidgeRegression(degree,alpha):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scaler",StandardScaler()),
("ridge_reg",Ridge(alpha=alpha))
])
# 注意alpha后面的参数是所有theta的平方和,而对于多项式回归来说,岭回归之前得到的θ都非常大
# 所以为了限制让他们比较小,我们前面系数可以取的小一些
ridge1_reg = RidgeRegression(degree=20,alpha=0.00001)
ridge1_reg.fit(X_train,y_train)
ridge1_predict = ridge1_reg.predict(X_test)
mean_squared_error(y_test,ridge1_predict)

1.387437803144217

# 通过使用岭回归,使得我们的均方误差小了非常多,曲线也缓和了非常多
plot_module(ridge1_reg)

image.png

ridge2_reg = RidgeRegression(degree=20,alpha=1)
ridge2_reg.fit(X_train,y_train)
ridge2_predict = ridge2_reg.predict(X_test)
mean_squared_error(y_test,ridge2_predict)

1.1888759304218461

# 让ridge2_reg 的alpha值等于1,均差误差更加的缩小,并且曲线越来越趋近于一根倾斜的直线
plot_module(ridge2_reg)

image.png

ridge3_reg = RidgeRegression(degree=20,alpha=100)
ridge3_reg.fit(X_train,y_train)
ridge3_predict = ridge3_reg.predict(X_test)
mean_squared_error(y_test,ridge3_predict)

1.31964561130862

# 得到的误差依然是比较小,但是比之前的1.18大了些,说明正则化做的有些过头了
plot_module(ridge3_reg)

image.png

ridge4_reg = RidgeRegression(degree=20,alpha=100000)
ridge4_reg.fit(X_train,y_train)
ridge4_predict = ridge4_reg.predict(X_test)
mean_squared_error(y_test,ridge4_predict)
# 当alpha非常大,我们的模型实际上相当于就是在优化θ的平方和这一项,使得其最小(因为MSE的部分相对非常小)
# 而使得θ的平方和最小,就是使得每一个θ都趋近于0,这个时候曲线就趋近于一根直线了
plot_module(ridge4_reg)

image.png


8.LASSO

使用|θ|代替θ2来标示θ的大小

image.png

Selection Operator -- 选择运算符

LASSO回归有一些选择的功能

image.png

1. 实际编程(准备代码参考上一节岭回归)

from sklearn.linear_model import Lasso

def LassoRegression(degree,alpha):
return Pipeline([
("poly",PolynomialFeatures(degree=degree)),
("std_scatter",StandardScaler()),
("lasso_reg",Lasso(alpha=alpha))
])
# 这里穿的alpha起始值比岭回归的时候大了很多,是由于现在是绝对值
lasso1_reg = LassoRegression(degree=20,alpha=0.01)
lasso1_reg.fit(X_train,y_train)
lasso1_predict = lasso1_reg.predict(X_test)
mean_squared_error(lasso1_predict,y_test)

1.1496080843259968

plot_module(lasso1_reg)

image.png

# 增大alpha继续试验
lasso2_reg = LassoRegression(degree=20,alpha=0.1)
lasso2_reg.fit(X_train,y_train)
lasso2_predict = lasso2_reg.predict(X_test)
mean_squared_error(lasso2_predict,y_test)

1.1213911351818648

# 非常接近一根直线
plot_module(lasso2_reg)

image.png

# 增大alpha继续试验
lasso3_reg = LassoRegression(degree=20,alpha=1)
lasso3_reg.fit(X_train,y_train)
lasso3_predict = lasso3_reg.predict(X_test)
mean_squared_error(lasso3_predict,y_test)

1.8408939659515595

# alpha=1的时候正则化已经过头了
plot_module(lasso3_reg)

image.png

2. 总结Ridge和Lasso

image.png

α=100的时候,使用Ridge的得到的模型曲线依旧是一根曲线,事实上,使用Ridge很难得到一根倾斜的直线,他一直是弯曲的形状

但是使用LASSO的时候,当α=0.1,虽然得到的依然是一根曲线,但是他显然比Radge的程度更低,更像一根直线

这是因为LASSO趋向于使得一部分theta值为0(而不是很小的值),所以可以作为特征选择用,LASSO的最后两个字母SO就是Selection Operator的首字母缩写 使用LASSO的过程如果某一项θ等于0了,就说明LASSO Regression认为这个θ对应的特征是没有用的,剩下的那些不等于0的θ就说明LASSO Regression认为对应的这些特征有用,所以他可以当做特征选择用


当使用Ridge的时候,当α趋近与无穷大,那么使用梯度下降法的J(θ)的导数如下图,J(θ)向0趋近的过程中,每个θ都是有值的

image.png

但是LASSO不同,在LASSO的损失函数中,如果我们让α趋近于无穷,只看后面一部分的话,那么后面一部分的绝对值实际上是不可导的,我们可以使用一种sign函数刻画一下绝对值导数,如下图。那么这个时候,同样在J(θ)向0趋近的过程中,他会先走到θ等于0的y轴位置,然后再沿着y轴往下向零点的方向走

image.png

这也说明了Ridge为什么叫岭回归,因为他更像是翻山越岭一样,在梯度下降法中一点一点找坡度缓的方向前进。而LASSO的路径就比较规则,会在训练的过程中碰到一些轴使得某些θ为0。

所以从计算准确度上来说,我们应该更加倾向于Ridge,但是如果我们的维度比较多,样本非常大(比如多项式回归时degree=100)

9.L1,L2和弹性网络

Ridge和LASSO都是在损失函数中添加一项,来调节θ的值使其尽可能的小,使得我们的模型泛化能力更好一些


在机器学习领域中,我们会发明不同的名词来描述不同的标准,比如用Ridge和LASSO来衡量正则化的这一项;MSE和MAE用来衡量回归结果的好坏,欧拉距离和曼哈顿距离用来衡量两点之间的距离。但是他们背后的数学思想是非常的类似的,表达出的数学含义也是一致的。只不过应用到不同的场景中产生了不同的效果

image.png

1.L1,L2正则

对明克夫斯基距离进一步泛化

image.png

对任意一个维度X,我们都可以求这样一个值,他的每一个值x都对他的p次方进行求和,再开p次方根。通常将这个式子成为LP范数。 当P=1的时候就是L1范数(曼哈顿距离|Ridge Regression),当P=2的时候就是 L2范数(欧拉距离|LASSO Regression)

image.png

注:有了L1,L2正则项,我们就可以进一步得到LN正则项,虽然实际应用中我们的n不会超过2,但是在数学推导中是有意义的

2.L0正则项

我们希望让θ的个数尽量小,描述的是非零θ元素的个数。我们用这样的方式来限制θ的数量尽可能的小,进而来限制我们的曲线不要太抖 不过实际上我们是很少使用L0正则的,因为L0正则的优化是一个NP难的问题,我们不能使用诸如梯度下降法甚至数学公式来找到一个最优解。 他是一个离散的值,我们需要穷举所有θ的值来找出哪些θ需要,哪些不需要。实际上可以用L1正则(LASS0 Regression)来替代,从而达到选择去掉一些θ的过程

image.png

3.弹性网 Elastic NET

在损失函数下,添加上一个L1正则项和一个L2正则项,并引入一个参数r来表示他们之间的比例。同时结合了岭回归和LASSO回归的优势

image.png

实际应用中,通常应该先尝试一下岭回归(如果计算能力足够的话)。但是如果θ数量太大的话,消耗计算资源可能非常大,而LASSO由于有的时候急于把一些θ化为0,可能会导致得到的偏差比价大。这个时候需要使用弹性网

回忆小批量梯度下降法也是将随机梯度下降法和批量梯度下降法结合到了一起。在机器学习领域中,经常使用这种方式来创造出一些新的方法,这些方法虽然名词非常的酷,但是他们背后的意义是非常简单的

模型泛化的一个举例。我们在考试前会做很多练习题。我们做练习题不是为了把全部的练习题(训练数据集)都得到满分,而是为了在最后的那一场考试(真实数据)中得到满分

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.多项式回归简介
  • 2.编程实验多项式回归
    • 1.模拟多项式回归的数据集
      • 2.使用线性回归拟合
        • 3.解决方案,添加一个特征
          • 3.总结
            • 1.使用scikit-learn中的多项式对数据进行预处理
              • 2.调用LinearRegression对X2进行预测
                • 3.关于PolynomialFeatures
                  • 3.Pipline
                    • 1.什么是过拟合和欠拟合
                      • 2.为什么要使用训练数据集和测试数据集
                        • 1. 什么是学习曲线
                          • 2.实际编程实现学习曲线
                            • 3.总结
                              • 1.交叉验证 Cross Validation
                                • 2.编程实现
                                  • 训练train_test_spilt
                                  • 使用交叉验证
                                  • 回顾网格搜素
                                • 3.总结
                                  • 偏差
                                  • 方差
                                  • 解决高方差的通常手段:
                                • 1.什么是模型正则化
                                  • 模型正则化基本原理
                                  • 岭回归 Ridge Regression
                                • 2.编程实现岭回归
                                  • 使用岭回归
                                  • 使用|θ|代替θ2来标示θ的大小
                                  • Selection Operator -- 选择运算符
                                • 1. 实际编程(准备代码参考上一节岭回归)
                                  • 2. 总结Ridge和Lasso
                                    • 所以从计算准确度上来说,我们应该更加倾向于Ridge,但是如果我们的维度比较多,样本非常大(比如多项式回归时degree=100)
                                      • 1.L1,L2正则
                                        • 2.L0正则项
                                          • 3.弹性网 Elastic NET
                                            • 模型泛化的一个举例。我们在考试前会做很多练习题。我们做练习题不是为了把全部的练习题(训练数据集)都得到满分,而是为了在最后的那一场考试(真实数据)中得到满分
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档