逻辑回归(分类算法)

转载自:【菜菜的sklearn】05 逻辑回归

1 概述

1.1 逻辑回归(分类算法)(理论)

线性回归模型
Y=β0+β1X++βnXn Y=\beta _0+\beta _1X+\cdots +\beta _nX_n

通常是处理因变量 Y 是连续变量的问题,

如果因变量是定性变量 (比如:yes 或 no,0 或 1,好或坏,输或赢),线性回归模型就不再适用了,需采用逻辑回归模型解决。
在这里插入图片描述

逻辑回归算法:虽然名字中带有回归两个字,但它却不是回归算法,而是一个经典的二分类算法

换句话说,逻辑回归就是用类似回归的思路来解决分类问题,本质是由线性回归变化而来的,一种广泛使用于分类问题中的广义回归算法,即:

y=g1(wTx+b) y=g^{-1}\left( w^Tx+b \right)

主要解决二分类问题,用来表示某件事情发生的可能性


设 研究分类观察结果为(Y);影响因素为(X)

因变量Y:二分类或多分类
自变量X:可以是连续的,也可以是分类的。

逻辑回归在处理分类问题时,其分类边界不一定都是线性的,也可以是非线性的。


考虑二分类任务,其输出标记 y∈{0,1},而线性回归模型产生的预测值 z=β0+β1x1++βnxnz=\beta _0+\beta _1x_1+\cdots +\beta _nx_n 是实值,于是,需要将实值 z 转换为 0/1 值。最理想的是“单位阶跃函数”:

