逻辑回归 - 参数部分(三)

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

参数

重要参数penalty & C

正则化

正则化是用来防止模型过拟合的过程,常用的有L1正则化L2正则化两种选项,分别通过在损失函数后加上参数向量 θ \theta 的L1范式和L2范式的倍数来实现。这个增加的范式,被称为“正则项”,也被称为"惩罚项"。损失函数改变,基于损失函数的最优化来求解的参数取值必然改变,我们以此来调节模型拟合的程度。

  • L1范式表现为参数向量中的每个参数的绝对值之和
  • L2范数表现为参数向量中的每个参数的平方和的开方值

image.png

  • J ( θ ) J(\theta) 是我们之前提过的损失函数
  • C是用来控制正则化程度的超参数
  • n是方程中特征的总数,也是方程中参数的总数
  • j代表每个参数。
    • 在这里,j要大于等于1,是因为我们的参数向量 θ \theta 中,第一个参数是 θ 0 \theta_0 ,是我们的截距,它通常是不参与正则化的。

在许多书籍和博客中,大家可能也会见到如下的写法:

image.png

其实和上面我们展示的式子的本质是一模一样的。不过在大多数教材和博客中,常数项是乘以正则项,通过调控正则项来调节对模型的惩罚。

sklearn当中,常数项C是在损失函数的前面,通过调控损失函数本身的大小,来调节对模型的惩罚。

参数 说明
penalty 可以输入"l1"或"l2"来指定使用哪一种正则化方式,不填写默认"l2"
若选择"l1"正则化,参数solver仅能够使用求解方式”liblinear"和"saga“
若使用“l2”正则化,参数solver中所有的求解方式都可以使用
C C正则化强度的倒数,必须是一个大于0的浮点数,不填写默认1.0,即默认正则项与损失函数的比值是1:1。
C越小,损失函数会越小,模型对损失函数的惩罚越重,正则化的效力越强,参数会逐渐被压缩得越来越小。

L1正则化和L2正则化虽然都可以控制过拟合,但它们的效果并不相同。当正则化强度逐渐增大(即C逐渐变小),参数的取值会逐渐变小,但L1正则化会将参数压缩为0,L2正则化只会让参数尽量小,不会取到0

  • L1正则化
    • L1正则化在逐渐加强的过程中,携带信息量小的、对模型贡献不大的特征的参数,会比携带大量信息的、对模型有巨大贡献的特征的参数更快地变成0,所以L1正则化本质是一个特征选择的过程,掌管了参数的“稀疏性”。
    • L1正则化越强,参数向量中就越多的参数为0,参数就越稀疏,选出来的特征就越少,以此来防止过拟合。
    • 如果特征量很大,数据维度很高,我们会倾向于使用L1正则化。由于L1正则化的这个性质,逻辑回归的特征选择可以由Embedded嵌入法来完成。
  • L2正则化
    • L2正则化在加强的过程中,会尽量让每个特征对模型都有一些小的贡献,但携带信息少,对模型贡献不大的特征的参数会非常接近于0
    • 如果我们的主要目的只是为了防止过拟合,选择L2正则化就足够了。但是如果选择L2正则化后还是过拟合,模型在未知数据集上的效果表现很差,就可以考虑L1正则化。

而两种正则化下C的取值,都可以通过学习曲线来进行调整。建立两个逻辑回归,L1正则化和L2正则化的差别就一目了然了:

  • 导入需要的模块和库
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression as LR
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import accuracy_score
复制代码
  • 导入数据,探索数据
data = load_breast_cancer()

data.data.shape
# (569, 30)

x = data.data
y = data.target
复制代码
  • 建模
# 逻辑回归中主要是看两个和正则化相关的参数
li_1 = LR(penalty='l1', C=0.5,solver="liblinear",max_iter=1000)
li_2 = LR(penalty='l2', C=0.5,solver="liblinear",max_iter=1000)
复制代码
  • 查看L1模型中的参数
# coef_我们可以通过这个属性看到模型中的参数
li_1.fit(x,y)
li_1.coef_

"""
array([[ 4.00087851,  0.03184484, -0.13719529, -0.01621991,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.50434395,  0.        , -0.07127028,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        , -0.24574011, -0.12855125, -0.01441038,  0.        ,
         0.        , -2.04054053,  0.        ,  0.        ,  0.        ]])
"""

(li_1.coef_ != 0).sum(axis=1)
# 最终剩下的模型参数个数
# array([10])
复制代码
  • 查看L2模型中的参数
