sklearn中的线性回归大家族

1 概述

1.1 线性回归大家族

回归是一种应用广泛的预测建模技术,这种技术的核心在于预测的结果是连续型变量。决策树、随机森林、支持向量机的分类器等分类算法的预测标签是分类变量,多以{0,1}来表示,而无监督学习算法(如CPA、Kmeans)并不是求解标签,注意加以区别。回归算法源于统计学理论,他可能是机器学习算法中产生最早的算法之一,其在现实中的应用非常广泛,包括使用其他经济指标预测股票市场指数,根据喷射流的特征预测区域内的降水量,根据公司的广告花费预测总销售额等等,只要一切基于特征预测连续型变量的需求,都使用回归技术。
既然线性回归是源于统计分析,是结合机器学习与统计学的重要算法。通常来说,可以认为统计学注重先验,而机器学习注重结果,因此机器学习中不会提前为线性回归排除共线性等可能会影响模型的因素,反而会先建立模型以查看效果。模型确立之后,如果效果不好,就根据统计学的指导来排除可能影响模型的因素。
回归需求在现实中非常多,所以也就有各种各样的回归类算法。最著名的就是线性回归和逻辑回归,并衍生出了岭回归、Lasso、弹性网等。除此之外,还有众多分类算法改进后的回归,如回归树、随机森林的回归、支持向量回归、贝叶斯回归等,还有各种鲁棒的回归,如RANSAC、Theil-Sen估计、胡贝尔回归等等。考虑到回归问题在现实中的泛用性,回归家族是非常庞大的。
回归类算法的数学相对简单,通常理解线性回归可以有两种角度:矩阵的角度和代数的角度。本文使用矩阵方式(线性代数的方式)来解释回归算法。

1.2 sklearn中的线性回归

sklearn中的线性模型模块是linear_model,其中包含了多种多样的类和函数,本文主要设计:普通线性回归、多项式回归、岭回归、Lasso以及弹性网。

类/函数 含义
普通线性回归
linear_model.LinearRegression 使用普通最小二乘法的线性回归
岭回归
linear_model.Ridge 岭回归,一种将L2作为正则化工具的线性最小二乘回归
linear_model.RidgeCV 带交叉验证的岭回归
linear_model.RidgeClassifier 岭回归的分类器
linear_model.RidgeClassifierCV 带交叉验证的岭回归的分类器
linear_model.ridge_regression 【函数】用正态方程法求解回归
Lasso
linear_model.Lasso Lasso,使用L1作为正则化工具来训练的线性回归模型
linear_model.LassoCV 带交叉验证和正则化迭代路径的Lasso
linear_model.LassoLars 使用最小角度回归求解的Lasso
linear_model.LassoLarsCV 带交叉验证的使用最小角度回归求解的Lasso
linear_model.LassoLarsIC 使用BIC或AIC进行模型选择的,使用最小角度回归求解的Lasso
linear_model.MultiTaskLasso 使用L1/L2混合范数作为正则化工具训练的多标签Lasso
linear_model.MultiTaskLassoCV 使用L1/L2混合范数作为正则化工具训练的,带交叉验证的多标签Lasso
linear_model.lasso_path 【函数】用坐标下降计算Lasso路径
弹性网
linear_model.ElasticNet 弹性网,一种将L1和L2组合作为正则化工具的线性回归
linear_model.ElasticNetCV 带交叉验证和正则化迭代路径的弹性网
linear_model.MultiTaskElasticNet 多标签弹性网
linear_model.MultiTaskElasticNetCV 带交叉验证的多标签弹性网
linear_model.enet_path 【函数】用坐标下降法计算弹性网的路径
最小角度回归
linear_model.Lars 最小角度回归(Least Angle Regression,LAR)
linear_model.LarsCV 带交叉验证的最小角度回归模型
linear_model.lars_path 【函数】使用LARS算法计算最小角度回归路径或Lasso的路径
正交匹配追踪
linear_model.OrthogonalMatchingPursuit 正交匹配追踪模型(OMP)
linear_model.OrthogonalMatchingPursuitCV 交叉验证的政教匹配追踪模型(OMP)
linear_model.orthogonal_mp 【函数】正交匹配追踪(OMP)
linear_model.orthogonal_mp_gram 【函数】Gram正交匹配追踪(OMP)
贝叶斯回归
linear_model.ARDRegression 贝叶斯ARD回归。ARD是自动相关性确定回归(Automatic Relevance Determination Regression),是一种类似于最小二乘的,用来计算参数向量的数学方法
linear_model.BayesianRidge 贝叶斯岭回归
其他回归
linear_model.PassiveAggressiveClassifier 被动攻击性分类器
linear_model.PassiveAggressiveRegressor 被攻击性回归
linear_model.Perceptron 感知机
linear_model.RANSACRegressor RANSAC(RANdom SAmple Consensus)算法
linear_model.HuberRegressor 胡博回归,对异常值具有鲁棒性的一种线性回归模型
linear_model.SGDRegressor 通过最小化SDG的正则化损失函数来拟合线性模型
linear_model.TheilSenRegreesor Theil-Sen估计器,一种鲁棒的多元回归模型

2 多元线性回归LinearRegression

2.1 多元线性回归的基本原理

线性回归是机器学习中最简单的回归算法,多元线性回归指的是一个样本有多个特征的线性回归问题。对于有 n n n个特征的样本 i i i而言,其回归结果可写为:
y ^ i = w 0 + w 1 x i 1 + w 2 x i 2 + . . . + w n x i n \hat y_i = w_0+w_1x_{i1}+w_2x_{i2}+...+w_nx_{in} y^i=w0+w1xi1+w2xi2+...+wnxin
其中 w w w被统称为模型的参数, w 0 w_0 w0被称为截距(intercept), w 1 , . . . , w n w_1 ,...,w_n w1,...,wn被称为回归系数(regression coefficient),有时也使用 θ \theta θ或者 β \beta β来表示。其中 y y y是目标变量,也就是标签。 x i 1 , . . . , x i n x_{i1},...,x_{in} xi1,...,xin是样本 i i i上的不同特征。如果考虑有 m m m个样本,则 m m m个样本的回归结果可以被写作:
y ^ = w 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n \hat \textbf y = w_0+w_1\textbf x_1+w_2\textbf x_2+...+w_n\textbf x_n y^=w0+w1x1+w2x2+...+wnxn
其中 y \textbf y y是包含了 m m m个全部样本的回归结果的列向量。注意,这里通常使用粗体的小写字母来表示列向量,粗体的大写字母表示矩阵或者行列式。使用矩阵来表示这个方程,其中 w \textbf w w可以被看做是一个结构为(1,n)的列矩阵, X \textbf X X是一个结构为(m,n)的特征矩阵,则有:
sklearn中的线性回归大家族_第1张图片
y ^ = wX \hat \textbf y = \textbf w\textbf X y^=wX
线性回归的任务,就是构造一个预测函数来映射输入的特征矩阵 X \textbf X X和标签值 y \textbf y y的线性关系,这个预测函数也可能被写作 f ( x ) f(x) f(x) y w ( x ) y_w(x) yw(x)或者 h ( x ) h(x) h(x)等形式,但无论如何,这个预测函数的本质就是需要构建的模型,而构造预测函数的核心就是找出模型的参数向量 w \textbf w w
在逻辑回归和SVM中,先定义了损失函数,然后通过最小化损失函数或损失函数的某种变化来求解参数向量,以此将单纯的求解问题转化为一个最优化问题。在多元线性回归中,定义损失函数如下: ∑ i = 1 m ( y i − y ^ i ) 2 \sum_{i=1}^m(y_i-\hat y_i)^2 i=1m(yiy^i)2 ∑ i = 1 m ( y i − X i w ) 2 \sum_{i=1}^m(y_i-\textbf X_i\textbf w)^2 i=1m(yiXiw)2其中 y i y_i yi是样本 i i i对应的真实标签, y ^ i \hat y_i y^i也就是 X i w \textbf X_i\textbf w Xiw,是样本 i i i在一组参数 w \textbf w w下的预测标签。
首先,这个损失函数代表了向量 y − y ^ y-\hat y yy^的L2范式的平方结果,L2范式的本质就是欧式距离,即是两个向量上的每个点对应相减后的平方和再开平方,现在只实现了向量上每个点对应相减后的平方和,并没有开方,所以损失函数是L2范式,即欧氏距离的平方结果。
在这个平方结果下, y y y y ^ \hat y y^分别是真实标签和预测值,也就是说,这个损失函数是在计算真实标签与预测值之间的距离。因此,这个损失函数衡量了构造模型的预测结果和真实标签的差异,自然是希望预测结果和真实值差异越小越好,所以求解目标可以转化为: min ⁡ w ∣ ∣ y − Xw ∣ ∣ 2 2 \min_w||\textbf y-\textbf X\textbf w||_2^2 wminyXw22其中右下角的2表示向量 y − Xw \textbf y-\textbf X\textbf w yXw的L2范式,也就是损失函数所代表的含义。在L2范式上开平方,就是损失函数。这个式子也正是sklearn中类Linear_model.LinearRegression背后使用的损失函数,往往称这个式子为SSE(Sum of Squared Error,误差平方和)或者RSS(Residual Sum of Squares,残差平方和)。在sklearn所有官方文档和网页上,都称之为RSS残差平方和

2.2 最小二乘法求解多元线性回归的参数

问题转换成了求解让RSS最小化的参数向量 w \textbf w w这种通过最小化真实值和预测值之间的RSS来求解参数的方法叫做最小二乘法。最小二乘法的过程相对简单,求解极值的第一步往往是求解一阶导数并让一阶导数等于0,因此要在残差平方和RSS上对参数向量 w \textbf w w求导。其中涉及的矩阵求导公式,可以查看:https://www.math.uwaterloo.ca/~hwolkowi/matrixcookbook.pdf
接下来,对 w \textbf w w求导:
∂ R S S ∂ w = ∂ ∣ ∣ y − Xw ∣ ∣ 2 2 ∂ w = ∂ ( y − Xw ) T ( y − Xw ) ∂ w \frac{\partial RSS}{\partial \textbf w} = \frac{\partial ||\textbf y-\textbf X\textbf w||_2^2}{\partial \textbf w}=\frac{\partial (\textbf y-\textbf X\textbf w)^T(\textbf y-\textbf X\textbf w)}{\partial \textbf w} wRSS=wyXw22=w(yXw)T(yXw)
( A − B ) T = A T − B T (A-B)^T = A^T-B^T (AB)T=ATBT并且 ( A B ) T = B T ∗ A T (AB)^T = B^T*A^T (AB)T=BTAT
∂ R S S ∂ w = ∂ ( y T − w T X T ) T ( y − Xw ) ∂ w = ∂ ( y T y − w T X T y − y T Xw + w T X T Xw ) ∂ w = f r a c ∂ y T y − ∂ w T X T y − ∂ y T Xw + ∂ w T X T Xw ∂ w \frac{\partial RSS}{\partial \textbf w} = \frac{\partial (\textbf y^T-\textbf w^T\textbf X^T)^T(\textbf y-\textbf X\textbf w)}{\partial \textbf w} = \frac{\partial (\textbf y^T\textbf y-\textbf w^T\textbf X^T\textbf y-\textbf y^T\textbf X\textbf w+\textbf w^T\textbf X^T\textbf X\textbf w)}{\partial \textbf w} = frac{\partial \textbf y^T\textbf y-\partial \textbf w^T\textbf X^T\textbf y-\partial \textbf y^T\textbf X\textbf w+\partial \textbf w^T\textbf X^T\textbf X\textbf w}{\partial \textbf w} wRSS=w(yTwTXT)T(yXw)=w(yTywTXTyyTXw+wTXTXw)=fracyTywTXTyyTXw+wTXTXww
由于矩阵求导中, a a a为常数,有如下规则:
∂ a ∂ A = 0 , ∂ A T B T C ∂ A = B T C , ∂ C T B A ∂ A = B T C , ∂ A T B A ∂ A = ( B + B T ) A \frac{\partial a}{\partial A} = 0,\frac{\partial A^TB^TC}{\partial A} = B^TC,\frac{\partial C^TBA}{\partial A} = B^TC,\frac{\partial A^TBA}{\partial A} = (B+B^T)A Aa=0,AATBTC=BTC,ACTBA=BTC,AATBA=(B+BT)A
∂ R S S ∂ w = 0 − X T y − X T y + 2 X T Xw = 2 ( X T Xw − X T y ) \frac{\partial RSS}{\partial \textbf w} = 0-\textbf X^T\textbf y-\textbf X^T\textbf y+2\textbf X^T\textbf X\textbf w =2(\textbf X^T\textbf X\textbf w-\textbf X^T\textbf y) wRSS=0XTyXTy+2XTXw=2(XTXwXTy)
让求导后的一阶导数为0:
X T Xw − X T y = 0 \textbf X^T\textbf X\textbf w-\textbf X^T\textbf y = 0 XTXwXTy=0
X T Xw = X T y \textbf X^T\textbf X\textbf w=\textbf X^T\textbf y XTXw=XTy
左乘一个 ( X T X ) − 1 (\textbf X^T\textbf X)^{-1} (XTX)1,则有:
w = ( X T X ) − 1 X T y \textbf w = (\textbf X^T\textbf X)^{-1}\textbf X^T\textbf y w=(XTX)1XTy
w \textbf w w留在等式的左边,其他与特征矩阵有关的部分放到等式的右边,这样就得到 w \textbf w w的最优解。因此只需要左乘一个 X T X \textbf X^T\textbf X XTX的逆矩阵就可以这里,逆矩阵存在的充分必要条件是特征矩阵不存在多重共线性
假设矩阵的逆是存在的,此时 w \textbf w w就是参数的最优解。求解出这个参数向量,就可以解出 Xw \textbf X\textbf w Xw,也就能够计算出预测值 y ^ \hat \textbf y y^
除了多元线性回归的推导之外,还需要提到一个在推导过程中不曾被体现的问题,即在统计学中,使用最小二乘法来求解线性回归的方法是一种“无偏估计”的方法,这种无偏估计要求因变量,也就是标签的分布,必须服从正态分布。那么必须进行正态化处理(如取对数、或者使用特征工程中的类QuantileTransformer或者PowerTransformer)。在机器学习中,会优先考虑模型的效果,如果模型效果不好,那可能考虑改变因变量的分布。

2.3 linear_model.LinearRegression

sklearn.linear_model.LinearRegression(fit_intercept = True, normalize = False, copy_X = True, n_jobs = None)

参数 含义
fit_intercept 布尔值,可不填,默认为True。是否计算此模型的截距,如果设置为False,则不会计算截距。
normalize 布尔值,可不填,默认为False。当fit_intercept设置为False时,将忽略此参数。如果为True,则特征矩阵X在进入回归前将会被减去均值(中心化)并除以L2范式(缩放)。如果希望进行标准化,需要在fit数据之前使用preprocessing模块中的标准化专用类StandardScaler。
copy_X 布尔值,可不填,默认为True。如果为真,将在X.copy()上进行操作,否则原本的特征矩阵X可能被线性回归影响并覆盖。
n_jobs 整数或None,可不填,默认为None。用于计算的作业数。只在多标签的回归和数据量足够大的时候才生效。除非None在joblib.parallel_backend上下文中,否则None统一表示为1。如果输入-1,则表示使用全部的CPU来进行计算。

线性回归的类仅有四个参数,并且这些参数中并没有一个是必填的,更没有对模型有不可替代作用的参数。这说明,线性回归的性能往往取决于数据本身,而并非调参能力,线性回归也因此对数据有着很高的要求。而现实中大部分连续型变量之间都存在这或多或少的线性联系,因此线性回归虽然简单,但是很强大。sklearn中的线性回归可以处理多标签问题,只需要在fit的时候输入多维度标签就可以。

#导入需要的模块和库
from sklearn.linear_model import LinearRegression as LR
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.datasets import fetch_california_housing as fch#加利福尼亚房屋价值数据集
import pandas as pd

#导入数据,探索数据
housevalue = fch()#需要下载
X = pd.DataFrame(housevalue.data)#放入DataFrame中便于查看
y = housevalue.target
X.shape
#结果:(20640, 8)
y.shape
#结果:(20640,)
X.head()

sklearn中的线性回归大家族_第2张图片

housevalue.feature_names#特征名字
'''
'MedInc':该街区住户的收入中位数
'HouseAge':该街区房屋使用年代的中位数
'AveRooms':该街区平均的房间数目
'AveBedrms':该街区平均的卧室数目
'Population':街区人口
'AveOccup':平均入住率
'Latitude':街区的维度
'Longitude':街区的经度
'''
X.columns = housevalue.feature_names
X.head()