y={0z<00.5z=01z>0 y=\left\{ \begin{array}{l} 0\text{,}z<0\\ \\ 0.5\text{,}z=0\\ \\ 1\text{,}z>0\\ \end{array} \right.

但是阶跃函数不是连续的,因此考虑用另一函数代替阶跃函数,即sigmoid函数:

引入联系函数(link function),将线性回归方程 z 变换为 y(z),并且令 y(z) 的值分布在(0,1)之间,且当y(z)接近0时样本的标签为类别0,当y(z)接近1时样本的标签为类别1,这样就得到了一个分类模型。而这个联系函数对于逻辑回归来说,就是Sigmoid函数

具体:
对于线性回归模型
z=β0+β1x1++βnxn z=\beta _0+\beta _1x_1+\cdots +\beta _nx_n

z 为实值,需要将实值z转换为0/1值:

若将 y 视为样本 x 作为正例的可能性,则 1−y 是其反例的可能性,两者的比值称为“机率“(odds),反映了 x 作为正例的相对可能性。对机率取对数则得到对数机率(逻辑回归也被称为”对数机率回归“):


z=log(y/(1y)) z=log\left( y/\left( 1-y \right) \right)
log(y1y)=β0+β1x1++βnxn \log \left( \frac{y}{1-y} \right) =\beta _0+\beta _1x_1+\cdots +\beta _nx_n

其中 y 是[0,1]之间的数值,可以认为是某件事情发生的可能性


注:逻辑回归返回概率这个命题,这种说法严谨吗?

事实上,虽然 y + (1-y) =1 ,但是 y 不是概率,因为 y 的概率密度在(,+)\left( -\infty ,+\infty \right)上面的积分(面积)不等于1,实际上积分值是正无穷。

但目前多数人都是以”返回概率“的方式来理解逻辑回归,并且这样使用它的性质


变换后得到:

在这里插入图片描述

在这里插入图片描述


Sigmoid函数的公式和性质
Sigmoid函数是一个S型的函数,当自变量z趋近正无穷时,因变量y(z)趋近于1,而当z趋近负无穷时,y(z)趋近于0,它能够将任何实数映射到(0,1)区间,使其可用于将任意值函数转换为更适合二分类的函数。

因为这个性质,Sigmoid函数也被当作是归一化的一种方法,与之前的MinMaxSclaer同理,是属于数据预处理中的“缩放”功能,可以将数据压缩到[0,1]之内。区别在于,MinMaxScaler归一化之后,是可以取到0和1的(最大值归一化后就是1,最小值归一化后就是0),但Sigmoid函数只是无限趋近于0和1。


将对数机率log(y/(1y))\log \left( y/\left( 1-y \right) \right)写为概率形式:

logP(y=1x)1P(y=1x)=logP(y=1x)P(y=0x)=β0+β1x1++βnxn \log \frac{P\left( y=1|x \right)}{1-P\left( y=1|x \right)}=\log \frac{P\left( y=1|x \right)}{P\left( y=0|x \right)}=\beta _0+\beta _1x_1+\cdots +\beta _nx_n

显然有
P(y=1x)=eβ0+β1x1++βnxn1+eβ0+β1x1++βnxn   P\left( y=1|x \right) =\frac{e^{\beta _0+\beta _1x_1+\cdots +\beta _nx_n}}{1+e^{\beta _0+\beta _1x_1+\cdots +\beta _nx_n}}\,\,

P(y=0x)=11+eβ0+β1x1++βnxn   P\left( y=0|x \right) =\frac{1}{1+e^{\beta _0+\beta _1x_1+\cdots +\beta _nx_n}}\,\,

1.2 为什么需要逻辑回归

  1. 逻辑回归对线性关系的拟合效果好,比如逻辑回归应用在金融领域
  2. 逻辑回归计算快:对于线性数据,逻辑回归的拟合和计算都非常快,计算效率优于SVM和随机森林
  3. 逻辑回归返回的分类结果不是固定的 0或1,而是以小数形式呈现的类概率数字:因此可以把逻辑回归返回的结果当成连续型数据来利用
  4. 逻辑回归抗噪能力强,在小数据集上表现更好

逻辑回归数学目的是求解能够让模型对数据拟合程度最高的参数的值,以此构建预测函数 y(x),然后将特征矩阵输入预测函数来计算出逻辑回归的结果y。注意,虽然我们熟悉的逻辑回归通常被用于处理二分类问题,但逻辑回归也可以做多分类。

1.3 sklearn中的逻辑回归

在这里插入图片描述

2 linear_model.LogisticRegression

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)

2.1 二元逻辑回归的损失函数

2.1.1 损失函数的概念与解惑

逻辑回归有着基于训练数据求解参数 β\beta 的需求,并且希望训练出来的模型能够尽可能地拟合训练数据,即模型在训练集上的预测准确率越靠近100%越好。

因此,使用”损失函数“这个评估指标,来衡量参数为 β\beta 的模型拟合训练集时产生的信息损失的大小,并以此衡量参数 β\beta 的优劣

在求解参数 β\beta 时,追求损失函数最小,让模型在训练数据上的拟合效果最优,即预测准确率尽量靠近100%。

关键概念:损失函数

  • 衡量参数 β\beta 的优劣的评估指标,用来求解最优参数的工具
  • 损失函数小,模型在训练集上表现优异,拟合充分,参数优秀
  • 损失函数大,模型在训练集上表现差劲,拟合不足,参数糟糕
  • 追求能够让损失函数最小化的参数组合

注意:没有”求解参数“需求的模型没有损失函数,比如KNN,决策树

逻辑回归的损失函数是由极大似然估计推导出来的,具体结果可以写作:
J(β)=i=1m(yilog(yβ(xi))+(1yi)log(1yβ(xi))) J\left( \beta \right) =-\sum_{i=1}^m{\left( y_i*\log \left( y_{\beta}\left( x_i \right) \right) +\left( 1-y_i \right) *\log \left( 1-y_{\beta}\left( x_i \right) \right) \right)}
其中,
β\beta 表示求解出来的一组参数,m是样本的个数,
yiy_i 是样本 i 上真实的标签,
yβ(xi)y_{\beta}\left( x_i \right) 是样本 i 上,基于参数β\beta 计算出来的逻辑回归返回值,
xix_i 是样本 i 各个特征的取值(行)

我们的目标是求解出使损失函数 J(β)J\left( \beta \right) 最小的 β\beta 取值。注意,在逻辑回归的本质函数y(x)里,特征矩阵x是自变量,参数是 β\beta 。但在损失函数中,参数β\beta 是损失函数的自变量,x 和 y 都是已知的特征矩阵和标签,相当于是损失函数的参数。

2.2 重要参数 penalty & C

2.2.1 正则化

如果模型在训练集上表示优秀,却在测试集上表现糟糕,模型就会过拟合。
对逻辑回归中过拟合的控制,通过正则化来实现

正则化是用来防止模型过拟合的过程,常用的有L1正则化L2正则化两种选项,分别通过在损失函数后加上参数向量 的L1范式和L2范式的倍数来实现。这个增加的范式,被称为“正则项”,也被称为"惩罚项"。

损失函数改变,基于损失函数的最优化来求解的参数取值必然改变,以此来调节模型拟合的程度。其中

  • L1范式表现为参数向量中的每个参数的绝对值之和,L1正则化为J(β)L1=CJ(β)+j=1nβj (j1) J\left( \beta \right) _{L1}=C*J\left( \beta \right) +\sum_{j=1}^n{\left| \beta _j \right|}\ \left( j\ge 1 \right)

  • L2范数表现为参数向量中的每个参数的平方和的开方值,L2正则化为J(β)L2=CJ(β)+j=1n(βj)2 (j1) J\left( \beta \right) _{L2}=C*J\left( \beta \right) +\sqrt{\sum_{j=1}^n{\left( \beta _j \right) ^2}}\ \left( j\ge 1 \right)

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

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

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

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

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

而两种正则化下C的取值,都可以通过学习曲线来进行调整。

下面建两个逻辑回归模型,分别使用L1正则化和L2正则化,进行对比

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()
data.data.shape  # (569, 30) 569个样本,30个特征
data.feature_names
'''
array(['mean radius', 'mean texture',..,'worst fractal dimension'], dtype='<U23')
'''

X = data.data # 特征矩阵
y = data.target
'''
array([0, 0, 0, 0, 0, 0,... 0, 0, 0, 0, 0, 0, 1])
'''

# 实例化 分别使用L1正则化和L2正则化 ,C相同
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_  # 30个系数,大部分为0,相当于进行特征选择
'''
array([[ 4.01042427,  0.03193774, -0.13858089, -0.01622997,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        ,  0.50477939,  0.        , -0.07125383,  0.        ,
         0.        ,  0.        ,  0.        ,  0.        ,  0.        ,
         0.        , -0.24588618, -0.128632  , -0.01440303,  0.        ,
         0.        , -2.03173397,  0.        ,  0.        ,  0.        ]])
'''

(lrl1.coef_ != 0).sum(axis=1)  #  array([10])

lrl2 = lrl2.fit(X,y)  
lrl2.coef_ # 均不为0 ,L2正则化则是对所有的特征都给出了参数
'''
array([[ 1.61543234e+00,  1.02284415e-01,  4.78483684e-02,
        -4.43927107e-03, -9.42247882e-02, -3.01420673e-01,
        -4.56065677e-01, -2.22346063e-01, -1.35660484e-01,
        -1.93917198e-02,  1.61646580e-02,  8.84531037e-01,
         1.20301273e-01, -9.47422278e-02, -9.81687769e-03,
        -2.37399092e-02, -5.71846204e-02, -2.70190106e-02,
        -2.77563737e-02,  1.98122260e-04,  1.26394730e+00,
        -3.01762592e-01, -1.72784162e-01, -2.21786411e-02,
        -1.73339657e-01, -8.79070550e-01, -1.16325561e+00,
        -4.27661014e-01, -4.20612369e-01, -8.69820058e-02]])
'''

究竟哪个正则化的效果更好呢?还是都差不多?

画参数 C 的学习曲线

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)
    # accuracy_score 准确率
    # lrl1.predict(Xtrain)模型预测的训练Y
    l1.append(accuracy_score(lrl1.predict(Xtrain),Ytrain))
    # lrl1.predict(Xtest) 模型预测的测试Y
    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 = ["#FF0000","#000000","#00bc12","#44cef6"]
