sklear机器学习:逻辑回归

本文深入解析逻辑回归原理,涵盖似然函数、损失函数及正则化项。探讨sklearn库中逻辑回归模型的使用,包括参数解释、样本不平衡处理及特征工程。通过实例演示不同正则化、参数C和max_iter的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关于逻辑回归的原理和推导,移步线性回归与逻辑回归。接下来不会再过多的解释原理,主要是学习库的调用与参数的解释。按照顺序依次学习以下内容

  1. 逻辑回归的简单介绍
  2. sklearn中的逻辑回归的使用与参数解释
  3. 样本不平衡问题

逻辑回归的介绍与简单推导

逻辑回归的损失函数是由似然函数得到的, 令 h θ ( x ) = g ( θ T x ) = 1 1 + e − θ T x h_{\theta}(x)=g(\theta^Tx)=\frac{1}{1+e^{-\theta^Tx}} hθ(x)=g(θTx)=1+eθTx1,那么我们就得到了回归模型了,接下来需要去学习 θ \theta θ,我们做如下的假设:

P ( y = 1 ∣ x ; θ ) = h θ ( x ) P(y=1|x;\theta)=h_{\theta}(x) P(y=1x;θ)=hθ(x)
P ( y = 0 ∣ x ; θ ) = 1 − h θ ( x ) P(y=0|x;\theta)=1-h_{\theta}(x) P(y=0x;θ)=1hθ(x)

假设现在给我个x,如何得到y? 使用上面两个式子得到两个P,哪个大就归为哪一类。现在将两个式子写到一起
P ( y ∣ x ; θ ) = h θ ( x ) y ( 1 − h θ ( x ) ) 1 − y P(y|x;\theta)=h_{\theta}(x)^y(1-h_{\theta}(x))^{1-y} P(yx;θ)=hθ(x)y(1hθ(x))1y

逻辑回归学习也就是求 θ \theta θ,使用极大似然估计来求解(极大似然估计使用的地方就是已知模型,有数据来求参数)。

L ( θ ) = ∏ i = 1 n h θ ( x i ) y i ( 1 − h θ ( x i ) ) 1 − y i L(\theta)=\prod\limits_{i=1}^{n}h_{\theta}(x_i)^{y_i}(1-h_{\theta}(x_i))^{1-y_i} L(θ)=i=1nhθ(xi)yi(1hθ(xi))1yi
l ( θ ) = l n L ( θ ) = ∑ i = 1 n [ y i l n ( h θ ( x i ) ) + ( 1 − y i ) l n ( 1 − h θ ( x i ) ) ) ] l(\theta)=lnL(\theta)=\sum\limits_{i=1}^{n}[y_iln(h_{\theta}(x_i)) + (1-y_i)ln(1-h_{\theta}(x_i)))] l(θ)=lnL(θ)=i=1n[yiln(hθ(xi))+(1yi)ln(1hθ(xi)))]

似然函数通常是求极大,而损失函数则是求极小,那么我们在上式中加个负号则得到逻辑回归的损失函数,如下

J ( θ ) = − ∑ i = 1 n [ y i l n ( h θ ( x i ) ) + ( 1 − y i ) l n ( 1 − h θ ( x i ) ) ) ] J(\theta)=-\sum\limits_{i=1}^{n}[y_iln(h_{\theta}(x_i)) + (1-y_i)ln(1-h_{\theta}(x_i)))] J(θ)=i=1n[yiln(hθ(xi))+(1yi)ln(1hθ(xi)))]

目标是求损失函数 J ( θ ) J(\theta) J(θ)的极小值从而得到参数 θ \theta θ,但是这样对训练数据学习的太好会出现过拟合,所以需要加上正则化项,可以是l1正则化 C ∑ j = 1 m ∣ θ j ∣ C\sum\limits_{j=1}^{m}|\theta_j| Cj=1mθj或者是l2正则化 C ∑ j = 1 m ( θ j ) 2 C\sqrt{\sum\limits_{j=1}^{m}(\theta_j)^2} Cj=1m(θj)2 。其中C是用来控制惩罚力度的,但是在sklearn中C是写在了 J ( θ ) J(\theta) J(θ)前,所以最终的损失函数是

J ( θ ) L 1 = C ⋅ J ( θ ) + ∑ j = 1 m ∣ θ j ∣ J(\theta)_{L1}=C\cdot J(\theta) + \sum\limits_{j=1}^{m}|\theta_j| J(θ)L1=CJ(θ)+j=1mθj
J ( θ ) L 2 = C ⋅ J ( θ ) + ∑ j = 1 m ( θ j ) 2 J(\theta)_{L2}=C\cdot J(\theta) +\sqrt{\sum\limits_{j=1}^{m}(\theta_j)^2} J(θ)L2=CJ(θ)+j=1m(θj)2

