sklearn实战-----9.sklearn中的线性回归大家族

1 概述

1.1 线性回归大家族

回归是一种应用广泛的预测建模技术,这种技术的核心在于预测的结果是连续型变量。决策树,随机森林,支持向量机的分类器等分类算法的预测标签是分类变量,多以{0,1}来表示,而无监督学习算法比如PCA,KMeans并不求解标签,注意加以区别。回归算法源于统计学理论,它可能是机器学习算法中产生最早的算法之一,其在现实中的应用非常广泛,包括使用其他经济指标预测股票市场指数,根据喷射流的特征预测区域内的降水量,根据公司的广告花费预测总销售额,或者根据有机物质中残留的碳-14的量来估计化石的年龄等等,只要一切基于特征预测连续型变量的需求,我们都使用回归技术。

既然线性回归是源于统计分析,是结合机器学习与统计学的重要算法。通常来说,我们认为统计学注重先验,而机器学习看重结果,因此机器学习中不会提前为线性回归排除共线性等可能会影响模型的因素,反而会先建立模型以查看
效果。模型确立之后,如果效果不好,我们就根据统计学的指导来排除可能影响模型的因素。我们的课程会从机器学习的角度来为大家讲解回归类算法,如果希望理解统计学角度的小伙伴们,各种统计学教材都可以满足你的需求。

回归需求在现实中非常多,所以我们自然也有各种各样的回归类算法。最著名的就是我们的线性回归和逻辑回归,从他们衍生出了岭回归,Lasso,弹性网,除此之外,还有众多分类算法改进后的回归,比如回归树,随机森林的回
归,支持向量回归,贝叶斯回归等等。除此之外,我们还有各种鲁棒的回归:比如RANSAC,Theil-Sen估计,胡贝尔回归等等。考虑到回归问题在现实中的泛用性,回归家族可以说是非常繁荣昌盛,家大业大了。

回归类算法的数学相对简单,相信在经历了逻辑回归,主成分分析与奇异值分解,支持向量机这三个章节之后,大家不会感觉到线性回归中的数学有多么困难。通常,理解线性回归可以有两种角度:矩阵的角度和代数的角度。几乎所
有机器学习的教材都是从代数的角度来理解线性回归的,类似于我们在逻辑回归和支持向量机中做的那样,将求解参数的问题转化为一个带条件的最优化问题,然后使用三维图像让大家理解求极值的过程。如果大家掌握了逻辑回归和支持向量机,这个过程可以说是相对简单的,因此我们在本章节中就不进行赘述了。相对的,在我们的课程中一直都缺乏比较系统地使用矩阵来解读算法的角度,因此在本堂课中,我将全程使用矩阵方式(线性代数的方式)为大家展现回归大家族的面貌

学完这堂课之后,大家需要对线性模型有个相对全面的了解,尤其是需要掌握线性模型究竟存在什么样的优点和问题,并且如何解决这些问题。

1.2 sklearn中的线性回归

sklearn中的线性模型模块是linear_model,我们曾经在学习逻辑回归的时候提到过这个模块。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 通过最小化SGD的正则化损失函数来拟合线性模型
linear_model.TheilSenRegressor Theil-Sen估计器,一种鲁棒的多元回归模型

2 多元线性回归LinearRegression

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

