一、逻辑回归的Scikit-Learn实现
### --- 逻辑回归的Scikit-Learn实现
~~~ # 参数详解
class sklearn.linear_model.LogisticRegression (penalty=’l2’, dual=False, tol=0.0001,
C=1.0,fifit_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
~~~ LogisticRegression默认就带了正则化项。
~~~ penalty参数可选择的值为"1"和"2",分别对应L1的正则化和L2的正则化,默认是L2的正则化。
~~~ 在调参时如果我们主要的目的只是为了解决过拟合,一般penalty选择L2正则化就够了。
~~~ 但是如果选择L2正则化发现还是过拟合,即预测效果差的时候,就可以考虑L1正则化。
~~~ 另外,如果模型的特征非常多,我们希望一些不重要的特征系数归零,
~~~ 从而让模型系数稀疏化的话,也可以使用L1正则化。
~~~ penalty参数的选择会影响我们损失函数优化算法的选择。
~~~ 即参数solver的选择,如果是L2正则化,
~~~ 那么4种可选的算法 ('newton-cg','Ibfgs','liblinear','sag'} 都可以选择。
~~~ 但是如果penalty是L1正则化的话,就只能选择 ‘liblinear'了。
~~~ 这是因为L1正则化的损失函数不是连续可导的,而 {‘newton-cg’,'Ibfgs,'sag'}
~~~ 这三种优化算法时都需要损失函数的一阶或者二阶连续导数。而 ’liblinear' 并没有这个依赖。
~~~ 而两种正则化下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()
X = data.data
y = data.target
data.shape
lrl1 = LR(penalty="l1",solver="liblinear",C=0.5,max_iter=1000)
#逻辑回归的重要属性coef_,查看每个特征所对应的参数
lrl2 = LR(penalty="l2",solver="liblinear",C=0.5,max_iter=1000)
lrl1 = lrl1.fit(X,y)
lrl1.coef_
(lrl1.coef_ != 0).sum(axis=1)
lrl2 = lrl2.fit(X,y)
lrl2.coef_
~~~ 可以看见,当我们选择L1正则化的时候,许多特征的参数都被设置为了0,
~~~ 这些特征在真正建模的时候,就不会出现在我们的模型当中了,
~~~ 而L2正则化则是对所有的特征都给出了参数。
~~~ 究竟哪个正则化的效果更好呢?还是都差不多?
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 = ["green","black","lightgreen","gray"]
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()
![](https://i-blog.csdnimg.cn/blog_migrate/1b0df9038865be63d0721f833f42fabb.png)
~~~ 可见,至少在我们的乳腺癌数据集下,两种正则化的结果区别不大。
~~~ 但随着C的逐渐变大,正则化的强度越来越小,模型在训练集和测试集上的表现都呈上升趋势,
~~~ 直到C=0.8左右,训练集上的表现依然在走高,但模型在未知数据集上的表现开始下跌,这时候就是出现了过拟合。
~~~ 我们可以认为,C设定为0.8会比较好。在实际使用时,基本就默认使用L2正则化,
~~~ 如果感觉到模型的效果不好,那就换L1试试看。
### --- 算法优化参数:solver
~~~ # solver参数决定了我们对逻辑回归损失函数的优化方法,有4种算法可以选择,分别是∶
~~~ liblinear∶使用了开源的liblinear库实现,内部使用了坐标轴下降法来迭代优化损失函数。
~~~ Ibfgs∶拟牛顿法的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
~~~ newton-cg∶也是牛顿法家族的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数。
~~~ sag∶即随机平均梯度下降,是梯度下降法的变种,和普通梯度下降法的区别是每次迭代仅用一部分的样本来计算梯度,适合于样本数据多的时候。
~~~ 从上面面的描述可以看出,newton-cg,
~~~ Ibfgs和sag这三种优化算法时都需要损失函数的一阶或者二阶连续导数,
~~~ 因此不能用于没有连续导数的L1正则化,只能用于L2正则化。而liblinear通吃L1正则化和L2正则化。
~~~ 同时,sag每次仅使用了部分样本进行梯度迭代,所以当样本量少的时候不要选择它,
~~~ 而如果样本量非常大,比如大于10万,sag是第一选择。
~~~ 但是sag不能用于L1正则化,所以当你有大量的样本,又需要L1正则化的话就要自己做取舍了。
~~~ 要么通过对样本采样来降低样本量量,要么回到L2正则化。
~~~ 从上面的描述,大家可能觉得,既然newton-cg,Ibfgs和sag这么多限制,
~~~ 如果不是大样本,我们选择liblinear不就行了吗?因为liblinear也有自己的弱点!
~~~ 我们知道,逻辑回归有二元逻辑回归和多元逻辑回归。
~~~ 对于多元逻辑回归常见的有one-vs-rest(OvR)和many-vs-many(MvM)两种,
~~~ 而MvM一般比OvR分类相对准确一些。liblinear只支持OVR,不支持MVM,
~~~ 这样如果我们需要相对精确的多元逻辑回归时就不的先择liblinear了。
~~~ 也意味着如果我们需要相对精确的多元逻辑回归不能位用L1正则化了。
### --- 梯度下降:重要参数max_iter
~~~ 逻辑回归的数学目的是求解能够让模型最优化,拟合程度最好的参数W的值,
~~~ 即求解能够让损失函数J(w)最小化的W值。
~~~ 对于二元逻辑回归来说,有多种方法可以用来求解参数W,
~~~ 最常见的有梯度下降法(Gradient Descent),坐标下降法(Coordinate Descent),
~~~ 牛顿法(Newton-Raphson method)等,其中又以梯度下降法最为著名。
~~~ 每种方法都涉及复杂的数学原理,但这些计算在执行的任务其实是类似的。
~~~ 来看看乳腺癌数据集下,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 = ["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中限制的步数已经走完了,逻辑回归却还没有找到损失函数的最小值,
~~~ 参数w的值还没有被收敛,sklearn就会弹出下面的红色警告
~~~ 当参数solver="liblinear"∶
![](https://i-blog.csdnimg.cn/blog_migrate/d5b500d2d21931dc49351bade2f92621.png)
~~~ 当参数solver="sag"∶
![](https://i-blog.csdnimg.cn/blog_migrate/708df4f947e73b3a3436918e4de9fe09.png)
~~~ 虽然写法看起来略有不同,但其实都是一个含义,
~~~ 这是在提醒我们:参数没有收敛,请增大max_iter中输入的数字。
~~~ 但我们不一定要听sklearn的。max_iter很大,意味着步长小,模型运行得会更加缓慢。然
~~~ 我们在梯度下降中追求的是损失函数的最小值,
~~~ 但这也可能意味着我们的模型会过拟合(在训练集上表现得太好,在测试集上却不一定),
~~~ 因此,如果在max iter报红条的情况下,
~~~ 模型的训练和预测效果都已经不错了,那我们就不需要再增大max_iter中的数目了,
~~~ 毕竟一切都以模型的预测效果为基准 一只要最终的预测效果好,运行又快,那就一切都好,无所谓是否报红色警告了。
### --- 分类方式选参数
~~~ multi_class参数决定了我们分类方式的选择,有ovr和multinomial两个值可以选择,默认是ovr。
~~~ ovr即前面提到的one-vs-rest(OvR),而multinomial即前面提到的many-vs-many(MvM)。
~~~ 如果是二元逻辑回归,ovr和multinomial并没有任何区别,区别主要在多元逻辑回归上。
~~~ OvR的思想很简单,无论你是多少元逻辑回归,我们都可以看做二元逻辑回归。
~~~ 具体做法是,对于第K 类的分类决策,我们把所有第K类的样本作为正例,
~~~ 除了第K类样本以外的所有样本都作为负例,然后在上面做二元逻辑回归,
~~~ 得到第K类的分类模型。其他类的分类模型获得以此类推。
~~~ # 生成三个假的数据集:
![](https://i-blog.csdnimg.cn/blog_migrate/2509d6bc54fbec24e44a1ccbe7ae7914.png)
~~~ # 定义一个函数:
![](https://i-blog.csdnimg.cn/blog_migrate/23d8d710382fdbe633128251da61771d.png)
~~~ 处理过的数据集就是⼆分类问题,通过逻辑回归可能得到⿊线区分不同类别。
~~~ 同理,
![](https://i-blog.csdnimg.cn/blog_migrate/177db35cb7e8fe43a0361b4219ed5853.png)
~~~ 定义函数:
![](https://i-blog.csdnimg.cn/blog_migrate/4128e12d09d5320ae5754b25f39ad05c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/67927dea2b73af4c2b1f28a903c7fdf0.png)
~~~ 处理过的数据集就是二分类问题,通过逻辑回归可能得到黑线区分不同类别。
~~~ 同理,
~~~ 当需要预测新的数据的类别时,使用如下公式∶
![](https://i-blog.csdnimg.cn/blog_migrate/22e2d47cde5d88db73a2a82c6822fa5a.png)
~~~ 使用不同的函数去预测输入X,分别计算不同Y^的值,然后取其中的最大值。
~~~ 哪个类别对应的 越大,就认为 属于哪个类。
~~~ 而MvM则相对复杂,这里举MvM的特例one-vs-one(OvO)作讲解。
~~~ 如果模型有T类,我们每次在所有的T类样本里面选择两类样本出来,
~~~ 不妨记为T1类和T2类,把所有的输出为T1和T2的样本放在一起,
~~~ 把T1作为正例,T2作为负例,进行二元逻辑回归,得到模型参数。
~~~ 我们一共需要T(T-1)/2次分类。
~~~ 从上面的描述可以看出OvR相对简单,
~~~ 但分类效果相对略差(这里指大多数样本分布情况,某些样本分布下OvR可能更好)。
~~~ 而MvM分类相对精确,但是分类速度没有OvR快。
~~~ 如果选择了ovr,则4种损失函数的优化方法liblinear,newton-cg,bfgs和sag都可以选择。
~~~ 但是如果选择了multinomial,则只能选择newton-g,Ibfgs和sag了。
~~~ 这里使用鸢尾花多分类数据集进行演示∶
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)
#打印两种multi_class模式下的训练分数
#%的⽤法,⽤%来代替打印的字符串中,想由变量替换的部分。%.3f表示,保留三位⼩数的浮点数。%s表示,
字符串。
#字符串后的%后使⽤元祖来容纳变量,字符串中有⼏个%,元组中就需要有⼏个变量
print("training score : %.3f (%s)" % (clf.score(iris.data,
iris.target),multi_class))