sklearn中的线性回归大家族_第3张图片

xtrain,xtest,ytrain,ytest = train_test_split(X,y,test_size = 0.3,random_state = 420)
#恢复索引
for i in [xtrain,xtest]:
    i.index = range(i.shape[0])
xtrain.shape
#结果:(14448, 8)
#如果希望进行数据标准化,要先用训练集训练(fit)标准化的类,然后用训练好的类分别转化(transform)训练集和测试集
#建模
reg = LR().fit(xtrain,ytrain)
yhat = reg.predict(xtest)#预测yhat
yhat
#结果:array([1.51384887, 0.46566247, 2.2567733 , ..., 2.11885803, 1.76968187,   0.73219077])
#探索建好的模型
reg.coef_
#结果:array([ 4.37358931e-01,  1.02112683e-02, -1.07807216e-01,  6.26433828e-01,5.21612535e-07, -3.34850965e-03, -4.13095938e-01, -4.26210954e-01])
reg.intercept_
#结果:-36.256893229203946
[*zip(xtrain.columns,reg.coef_)]
'''
结果:
[('MedInc', 0.43735893059684),
 ('HouseAge', 0.010211268294493828),
 ('AveRooms', -0.10780721617317682),
 ('AveBedrms', 0.6264338275363777),
 ('Population', 5.21612535346952e-07),
 ('AveOccup', -0.003348509646333501),
 ('Latitude', -0.41309593789477195),
 ('Longitude', -0.4262109536208474)]
'''
属性 含义
coef_ 数组,形状为(n_features,)或者(n_targets,n_features)。线性回归方程中估计出的系数。如果在fit中传递多个标签(当y为二维或以上的时候),则返回的系数是形状为(n_targets,n_features)的二维数组,而如果仅传递一个标签,则返回的系数是长度为(n_features,)的一维数组。
intercept_ 数组,线性回归中的截距项。

建模的过程在sklearn中非常简单,但模型的效果需要看看多元线性回归的模型评估指标。

3 回归类的模型评估指标

回归类算法的模型评估一直都是回归算法中的一个难点,但不像无监督学习算法中的轮廓系数等评估指标,回归类与分类型算法的模型评估其实是相似的法则,即找出真实标签与预测值的差异。只不过在分类型算法中,这种差异只有一种角度来评判,那就是是否则预测到了正确的分类,而在回归类算法中,有两种不同的角度来看待回归的效果:(1)是否预测到了正确的数值;(2)是否拟合到了足够的信息。这两种角度分别对应着不同的模型评估指标。

3.1 是否预测了正确的数值

RSS残差平方和的本质是预测值与真实值之间的差异,也就是从第一种角度来评估回归的效果。所以RSS即是损失函数,也是回归类模型的模型评估指标之一。但是,RSS有着致命的缺点:它是一个无界的和,可以无限地大。只知道要求解最小的RSS,从RSS的公式来看,他不能为负,所以RSS越接近0越好,但是没有“多小才算好、多接近0才算好”的概念。为了应对这种状况,sklearn中使用RSS的变体,均方误差MSE(mean squared error)来衡量预测值与真实值的差异: M S E = 1 m ∑ i = 1 m ( y i − y ^ i ) 2 MSE = \frac{1}{m}\sum_{i=1}^m(y_i-\hat y_i)^2 MSE=m1i=1m(yiy^i)2均方误差本质是在RSS的基础上除以样本总量,得到了每个样本量上的平均误差。将平均误差与标签的取值范围进行比较,就可以获得一个较为可靠的评估依据。在sklearn中,有两种方式调用这个评估指标,一种是使用sklearn专用的模型评估模块metrics里的类mean_squared_error,另一种是调用交叉验证的leukemiacross_val_score并使用里面的scoring参数来设置使用均方误差。

from sklearn.metrics import mean_squared_error as MSE
MSE(yhat,ytest)
#结果:0.5309012639324575
y.max()
#结果:5.00001
y.min()
#结果:0.14999
#cross_val_score(reg,X,y,cv = 10,scoring = "mean_squared_error")
#运行会报错ValueError: 'mean_squared_error' is not a valid scoring value. Use sorted(sklearn.metrics.SCORERS.keys()) to get valid options.可以发现是scoring参数有问题,可以查看scoring有哪些可填项
import sklearn
sorted(sklearn.metrics.SCORERS.keys())
'''
结果:
['accuracy',
 'adjusted_mutual_info_score',
 'adjusted_rand_score',
 'average_precision',
 'balanced_accuracy',
 'completeness_score',
 'explained_variance',
 'f1',
 'f1_macro',
 'f1_micro',
 'f1_samples',
 'f1_weighted',
 'fowlkes_mallows_score',
 'homogeneity_score',
 'jaccard',
 'jaccard_macro',
 'jaccard_micro',
 'jaccard_samples',
 'jaccard_weighted',
 'max_error',
 'mutual_info_score',
 'neg_brier_score',
 'neg_log_loss',
 'neg_mean_absolute_error',
 'neg_mean_absolute_percentage_error',
 'neg_mean_gamma_deviance',
 'neg_mean_poisson_deviance',
 'neg_mean_squared_error',
 'neg_mean_squared_log_error',
 'neg_median_absolute_error',
 'neg_root_mean_squared_error',
 'normalized_mutual_info_score',
 'precision',
 'precision_macro',
 'precision_micro',
 'precision_samples',
 'precision_weighted',
 'r2',
 'rand_score',
 'recall',
 'recall_macro',
 'recall_micro',
 'recall_samples',
 'recall_weighted',
 'roc_auc',
 'roc_auc_ovo',
 'roc_auc_ovo_weighted',
 'roc_auc_ovr',
 'roc_auc_ovr_weighted',
 'top_k_accuracy',
 'v_measure_score']
'''
cross_val_score(reg,X,y,cv = 10,scoring = "neg_mean_squared_error")
#结果:array([-0.48922052, -0.43335865, -0.8864377 , -0.39091641, -0.7479731 , -0.52980278, -0.28798456, -0.77326441, -0.64305557, -0.3275106 ])

在决策树和随机森林中提过,虽然均方误差永远为正,但是sklearn中的参数scoring下,均方误差作为评判标准时,计算的却是“负均方误差”(neg_mean_squared_error)。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss)。在sklearn中,所有的损失都使用负数表示,因此均方误差也被显示为负数。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。
除了MSE,还有类似的MAE(Mean absolute error,绝对均值误差): M A E = 1 m ∑ i = 0 m − 1 ∣ y i − y ^ i ∣ MAE = \frac{1}{m}\sum_{i=0}^{m-1}|y_i-\hat y_i| MAE=m1i=0m1yiy^i其表达的概念与均方误差完全一致,不过在衡量真实标签和预测值之间的差异时,使用的是L1范式(绝对值)。现实使用中,MSE和MAE选择一个使用即可。在sklearn中,使用命令from sklearn.metrics import mean_absolute_error来调用MAE,或者使用交叉验证中的scoring = “neg_mean_absolute_error”,以此在交叉验证时调用MAE。

3.2 是否拟合了足够的信息

对于回归类算法而言,只探索数据预测是否准确是不足够的。除了数据本身的数值大小之外,还希望模型能够捕捉到数据的“规律”,如数据的分布规律、单调性等,而是否捕获了这些信息无法使用MSE来衡量。
sklearn中的线性回归大家族_第4张图片
上图中红色线是真实标签,而蓝色线是拟合模型。这是一个比较极端,但的确可能发生的情况。图中前半部分的拟合非常成功,看上去真实标签与预测结果几乎重合,但后半部分的拟合却非常糟糕,模型向着与真实标签完全相反的方向去了。对于这样的一个拟合模型,如果使用MSE来对它进行判断,MSE会很小,因为大部分样本其实都被完美拟合了,少数样本的真实值和预测值的巨大差异在被均分到每个样本上之后,MSE就会很小。但这样的拟合结果必然不是一个好结果,因为一旦新样本是处于拟合曲线的后半段的,预测结果必然会有巨大的偏差。所以,想要找到新的指标,除了判断预测的数值是否正确之外,还要能够判断模型是否拟合了足够多的数值之外的信息。
在学习降维算法PCA时,提到使用方差来衡量数据上的信息量。如果方差越大,代表数据上的信息量越多,而这个信息量不仅包括了数值的大小,还包括了模型捕捉的哪些规律。为了衡量模型对数据上的信息量的捕捉,定义了 R 2 R^2 R2
R 2 = 1 − ∑ i = 0 m ( y i − y ^ i ) 2 ∑ i = 0 m ( y i − y ˉ ) 2 = 1 − R S S ∑ i = 0 m ( y i − y ˉ ) 2 R^2 = 1-\frac{\sum_{i=0}^m(y_i-\hat y_i)^2}{\sum_{i=0}^m(y_i-\bar y)^2} = 1-\frac{RSS}{\sum_{i=0}^m(y_i-\bar y)^2} R2=1i=0m(yiyˉ)2i=0m(yiy^i)2=1i=0m(yiyˉ)2RSS
其中 y \textbf y y是真实标签, y ^ \hat \textbf y y^是预测结果, y ˉ \bar \textbf y yˉ是均值, y i − y ˉ y_i-\bar y yiyˉ的平方和项如果除以样本量 m m m就是方差。方差的本质是任意一个 y y y值和样本均值的差异,差异越大,这些值所带的信息越多。在 R 2 R^2 R2中,分子是真实值与预测值之间的差值,也就是模型没有捕获到的信息总量,分母是真实标签所带的信息量,所以其衡量的是1-模型没有捕获到的信息量占真实标签中所带的信息量的比例,所以 R 2 R^2 R2越接近1越好。
R 2 R^2 R2可以使用三种方法来调用,第一种是直接熊metrics中导入r2_score,输入预测值和真实值后打分,第二种是直接从线性回归LinearRegression的接口score来进行调用,第三种是在交叉验证中,输入“r2”来调用。

#调用R2
from sklearn.metrics import r2_score
r2_score(yhat,ytest)
#结果:0.33806537615559784
r2 = reg.score(xtest,ytest)
r2
#结果:0.6043668160178813

**相同的评估指标,为什么结果会不一致呢?**这是回归算法和分类算法的差异导致的。在分类模型的评估指标中,进行的是if ab的对比,这种判断和if ba其实完全是一种概念,所以在进行模型评估的时候,没有遇到这种结果不一致的问题。但 R 2 R^2 R2的计算公式中, R 2 R^2 R2明显和分裂模型的指标中的accuracy或者precision不一样, R 2 R^2 R2涉及到的计算对预测值和真实值有极大的区别,必须是预测值在分子,真实值在分母,所以调用metrics模块中的模型评估指标的时候,必须查清楚,指标的参数中,究竟是先输入真实值还是先输入预测值。

#使用shift tab键检查究竟哪个值先输入
r2_score(ytest,yhat)
#结果:0.6043668160178813
#或者也可以指定参数,就不必在意顺序
r2_score(y_true = ytest,y_pred = yhat)
#结果:0.6043668160178813
cross_val_score(reg,X,y,cv = 10,scoring = "r2").mean()
#结果:0.5110068610524536

可以观察到,在加利福尼亚房屋价值数据集上的MSE其实并不是一个很大的数(0.5),但 R 2 R^2 R2不高,这证明模型比较好的拟合了一部分数据的数值,却没能正确拟合数据的分布。通过绘制真实标签ytest和预测结果yhat的图像来看看,两条曲线的交叠越多,说明模型拟合就越好。

import matplotlib.pyplot as plt
sorted(ytest)#排序
plt.plot(range(len(ytest)),sorted(ytest),c = "black",label = "Data")
plt.plot(range(len(yhat)),sorted(yhat),c = "red",label = "Predict")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第5张图片
可见,虽然大部分数据被拟合得比较好,但是图像的开头和结尾处有较大的拟合误差。如果在图像右侧分布着更多的数据,模型就会越来越偏离真正的标签。虽然在有限的数据集上将数值预测正确了,但却没有正确拟合数据的分布,如果有更多的数据进入模型,那数据标签被预测错误的可能性非常大。在sklearn中,有一个与 R 2 R^2 R2相似的指标,叫做可解释性方差分数(explained_variance_score,EVS),也是衡量1-没有捕获到的信息占总信息的比例,但它和 R 2 R^2 R2略有不同,在实际中EVS应用得不多。

import numpy as np
rng = np.random.RandomState(42)
X = rng.randn(100,80)
y = rng.randn(100)
cross_val_score(LR(),X,y,cv=5,scoring = "r2")
#结果:array([-178.71468148,   -5.64707178,  -15.13900541,  -77.74877079, -60.3727755 ])

为什么sklearn中计算的 R 2 R^2 R2也是负的?看看 R 2 R^2 R2的计算公式:
R 2 = 1 − ∑ i = 0 m ( y i − y ^ i ) 2 ∑ i = 0 m ( y i − y ˉ ) 2 = 1 − R S S ∑ i = 0 m ( y i − y ˉ ) 2 R^2 = 1-\frac{\sum_{i=0}^m(y_i-\hat y_i)^2}{\sum_{i=0}^m(y_i-\bar y)^2} = 1-\frac{RSS}{\sum_{i=0}^m(y_i-\bar y)^2} R2=1i=0m(yiyˉ)2i=0m(yiy^i)2=1i=0m(yiyˉ)2RSS
由于除了RSS之外,还有解释平方和ESS(Explained Sum of Squares,也叫作SSR回归平方和)以及总离差平方和TSS(Total Sum of Squares,也叫作SST总离差平方和)。解释平方和ESS定义了预测值与样本均值之间的差异,而总离差平方和定义了真实值与样本均值之间的差异(就是 R 2 R^2 R2中的分母),这两个指标分别写作: T S S = ∑ i = 0 m ( y i − y ˉ ) 2 TSS = \sum_{i=0}^m(y_i-\bar y)^2 TSS=i=0m(yiyˉ)2 E S S = ∑ i = 0 m ( y ^ i − y ˉ ) 2 ESS = \sum_{i=0}^m(\hat y_i-\bar y)^2 ESS=i=0m(y^iyˉ)2有公式: T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS,代入 R 2 R^2 R2的公式,有:
R 2 = 1 − R S S T S S = T S S − R S S T S S = E S S T S S R^2 = 1-\frac{RSS}{TSS} = \frac{TSS-RSS}{TSS} = \frac{ESS}{TSS} R2=1TSSRSS=TSSTSSRSS=TSSESS
而ESS和TSS都带平方,那么必然是正数。但公式 T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS不是永远成立的
那么是如何证明 T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS的呢? T S S = ∑ i = 0 m ( y i − y ˉ ) 2 = ∑ i = 0 m ( y i − y ^ i + y ^ i − y ˉ ) 2 = ∑ i = 0 m ( y i − y ^ i ) 2 + ∑ i = 0 m ( y ^ i − y ˉ ) 2 + 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) = R S S + E S S + 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) TSS = \sum_{i=0}^m(y_i-\bar y)^2 \\ = \sum_{i=0}^m(y_i-\hat y_i+\hat y_i-\bar y)^2 \\ =\sum_{i=0}^m(y_i-\hat y_i)^2+\sum_{i=0}^m(\hat y_i-\bar y)^2+2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y) \\ =RSS+ESS+2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y) TSS=i=0m(yiyˉ)2=i=0m(yiy^i+y^iyˉ)2=i=0m(yiy^i)2+i=0m(y^iyˉ)2+2i=0m(yiy^i)(y^iyˉ)=RSS+ESS+2i=0m(yiy^i)(y^iyˉ)两边同时除以TSS,则有: 1 = R S S T S S + E S S T S S + 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) T S S 1 = \frac{RSS}{TSS}+\frac{ESS}{TSS}+\frac{2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y)}{TSS} 1=TSSRSS+TSSESS+TSS2i=0m(yiy^i)(y^iyˉ) 1 − R S S T S S = E S S + 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) T S S 1-\frac{RSS}{TSS} = \frac{ESS+2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y)}{TSS} 1TSSRSS=TSSESS+2i=0m(yiy^i)(y^iyˉ) R 2 = E S S + 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) T S S R^2 = \frac{ESS+2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y)}{TSS} R2=TSSESS+2i=0m(yiy^i)(y^iyˉ)当令 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) 2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y) 2i=0m(yiy^i)(y^iyˉ)这个式子等于0,公式 T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS就成立了,但这个式子成立是有条件的。由于 ( y i − y ^ i ) (y_i-\hat y_i) (yiy^i)衡量的是真实值到预测值的距离,而 ( y ^ i − y ˉ ) (\hat y_i-\bar y) (y^iyˉ)衡量的是预测值到均值的距离,只要这两部分的符号不同,式子 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ˉ ) 2\sum_{i=0}^m(y_i-\hat y_i)(\hat y_i-\bar y) 2i=0m(yiy^i)(y^iyˉ)就为负,而 R 2 R^2 R2就有机会是一个负数。
sklearn中的线性回归大家族_第6张图片
上图中,蓝色的横线是均值线 y ˉ \bar y yˉ,橙色的线是模型预测线 y ^ \hat y y^,蓝色点是样本点。对于 x i x_i xi来说,真实标签减预测值的值 ( y i − y ^ i ) (y_i-\hat y_i) (yiy^i)为正,但预测值减均值 ( y ^ i − y ˉ ) (\hat y_i-\bar y) (y^iyˉ)却是一个负数,这说明数据本身的均值比拟合模型本身更接近数据的真实值,那模型完全没有作用,类似于分类模型中的分类准确率为50%。
也就是说,当 R 2 R^2 R2显示为负时,证明模型对数据的拟合非常糟糕,模型完全不能用。所以一个负的 R 2 R^2 R2是可能存在的,但在现实应用中如果发现线性回归模型出现了负的 R 2 R^2 R2,首先要检查建模过程和数据处理过程是否正确,也许已经伤害了数据本身,也许建模过程存在问题。如果是集成模型的回归,还要检查弱评估器的数量是否不足,随机森林、提升树这些模型在只有两三棵树的时候很容易出现负的 R 2 R^2 R2。如果检查了所有代码、确定了预处理没有问题,但 R 2 R^2 R2还是负的,那就证明,线性回归模型不适合这个数据。