线性回归是机器学习中最简单的回归算法,多元线性回归指的就是一个样本有多个特征的线性回归问题。对于一个有 n n n个特征的样本 i i i而言,它的回归结果可以写作一个几乎人人熟悉的方程:
在这里插入图片描述
w w w被统称为模型的参数,其中 w 0 w_0 w0被称为截距(intercept), w 1 w_1 w1~ w n w_n wn 被称为回归系数(regression coefficient),有时也是使用 θ \theta θ或者 β \beta β来表示。这个表达式,其实就和我们小学时就无比熟悉的 y = a x + b y=ax+b y=ax+b是同样的性质。其中 y y y是我们的目标变量,也就是标签。 x i 1 x_{i1} xi1~ x i n x_{in} xin 是样本 i i i上的特征不同特征。如果考虑我们有m个样本,则回归结果可以被写作:
在这里插入图片描述
其中 y y y是包含了m个全部的样本的回归结果的列向量。注意,我们通常使用粗体的小写字母来表示列向量,粗体的大写字母表示矩阵或者行列式。我们可以使用矩阵来表示这个方程,其中 w w w可以被看做是一个结构为(n+1,1)的列矩阵 X X X是一个结构为(m,n+1)的特征矩阵,则有:
sklearn实战-----9.sklearn中的线性回归大家族_第1张图片
线性回归的任务,就是构造一个预测函数来映射输入的特征矩阵 X X X和标签值 y y y的线性关系,这个预测函数在不同的教材上写法不同,可能写作 f ( x ) f(x) f(x) y w ( x ) y_w(x) yw(x),或者 h ( x ) h(x) h(x)等等形式,但无论如何,这个预测函数的本质就是我们需要构建的模型,而构造预测函数的核心就是找出模型的参数向量 w w w。但我们怎样才能够求解出参数向量呢?

记得在逻辑回归和SVM中,我们都是先定义了损失函数,然后通过最小化损失函数或损失函数的某种变化来将求解参数向量,以此将单纯的求解问题转化为一个最优化问题。在多元线性回归中,我们的损失函数如下定义:
sklearn实战-----9.sklearn中的线性回归大家族_第2张图片
其中 y i y_i yi是样本 i i i对应的真实标签, y ^ i \hat y_i y^i,也就是 X i w X_iw Xiw是样本 i i i在一组参数 w w w下的预测标签。

首先,这个损失函数代表了向量 y − y i y-y_i yyi的L2范式的平方结果,L2范式的本质是就是欧式距离,即是两个向量上的每个点对应相减后的平方和再开平方,我们现在只实现了向量上每个点对应相减后的平方和,并没有开方,所以我们的损失函数是L2范式,即欧式距离的平方结果。

在这个平方结果下,我们的 y y y y ^ \hat y y^分别是我们的真实标签和预测值,也就是说,这个损失函数实在计算我们的真实标签和预测值之间的距离。因此,我们认为这个损失函数衡量了我们构造的模型的预测结果和真实标签的差异,因此我
们固然希望我们的预测结果和真实值差异越小越好。所以我们的求解目标就可以转化成:
在这里插入图片描述
其中右下角的2表示向量 y − X w y-Xw yXw的L2范式,也就是我们的损失函数所代表的含义。在L2范式上开平方,就是我们的损失函数。这个式子,也正是sklearn当中,用在Linear_model.LinerRegression背后使用的损失函数。我们往往称呼这个式子为SSE(Sum of Sqaured Error,误差平方和)或者RSS(Residual Sum of Squares 残差平方和)。在sklearn所有官方文档和网页上,我们都称之为RSS残差平方和,因此在我们的课件中我们也这样称呼。

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

现在问题转换成了求解让RSS最小化的参数向量 w w w ,这种通过最小化真实值和预测值之间的RSS来求解参数的方法叫做最小二乘法。求解极值的第一步往往是求解一阶导数并让一阶导数等于0,最小二乘法也不能免俗。因此,我们现在残差平方和RSS上对参数向量 w w w求导。这里的过程涉及到少数矩阵求导的内容,需要查表来确定,感兴趣可以走维基百科去查看矩阵求导的详细公式的表格:

https://en.wikipedia.org/wiki/Matrix_calculus

接下来,我们就来对 w w w求导:
sklearn实战-----9.sklearn中的线性回归大家族_第3张图片
sklearn实战-----9.sklearn中的线性回归大家族_第4张图片
我们让求导后的一阶导数为0:
sklearn实战-----9.sklearn中的线性回归大家族_第5张图片
到了这里,我们希望能够将 w w w留在等式的左边,其他与特征矩阵有关的部分都放到等式的右边,如此就可以求出 w w w的最优解了。这个功能非常容易实现,只需要我们左乘 X T X X^TX XTX的逆矩阵就可以。在这里,逆矩阵存在的充分必要条件是特征矩阵不存在多重共线性。我们将会在第四节详细讲解多重共线性这个主题。