label = ["L1","L2","L1test","L2test"]

plt.figure(figsize=(6,6)) # 画布大小
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) #图例的位置在哪里?4表示,右下角
    plt.show()

在这里插入图片描述
有轻微的过拟合

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

2.2.2 逻辑回归中的特征工程

当特征的数量很多的时候,出于业务考虑,也出于计算量的考虑,希望对逻辑回归进行特征选择来降维。

  • 业务选择
    说到降维和特征选择,首先要想到的是利用自己的业务能力进行选择,肉眼可见明显和标签有关的特征就是需要留下的。

  • PCA和SVD一般不用
    这两种方法大多数时候不适用于逻辑回归,因为PCA和SVD的降维结果是不可解释的,因此一旦降维后,我们就无法解释特征和标签之间的关系了。当然,在不需要探究特征与标签之间关系的线性数据上,降维算法PCA和SVD也是可以使用的。

  • 统计方法可以使用,但不是非常必要
    逻辑回归对数据的总体分布和方差没有要求,也不需要排除特征之间的共线性

  • 高效的嵌入法embedded
    由于L1正则化会使得部分特征对应的参数为0,因此L1正则化可以用来做特征选择,结合嵌入法的模块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  # (569, 30)
# 实例化 默认penalty="l1"  这里C=0.9
LR_ = LR(solver="liblinear",C=0.9,random_state=420)
# 降维前 10折 交叉验证
cross_val_score(LR_,data.data,data.target,cv=10).mean()
 # 0.9490601503759398 效果不错

