多项式回归与模型泛化

之前总结的线性回归都是在特征集每个特征项数为1的情况下进行求解,即y=θ0 * X0 + θ1 * X1 + ... + θn * Xn,所有特征都是一次项。实际情况下,样本数据可能并不能很好的用这种一次项表达式进行回归。举个例子

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
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = y + noise

plt.scatter(X, y)
plt.show()

给定这样的数据集,以y = 0.5 * X^2 + X + 2加入噪声生成,其图像



当时用线性回归获取回归方程,并用预测结果对比实际情况,得到

'''普通线性回归'''
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()

红色直线是通过回归方程得到的所有样本预测值连成的直线,可以看出,一次项式的回归曲线不能很好的完成预测。
此时在X样本集中添加X^2作为另一个特征,并使用线性回归获得回归函数。

'''对原始数据增加X^2项的多项式特征'''
#添加X^2项作为特征
X_new = np.hstack([X, X**2])
lin_reg2 = LinearRegression()
#将新的特征集合进行线性回归
lin_reg2.fit(X_new, y)
y_predict2 = lin_reg2.predict(X_new)
#绘制回归曲线
plt.scatter(X, y)
plt.plot(np.sort(x), y_predict2[np.argsort(x)], color='r')
plt.show()

加入特征平方项作为新的特征后,得到新的回归曲线明显更好的贴合数据。这种向特征集中加入多次项产生新的特征集并训练模型的方式称为多项式回归。

sklearn中的多项式回归

线性回归在sklearn中被封装在linear_model包下的LinearRegression中。但LinearRegression不支持生成含有多项式的特征集,需要使用preprocessing包下的PolynomialFeatures进行生成。

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

'''创建数据'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + 2 + noise

plt.scatter(X, y)
plt.show()

'''使用sklearn中的PolynomialFeatures进行多项式回归'''
poly = PolynomialFeatures(degree=2)
poly.fit(X)
#获得PolynomialFeatures得到的多项式
X_new = poly.transform(X)
#使用线性回归对X_new进行回归
lin_reg = LinearRegression()
lin_reg.fit(X_new, y)
#根据线性回归得到预测值
y_predect = lin_reg.predict(X_new)
#画图
plt.scatter(X, y)
plt.plot(np.sort(x), y_predect[np.argsort(x)], color='r')
plt.show()

创建PolynomialFeatures对象时,传入一个degree参数,这个参数的意思是特征最高次项的项数,这里传入2表示最高产生特征数据平方项。得到回归曲线


#查看得到的多项式矩阵
print(X_new)
#查看回归得到的系数
print('X0到X2的系数分别是 %s' % lin_reg.coef_)
print('求得回归曲线截距为 %s' % lin_reg.intercept_)

coef_表示每一项的系数,intercept_表示截距,即y = ax + b中的b。

X0到X2的系数分别是 [[0.         1.07913561 0.58108989]]
求得回归曲线截距为 [1.8117245]

可以看到平方项的系数0.58接近0.5,一次项的系数约为1,截距因为加入噪声约为1.8117245,基本符合创造数据的方式。
假设数据有两个特征时,对其进行多项式转换得到的新的数据集什么样?

#假设数据有两个特征,查看PolynomialFeature生成的多项式矩阵
#创建特征数据集,一个5 * 2的矩阵
x = np.arange(1, 11)
X = x.reshape(-1, 2)
poly = PolynomialFeatures(degree=2)
X_new = poly.fit_transform(X)
print('X_new的大小为: ', X_new.shape)
print(X_new)

这里创建一个5 * 2的矩阵,PolynomialFeatures的degree传入2.打印得到的新特征矩阵。

X_new的大小为:  (5, 6)
[[  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.]]

新矩阵的大小是5 * 6,多了4列。第一列全是1,这是线性回归的默认操作将数据集第一列全部设为1,作为数据的X0。第2、3列是原始数据。第4列是第2列每个数的平方值,第5列是第2列和第3列的乘积,第6列则是第3列的平方值。
如果是degree取3的情况呢?

poly = PolynomialFeatures(degree=3)
X_new = poly.fit_transform(X)
print('X_new的大小为: ', X_new.shape)
print(X_new)

得到

X_new的大小为:  (5, 10)
[[   1.    1.    2.    1.    2.    4.    1.    2.    4.    8.]
 [   1.    3.    4.    9.   12.   16.   27.   36.   48.   64.]
 [   1.    5.    6.   25.   30.   36.  125.  150.  180.  216.]
 [   1.    7.    8.   49.   56.   64.  343.  392.  448.  512.]
 [   1.    9.   10.   81.   90.  100.  729.  810.  900. 1000.]]

第1列不用管,记第2列为X1,第3列为X2,第4列可以看出是X1^2,第5列X1 * X2,第6列X2^2,第7列 X1^3,第8列 X1^2 * X2,第9列X2^2 * X1,第10列X2^3。
可以看出,PolynomialFeatures会生成新特征矩阵的方式是生成每个特征从二次项到最高次项表达式和所有特征项数相加为最高次项的表达式。这样可以表达各个特征在不同次项上的系数,但可能造成模型的过拟合问题。

使用PipeLine简化过程

之前的步骤中,依次使用PolynomialFeatures对原始数据进行多项式扩充,再使用LinearRegression进行回归。sklearn提供了管道——PipeLine可以很方便的将所有步骤简化为一步进行。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

'''创建数据'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1, size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + 2 + noise

