查看原文
其他

机器学习 | 逻辑回归算法(二)LogisticRegression

云朵 数据STUDIO 2022-04-28

文章机器学习 | 逻辑回归算法(一)理论中详细介绍了逻辑回归理论知识。

逻辑回归是一种广义线性回归模型,是Sigmoid函数归一化后的线性回归模型,常用来解决二元分类问题,可解释性强。它假设数据服从伯努利分布,通过梯度下降法对其损失函数(极大似然函数)求解,以达到数据二分类的目的。

逻辑回归不要求自变量和因变量是线性关系。它可以处理各种类型的关系,因为它对预测的相对风险指数或使用了一个非线性的转换。它广泛的用于分类问题。

基本原理是其函数表达式为对数几率函数,通过Sigmoid函数将线性回归方程转化,将任何实数映射到(0,1)之间


优化目标是通过观测样本的极大似然估计值来选择参数。

损失函数表征模型预测值与真实值的不一致程度。LR损失函数为负的对数损失函数。逻辑回归,假设样本服从伯努利分布(0-1分布),然后求得满足该分布的似然函数,接着取对数求极值最小化负的似然函数。应用极大似然估计法估计模型参数,从而得到逻辑回归模型。逻辑回归的损失函数求最小值,就是根据最大似然估计的方法来的。并使用梯度下降法求解损失函数。

sklearn中逻辑回归

Sklearn中逻辑回归相关的类说明
linear_model.LogisticRegression逻辑回归分类器(又叫logit回归,最大熵分类器)
linear_model.LogisticRegressionCV带交叉验证的逻辑回归分类器
linear_model.logistic_regression_path计算Logistic回归模型以获得正则化参数的列表
linear_model.SGDClassifier利用梯度下降求解的线性分类器(SVM,逻辑回归等等)
linear_model.SGDRegressor利用梯度下降最小化正则化后的损失函数的线性回归模型
metrics.log_loss对数损失,又称逻辑损失或交叉熵损失

sklearn.linear_model.LogisticRegression (penalty=’l2’, dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver=’warn’, max_iter=100, multi_class=’warn’, verbose=0, warm_start=False, n_jobs=None)

逻辑回归的损失函数

正则化参数penalty & C


penalty: {‘l1’, ‘l2’, ‘elasticnet’, ‘none’}, default=’l2’

可以输入"l1""l2"来指定使用哪一种正则化方式,不填写默认"l2"。注意,若选择"l1"正则化,参数solver仅能够使用求解方式 "liblinear" 和 "saga",若使用"l2"正则 化,参数solver中所有的求解方式都可以使用。


C: float, default=1.0

C正则化强度的倒数,必须是一个大于0的浮点数,不填写默认1.0,即默认正则项与损失函数的比值是1:1。C越小,损失函数会越小,模型对损失函数的惩罚越重,正则化的效力越强,参数  会逐渐被压缩得越来越小。


正则化是用来防止模型过拟合的过程,常用的有L1正则化L2正则化两种选项,分别通过在损失函数后加上参数向量  的L1范式和L2范式的倍数来实现。

sklearn 中的带L1正则化的损失函数

L2正则化

其中  是损失函数,C是用来控制正则化程度的超参数,n是方程中特征的总数,也是方程中参数的总数,i代表每个参数,且  是因为我们的参数向量中,第一个参数是 ,为截距,它通常是不参与正则化的。

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

>>> 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 train_test_split
>>> from sklearn.metrics import accuracy_score
>>> data = load_breast_cancer()
>>> X = data.data
>>> y = data.target
>>> LRL1 = LR(penalty="l1",solver="liblinear",
              C=0.5,max_iter=1000)
>>> LRL2 = LR(penalty="l2",solver="liblinear",
              C=0.5,max_iter=1000)
>>> #逻辑回归的重要属性coef_,查看每个特征所对应的参数 
>>> LRL1 = LRL1.fit(X,y)
>>> LRL1.coef_

array([[ 4.00522396,  0.03209852-0.13789572,
        -0.01621777,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.50500876,  
         0.        , -0.07126212,  0.        ,
         0.        ,  0.        ,  0.        ,
         0.        ,  0.        ,  0.        , 
        -0.24596296-0.12854231-0.01441124,
         0.        ,  0.        , -2.0363906 ,
         0.        ,  0.        ,  0.        ]])
>>> (LRL1.coef_ != 0).sum(axis=1)
array([10])
>>> lLRL2 = LRL2.fit(X,y)
>>> LRL2.coef_
array([[ 1.61520498e+00,  1.03165077e-01,  4.89949912e-02,
        -4.54501839e-03-9.48008442e-02-3.01421091e-01,
        -4.56859977e-01-2.23258644e-01-1.36553775e-01,
        -1.93906452e-02,  1.51863698e-02,  8.84898768e-01,
         1.16165402e-01-9.44763010e-02-9.89655843e-03,
        -2.29725881e-02-5.65958129e-02-2.71050978e-02,
        -2.78528014e-02,  3.23039398e-04,  1.25588920e+00,
        -3.02228846e-01-1.72470625e-01-2.21057469e-02,
        -1.74443731e-01-8.78235976e-01-1.16396035e+00,
        -4.29422429e-01-4.23200446e-01-8.69294093e-02]])