4 多重共线性:岭回归与Lasso

4.1 最熟悉的陌生人:多重共线性

在对多元线性回归的损失函数求导,求解系数 w \textbf w w的过程中,需要左乘 X T X \textbf X^T\textbf X XTX的逆矩阵,而逆矩阵存在的充分必要条件是特征矩阵不存在多重共线性

  • 逆矩阵存在的充分必要条件
    首先需要先理解逆矩阵存在与否的意义和影响。来看逆矩阵的计算公式:
    A − 1 = 1 ∣ A ∣ A ∗ A_{-1} = \frac{1}{|A|}A^* A1=A1A
    分子上的 A ∗ A^* A是伴随矩阵,任何矩阵都可以有伴随矩阵,因此这一部分不影响逆矩阵的存在。而分母上的行列式 ∣ A ∣ |A| A不同,由于位于分母的变量不能为0,一旦为0则无法计算出逆矩阵。因此逆矩阵存在的充分必要条件是:矩阵的行列式不能为0,对于线性回归而言,也就是说 ∣ X T X ∣ |\textbf X^T\textbf X| XTX不能为0。这是使用最小二乘法来求解线性回归模型参数的核心条件之一。
  • 行列式不为0的充分必要条件
    根据线性代数中基本知识,假设特征矩阵 X \textbf X X结构为(m,n),则 X T X \textbf X^T\textbf X XTX就是结构为(n,m)的矩阵乘以结构为(m,n)的矩阵,从而得到结果为(n,n)的方阵。
    X T X = ( n , m ) ∗ ( m , n ) = ( n , n ) \textbf X^T\textbf X = (n,m)*(m,n) = (n,n) XTX=(n,m)(m,n)=(n,n)
重要定义:矩阵和行列式
矩阵是一组数按一定方式排列成的数表,一般记作 A \textbf A A
行列式是这一组数按某种运算法则最后计算出来的一个数,通常记作 ∣ A ∣ \mid A \mid A或者 d e t A detA detA

任何矩阵都可以有行列式。将行列式化成梯形行列式(上三角矩阵的行列式),主对角线上的元素相乘,即为该行列式的值。此时只要主对角线上的任意元素为0,整个行列式都为0,。也就是只要主对角线上没有一个元素为0,行列式就不会为0。

重要定义:满秩矩阵
满秩矩阵:A是一个n行n列的矩阵,若A转换为梯形矩阵后,没有任何全为0的行或者全为0的列,则称A为满秩矩阵。简单来说,只要对角线上没有一个元素为0,则这个矩阵中绝对不可能存在全为0的行或者列。

即是说,矩阵满秩(即转换为梯形矩阵后对角线上没有0)是矩阵的行列式不为0的充分必要条件

  • 矩阵满秩的充分必要条件
    根据线性代数知识可知,通过对矩阵做初等行变换和列变换,包括交换行/列顺序,将一列/一行乘以一个常数后加减到另一列/另一行上,来将矩阵转化为梯形矩阵。矩阵进行初等行列变换过程中,如果一行可使另一行为0的关系,被称为“精确相关关系,即完全相关”。在这种精确相关关系下,矩阵的行列式为0,则矩阵的逆不存在。在最小二乘法中,如果矩阵 X T X \textbf X^T\textbf X XTX中存在这种精确相关关系,则逆不存在,最小二乘法完全无法使用,线性回归会无法求出结果:
    ( X T X ) − 1 = 1 ∣ X T X ∣ X T X ∗ → 1 0 X T X ∗ → 除 零 错 误 (\textbf X^T\textbf X)^{-1} = \frac{1}{|\textbf X^T\textbf X|}\textbf X^T\textbf X^* \rightarrow \frac{1}{0}\textbf X^T\textbf X^* \rightarrow 除零错误 (XTX)1=XTX1XTX01XTX
    如果矩阵中的两行非常接近于“精确相关关系”,但又不是完全相关,一行不能使另一行为0,这种关系被称为“高度相关关系”。在这种高度相关关系下,矩阵的行列式不为0,但是一个非常接近0的数,矩阵A的逆存在,不过接近于无限大。在这种情况下,最小二乘法可以使用,不过得到的逆会很大,直接影响对参数向量 w \textbf w w的求解:
    ( X T X ) − 1 = 1 ∣ X T X ∣ X T X ∗ → 1 非 常 接 近 0 的 数 X T X ∗ → ∞ (\textbf X^T\textbf X)^{-1} = \frac{1}{|\textbf X^T\textbf X|}\textbf X^T\textbf X^* \rightarrow \frac{1}{非常接近0的数}\textbf X^T\textbf X^* \rightarrow \infty (XTX)1=XTX1XTX01XTX
    w = ( X T X ) − 1 X T y → ∞ \textbf w = (\textbf X^T\textbf X)^{-1} \textbf X^T \textbf y \rightarrow \infty w=(XTX)1XTy
    这样求解出来的参数向量 w \textbf w w会很大,因此会影响建模的结果,造成模型有偏差或者不可用。精确相关关系和高度相关关系并称为“多重共线性”。在多重共线性下,模型无法建立或者模型不可用。
    如果梯形矩阵看起来非常正常,对角线上没有任何元素特别接近于0,因此其行列式也就不会接近0或者为0,求解的参数向量 w \textbf w w就不会有太大偏差,对于模型拟合是比较理想的:
    ( X T X ) − 1 = 1 ∣ X T X ∣ X T X ∗ → 1 不 是 非 常 接 近 0 的 数 X T X ∗ → 逆 矩 阵 的 大 小 正 常 (\textbf X^T\textbf X)^{-1} = \frac{1}{|\textbf X^T\textbf X|}\textbf X^T\textbf X^* \rightarrow \frac{1}{不是非常接近0的数}\textbf X^T\textbf X^* \rightarrow 逆矩阵的大小正常 (XTX)1=XTX1XTX01XTX
    w = ( X T X ) − 1 X T y → 拟 合 出 适 合 的 w \textbf w = (\textbf X^T\textbf X)^{-1} \textbf X^T \textbf y \rightarrow 拟合出适合的w w=(XTX)1XTyw
    从上面的过程可以看出,一个矩阵如果要满秩,则要求矩阵中每个向量之间不能存在多重共线性。这也构成了线性回归算法对于特征矩阵的要求。
    sklearn中的线性回归大家族_第7张图片
  • 多重共线性与相关性
    多重共线性如果存在,则线性回归就无法使用最小二乘法来进行求解,或者求解会出现偏差。但是不能存在多重共线性,不代表不能存在相关性,机器学习不要求特征之间必须独立,必须不想关,只要不是高度相关或者精确相关就好。
多重共线性(Multicollinearity)与相关性(Correlation)
多重共线性是一种统计现象,是指线性模型中的特征(解释变量)之间由于存在精确相关关系或高度相关关系,多重共线性的存在会使模型无法建立,或者估计失真。多重共线性使用指标方差膨胀因子(variance inflation factor,VIF)来进行衡量,通常提到“共线性”,都特指多重共线性。
相关性是衡量两个或多个变量一起波动的程度的指标,它可以是正的、负的或者0。当说变量之间具有相关性,通常是指线性相关性,线性相关一般有皮尔逊相关系数进行衡量,非线性相关可以使用斯皮尔曼相关系数或者互信息法进行衡量。

在现实中特征之间完全独立的情况非常少,因为大部分数据统计手段或者收集者并不考虑统计学或者机器学习建模时的要求,现实数据多多少少都会存在一些相关性,极端情况下,甚至还可能出现收集的特征数量比样本数量多的情况。通常来说,这些相关性在机器学习中通常无伤大雅(在统计学中可能是比较严重的问题),即便有一些偏差,只要最小二乘法能够求解,都可以无视。毕竟想要消除特征的相关性,无论使用怎么样的手段都无法避免进行特征选择,这意味着可用的信息量变得更加少。对于机器学习而言,很可能尽量排除相关性后,模型的整体效果会受到巨大的打击。这种情况下,选择不处理相关性,只要结果好即可。
然而多重共线性的存在会造成模型极大的偏移,无法拟合数据。因此这是必须解决的问题。为了保留线性模型计算快速、理解容易的优点,并不希望更换成非线性模型,这促使统计学家和机器学习研究者们钻研出多种能够处理多重共线性的方法,其中三种比较常见:

使用统计学的先验思路 使用向前逐步回归 改进线性回归
在开始建模之前,先对数据进行各种相关性检验,如果存在多重共线性则可考虑对数据的特征进行删减筛查,或者使用降维算法对其进行处理,最终获得一个完全不存在相关性的数据集 逐步回归能够筛选对标签解释力度最强的特征,同时对于存在相关性的特征们加上一个惩罚项,削弱其对标签的贡献,以绕过最小二乘法对共线性较为敏感的缺陷 在原有的线性回归算法基础上进行修改,使其能够容忍特征列存在多重共线性的情况,并且能够顺利建模,且尽可能的保证RSS取得最小值

这三种手段中,第一种相对耗时耗力,需要较多的人工操作,并且会需要混合各种统计学中的知识和检验来进行使用。在机器学习中,能够使用一种模型解决的问题,尽量不用多个模型来解决,如果能够追求结果,会尽量避免进行一系列检验。况且,统计学中的检验往往以“让特征独立”为目标,与机器学习中的“稍微有点相关性也无妨”不太一致。第二种在现实中应用较多,不过由于理论复杂,效果也不是非常高效,因此向前逐步回归不是机器学习的首选。第三种是改进线性回归来处理多重共线性是核心。为此,一系列算法(如岭回归、Lasso、弹性网等)就被研究出来了。

4.2 岭回归

4.2.1 岭回归解决多重共线性问题

在线性模型中,除了线性回归之外,最知名的就是岭回归与Lasso了。这两个算法非常神秘,其原理和应用都不像其他算法那样高调,学习资料也少。这可能是因为这两个算法不是为了提升模型的表现,而是为了解决多重共线性问题而设计的(实际上,使用岭回归或者Lasso,模型的效果往往会下降一些,因为删除了一小部分信息)。因此在结果为上的机器学习领域有些被冷落。
岭回归,又称为吉洪诺夫正则化(Tikhonov regularization)。通常来说,大部分的机器学习会使用代数的形式来展现岭回归的原理,这个原理和逻辑回归及支持向量机非常相似,都是将求解 w \textbf w w的过程转化为一个带条件的最优化问题,然后用最小二乘法求解。然而,岭回归的原理也可以用矩阵表达出来。
岭回归在多元线性回归的损失函数上加上了正则化,表达为系数 w \textbf w w的L2范式(即系数 w \textbf w w的平方项)乘以正则化系数 α \alpha α。也会将正则化系数写作 λ \lambda λ,用以和Lasso区别,不过在sklearn中由于是两个不同的算法,因此正则化系数都使用 α \alpha α来表示。岭回归的损失函数的完整表达式写作: min ⁡ w ∣ ∣ Xw − y ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 2 2 \min_w||\textbf X\textbf w-\textbf y||_2^2+\alpha||\textbf w||_2^2 wminXwy22+αw22在线性回归中,通过在损失函数上对 w \textbf w w求导来求解极值,在这里虽然加上了正则化,依然使用最小二乘法来求解。假设特征矩阵结构为(m,n),系数 w \textbf w w的结构为(1,n),则可以有:
∂ ( R S S + α ∣ ∣ w ∣ ∣ 2 2 ) ∂ w = ∂ ( ∣ ∣ y − Xw ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 2 2 ) ∂ w = ∂ ( y − Xw ) T ( y − Xw ) ∂ w + ∂ α ∣ ∣ w ∣ ∣ 2 2 ∂ w = 0 − 2 X T y + 2 X T Xw + 2 α w \frac{\partial (RSS+\alpha ||\textbf w||_2^2)}{\partial \textbf w} = \frac{\partial (||\textbf y - \textbf X \textbf w||_2^2+\alpha ||\textbf w||_2^2)}{\partial \textbf w} \\ = \frac{\partial (\textbf y-\textbf X\textbf w)^T(\textbf y-\textbf X\textbf w)}{\partial \textbf w}+\frac{\partial \alpha ||\textbf w||_2^2}{\partial \textbf w} \\ =0-2\textbf X^T\textbf y+2\textbf X^T\textbf X\textbf w+2\alpha\textbf w w(RSS+αw22)=w(yXw22+αw22)=w(yXw)T(yXw)+wαw22=02XTy+2XTXw+2αw
将含有 w \textbf w w的项合并,其中 α \alpha α为常数。为了实现矩阵相加,让它乘以一个结构为 n ∗ n n*n nn的单位矩阵 I I I
∂ ( R S S + α ∣ ∣ w ∣ ∣ 2 2 ) ∂ w = ( X T X + α I ) w − X T y \frac{\partial (RSS+\alpha ||\textbf w||_2^2)}{\partial \textbf w} = (\textbf X^T\textbf X+\alpha \textbf I)\textbf w - \textbf X^T\textbf y w(RSS+αw22)=(XTX+αI)wXTy
( X T X + α I ) w = X T y (\textbf X^T\textbf X+\alpha \textbf I)\textbf w = \textbf X^T\textbf y (XTX+αI)w=XTy
只要 ( X T X + α I ) (\textbf X^T\textbf X+\alpha \textbf I) (XTX+αI)存在逆矩阵,就可以求解出 w \textbf w w。一个矩阵存在逆矩阵的充分必要条件是这个矩阵的行列式不为0。假设原本的特征矩阵中存在共线性,则方针 X T X \textbf X^T\textbf X XTX就会不满秩(存在全为零的行)。此时方针 X T X \textbf X^T\textbf X XTX就是没有逆的,最小二乘法就无法使用。然而,加上 α I \alpha \textbf I αI之后,矩阵行列式变换后的梯形行列式就不存在全为0的行或者列了,除非:(1) α \alpha α等于0;(2)原本的矩阵 X T X \textbf X^T\textbf X XTX中存在对角线上元素为 − α -\alpha α,其他元素都为0的行或者列。否则矩阵 X T X + α I \textbf X^T\textbf X+\alpha \textbf I XTX+αI永远都是满秩。在sklearn中, α \alpha α的值可以自由控制,因此可以让其不为0,以避免(1)。而对于(2),如果发现某个 α \alpha α的取值下模型无法求解,那只需要换一个 α \alpha α的取值,也可以顺利避免。也就是说,矩阵的逆是存在的, w \textbf w w可以写作:
( X T X + α I ) w − X T y = 0 (\textbf X^T\textbf X+\alpha \textbf I)\textbf w-\textbf X^T\textbf y = 0 (XTX+αI)wXTy=0
( X T X + α I ) w = X T y (\textbf X^T\textbf X+\alpha \textbf I)\textbf w = \textbf X^T\textbf y (XTX+αI)w=XTy
左乘一个 ( X T X + α I ) − 1 (\textbf X^T\textbf X+\alpha \textbf I)^{-1} (XTX+αI)1,则有:
w = ( X T X + α I ) − 1 X T y \textbf w = (\textbf X^T\textbf X+\alpha \textbf I)^{-1}\textbf X^T\textbf y w=(XTX+αI)1XTy
如此,正则化系数 α \alpha α就避免了“精确相关关系”带来的影响,至少最小二乘法在 α \alpha α存在的情况下是一定可以使用的。对于存在“高度相关关系”的矩阵,也可以通过调大 α \alpha α,使得 X T X + α I \textbf X^T\textbf X+\alpha \textbf I XTX+αI矩阵的行列式变大,从而使得逆矩阵变小,以此控制参数向量 w \textbf w w的偏移。当 α \alpha α越大,模型越不容易受到共线性的影响。
( X T X + α I ) − 1 = 1 X T X + α I ( X T X + α I ) ∗ (\textbf X^T\textbf X+\alpha \textbf I)^{-1} = \frac{1}{\textbf X^T\textbf X+\alpha \textbf I}(\textbf X^T\textbf X+\alpha \textbf I)^* (XTX+αI)1=XTX+αI1(XTX+αI)
这样多重共线性就被控制住了:最小二乘法一定有解,并且这个解可以通过 α \alpha α来进行调节,以确保不会偏离太多。但是 α \alpha α挤占了 w \textbf w w中由原始的特征矩阵贡献的空间,因此如果 α \alpha α太大,也会导致 w \textbf w w的估计出现较大的偏移,无法正确拟合数据的真实面貌。在使用中,需要找出 α \alpha α让模型效果变好的最佳取值。

