逻辑回归 - 参数部分(四)

根据菜菜的课程进行整理,方便记忆理解

代码位置如下:

参数(二)

梯度下降:重要参数max_iter

逻辑回归的数学目的是求解能够让模型最优化,拟合程度最好的参数的值,即求解能够让损失函数最小化的值。对于二元逻辑回归来说,有多种方法可以用来求解参数,最常见的有梯度下降法(Gradient Descent),坐标下降法(Coordinate Descent),牛顿法(Newton-Raphson method)等,其中又以梯度下降法最为著名。

梯度下降求解逻辑回归

我们以最著名也最常用的梯度下降法为例,来看看逻辑回归的参数求解过程究竟实在做什么。现在有一个带两个特征并且没有截距的逻辑回归 y ( x 1 , x 2 ) y(x_1,x_2) ,两个特征所对应的参数分别为[ θ 1 , θ 2 \theta_1,\theta_2 ]。下面这个华丽的平面就是我们的损失函数 J ( θ 1 , θ 2 ) J(\theta_1,\theta_2) 在以 θ 1 \theta_1 θ 1 \theta_1 J J 为坐标轴的三维立体坐标系上的图像。现在,我们寻求的是损失函数的最小值,也就是图像的最低点。

image.png

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

可以看见,小球从高处滑落,在深蓝色的区域中来回震荡,最终停留在了图像凹陷处的某个点上。非常明显,我们可以观察到几个现象:

首先,小球并不是一开始就直向着最低点去的,它先一口气冲到了蓝色区域边缘,后来又折回来,我们已经规定了小球是多次滚动,所以可见,小球每次滚动的方向都是不同的。

另外,小球在进入深蓝色区域后,并没有直接找到某个点,而是在深蓝色区域中来回震荡了数次才停下。这有两种可能:

  • 小球已经滚到了图像的最低点,所以停下了
  • 由于我设定的步数限制,小球还没有找到最低点,但也只好在100步的时候停下了

也就是说,小球不一定滚到了图像的最低处。

但无论如何,小球停下的就是我们在现有状况下可以获得的唯一点了。如果我们够幸运,这个点就是图像的最低点,那我们只要找到这个点的对应坐标( θ 1 , θ 2 , J m i n \theta_{1}*,\theta_{2}*,J_{min} ),就可以获取能够让损失函数最小的参数取值[ θ 1 , θ 2 , \theta_{1}*,\theta_{2}*, ]了。如此,梯度下降的过程就已经完成。

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

所以梯度下降,其实就是在众多[ θ 1 , θ 2 \theta_1,\theta_2 ]可能的值中遍历,一次次求解坐标点的梯度向量,不断让损失函数的取值 J J 逐渐逼近最小值,再返回这个最小值对应的参数取值[ θ 1 , θ 2 , \theta_{1}*,\theta_{2}*, ]的过程。

梯度下降的概念与解惑
  • 梯度
    • 定义
    • 在多元函数上对各个自变量求∂偏导数,把求得的各个自变量的偏导数以向量的形式写出来,就是梯度。
      • 比如损失函数 J ( θ 1 , θ 2 ) J(\theta_1,\theta_2) ,其自变量是逻辑回归预测函数 y θ ( x ) y_{\theta}(x) 的参数 θ 1 , θ 2 \theta_1,\theta_2 ,在损失函数上对 θ 1 , θ 2 \theta_1,\theta_2 求偏导数,求得的梯度向量d就是[ J θ 1 , J θ 2 \frac{∂J}{∂\theta_1},\frac{∂J}{∂\theta_2} ],简称grad J ( θ 1 , θ 2 ) J(\theta_1,\theta_2) 或者 J ( θ 1 , θ 2 ) \nabla J(\theta_1,\theta_2) 。在 θ 1 , θ 2 \theta_1,\theta_2 J J 的取值构成的坐标系上,点 ( θ 1 , θ 2 , J ) (\theta_1*,\theta_2*,J) 具体的梯度向量就是[ J θ 1 , J θ 2 \frac{∂J}{∂\theta_1*},\frac{∂J}{∂\theta_2*} ],或者 J ( θ 1 , θ 2 ) \nabla J(\theta_1*,\theta_2*) 。如果是3个参数的梯度向量,就是 [ J θ 1 , J θ 2 , J θ 3 ] T [\frac{∂J}{∂\theta_1},\frac{∂J}{∂\theta_2},\frac{∂J}{∂\theta_3}]^T ,以此类推