假设矩阵的逆是存在的,此时我们的 w w w就是我们参数的最优解。求解出这个参数向量,我们就解出了我们的 X w Xw Xw,也就能够计算出我们的预测值 y ^ \hat y y^了。对于多元线性回归的理解,到这里就足够了,如果大家还希望继续深入,那我可以给大家一个方向:你们知道矩阵 X T X X^TX XTX其实是使用奇异值分解来进行求解的吗?可以仔细去研究一下。以算法工程师和数据挖掘工程师为目标的大家,能够手动推导上面的求解过程是基本要求。

除了多元线性回归的推导之外,这里还需要提到一些在上面的推导过程中不曾被体现出来的问题。在统计学中,使用最小二乘法来求解线性回归的方法是一种”无偏估计“的方法,这种无偏估计要求因变量,也就是标签的分布必须服从正态分布。这是说,我们的y必须经由正太化处理(比如说取对数,或者使用在第三章《数据预处理与特征工程》中提到的类QuantileTransformer或者PowerTransformer)。在机器学习中,我们会先考虑模型的效果,如果模型效
果不好,那我们可能考虑改变因变量的分布。

2.3 linear_model.LinearRegression

class 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的时候输入多维度标签就可以了。

  • 来做一次回归试试看吧
  1. 导入需要的模块和库
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
  1. 导入数据,探索数据
housevalue = fch() #会需要下载,大家可以提前运行试试看

housevalue.data

X = pd.DataFrame(housevalue.data) #放入DataFrame中便于查看

X.shape
#(20640, 8)

y = housevalue.target
y.min()
#0.14999
y.max()
#5.00001
y.shape
#(20640,)

X.head()

housevalue.feature_names #特征名字

X.columns = housevalue.feature_names

sklearn实战-----9.sklearn中的线性回归大家族_第6张图片
sklearn实战-----9.sklearn中的线性回归大家族_第7张图片

"""
MedInc:该街区住户的收入中位数
HouseAge:该街区房屋使用年代的中位数
AveRooms:该街区平均的房间数目
AveBedrms:该街区平均的卧室数目
Population:街区人口
AveOccup:平均入住率
Latitude:街区的纬度
Longitude:街区的经度
"""
  1. 分训练集和测试集
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)训练集和测试集
  1. 建模
reg = LR().fit(Xtrain, Ytrain)
yhat = reg.predict(Xtest) #预测我们的yhat
yhat.min()
#-0.6528439725035966
yhat.max()
#7.146198214270875
  1. 探索建好的模型
reg.coef_ #w,系数向量

[*zip(Xtrain.columns,reg.coef_)]


sklearn实战-----9.sklearn中的线性回归大家族_第8张图片
"