那么对于损失函数如何求极小呢?比如可以使用梯度下降法(细分又可以分为随机梯度下降、批量梯度下降、下批量随机梯度下降),牛顿法和拟牛顿法等。具体使用哪种方法根据需要自己选择,sklearn中基本都提供了。下面写出梯度下降的式子

θ j : = θ j − α ∂ l ( θ ) ∂ θ j \theta_j := \theta_j - \alpha \frac{\partial l(\theta)}{\partial \theta_j} θj:=θjαθjl(θ)

对于 θ 1 − θ m \theta_1-\theta_m θ1θm我们每次都执行上面的更新操作,直到两次更新前后的差很小的时候便停止,此时的 θ 1 − θ m \theta_1-\theta_m θ1θm就是我们所需要的,同时也满足损失函数值最小。

注意到这里的超参数 α \alpha α,称为步长,拿下山的例子来说,表示每次沿着梯度的反方向走下去的长度,如果步子太小,那么求解会收敛的很慢;但是如果步子太长,很可能直接越过了最低点,不管怎样,设置了允许跨的最多的步数后,此时不管有没有走到最低点都会停止。所以不能保证能到达最低点。

sklearn中的逻辑回归

class 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)

参数不算太多,但是不像决策树那样简单,基于前面的介绍,下面来使用sklearn中实现好的逻辑回归模型。

import numpy as np
import matplotlib.pyplot as plt
from sklearn.linear_model import LogisticRegression as LR
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
#使用乳腺癌数据
cancer = load_breast_cancer()
X = cancer.data   #特征矩阵
y = cancer.target  #标签
#训练集和测试的划分
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size = 0.3)
lr1 = LR()  #实例化模型
lr1.fit(Xtrain,Ytrain) #模型拟合
score = lr1.score(Xtest,Ytest)  #得分
score,len(cancer.feature_names) #30个特征 所以有三十个theta

输出:(0.9532163742690059, 30),达到了0.9532,效果还是不错的,上面实例化模型使用的都是默认参数,那么就着重介绍几个参数吧。

lr1 = LR(penalty="l1",C=0.5,solver="liblinear",max_iter=1000)
lr2 = LR(penalty="l2",C=0.5,solver="liblinear",max_iter=1000)

上面实例化模型使用了四个参数,下面一一介绍
(1)、penalty
penalty看名字就知道是惩罚项的意思,也就是正则化,默认的是字符串"l2"表示l2正则化,填写"l1"则表示使用l1正则化。这两者是有区别的,l1正则化会将参数压缩到0,但是l2正则化只会使参数尽量下而不会使之变为0。

在L1正则化在逐渐加强的过程中,携带信息量小的、对模型贡献不大的特征的参数,会比携带大量信息的、对模型有巨大贡献的特征的参数更快地变成0,所以L1正则化本质是一个特征选择的过程,掌管了参数的“稀疏性”。L1正则化越强,参数向量中就越多的参数为0,参数就越稀疏,选出来的特征就越少,以此来防止过拟合。因此,如果特征量很大,数据维度很高,我们会倾向于使用L1正则化。由于L1正则化的这个性质,逻辑回归的特征选择可以由Embedded嵌入法来完成。

相对的,L2正则化在加强的过程中,会尽量让每个特征对模型都有一些小的贡献,但携带信息少,对模型贡献不大的特征的参数会非常接近于0。通常来说,如果我们的主要目的只是为了防止过拟合,选择L2正则化就足够了。但是如果选择L2正则化后还是过拟合,模型在未知数据集上的效果表现很差,就可以考虑L1正则化。

(2)、C
C也就是之前介绍的控制惩罚力度的(这里是之前介绍的C的倒数,即正则化强度的倒数),需要填浮点数,默认是1.0,C的值越小,模型对损失函数的惩罚就越严重,正则化的效力就越强。

(3)、max_iter
最大迭代次数,代表能走的最大步数,是用来控制步长 α \alpha α的,max_iter越大,代表步长越小,模型迭代时间越长,反之,则代表步长设置很大,模型迭代时间很短。

参数liblinear后面继续介绍,先来看这三个参数的使用。

lr1.fit(Xtrain,Ytrain)
lr1.coef_,lr1.score(Xtest,Ytest)

输出如下
在这里插入图片描述

(lr1.coef_ != 0).sum(axis=1)

输出 :array([9])