4.2.2 linear_model.Ridge

在sklearn中,岭回归由线性模型库中的Ridge类来调用:
sklearn.linear_model.Ridge(alpha = 1.0, fit_intercept = True, normalize = False, copy -X = True, max_iter = None, tol = 0.001, solver = “auto”, random_state = None)
和线性回归相比,岭回归的参数多了一些,但是真正核心的参数就是正则化的系数 α \alpha α,其他参数是当使用最小二乘法之外的求解方法求解岭回归的时候才需要的,通常不需要去触碰这些参数,只需要了解 α \alpha α的用法即可。
在加利佛尼亚房屋价值数据集上使用线性回归,得出的结果大概是训练集上的拟合程度为60%,测试集上的拟合程度也是60%左右,为了判断这种低的拟合程度是不是由多重共线性造成的,在统计学中,会通过VIF或者各种检验来判断数据是否存在共线性。然而在机器学习中,可以使用模型来判断:如果一个数据集在领回归中使用各种正则化参数取值下模型表现没有明显上升(如出现持平或下降),则说明数据没有多重共线性,顶多是特征之间有一些相关性。反之,如果一个数据集在岭回归的各种正则化参数取值下表现出明显的上升趋势,则说明数据存在多重共线性。接下来,在加尼福尼亚房屋价值数据集上来验证这个说法:

housevalue = fch()
X = pd.DataFrame(housevalue.data)
y = housevalue.target
X.columns = ["住户收入中位数","房屋使用年代中位数","平均房间数目","平均卧室数目","街区人口","平均入住率","街区的纬度","街区的经度"]
X.head()

sklearn中的线性回归大家族_第8张图片

xtrain,xtest,ytrain,ytest = TTS(X,y,test_size = 0.3,random_state = 420)
for i in [xtrain,xtest]:
    i.index = range(i.shape[0])
#使用岭回归来进行建模
reg = Ridge(alpha = 1).fit(xtrain,ytrain)
reg.score(xtest,ytest)
#结果:0.6043610352312286
#交叉验证下,与线性回归相比,岭回归的结果如何变化
alpharange = np.arange(1,1001,100)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    linear = LinearRegression()
    regs = cross_val_score(reg,X,y,cv = 5,scoring = "r2").mean()
    linears = cross_val_score(linear,X,y,cv = 5,scoring = "r2").mean()
    ridge.append(regs)
    lr.append(linears)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("Mean")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第9张图片

#细化一下学习曲线
alpharange = np.arange(1,201,10)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    linear = LinearRegression()
    regs = cross_val_score(reg,X,y,cv = 5,scoring = "r2").mean()
    linears = cross_val_score(linear,X,y,cv = 5,scoring = "r2").mean()
    ridge.append(regs)
    lr.append(linears)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("Mean")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第10张图片
可以看出,在加利福利亚数据集上,岭回归的结果轻微上升,然后骤降。可以说,加利福尼亚房屋价值数据集有很轻微的一部分共线性,这种共线性被正则化参数 α \alpha α消除后,模型的效果提升了一点点,但是对于整个模型而言是杯水车薪。在过多控制多重共线性的点后,模型的效果飞速下降,显然是正则化的程度太重,挤占了参数 w \textbf w w本来的估计空间。从结果来看,加利福尼亚数据集的核心问题不在于多重共线性,岭回归不能够提升模型表现。另外,在正则化参数逐渐增大的过程中,可以观察一下模型方差的变化:

alpharange = np.arange(1,1001,100)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    linear = LinearRegression()
    varR = cross_val_score(reg,X,y,cv = 5,scoring = "r2").var()
    varLR = cross_val_score(linear,X,y,cv = 5,scoring = "r2").var()
    ridge.append(varR)
    lr.append(varLR)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("variance")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第11张图片
可以发现,模型的方差上升快速,不过方差的值本身很小,其变化不超过 R 2 R^2 R2上升部分的1/3,因此只要噪声的状况维持稳定,模型的泛化误差可能还是一定程度上降低了的,虽然岭回归和Lasso不是设计来提升模型表现的,而是专注于解决多重共线性问题的,但当 α \alpha α在一定范围内变动的时候,消除多重共线性也许能够在一定程度上提高模型的泛化能力。但是泛化能力并没有直接衡量的指标,因此往往只能够通过观察模型的准确性指标和方差的变化情况,来判断泛化能力是否提高。接下来使用波士顿房价数据集,来看看多重共线性比较明显的情况:

from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
X = load_boston().data
y = load_boston().target
xtrain,xtest,ytrain,ytest = TTS(X,y,test_size = 0.3,random_state = 420)
#先看方差的变化
alpharange = np.arange(1,1001,100)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    linear = LinearRegression()
    varR = cross_val_score(reg,X,y,cv = 5,scoring = "r2").var()
    varLR = cross_val_score(linear,X,y,cv = 5,scoring = "r2").var()
    ridge.append(varR)
    lr.append(varLR)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("variance")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第12张图片

#查看R2的变化
alpharange = np.arange(1,1001,100)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    linear = LinearRegression()
    varR = cross_val_score(reg,X,y,cv = 5,scoring = "r2").mean()
    varLR = cross_val_score(linear,X,y,cv = 5,scoring = "r2").mean()
    ridge.append(varR)
    lr.append(varLR)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("Mean")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第13张图片

#细化学习曲线
alpharange = np.arange(100,300,10)
ridge,lr = [],[]
for alpha in alpharange:
    reg = Ridge(alpha = alpha)
    #linear = LinearRegression()
    varR = cross_val_score(reg,X,y,cv = 5,scoring = "r2").mean()
    #varLR = cross_val_score(linear,X,y,cv = 5,scoring = "r2").mean()
    ridge.append(varR)
    lr.append(varLR)
plt.plot(alpharange,ridge,color = "red",label = "Ridge")
#plt.plot(alpharange,lr,color = "orange",label = "LR")
plt.title("Mean")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第14张图片
可以发现,比起加利福尼亚房屋价值数据集,波士顿房价数据集的方差降低明显,偏差也降低明显,可见使用岭回归还是起到了一定作用,模型的泛化能力是可能会上升的。对于具有一些相关性的数据,如果使用岭回归或者Lasso,那么模型的效果都是会降低的,很难升高,这恐怕也是岭回归和Lasso一定程度上被机器学习冷遇的原因。

4.2.3 选取最佳的正则化参数取值

既然要选择 α \alpha α的范围,就不可避免地要进行最优参数的选择。通常,会使用岭迹图来判断正则项参数的最佳取值。传统的岭迹图形似一个开口的喇叭图(根据横坐标的正负,喇叭可能朝右或者朝左):
sklearn中的线性回归大家族_第15张图片上图是一个以正则化参数为横坐标,线性模型求解的系数 w w w为纵坐标的图像,其中每一条彩色的线都是一个系数 w w w。其目标是建立正则化参数与系数 w w w之间的直接关系,一次来观察正则化参数的变化如何影响了系数 w w w的拟合。岭迹图认为线条交叉越多,则说明特征之间的多重共线性越高。应该选择系数较为平稳的喇叭口所对应的 α \alpha α取值作为最佳的正则化参数的取值。岭迹图的绘制方法非常简单,代码如下:

#绘图展示结果
plt.rcParams['font.sans-serif'] = ['SimHei']   #用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False   #用来正常显示负号
ax = plt.gca()
ax.plot(alphas,coefs)
ax.set_xscale("log")
ax.set_xlim(ax.get_xlim()[::-1])#将横坐标逆转
plt.xlabel("正则化参数alpha")
plt.ylabel("系数w")
plt.title("岭回归下的岭迹图")
plt.axis("tight")
plt.show()

sklearn中的线性回归大家族_第16张图片
其中涉及的希尔伯特矩阵如下: H = ( 1 1 2 1 3 1 4 1 5 1 2 1 3 1 4 1 5 1 6 1 3 1 4 1 5 1 6 1 7 1 4 1 5 1 6 1 7 1 8 1 5 1 6 1 7 1 8 1 9 ) H = \begin{pmatrix} 1 & \frac{1}{2} & \frac{1}{3} & \frac{1}{4} & \frac{1}{5} \\ \frac{1}{2} & \frac{1}{3} & \frac{1}{4} & \frac{1}{5} & \frac{1}{6} \\ \frac{1}{3} & \frac{1}{4} & \frac{1}{5} & \frac{1}{6} & \frac{1}{7} \\ \frac{1}{4} & \frac{1}{5} & \frac{1}{6} & \frac{1}{7} & \frac{1}{8} \\ \frac{1}{5} & \frac{1}{6} & \frac{1}{7} & \frac{1}{8} & \frac{1}{9} \\ \end{pmatrix} H=1213141512131415161314151617141516171815161718191然而,非常不建议使用岭迹图来作为寻找最佳参数的标准。有两个理由:(1)岭迹图的很多细节难以解释,如为什么多重共线性存在会使得线与线之间有很多交点?当 α \alpha α很大之后,看上去所有的系数都很接近于0,难道不是那时候线之间的交代最多吗?(2)岭迹图的评判标准非常模糊,哪里才是最佳的喇叭口?哪里才是所谓的系数开始变得“平稳”的时候?
因此,除了统计学家,几乎没有人会再使用岭迹图,在现实中,真正用来选择正则化参数的技术是交叉验证,并且选择的标准非常明确——选择让交叉验证下的均方误差最小的正则化系数 α \alpha α。在sklearn中,有带交叉验证的岭回归可使用:
sklearn.linear_model.RidgeCV(alphas = (0.1,1.0,10.0), fit_intercept = True, normalize = False, scoring = None, cv = None, gcv_mode = None, store_cv_values = False)
可以看到,这个类似于普通的岭回归类Ridge非常相似,不过在输入正则化系数 α \alpha α的时候,可以传入元组作为正则化系数的备选,非常类似于在画学习曲线前设定的for i in 的列表对象。以下说明RidgeCV的重要参数、属性和接口:

重要参数 含义
alphas 需要测试的正则化参数的取值的元祖
scoring 用来进行交叉验证的模型评估指标,默认是 R 2 R^2 R2,可自行调整
store_cv_values 是否保存每次交叉验证的结果,默认False
cv 交叉验证的模式,默认是None,表示默认进行留一交叉验证,可以输入Kfold对象和StratifiedKFold对象来进行交叉验证。注意,仅仅当为None时,每次交叉验证的结果才可以被保存下来,当cv有值存在(不是None)时,store_cv_values无法被设定为True
重要属性 含义
alpha_ 查看交叉验证选中的alpha
cv_values_ 调用所有交叉验证的结果,只有当store_cv_values = True时才能够调用,因此返回的结构是(n_samples,n_alphas)
重要接口 含义
score 调用Ridge类不进行交叉验证的情况下返回的R平方

依然利用加利福尼亚房屋价值数据集来学习这个类的使用:

import numpy as np
import pandas as pd
from sklearn.linear_model import RidgeCV,LinearRegression
from sklearn.model_selection import train_test_split as TTS
from sklearn.datasets import fetch_california_housing as fch
import matplotlib.pyplot as plt

housevalue = fch()
X = pd.DataFrame(housevalue.data)
y = housevalue.target
X.columns = ["住户收入中位数","房屋使用年代中位数","平均房间数目","平均卧室数目","街区人口","平均入住率","街区的纬度","街区的经度"]
Ridge_ = RidgeCV(alphas = np.arange(1,1001,100)
                #,scoring = "neg_mean_squared_error"
                ,store_cv_values = True
                #,cv = 5
                ).fit(X,y)
#无交叉验证的结果
Ridge_.score(X,y)
#结果:0.6060251767338444
#调用所有交叉验证的结果
Ridge_.cv_values_.shape
#结果:(20640, 10)
#进行平均后可以查看每个正则化系数取值下的交叉验证结果
Ridge_.cv_values_.mean(axis = 0)
#结果:array([0.52823795, 0.52787439, 0.52807763, 0.52855759, 0.52917958, 0.52987689, 0.53061486, 0.53137481, 0.53214638, 0.53292369])
#查看被选择出来的最佳正则化系数
Ridge_.alpha_
#结果:101

4.3 Lasso

4.3.1 Lasso与多重共线性

除了岭回归之外,最常被提到的还有模型Lasso。Lasso全称是最小绝对收缩和选择算子(least absolute shrinkage and selection operator),由于名字过于复杂,故简称为Lasso。和岭回归一样,Lasso是被创造来作用于多重共线性问题的算法,不过Lasso使用的是系数 w \textbf w w的L1范式(L1范式则是系数 w \textbf w w的绝对值)乘以正则化系数 α \alpha α,所以Lasso的损失函数表达式为: min ⁡ w ∣ ∣ Ww − y ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 1 \min_w||\textbf W\textbf w-\textbf y||_2^2+\alpha||\textbf w||_1 wminWwy22+αw1当使用最小二乘法来求解Lasso中的参数 w \textbf w w,依然对损失函数进行求导:
∂ ( R S S + α ∣ ∣ w ∣ ∣ 1 ) ∂ w = ∂ ( ∣ ∣ y − Xw ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 1 ) ∂ w = ∂ ( y − Xw ) T ( y − Xw ) ∂ w + ∂ α ∣ ∣ w ∣ ∣ 1 ∂ w \frac{\partial (RSS+\alpha ||\textbf w||_1)}{\partial \textbf w} = \frac{\partial (||\textbf y - \textbf X \textbf w||_2^2+\alpha ||\textbf w||_1)}{\partial \textbf w} \\ = \frac{\partial (\textbf y-\textbf X\textbf w)^T(\textbf y-\textbf X\textbf w)}{\partial \textbf w}+\frac{\partial \alpha ||\textbf w||_1}{\partial \textbf w} w(RSS+αw1)=w(yXw22+αw1)=w(yXw)T(yXw)+wαw1
前半部分推导过,后半部分对 w \textbf w w求导和岭回归有明显的区别。假设所有的系数都为正:
∂ ( R S S + α ∣ ∣ w ∣ ∣ 1 ) ∂ w = 0 − 2 X T y + 2 X T Xw + α \frac{\partial (RSS+\alpha ||\textbf w||_1)}{\partial \textbf w}=0-2\textbf X^T\textbf y+2\textbf X^T\textbf X\textbf w+\alpha w(RSS+αw1)=02XTy+2XTXw+α
将含有 w \textbf w w的项合并,其中 α \alpha α为常数。为了实现矩阵相加,乘以一个结构为 n ∗ n n*n nn的单位矩阵 I I I
∂ ( R S S + α ∣ ∣ w ∣ ∣ 1 ) ∂ w = X T Xw − X T y + α I 2 \frac{\partial (RSS+\alpha ||\textbf w||_1)}{\partial \textbf w}=\textbf X^T\textbf X\textbf w - \textbf X^T\textbf y+\frac{\alpha \textbf I}{2} w(RSS+αw1)=XTXwXTy+2αI
X T Xw = X T y − α I 2 \textbf X^T\textbf X\textbf w = \textbf X^T\textbf y-\frac{\alpha \textbf I}{2} XTXw=XTy2αI
问题回到了要求 X T X \textbf X^T\textbf X XTX的逆必须存在。再岭回归中,通过正则化系数 α \alpha α能够向方阵 X T X \textbf X^T\textbf X XTX加上一个单位矩阵,一次来防止方阵 X T X \textbf X^T\textbf X XTX的行列式为0,而现在L1范式所带的正则项 α \alpha α在求导之后并不带有 w \textbf w w这个项,因此无法对 X T X \textbf X^T\textbf X XTX造成任何影响。也就是说,Lasso无法解决特征之间“精确相关”的问题。当使用最小二乘法求解线性回归时,如果线性回归无解或者报除零错误,换Lasso不能解决任何问题。

