ML–广义线性模型
本节涉及的知识点有:
- 线性模型的基本概念
- 线性回归模型
- 岭回归模型
- 套索回归模型
- 二元分类器中的逻辑回归和线性SVC模型
一.线性模型的基本概念
线性模型原本是一个统计学中的术语。实际上线性模型并不是特指某一个模型,而是一类模型。在机器学习领域,常用的线性模型包括线性回归
,岭回归
,套索回归
,逻辑回归
和线性SVC
等
1.线性模型的一般公式
在回归分析当中,线性模型的一般预测公式如下:
=w[0]*x[0]+w[1]*x[1]+…+w[p]*x[p]+b式中:x[0],x[1],…,x[p]为数据集中特征变量的数量(这个公式表示数据集中的数据点一共有p个特征);w和b为模型的参数;y为模型对于数据结果的预测值。对于只有一个特征变量的数据集,公式可以简化为:y=w[0]*x[0]+b注意 y读作"y hat",代表y的估计值假设我们有一条直线,其方程是y=0.5x+3
,我们可以使用代码将它画出来:
import numpy as npimport matplotlib.pyplot as plt# 令x为-5到5之间,元素数为100的等差数列x=np.linspace(-5.5,100)# 输入直线方程y=0.5*x+3plt.plot(x,y,c='orange')plt.title('Straight Line')plt.show()[结果分析] 线性模型正是通过训练数据集确定自身的系数(斜率)和截距的
2.线性模型的图形表示
假设有两个点,它们的坐标是(1,3)和(4,5),那么我们可以画出一条直线来穿过这两个点,并且计算出这条直线的方程
# 导入线性回归模型from sklearn.linear_model import LinearRegression# 输入两个点的横坐标X=[[1],[4]]# 输入两个点的纵坐标y=[3,5]# 用线性模型拟合这两个点lr=LinearRegression().fit(X,y)# 画出两个点和直线的图形z=np.linspace(0,5,20)plt.scatter(X,y,s=80)plt.plot(z,lr.predict(z.reshape(-1,1)),c='k')plt.title('Straight Line')plt.show()
输出直线的系数和截距:
print('y={:.3f}'.format(lr.coef_[0]),'x','+{:.3f}'.format(lr.intercept_))
y=0.667 x +2.333
假设现在有第3个点,坐标是(3,3),我们把这个点添加进去:
# 输入3个点的横坐标X=[[1],[4],[3]]# 输入3个点的纵坐标y=[3,5,3]# 用线性模型拟合这3个点lr=LinearRegression().fit(X,y)# 画出3个点和直线的图形z=np.linspace(0,5,20)plt.scatter(X,y,s=80)plt.plot(z,lr.predict(z.reshape(-1,1)),c='k')plt.title('Straight Line')plt.show()
计算这条直线的方程:
print('y={:.3f}'.format(lr.coef_[0]),'x','+{:.3f}'.format(lr.intercept_))
y=0.571 x +2.143
现在我们以scikit-learn
生成的make_regression
数据集为例,用python语句绘制一条线性模型的预测线,更加清晰地反映出线性模型的原理:
from sklearn.datasets import make_regression# 生成用于回归分析的数据集X,y=make_regression(n_samples=50,n_features=1,n_informative=1,noise=50,random_state=1)# 使用线性模型对数据进行拟合reg=LinearRegression()reg.fit(X,y)# z是我们生成的等差数列,用来画出线性模型的图形z=np.linspace(-3,3,200).reshape(-1,1)plt.scatter(X,y,c='b',s=60)plt.plot(z,reg.predict(z),c='k')plt.title("Linear Regression")plt.show()
[结果分析] 黑色直线是线性回归模型在make_regression
数据集中生成的预测线。接下来我们计算这条直线对应的斜率和截距:
print('直线的系数是:{:.2f}'.format(reg.coef_[0]))print('直线的截距是:{:.2f}'.format(reg.intercept_))
直线的系数是:79.52直线的截距是:10.92
注意:coef_
和intercept_
这两个属性非常特殊,它们都是以下划线_结尾。这是scikit-learn
的一个特点,它总是用下划线作为来自训练数据集的属性的结尾,以便将它们与由用户设置的参数区分开
3.线性模型的特点
用于回归分析的线性模型在特征数为1的数据集中,是使用一条直线来进行预测分析。而当数据的特征数量达到2个时则是一个平面,而对于更多特征数量的数据集来说,则是一个高维度的超平面
如果和K最近邻模型生成的预测进行比较的话,你会发现线性模型的预测方法是非常有局限性的—很多数据都没有体现在这条直线上。因为使用线性模型的前提条件,是假设目标y是数据特征的线性组合
用于回归分析的线性模型也有很多种类。这些模型之间的区别在于如何从训练数据中确定模型参数w和b,以及如何控制模型复杂度
二.最基本的线性模型–线性回归
线性回归,也称为普通最小二乘法(OLS)
,是在回归分析中最简单也是最经典的线性模型
1.线性回归的基本原理
线性回归的原理是,找到当训练数据集中y的预测值和其真实值的平方差最小的时候,所对应的w值和b值。线性回归没有可供用户调节的参数,这是它的优势,但也代表我们无法控制模型的复杂性
接下来我们继续使用make_regression
函数,生成一个样本数量为100,特征数量为2的数据集,并且用train_test_split
函数将数据集分割成训练数据集和测试数据集,再用线性回归模型计算出w值和b值
# 导入数据集拆分工具from sklearn.model_selection import train_test_splitfrom sklearn.linear_model import LinearRegressionX,y=make_regression(n_samples=100,n_features=2,n_informative=2,random_state=38)X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=8)lr=LinearRegression().fit(X_train,y_train)
方程的斜率w,也被称为权重或者系数,被存储在coef_
属性中,而截距b被存储在intercept_
属性中:
print("lr.coef_:{}".format(lr.coef_))print("lr.intercept_:{}".format(lr.intercept_))
lr.coef_:[ 70.38592453 7.43213621]lr.intercept_:-1.4210854715202004e-14
[结果分析] intercept_
属性一直是一个浮点数,而coef_
属性则是一个Numpy
数组,其中每个特征对应数据中的一个数值。即在本例中线性回归模型的方程可以表示为:
y=70.39x1+7.43x2-1.42e-14
2.线性回归的性能表现
下面我们来看看线性回归在make_regression
生成的训练数据集和测试数据集中的性能如何:
print("训练数据集得分:{:.2f}".format(lr.score(X_train,y_train)))print("测试数据集得分:{:.2f}".format(lr.score(X_test,y_test)))
训练数据集得分:1.00测试数据集得分:1.00
真实世界的数据集,往往体征要多得多,而且noise
也不少,这会给线性模型带来不少的困扰。下面我们就来生成一个真实的数据集—糖尿病情数据集,再来测试一个:
from sklearn.datasets import load_diabetes# 载人糖尿病情数据集X,y=load_diabetes().data,load_diabetes().target# 将数据集拆分成训练集和测试集X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=8)# 使用线性回归模型进行拟合lr=LinearRegression().fit(X_train,y_train)
然后我们来看下这个模型针对训练数据集和测试数据集的得分如何:
print("训练数据集得分:{:.2f}".format(lr.score(X_train,y_train)))print("测试数据集得分:{:.2f}".format(lr.score(X_test,y_test)))
训练数据集得分:0.53测试数据集得分:0.46
[结果分析] 这次模型的分数降低了很多,模型在训练数据集中分数只有0.53,而测试数据集的得分就只有0.46
由于线性回归自身的特点,非常容易出现过拟合的现象。在训练集的得分和测试集的得分之间存在的巨大差异的出现过拟合问题的一个明确信号,因此,我们应该找到一个模型,使我们能够控制模型的复杂度
三.使用L2正则化的线性模型–岭回归
岭回归也是回归分析中常用的线性模型,它实际上是一种改良的最小二乘法
1.岭回归的原理
从实用的角度来说,岭回归实际上是一种能够避免过拟合的线性模型。在岭回归中,模型会保留所有的特征变量,但是会减少特征变量的系数值,让特征变量对预测结果的影响变小,在岭回归中是通过改变其alpha
参数来控制减少特征变量系数的程度。而这种通过保留全部特征变量,只是降低特征变量的系数值来避免过拟合的方法,我们称之为L2正则化
岭回归在scikit-learn
中是通过linear_model.Ridge
函数来调用的,下面我们继续使用波士顿房价的扩展数据集为例,看看岭回归的表现如何:
# 导入岭回归from sklearn.linear_model import Ridge# 使用岭回归对数据进行拟合ridge=Ridge().fit(X_train,y_train)print("岭回归的训练数据集得分:{:.2f}".format(ridge.score(X_train,y_train)))print("岭回归的测试数据集得分:{:.2f}".format(ridge.score(X_test,y_test)))
岭回归的训练数据集得分:0.43岭回归的测试数据集得分:0.43
[结果分析] 复杂度越低的模型,在训练数据集上的表现越差,但是其泛化的能力会更好。如果我们更在意模型在泛化方面的表现,那么我们就应该选择岭回归模型,而不是线性回归模型
2.岭回归的参数调节
岭回归是在模型的简单性(使系数趋近于零)和它在训练集上的性能之间取得平衡的一种模型。用户可以使用alpha
参数控制模型更加简单还是在训练集上的性能更高,在上一个示例中,我们使用默认参数alpha=1
注意:alpha
的取值并没有一定之规。alpha
的最佳设置取决于我们使用的特定数据集。增加alpha
值会降低特征变量的系数,使其趋于零,从而降低在训练集的性能,但更有助于泛化
下面我们再看一个例子,仍然使用糖尿病数据集,但是把正则项系数alpha
设置为10,代码如下:
# 修改alpha参数为10ridge10=Ridge(alpha=10).fit(X_train,y_train)print("岭回归的训练数据集得分:{:.2f}".format(ridge10.score(X_train,y_train)))print("岭回归的测试数据集得分:{:.2f}".format(ridge10.score(X_test,y_test)))
岭回归的训练数据集得分:0.15岭回归的测试数据集得分:0.16
[结果分析] 提高了alpha
值之后,我们看到模型的得分大幅降低了,然而有意思的是模型在测试集的得分超过了在训练集的得分。这说明,如果我们的模型出现了过拟合的现象,那么我们可以提高alpha
值来降低过拟合的程度
同时,降低alpha
值会让系数的限制变得不那么严格,如果我们用一个非常小的alpha
值,那么系统的限制几乎可以忽略不计,得到的结果也会非常接近线性回归
# 修改alpha参数为0.1ridge01=Ridge(alpha=0.1).fit(X_train,y_train)print("岭回归的训练数据集得分:{:.2f}".format(ridge01.score(X_train,y_train)))print("岭回归的测试数据集得分:{:.2f}".format(ridge01.score(X_test,y_test)))
岭回归的训练数据集得分:0.52岭回归的测试数据集得分:0.47
[结果分析] 把参数alpha
设置为0.1似乎让模型的在训练集的得分比线性回归模型略低,但在测试集的得分却有轻微的提升
为了更清晰地看出alpha
值对于模型的影响,我们用图像来观察不同alpha
值对应的模型的coef_
属性。较高的alpha
值代表模型的限制更加严格,所以我们认为在较高的alpha
值下,coef_
属性的数值会更小,反之coef_
属性的数值更大
# 绘制alpha=1时模型系数plt.plot(ridge.coef_,'s',label='Ridge alpha=1')# 绘制alpha=10时模型系数plt.plot(ridge10.coef_,'^',label='Ridge alpha=10')# 绘制alpha=0.1时的模型系数plt.plot(ridge01.coef_,'v',label='Ridge alpha=0.1')# 绘制线性回归的系数作为对比plt.plot(lr.coef_,'o',label='linear regression')plt.xlabel('coeficient index')plt.ylabel('coeficient magnitude')plt.hlines(0,0,len(lr.coef_))plt.legend()plt.show()
[结果分析] 横轴代表的是coef_
属性:x=0显示第一个特征变量的系数,x=1显示第二个特征变量的系数,依次类推,直到x=10。纵轴显示特征变量的系数量级。从图中可以看出,当alpha=10
时,特征变量系数大多在0附近;而当alpha=1
时,岭模型的特征变量系数普遍增大了。而当alpha=0.1
时,特征变量的系数就更大了,甚至大部分与线性回归的点重合了,而线性回归模型由于灭有经过任何正则化处理,其所对应的特征变量系数值就会非常大,其中有一些快跑到图表之外了
还有一个能够帮助我们更好理解正则化对模型影响的方法,那就是取一个固定的alpha
值,然后改变训练数据集的数据量。比如我们在糖尿病数据集中采样,然后用这些采样的字集对线性回归模型和alpha
值等于1的岭回归模型进行评估,并进行绘图,得到一个随数据集大小而不断改变的模型评分折线图,其中的折线我们也称之为学习曲线(learning curves)
下面我们初步画一下两个模型在糖尿病数据集中的学习曲线:
from sklearn.model_selection import learning_curve,KFold# 定义一个绘制学习曲线的函数def plot_learning_curve(est,X,y): # 将数据进行20次拆分用来对模型进行评分 training_set_size,train_scores,test_scores=learning_curve(est,X,y,train_sizes=np.linspace(.1,1,20),cv=KFold(20,shuffle=True,random_state=1)) estimator_name=est.__class__.__name__ line=plt.plot(training_set_size,train_scores.mean(axis=1),'--',label='training'+estimator_name) plt.plot(training_set_size,test_scores.mean(axis=1),'-',label="test"+estimator_name,c=line[0].get_color()) plt.xlabel("Training set size") plt.ylabel("score") plt.ylim(0,1.1) plot_learning_curve(Ridge(alpha=1),X,y)plot_learning_curve(LinearRegression(),X,y)plt.legend(loc=(0,1.05),ncol=2,fontsize=11) plt.show()
[结果分析] 无论是在岭回归中还是在线性回归中,训练数据集的得分都比测试数据集的得分要高。而由于岭回归是经过正则化的模型,因此它在整个图像中训练数据集的得分要比线性回归的得分低。在数据量小于50条的情况下,线性回归几乎不能让机器学到任何东西。随着数据集的规模越来越大,两个模型的表现也越来越好,最后线性回归的得分赶上了岭回归的得分。不难看出,如果有足够多的数据,那么正则化就显得不是那么重要了,岭回归和线性回归的表现也相差无几
注意:随着数据量的增加,线性回归在训练数据集的得分是下降的,这说明随着数据增加,线性回归模型就越不容易产生过拟合的现象,或者说越难记住这些数据
四.使用L1正则化的线性模型–套索回归
1.套索回归的原理
和岭回归一样,套索回归也会将系数限制在非常接近0的范围内,但它进行限制的方式稍微有一点不同,我们称之为L1正则化
。与L2正则化
不同的是,L1正则化
会导致在使用套索回归的时候,有一部分特征的系数会正好等于0.也就是说,有一些特征会彻底被模型忽略掉,这也可以看成是模型对于特征进行自动选择的一种方式。把一部分系数变成0有助于让模型更容易理解,而且可以突出体现模型中最重要的那些特征
让我们再用糖尿病数据集来验证一下套索回归,代码如下:
# 导入套索回归from sklearn.linear_model import Lasso# 使用套索回归拟合数据lasso=Lasso().fit(X_train,y_train)print("套索回归在训练数据集的得分:{:.2f}".format(lasso.score(X_train,y_train)))print("套索回归在测试数据集的得分:{:.2f}".format(lasso.score(X_test,y_test)))print("套索回归使用的特征数:{}".format(np.sum(lasso.coef_!=0)))
套索回归在训练数据集的得分:0.36套索回归在测试数据集的得分:0.37套索回归使用的特征数:3
[结果分析] 套索回归在训练数据集和测试数据集的得分都相当糟糕。这意味着我们的模型发生了欠拟合的问题,而且你会发现,在10个特征里面,套索回归只用了3个。与岭回归类似,套索回归也有一个正则化参数alpha
,用来控制特征变量系数被约束到0的强度
2.套索回归的参数调节
在上面的例子里,我们用来alpha
的默认值1.0.为了降低欠拟合的程度,我们可以试着降低alpha
的值。与此同时,我们还需要增加最大迭代次数(max_iter)
的默认设置
# 增加最大迭代次数的默认设置# 否则模型会提示我们增加最大迭代次数lasso01=Lasso(alpha=0.1,max_iter=100000).fit(X_train,y_train)print("alpha=0.1时套索回归在训练数据集的得分:{:.2f}".format(lasso01.score(X_train,y_train)))print("alpha=0.1时套索回归在测试数据集的得分:{:.2f}".format(lasso01.score(X_test,y_test)))print("alpha=0.1套索回归使用的特征数:{}".format(np.sum(lasso01.coef_!=0)))
alpha=0.1时套索回归在训练数据集的得分:0.52alpha=0.1时套索回归在测试数据集的得分:0.48alpha=0.1套索回归使用的特征数:7
[结果分析] 降低alpha
值可以拟合出更复杂的模型,从而在训练数据集和测试数据集都能获得良好的表现。相对岭回归,套索回归的表现还要稍好一点,而且它只用了10个特征中的7个
但是,如果我们把alpha
值设置得太低,就等于把正则化的效果去除了,那么模型就可能会像线性回归一样,出现过拟合的问题
# 修改alpha值为0.0001lasso00001=Lasso(alpha=0.0001,max_iter=100000).fit(X_train,y_train)print("alpha=0.0001时套索回归在训练数据集的得分:{:.2f}".format(lasso00001.score(X_train,y_train)))print("alpha=0.0001时套索回归在测试数据集的得分:{:.2f}".format(lasso00001.score(X_test,y_test)))print("alpha=0.0001套索回归使用的特征数:{}".format(np.sum(lasso00001.coef_!=0)))
alpha=0.0001时套索回归在训练数据集的得分:0.53alpha=0.0001时套索回归在测试数据集的得分:0.46alpha=0.0001套索回归使用的特征数:10
3.套索回归与岭回归的对比
我们来对不同alpha
值的套索回归和岭回归进行系数对比,代码如下:
# 绘制alpha=1时模型系数plt.plot(lasso.coef_,'s',label='Lasso alpha=1')# 绘制alpha=0.1时模型系数plt.plot(lasso01.coef_,'^',label='Lasso alpha=0.1')# 绘制alpha=0.0001时的模型系数plt.plot(lasso00001.coef_,'v',label='Lasso alpha=0.0001')# 绘制alpha=0.1时的岭回归模型系数作为对比plt.plot(ridge01.coef_,'o',label='Ridge alpha=0.1')plt.legend(ncol=2,loc=(0,1.05))plt.ylim(-25,25)plt.xlabel('Coeficient index')plt.ylabel('Coeficient magnitude')plt.show()
注意:scikit-learn
还提供了一种模型,称为弹性网模型(Elastic Net)。弹性网模型综合了套索回归和岭回归的惩罚因子。在实践中这两个模型的组合是效果最好的,然而代价是用户需要调整两个参数,一个是L1正则化参数,另一个是L2正则化参数
五.小结
介绍了几种常用的线性模型,包括线性回归,岭回归和套索回归。实际上,线性模型并不止这三种,还有比较知名的逻辑回归(Logistic Regression)
,线性支持向量机(Linear SVM)
等
对于线性模型来说,最主要的参数就是正则化参数(Regularization Parameter)。在线性回归,岭回归和套索回归中,是通过alpha
参数来进行调节的,而对于逻辑回归和线性支持向量机来说,则是通过调节参数C
来实现的