可以发现,使用l1正则化的时候,30个特征本应该有30个 θ \theta θ,但是最终只有9个了,即21个被压缩成了0。再来看l2正则化,对了,属性coef_是用来看模型中参数的(这里就是 θ \theta θ

lr2.fit(Xtrain,Ytrain)
lr2.coef_

输出:
在这里插入图片描述

对于使用哪个正则化,鸢尾花数据集上来看是差不多的,具体的可以画学习曲线来看,这里就忽略了。接下来会画参数C的学习曲线来找合适的值

l1_score_ = []
l2_score_ = []
for i in np.linspace(0.05,10,30):
    lr_1 = LR(penalty="l1",C=i,solver="liblinear",max_iter = 1000)
    lr_2 = LR(penalty="l2",C=i,solver="liblinear",max_iter = 1000)
    lr_1.fit(Xtrain,Ytrain)
    l1_score_.append(lr_1.score(Xtest,Ytest))
    lr_2.fit(Xtrain,Ytrain)
    l2_score_.append(lr_2.score(Xtest,Ytest))
graph = [l1_score_,l2_score_]
color = ["green","black"]
lab = ["l1","l2"]
plt.figure(figsize=(7,6))
for i in range(len(graph)):
    plt.plot(np.linspace(0.05,8,30),graph[i],color = color[i],label=lab[i])
plt.legend(loc=4)
plt.show()

输出

在这里插入图片描述

就拿绿色那条线来看,初始的C较小,模型效果呈上升趋势,但是到了2之后就下降了,基本可以确定是由于惩罚项的强度小了,导致了模型过拟合。如果要找出具体的值,那么细化C,即C在0到2之间取值,再画出学习曲线找到使得模型效果最好的C的值。

再来画max_iter的学习曲线找合适的值

#max_iter的学习曲线
ls = []
for i in np.arange(10,200,10):
    lr = LR(penalty="l2",C=2,solver="liblinear",max_iter=i)
    ls.append(cross_val_score(lr,X,y,cv=5).mean())
plt.plot(np.arange(10,200,10),ls)
plt.show()

输出

在这里插入图片描述

哪个警告指的是因为迭代次数太少导致没有收敛。从图中看出当迭代次数得到40左右就可以收敛了。

接下来学习下逻辑回归的特征工程,在之前学过sklearn中的PCA与SVD来进行降维,遗憾的是,这两种方法大多数时候不适用于逻辑回归。逻辑回归是由线性回归演变而来,线性回归的一个核心目的是通过求解参数来探究特征X与标签y之间的关系,而逻辑回归也传承了这个性质,我们常常希望通过逻辑回归的结果,来判断什么样的特征与分类结果相关,因此我们希望保留特征的原貌。PCA和SVD的降维结果是不可解释的,因此一旦降维后,我们就无法解释特征和标签之间的关系了。当然,在不需要探究特征与标签之间关系的线性数据上,降维算法PCA和SVD也是可以使用的。

在之前讲了数据预处理与特征工程,里面介绍了方差过滤,卡方检验等,最后介绍了Embeded嵌入法,逻辑回归中可以使用该方法。我们已经说明了,由于L1正则化会使得部分特征对应的参数为0,因此L1正则化可以用来做特征选择,结合嵌入法的模块SelectFromModel,我们可以很容易就筛选出让模型十分高效的特征。

cancer = load_breast_cancer()
X = cancer.data
y = cancer.target
cancer.data.shape

输出:(569, 30) 也就是初始有30个特征了,继续

from sklearn.model_selection import cross_val_score
from sklearn.feature_selection import SelectFromModel
lr = LR(penalty="l2",C=0.8,solver="liblinear",max_iter = 1000)
score1 = cross_val_score(lr,X,y,cv=5).mean()
x_emb = SelectFromModel(lr,norm_order=1).fit_transform(X,y)
score2 = cross_val_score(lr,x_emb,y,cv=5).mean()
x_emb.shape,score1,score2

输出:((569, 9), 0.9509041939207385, 0.935005771450558),特征降成了9个,模型效果却没有差太多,但是能不能让模型效果更好呢?下面介绍两种方式

(1)、调整SelectFromModel的参数threshold

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

lr.fit(Xtrain,Ytrain)
lr.coef_
x_l = []
x_f = []
threshold = np.linspace(0,abs((lr.coef_)).max(),20)
ans = []
k = 0
for i in threshold:
    x_emb = SelectFromModel(lr,threshold=i).fit_transform(X,y)
    score = cross_val_score(lr,x_emb,y,cv=5).mean()
    x_l.append(score)
    score2 = cross_val_score(lr,X,y,cv=5).mean()
    x_f.append(score2)
    if score > 0.94:
        ans.append(x_emb.shape[1])
    print(threshold[k],x_emb.shape[1],score)
    k += 1
plt.plot(threshold,x_l)
plt.plot(threshold,x_f)
plt.show()

这里不给出图了,结果是没有一个合适的阈值,那么考虑第二种方式

(2)、逻辑回归调参
这里调整的是参数C,画出学习曲线来选出合适的值

#第二种调整方法  调整逻辑回归参数
list1 = []
list2 = []
C = np.arange(0.01,10.01,0.5)
for i in C:
    lr = LR(penalty="l2",C=i,solver="liblinear",max_iter = 1000)
    list1.append(cross_val_score(lr,X,y,cv=5).mean())
    x_emb = SelectFromModel(lr,norm_order=1).fit_transform(X,y)
    list2.append(cross_val_score(lr,x_emb,y,cv=5).mean())
print(max(list2),C[list2.index(max(list2))])  
plt.figure(figsize=(10,6))
plt.plot(C,list1,label="full")
plt.plot(C,list2,label="feature selection")
plt.legend(loc=4)
plt.show()

从图中确定了大致的C的范围,然后细化C继续画学习曲线来寻找即可。

补充:可以使用Embeded嵌入法,同样也可以使用Wrapper包装法

相对的,包装法可以直接设定我们需要的特征个数,逻辑回归在现实中运用时,可能会有”需要5~8个变量”这种需求,包装法此时就非常方便了。不过逻辑回归的包装法的使用和其他算法一样,并不具有特别之处,这里就不多说了,参考数据预处理与特征工程

之前说的都是二分类问题,sklearn中的逻辑回归也是支持多分类的。比如说,我们可以把某种分类类型都看作1,其余的分类类型都为0值,和”数据预处理“中的二值化的思维类似,这种方法被称为"一对多"(One-vs-rest),简称OvR,在sklearn中表示为“ovr"。又或者,我们可以把
好几个分类类型划为1,剩下的几个分类类型划为0值,这是一种”多对多“(Many-vs-Many)的方法,简称MvM,在sklearn中表示为"Multinominal"。每种方式都配合L1或L2正则项来使用。

在sklearn中使用参数multi_class来告诉模型是使用哪种分类

ovr’:表示分类问题是二分类,或让模型使用"一对多"的形式来处理多分类问题。
‘multinomial’:表示处理多分类问题,这种输入在参数solver是’liblinear’时不可用。
“auto”:表示会根据数据的分类情况和其他参数来确定模型要处理的分类问题的类型。比如说,如果数据是二分类,或者solver的取值为"liblinear",“auto"会默认选择"ovr”。反之,则会选择"nultinomial"。
注意:默认值将在0.22版本中从"ovr"更改为"auto"。

之前不是说,可以选择不同的方法来求解模型参数 θ \theta θ吗,在sklearn中使用参数solver来确定,取值如下
在这里插入图片描述注意:liblinear是二分类专用

#鸢尾花数据集上,multinomial和ovr的区别怎么样
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
y = iris.target
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3)
for i in ("multinomial","ovr"):
    lr = LR(penalty="l2",solver="sag",max_iter=100,multi_class=i).fit(Xtrain,Ytrain)
    print("training score:%.3f(%s)"%(lr.score(Xtest,Ytest),i))