岭回归 vs Lasso
岭回归可以解决特征间的精确相关关系导致的最小二乘法无法使用的问题,而Lasso不行。

幸运的是,在现实中其实比较少会遇到“精确相关”的多重共线性问题,大部分多重共线性问题应该是“高度相关”,而如果假设方阵 X T X \textbf X^T\textbf X XTX的逆是一定存在的,那可以有:
w = ( X T X ) − 1 ( X T y − α I 2 ) \textbf w = (\textbf X^T\textbf X)^{-1}(\textbf X^T\textbf y-\frac{\alpha \textbf I}{2}) w=(XTX)1(XTy2αI)
通过增大 α \alpha α,可以为 w \textbf w w的计算增加一个负项,从而限制参数估计中 w \textbf w w的大小,而防止多重共线性引起的参数 w \textbf w w被估计过大导致模型失准的问题。Lasso不是从根本上解决多重共线性问题,而是限制多重共线性带来的影响。何况,这还是假设所有系数都为正的情况下,假设系数 w \textbf w w无法都为正,则很可能需要将正则项参数 α \alpha α设定为负,因此 α \alpha α可以取负数,并且负数越大,对共线性的限制也越大。
所有这些让Lasso成为了一个神奇的算法,尽管它是为了限制多重共线性被创造出来的,然而其实并不使用其来抑制多重共线性,反而接受了它在其他方面的优势。L1和L2正则化一个核心差异就是它们对系数 w \textbf w w的影响:两个正则化都会压缩系数 w \textbf w w的大小,对标签贡献更少的特征的系数会更小,也会更容易被压缩。不过,L2正则化只会将系数压缩到尽量接近0,但L1正则化主导稀疏性,因此会将系数压缩到0。这个性质让Lasso成为线性模型中的特征选择工具的首选。接下来,看看如何使用Lasso来选择特征。

4.3.2 Lasso的核心作用:特征选择

sklearn.linear_model.Lasso(alpha = 1.0, fit_intercept = True, normalize = False, precompute = False, copy_X = True, max_iter = 1000, tol = 0.0001,warm_start = False, positive = False, random_state = None, selection=‘cyclic’)
sklearn中使用类Lasso来调用lasso回归,众多参数中需要比较在意的就是表示正则化系数的参数 α \alpha α,另一个需要注意的就是参数positive。当这个参数为“True”时,是要求Lasso回归出的系数必须为正数,一次来保证 α \alpha α一定以增大来控制正则化的程度。需要注意的是,在sklearn中Lasso使用的损失函数是: min ⁡ w 1 2 n s a m p l e s ∣ ∣ Xw − y ∣ ∣ 2 2 + α ∣ ∣ w ∣ ∣ 1 \min_w\frac{1}{2n_{samples}}||\textbf X \textbf w - \textbf y||_2^2+\alpha||\textbf w||_1 wmin2nsamples1Xwy22+αw1其中 1 2 n s a m p l e s \frac{1}{2n_{samples}} 2nsamples1只是作为系数存在,用来消除对损失函数求导后多出来的那个2的(求解 w \textbf w w时所带的1/2),然后对整体的RSS求了一个平均而已。无论是从损失函数的意义来看,还是从Lasso的性质和功能来看,这个变化没有造成任何影响,只不过计算上会更加简便一些。
接下来,看看Lasso如何做特征选择:

housevalue = fch()
X = pd.DataFrame(housevalue.data)
y = housevalue.target
X.columns = ["住户收入中位数","房屋使用年代中位数","平均房间数目","平均卧室数目","街区人口","平均入住率","街区的纬度","街区的经度"]
#X.head()
xtrain,xtest,ytrain,ytest = TTS(X,y,Test_size = 0.3,random_state = 420)
#恢复索引
for i in [xtrain,ytrain]:
    i.index = range(i.shape[0])
#线性回归进行拟合
reg = LinearRegression().fit(xtrain,ytrain)
(reg.coef_*100).tolist()
'''
结果:
[43.735893059684,
 1.0211268294493827,
 -10.780721617317681,
 62.643382753637766,
 5.2161253534695196e-05,
 -0.3348509646333501,
 -41.3095937894772,
 -42.62109536208474]
'''
#岭回归进行拟合
Ridge_ = Ridge(alpha = 0).fit(xtrain,ytrain)
(Ridge_.coef_*100).tolist()
'''
结果:
[43.73589305968356,
 1.0211268294493694,
 -10.780721617316962,
 62.6433827536353,
 5.2161253532548055e-05,
 -0.3348509646333529,
 -41.30959378947995,
 -42.62109536208777]
'''
#Lasso进行拟合
Lasso_ = Lasso(alpha = 0).fit(xtrain,ytrain)
(Lasso_.coef_*100).tolist()
'''
结果:
[43.73589305968403,
 1.0211268294494058,
 -10.780721617317653,
 62.643382753637724,
 5.2161253532678864e-05,
 -0.33485096463335745,
 -41.30959378947717,
 -42.62109536208475]
'''

可以看到,岭回归没有报错错误,但是Lasso就不一样,虽然依然对系数进行了计算,但是报出了以下三个错误:
sklearn中的线性回归大家族_第17张图片
这三条错误分别是:(1)正则化系数为0,这样算法不可收敛。如果想让正则化系数为0,就使用线性回归吧;(2)没有正则项的坐标下降法可能会导致意外的结果,不鼓励这样做;(3)目标函数没有收敛,也许想要增加迭代次数,使用一个非常小的 α \alpha α来拟合模型可能会造成精确度问题。
可能比较疑惑(2)中提及的坐标下降法,这是由于sklearn中的Lasso类不是使用最小二乘法来进行求解的,而是使用坐标下降法。那么Lasso既然不能从根本上解决多重共线性引起的最小二乘无用的问题,为什么要坚持最小二乘法呢?明明坐标下降法就很好。以下两篇论文解释了sklearn坐标下降求解器中使用的迭代方法,以及用于收敛控制的对偶间隙计算方式:(1)Regularization Paths for Generalized Linear Models via Coordinate Descent;(2)An Interior-Point Method for Large-Scale L1-Regularized Least Squares。
有了坐标下降法,就有迭代和收敛的问题,因此sklearn不推荐使用0这样的正则化系数,如果的确希望取到0,那可以使用一个比较小的数,如 0.01 0.01 0.01 10 ∗ e − 3 10*e^{-3} 10e3这样的值:

#岭回归进行拟合
Ridge_ = Ridge(alpha = 0.01).fit(xtrain,ytrain)
(Ridge_.coef_*100).tolist()
'''
结果:
[43.73575720621553,
 1.0211292318121377,
 -10.78046033625102,
 62.64202320775469,
 5.217068073227091e-05,
 -0.3348506517067568,
 -41.309571432294405,
 -42.621053889327314]
'''
#Lasso进行拟合
Lasso_ = Lasso(alpha = 0.01).fit(xtrain,ytrain)
(Lasso_.coef_*100).tolist()
'''
结果:
[40.10568371834486,
 1.093629260786014,
 -3.7423763610244563,
 26.524037834897197,
 0.00035253685115039417,
 -0.32071293948878005,
 -40.064830473448424,
 -40.81754399163315]
'''

这样就没有报错了。并且两种回归的系数有比较大的区别。

#加大正则项系数,观察模型的系数发生了什么变化
Ridge_ = Ridge(alpha = 10**4).fit(xtrain,ytrain)
(Ridge_.coef_*100).tolist()
'''
结果:
[34.62081517607707,
 1.5196170869238759,
 0.3968610529209999,
 0.915181251035547,
 0.0021739238012248533,
 -0.34768660148101127,
 -14.73696347421548,
 -13.435576102527182]
'''
lasso_ = Lasso(alpha = 10**4).fit(xtrain,ytrain)
(lasso_.coef_*100).tolist()
#结果:[0.0, 0.0, 0.0, -0.0, -0.0, -0.0, -0.0, -0.0]
#看来10**4对于Lasso来说是一个过于大的取值
lasso_ = Lasso(alpha = 1).fit(xtrain,ytrain)
(lasso_.coef_*100).tolist()
'''
#结果:
[14.581141247629423,
 0.6209347344423876,
 0.0,
 -0.0,
 -0.00028065986329009983,
 -0.0,
 -0.0,
 -0.0]
'''
#将系数进行绘图
plt.plot(range(1,9),(reg.coef_*100).tolist(),color = "red",label = "LR")
plt.plot(range(1,9),(Ridge_.coef_*100).tolist(),color = "orange",label = "Ridge")
plt.plot(range(1,9),(lasso_.coef_*100).tolist(),color = "k",label = "Lasso")
plt.plot(range(1,9),[0]*8,color = "grey",linestyle = "--")
plt.xlabel("w")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第18张图片
可见,比起岭回归,Lasso所带的L1正则项对于系数的惩罚要重得多,并且会将系数压缩至0,因此可以被用来做特征选择,也因此往往让Lasso的正则化系数 α \alpha α在很小的空间中变动,以此来寻找最佳的正则化系数。

4.3.3 选取最佳的正则化参数取值

sklearn.linear_model.LassoCV(eps = 0.001, n_alphas = 100, alphas = None, fit_intercept = True, normalize = False, precompute = “auto”, max_iter = 1000, tol = 0.0001, copy_X = True, cv = “warn”,verbose = False, n_jobs = None, positive = False, random_state = None, selection = “cyclic”)
使用交叉验证的Lasso类的参数看起来与岭回归略有不同,这是由于Lasso对于alpha的取值更加敏感的性质决定的。由于Lasso对于正则化系数的变动过于敏感,因此往往让 α \alpha α在很小的空间中变动。这个小空间小到超乎想象(0.01到0.02之间这样的空间,对lasso而言还是太大了),因此设定了一个重要概念“正则化路径”,用来设定正则化系数的变动。

重要概念:正则化路径(regularization path)
假设特征矩阵中有 n n n个特征,则有特征向量 x 1 , x 2 , . . . , x n \textbf x_1,\textbf x_2,...,\textbf x_n x1,x2,...,xn。对于每一个 α \alpha α的取值,都可以得出一组对应这个特征向量的参数向量 w \textbf w w,其中包含了 n + 1 n+1 n+1个参数,分别是 w 0 , w 1 , . . . , w n w_0,w_1,...,w_n w0,w1,...,wn。这些参数可以被看作是一个 n n n维空间中的一个点。对于不同的 α \alpha α取值,将得到许多个在 n n n维空间中的点,所有这些点形成的序列,被称之为是正则化路径。

把形成这个正则化路径的 α \alpha α的最小值除以 α \alpha α的最大值得到的量 α . m i n α . m a x \frac{\alpha.min}{\alpha.max} α.maxα.min称为正则化路径的长度(length of the path)。在sklearn中,可以通过规定正则化路径的长度(即限制 α \alpha α的最小值和最大值之间的比例),以及路径中 α \alpha α的个数,来让sklearn自动生成 α \alpha α的取值,这就避免了需要自己生成非常非常小的 α \alpha α的取值列表来让交叉验证类使用,类LassoCV自己就可以计算。
和岭回归的交叉验证类相似,除了进行交叉验证之外,LassoCV也会单独建立模型,它会找出最佳的正则化参数,然后在这个参数下按照模型评估指标进行建模。需要注意的是,LassoCV的模型评估指标选用的是均方误差,而岭回归的模型评估指标是可以自己设定的,并且默认为 R 2 R^2 R2

参数 含义
eps 正则化路径的长度,默认为0.001
n_alphas 正则化路径中 α \alpha α的个数,默认为100
alphas 需要测试的正则化参数的取值的元组,默认为None。当不输入时,自动使用eps和n_alphas来自动生成带入交叉验证的正则化参数
cv 交叉验证的次数,默认3折交叉验证,在0.22版本中改为5折交叉验证
属性 含义
alpha_ 调用交叉验证选出来的最佳正则化参数
alphas_ 使用正则化路径的长度和路径中 α \alpha α的个数来自动生成的,用来进行交叉验证的正则化参数
mse_path 返回所有交叉验证的结果细节
coef_ 调用最佳正则化参数下建立的模型的系数

看看这些参数和属性付诸实践的代码:

from sklearn.linear_model import LassoCV
#自己建立Lasso进行alpha选择的范围
alpharange = np.logspace(-10,-2,200,base = 10)
#alpharange
lasso_ = LassoCV(alphas = alpharange
                ,cv = 5
                ).fit(xtrain,ytrain)
#查看被选择出来的最佳正则化系数
lasso_.alpha_
#结果:0.0020729217795953697
#调用所有交叉验证的结果
lasso_.mse_path_
lasso_.mse_path_.shape#返回每个alpha下的五折交叉验证结果
#结果:(200, 5)
lasso_.mse_path_.mean(axis=1)#注意到岭回归中轴向是axis = 0
#在岭回归中,是留一验证,因此交叉验证结果返回的是,每一个样本在每个alpha下的交叉验证结果
#因此要求每个alpha下的交叉验证均值,就是axis = 0,跨行求均值
#在这里,返回的是每一个alpha取值下,每一折交叉验证的结果
#因此要求每个alpha下的交叉验证均值,就是axis = 1,跨行求均值
#最佳正则化系数下获得的模型的系数结果
lasso_.coef_
#结果:array([ 4.29867301e-01,  1.03623683e-02, -9.32648616e-02,  5.51755252e-01, 1.14732262e-06, -3.31941716e-03, -4.10451223e-01, -4.22410330e-01])
lasso_.score(xtest,ytest)
#结果:0.6038982670571434
#与线性回归相比如何?
reg = LinearRegression().fit(xtrain,ytrain)
reg.score(xtest,ytest)
#结果:0.6043668160178813
#使用LassoCV自带的正则化路径长度和路径中的alpha个数来自动建立alpha选择的范围
ls_ = LassoCV(eps = 0.00001
             ,n_alphas = 300
             ,cv = 5
             ).fit(xtrain,ytrain)
ls_.alpha_
#结果:0.0020954551690628557
ls_.alphas_#查看所有自动生成的alpha取值
ls_.alphas_.shape
#结果:(300,)
ls_.score(xtest,ytest)
#结果:0.60389154238192

Lasso作为线性回归家族中在改良上走得最远的算法,还有许多领域等待去探讨。比如,在现实中,不仅可以使用交叉验证来选择最佳正则化系数,也可以使用BIC(贝叶斯信息准则)或者AIC(Akaike information criterion,艾凯克信息准则)来做模型选择。同时可以不使用坐标下降法,还可以使用最小角度回归来对lasso进行计算。
当然这些方法下做的模型选择和模型计算,其实在模型效果上表现和普通的lasso没有太大的区别,不过都在各个方面对原有的lasso做了一些相应的改进(如提升了原本就已经很快的计算速度,增加了模型选择的维度,因为均方误差作为损失函数只考虑了偏差,不考虑方差的存在)。除了解决多重共线性这个核心问题之外,线性模型更重要的是:提升模型表现。这才是机器学习最核心的需求,而lasso和岭回归不是为此而设计的。

5 非线性问题:多项式回归

5.1 重塑“线性”概念

5.1.1 变量之间的线性关系

首先,“线性”这个词用于描述不同事物时有着不同的含义。最常使用的线性是指“变量之间的线性关系(linear relationship)”,它表示两个变量之间的关系可以展示为一条直线,即可以使用方程 y = a x + b y = ax+b y=ax+b来进行拟合。要探索两个变量之间的关系是否是线性的,最简单的方式就是绘制散点图,如果散点图能够相对均匀地分布在一条直线的两端,则说明两个变量之间的关系是线性的。因此,三角函数(如 s i n ( x ) sin(x) sin(x))、高次函数(如 y = a x 3 + b , ( a ≠ 0 ) y = ax^3+b,(a\neq0) y=ax3+b,(a=0))、指数函数(如 y = e x y = e^x y=ex)等等图像不为直线的函数所对应的自变量和因变量之间的非线性关系(non-linear relationship)。 y = a x + b y = ax+b y=ax+b也因此被称为线性方程或线性函数(linear function),三角函数、高次函数等也因此被称为非线性函数(non-linear function)。

