机器学习基础-6.多项式回归

一、多项式回归

1.思想

线性回归的局限性是只能应用于存在线性关系的数据中,但是在实际生活中,很多数据之间是非线性关系,虽然也可以用线性回归拟合非线性回归,但是效果将会很差,这时候就需要对线性回归模型进行改进,使之能够拟合非线性数据。

如图所示,左图为数据呈现出线性关系,用线性回归可以得到较好的拟合效果。右图数据呈现非线性关系,需要多项式回归模型。多项式回归是在线性回归基础上进行改进,相当于为样本再添加特征项。如右图所示,为样本添加一个x^2的特征项,可以较好地拟合非线性的数据。

2.代码实现

准备数据,并引入随机噪声。

import numpy as np
import matplotlib.pyplot as plt

x = np.random.uniform(-3,3, size=100) # 产生100个随机数
X = x.reshape(-1,1) #将x变成矩阵,1行1列的形式,接下来的代码要区分好X和x
y = 0.5 * x**2 +x +2 + np.random.normal(0,1,size=100) #后面引入噪声
plt.scatter(x,y)
plt.show()

首先采用线性回归的方式。

#首先用线性回归的方式,可以看出拟合效果很差
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()

现在采用多项式回归。

x2 = np.hstack([X,X**2]) #这里给样本X再引入1个特征项,现在的特征就有2个
lin_reg2 = LinearRegression()
lin_reg2.fit(x2,y)
y_predict2 = lin_reg2.predict(x2)
plt.scatter(x,y)
plt.plot(np.sort(x),y_predict2[np.argsort(x)],color='r') #绘制的时候要注意,因为x是无序的,为了画出如下图平滑的线条,需要先将x进行排序,y_predict2按照x从的大小的顺序进行取值,否则绘制出的如右下图。

lin_reg2.coef_ ,in_reg2.intercept_#检查拟合的系数,和原来预设的系数很接近

3.sklearn

sklearn中也封装了为样本添加特征项的工具,在数据预处理模块中。

from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(degree=2) #设置最多添加几次幂的特征项
poly.fit(X)
x2 = poly.transform(X)
#x2.shape 这个时候x2有三个特征项,因为在第1列加入1列1,并加入了x^2项
from sklearn.linear_model import LinearRegression #接下来的代码和线性回归一致
lin_reg2 = LinearRegression()
lin_reg2.fit(x2,y)
y_predict2 = lin_reg2.predict(x2)
plt.scatter(x,y)
# plt.plot(np.sort(x),y_predict2[np.argsort(x)],color='r')
plt.plot(x,y_predict2,color='r')
lin_reg2.coef_,lin_reg2.intercept_ #绘制的图像和预测所得的值和上面完全一致,唯一不同的是lin_reg2.coef_有3个系数,第一个系数值为0

当PolynomialFeatures(degree=3)时,对于原本有2个特征的样本将会产生共10个项的样本,具体的生成方式通过下图便可得出规律,这里不再总结。

4.pipeline

pipeline可以将相同的处理过程进行封装,简化代码。

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)

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

poly_reg = Pipeline(degree)([ #这里将三个处理步骤进行了封装,将数据传入poly_reg之后,将会智能地沿着该管道进行处理
    ("poly",PolynomialFeatures(degree=degree)),
    ("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') #效果和之前代码一致

二、过拟合和欠拟合

过拟合指的是数据进行过度训练,得出来的训练模型虽然对于训练数据来说,拟合地非常好,但是对于测试数据,将会有糟糕的表现,原因是过度地拟合将会把噪声也极大地引入。

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)
from sklearn.metrics import mean_squared_error #引入均方误差用来测试拟合的分数
from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

def PolynomialRegression(degree):
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",LinearRegression())    
    ])

现在不断改变degree值,观察拟合结果。

poly_reg = PolynomialRegression(degree=10)
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')

degree=30,可以发现虽然拟合该数据较好,但是其实不符合实际的数据走势。