li_2.fit(x,y)
li_2.coef_

"""
array([[ 1.61548611e+00,  1.02632258e-01,  4.80200407e-02,
        -4.45411589e-03, -9.42317076e-02, -3.01406432e-01,
        -4.56094877e-01, -2.22369933e-01, -1.35669712e-01,
        -1.93872998e-02,  1.61633556e-02,  8.84112777e-01,
         1.20105216e-01, -9.47545435e-02, -9.81704357e-03,
        -2.37182410e-02, -5.71759637e-02, -2.70216145e-02,
        -2.77524508e-02,  2.01578322e-04,  1.26397193e+00,
        -3.02012969e-01, -1.72892385e-01, -2.21724655e-02,
        -1.73357968e-01, -8.79005176e-01, -1.16331712e+00,
        -4.27705813e-01, -4.20636312e-01, -8.69727550e-02]])
"""
复制代码

可以看见,当我们选择L1正则化的时候,许多特征的参数都被设置为了0,这些特征在真正建模的时候,就不会出现在我们的模型当中了,而L2正则化则是对所有的特征都给出了参数。

  • 使用学习曲线选取正则化
# 我们可以绘制在训练样本上和测试样本上的准确率曲线,来观察逻辑回归模型
# 当随着C的值逐渐变大的时候,我们发现惩罚力度逐渐变小,在测试集上的准确率逐渐上升,损失函数逐渐变小,
# 但是测试集上,先上升在下降,我们可知模型处于过拟合的状态
# 在c=0.4的情况下,测试集和训练集上都表现很好

l1 = []
l2 = []
l1_test = []
l2_test = []

xtrain,xtest,ytrain,ytest = train_test_split(x,y,test_size=0.3)

for i in np.linspace(0.05,1,19):
    li_1 = LR(penalty='l1', C=i,solver="liblinear",max_iter=1000)
    li_2 = LR(penalty='l2', C=i,solver="liblinear",max_iter=1000)

    li1_model = li_1.fit(xtrain,ytrain)
    l1.append(accuracy_score(li1_model.predict(xtrain),ytrain))
    l1_test.append(accuracy_score(li1_model.predict(xtest),ytest))

    li2_model = li_2.fit(xtrain, ytrain)
    l2.append(accuracy_score(li2_model.predict(xtrain), ytrain))
    l2_test.append(accuracy_score(li2_model.predict(xtest), ytest))

color = ["red","orange","blue","black"]
graph = [l1,l2,l1_test,l2_test]
labels = ["L1","L2","L1test","L2test"]

plt.figure(figsize=(10,5))
for i in range(len(graph)):
    plt.plot(np.linspace(0.05,1,19),graph[i],color=color[i],label=labels[i])
plt.legend(loc=4)
plt.show()
复制代码

image.png

可见,至少在我们的乳腺癌数据集下,两种正则化的结果区别不大。但随着C的逐渐变大,正则化的强度越来越小,模型在训练集和测试集上的表现都呈上升趋势,直到C=0.8左右,训练集上的表现依然在走高,但模型在未知数据集上的表现开始下跌,这时候就是出现了过拟合。我们可以认为,C设定为0.8会比较好。

在实际使用时,基本就默认使用l2正则化,如果感觉到模型的效果不好,那就换L1试试看

逻辑回归中的特征工程