5.1.2 数据的线性与非线性

从线性关系这个概念出发,有一种说法叫做“线性数据”。通常来说,一组数据由多个特征和标签组成。当这些特征分别与标签存在线性关系的时候,就说这组数据是线性数据。当特征矩阵中任意一个特征与标签之间的关系需要使用三角函数、指数函数等函数来定义,则说这种数据叫做“非线性数据”。对于线性和非线性数据,最简单的判别方法就是利用模型——如果是做分类则使用逻辑回归,如果做回归则使用线性回归,如果效果好那么数据是线性的,效果不好则数据不是线性的。当然,也可以降维后进行绘图,绘制出的图像分布接近一条直线,则数据是线性的。
sklearn中的线性回归大家族_第19张图片
不难发现,以上展示的都是或多或少能够连成线的数据分布,它们之间只不过是直线与曲线的分别罢了。然而当进行分类时,决策函数往往是一个分段函数,如二分类下的决策函数可以是符号函数 s i g n ( x ) sign(x) sign(x),符号函数的图像可以表示为取值为1和-1的两条直线。这个函数明显不符合可以使用一条直线来进行表示的属性,因此分类问题中特征与标签[0,1]或者[-1,1]之间的关系明显是非线性的关系。除非在拟合分类的概率,否则不存在例外。
sklearn中的线性回归大家族_第20张图片
还注意到,当进行分类的时候,数据的分布往往是这样的(区别线性关系图和数据分布图):
sklearn中的线性回归大家族_第21张图片
可以看出,这些数据都不能由一条直线来进行拟合,也没有均匀分布在某一条线的周围,那么怎么判断这些数据是线性数据还是非线性数据?当在回归中绘制图像时,绘制的是特征与标签的关系图,横坐标是特征,纵坐标是标签。标签是连续型的,可以通过是否能够使用一条直线来拟合图像判断数据究竟属于线性还是非线性。然而在分类数据中,使用“是否线性可分”(linearly separable)这个概念来划分分类数据集。当分类数据的分布上可以使用一条直线来将两类数据分开时,就认为数据是线性可分的。反之,数据不是线性可分的。
总结一下,对于回归问题,数据若能分布为一条直线,则是线性的,否则是非线性的。对于分类问题,数据分布若能使用一条直线来划分类别,则是线性可分的,否则数据则是线性不可分的。

5.1.3 线性模型与非线性模型

在回归中,线性数据可以使用如下的方程来进行拟合:
y = w 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n y = w_0+w_1x_1+w_2x_2+...+w_nx_n y=w0+w1x1+w2x2+...+wnxn
也就是线性回归的方程。根据线性回归的方程,可以拟合出一组参数 w \textbf w w,在这一组固定的参数下可以建立一个模型,而这个模型就被称之为是线性回归模型。所以建模的过程就是寻找参数的过程。此时建立的线性回归模型,是一个用于拟合线性数据的线性模型。作为线性模型的典型代表,可以从线性回归的方程中总结出线性模型的特点:其自变量都是一次项
接下来,建立一个明显的非线性的数据集,并观察线性回归和决策树回归在拟合非线性数据集上的表现:

#导入所需要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor
#创建需要拟合的数据集
rnd = np.random.RandomState(42)#设置随机数种子
X = rnd.uniform(-3,3,size = 100)#random.uniform,从输入的任意两个整数中取出size个随机数
#生成y的思路:先使用NumPy中的函数生成一个sin函数图像,然后再人为添加噪音
y = np.sin(X)+rnd.normal(size = len(X))/3#random.normal,生成size个服从正态分布的随机数
#使用散点图观察建立的数据集是什么样
plt.scatter(X,y,marker = "o",c = "k",s = 20)
plt.show()

sklearn中的线性回归大家族_第22张图片

#为后续建模做准备:sklearn只接受二维以上数组作为特征矩阵的输入
X.shape
#结果:(100,)
X = X.reshape(-1,1)
X.shape
#结果:(100, 1)
#使用原始数据进行建模
LinearR = LinearRegression().fit(X,y)
TreeR = DecisionTreeRegressor(random_state = 0).fit(X,y)
#放置画布
fig,ax1 = plt.subplots(1)
#创建测试数据:一系列分布在横坐标上的点
line = np.linspace(-3,3,1000,endpoint = False).reshape(-1,1)
#将测试数据代入predict接口,获得模型的拟合效果并进行绘制
ax1.plot(line,LinearR.predict(line),linewidth = 2,color = "green",label = "linear regression")
ax1.plot(line,TreeR.predict(line),linewidth = 2,color = "red",label = "decision tree")
#将原始数据上的拟合绘制在图像上
ax1.plot(X[:,0],y,"o",c = "k")
#其他图形选项
ax1.legend(loc = "best")
ax1.set_xlabel("Input feature")
ax1.set_ylabel("Regression output")
ax1.set_title("Result before discretization")
plt.tight_layout()
plt.show()

sklearn中的线性回归大家族_第23张图片
从图像上可以看出,线性回归无法拟合出这条带噪音的正弦曲线的真实面貌,只能够模拟出大概的趋势,而决策树却通过建立复杂的模型将几乎每个点都拟合出来了。可见,使用线性回归模型来拟合非线性数据的效果并不好,而决策树这样的模型却拟合得太细致,但相比之下,还是决策树的拟合效果更好一些。而决策树无法写作一个线性方程,而是一个典型的非线性模型,当它被用于拟合非线性数据,可以发挥奇效。其他典型的非线性模型还包括使用高斯核的支持向量机、树的集成算法以及一切通过三角函数、指数函数等非线性方程来建立的模型。
根据这个思路,也许可以这样推断:线性模型用于拟合线性数据,非线性模型用于拟合非线性数据。但事实上机器学习远远比想象得灵活得多,线性模型可以用来拟合非线性数据,而非线性模型也可以用来拟合线性数据,更神奇的是,有的算法没有模型也可以处理各类数据,而有的模型既可以是线性,也可以是非线性模型

  • 非线性模型拟合线性数据
    非线性模型几乎都可以在线性可分数据集上有不逊于线性模型的表现。同样的,如果使用随机森林来拟合一条直线,那随机森林毫无疑问会过拟合,因为线性数据对于非线性模型来说太简单,很容易把训练集上的 R 2 R^2 R2训练得很高,MSE训练得很低。
    sklearn中的线性回归大家族_第24张图片
  • 线性模型拟合非线性数据
    相反的,线性模型如果用来拟合非线性数据或者对非线性可分的数据进行分类,那通常都会表现得非常糟糕。通常如果已经发现数据属于非线性数据或者数据非线性可分,则不会选择使用线性模型来进行建模。改善线性模型在非线性数据上效果的方法之一就是进行分箱,并且分箱的效果不是一般得好,甚至高过一些非线性模型。但在没有其他算法或者预处理帮忙的情况下,线性模型在非线性数据上的表现是很糟糕的。
    sklearn中的线性回归大家族_第25张图片
    从上图中可以观察出一个特性:线性模型的决策边界都是一条条平行的直线,而非线性模型的决策边界是交互的直线(格子)、曲线、环形等等。对于分类模型来说,这是判断模型是线性还是非线性的重要评判因素:线性模型的决策边界是平行的直线,非线性模型的决策边界是曲线或者交叉的直线。模型上如果自变量上的最高次方是1,则模型是线性的,但这种方式只适用于回归问题。分类模型中,很少讨论模型是否线性,因为很少使用线性模型来执行分类任务(逻辑回归是一个特例),但是从上面总结的结果来看,可以认为对分类问题而言,如果一个分类模型的决策边界上自变量的最高次方为1,则称这个模型是线性模型
  • 既是线性,也是非线性的模型
    对于有一些模型来说,它们既可以处理线性模型又可以处理非线性模型,如支持向量机。支持向量机的前身是感知机模型,朴实的感知机模型是实打实的线性模型(其决策边界是直线),在线性可分数据上表现优秀,但在非线性可分的数据上基本属于无法使用状态。
    但支持向量机不同,支持向量机本身也是处理线性可分数据的,但却可以通过对数据进行升维(将数据 x x x转移到高维空间 Φ \Phi Φ中),将非线性可分数据变成高维空间中的线性可分数据,然后使用相应的“核函数”来求解。当选用线性核函数“linear”时,数据没有进行变换,支持向量机中就是线性模型,此时它的决策边界是直线。当选用非线性核函数(如高斯径向基核函数)时,数据进行了升维变化,此时支持向量机就是非线性模型,此时它的决策边界在二维空间中是曲线。所以这个模型可以在线性和非线性之间自由切换,一切取决于它的核函数。
    sklearn中的线性回归大家族_第26张图片
    还有更特殊的,没有模型的算法,如最近邻算法KNN以及朴素贝叶斯,这些都是不建模,但是能够直接预测出标签或做出判断的算法。而这些算法并没有线性非线性之分,单纯地是不建模的算法。下表进行了一些总结:
线性模型 非线性模型
代表模型 线性回归、逻辑回归、弹性网、感知机 决策树、树的集成模型、使用高斯核的SVM
模型特点 模型简单,运行速度快 模型复杂,效果好,但速度慢
数学特征:回归 自变量是一次项 自变量不都是一次项
分类 决策边界上的自变量都是一次项 决策边界上的自变量不都是一次项
可视化:回归 拟合出的图像是一条直线 拟合出的图像不是一条直线
分类 决策边界在二维平面是一条直线 决策边界在二维平面不是一条直线
擅长数据类型 主要是线性数据,线性可分数据 所有数据

模型在线性和非线性数据集上的表现为选择模型提供了思路:当获取数据时,往往希望使用线性模型来对数据进行最初的拟合(线性回归用于回归,逻辑回归用于分类),如果线性模型表现良好,则说明数据本身很可能是线性的或者线性可分的,如果线性模型表现糟糕,那毫无疑问会投入决策树、随机森林这些模型的怀抱,就不必浪费时间在线性模型上了。
不过并不代表着就完全不能使用线性模型来处理非线性数据了。在现实中,线性模型有着不可替代的优势:计算速度异常快速,所以也还是存在着无论如何也希望使用线性回归的情况。因此,产生多种手段来处理线性回归无法拟合非线性数据的问题。

5.2 使用分箱处理非线性问题

让线性回归在非线性数据上表现提升的核心方法之一是对数据进行分箱,也就是离散化。与线性回归相比,常用的一种回归是决策树回归。之前拟合过一条带有噪声的正弦曲线以展示多元线性回归与决策树的效用差异,来分析一下这张图,再采取措施帮助提升线性回归的效果。

#导入所需要的库
import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
from sklearn.tree import DecisionTreeRegressor

#创建需要拟合的数据集
rnd = np.random.RandomState(42)#设置随机数种子
X = rnd.uniform(-3,3,size = 100)#random.uniform,从输入的任意两个整数中取出size个随机数
#生成y的思路:先使用NumPy中的函数生成一个sin函数图像,然后再人为添加噪音
y = np.sin(X)+rnd.normal(size = len(X))/3#random.normal,生成size个服从正态分布的随机数
#使用散点图观察建立的数据集是什么样
plt.scatter(X,y,marker = "o",c = "k",s = 20)
plt.show()

sklearn中的线性回归大家族_第27张图片

#为后续建模做准备:sklearn只接受二维以上数组作为特征矩阵的输入
#X.shape
X = X.reshape(-1,1)
#使用原始数据进行建模
LinearR = LinearRegression().fit(X,y)
TreeR = DecisionTreeRegressor(random_state = 0).fit(X,y)

#放置画布
fig,ax1 = plt.subplots(1)
#创建测试数据:一系列分布在横坐标上的点
line = np.linspace(-3,3,1000,endpoint = False).reshape(-1,1)
#将测试数据代入predict接口,获得模型的拟合效果并进行绘制
ax1.plot(line,LinearR.predict(line),linewidth = 2,color = "green",label = "linear regression")
ax1.plot(line,TreeR.predict(line),linewidth = 2,color = "red",label = "decision tree")
#将原始数据上的拟合绘制在图像上
ax1.plot(X[:,0],y,"o",c = "k")
#其他图形选项
ax1.legend(loc = "best")
ax1.set_xlabel("Input feature")
ax1.set_ylabel("Regression output")
ax1.set_title("Result before discretization")
plt.tight_layout()
plt.show()

sklearn中的线性回归大家族_第28张图片
从图像上可以看出,线性回归无法拟合出这条带噪音的正弦曲线的真实面貌,只能够模拟出大概的趋势,而决策树却通过建立复杂的模型将几乎每个点都拟合出来了。此时决策树正处于过拟合的状态,对数据的学习过于细致,而线性回归处于拟合不足的状态,这是由于模型本身只能够在线性关系间进行拟合的性质决定的。为了让线性回归在类似的数据上变得更加强大,可以使用分箱,也就是离散化连续型变量的方法来处理原始数据,以此来提升线性回归的表现。

#分箱及分箱的相关问题
from sklearn.preprocessing import KBinsDiscretizer
#将数据分箱
enc = KBinsDiscretizer(n_bins = 10  #分几类
                       ,encode = "onehot")#“onehot”和“ordinal”
#encode模式“onehot”:使用做哑变量方式做离散化
#之后返回一个稀疏矩阵(m,n_bins),每一列是一个特征中的一个类别,含有该类别的样本表示为1,不含的表示为0
X_binned = enc.fit_transform(X)
#X
X_binned
#结果:
#<100x10 sparse matrix of type ''
#	 with 100 stored elements in Compressed Sparse Row format>
#使用pandas更易看清
import pandas as pd
pd.DataFrame(X_binned.toarray()).head()

sklearn中的线性回归大家族_第29张图片

#将使用分箱后的数据来训练模型,在sklearn中,测试集和训练集的结果必须保持一致,否则报错
LinearR_ = LinearRegression().fit(X_binned,y)
#LinearR_.predict(line) #line作为测试集
#直接运行上一句会直接报错
#ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 10 is different from 1)
line.shape#测试
#结果:(1000, 1)
X_binned.shape#训练
#结果:(100, 10)
#因此需要创建分箱后的测试集,按照已经建好的分箱模型将line分箱
line_binned = enc.transform(line)
line_binned.shape
#结果:(1000, 10)
LinearR_.predict(line_binned).shape
#结果:(1000,)

#使用分箱数据进行建模和绘图
#准备数据
enc = KBinsDiscretizer(n_bins = 10,encode = "onehot")
X_binned = enc.fit_transform(X)
line_binned = enc.transform(line)
#将两张图像绘制在一起,布置画布
fig,(ax1,ax2) = plt.subplots(ncols = 2
                            ,sharey = True  #让两张图共享y轴上的刻度
                            ,figsize = (10,4))
#在图1中布置在原始数据上建模的结果
ax1.plot(line,LinearR.predict(line),linewidth = 2,color = "green",label = "linear regression")
ax1.plot(line,TreeR.predict(line),linewidth = 2,color = "red",label = "decision tree")
ax1.plot(X[:,0],y,"o",c = "k")
ax1.legend(loc = "best")
ax1.set_ylabel("Regreesion output")
ax1.set_xlabel("Input feature")
ax1.set_title("Result before discretization")

#使用分箱数据进行建模
LinearR_ = LinearRegression().fit(X_binned,y)
TreeR_ = DecisionTreeRegressor(random_state = 0).fit(X_binned,y)
#进行预测,在图2中布置分箱数据进行预测的结果
ax2.plot(line#横坐标
        ,LinearR_.predict(line_binned)#分箱后的特征矩阵的结果
        ,linewidth = 2
        ,color = "green"
        ,linestyle = "-"
        ,label = "linear regression")
ax2.plot(line,TreeR_.predict(line_binned),linewidth = 2,color = "red",linestyle = ":",label = "decision tree")
#绘制和箱宽一致的竖线
ax2.vlines(enc.bin_edges_[0]#x轴
          ,*plt.gca().get_ylim()#y轴的上限和下限
          ,linewidth = 1
          ,alpha = 0.2)

#将原始数据分布放置在图像上
ax2.plot(X[:,0],y,"o",c = "k")
#其他绘图设定
ax2.legend(loc = "best")
ax1.set_xlabel("Input feature")
ax1.set_title("Result after discretization")
plt.tight_layout()
plt.show()