# 特征选择  不考虑训练集测试集的问题,把所有的数据都放入模型进行降维
# norm_order=1 指的是使用L1范式来进行筛选
X_embedded = SelectFromModel(LR_,norm_order=1).fit_transform(data.data,data.target)
X_embedded.shape  # (569, 9) 特征由30个 降到 9个
# 降维后 交叉验证
cross_val_score(LR_,X_embedded,data.target,cv=10).mean()
# 0.9368107769423559  模型效果略微下降

能否让模型的拟合效果更好呢?在这里,有两种调整方式:

1)调节SelectFromModel这个类中的参数threshold

这是嵌入法的阈值,表示删除所有参数的绝对值低于这个阈值的特征。现在threshold默认为None,所以SelectFromModel只根据L1正则化的结果来选择了特征,即选择了所有L1正则化后参数不为0的特征。

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

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.10897274787330495, 17)
(0.2179454957466099, 12)
(0.32691824361991484, 10)
(0.4358909914932198, 8)
(0.5448637393665248, 8)
(0.6538364872398297, 5)
(0.7628092351131347, 5)
(0.8717819829864396, 5)
(0.9807547308597445, 5)
(1.0897274787330495, 5)
(1.1987002266063544, 4)
(1.3076729744796594, 3)
(1.4166457223529643, 2)
(1.5256184702262694, 2)
(1.6345912180995743, 1)
(1.7435639659728792, 1)
(1.8525367138461841, 1)
(1.961509461719489, 1)
(2.070482209592794, 1)

在这里插入图片描述
当threshold越来越大,被删除的特征越来越多,模型的效果也越来越差,模型效果最好的情况下需要保证有17个以上的特征。

2)第二种调整方法,是调逻辑回归的类LR_,通过画C的学习曲线来实现:

fullx = []
fsx = []
C=np.arange(0.01,10.01,0.5) # 调C
for i in C:
    LR_ = LR(solver="liblinear",C=i,random_state=420) # 默认使用L1正则化
    # 特征选择前
    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=(10,5))
plt.plot(C,fullx,label="full")
plt.plot(C,fsx,label="feature selection")
plt.xticks(C)
plt.legend()
plt.show()

0.8822994987468672 0.01
0.9244987468671677 0.51
0.9368107769423559 1.01
0.9438283208020051 1.51
0.9473370927318296 2.01
0.9473370927318296 2.01
0.9490914786967419 3.01
0.9508458646616542 3.51
0.9543546365914788 4.01
0.9543546365914788 4.01
0.9543546365914788 4.01
0.9543546365914788 4.01
0.9543546365914788 4.01
0.9543546365914788 4.01
0.9561090225563911 7.01
0.9561090225563911 7.01
0.9561090225563911 7.01
0.9561090225563911 7.01
0.9561090225563911 7.01
0.9561090225563911 7.01

在这里插入图片描述

  • 比较麻烦的系数累加法
  • 简单快速的包装法

2.3 梯度下降:重要参数max_iter

逻辑回归的数学目的求解能够让损失函数 最小化的参数 β\boldsymbol{\beta } 值。对于二元逻辑回归来说,有多种方法可以用来求解参数 ,最常见的有梯度下降法(Gradient Descent),座标下降法,牛顿法等,其中又以梯度下降法最为著名。每种方法都涉及复杂的数学原理,但这些计算在执行的任务其实是类似的。

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)

2.3.1 梯度下降求解逻辑回归

梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。

在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。

在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。

反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了

在机器学习中,基于基本的梯度下降法发展了两种梯度下降方法,分别为随机梯度下降法和批量梯度下降法。


现在有一个带两个特征并且没有截距的逻辑回归 y(β1,β2),两个特征所对应的参数分别为β1和β2 。下面这个华丽的平面就是我们的损失函数 J(β1,β2)J\left( \beta _1,\beta _2 \right) 在以 β1\beta _1β2\beta _2 ,J 为座标轴的三维立体座标系上的图像。现在,寻求的是损失函数的最小值,也就是图像的最低点。