mean_squared_error(y,y_predict) #随着degree的增大,均方误差逐渐减小,但是实际上过拟合地越严重,效果越差

欠拟合即数据训练的不够,拟合地太过简单了,不能完整表达数据关系,如下图。

三、模型泛化能力

在过拟合和欠拟合中,训练出来的模型对于新的数据样本,预测值和真实值都会有很大的偏差,这时候模型的泛化能力较差,即预测能力较差;如果数据被正确地训练,最终能够较好地为新的数据样本做预测,则可以说该模型的泛化能力较好。目标是训练出泛化能力好的模型,而非拟合训练数据好的模型。

一般将数据集分成训练数据集和测试数据集,在训练数据集上训练的出的模型对训练数据集也有很好的结果,这个时候就可以说模型的泛化能力较强。接下来,采用均方差来衡量模型的泛化能力。均方差越小,泛化能力越强。

1.线性回归

from sklearn.model_selection import train_test_split
x_train,x_test,y_train,y_test = train_test_split(X,y,random_state=666)
lin_reg = LinearRegression()
lin_reg.fit(x_train,y_train)
y_predict = lin_reg.predict(x_test)
mean_squared_error(y_predict,y_test)

最终得到的结果为:2.2199965269396573。

2.多项式回归

poly_reg = PolynomialRegression(degree=2)
poly_reg.fit(x_train,y_train)
y_predict = poly_reg.predict(x_test)
mean_squared_error(y_predict,y_test)

最终得到的结果为:0.8035641056297901。当degree=30,结果为16.91453591949335,泛化能力明显降低。

模型的准确率和模型的复杂程度一般有如下的关系,我们的目标即寻找到泛化能力最好的地方。

3.学习曲线

学习曲线能够表示出:随着训练样本的逐渐增多,算法训练出的模型的表现能力。

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

x_train,x_test,y_train,y_test = train_test_split(X,y,random_state=10)

train_score = [] #用来记录训练数据拟合的程度
test_score = [] #用来记录测试数据拟合的程度
for i in range(1,76):
    lin_reg = LinearRegression()
    lin_reg.fit(x_train[:i],y_train[:i])
    y_train_predict = lin_reg.predict(x_train[:i])
    y_test_predict = lin_reg.predict(x_test)
    train_score.append(mean_squared_error(y_train_predict,y_train[:i]))
    test_score.append(mean_squared_error(y_test_predict,y_test))
plt.plot([i for i in range(1,76)],np.sqrt(test_score),label='train')
plt.plot([i for i in range(1,76)],np.sqrt(train_score),label='test')
plt.legend()
plt.show()

从学习曲线可以发现,随着训练样本的不断增加,训练数据的均方根误差先上升后下降,测试数据的均方根误差不断上升,二者最后都区域一个稳定值。

对上面代码进行封装。

def plot_learning_curve(algo,x_train,x_test,y_train,y_test): #algo代表不同算法
    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])
        y_test_predict = algo.predict(x_test)
        train_score.append(mean_squared_error(y_train_predict,y_train[:i]))
        test_score.append(mean_squared_error(y_test_predict,y_test))
    plt.plot([i for i in range(1,76)],np.sqrt(test_score),label='train')
    plt.plot([i for i in range(1,76)],np.sqrt(train_score),label='test')
    plt.legend()
    plt.axis([0,len(x_train)+1,0,4])#限定坐标显示的范围,因为主要还是比较t_train和t_test相近的地方。
    plt.show()

对于线性回归算法。

plot_learning_curve(LinearRegression(),x_train,x_test,y_train,y_test)

对于多项式回归算法。

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
def PolynomialRegression(degree):
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",LinearRegression())    
    ])
poly2_reg = PolynomialRegression(degree=2)
plot_learning_curve(poly2_reg,x_train,x_test,y_train,y_test) #可以看到稳定的值比线性回归低

四、交叉验证

1.进行交叉验证的原因