当特征的数量很多的时候,我们出于业务考虑,也出于计算量的考虑,希望对逻辑回归进行特征选择来降维。比如,在判断一个人是否会患乳腺癌的时候,医生如果看5~8个指标来确诊,会比需要看30个指标来确诊容易得多。

  • 业务选择
    • 说到降维和特征选择,首先要想到的是利用自己的业务能力进行选择,肉眼可见明显和标签有关的特征就是需要留下的。当然,如果我们并不了解业务,或者有成千上万的特征,那我们也可以使用算法来帮助我们。或者,可以让算法先帮助我们筛选过一遍特征,然后在少量的特征中,我们再根据业务常识来选择更少量的特征。
  • PCA和SVD一般不用
    • 说到降维,我们首先想到的是之前提过的高效降维算法,PCA和SVD,遗憾的是,这两种方法大多数时候不适用于逻辑回归。逻辑回归是由线性回归演变而来,线性回归的一个核心目的是通过求解参数来探究特征X与标签y之间的关系,而逻辑回归也传承了这个性质,我们常常希望通过逻辑回归的结果,来判断什么样的特征与分类结果相关,因此我们希望保留特征的原貌。PCA和SVD的降维结果是不可解释的,因此一旦降维后,我们就无法解释特征和标签之间的关系了。当然,在不需要探究特征与标签之间关系的线性数据上,降维算法PCA和SVD也是可以使用的。
  • 统计方法可以使用,但不是非常必要
    • 既然降维算法不能使用,我们要用的就是特征选择方法。逻辑回归对数据的要求低于线性回归,由于我们不是使用最小二乘法来求解,所以逻辑回归对数据的总体分布和方差没有要求,也不需要排除特征之间的共线性,但如果我们确实希望使用一些统计方法,比如方差,卡方,互信息等方法来做特征选择,也并没有问题。过滤法中所有的方法,都可以用在逻辑回归上。
    • 在一些博客中有这样的观点:多重共线性会影响线性模型的效果。对于线性回归来说,多重共线性会影响比较大,所以我们需要使用方差过滤和方差膨胀因子VIF(variance inflation factor)来消除共线性。但是对于逻辑回归,其实不是非常必要,甚至有时候,我们还需要多一些相互关联的特征来增强模型的表现。当然,如果我们无法通过其他方式提升模型表现,并且你感觉到模型中的共线性影响了模型效果,那懂得统计学的你可以试试看用VIF消除共线性的方法,遗憾的是现在sklearn中并没有提供VIF的功能。
  • 高效的嵌入法embedded
    • 但是更有效的方法,毫无疑问会是我们的embedded嵌入法。我们已经说明了,由于L1正则化会使得部分特征对应的参数为0,因此L1正则化可以用来做特征选择,结合嵌入法的模块SelectFromModel,我们可以很容易就筛选出让模型十分高效的特征。注意,此时我们的目的是,尽量保留原数据上的信息,让模型在降维后的数据上的拟合效果保持优秀,因此我们不考虑训练集测试集的问题,把所有的数据都放入模型进行降维
  • 比较麻烦的系数累加法
    • 系数累加法的原理非常简单。在PCA中,我们通过绘制**累积可解释方差贡献率曲线来选择超参数**,在逻辑回归中我们可以使用系数coef_来这样做,并且我们选择特征个数的逻辑也是类似的:找出曲线由锐利变平滑的转折点转折点之前被累加的特征都是我们需要的,转折点之后的我们都不需要。不过这种方法相对比较麻烦,因为我们要先对特征系数进行从大到小的排序,还要确保我们知道排序后的每个系数对应的原始特征的位置,才能够正确找出那些重要的特征。如果要使用这样的方法,不如直接使用嵌入法来得方便。
  • 简单快速的包装法
    • 相对的,包装法可以直接设定我们需要的特征个数,逻辑回归在现实中运用时,可能会有”需要5~8个变量”这种需求,包装法此时就非常方便了。不过逻辑回归的包装法的使用和其他算法一样,并不具有特别之处.
对嵌入法的实践应用
  • 导入需要的模块和库
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_breast_cancer
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel
复制代码
  • 导入数据,探索数据
data = load_breast_cancer()
data.data.shape
# (569, 30)
复制代码
  • 建模
LR_ = LR(solver="liblinear",C=0.9,random_state=420)
复制代码
  • 交叉验证,查看效果
cross_val_score(LR_,data.data,data.target,cv=10).mean()
# 0.9508145363408522
复制代码
  • 使用嵌入法进行特征选择
# 在这个地方我们是根据LR的l1惩罚,将无用的特征进行过滤
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)

X_embedded.shape
# (569, 9)
复制代码
  • 使用特征选择后的特征交叉验证结果(效果并不好)
# 过滤后会有损失
cross_val_score(LR_,X_embedded,data.target,cv=10).mean()

# 0.9368107769423559
复制代码

看看结果,特征数量被减小到个位数,并且模型的效果却没有下降太多,如果我们要求不高,在这里其实就可以停下了

模型的拟合效果更好的两种调整方式
  • 调节SelectFromModel这个类中的参数threshold,这是嵌入法的阈值,表示删除所有参数的绝对值低于这个阈值的特征。
    • 现在threshold默认为None,所以SelectFromModel只根据L1正则化的结果来选择了特征,即选择了所有L1正则化后参数不为0的特征。
    • 我们此时,只要调整threshold的值(画出threshold的学习曲线),就可以观察不同的threshold下模型的效果如何变化。
    • 一旦调整threshold,就不是在使用L1正则化选择特征,而是使用模型的属性.coef_中生成的各个特征的系数来选择
      • coef_虽然返回的是特征的系数,但是系数的大小和决策树中的feature_ importances_以及降维算法中的可解释性方差explained_vairance_概念相似,其实都是衡量特征的重要程度和贡献度的,因此SelectFromModel中的参数threshold可以设置为coef_的阈值,即可以剔除系数小于threshold中输入的数字的所有特征