'''创建管道进行多项式回归'''
pipe = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=2)), #传入一个PolynomialFeatures
        ('std', StandardScaler()),  #StandardScaler对数据归一化
        ('lin_reg', LinearRegression()) #传入LinearRegression对象
    ]
)

print(pipe.fit(X, y))
pipe.fit(X, y)
y_predict = pipe.predict(X)

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

创造一个Pipeline实例,一次传入PolynomialFeatures等需要使用的对象,之后调用fit即可完成训练。也能得到相应的图像,类似之前的曲线。

过拟合和欠拟合

使用多项式回归可以使模型更加复杂,提高模型的准确度,但如果模型过于复杂,过于贴近训练数据,导致对真实数据的预测准确度并不高。通常使用学习曲线分别对训练数据和测试数据进行拟合,使用得到的预测数据均值平方差作为衡量标准,均值平方差越小预测越准确。举个例子:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

'''创建数据集'''
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X**2 + X + noise
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def plot_learning_curve(algo, X_train, X_test, y_train, y_test):
    '''
    绘制学习曲线函数
    :param algo:
    :param X_train:
    :param X_test:
    :param y_train:
    :param y_test:
    :return:
    '''
    #train_score记录不同项数下,模型对训练数据的准确度
    #test_score记录不同项数下,模型对测试数据的准确度
    train_score = []
    test_score = []
    #训练数据有75个,for循环通过训练数据个数从1到75,测试训练样本数对准确度的影响
    for i in range(1, 76):
        algo.fit(X_train[:i], y_train[:i])

        #分别对训练集和测试集预测
        y_train_predict = algo.predict(X_train[:i])
        y_test_predict = algo.predict(X_test)

        #记录不同i值下的准确度,使用mean_squared_error均值平方差衡量
        train_score.append(mean_squared_error(y_train[:i], y_train_predict))
        test_score.append(mean_squared_error(y_test, y_test_predict))

    #绘制学习曲线
    plt.plot([i for i in range(1, 76)], np.sqrt(train_score), label='train')
    plt.plot([i for i in range(1, 76)], np.sqrt(test_score), label='test')
    plt.legend()
    plt.axis([0, len(X_train) + 1, 0, 4])
    plt.show()

#单项式回归时的学习曲线
lin_reg = LinearRegression()
plot_learning_curve(lin_reg, X_train, X_test, y_train, y_test)

使用单项式回归得到的曲线



可以看到当训练样本数少时,预测的准确度会很差,方差很大,随着训练样本数增加,训练数据和测试数据的拟合程度趋于稳定,方差在1.5上下,且训练样本准确度好于测试样本。
接下来使用多项式回归进行测试,设degree为2

#degree=2时的学习曲线
pipe = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=2)),
        ('std', StandardScaler()),
        ('lin_reg', LinearRegression()),
    ]
)
plot_learning_curve(pipe, X_train, X_test, y_train, y_test)

得到学习曲线



从均值方差上看,二项式回归的方差明显低于单项式回归,说明这个模型好于上一个模型,因为创建的数据集就是一个二项式的基础上添加噪声而成的。
如果将项数提高会怎样,设degree=10进行测试。

#degree=10时的学习曲线
pipe2 = Pipeline(
    [
        ('poly', PolynomialFeatures(degree=10)),
        ('std', StandardScaler()),
        ('lin_reg', LinearRegression()),
    ]
)
plot_learning_curve(pipe2, X_train, X_test, y_train, y_test)

得到学习曲线



最终的方差确实降低了,但从训练集个数上看,只有当训练样本的个数到30+时才趋于稳定,相较前两幅图,观察训练样本的个数和训练样本的个数少的情况下测试集的预测准确度,明显不如二项式回归,因此,对于训练数据而言,这个模型可以比较好的拟合,但对于测试样本,这个拟合效果不如项数稍低时的效果。这就是所谓的过拟合状态。需要对模型进行范化。