在这里插入图片描述

在这个图像上随机放一个小球,这个小球就会顺着这个华丽的平面滚落,直到滚到深蓝色的区域——损失函数的最低点。为了严格监控这个小球的行为,让小球每次滚动的距离有限,不让它一次性滚到最低点,并且最多只允许它滚动100步,还要记下它每次滚动的方向,直到它滚到图像上的最低点。

可以看见,小球从高处滑落,在深蓝色的区域中来回震荡,最终停留在了图像凹陷处的某个点上

小球每次滚动的方向都是不同的。

小球不一定滚到了图像的最低处。

但无论如何,小球停下的就是在现有状况下可以获得的唯一点了。如果够幸运,这个点就是图像的最低点,那只要找到这个点的对应座标 (β1,β2,Jmin)\left( \beta _1^*,\beta _2^*,J_{\min} \right),就可以获取能够让损失函数最小的参数取值 了。如此,梯度下降的过程就已经完成。


在这个过程中,小球其实就是一组组的座标点 (β1,β2,J)\left( \beta _1,\beta _2,J \right) ;小球每次滚动的方向就是那一个座标点的梯度向量的方向,因为每滚动一步,小球所在的位置都发生变化,座标点和座标点对应的梯度向量都发生了变化,所以每次滚动的方向也都不一样;人为设置的100次滚动限制,就是sklearn中逻辑回归的参数max_iter(最大迭代次数),代表着能走的最大步数,即最大迭代次数。

所以,梯度下降其实就是在众多(β1,β2)\left( \beta _1,\beta _2 \right) 可能的值中遍历,一次次求解座标点的梯度向量,不断让损失函数的取值 J 逐渐逼近最小值,再返回这个最小值对应的参数取值(β1,β2)\left( \beta _1^*,\beta _2^* \right) 的过程。

2.3.2 梯度下降的概念与解惑

百度百科解释
梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)。

对于可微的数量场f(x,y,z)f\left( x,y,z \right) ,以(fx,fy,fz)\left( \frac{\partial f}{\partial x},\frac{\partial f}{\partial y},\frac{\partial f}{\partial z} \right)为分量的向量场称为 f 的梯度或斜量。(注意:这里的 x y z 与上面逻辑回归中的x y z 不是同一个,不要混淆)


核心误区:到底在哪个函数上,求什么的偏导数?

一个多元函数的梯度,是对其自变量求偏导的结果,不是对其参数求偏导的结果。

在多元函数(损失函数)上对自变量(逻辑回归的预测函数y(x)的参数)求偏导
即是说:
求解梯度,是在损失函数 J(β1,β2)J\left( \beta _1,\beta _2 \right) 上对损失函数自身的自变量 β1\beta _1β2\beta _2 求偏导,而这两个自变量,刚好是逻辑回归的预测函数 y(x)=11+eβTxy\left( x \right) =\frac{1}{1+e^{-\boldsymbol{\beta }^{\boldsymbol{T}}\boldsymbol{x}}} 的参数。


梯度是一个向量,因此它有大小也有方向。它的大小,就是偏导数组成的向量的大小,又叫做向量的模,记作 d 。它的方向,几何上来说,就是损失函数 J(β)\boldsymbol{J}\left( \boldsymbol{\beta } \right) 的值增加最快的方向,就是小球每次滚动的方向的反方向。只要沿着梯度向量的反方向移动座标,损失函数 J(β)\boldsymbol{J}\left( \boldsymbol{\beta } \right) 的取值就会减少得最快,也就最容易找到损失函数的最小值。

在逻辑回归中,损失函数如下所示:

J(β)=i=1m(yilog(yβ(xi))+(1yi)log(1yβ(xi))) J\left( \beta \right) =-\sum_{i=1}^m{\left( y_i*\log \left( y_{\beta}\left( x_i \right) \right) +\left( 1-y_i \right) *\log \left( 1-y_{\beta}\left( x_i \right) \right) \right)}
其中,
β\beta 表示求解出来的一组参数,m是样本的个数,
yiy_i 是样本 i 上真实的标签,
yβ(xi)y_{\beta}\left( x_i \right) 是样本 i 上,基于参数β\beta 计算出来的逻辑回归返回值,
xix_i 是样本 i 各个特征的取值(行)