# 我们使用coef_,就是逻辑回归中的系数作为判断指标

fullx = []
fsx = []
 
threshold = np.linspace(0,abs((LR_.fit(data.data,data.target).coef_)).max(),20)
 
k=0
for i in threshold:
    X_embedded = SelectFromModel(LR_,threshold=i).fit_transform(data.data,data.target)
    fullx.append(cross_val_score(LR_,data.data,data.target,cv=5).mean())
    fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=5).mean())
    # 打印在当前的阈值下,我们的剩余的特征的个数
    print((threshold[k],X_embedded.shape[1]))
    k+=1
    
plt.figure(figsize=(20,5))
plt.plot(threshold,fullx,label="full")
plt.plot(threshold,fsx,label="feature selection")
plt.xticks(threshold)
plt.legend()
plt.show()

"""
(0.0, 30)
(0.10699647940412275, 17)
(0.2139929588082455, 12)
(0.32098943821236825, 11)
(0.427985917616491, 8)
(0.5349823970206138, 8)
(0.6419788764247365, 6)
(0.7489753558288593, 5)
(0.855971835232982, 5)
(0.9629683146371047, 5)
(1.0699647940412276, 5)
(1.1769612734453503, 4)
(1.283957752849473, 2)
(1.3909542322535957, 2)
(1.4979507116577186, 2)
(1.6049471910618414, 1)
(1.711943670465964, 1)
(1.8189401498700868, 1)
(1.9259366292742095, 1)
(2.0329331086783324, 1)
"""
复制代码

image.png

然而,这种方法其实是比较无效的,大家可以用学习曲线来跑一跑:当threshold越来越大,被删除的特征越来越多,模型的效果也越来越差,模型效果最好的情况下需要保证有17个以上的特征。实际上我画了细化的学习曲线,如果要保证模型的效果比降维前更好,我们需要保留25个特征,这对于现实情况来说,是一种无效的降维:需要30个指标来判断病情,和需要25个指标来判断病情,对医生来说区别不大

  • 第二种调整方法,是调逻辑回归的类LR_,通过画C的学习曲线来实现:
fullx = []
fsx = []
 
C = np.arange(0.01,10.01,0.5)
 
for i in C:
    LR_ = LR(solver="liblinear",C=i,random_state=420)
    
    fullx.append(cross_val_score(LR_,data.data,data.target,cv=10).mean())
    
    X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
    fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=10).mean())
    
print(max(fsx),C[fsx.index(max(fsx))])
 
plt.figure(figsize=(20,5))
plt.plot(C,fullx,label="full")
plt.plot(C,fsx,label="feature selection")
plt.xticks(C)
plt.legend()
plt.show()

# 0.9561090225563911 9.51
复制代码

image.png

继续细化学习曲线:

fullx = []
fsx = []
 
C=np.arange(9.05,10.05,0.005)
 
for i in C:
    LR_ = LR(solver="liblinear",C=i,random_state=420)
    
    fullx.append(cross_val_score(LR_,data.data,data.target,cv=10).mean())
    
    X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
    fsx.append(cross_val_score(LR_,X_embedded,data.target,cv=10).mean())
    
print(max(fsx),C[fsx.index(max(fsx))])
 
plt.figure(figsize=(20,5))
plt.plot(C,fullx,label="full")
plt.plot(C,fsx,label="feature selection")
plt.xticks(C)
plt.legend()
plt.show()

# 0.9561090225563911 9.055000000000001


#验证模型效果:降维之前
LR_ = LR(solver="liblinear",C=9.055000000000001,random_state=420)
cross_val_score(LR_,data.data,data.target,cv=10).mean()             #0.947360859044162

#验证模型效果:降维之后
LR_ = LR(solver="liblinear",C=9.055000000000001,random_state=420)
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
cross_val_score(LR_,X_embedded,data.target,cv=10).mean()                          # 0.9561090225563911

X_embedded.shape
# (569, 9)
复制代码

image.png

猜你喜欢

转载自juejin.im/post/7088205924115939336
今日推荐