交叉验证

通过训练数据得到的模型,如果在测试数据上偏差较大,这种情况称为对训练数据过拟合,但如果模型能很好的拟合测试样本,也不能说模型是准确的,因为可能存在测试数据的过拟合。为了得到可靠的模型,常用以下方法。
之前只是将数据分为训练数据和测试数据两部分,改进一下可以将数据分为三份,分别是训练数据、验证数据和测试数据。训练数据训练出最初的模型,之后使用验证数据进行验证和调参,最后使用测试数据测试。注意,只有测试数据不参与模型训练。 但这样的方式也存在一个问题,数据的划分存在随机性,怎么能够将随机性造成的影响降到最低,应当使用交叉验证的方式。



如图给出交叉验证的原理,将训练数据(可以理解为训练数据和验证数据的总称)分为k份,通过不同的组合方式训练出多个模型。通过对每个模型进行测试,计算当前参数下模型准确度的最高的进行作为最优模型,这个衡量标准通常选用准确度均值作为标准进行衡量。
加入现在训练一个KNN模型,通过for循环对参数进行调整

import numpy as np
import sklearn.datasets as dataset
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score

'''加载数据'''
data = dataset.load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.4,random_state=666)

'''通过for循环进行调试'''
best_score = -1
best_k = 0
best_p = 0
for k in range(2, 10):
    for p in range(1, 6):
        knn_clf = KNeighborsClassifier(n_neighbors=k, p=p, weights='distance')
        knn_clf.fit(X_train, y_train)
        score = knn_clf.score(X_test, y_test)

        if score > best_score:
            best_score = score
            best_k = k
            best_p = p
print('best K is ', best_k)
print('best p is ', best_p)
print('best score is ', best_score)

得到的最佳的k,p和准确度为

best K is  3
best p is  4
best score is  0.9860917941585535

使用交叉验证获取最佳参数

'''通过交叉验证进行调参'''
knn_clf = KNeighborsClassifier()
scores = cross_val_score(knn_clf, X_train, y_train)
print(scores)

#放入for循环中获取最佳参数
best_score = -1
best_k = 0
best_p = 0
for k in range(2, 10):
    for p in range(1, 6):
        knn_clf = KNeighborsClassifier(n_neighbors=k, p=p, weights='distance')
        knn_clf.fit(X_train, y_train)
        scores = cross_val_score(knn_clf, X_train, y_train)
        score = np.mean(scores)

        if score > best_score:
            best_score = score
            best_k = k
            best_p = p
print('best K is ', best_k)
print('best p is ', best_p)
print('best score is ', best_score)

交叉验证在sklearn中被封装在model_selection包下的cross_val_score中,传入参数有,通过第一个print()函数,得到的输出

[0.98895028 0.97777778 0.96629213]

可见当前数据被分为三份,通过交叉验证的方式得到的每个组合的准确度为0.98895028、0.97777778和0.96629213。3是默认的分割分数,可以通过cv参数调整数据被分割的份数。
之后通过for循环的方式指定KNN参数并使用交叉验证获取准确度平均值最高者为最优的模型得到结果

best K is  2
best p is  2
best score is  0.9823599874006478

和之前单纯用for循环的结果不同了,best score不如之前高,但由于交叉验证的准确度计算方式的不同和使用交叉验证尽可能避免随机性带来的影响,可能该模型的准确性在实际情况下会更好。

偏差与方差的权衡

在训练模型的过程中,存在两种误差,一种是偏差,一种是方差。



偏差表示实际数据和预测数据之间的差值,方差则反映了数据的离散程度。以这张图为例,红点表示预测值,左上角明显的样本比较集中,而且分布在预测值上,这种情况可以视为方差和偏差都很小,而右上的数据虽然围绕在预测值,但彼此之间并不集中,属于低偏差高方差的情况。左下角虽然集体偏离预测值,但样本集中,属于高偏差低方差。而右下距离预测值又远还又分散,就是偏差和方差都很高的情况。
训练模型的误差来源:


偏差

偏差的根本原因是对问题本身的假设不正确,比如对非线性的数据进行线性回归,训练出的模型多半不会有很好的效果。
这种情况也就是欠拟合。

方差