sklearn中的线性回归大家族_第30张图片
从图像上可以看出,离散化后线性回归和决策树上的预测结果完全相同了,线性回归比较成功地拟合了数据的分布,而决策树的过拟合效应也减轻了。由于特征矩阵被分箱,因此特征矩阵在每个区域内获得的值是恒定的,因此所有模型对同一个箱中所有样本都会获得相同的预测值,与分箱前的结果相比,线性回归明显变得更加灵活,而决策树的过拟合问题也得到了改善。但注意,一般来说是不使用分箱来改善决策树的过拟合问题的,因为树模型带有丰富而有效的剪枝功能来防止过拟合。上面的过程中设置的分箱箱数为10,而这个箱数的设定肯定会影响模型最后的预测结果,接下里看看不同的箱数会如何影响回归的结果:

#箱子数如何影响模型的结果
enc = KBinsDiscretizer(n_bins = 5,encode = "onehot")
x_binned = enc.fit_transform(X)
line_binned = enc.fit_transform(line)
fig,ax2 = plt.subplots(1,figsize = (5,4))

LinearR_ = LinearRegression().fit(x_binned,y)
print(LinearR_.score(line_binned,np.sin(line)))
TreeR_ = DecisionTreeRegressor(random_state = 0).fit(x_binned,y)
ax2.plot(line#横坐标
        ,LinearR_.predict(line_binned)#分箱后的特征矩阵的结果
        ,linewidth = 2
        ,color = "green"
        ,linestyle = "-"
        ,label = "linear regression")
ax2.plot(line,TreeR_.predict(line_binned),linewidth = 2,color = "red",linestyle = ":",label = "decision tree")
ax2.vlines(enc.bin_edges_[0],*plt.gca().get_ylim(),linewidth = 1,alpha = 0.2)
ax2.plot(X[:,0],y,"o",c = "k")
ax2.legend(loc = "best")
ax2.set_xlabel("Input feature")
ax2.set_ylabel("Result after discretization")
plt.tight_layout()
plt.show()
#结果:0.8314901357806623

sklearn中的线性回归大家族_第31张图片

#如何选取最优的箱数
#怎样选取最优的箱子?
from sklearn.model_selection import cross_val_score as CVS
import numpy as np
pred,score,var = [],[],[]
binsrange = [2,5,10,15,20,30]
for i in binsrange:
    #实例化分箱类
    enc = KBinsDiscretizer(n_bins = i,encode = "onehot")
    #转换数据
    x_binned = enc.fit_transform(X)
    line_binned = enc.fit_transform(line)
    #建立模型
    LinearR_ = LinearRegression()
    #全数据集上的交叉验证
    cvresult = CVS(LinearR_,x_binned,y,cv = 5)
    score.append(cvresult.mean())
    var.append(cvresult.var())
    #测试数据集上的打分结果
    pred.append(LinearR_.fit(x_binned,y).score(line_binned,np.sin(line)))
#绘制图像
plt.figure(figsize = (6,5))
plt.plot(binsrange,pred,c = "orange",label = "test")
plt.plot(binsrange,score,c = "k",label = "full_data")
plt.plot(binsrange,score+np.array(var)*0.5,c = "red",linestyle = "--",label = "var")
plt.plot(binsrange,score-np.array(var)*0.5,c = "red",linestyle = "--")
plt.legend()
plt.show()

sklearn中的线性回归大家族_第32张图片
在工业中,大量离散化变量与线性模型连用的实例很多,在深度学习出现之前,这种模式甚至一度通知一些工业中的机器学习应用场景,可见效果优秀,应用广泛。对于现在的很多工业场景而言,大量离散化特征的情况可能已经不是那么多了,但依然需要对“分箱 能够解决线性模型无法处理非线性数据的问题”有所了解。

5.3 多项式回归PolynomialFeatures

5.3.1 多项式对数据做了什么

除了分箱,另一种更普遍的用于解决“线性回归只能处理线性数据”问题的手段,就是使用多项式回归对线性回归进行改进。这样的思想是机器学习研究者们从支持向量机中获得的:支持向量机通过升维可以将非线性可分数据转化为线性可分,然后使用核函数在低维空间中进行计算,这是一种“高维呈现,低维解释”的思维。同样地,可以让线性回归使用类似于升维的转换,将数据由非线性转换为线性,从而为线性回归赋予处理非线性数据的能力。
sklearn中的线性回归大家族_第33张图片
线性模型中的升维工具:多项式变化。这是一种通过增加自变量上的次数,而将数据映射到高维空间的方法,只要设定一个自变量上的次数(大于1),就可以相应地获得数据投影在高次方的空间中的结果。这种方法可以非常容易地通过sklearn中的类PolynomialFeatures来实现。
sklearn.preprocessing.PolynomialFeatures(degree = 2, interaction_only = False, include_bias = True)

参数 含义
degree 多项式中的次数,默认为2
interaction_only 布尔值,是否只产生交互项,默认为False
include_bias 布尔值,是否产生与截距项相乘的 x 0 x_0 x0,默认为True
from sklearn.preprocessing import PolynomialFeatures
import numpy as np
#如果原始数据是一维的
X = np.arange(1,4).reshape(-1,1)
X
#结果:
#array([[1],
#       [2],
#       [3]])
X.shape
#结果:(3, 1)
##二次多项式,参数degree控制多项式的次方
poly = PolynomialFeatures(degree = 2)
#接口transform直接调用
X_ = poly.fit_transform(X)
X_
#结果:
#array([[1., 1., 1.],
#       [1., 2., 4.],
#       [1., 3., 9.]])
X_.shape
#结果:(3, 3)
#三次多项式 
PolynomialFeatures(degree = 3).fit_transform(X)
#结果:
#array([[ 1.,  1.,  1.,  1.],
#       [ 1.,  2.,  4.,  8.],
#       [ 1.,  3.,  9., 27.]])

不难注意到,多项式变化后数据看起来不太一样了:首先,数据的特征(维度)增加了,这正符合希望将数据转换到高维空间的愿望,其次,维度的增加是有一定规律的,如果原本的特征矩阵中只有一个特征 x x x,而转换后得到 x 0 、 x 、 x 2 、 x 3 x_0、x、x^2、x^3 x0xx2x3。这个规律在转换为二次多项式时同样适用。原本模型应该是形似 y = a x + b y = ax+b y=ax+b的结构,而转换后特征变化导致模型变化。根据在支持向量机中的经验,现在这个被投影到更高维空间中的数据在某个角度上看起来已经是一条直线了,于是可以继续使用线性回归来进行拟合。线性回归是会对每个特征拟合出权重 w w w的,所以当拟合高维数据时,会得到下面的模型:
y = w 0 x 0 + w 1 x + w 2 x 2 + w 3 x 3 , ( x 0 = 1 ) y = w_0x_0+w_1x+w_2x^2+w_3x^3,(x_0 = 1) y=w0x0+w1x+w2x2+w3x3,(x0=1)
由此推断,假设多项式转化的次数是 n n n,则数据会被转换为形如: [ 1 , x , x 2 , x 3 , . . . , x n ] [1,x,x^2,x^3,...,x^n] [1,x,x2,x3,...,xn]。而拟合出来的方程也可以被写成:
y = w 0 x 0 + w 1 x + w 2 x 2 + w 3 x 3 + . . . + w n x n , ( x 0 = 1 ) y = w_0x_0+w_1x+w_2x^2+w_3x^3+...+w_nx^n,(x_0 = 1) y=w0x0+w1x+w2x2+w3x3+...+wnxn,(x0=1)
这就是“多项式回归”的表达式。它是将原始的 x x x上的次方增加,并且为这些次方项都加上权重 w w w,然后增加一列所有次方为0的列作为截距乘数的 x 0 x_0 x0,参数include_bias就是用来控制 x 0 x_0 x0的生成的。

#三次多项式,不带与截距项相乘的x0
PolynomialFeatures(degree = 3,include_bias = False).fit_transform(X)
#结果:
#array([[ 1.,  1.,  1.],
#       [ 2.,  4.,  8.],
#       [ 3.,  9., 27.]])
#为什么会希望不生成与截距项相乘的x0呢?
#对于多项式回归来说,已经为线性回归准备好了x0,但是线性回归并不知道
xxx = PolynomialFeatures(degree = 3).fit_transform(X)
xxx.shape
#结果:(3, 4)
rnd = np.random.RandomState(42)#设置随机数种子
y = rnd.randn(3)
y
#结果:array([ 0.49671415, -0.1382643 ,  0.64768854])
from sklearn.linear_model import LinearRegression
#生成了多少个系数?
LinearRegression().fit(xxx,y).coef_
#结果:array([ 3.10862447e-15, -3.51045297e-01, -6.06987134e-01,  2.19575463e-01])
#查看截距
LinearRegression().fit(xxx,y).intercept_
#结果:1.2351711202036884
#可以发现:线性回归并没有把多项式生成的x0当作是截距项
#所以可以选择关闭多项式回归中的include_bias
#也可以选择关闭线性回归中的fit_intercept
#生成多少个系数?
LinearRegression(fit_intercept = False).fit(xxx,y).coef_
#结果:array([ 1.00596411,  0.06916756, -0.83619415,  0.25777663])
#查看截距
LinearRegression(fit_intercept = False).fit(xxx,y).intercept_
#结果:0.0

不过,这只是一维状况下的表达,大多数时候原始特征矩阵不可能会是一维的,至少也是二维以上,很多时候还可能存在上千个特征或者维度,接下来看看原始特征矩阵是二维的状况:

x = np.arange(6).reshape(3,2)
x
#结果:
#array([[0, 1],
#       [2, 3],
#       [4, 5]])
#尝试二次多项式
PolynomialFeatures(degree = 2).fit_transform(x)
#结果:
#array([[ 1.,  0.,  1.,  0.,  0.,  1.],
#       [ 1.,  2.,  3.,  4.,  6.,  9.],
#       [ 1.,  4.,  5., 16., 20., 25.]])

很明显,上面一维的转换公式已经不适用了,可以看出这样的规律: x 0 、 x 1 、 x 2 、 x 1 2 、 x 1 x 2 、 x 2 2 x_0、x_1、x_2、x_1^2、x_1x_2、x_2^2 x0x1x2x12x1x2x22。即当原始特征为二维时,多项式的二次变化突然增加到了六维,其中一维是常量(也就是截距)。继续使用线性回归去拟合时,会得到如下方程:
[ x 1 , x 2 ] → [ w 0 , x 1 , x 2 , x 1 x 2 , x 1 2 , x 2 2 ] [x_1,x_2] \to [w_0,x_1,x_2,x_1x_2,x_1^2,x_2^2] [x1,x2][w0,x1,x2,x1x2,x12,x22]
y = w 0 + w 1 x 1 + w 2 x 2 + w 3 x 1 x 2 + w 4 x 1 2 + w 5 x 2 2 y = w_0+w_1x_1+w_2x_2+w_3x_1x_2+w_4x_1^2+w_5x_2^2 y=w0+w1x1+w2x2+w3x1x2+w4x12+w5x22
为了总结规律,接下来尝试三次多项式:

#尝试三次多项式
PolynomialFeatures(degree = 3).fit_transform(x)
#结果:
#array([[  1.,   0.,   1.,   0.,   0.,   1.,   0.,   0.,   0.,   1.],
#       [  1.,   2.,   3.,   4.,   6.,   9.,   8.,  12.,  18.,  27.],
#       [  1.,   4.,   5.,  16.,  20.,  25.,  64.,  80., 100., 125.]])

很明显,可以看出以上生成数据的规律为: x 0 、 x 1 、 x 2 、 x 1 2 、 x 1 x 2 、 x 2 2 、 x 1 3 、 x 1 2 x 2 、 x 1 x 2 2 、 x 2 3 x_0、x_1、x_2、x_1^2、x_1x_2、x_2^2、x_1^3、x_1^2x_2、x_1x_2^2、x_2^3 x0x1x2x12x1x2x22x13x12x2x1x22x23。不难发现:当进行多项式转换时,多项式会产出到最高次数为止的所有低高次项。比如,如果规定多项式的次数为2,多项式就会产出所有次数为1和次数为2的项,相应的如果规定多项式的次数为 n n n,则多项式会产出所有从次数1到次数 n n n的项。注意, x 1 x 2 x_1x_2 x1x2 x 1 2 x_1^2 x12一样都是二次项,一个自变量的平方其实也就是相当于是 x 1 x 1 x_1x_1 x1x1,所以在三次多项式中 x 1 2 x 2 x_1^2x_2 x12x2就是三次项。
在多项式回归中,可以规定是否产生平方或者立方项,其实如果只要求高次项的话, x 1 x 2 x_1x_2 x1x2会是一个比 x 1 2 x_1^2 x12更好的高次项,因为 x 1 x 2 x_1x_2 x1x2 x 1 x_1 x1之间的共线性会比 x 1 2 x_1^2 x12 x 1 x_1 x1之间的共线性好一点(只是一点点),而多项式转换之后是需要使用线性回归模型来进行拟合的,计算机器学习中不是那么在意数据上的基本假设,但是太过分的共线性还是会影响到模型的拟合。因此sklearn中存在控制是否要生成平方项和立方项的参数interaction_only,默认为False,以减少共线性。看看这个参数是如何工作的:

PolynomialFeatures(degree = 2).fit_transform(x)
#结果:
#array([[ 1.,  0.,  1.,  0.,  0.,  1.],
#       [ 1.,  2.,  3.,  4.,  6.,  9.],
#       [ 1.,  4.,  5., 16., 20., 25.]])
PolynomialFeatures(degree = 2,interaction_only = True).fit_transform(x)
#对比之下,当interaction_only = True时,只生成交互项
#结果:
#array([[ 1.,  0.,  1.,  0.],
#       [ 1.,  2.,  3.,  6.],
#       [ 1.,  4.,  5., 20.]])

从之前的尝试中可以看出,随着多项式的次数逐渐变高,特征矩阵会被转化得越来越复杂。不仅是次数,当特征矩阵中的维度数(特征数)增加的时候,多项式同样会变得更加复杂:

#更高维度的原始特征矩阵
x = np.arange(9).reshape(3,3)
x
#结果:
#array([[0, 1, 2],
#       [3, 4, 5],
#       [6, 7, 8]])
PolynomialFeatures(degree = 2).fit_transform(x)
#结果:
#array([[ 1.,  0.,  1.,  2.,  0.,  0.,  0.,  1.,  2.,  4.],
#       [ 1.,  3.,  4.,  5.,  9., 12., 15., 16., 20., 25.],
#       [ 1.,  6.,  7.,  8., 36., 42., 48., 49., 56., 64.]])
PolynomialFeatures(degree = 3).fit_transform(x)
'''
结果:
array([[  1.,   0.,   1.,   2.,   0.,   0.,   0.,   1.,   2.,   4.,   0.,
          0.,   0.,   0.,   0.,   0.,   1.,   2.,   4.,   8.],
       [  1.,   3.,   4.,   5.,   9.,  12.,  15.,  16.,  20.,  25.,  27.,
         36.,  45.,  48.,  60.,  75.,  64.,  80., 100., 125.],
       [  1.,   6.,   7.,   8.,  36.,  42.,  48.,  49.,  56.,  64., 216.,
        252., 288., 294., 336., 384., 343., 392., 448., 512.]])
'''
x_ = PolynomialFeatures(degree = 20).fit_transform(x)
x_.shape
#结果:(3, 1771)

如此,多项式变化对于数据会有怎样的影响就一目了然了:随着原特征矩阵的维度上升,随着规定的最高次数的上升,数据会变得越来越复杂,维度越来越多,并且这种维度的增加并不能用太简单的数学公式表达出来。因此,多项式回归没有固定的模型表达式,多项式回归的模型最终长什么样子是由数据和最高次数决定的,因此无法断言说某个数学表达式“就是多项式回归的数学表达”,因此要求解多项式回归不是一件容易的事儿。接下来看看多项式回归的根本作用:处理非线性问题。

5.3.2 多项式回归处理非线性问题

希望通过将数据投影到高维的方式来帮助解决非线性问题,那么多项式转化对模型造成了什么影响:

from sklearn.preprocessing import PolynomialFeatures as PF
from sklearn.linear_model import LinearRegression
import numpy as np

rnd = np.random.RandomState(42)#设置随机数种子
x = rnd.uniform(-3,3,size = 100)
y = np.sin(x)+rnd.normal(size = len(x))/3
#将x升维,准备好放入sklearn中
x = x.reshape(-1,1)
#创建测试数据,均匀分布在训练集x的取值范围内的一千个点
line = np.linspace(-3,3,1000,endpoint=False).reshape(-1,1)
#原始特征矩阵的拟合结果
LinearR = LinearRegression().fit(x,y)
#对训练数据打分
LinearR.score(x,y)
#结果:0.5361526059318595
#对测试数据的拟合
LinearR.score(line,np.sin(line))
#结果:0.6800102369793313
#多项式拟合,设定高次项
d = 5
#进行高次项转换
poly = PF(degree = d)
x_ = poly.fit_transform(x)
line_ = poly.transform(line)
#训练数据的拟合
LinearR_ = LinearRegression().fit(x_,y)
LinearR_.score(x_,y)
#结果:0.8561679370344799
#测试数据的拟合
LinearR_.score(line_,np.sin(line))
#结果:0.9868904451787948