到底在哪个函数上,求什么的偏导数?

注意,在一些博客或教材中,讲解梯度向量的定义时会写一些让人容易误解的句子,比如“对多元函数的参数求∂偏导数,把求得的各个参数的偏导数以向量的形式写出来,就是梯度”。注意,这种解释一眼看上去没错,却是不太严谨的。

一个多元函数的梯度,是对其自变量求偏导的结果,不是对其参数求偏导的结果。但是在逻辑回归的数学过程中,损失函数的自变量刚好是逻辑回归的预测函数y(x)的参数,所以才造成了这种让人误解的,“对多元函数的参数求偏导”的写法。务必记住,正确的做法是:在多元函数(损失函数)上对自变量(逻辑回归的预测函数y(x)的参数)求偏导,求解梯度的方式,和逻辑回归本身的预测函数y(x)没有一丝联系。

以及,有一些博客会以f(x,y)作为例子,解释说梯度向量是 ( f x , f y ) T (\frac{∂f}{∂x},\frac{∂f}{∂y})^T 。这种举例方式又会造成误解:很多人看到这个式子,会特别自然地理解成:“ x是逻辑回归中的特征呀,所以梯度向量是对模型的特征,即x求偏导数”。这个例子,其实是在表明,我们是对多元函数的自变量求偏导数,并不是代表我们在逻辑回归 中要对特征求偏导数。

在这里我会不厌其烦地给大家强调:求解梯度,是在损失函数 J ( θ 1 , θ 2 ) J(\theta_1,\theta_2) 上对损失函数自身的自变量 θ 1 \theta_1 θ 2 \theta_2 求偏导,而这两个自变量,刚好是逻辑回归的预测函数 y ( x ) = 1 1 + e θ T x y(x) = \frac{1}{1 + e^{-\theta^Tx}} 的参数。

那梯度有什么含义呢?

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

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

image.png

我们对这个函数上的自变量求偏导,就可以得到梯度向量在第组的坐标点上的表示形式:

image.png

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

image.png

其中 θ j + 1 \theta_{j + 1} 是第j次迭代后的参数向量, θ j \theta_j 是第j次迭代是的参数向量, α \alpha 被称为步长,控制着每走一步(每迭代一次)后 θ \theta 的变化,并以此来影响每次迭代后的梯度向量的大小和方向。

步长的概念

核心误区:步长到底是什么? 许多博客和教材在描述步长的时候,声称它是”梯度下降中每一步沿梯度的反方向前进的长度“,”沿着最陡峭最易下山的位置走的那一步的长度“或者”梯度下降中每一步损失函数减小的量“,甚至有说,步长是二维平面著名的求导三角形中的”斜边“或者“对边”的。

这些说法都是错误的!

来看下面这一张二维平面的求导三角型图。类比到我们的损失函数和梯度概念上,图中的抛物线就是我们的损失函数 J ( θ ) J(\theta) A ( θ a , J ( θ a ) ) A(\theta_a,J(\theta_a)) 就是小球最初在的位置, B ( θ b , j ( θ b ) ) B(\theta_b,j(\theta_b)) 就是一次滚动后小球移动到的位置。从A到B的方向就是梯度向量的反方向,指向损失函数在A点下降最快的方向。而梯度向量的大小是点A在图像上对求导后的结果,也是点A切线方向的斜率,橙色角的tan结果,记作d。

image.png

梯度下降每走一步,损失函数减小的量,是损失函数在 θ \theta 变化之后的取值的变化,写作 J ( θ b ) J ( θ a ) J(\theta_b) - J(\theta_a) ,这是二维平面的求导三角型中的“对边”。 梯度下降每走一步,参数向量的变化,写作 θ a θ b \theta_a - \theta_b ,根据我们参数向量的迭代公式,就是我们的 步长 * 梯度向量的大小,记作 α d \alpha * d ,这是二维平面的求倒三角形中的“邻边”。

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

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