如图所示。为了得到泛化能力强的模型,将数据分为测试数据和训练数据,当模型在测试数据也有较好的拟合效果时,则可以说模型的泛化能力较强。但是依然存在一个问题,一旦训练数据得出的模型在测试数据没有得到较好的效果,则需要调整测试数据的参数,换言之,其实就是为了使模型更好地拟合测试数据集,带来的问题就是该模型对于测试数据集将可能是个过拟合的。为此将数据集分成三部分,引入验证数据集。训练数据用来训练,验证数据用来验证模型好坏并进行超参数调整,用测试数据做为最终的评判标准。

     

但是这里同样存在可能过拟合验证数据的问题,因此需要进行随机处理,所以就引入了交叉验证。如下图所示,将训练数据切分成多份(3份为例),对切分的数据进行组合,整合成训练数据和验证数据,每个整合训练出不同的模型,将这3个模型的性能指标取平均值,作为衡量当前算法得到模型的性能指标。

2.代码实现

首先用split方式。

#首先使用传统的split方式
import numpy as np 
from sklearn import datasets
digits = datasets.load_digits()
x = digits.data
y = digits.target
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=10)
from sklearn.neighbors import KNeighborsClassifier
bestscore,bestp,bestk=0,0,0
for k in range(2,11):
    for p in range(1,6):
        knn = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
        knn.fit(x_train,y_train)
        score = knn.score(x_test,y_test)
        if bestscore<score:
            bestscore = score
            bestp = p
            bestk =k
print("best k =",bestk)
print("best p =",bestp)
print("best score",bestscore)

使用交叉验证方式。

from sklearn.model_selection import cross_val_score #导入函数
bestscore,bestp,bestk=0,0,0
for k in range(2,11):
    for p in range(1,6):
        knn = KNeighborsClassifier(weights='distance',n_neighbors=k,p=p)
        scores = cross_val_score(knn,x_train,y_train) #这里使用交叉验证,scores有三个值,需要求平均,可以再传入cv参数,默认为3份
        score = np.mean(scores)
        if bestscore<score:
            bestscore = score
            bestp = p
            bestk =k
print("best k =",bestk)
print("best p =",bestp)
print("best score",bestscore)

可以发现,这个和上面split求得的最佳值不太一样,上面用简单的split方式得到的可能仅仅是过拟合的模型。得到最佳参数后,现在就需要应用x_test数据进行验证。

bestknn = KNeighborsClassifier(weights="distance",n_neighbors=4,p=5)
bestknn.fit(x_train,y_train)
bestknn.score(x_test,y_test)

因此可以说,通过三交叉验证的方式,最终获得模型的准确率为98,可信度较高。

这里回顾下网格搜索。这里的CV即cross validation,交叉验证。

from sklearn.model_selection import GridSearchCV
param_grid = [  
    {  
         'weights': ['uniform'],  
       'n_neighbors':[i for i in range(1,11)]  
   },  
    {  
         'weights': ['distance'],  
       'n_neighbors': [i for i in range(1,11)],  
       'p': [i for i in range(1,6)]  
    }  
] 

交叉验证可以发展为留一法(loo-cv,leave-one-out cross validation)。即有m个样本,将m-1个样本作为训练数据,只留一个作为最终预测。这时候将会完全不受随机的影响,最接近模型真正的性能指标,但是缺点显而易见,计算量将会十分巨大。

五、偏差、方差权衡

偏差描述样本偏离实际值的情况,方差描述样本的分布疏密情况。下图中红色点为真值,蓝色点为样本点,描绘了不同偏差和方差分布情况。

模型的误差=方差+偏差+不可避免的误差(例如测量带来的精度损失、噪声等)

导致偏差的原因,可能是对模型的假设是错误的,例如用线性的假设去预测非线性数据,或者采用的特征和预测其实没有关系。在机器学习中,一般不会出现采取的特征和预测毫无相关的情况,事实上导致高偏差的原因主要是欠拟合。

