机器学习 | 逻辑回归算法(二)LogisticRegression
文章机器学习 | 逻辑回归算法(一)理论中详细介绍了逻辑回归理论知识。
逻辑回归是一种广义线性回归模型,是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
(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
>>> 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
对比下结果:
降维前 | 降维后 | |
---|---|---|
维数 | 30 | 9 |
模型得分 | 0.9508145363 | 0.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
中输入的数字的所有特征。
代码见附录2、threshold的学习曲线1
然而,这种方法其实是比较无效的,当threshold
越来越大,被删除的特征越来越多,模型的效果也越来越差。当模型中保留17个以上的特征时模型效果最好。
第二种通过逻辑回归的类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
(569, 9)
梯度下降参数max_iter
max_iter: int, default=100
使求解器收敛所需的最大迭代次数。
在逻辑回归理论篇有介绍利用梯度下降法求解损失函数,损失函数沿着下降最快的方向,即梯度向量的反方向,按照一定的步长不断迭代,直至下降到最小值时停止迭代。
步长是梯度向量的大小上的一个比例,影响着参数向量每次迭代后改变的部分。
sklearn当中,参数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();
推荐阅读