将上面的过程进行可视化:

import matplotlib.pyplot as plt
d = 5

#和上面展示一致的建模流程
LinearR = LinearRegression().fit(x,y)
x_ = PF(degree = d).fit_transform(x)
LinearR_ = LinearRegression().fit(x_,y)
line = np.linspace(-3,3,1000,endpoint = False).reshape(-1,1)
line_ = PF(degree = d).fit_transform(line)

#放置画布
fig,ax1 = plt.subplots(1)
#将测试数据代入predict接口,获得模型的拟合结果并进行绘制
ax1.plot(line,LinearR.predict(line),linewidth = 2,color = "green",label = "linear regression")
ax1.plot(line,LinearR_.predict(line_),linewidth = 2,color = "red",label = "Polynomial regression")
#将原数据上的拟合绘制在图像上
ax1.plot(x[:,0],y,"o",c = "k")

#其他图形选项
ax1.legend(loc = "best")
ax1.set_ylabel("Regression output")
ax1.set_xlabel("Input feature")
ax1.set_title("Linear Regression ordinary vs. poly")
plt.tight_layout()
plt.show()

#还可以试试较低和较高次方会发生什么变化d = 2或d = 20

sklearn中的线性回归大家族_第34张图片
可以看出,多项式回归能够较好地拟合非线性数据,还不容易发生过拟合,可以说是保留了线性回归作为线性模型所带的“不容易过拟合”和“计算快速”的性质,同时又实现了优秀地拟合非线性数据。

5.3.3 多项式回归的可解释性

线性回归是一个具有高解释性的模型,它能够对每个特征拟合出参数 w w w以帮助理解每个特征对于标签的作用。当进行了多项式转换后,尽管还是形成形如线性回归的方程,但随着数据维度和多项式次数的上升,方程也变得异常复杂,可能无法一眼看出增维后的特征是由之前的什么特征组成的。不过,多项式回归的可解释性依然是存在的,可以使用接口get_feature_names来调用生成的新特征矩阵的名称,以便帮助解释模型。

import numpy as np
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
x = np.arange(9).reshape(3,3)
x
#结果:
#array([[0, 1, 2],
#       [3, 4, 5],
#       [6, 7, 8]])
poly = PolynomialFeatures(degree = 5).fit(x)
#重要接口get_feature_names
poly.get_feature_names()
'''
结果:
['1',
 'x0',
 'x1',
 'x2',
 'x0^2',
 'x0 x1',
 'x0 x2',
 'x1^2',
 'x1 x2',
 'x2^2',
 'x0^3',
 'x0^2 x1',
 'x0^2 x2',
 'x0 x1^2',
 'x0 x1 x2',
 'x0 x2^2',
 'x1^3',
 'x1^2 x2',
 'x1 x2^2',
 'x2^3',
 'x0^4',
 'x0^3 x1',
 'x0^3 x2',
 'x0^2 x1^2',
 'x0^2 x1 x2',
 'x0^2 x2^2',
 'x0 x1^3',
 'x0 x1^2 x2',
 'x0 x1 x2^2',
 'x0 x2^3',
 'x1^4',
 'x1^3 x2',
 'x1^2 x2^2',
 'x1 x2^3',
 'x2^4',
 'x0^5',
 'x0^4 x1',
 'x0^4 x2',
 'x0^3 x1^2',
 'x0^3 x1 x2',
 'x0^3 x2^2',
 'x0^2 x1^3',
 'x0^2 x1^2 x2',
 'x0^2 x1 x2^2',
 'x0^2 x2^3',
 'x0 x1^4',
 'x0 x1^3 x2',
 'x0 x1^2 x2^2',
 'x0 x1 x2^3',
 'x0 x2^4',
 'x1^5',
 'x1^4 x2',
 'x1^3 x2^2',
 'x1^2 x2^3',
 'x1 x2^4',
 'x2^5']
'''

使用加利福尼亚房屋价值数据集作为例子,当有标签名称时,可以直接在接口get_feature_names()中输入标签名称来查看新特征究竟是由原特征矩阵中的什么特征组成的:

from sklearn.datasets import fetch_california_housing as fch
import pandas as pd
housevalue = fch()
x = pd.DataFrame(housevalue.data)
y = housevalue.target
housevalue.feature_names
#结果:
#['MedInc',
# 'HouseAge',
# 'AveRooms',
# 'AveBedrms',
# 'Population',
# 'AveOccup',
# 'Latitude',
# 'Longitude']
x.columns = ["住户收入中位数","房屋使用年代中位数","平均房间数目","平均卧室数目","街区人口","平均入住率","街区的纬度","街区的经度"]
poly = PolynomialFeatures(degree = 2).fit(x,y)
poly.get_feature_names(x.columns)
'''
结果:
['1',
 '住户收入中位数',
 '房屋使用年代中位数',
 '平均房间数目',
 '平均卧室数目',
 '街区人口',
 '平均入住率',
 '街区的纬度',
 '街区的经度',
 '住户收入中位数^2',
 '住户收入中位数 房屋使用年代中位数',
 '住户收入中位数 平均房间数目',
 '住户收入中位数 平均卧室数目',
 '住户收入中位数 街区人口',
 '住户收入中位数 平均入住率',
 '住户收入中位数 街区的纬度',
 '住户收入中位数 街区的经度',
 '房屋使用年代中位数^2',
 '房屋使用年代中位数 平均房间数目',
 '房屋使用年代中位数 平均卧室数目',
 '房屋使用年代中位数 街区人口',
 '房屋使用年代中位数 平均入住率',
 '房屋使用年代中位数 街区的纬度',
 '房屋使用年代中位数 街区的经度',
 '平均房间数目^2',
 '平均房间数目 平均卧室数目',
 '平均房间数目 街区人口',
 '平均房间数目 平均入住率',
 '平均房间数目 街区的纬度',
 '平均房间数目 街区的经度',
 '平均卧室数目^2',
 '平均卧室数目 街区人口',
 '平均卧室数目 平均入住率',
 '平均卧室数目 街区的纬度',
 '平均卧室数目 街区的经度',
 '街区人口^2',
 '街区人口 平均入住率',
 '街区人口 街区的纬度',
 '街区人口 街区的经度',
 '平均入住率^2',
 '平均入住率 街区的纬度',
 '平均入住率 街区的经度',
 '街区的纬度^2',
 '街区的纬度 街区的经度',
 '街区的经度^2']
'''
x_ = poly.transform(x)
#在这之后,依然可以直接建立模型,然后使用线性回归的coef_属性来查看什么特征对标签的影响最大
reg = LinearRegression().fit(x_,y)
coef = reg.coef_
[*zip(poly.get_feature_names(x.columns),reg.coef_)]
'''
结果:
[('1', 5.91955490391196e-08),
 ('住户收入中位数', -11.243025331812477),
 ('房屋使用年代中位数', -0.8488985486598638),
 ('平均房间数目', 6.4410590474305245),
 ('平均卧室数目', -31.591329075514484),
 ('街区人口', 0.00040609052319174905),
 ('平均入住率', 1.0038623331888172),
 ('街区的纬度', 8.705681906277963),
 ('街区的经度', 5.880632747708473),
 ('住户收入中位数^2', -0.031308128932015324),
 ('住户收入中位数 房屋使用年代中位数', 0.0018599475753805967),
 ('住户收入中位数 平均房间数目', 0.04330204746002773),
 ('住户收入中位数 平均卧室数目', -0.18614228106446193),
 ('住户收入中位数 街区人口', 5.728314621293758e-05),
 ('住户收入中位数 平均入住率', -0.002590194646868784),
 ('住户收入中位数 街区的纬度', -0.15250571489459583),
 ('住户收入中位数 街区的经度', -0.14424294139812083),
 ('房屋使用年代中位数^2', 0.00021172531993121722),
 ('房屋使用年代中位数 平均房间数目', -0.001262190027965632),
 ('房屋使用年代中位数 平均卧室数目', 0.010611505323046624),
 ('房屋使用年代中位数 街区人口', 2.8188513449406427e-06),
 ('房屋使用年代中位数 平均入住率', -0.0018171695149762587),
 ('房屋使用年代中位数 街区的纬度', -0.01006903722502449),
 ('房屋使用年代中位数 街区的经度', -0.009999501746049888),
 ('平均房间数目^2', 0.007269477625806538),
 ('平均房间数目 平均卧室数目', -0.06890643733681577),
 ('平均房间数目 街区人口', -6.823658344886838e-05),
 ('平均房间数目 平均入住率', 0.026887884047270265),
 ('平均房间数目 街区的纬度', 0.08750899026955807),
 ('平均房间数目 街区的经度', 0.08228903545280704),
 ('平均卧室数目^2', 0.1601809560404781),
 ('平均卧室数目 街区人口', 0.000514264131156233),
 ('平均卧室数目 平均入住率', -0.08719114222743157),
 ('平均卧室数目 街区的纬度', -0.43704300697085413),
 ('平均卧室数目 街区的经度', -0.40415058553338257),
 ('街区人口^2', 2.7377939915140814e-09),
 ('街区人口 平均入住率', 1.9142674908982638e-05),
 ('街区人口 街区的纬度', 2.295297873877722e-05),
 ('街区人口 街区的经度', 1.4656773026033854e-05),
 ('平均入住率^2', 8.715610619899113e-05),
 ('平均入住率 街区的纬度', 0.021334459217885335),
 ('平均入住率 街区的经度', 0.016241293831896087),
 ('街区的纬度^2', 0.0618867357572938),
 ('街区的纬度 街区的经度', 0.10810717324957053),
 ('街区的经度^2', 0.03990773506144107)]
'''
#放到dataframe中进行排序
coeff = pd.DataFrame([poly.get_feature_names(x.columns),reg.coef_.tolist()]).T
coeff.columns = ["feature","coef"]
coeff.sort_values(by = "coef")

sklearn中的线性回归大家族_第35张图片
可以发现,不仅数据的可解释性还在,而且还可以通过这样的手段做特征工程(特征创造)。多项式转换进行了一系列特征之间相乘的组合,若能够找出组合起来后对标签贡献巨大的特征,那就是创造了新的有效特征,对于任何科学而言发现新特征都是非常有价值的。接着在这个数据集上来确认多项式回归提升模型表现的能力:

#顺便可以查看一下多项式变化之后,模型的拟合效果如何
poly = PolynomialFeatures(degree = 4).fit(x,y)
x_ = poly.transform(x)
reg = LinearRegression().fit(x,y)
reg.score(x,y)
#结果:0.606232685199805
from time import time
time0 = time()
reg_ = LinearRegression().fit(x_,y)
print("R2:{}".format(reg_.score(x_,y)))
print("time:{}".format(time()-time0))
#结果:
#R2:0.745294009169007
#time:0.37960076332092285

#使用其他模型
from sklearn.ensemble import RandomForestRegressor as RFR
time0 = time()
print("R2:{}".format(RFR(n_estimators = 100).fit(x,y).score(x,y)))
print("time:{}".format(time()-time0))
#结果:
#R2:0.9741048124241694
#time:10.637548446655273

5.3.4 线性还是非线性?

另一个围绕多项式回归的核心问题是,多项式回归是线性模型还是非线性模型?从之前对线性模型的定义来看,自变量上需要没有高次项才是线性模型,按照这个定义,在 x x x上添上高次方的多项式回归肯定不是线性模型了。然而事情没有这么简单。来看原始特征为二维,多项式次数为二次的多项式回归表达式:
y = w 0 + w 1 x 1 + w 2 x 2 + w 3 x 1 x 2 + w 4 x 1 2 + w 5 x 2 2 y = w_0+w_1x_1+w_2x_2+w_3x_1x_2+w_4x_1^2+w_5x_2^2 y=w0+w1x1+w2x2+w3x1x2+w4x12+w5x22
经过变化后的数据有六个特征,分别是: [ w 0 、 x 1 、 x 2 、 x 1 x 2 、 x 1 2 、 x 2 2 ] [w_0、x_1、x_2、x_1x_2、x_1^2、x_2^2] [w0x1x2x1x2x12x22]。可以一眼看出从第四个特征开始都是高次特征,而这些高次特征与 y y y之间的关系必然不是线性的。但是也可以换一种方式来思考:假设不知道这些特征是由多项式变换改变来的,只是拿到了含有六个特征的任意数据,于是这六个特征就是: [ z 0 、 z 1 、 z 2 、 z 3 、 z 4 、 z 5 ] [z_0、z_1、z_2、z_3、z_4、z_5] [z0z1z2z3z4z5]
通过检验发现, z 1 z_1 z1 z 4 、 z 5 z_4、z_5 z4z5之间存在一定的共线性,但是现实中数据不太可能完全不相关,因此一部分的共线性是合理的。所以使用线性回归来对数据进行拟合,得到方程:
y = w 0 z 0 + w 1 z 1 + w 2 z 2 + w 3 z 3 + w 4 z 4 + w 5 z 5 y = w_0z_0+w_1z_1+w_2z_2+w_3z_3+w_4z_4+w_5z_5 y=w0z0+w1z1+w2z2+w3z3+w4z4+w5z5
此时就是一个线性方程了,并存在任何高次项,只要拟合结果不错,大概也没人会在意这六个特征的原始数据是怎么来的,那多项式回归就变成一个含有部分共线性的线性方程了。在许多教材中,多项式回归被称为“线性回归的一种特殊情况”。因此许多人会产生混淆:多项式回归究竟是线性模型还是非线性模型?这需要理解“线性模型”狭义和广义的定义。

狭义线性模型 vs. 广义线性模型
狭义线性模型:自变量上不能有高次项,自变量与标签之间不能存在非线性关系。广义线性模型:只要标签与模型拟合出的参数之间的关系是线性的,模型就是线性的。这就是说,只要生成的一系列 w w w之间没有相乘或者相除的关系,就认为模型是线性的。

就多项式回归本身的性质来说,如果考虑狭义线性模型的定义,那肯定是一种非线性模型,否则如何能够处理非线性数据,并且在统计学中认为,特征之间若存在精确相关关系或高度相关关系,线性模型的估计就会被“扭曲”,从而失真或难以估计准确。多项式正是利用线性回归的这种“扭曲”,为线性模型赋予了处理非线性数据的能力。但如果考虑广义线性模型的定义,多项式回归就是一种线性模型,毕竟其系数 w w w之间也没有相乘或者相除。
另外,在Python处理数据时,并不知道这些特征是由多项式变化来的,它只注意到这些特征之间高度相关,然而既然使用线性回归,那就忠实地执行命令,因此Python看待数据的方式是并不了解数据之间真实关系的建模,更倾向于认为多项式回归是一种是特殊的线性模型。不过,它中间的特征包含了相当的共线性,如果处理线性数据,会是严重失误的。关于多项式回归究竟是线性还是非线性的讨论,可以参考这一片非常优秀的stackexchange问答:https://stats.stackexchange.com/questions/92065/why-is-polynomial-regression-considered-a-special-case-of-multiple-linear-regres
总结一下,多项式回归通常被认为是非线性模型,但广义上它是一种特殊的线性模型,它能够处理非线性数据,是线性回归的一种进化,要理解多项式回归的争议从哪里来,并且能够解释清楚观察多项式回归的不同角度,以避免混淆。另外一个需要注意的点是,线性回归进行多项式变化后被称为多项式回归,但这并不代表多项式变化只能够与线性回归连用。在现实中,多项式变化疯狂增加数据维度的同时,也增加了过拟合的可能性,因此多项式变化多与能够处理过拟合的线性模型(如岭回归、Lasso等)连用,与在线性回归上使用的效果是一致的。多项式回归主要是通过对自变量上的次方进行调整,来为线性回归赋予更多的学习能力,它的核心表现在提升模型的现有数据集上的表现。
多元线性回归、岭回归、Lasso和多项式回归这四个算法,都是围绕着原始的线性回归进行的拓展和改进。其中岭回归和Lasso是为了解决多元线性回归中使用最小二乘法的各种限制,主要用途是消除多重共线性带来的影响并做特征选择,而多项式回归解决了线性回归无法拟合非线性数据的明显缺点,核心作用是提升模型的表现。

参考资料:https://www.bilibili.com/video/BV1WL4y1H7rD?p=77

你可能感兴趣的:(机器学习,sklearn,线性回归,机器学习)