对于方差而言,一点点数据扰动可能会造成模型的巨大变化,根本原因就是模型过度复杂,太依赖数据,也就是所谓的过拟合。常见的比如KNN算法就是一种对方差特别敏感的模型。
在机器学习中,有一些算法本质上就是高方差的算法,非参数学习算法通常都是高方差算法,因为非参数学习不对数据样本进行假设。常见的KNN、决策树。
参数学习算法通常是高偏差的算法,对数据样本有较强的假设,例如线性回归等。
训练模型调参的过程实际上就是调整偏差和方差的过程。以KNN调整k个数为例,k越小说明模型越复杂,对应的方差也就越大,偏差越小,比如当k=1时,实际上就是比那种类型的个数多而已,但当数据发生很小的变化,分类模型可能就会变动。当k大时,这种情况就会明显减少。

方差和偏差是矛盾的,一般而言降低方差的同时,模型偏差会变大。而降低偏差的同时,方差会变大。
机器学习的主要挑战来自于方差。
解决高方差的常用手段

①降低模型复杂度
②减少数据维度和噪音
③增加样本数
④使用验证集

模型范化

模型泛化也叫正则化,目的是降低模型的复杂度。常见的方法有岭回归、Lasso正则化和Elastic Net正则化。
正则化项可以理解为是接在损失函数后的额外项,可以看做是损失函数的惩罚项,惩罚项对损失函数的某些参数进行限制。
回想线性回归中常见的以误差平方和的平均值作为代价函数的情景。



通过上面的代价函数(有时会使用1/2m作为系数)可以最终求得一个代价函数值最小的模型。为了提升模型的范化能力,线性回归中常用岭回归和Lasso回归对线性回归进行正则化操作。给出岭回归和Lasso回归的损失函数。



λ是正则化系数,它控制正则化项的占比,对模型范化而言很重要。
岭回归

为了更好的理解,自建创建一个数据集,并使用普通的多项式回归和岭回归进行对比。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import Ridge

'''创建数据集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

创建一个数据集,y = 0.5x + 3并加入噪声。接下来封装几个需要用到的函数。

def PolynomialRegression(degree):
    '''
    多项式回归,返回PipeLine对象
    :param degree: 
    :return: 
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    '''
    画出回归模型预测值形成的模型曲线
    :param model: 
    :return: 
    '''
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 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()

'''使用degree=20的多项式回归'''
poly = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

先使用degree=20的多项式回归,得到的模型必然是个过拟合的模型,因此对测试样本的预测效果必然不好,根据得到的结果也能看出来。

with linear regression the mse is  167.9401086326601

接下来使用岭回归进行模型范化,在sklearn的linear_model下的Ridge类中已经对岭回归进行封装。对之前定义的回归函数进行修改,得到岭回归适合的函数

'''岭回归'''
def RidgeRegression(degree, alpha):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', Ridge(alpha=alpha))
        ]
    )

多传入一个alpha参数,对应的就是之前岭回归损失函数的λ正则化系数。测试下λ取0.0001时的岭回归模型。

ridge1 = RidgeRegression(degree=20, alpha=0.0001)
ridge1.fit(X_train, y_train)
y_test_predict = ridge1.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with ridge regression and alpha = 0.0001 the mse is ', mse)
plot_model(ridge1)

得到的MSE和回归曲线

with ridge regression and alpha = 0.0001 the mse is  1.3233492753942047

从测试数据预测值的MSE可以看出,模型的准确度大幅提升,而且回归曲线也不那么突兀了。
如果调大正则化系数会有什么影响?

ridge2 = RidgeRegression(20, 1)
ridge2.fit(X_train, y_train)
y_test_predict = ridge2.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with ridge regression and alpha = 1 the mse is ', mse)
plot_model(ridge2)

将正则化系数改为1进行测试,得到

with ridge regression and alpha = 1 the mse is  1.1888759304218468

曲线上看又平滑了一些,但预测的效果不如之前但也相差不大,因此,正则化系数的变动对曲线的影响从形成的图像上就能看出来。

岭回归的原理

岭回归也称L2正则化。一般认为参数较小的模型简单,适应不同数据集的效果更好,可以一定程度上避免过拟合。
证明过程:


Lasso回归

另一种常用的模型范化方式称为Lasso回归,也称L1正则化。从之前给的损失函数看,它和岭回归的不同就是损失函数后面额外项不同。它不仅可以解决过拟合问题,而且可以在参数缩减过程中,将一些重复的没必要的参数直接缩减为零,也就是完全减掉了。这可以达到提取有用特征的作用。
记原始的代价函数为J0,L1正则化的代价函数J = J0 + λ∑|θi|,令L= λ∑|θi|,则代价函数J = J0 + L。此时回归的任务变为在L的约束下求出代价函数J的最小值对应的解。
假设在二维的情况下,L = |θ1| + |θ2|,将J0和L分别体现在坐标轴。