""
MedInc:该街区住户的收入中位数
HouseAge:该街区房屋使用年代的中位数
AveRooms:该街区平均的房间数目
AveBedrms:该街区平均的卧室数目
Population:街区人口
AveOccup:平均入住率
Latitude:街区的纬度
Longitude:街区的经度
"""

reg.intercept_
#-36.25689322920386
属性 含义
coef_ 数组,形状为 (n_features, )或者(n_targets, n_features);线性回归方程中估计出的系数。如果在fit中传递多个标签(当y为二维或以上的时候),则返回的系数是形状为(n_targets,n_features)的二维数组,而如果仅传递一个标签,则返回的系数是长度为n_features的一维数组
intercept_ 数组,线性回归中的截距项。

建模的过程在sklearn当中其实非常简单,但模型的效果如何呢?接下来我们来看看多元线性回归的模型评估指标。

3 回归类的模型评估指标

回归类算法的模型评估一直都是回归算法中的一个难点,但不像我们曾经讲过的无监督学习算法中的轮廓系数等等评估指标,回归类与分类型算法的模型评估其实是相似的法则——找真实标签和预测值的差异。只不过在分类型算法中,这个差异只有一种角度来评判,那就是是否预测到了正确的分类,而在我们的回归类算法中,我们有两种不同的角度来看待回归的效果:

第一,我们是否预测到了正确的数值。

第二,我们是否拟合到了足够的信息。

这两种角度,分别对应着不同的模型评估指标。

3.1 是否预测了正确的数值

回忆一下我们的RSS残差平方和,它的本质是我们的预测值与真实值之间的差异,也就是从第一种角度来评估我们回归的效力,所以RSS既是我们的损失函数,也是我们回归类模型的模型评估指标之一。但是,RSS有着致命的缺点:
它是一个无界的和,可以无限地大。我们只知道,我们想要求解最小的RSS,从RSS的公式来看,它不能为负,所以RSS越接近0越好,但我们没有一个概念,究竟多小才算好,多接近0才算好?为了应对这种状况,sklearn中使用RSS的变体,均方误差MSE(mean squared error)来衡量我们的预测值和真实值的差异:
在这里插入图片描述
均方误差,本质是在RSS的基础上除以了样本总量,得到了每个样本量上的平均误差。有了平均误差,我们就可以将平均误差和我们的标签的取值范围在一起比较,以此获得一个较为可靠的评估依据。在sklearn当中,我们有两种方式调用这个评估指标,一种是使用sklearn专用的模型评估模块metrics里的mean_squared_error,另一种是调用交叉验证的类cross_val_score并使用里面的scoring参数来设置使用均方误差。

from sklearn.metrics import mean_squared_error as MSE
MSE(yhat,Ytest)
#0.5309012639324571
Ytest.mean()
#2.0819292877906976
y.max()
#5.00001
y.min()
#0.14999

cross_val_score(reg,X,y,cv=10,scoring="mean_squared_error")

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

#为什么报错了?来试试看!
import sklearn
sorted(sklearn.metrics.SCORERS.keys())
cross_val_score(reg,X,y,cv=10,scoring="neg_mean_squared_error")
# -0.550952429695658

sklearn实战-----9.sklearn中的线性回归大家族_第10张图片
欢迎来的线性回归的大坑一号:均方误差为负。
我们在决策树和随机森林中都提到过,虽然均方误差永远为正,但是sklearn中的参数scoring下,均方误差作为评判标准时,却是计算”负均方误差“(neg_mean_squared_error)。这是因为sklearn在计算模型评估指标的时候,会考虑指标本身的性质,均方误差本身是一种误差,所以被sklearn划分为模型的一种损失(loss)。在sklearn当中,所有的损失都使用负数表示,因此均方误差也被显示为负数了。真正的均方误差MSE的数值,其实就是neg_mean_squared_error去掉负号的数字。

除了MSE,我们还有与MSE类似的MAE(Mean absolute error,绝对均值误差):
在这里插入图片描述
其表达的概念与均方误差完全一致,不过在真实标签和预测值之间的差异外我们使用的是L1范式(绝对值)。现实使用中,MSE和MAE选一个来使用就好了。在sklearn当中,我们使用命令from sklearn.metrics import mean_absolute_error来调用MAE,同时,我们也可以使用交叉验证中的scoring =“neg_mean_absolute_error”,以此在交叉验证时调用MAE。

3.2 是否拟合了足够的信息

对于回归类算法而言,只探索数据预测是否准确是不足够的。除了数据本身的数值大小之外,我们还希望我们的模型能够捕捉到数据的”规律“,比如数据的分布规律,单调性等等,而是否捕获了这些信息并无法使用MSE来衡量。
sklearn实战-----9.sklearn中的线性回归大家族_第11张图片
来看这张图,其中红色线是我们的真实标签,而蓝色线是我们的拟合模型。这是一种比较极端,但的确可能发生的情况。这张图像上,前半部分的拟合非常成功,看上去我们的真实标签和我们的预测结果几乎重合,但后半部分的拟合却非常糟糕,模型向着与真实标签完全相反的方向去了。对于这样的一个拟合模型,如果我们使用MSE来对它进行判断,它的MSE会很小,因为大部分样本其实都被完美拟合了,少数样本的真实值和预测值的巨大差异在被均分到每个样本上之后,MSE就会很小。但这样的拟合结果必然不是一个好结果,因为一旦我的新样本是处于拟合曲线的后半段的,我的预测结果必然会有巨大的偏差,而这不是我们希望看到的。所以,我们希望找到新的指标,除了判断预测的数值是否正确之外,还能够判断我们的模型是否拟合了足够多的,数值之外的信息。

在我们学习降维算法PCA的时候,我们提到我们使用方差来衡量数据上的信息量。如果方差越大,代表数据上的信息量越多,而这个信息量不仅包括了数值的大小,还包括了我们希望模型捕捉的那些规律。为了衡量模型对数据上的信息量的捕捉,我们定义了 R 2 R^2 R2来帮助我们:
sklearn实战-----9.sklearn中的线性回归大家族_第12张图片
其中 y y y是我们的真实标签, y ^ \hat y y^是我们的预测结果, y ‾ \overline y y是我们的均值, y i − y ‾ y_i-\overline y yiy 如果除以样本量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.33806537615560006

r2 = reg.score(Xtest,Ytest)
r2
#0.6043668160178817

我们现在踩到了线性回归的大坑二号:相同的评估指标不同的结果

为什么结果会不一致呢?这就是回归和分类算法的不同带来的坑。

在我们的分类模型的评价指标当中,我们进行的是一种 if a == b的对比,这种判断和if b == a其实完全是一种概念,所以我们在进行模型评估的时候,从未踩到我们现在在的这个坑里。然而看R2的计算公式,R2明显和分类模型的指标中的accuracy或者precision不一样,R2涉及到的计算中对预测值和真实值有极大的区别,必须是预测值在分子,真实值在分母,所以我们在调用metrcis模块中的模型评估指标的时候,必须要检查清楚,指标的参数中,究竟是要求我们先输入真实值还是先输入预测值。

#使用shift tab键来检查究竟哪个值先进行输入
r2_score(Ytest,yhat)
#0.6043668160178817

#或者你也可以指定参数,就不必在意顺序了
r2_score(y_true = Ytest,y_pred = yhat)
#0.6043668160178817

cross_val_score(reg,X,y,cv=10,scoring="r2").mean()
#0.5110068610524564

我们观察到,我们在加利福尼亚房屋价值数据集上的MSE其实不是一个很大的数(0.5),但我们的 不高,这证明我们的模型比较好地拟合了一部分数据的数值,却没有能正确拟合数据的分布。让我们与绘图来看看,究竟是不是这样一回事。我们可以绘制一张图上的两条曲线,一条曲线是我们的真实标签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实战-----9.sklearn中的线性回归大家族_第13张图片
可见,虽然我们的大部分数据被拟合得比较好,但是图像的开头和结尾处却又着较大的拟合误差。如果我们在图像右侧分布着更多的数据,我们的模型就会越来越偏离我们真正的标签。这种结果类似于我们前面提到的,虽然在有限的数据集上将数值预测正确了,但却没有正确拟合数据的分布,如果有更多的数据进入我们的模型,那数据标签被预测错误的可能性是非常大的。

思考
在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')

在这里插入图片描述
好了,现在我们跋山涉水来到了线性回归的三号大坑:负的 R 2 R^2 R2
许多学习过统计理论的小伙伴此时可能会感觉到,太坑了!sklearn真的是设置了太多障碍,均方误差是负的, R 2 R^2 R2也是负的,不能忍了。还有的小伙伴可能觉得,这个 R 2 R^2 R2里都带平方了,居然是负的,好气哦!无论如何,我们再来看看 R 2 R^2 R2的计算公式:
在这里插入图片描述
第一次学习机器学习或者统计学的小伙伴,可能会感觉没什么问题了, R 2 R^2 R2 是1减一个数,后面的部分只要大于1的话 R 2 R^2 R2完全可以小于0。但是学过机器学习,尤其是在统计学上有基础的小伙伴可能会坐不住了:这不对啊!

一直以来,众多的机器学习教材中都有这样的解读:

除了RSS之外,我们还有解释平方和ESS(Explained Sum of Squares,也叫做SSR回归平方和)以及总离差平方和TSS(Total Sum of Squares,也叫做SST总离差平方和)。解释平方和ESS定义了我们的预测值和样本均值之间的差异,而总离差平方和定义了真实值和样本均值之间的差异(就是 R 2 R^2 R2中的分母),两个指标分别写作:
sklearn实战-----9.sklearn中的线性回归大家族_第14张图片
而我们有公式:
T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS

看我们的 R 2 R^2 R2的公式,如果带入我们的TSS和ESS,那就有:
在这里插入图片描述
而ESS和TSS都带平方,所以必然都是正数,那 R 2 R^2 R2怎么可能是负的呢?

好了,颠覆认知的时刻到来了——公式TSS = RSS + ESS不是永远成立的!就算所有的教材和许多博客里都理所当然这样写了大家也请抱着怀疑精神研究一下,你很快就会发现很多新世界。我们来看一看我们是如何证明(1)这个公式的:
sklearn实战-----9.sklearn中的线性回归大家族_第15张图片

许多教材和博客中让 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ‾ ) 2\sum^{m}_{i=0}(y_i- \hat y_i)(\hat y_i-\overline y) 2i=0m(yiy^i)(y^iy)这个式子为0,公式 T S S = R S S + E S S TSS=RSS+ESS TSS=RSS+ESS 自然就成立了,但要让这个式子成立是有条件的。现在有了这个式子的存在, R 2 R^2 R2 就可以是一个负数了。只要我们的 ( y i − y ^ i ) (y_i- \hat y_i) (yiy^i)衡量的是真实值到预测值的距离,而 ( y ^ i − y ‾ ) (\hat y_i-\overline y) (y^iy)衡量的是预测值到均值的距离,只要当这两个部分的符号不同的时候,我们的式子 2 ∑ i = 0 m ( y i − y ^ i ) ( y ^ i − y ‾ ) 2\sum^{m}_{i=0}(y_i- \hat y_i)(\hat y_i-\overline y) 2i=0m(yiy^i)(y^iy) 就为负,而 R 2 R^2 R2就有机会是一个负数。

看下面这张图,蓝色的横线是我们的均值线 y ‾ \overline 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-\overline y) (y^iy)却是一个负数,这说明,数据本身的均值,比我们对数据的拟合模型本身更接近数据的真实值,那我们的模型就是废的,完全没有作用,类似于分类模型中
的分类准确率为50%,不如瞎猜。
sklearn实战-----9.sklearn中的线性回归大家族_第16张图片
也就是说,当我们的 R 2 R^2 R2显示为负的时候,这证明我们的模型对我们的数据的拟合非常糟糕,模型完全不能使用。所有,一个负的 R 2 R^2 R2 是合理的。当然了,现实应用中,如果你发现你的线性回归模型出现了负的 R 2 R^2 R2,不代表你就要接受他了,首先检查你的建模过程和数据处理过程是否正确,也许你已经伤害了数据本身,也许你的建模过程是存在bug的。如果是集成模型的回归,检查你的弱评估器的数量是否不足,随机森林,提升树这些模型在只有两三棵树的时候很容易出现负的 R 2 R^2 R2 。如果你检查了所有的代码,也确定了你的预处理没有问题,但你的 R 2 R^2 R2也还是负的,那这就证明,线性回归模型不适合你的数据,试试看其他的算法吧。

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

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

sklearn实战-----9.sklearn中的线性回归大家族_第17张图片
sklearn实战-----9.sklearn中的线性回归大家族_第18张图片
sklearn实战-----9.sklearn中的线性回归大家族_第19张图片
sklearn实战-----9.sklearn中的线性回归大家族_第20张图片
sklearn实战-----9.sklearn中的线性回归大家族_第21张图片
sklearn实战-----9.sklearn中的线性回归大家族_第22张图片
sklearn实战-----9.sklearn中的线性回归大家族_第23张图片
sklearn实战-----9.sklearn中的线性回归大家族_第24张图片
sklearn实战-----9.sklearn中的线性回归大家族_第25张图片
在这里插入图片描述

4.2 岭回归

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

sklearn实战-----9.sklearn中的线性回归大家族_第26张图片
sklearn实战-----9.sklearn中的线性回归大家族_第27张图片
sklearn实战-----9.sklearn中的线性回归大家族_第28张图片
sklearn实战-----9.sklearn中的线性回归大家族_第29张图片

4.2.2 linear_model.Ridge

在sklearn中,岭回归由线性模型库中的Ridge类来调用:

class 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或者各种检验来判断数据是否存在共线性,然而在机器学习中,我们可以使用模型来判断——如果一个数据集在岭回归中使用各种正则化参数取值下模型表现没有明显上升(比如出现持平或者下降),则说明数据没有多重共线性,顶多是特征之间有一些相关性。反之,如果一个数据集在岭回归的各种正则化参数取值下表现出明显的上升趋势,则说明数据存在多重共线性。

接下来,我们就在加利佛尼亚房屋价值数据集上来验证一下这个说法

import numpy as np
import pandas as pd
from sklearn.linear_model import Ridge, LinearRegression, Lasso
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 = ["住户收入中位数","房屋使用年代中位数","平均房间数目"
            ,"平均卧室数目","街区人口","平均入住率","街区的纬度","街区的经度"]

X.head()

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)

#交叉验证下,与线性回归相比,岭回归的结果如何变化?
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.sklearn中的线性回归大家族_第30张图片

#使用岭回归来进行建模
reg = Ridge(alpha=0).fit(Xtrain,Ytrain)
reg.score(Xtest,Ytest) #加利佛尼亚房屋价值数据集中应该不是共线性问题
#细化一下学习曲线
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实战-----9.sklearn中的线性回归大家族_第31张图片

可以看出,加利佛尼亚数据集上,岭回归的结果轻微上升,随后骤降。可以说,加利佛尼亚房屋价值数据集带有很轻微的一部分共线性,这种共线性被正则化参数 α \alpha α消除后,模型的效果提升了一点点,但是对于整个模型而言是杯水车薪。在过了控制多重共线性的点后,模型的效果飞速下降,显然是正则化的程度太重,挤占了参数 w 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实战-----9.sklearn中的线性回归大家族_第32张图片
可以发现,模型的方差上升快速,不过方差的值本身很小,其变化不超过 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)

X.shape
#(506, 13)

#先查看方差的变化
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实战-----9.sklearn中的线性回归大家族_第33张图片

#查看R2的变化
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.sklearn中的线性回归大家族_第34张图片

#细化学习曲线
alpharange = np.arange(100,300,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实战-----9.sklearn中的线性回归大家族_第35张图片可以发现,比起加利佛尼亚房屋价值数据集,波士顿房价数据集的方差降低明显,偏差也降低明显,可见使用岭回归还是起到了一定的作用,模型的泛化能力是有可能会上升的。

遗憾的是,没有人会希望自己获取的数据中存在多重共线性,因此发布到scikit-learn或者kaggle上的数据基本都经过一定的多重共线性的处理的,要找出绝对具有多重共线性的数据非常困难,也就无法给大家展示岭回归在实际数据中大显身手的模样。我们也许可以找出具有一些相关性的数据,但是大家如果去尝试就会发现,基本上如果我们使用岭回归或者Lasso,那模型的效果都是会降低的,很难升高,这恐怕也是岭回归和Lasso一定程度上被机器学习领域冷遇的原因。

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

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

你可能感兴趣的:(Sklearn实战)