不难发现,既然参数迭代是靠梯度向量的大小d * 步长 α \alpha 来实现的,而 J ( θ ) J(\theta) 的降低又是靠调节 θ \theta 来实现的,所以步长可以调节损失函数下降的速率。在损失函数降低的方向上,步长越长, θ \theta 的变动就越大。相对的,步长如果很短, θ \theta 的每次变动就很小。具体地说,如果步长太大,损失函数下降得就非常快,需要的迭代次数就很少,但梯度下降过程可能跳过损失函数的最低点,无法获取最优值。而步长太小,虽然函数会逐渐逼近我们需要的最低点,但迭代的速度却很缓慢,迭代次数就需要很多。

image.png

记得我们在看小球运动时注意到,小球在进入深蓝色区域后,并没有直接找到某个点,而是在深蓝色区域中来回震荡了数次才停下,这种”震荡“其实就是因为我们设置的步长太大的缘故。但是在我们开始梯度下降之前,我们并不知道什么样的步长才合适,但梯度下降一定要在某个时候停止才可以,否则模型可能会无限地迭代下去。因此,在sklearn当中,我们设置参数max_iter最大迭代次数来代替步长,帮助我们控制模型的迭代速度并适时地让模型停下。max_iter越大,代表步长越小,模型迭代时间越长,反之,则代表步长设置很大,模型迭代时间很短

迭代结束,获取到的最小值后,我们就可以找出这个最小值对应的参数向量,逻辑回归的预测函数也就可以根据这个参数向量来建立了。来看看乳腺癌数据集下,max_iter的学习曲线:

# max_iter是进行随机梯度下降的次数
# 下面的警告中说的是,算法还没有达到收敛,但是迭代次数已经用完了,请加大迭代次数
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
l2 = []
l2test = []

data = load_breast_cancer()
x = data.data
y = data.target

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()
复制代码

image.png

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

  • 当参数solver="liblinear":

image.png

  • 当参数solver="sag":

image.png

虽然写法看起来略有不同,但其实都是一个含义,这是在提醒我们:参数没有收敛,请增大max_iter中输入的数字。但我们不一定要听sklearn的。max_iter很大,意味着步长小,模型运行得会更加缓慢。虽然我们在梯度下降中追求的是损失函数的最小值,但这也可能意味着我们的模型会过拟合(在训练集上表现得太好,在测试集上却不一定),因此,如果在max_iter报红条的情况下,模型的训练和预测效果都已经不错了,那我们就不需要再增大max_iter中的数目了,毕竟一切都以模型的预测效果为基准——只要最终的预测效果好,运行又快,那就一切都好,无所谓是否报红色警告了。

二元回归与多元回归:重要参数solver & multi_class

之前我们对逻辑回归的讨论,都是针对二分类的逻辑回归展开,其实sklearn提供了多种可以使用逻辑回归处理多分类问题的选项。

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

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

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

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

solver

我们之前提到的梯度下降法,只是求解逻辑回归参数的一种方法,并且我们只讲解了求解二分类变量的参数时的各种原理。sklearn为我们提供了多种选择,让我们可以使用不同的求解器来计算逻辑回归。

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

image.png

来看看鸢尾花数据集上,multinomial和ovr的区别怎么样:

# 我们来看一下在鸢尾花数据集上的multinomial和ovr表现
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))

"""
training score : 0.987 (multinomial)
training score : 0.960 (ovr)
"""
复制代码

样本不平衡与参数class_weight

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

  • 什么情况下误分类的代价很高?
    • 例如,我们现在要对潜在犯罪者和普通人进行分类,如果没有能够识别出潜在犯罪者,那么这些人就可能去危害社会,造成犯罪,识别失败的代价会非常高,但如果,我们将普通人错误地识别成了潜在犯罪者,代价却相对较小。所以我们宁愿将普通人分类为潜在犯罪者后再人工甄别,但是却不愿将潜在犯罪者分类为普通人,有种"宁愿错杀不能放过"的感觉。
    • 再比如说,在银行要判断“一个新客户是否会违约”,通常不违约的人vs违约的人会是99:1的比例,真正违约的人其实是非常少的。这种分类状况下,即便模型什么也不做,全把所有人都当成不会违约的人,正确率也能有99%,这使得模型评估指标变得毫无意义,根本无法达到我们的“要识别出会违约的人”的建模目的。

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

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

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

猜你喜欢

转载自juejin.im/post/7088206159739355149
今日推荐