导致方差的原因,通常是因为模型太过复杂,一点的数据抖动都将会影响到结果,过拟合是导致高方差的主要原因。

非参数学习通常都是高方差,因为不对数据进行任何的假设。例如KNN、决策树,高度依赖样本数据。

参数学习通常都是高偏差算法,因为对数据具有极强的假设,例如线性回归。

偏差和方差通常是相互矛盾的,降低偏差将会提高方差,降低方差将会提高偏差。在算法领域,主要的挑战来自方差。

解决的方法:降低模型复杂度、减少数据维度,降噪;增加样本数;使用验证集、模型正则化等。

六、模型正则化

1.岭回归正则化

图下图所示,当degree很大时,模型易出现过拟合,具体表现就是某些系数值θ,将会非常大,因此需要对模型正则化:限制参数的大小。

注意:模型正则化时,θ从1开始,而不是0开始,原因是θ0仅仅影响曲线的整体高度,不进行限制。α是个超参数,可以进行调整。这种限制θ参数大小的方式通常被称为岭回归(ridge regression)

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 + np.random.normal(0,1,size=100)
plt.scatter(x,y)
plt.show()

import numpy as np  
import matplotlib.pyplot as plt 

from sklearn.linear_model import LinearRegression
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

np.random.seed(666)
x_train,x_test,y_train,y_test = train_test_split(X,y,random_state=10)

def PolynomialRegression(degree):
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",LinearRegression())    
    ])
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()

首先使用多项式回归,可以看到出现了过拟合。

poly_reg = PolynomialRegression(degree=50)
poly_reg.fit(X,y)
plot_model(poly_reg)

接着使用加入岭回归正则化的多项式回归。

from sklearn.linear_model import Ridge
def RridgeRegression(degree,alpha): #修改管道
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",Ridge(alpha=alpha))    #加入岭回归
    ])
ridge1 = RridgeRegression(20,0.0001)
ridge1.fit(x_train,y_train)
y_test = ridge1.predict(x_test)
#mean_squared_error(y_test,x_test)
plot_model(ridge1)

修改alpha值,分别为1,100,100000。可以看出线条逐渐变得平滑,当alpha很大的时候,为了使目标函数小,所以会使系数趋近于0,因此会得出几乎平行的一条直线。

3.LASSO正则化

lasso的全拼为:least absolute shrinkage and selection operator regression。

4.lasso代码实现

from sklearn.linear_model import Lasso
def RridgeRegression(degree,alpha):
    return Pipeline([
        ("poly",PolynomialFeatures(degree=degree)),
        ("std_scaler",StandardScaler()),
        ("lin_reg",Lasso(alpha=alpha))    
    ])
ridge1 = RridgeRegression(20,0.1)
ridge1.fit(x_train,y_train)
y_test = ridge1.predict(x_test)
#mean_squared_error(y_test,x_test)
plot_model(ridge1)

绘制出的图像可以看出lasso回归拟合的图像更趋近于直线。lasso会趋向于使得一部分θ值变为0,所以可以作为特征选取,这就是selection operator的含义。下面简单介绍下其中的原因。

如图上图所示,对于岭回归,在求解目标函数最小值时,假设alpha趋近无穷,初始系数点为蓝色点,将会沿着红线趋于0,在这个过程中θ都是有值的。

而对于lasso回归,因为θ不可导,只能写成如上图所示的分段函数。

七、L1,L2和弹性网络

1.比较岭回归和lasso回归

这里回归下明科夫斯基距离。

  

将上面式子进行泛化,表达成如下形式,得出了L1,L2正则项,分别对应lasso和岭回归(只是没有进行开方,但是也称为L2)。

实际上还存在L0正则项,即希望θ尽可能少,一般很少使用。

2.弹性网

弹性网的意义比较简单,即将岭回归和lasso回归一起考虑,结合两个项的优点,通过r来调整两个项的比例。

  • 23
    点赞
  • 116
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值