图中等值线就是J0,方形对应L。二维情况下,L有四个角,当维数增加,L将会升级为体,产生更多的角,J0与这些角接触的概率远远大于与L其他部分接触的概率。在角的位置上,会有系数为0,例如在这张图上,β1为0时可以与等值线有交点,这样的交点就是Lasso代价函数的最小值对应的解。因为β1为零,二维情况下,模型只需要考虑β2即可,简化了模型的难度。
当这种思想用在特征提取时,部分特征权重为0,即特征将不被考虑,以此达到特征提取的目的。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import Lasso

'''创建数据集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def PolynomialRegression(degree):
    '''
    普通多项式回归
    :param degree: 
    :return: 
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 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 = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

还是岭回归的数据,首先使用普通的多项式回归进行测试。在degree=20的情况下,还是过拟合严重。

with linear regression the mse is  167.9401086326601

接下来引入Lasso回归,首先定义Lasso回归的函数

def LassoRegression(degree, alpha):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', Lasso(alpha=alpha))
        ]
    )

'''正则化系数0.01'''
lasso1 = LassoRegression(20, 0.01)
lasso1.fit(X_train, y_train)
y_predict = lasso1.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 0.01 the mse is ', mse)
plot_model(lasso1)

sklearn的Lasso在linear_model下,先使用正则化系数0.01,得到的回归结果

with lasso regression and alpha = 0.01 the mse is  1.1496080843259968

从预测的准确性和生成的图像可以看出,模型已经得到很大的优化。
接下来测试正则化系数过大造成模型欠拟合。

'''正则化系数过大'''
lasso2 = LassoRegression(20, 1)
lasso2.fit(X_train, y_train)
y_predict = lasso2.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 1 the mse is ', mse)
plot_model(lasso2)

得到结果

with lasso regression and alpha = 1 the mse is  1.8408939659515595

当正则化系数过小,会导致模型拟合程度升高,范化能力下降。

'''正则化系数0.0001'''
lasso1 = LassoRegression(20, 0.0001)
lasso1.fit(X_train, y_train)
y_predict = lasso1.predict(X_test)
mse = mean_squared_error(y_predict, y_test)
print('with lasso regression and alpha = 0.0001 the mse is ', mse)
plot_model(lasso1)

得到结果

with lasso regression and alpha = 0.0001 the mse is  1.3773861509225418

从预测准确度到曲线的复杂程度都有所降低。
因此得出正则化系数与Lasso回归的关系。正则化系数越大,范化能力越强,正则化系数越小,范化能力越弱。

ElasticNet回归

ElasticNet回归结合了岭回归和Lasso回归。



r是一个0到1之间的权值,表示了岭回归和Lasso回归各自占的比重。r=0即为L1正则,r=1即为L2正则。
sklearn中在linear_model的ElasticNet类中对其进行了封装。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import  train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from sklearn.linear_model import ElasticNet

'''创建数据集'''
np.random.seed(42)
x = np.random.uniform(-3, 3, size=100)
X = x.reshape(-1, 1)
noise = np.random.normal(0, 1,size=100)
noise = noise.reshape(-1, 1)
y = 0.5 * X + 3 + noise
np.random.seed(666)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

def PolynomialRegression(degree):
    '''
    普通多项式回归
    :param degree:
    :return:
    '''
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', LinearRegression())
        ]
    )

def plot_model(model):
    X_plot = np.linspace(-3, 3, 100).reshape(-1, 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 = PolynomialRegression(degree=20)
poly.fit(X_train, y_train)
y_test_predict = poly.predict(X_test)
mse = mean_squared_error(y_test, y_test_predict)
print('with linear regression the mse is ', mse)
plot_model(poly)

def ElasticNetRegression(degree, alpha,l1_ratio):
    return Pipeline(
        [
            ('poly', PolynomialFeatures(degree=degree)),
            ('std', StandardScaler()),
            ('lin_reg', ElasticNet(alpha=alpha, l1_ratio=l1_ratio))
        ]
    )

'''ElasticNet回归'''
elastic_net = ElasticNetRegression(degree=20, alpha=0.01, l1_ratio=0.2)
elastic_net.fit(X_train, y_train)
y_test_predict = elastic_net.predict(X_test)
mse = mean_squared_error(y_test_predict, y_test)
print('with degree = 20, alpha = 0.01 and l1_ratio = 0.2 mse is ', mse)
plot_model(elastic_net)

得到

with degree = 20, alpha = 0.01 and l1_ratio = 0.2 mse is  1.2011937623442832

推荐阅读更多精彩内容