在乳腺癌数据集上比较L1正则化与L2正则化对逻辑回归效果的对比如下图。

正则化
代码见附录1、正则化

L1正则化做特征选择

在学习线性回归中,详细介绍过Lasso回归分析,Lasso全称最小绝对收缩和选择算子,以L1先验作为正则化器训练的线性模型。 Lasso可以把这些不重要变量的系数压缩为0,既实现了较为准确的参数估计,也实现了特征选择即降维。

在逻辑回归中,同样也使用了L1正则化来做特征选择。如使用高效的嵌入法embedded对逻辑回归进行特征选择来降维。嵌入法的模块SelectFromModel在尽量保留原数据上的信息筛选出让模型十分高效的特征,让模型在降维后的数据上的拟合效果保持优秀。

>>> 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
(56930)
>>> LR_ = LR(solver="liblinear",C=0.9,random_state=420)
>>> cross_val_score(LR_,data.data,data.target,cv=10).mean()
0.9508145363408522
>>> X_embedded=SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
>>> X_embedded.shape
(5699)
>>> cross_val_score(LR_,X_embedded,data.target,cv=10).mean()
0.9368107769423559

对比下结果:


降维前降维后
维数309
模型得分0.95081453630.9368107769

看看结果,特征数量被减小到个位数,并且模型的效果却没有下降太多,如果我们要求不高,在这里其实就可以停下了。但是,能否让模型的拟合效果更好呢?在这里,我们有两种调整方式:

第一种调节SelectFromModel这个类中的参数threshold是嵌入法的阈值,表示删除所有参数的绝对值低于这个阈值的特征。


threshold : string, float, optional default None

threshold默认为None,所以SelectFromModel只根据L1正则化的结果来选择了特征,即选择了所有L1正则化后参数不为0的特征。

通过画出threshold的学习曲线,观察不同的threshold下模型的效果如何变化。这种情况不是在使用L1正则化选择特征,而是使用模型的属性coef_中生成的各个特征的系数来选择。coef_虽然返回的是特征的系数,但是系数的大小和决策树中的 feature_ importances_以及降维算法中的可解释性方差explained_vairance_概念相似,其实都是衡量特征的重要程度和贡献度的,因此SelectFromModel中的参数threshold可以设置为coef_的阈值,即可以剔除系数小于threshold中输入的数字的所有特征。


L1正则化做特征选择
代码见附录2、threshold的学习曲线1

然而,这种方法其实是比较无效的,当threshold越来越大,被删除的特征越来越多,模型的效果也越来越差。当模型中保留17个以上的特征时模型效果最好。

第二种通过逻辑回归的类LR_中参数C的学习曲线来实现:


逻辑回归的类LR_中参数C的学习曲线
代码见附录3、threshold的学习曲线2

C=7.01时等分最高0.9561090225563911

# 降维之前模型效果
>>> LR_ = LR(solver="liblinear",C=6.069999999999999,random_state=420)
>>> cross_val_score(LR_,data.data,data.target,cv=10).mean()
0.9508145363408522

# 降维之后模型效果
>>> LR_ = LR(solver="liblinear",C=6.069999999999999,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
(5699)

梯度下降参数max_iter


max_iter: int, default=100

使求解器收敛所需的最大迭代次数。


在逻辑回归理论篇有介绍利用梯度下降法求解损失函数,损失函数沿着下降最快的方向,即梯度向量的反方向,按照一定的步长不断迭代,直至下降到最小值时停止迭代。


步长是梯度向量的大小上的一个比例,影响着参数向量每次迭代后改变的部分。


sklearn当中,参数max_iter最大迭代次数来代替步长,控制模型的迭代速度并适时地让模型停下。max_iter越大,代表步长越小,模型迭代时间越长,反之,则代表步长设置很大,模型迭代时间很短。

乳腺癌数据集下,max_iter的学习曲线:

max_iter的学习曲线
代码见附录4、max_iter学习曲线
#我们可以使用属性.n_iter_来调用本次求解中真正实现的迭代次数
>>> lr = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=300).fit(Xtrain,Ytrain)
>>> lr.n_iter_
array([24], dtype=int32)

二元与多元回归参数 solver & multi_class


solver: {‘newton-cg’, ‘lbfgs’, ‘liblinear’, ‘sag’, ‘saga’}, default=’lbfgs’

算法用于优化问题。

  • 对于小数据集,"liblinear"是一个不错的选择,而"sag"和"saga"对于大数据集更快。

  • 对于多类问题,只有"newton-cg"、"sag"、"saga"和"lbfgs"处理多项式损失;"liblinear"仅限于"OvR"方案。

  • ' newton-cg ', ' lbfgs ', ' sag '和' saga '支持L2或没有惩罚

  • "liblinear""saga"也支持L1惩罚

  • "saga"也支持"elasticnet" 惩罚

  • 'liblinear'不支持设置penalty='none'