样本不平衡问题

在银行要判断“一个新客户是否会违约”,通常不违约的人vs违约的人会是99:1的比例,真正违约的人其实是非常少的。这种分类状况下,即便模型什么也不做,全把所有人都当成不会违约的人,正确率也能有99%,这使得模型评估指标变得毫无意义,根本无法达到我们的“要识别出会违约的人”的建模目的。

虽然sklearn的逻辑回归模型提供了参数class_weight可以对样本标签进行均衡,给少量的标签更多的权重给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重,即自动1:1。当误分类的代价很高的时候,我们使用”balanced“模式,我们只是希望对标签进行均衡的时候,什么都不填就可以解决样本不均衡问题。但是,sklearn当中的参数class_weight变幻莫测,我们很难去找出这个参数引导的模型趋势,或者画出学习曲线来评估参数的效果,因此可以说是非常难用

我们有着处理样本不均衡的各种方法,其中主流的是采样法,是通过重复样本的方式来平衡标签,可以进行上采样(增加少数类的样本),比如SMOTE,或者下采样(减少多数类的样本)。对于逻辑回归来说,上采样是最好的办法

import pandas as pd
data = load_breast_cancer()
X=data.data
y = data.target
print(X.shape)
pd.Series(y).value_counts()

输出

在这里插入图片描述

#上采样
import imblearn
from imblearn.over_sampling import SMOTE
sm = SMOTE(random_state=200)
X,y = sm.fit_sample(X,y)
print(X.shape)
pd.Series(y).value_counts()

在这里插入图片描述
是不是发现了样本量增加了,两类标签的样本数也是一样了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值