对这个函数上的自变量 β\beta 求偏导,就可以得到梯度向量在第 j 组 β\beta 的座标点上的表示形式:
βjJ(β)=dj=i=1m(yβ(xi)yi)xij \frac{\partial}{\partial \beta _j}J\left( \beta \right) =d_j=\sum_{i=1}^m{\left( y_{\beta}\left( x_i \right) -y_i \right) x_{ij}}

在这个公式下,只要给定一组 β\beta 的取值 βj\beta _j ,再带入特征矩阵 x ,就可以求得这一组 β\beta 取值下的预测结果 yβ(xi)y_{\beta}\left( x_i \right),结合真实标签向量 y ,就可以获得这一组 β\beta 取值下的梯度向量,其大小表示为 d。之前说过,我们的目的是在可能的 β\beta 取值上进行遍历,一次次计算梯度向量,并在梯度向量的反方向上让损失函数 J 下降至最小值。在这个过程中,β\beta 和梯度向量的大小 d 都会不断改变,而遍历 β\beta 的过程可以描述为:

βj+1=βjαdj=βjαi=1m(yβ(xi)yi)xij \beta _{j+1}=\beta _j-\alpha *d_j=\beta _j-\alpha *\sum_{i=1}^m{\left( y_{\beta}\left( x_i \right) -y_i \right) x_{ij}}
其中 βj+1\beta _{j+1} 是第 j 次迭代后的参数向量,βj\beta _j 是第 j 次迭代是的参数向量, α\alpha 被称为步长,控制着每走一步(每迭代一次)后 β\beta 的变化,并以此来影响每次迭代后的梯度向量的大小和方向。

2.3.3 步长的概念与解惑

核心误区:步长到底是什么?
步长不是一个物理距离,不是我们所理解的长度或者距离

下面这一张二维平面的求导三角型图。
在这里插入图片描述

图中的抛物线就是我们的损失函数 J(θ)J\left( \theta \right) (注:这里 θ\thetaβ\beta 一样,符号表示不同而已 ) , A(θa,J(θa))A\left( \theta _a,J\left( \theta _a \right) \right) 就是小球最初在的位置, B(θb,J(θb))B\left( \theta _b,J\left( \theta _b \right) \right) 就是一次滚动后小球移动到的位置。从A到B的方向就是梯度向量的反方向,指向损失函数在A点下降最快的方向。而梯度向量的大小是点A在图像上对 θ\theta 求导后的结果,也是点A切线方向的斜率橙色角的 tan 结果,记作 d

  • 梯度下降每走一步,损失函数减小的量,是损失函数在 θ\theta 变化之后的取值的变化,写作 J(θa)J(θb)J\left( \theta _a \right) -J\left( \theta _b \right) ,这是二维平面的求导三角型中的“对边”。

  • 梯度下降每走一步,参数向量的变化,写作 θaθb\theta _a-\theta _b ,根据向量的迭代公式,就是 步长 * 梯度向量的大小,记作 αd\alpha *d ,这是二维平面的求倒三角形中的“邻边”。

  • 梯度下降中每走一步,下降的距离,是 (αd)2+(J(θa)J(θb))2\sqrt{\left( \alpha *d \right) ^2+\left( J\left( \theta _a \right) -J\left( \theta _b \right) \right) ^2} ,是对边和邻边的根号下平方和,是二维平面的求导三角型中的”斜边“。

所以,步长不是任何物理距离,它甚至不是梯度下降过程中任何距离的直接变化,它是梯度向量的大小 d 上的一个比例,影响着参数向量每次迭代后改变的部分。

步长可以调节损失函数下降的速率。在损失函数降低的方向上,

  • 步长越长,θ\boldsymbol{\theta } 的变动就越大。
  • 步长如果很短,θ\boldsymbol{\theta } 的每次变动就很小。

具体地说,如果步长太大,损失函数下降得就非常快,需要的迭代次数就很少,但梯度下降过程可能跳过损失函数的最低点,无法获取最优值。而步长太小,虽然函数会逐渐逼近我们需要的最低点,但迭代的速度却很缓慢,迭代次数就需要很多。

在这里插入图片描述


搜集几张图,帮助理解
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