multi_class: {‘auto’, ‘ovr’, ‘multinomial’}, default=’auto’

输入"ovr", "multinomial", "auto"来告知模型,我们要处理的分类问题的类型。默认是"ovr"。

  • 'ovr':表示分类问题是二分类,或让模型使用"OvR"的形式来处理多分类问题。

  • 'multinomial':表示处理多分类问题,这种输入在参数 solver='liblinear'时不可用。

  • "auto":表示会根据数据的分类情况和其他参数来确定模型要处理的分类问题的类型。比如说,如果数据是二分类,或者solver的取值为"liblinear","auto"会默认选择"ovr"。反之,则会选择"nultinomial"

注意:默认值将在0.22版本中从"ovr"更改为"auto"


sklearn中"ovr"表示把某种分类类型都看作1,其余的分类类型都为0值称为"一对多"(One-vs-rest),简称OvR。

sklearn中"Multinominal"中把好几个分类类型划为1,剩下的几个分类类型划为0值,这是一种"多对多"(Many-vs-Many)的方法,简称MvM,

solver控制求解器以及对应支持回归类型、求解器效果统计如下表。

样本不平衡参数class_weight


class_weight: dict or ‘balanced’, default=None

{class_label: weight}的形式与类关联的权重。如果没有给出,所有类的权重都应该是1。

'balanced'模式使用y的值自动调整权值与输入数据中的类频率成反比,如n_samples/(n_classes*np.bincount(y))

注意,如果指定了sample_weight,那么这些权重将与sample_weight相乘(通过fit方法传递)。


因此我们要使用参数class_weight对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认class_weight=None,此模式表示自动给与数据集中的所有标签相同的权重,即自动1: 1。当误分类的代价很高的时候,我们使用"class_weight=balanced"模式,我们只是希望对标签进行均衡的时候,什么都不填就可以解决样本不均衡问题。

但是,sklearn当中的参数class_weight变幻莫测,很难去找出这个参数引导的模型趋势,或者画出学习曲线来评估参数的效果,因此可以说是非常难用。我们有着处理样本不均衡的各种方法,其中主流的是采样法,是通过重复样本的方式来平衡标签,可以进行上采样(增加少数类的样本),比如SMOTE, 或者下采样(减少多数类的样本)。对于逻辑回归来说,上采样是最好的办法。更多样本不均衡处理方式可以参见机器学习中样本不平衡

附录代码

1、正则化

l1 = []
l2 = []
l1test = []
l2test = []
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
for i in np.linspace(0.05,1,19):
    lrl1 = LR(penalty="l1",solver="liblinear",C=i,max_iter=1000)
    lrl2 = LR(penalty="l2",solver="liblinear",C=i,max_iter=1000)
    lrl1 = lrl1.fit(Xtrain,Ytrain)
    l1.append(accuracy_score(lrl1.predict(Xtrain),Ytrain))
    l1test.append(accuracy_score(lrl1.predict(Xtest),Ytest))
    lrl2 = lrl2.fit(Xtrain,Ytrain)
    l2.append(accuracy_score(lrl2.predict(Xtrain),Ytrain))
    l2test.append(accuracy_score(lrl2.predict(Xtest),Ytest))
graph = [l1,l2,l1test,l2test]
color = ["darkorange","black","orange","gray"]
label = ["L1","L2","L1test","L2test"]
plt.figure(figsize=(15,8))
for i in range(len(graph)):
    plt.plot(np.linspace(0.05,1,19),graph[i],color[i],label=label[i]) 
plt.legend(loc=4,fontsize=13)
plt.show()

2、threshold的学习曲线1

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.xlabel('np.linspace(0,abs((LR_.fit(data.data,data.target).coef_)).max(),20)',fontsize=15)
plt.ylabel('score',fontsize=15)
plt.legend()
plt.show()

3、 threshold的学习曲线2

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.xlabel('np.arange(0.01,10.01,0.5)',fontsize=15)
plt.ylabel('score',fontsize=15)
plt.legend()
plt.show()

4、max_iter学习曲线

l2 = []
l2test = []
Xtrain, Xtest, Ytrain, Ytest = train_test_split(X,y,test_size=0.3,random_state=420)
for i in np.arange(1,201,10):
    lrl2 = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=i)
    lrl2 = lrl2.fit(Xtrain,Ytrain)
    l2.append(accuracy_score(lrl2.predict(Xtrain),Ytrain))
    l2test.append(accuracy_score(lrl2.predict(Xtest),Ytest))
graph = [l2,l2test]
color = ["orange","black"]
label = ["L2","L2test"]
plt.figure(figsize=(15,5))
for i in range(len(graph)):
    plt.plot(np.arange(1,201,10),graph[i],color[i],label=label[i])
    plt.legend(loc=4,fontsize=15)
    plt.xticks(np.arange(1,201,10))
plt.title('max_iter',fontsize=15)
plt.show();
推荐阅读

机器学习 | 逻辑回归算法(一)理论

机器学习中样本不平衡,怎么办?

-- 数据STUDIO --

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存