因此,在sklearn当中,通过设置参数 max_iter 最大迭代次数来代替步长,帮助我们控制模型的迭代速度并适时地让模型停下

  • max_iter越大,代表步长越小,模型迭代时间越长,
  • max_iter越小,则代表步长设置很大,模型迭代时间很短。

迭代结束,获取到 J(θ)\boldsymbol{J}\left( \boldsymbol{\theta } \right) 的最小值后,就可以找出这个最小值对应的参数向量 θ\boldsymbol{\theta } ,逻辑回归的预测函数也就可以根据这个参数向量 θ\boldsymbol{\theta } 来建立了。

max_iter的学习曲线:

# 导包
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 # 特征选择
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载乳腺癌数据,判断是否患乳腺癌
data = load_breast_cancer()
data.data.shape  # (569, 30) 569个样本,30个特征
data.feature_names
'''
array(['mean radius', 'mean texture',..,'worst fractal dimension'], dtype='<U23')
'''

X = data.data # 特征矩阵
y = data.target.data # 标签

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 = ["black","gray"]
label = ["L2","L2test"]
plt.figure(figsize=(20,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)
plt.xticks(np.arange(1,201,10))
plt.show()

#我们可以使用属性.n_iter_来调用本次求解中真正实现的迭代次数
lr = LR(penalty="l2",solver="liblinear",C=0.9,max_iter=300).fit(Xtrain,Ytrain)
lr.n_iter_

当 max_iter 中限制的步数已经走完了,逻辑回归却还没有找到损失函数的最小值,参数 的值还没有被收敛,sklearn就会弹出红色警告

这是在提醒我们:参数没有收敛,请增大max_iter中输入的数字。但我们不一定要听sklearn的。

max_iter很大,意味着步长小,模型运行得会更加缓慢。虽然在梯度下降中追求的是损失函数的最小值,但这也可能意味着我们的模型会过拟合(在训练集上表现得太好,在测试集上却不一定)

因此,如果在max_iter报红条的情况下,模型的训练和预测效果都已经不错了,那我们就不需要再增大max_iter中的数目了,毕竟一切都以模型的预测效果为基准——只要最终的预测效果好,运行又快,那就一切都好,无所谓是否报红色警告了。

2.4 二元回归与多元回归:重要参数solver & multi_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提供了多种可以使用逻辑回归处理多分类问题的选项。

比如说,

  • "一对多"(One-vs-rest),简称OvR,在sklearn中表示为“ovr"
    把某种分类类型都看作1,其余的分类类型都为0值
  • ”多对多“(Many-vs-Many)的方法,简称MvM,在sklearn中表示为"Multinominal"
    把好几个分类类型划为1,剩下的几个分类类型划为0值

每种方式都配合L1或L2正则项来使用。

在sklearn中,使用参数multi_class来告诉模型,我们的预测标签是什么样的类型。
multi_class

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

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


sklearn 提供了不同的求解器来计算逻辑回归的参数(梯度下降法就是其中之一)

由参数"solver"控制,共有五种选择。其中“liblinear”是二分类专用,也是现在的默认求解器。

在这里插入图片描述

multinomial 和 ovr 的区别怎么样:

from sklearn.linear_model import LogisticRegression as LR # 逻辑回归
from sklearn.datasets import load_iris
iris = load_iris()
for multi_class in ('multinomial', 'ovr'): 
    clf = LR(solver='sag', 
                             max_iter=100, 
                             random_state=42,
                             multi_class=multi_class).fit(iris.data, iris.target)
    print("training score : %.3f (%s)" % (clf.score(iris.data, iris.target),multi_class))

# training score : 0.987 (multinomial)
# training score : 0.960 (ovr)

如果是多分类问题,一般选择 multi_class=multinomial

2.5 样本不平衡与参数class_weight

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)

样本不平衡是指在一组数据集中,标签的一类天生占有很大的比例,或误分类的代价很高,即我们想要捕捉出某种特定的分类的时候的状况。

因此我们要使用参数class_weight对样本标签进行一定的均衡,给少量的标签更多的权重,让模型更偏向少数类,向捕获少数类的方向建模。

该参数默认None,此模式表示自动给与数据集中的所有标签相同的权重,即自动1:1。当误分类的代价很高的时候,我们使用”balanced“模式,我们只是希望对标签进行均衡的时候,什么都不填就可以解决样本不均衡问题。

但是,sklearn当中的参数class_weight变幻莫测,可以说是非常难用。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章