第 6 章 逻辑斯谛回归

在介绍了机器学习中相关的基本概念和技巧后,本章我们继续讲解参数化模型中的线性模型。有了前文的基础,我们可以先来对 KNN 算法和线性回归进行比较,进一步回答“什么是参数化模型”这一问题。对于机器学习算法来说,其目标通常可以抽象为得到某个从输入空间到输出空间的映射,对每个输入数据,该映射给出预测的标签。而对于映射的形式,不同算法有不同的假设。像 KNN 这样,不对的形式做先验假设、在学习中可以得到其任意形式的模型,称为非参数化模型(nonparametric model)。而与之相对,有一类算法会先假设具有某种特定的形式。例如,我们可以假设输入与输出一定满足某个二次函数关系,即。这时,我们就只需要对映射的参数进行学习。像这样对进行先验假设的模型,称为参数化模型(parametric model)。

相比于非参数化模型,参数化模型由于限制了可能的集合,学习难度相对较低。以上面的二次函数为例,非参数化模型需要在所有可能映射的集合中寻找合适的,而参数化模型的搜索范围只有,对应于一个 3 维的实数参数空间,降低了搜索难度和时间开销。然而,对搜索空间的限制也成为了参数化模型的缺点。如果真实的输入输出关系是,那么以二次函数为基础的参数化模型,显然无法在大范围的输入上得到都误差较小的结果。因此,参数化模型通常需要对已知数据和问题特性进行分析,确定合适的参数化假设,才能得到理想的学习结果。除此之外,参数化模型由于假设了数据的分布,其参数的数量通常和数据集的大小无关。因为无论数据集中有多少数据,我们都认为它们是由同一个分布生成的。而非参数化模型也并非“没有参数的模型”,但其参数的数量通常会随数据集的大小而变化,例如 KNN 算法的参数可以看成是每一个数据的特征和标签,它与数据集始终是同等规模的。

在线性回归中,我们利用参数化的线性假设解决了回归问题,而分类问题作为机器学习任务中的另一大类别,其与回归问题既有相似也有不同。通常来说,回归问题的输出是连续的,而分类问题的输出是离散的。设输入数据,输出标签。其中是一有限的离散集合,其每一个元素表示一个不同的类别。事实上,当是一有限的离散集合时,多分类问题与二分类问题等价。因为我们总可以先判断“是否属于第一类”,再判断“是否属于第二类”,以此类推,从而可以用至多次二分类来完成分类。因此,大多数时候我们可以只考虑最简单的二分类问题,其中样本标签。下面,我们将详细介绍如何利用参数化模型逻辑斯谛回归(logistic regression)来处理分类问题。

6.1 逻辑斯谛函数下的线性模型

与线性回归类似,对二分类问题,我们同样可以作线性假设。设学习到的映射为,参数为。在线性回归中,我们直接计算输入样本与的乘积。然而,直接使用该乘积在二分类问题中有两个问题。第一,该乘积是连续的,并不能拟合离散变量;第二,该乘积的取值范围是,与我们期望的相距很远。为了解决这两个问题,最简单的方法是再引入阈值,定义如下:

似乎刚刚的两个问题都得到了解决,但是,这一方法又为模型训练带来了新的困难。在线性回归中我们已经介绍过,无论是解析方法还是梯度下降,都需要以函数的梯度为基础。然而,这样的分类方法太“硬”了,使得在阈值处出现了跳跃,从而不再可导;而在阈值之外可导的地方,其导数又始终为。因此,硬分类得到的难以直接训练。

我们不妨换一个角度来考虑二分类问题。如果把样本的类别看作是有 0 和 1 两种取值的随机变量,我们只需要判断之间大小关系,再将归为概率较大的一类即可。事实上,我们并不一定需要的输出必须是 0 或 1 之中的一个。如果能够给出样本的类别的概率分布,即,同样可以达到分类的效果。相比于硬分类,概率分布可以用连续函数建模,从而可以对求梯度。并且,在很多决策问题中,给出每个分类的概率信息比直接给出最后的分类结果要更有用。

至此我们已经解决了用连续函数给出离散分类和硬分类函数不可导的两个问题。但我们最开始已经提到,的取值范围是,而概率分布的取值范围应当是。因此,我们需要某种从的映射来确保其取值范围相同。在实践中,我们通常采用逻辑斯谛函数(logistic function),其定义为:

该函数的图像如图 6-1 所示。

logistic
图 6-1 逻辑斯谛函数的图像

逻辑斯谛函数是一种常见的 sigmoid 函数。sigmoid 函数是一类形状类似于‘S’型的函数,但在实践中如无特殊说明,一般就指逻辑斯谛函数。逻辑斯谛函数有许多优秀的性质。首先,它关于点对称,从而有,这意味着,即当相反时,其概率分布也正好相反。其次,从图像中可以看出,偏离 0 时会迅速收敛到 0 或 1。例如在时,,与 1 已经非常接近。这一性质使得其对的变化较为敏感,适合作为分类函数。最后,逻辑斯谛函数在上连续、单调递增且可导,具有良好的分析性质。其导数为:

综上所述,我们只需要用逻辑斯谛函数对进行变换,就可以得到符合要求的映射。虽然这一映射不再是线性的,但它依然是以线性函数为基础,再经过某种变换得到的。这样的模型属于广义线性模型,感兴趣的读者可以参考本章的拓展阅读。

6.2 最大似然估计

确定了逻辑斯谛回归的数学模型之后,我们接下来还需要确定优化目标。对于有关概率分布的问题,我们常常使用最大似然估计(maximum likelihood estimation,MLE)的思想来优化模型,也即是寻找逻辑斯谛回归的参数使得模型在训练数据上预测出正确标签的概率最大。

设共有个样本,类别分别是。对于样本,如果,那么模型预测正确的概率为;如果,那么概率为。将两者综合起来,可以得到模型正确的概率为。假设样本之间是两两独立的,那么模型将所有样本的分类都预测正确的概率就等于单个样本概率的乘积:

该函数也称为似然函数(likelihood function)。为了使模型的预测尽可能准确,我们需要寻找使似然函数最大的参数。但是,该函数的连乘形式使得求导和优化都很困难,在计算机上直接计算甚至很容易造成浮点数越界。因此,我们一般将似然值取对数,也即是对数似然(log-likelihood),将连乘转化为求和:

由于对数函数是单调递增的,优化可以得到相同的结果。于是,我们的优化目标为:

求梯度,得到:

再把代入,并利用就得到:

注意到我们的优化目标是最大化,因此定义损失函数,将优化目标转化为最小化。根据梯度下降算法,设学习率为,则的更新公式为:

需要注意,在实际计算时,我们通常会对样本取平均,来消除样本规模对学习率的影响。为了得到更简洁和易于计算的形式,定义样本矩阵,标签向量,以及向量形式的逻辑斯谛函数,就可以将梯度写成矩阵形式:

从而梯度下降算法的矩阵形式为:

除此之外,我们再为模型加入正则化约束,设正则系数为,则完整的优化目标与迭代公式为:

逻辑斯谛回归的损失函数也是凸函数,图 6-2 展示了在逻辑斯谛回归模型中,带有正则化约束的损失函数等值线。为了能在二维平面中展示参数的变化,已经固定,仅有变化。图中绿色的曲线表示从不同初始参数开始进行梯度下降的轨迹。这些等值线已经不是椭圆形,但进行梯度下降时,凸函数的性质仍然保证可以收敛到最优解。

lr_sgd
图 6-2 逻辑斯谛回归损失函数的梯度下降

由于逻辑斯谛函数只适用于二分类的情况,对于多分类任务,我们需要寻找新的映射函数。这时,我们通常采用柔性最大值(softmax)函数来将线性部分的预测映射到多分类概率上。该函数的定义为

其中,是一个维的得分向量,每一维取值代表第类的得分。softmax 函数将维的向量映射到新的维向量,容易验证,映射后的向量各个元素取值在,并且它们取值之和为 1。因此,可以看作一个有种取值的离散随机变量的概率分布。这一特性使得 softmax 函数可以在多分类问题上作为映射函数。设参数分别用来预测样本属于第 1 类到第类的未归一化概率,记。那么,就给出了属于第类的概率。

下面我们来考察 softmax 函数的一些性质。直观上看,在原始的向量中越大的元素,映射后仍然越大。因此,softmax 函数不改变向量元素的大小顺序。此外,与逻辑斯谛函数类似,softmax 函数避免了直接取最大元素导致的不可导问题。事实上,如果在上式中令,softmax 函数就退化为逻辑斯谛函数。或者说,逻辑斯谛函数只是 softmax 函数在二分类前提下的特殊情况。所以,我们同样用来表示 softmax 函数。

简单起见,在本节的后续部分我们仍然考虑二分类任务。softmax 函数的更多性质与多分类任务的损失函数留作习题供读者思考。

6.3 分类问题的评价指标

在动手实现逻辑斯谛回归之前,我们还需要解决一个问题:如何判断分类模型的好坏呢?对于一个二分类问题,最简单的评估模型好坏的方式就是测试模型的正确率。这时,无论是用直接给出类别的硬分类,还是给出不同分类概率的软分类,我们都需要考虑分类的阈值。例如,如果模型判断某样本的类别是正类的概率是 0.6,而预设的阈值是 0.5,就应当将该样本归为正类;如果阈值是 0.7,虽然该样本是正类的概率比是负类的概率大,我们仍然应当将它归为负类。在我们的默认思维中,一般会通过比较正负类的概率大小给出最后的判断,这实际上隐含了“阈值为 0.5”的假设。

读者或许有疑问,为什么不能遵循最自然的大小关系,将 0.5 作为阈值呢?这与实际应用中分类任务的特点有关。例如,当前的任务是进行汽车的出厂检测,根据检测的数据判断汽车是否有质量问题,没有问题是负类,有问题是正类。在这样的任务中,如果模型把有问题的汽车错判为没有问题,就可能让质量不合格的汽车出场,其后果比把没有问题的汽车错判为有问题要严重得多。因此,我们需要把正类的阈值设置的很高,比如 0.99。可以发现,虽然抽象的分类问题中,正类负类之间的地位是对等的,因此将正类错判为负类和将负类错判为正类没有什么区别;但是实际场景中,这两者往往并不对称,我们必须根据任务的特点设置合适的阈值。并且,只有正确率这一标准是不够的,还需要分别考虑正负类分别被错判的比例。

在机器学习中,我们通常用图 6-3 的混淆矩阵(confusion matrix)来统计不同的分类结果。其中,真阳性(true positive,TP)表示将真实的正类判别为正类,真阴性(true negative,TN)表示将真实的负类判别为负类,这两者属于判断正确的结果。假阴性(false negative,FN)表示将真实的正类判别为负类,假阳性(false positive,FP)表示将真实的负类判别为正类,属于判断错误的结果。我们常说的精度(accuracy)就是判断正确的样本数量除以样本总数:

在上面汽车质检的例子中,我们更关心找出有问题的汽车占真正有问题汽车的比例,即真阳性率(true positive rate),也称查全率或召回率(recall):

此外,我们常常还希望假阳性尽可能少,即真阳性占模型判为阳性的样本比例尽可能高。这一指标称为查准率(precision):

由于样本总数固定,混淆矩阵的 4 个指标并非相互独立。因此,我们常用查全率和查准率这两个标准作为代表,其他指标的变化也可以通过它们来间接反映。

confusion matrix
图 6-3 混淆矩阵的组成

或许读者已经发现,混淆矩阵导出的数个统计指标是不可兼得的,它们都与预设的阈值有关。例如,如果我们将阈值设得很低,就会将大量样本判为正类,提高真阳性的数量,从而使召回率上升。然而,这种做法同时会使许多实际上为负类的样本被归为正类,造成大量假阳性,反而可能让精确率下降。因此,我们应当根据任务具体的特点,先确定不同指标的重要程度,再选取合适的阈值。为了同时反映各个指标的情况,达到平衡点,我们引入新的指标 F1 分数(F1 score),定义为精确率和召回率的调和平均值:

可以看出,F1 分数最小为 0,在精确率或召回率有一方为 0 时取到;最大为 1,在精确率和召回率相等时取到。因此,单方面增大或减小精确率和召回率中的一个无法使 F1 分数增高,必须要让两者得到平衡。如果在实际问题中,精确率和召回率的重要程度不同,我们还可以把 F1 分数扩展为 F-beta 分数:

上式相当于给精确率添加了权重,给召回率添加了权重越大,召回率就越重要;反之,越小,精确率就越重要。从数学角度来看,当的定义式中时,它就退化为精确率;当时,它就退化为召回率。在实践中,都是较为常用的指标。

对于更普遍的情况来说,我们希望衡量模型在不同阈值下的整体表现,根据不同指标随阈值的变化来得出与阈值无关的模型本身的特性。因此,我们选择假阳性率(false positive rate,FPR)与真阳性率(召回率)这两个随阈值变化趋势相同的指标,把它们的值绘制成曲线。其中,FPR 定义为

我们来具体分析一下随阈值的变化趋势。当阈值为 0 时,模型会把所有样本归为正类。这时不存在假阴性和真阴性,因此都为 1。当阈值逐渐增大,模型将把越来越多的样本归为负类,所以真阳性和假阳性减少,假阴性和真阴性增多,从而都减小。而阈值增大到时,所有样本都被归为负类,情况与阈值为 0 时正好相反,不存在真阳性和假阳性,都减小到 0。因此,这两个统计指标都随阈值的增大而减小。

那么,它们的变化趋势为何能反映模型的好坏呢?为了方便读者理解,我们在图 6-4 中绘制了的变化曲线,该曲线称为受试者操作特征(receiver operating characteristic,ROC)曲线。从右上角到左下角,阈值由 0 增大到 1。需要注意,这两个指标并非同步变化的。例如,某样本真实类别为负类,模型判断它是正类的概率为 0.305。并且阈值从 0.3 增大到 0.31 的过程中,其他样本的类别预测没有变化,仅有这一个样本由正类变为负类。那么在这个过程中,真阳性和假阴性没有变化,假阳性减少,真阴性增加,从而不变、减小。该过程反映在 ROC 上是一条水平线。相反,如果上例的样本真实类别是正类,那么真阳性减少、假阴性增加、假阳性和真阴性不变,从而减小,不变,在 ROC 线上是一条竖直线。由于数据集是离散的,而模型预测的概率值连续,绝大多数情况下不会有两个样本的预测值相同。因此,ROC 曲线一般是交替由水平线和竖直线组成,呈阶梯状,仅当不少于两个且类别不同的样本被模型赋予了完全相同预测值,ROC 曲线才会出现斜线的部分。

auc
图 6-4 ROC 与 AUC 示意

对于模型来说,我们期望它能将尽可能多的样本分类正确、同时还要少出错,也即 TPR 尽可能高、FPR 尽可能低。最理想的情况下,当阈值合适的时候,所有的正样本都归为正类,所有的负样本都归为负类,这时,在图中是左上角的顶点。由于 ROC 曲线的形状是固定的,该曲线必然只有的两段折线。而一般来说,由于数据集中噪声和模型自身能力的限制,虽然不一定存在完美分割的阈值,但我们仍然希望 ROC 曲线能尽可能偏向左上方。图 6-4 中还有一条从左下到右上的黑色虚线,它表示模型完全随机预测时的 ROC。平均意义上,由于每个样本在任何阈值下被归为正类和负类的概率都是 0.5,将同步变化,形成了这条直线。

“曲线偏向左上方”是一个定性的描述,为了定量衡量 ROC 曲线表示的模型好坏,我们通常计算 ROC 曲线与轴和直线围成的面积,称为曲线下面积(area under the curve,AUC)。最好情况下,ROC 经过左上角,AUC 为 1;随机情况下 AUC 是黑色虚线下的三角形面积,为 0.5;如果模型比随机预测还要差,AUC 会小于 0.5,但这种情况事实上不会发生,对于其原因的思考留作习题。

AUC 除了 ROC 下的面积,还有另一层含义。我们以一个包含 5 个样本的数据集来说明,这些样本依次记为,其中是负类,是正类。我们将样本按模型预测的正类概率从小到大排序。理想情况下,存在将正负类完美分隔的阈值,即所有正类的概率大于所有负类的概率,如表 6-1 所示。

表 6-1 可以完美分隔的情况

显然,如果我们任取一个负类、任取一个正类,负类排在正类前面的概率是,此时 AUC 也是。假如模型并不理想,给出的概率如表 6-2 所示。

表 6-2 无法完美分隔的情况

同样任取一个负类、任取一个正类,这时负类排在正类前面的概率为

再来计算此时的 AUC。绘制出形如图 6-5 的 ROC 曲线,容易算出 AUC 为

auc_eg
图 6-5 示例中的ROC曲线

与上面得到的负类排在正类前面的概率相等!事实上,这一结论对于更多样本的场合也是成立的。我们将样本离散且没有相同概率的情况留作习题由读者证明。从这里可以看出,模型将真实的正类和负类分得越清楚,就有越多的负类排在正类前面,AUC 就越大。如果模型分类完全随机,那么任取一个负类和一个正类,它们的位置也是随机的,负类排在正类前面的概率为 0.5,所以得到的 AUC 也是 0.5,同样与上面的结论相符。这一结论告诉我们,虽然我们是从阈值的变化中推导出 ROC 与 AUC,但是 AUC 的值事实上与阈值是的选取无关的,只与模型本身对正负类的预测结果有关。在下一节的动手应用中,我们就用本节讲述的各种标准来作为模型的评价指标。

此外需要提醒的是,本节介绍的分类指标大都是针对二分类问题的。对于多分类问题,准确率()仍然是一个最直接的分类指标,也即是样本预测类别和真实类别相同的概率。而召回率、精确率和 F1 分数皆为特定分类而计算的,例如在二分类中它们是针对正例而计算的。因此,在多分类任务中,我们可以针对关注的类别来计算召回率、精确率和 F1 分数。更进一步,我们可以对所有类别的 F1 分数做平均,从而得到该多分类任务的一个总体的 F1 分数。具体来说,如果是对所有类别的 F1 分数做直接平均,那么得到的最终指标称为 macro-F1(微观 F1)分数;如果是根据每个类别的样本数量对 F1 分数做加权平均,那么得到的最终指标称为 micro-F1(宏观 F1)分数。有兴趣的读者可以查阅多分类的相关文献进一步了解这些指标。

6.4 动手实现逻辑斯谛回归

下面,我们动手实现梯度下降算法求解逻辑斯谛回归。本章所用的数据集 lr_dataset.csv 包含了二维平面上的一些点,这些点按位置的不同分为两类。表 6-3 展示了数据集中的几条样本,每条样本依次包含横坐标、纵坐标和类别标签。我们的任务是训练逻辑斯谛回归模型,使模型对点的类别预测尽可能准确。

表 6-3 逻辑斯谛回归数据集中的样本示例

横坐标纵坐标标签
0.43040.20551
0.0898-0.15271
-0.8257-0.95960

首先,我们读入并处理数据集,并将其在平面上的分布展示出来。图中的每个红色圆圈代表一个正类,蓝色叉号代表一个负类。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator
# 从源文件中读入数据并处理
lines = np.loadtxt('lr_dataset.csv', delimiter=',', dtype=float)
x_total = lines[:, 0:2]
y_total = lines[:, 2]
print('数据集大小:', len(x_total))
# 将得到的数据在二维平面上制图,不同的类别染上不同的颜色以便于观察样本点的分布
pos_index = np.where(y_total == 1)
neg_index = np.where(y_total == 0)
plt.scatter(x_total[pos_index, 0], x_total[pos_index, 1],
marker='o', color='coral', s=10)
plt.scatter(x_total[neg_index, 0], x_total[neg_index, 1],
marker='x', color='blue', s=10)
plt.xlabel('X1 axis')
plt.ylabel('X2 axis')
plt.show()
# 划分训练集与测试集
np.random.seed(0)
ratio = 0.7
split = int(len(x_total) * ratio)
idx = np.random.permutation(len(x_total))
x_total = x_total[idx]
y_total = y_total[idx]
x_train, y_train = x_total[:split], y_total[:split]
x_test, y_test = x_total[split:], y_total[split:]
数据集大小: 1000

png

接下来,我们实现上节中所描述的几个评价指标。这里选用准确率和 AUC 两种,其他评价指标留给读者自行实践。对于 AUC 以外的指标,我们需要样本的真实标签以及某个阈值下模型的预测标签。而 AUC 中已经包含了阈值变化,所以需要的是模型原始的预测概率。

def acc(y_true, y_pred):
return np.mean(y_true == y_pred)
def auc(y_true, y_pred):
# 按预测值从大到小排序,越靠前的样本预测正类概率越大
idx = np.argsort(y_pred)[::-1]
y_true = y_true[idx]
y_pred = y_pred[idx]
# 把y_pred中不重复的值当作阈值,依次计算FP样本和TP样本数量
# 由于两个数组已经排序且位置对应,直接从前向后累加即可
tp = np.cumsum(y_true)
fp = np.cumsum(1 - y_true)
tpr = tp / tp[-1]
fpr = fp / fp[-1]
# 依次枚举FPR,计算曲线下的面积
# 方便起见,给FPR和TPR最开始添加(0,0)
s = 0.0
tpr = np.concatenate([[0], tpr])
fpr = np.concatenate([[0], fpr])
for i in range(1, len(fpr)):
s += (fpr[i] - fpr[i - 1]) * tpr[i]
return s

由于本章所用的数据集大小较小,我们不再每次取小批量进行迭代,而是直接用完整的训练集计算梯度。接下来,我们按照之前推导的梯度下降公式定义训练函数,并设置学习率和迭代次数,查看训练结果。除了训练曲线之外,我们还将模型参数表示的直线在平面上展示出来。在直线同一侧的点被模型判断成同一类。

# 逻辑斯谛函数
def logistic(z):
return 1 / (1 + np.exp(-z))
def GD(num_steps, learning_rate, l2_coef):
# 初始化模型参数
theta = np.random.normal(size=(X.shape[1],))
train_losses = []
test_losses = []
train_acc = []
test_acc = []
train_auc = []
test_auc = []
for i in range(num_steps):
pred = logistic(X @ theta)
grad = -X.T @ (y_train - pred) + l2_coef * theta
theta -= learning_rate * grad
# 记录损失函数
train_loss = - y_train.T @ np.log(pred) \
- (1 - y_train).T @ np.log(1 - pred) \
+ l2_coef * np.linalg.norm(theta) ** 2 / 2
train_losses.append(train_loss / len(X))
test_pred = logistic(X_test @ theta)
test_loss = - y_test.T @ np.log(test_pred) \
- (1 - y_test).T @ np.log(1 - test_pred)
test_losses.append(test_loss / len(X_test))
# 记录各个评价指标,阈值采用0.5
train_acc.append(acc(y_train, pred >= 0.5))
test_acc.append(acc(y_test, test_pred >= 0.5))
train_auc.append(auc(y_train, pred))
test_auc.append(auc(y_test, test_pred))
return theta, train_losses, test_losses, \
train_acc, test_acc, train_auc, test_auc

最后,我们定义各个超参数,进行训练并绘制出训练损失、准确率和 AUC 随训练轮数的变化。

# 定义梯度下降迭代的次数,学习率,以及L2正则系数
num_steps = 250
learning_rate = 0.002
l2_coef = 1.0
np.random.seed(0)
# 在x矩阵上拼接1
X = np.concatenate([x_train, np.ones((x_train.shape[0], 1))], axis=1)
X_test = np.concatenate([x_test, np.ones((x_test.shape[0], 1))], axis=1)
theta, train_losses, test_losses, train_acc, test_acc, \
train_auc, test_auc = GD(num_steps, learning_rate, l2_coef)
# 计算测试集上的预测准确率
y_pred = np.where(logistic(X_test @ theta) >= 0.5, 1, 0)
final_acc = acc(y_test, y_pred)
print('预测准确率:', final_acc)
print('回归系数:', theta)
plt.figure(figsize=(13, 9))
xticks = np.arange(num_steps) + 1
# 绘制训练曲线
plt.subplot(221)
plt.plot(xticks, train_losses, color='blue', label='train loss')
plt.plot(xticks, test_losses, color='red', ls='--', label='test loss')
plt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
# 绘制准确率
plt.subplot(222)
plt.plot(xticks, train_acc, color='blue', label='train accuracy')
plt.plot(xticks, test_acc, color='red', ls='--', label='test accuracy')
plt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
# 绘制AUC
plt.subplot(223)
plt.plot(xticks, train_auc, color='blue', label='train AUC')
plt.plot(xticks, test_auc, color='red', ls='--', label='test AUC')
plt.gca().xaxis.set_major_locator(MaxNLocator(integer=True))
plt.xlabel('Epochs')
plt.ylabel('AUC')
plt.legend()
# 绘制模型学到的分隔直线
plt.subplot(224)
plot_x = np.linspace(-1.1, 1.1, 100)
# 直线方程:theta_0 * x_1 + theta_1 * x_2 + theta_2 = 0
plot_y = -(theta[0] * plot_x + theta[2]) / theta[1]
pos_index = np.where(y_total == 1)
neg_index = np.where(y_total == 0)
plt.scatter(x_total[pos_index, 0], x_total[pos_index, 1],
marker='o', color='coral', s=10)
plt.scatter(x_total[neg_index, 0], x_total[neg_index, 1],
marker='x', color='blue', s=10)
plt.plot(plot_x, plot_y, ls='-.', color='green')
plt.xlim(-1.1, 1.1)
plt.ylim(-1.1, 1.1)
plt.xlabel('X1 axis')
plt.ylabel('X2 axis')
plt.show()
预测准确率: 0.8766666666666667
回归系数: [3.13501827 2.90998799 0.55095127]

png

为了进一步展示模型学到的概率信息,我们把模型预测的每个数据点的概率展示在图 6-6 的左半部分中,其中的轴和轴是数据点的坐标,轴是经过逻辑斯谛函数后模型预测的概率,绿色的平面是把分隔直线从二维伸展到三维的结果。样本点越靠上,说明模型预测该样本点属于正类的概率越大。可以看出,这些点分布出的曲面很像空间中逻辑斯谛函数的图像。事实上,如果沿着分隔平面的视角看过去,这些点恰好会组成逻辑斯谛函数的曲线,如图 6-6(b)所示。这是因为逻辑斯谛回归模型给出的分类结果本质上还是概率结果,在分隔平面附近的点模型很难判断它们是正类还是负类,给出的概率接近 0.5;远离分隔平面的点模型比较有信心,给出的概率接近 0 或者 1。由于逻辑斯谛回归中线性预测部分是由逻辑斯谛函数映射为概率的,这些点的概率在空间中自然也就符合逻辑斯谛函数的曲线。

lr_3d
图 6-6 逻辑斯谛回归预测值的三维示意

6.4 使用 sklearn 中的逻辑斯谛回归模型

与线性回归相似,sklearn 同样提供了封装好的逻辑斯谛回归模型LogisticRegression。我们直接使用该工具求解逻辑斯谛回归问题,并与自己实现的梯度下降方法进行比较。sklearn 中的方法默认添加了正则系数约束,与我们的参数设置相同。最终得到的回归系数与我们动手实现得到的结果基本是一致的。

from sklearn.linear_model import LogisticRegression
# 使用线性模型中的逻辑回归模型在数据集上训练
# 其提供的liblinear优化算法适合在较小数据集上使用
# 默认使用系数为1.0的L2正则化约束
# 其他可选参数请参考官方文档
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(x_train, y_train)
print('回归系数:', lr_clf.coef_[0], lr_clf.intercept_)
# 在数据集上用计算得到的逻辑回归模型进行预测并计算准确度
y_pred = lr_clf.predict(x_test)
print('准确率为:',np.mean(y_pred == y_test))
回归系数: [3.14129907 2.91620111] [0.5518978]
准确率为: 0.8766666666666667

6.5 交叉熵与最大似然估计

在训练逻辑斯谛回归模型时,我们从概率分布出发,采用最大似然估计的思想得到了损失函数。而从信息论的角度,我们也能得到相同的结果。在信息论中,当一个随机事件发生时,它就会提供一定的信息。而事件发生概率越小,其发生时所提供的信息量也就越大。例如,连续抛一枚硬币次,如果出现正面和反面的次数接近,那么这很正常;如果连续出现次正面,那么我们就不免怀疑硬币上是否有什么机关。用数学语言描述,设事件发生的概率为,那么发生所能提供的信息是:

从上式中可以看出,确定事件发生不会提供任何信息,这也符合我们的直观感受。而当许多事件互相影响时,我们还需要对这些事件整体的性质进行研究。

下面,我们只考虑事件离散且有限的情况。设有个事件,其发生的概率分别为,满足,且任意两个事件都互斥,即。我们可以用一个随机变量来表示这些事件,表示事件发生,并用来表示这些事件的概率分布。在每一时刻,这些事件中有且仅有一个会发生。可以发现,预测每一时刻发生事件的难度取决于分布的整体性质。如果该分布中有某个事件发生的概率很大,预测的难度就较低;反过来,如果各个事件发生的概率都很接近,那么就很难预测到底哪一个事件会发生,也就是分布的不确定性更大。例如,我们可以几乎确定太阳明天会从东边升起,却无法预测抛一枚均质硬币会得到正面还是反面,因为两者出现的概率几乎都是。模仿物理学中衡量系统无序程度的熵(entropy)的概念,在信息论中,我们也用熵来衡量分布的不确定程度。上述分布的熵定义为

经过一些数学推导可以得到,当某个事件发生的概率为、其他事件发生概率为时,分布的熵最小,为;当所有事件发生的概率都相等,即时,分布的熵最大,为

更进一步,如果关于随机变量存在两个概率分布,我们可以用相对熵(relative entropy)来衡量这两个分布的距离。相对熵又称为库尔贝克-莱布勒散度(Kullback-Leibler divergence),简称 KL 散度,其定义为:

KL 散度是一种比较特殊的距离度量。我们知道,判断两个数的关系,既可以计算它们的差,也可以计算它们的比值说明较大,反之同理,而比值越接近 1,则说明越接近。我们观察它的定义式,其期望的内部是,是在衡量处两个分布之间的距离。同时,期望是以分布为基准计算的。因此,KL 散度可以理解为以为权重的、分布的加权平均距离。从定义就可以看出,它不满足我们一般需要距离满足的对称性,即。但是,对任意两个分布,KL 散度始终是非负的。

我们以图 6-7 为例定性地说明这一点,图的左半部分展示了两个连续变量的概率密度函数,右半部分的绿色曲线是,其下方的面积就是 KL 散度的值。观察右边绿色曲线大于 0 和小于 0 的部分与左边之间大小的对应关系可以发现,在的地方,总是正数,其曲线下的面积也是正数;在的地方,虽然是负数,但是其权重较小,加权后的总面积总是小于的部分。由于概率密度需要满足归一化性质,即其曲线下方的面积必须为 1,不可能出现始终在上方的情况。因此,KL 散度计算时,曲线下的正负面积抵消后总是非负,并且在时取到最小值 0。关于这一性质的严格证明我们留作习题,供有一定数学基础的读者练习。

KL
图 6-7 KL散度示意图

对于离散随机变量,KL 散度可以进一步拆分为:

其中,就称为分布的交叉熵(cross entropy)。在二分类问题中,随机变量对应样本的类别,只有 0 和 1 两种取值。令等于样本的类别是的概率;等于模型预测的样本类别为的概率,即。我们期望模型预测的概率尽可能接近真实类别,因此要最小化之间的距离。而在离散化 KL 散度的定义中,只与样本真实类别有关,无法通过模型优化。因此,我们只需要最小化交叉熵。将用上述定义代入,可得:

如果再对所有样本的交叉熵求和,就得到总的交叉熵为:

可以发现,总交叉熵恰好等于负的对数似然函数。因此,逻辑回归问题中,最大化对数似然函数与最小化交叉熵是等价的。事实上,无论是离散情况还是连续情况这一结论都成立,所以我们也经常称交叉熵为逻辑回归的损失函数。交叉熵在涉及到概率分布的模型中十分重要,我们在后面的树模型中还会再次用到这一概念。

6.6 本章小结

本章讲解了机器学习的其中一大类任务:分类任务。任何有限分类任务均可以转化为多次二分类任务,因此二分类是分类问题的基础。我们重点讲解了二分类问题的最简单的线性模型:逻辑斯谛回归。和回归问题中的连续值标签不同,分类问题的标签是离散不可导的类别标签。由于线性模型的取值范围与分类任务不相符,我们需要将线性模型的结果映射到类别上,同时保证该映射可导。因此,我们从直接建模分类转为建模不同类别的概率,再利用最大似然估计的学习目标,解决了模型优化与训练的问题,这其实是分类问题求解的基本思想。最后,我们在简单的数据集上分别用梯度下降法和 sklearn 中的工具进行了实践。此外,我们还从信息论的角度出发,推导了交叉熵的公式,得到了分类问题中交叉熵与最大似然估计的等价性。

逻辑斯谛回归,虽然其名字包含“回归”二字,但它是最具有代表性的机器学习分类模型,至今还在学术研究和工业落地场景中被广泛使用。逻辑斯谛回归具有较好的可解释性,其参数的绝对值大小和正负代表了对应的特征对于预测数据类别的重要性,在医学、营销学、金融学等领域广泛被用于目标归因。逻辑斯谛回归具有极好的可并行性,其优化目标相对参数是凸函数,具有全局唯一最优解,因此在工业实践中也时常使用分布式并行训练的逻辑斯谛回归方法。可以说,掌握了线性回归和逻辑斯谛回归方法,日常大部分机器学习预测任务都可以有一个基本解决方案了。

习题

  1. 以下关于最大似然估计的表述中正确的是: A. 以概率为输出的模型常用最大似然估计得到损失函数。 B. 由于最大似然估计优化的是对数似然而非似然,得到的结果只是最优解的近似。 C. 最大似然估计与交叉熵的训练目标不等价。 D. 最大似然估计中引入了概率分布,所以不能采用梯度下降法来优化最大似然估计导出的损失函数。

  2. 以下关于分类问题的说法中不正确的是: A. 分类问题中,最后往往需要通过阈值来决定样本最后的标签。对于标签为 0 或 1 的二分类问题,当的数值大于 0.5 时即可认为标签为 1,反之亦然。 B. 如果使用确定性模型,将会导致模型对于参数无法微分,因此我们需要使用概率模型来建模问题。 C. 对于多分类问题,在设计损失函数时仍然可以采用交叉熵损失。如果有个类别,损失函数就是每一类的损失相乘。 D. softmax 函数可以看作是逻辑斯谛函数在多分类情况下的延伸,因此也可以用 softmax 函数作为二分类问题的损失函数。

  3. 以下关于分类问题评价指标的说法,不正确的是: A. 精确率是指分类正确的样本占全体样本的比例。 B. 准确率是指分类为正例的样本中标签为正例的比例。 C. 召回率是指标签为正例的样本中分类为正例的比例。 D. AUC 是由阈值从小到大增加过程中,模型分类的假阳性率以及真阳性率变化趋势进行绘制的。

  4. 逻辑斯谛回归虽然引入了非线性的逻辑斯谛函数,但通常仍然被视为线性模型,试从模型参数化假设的角度解释原因。

  5. 如果某模型的 AUC 低于 0.5,是否有办法立即得到一个 AUC 高于 0.5 的模型?

  6. 对于一个二分类任务,数据的标签和对于预测正例的概率如下表所示,试画出 ROC 曲线并计算模型的 AUC 值。

表 6-4 习题 6 概率表

  1. 设数据集中包含样本,其中有个正样本,个负样本。模型预测任意两个不同样本属于正类的概率不同。证明从中均匀随机选取一个正样本和一个负样本,有

    (提示:考虑 ROC 曲线上每一段横线和竖线的意义。对于选出的负样本,预测值更大的正样本数量和有什么关系?呢?)

  2. 对于分类 softmax 函数,试推导其中一个分类的逻辑斯谛值总可以设为 0,进而分类逻辑斯谛回归模型其实只需要使用个参数向量即可完成等价建模,而具体的二分类逻辑斯谛回归的形式则正是 softmax 函数在时由这样化简得到的。

  3. 从二分类到多分类。

    (1)推导 softmax 函数的梯度。

    (2)将作为模型预测的概率分布,分别用 MLE 和交叉熵计算分类问题的损失函数。两者的结果是否相同?

    (3)利用(1)和(2)的结果实现多分类的梯度下降算法,并在multiclass.csv数据集上测试。该数据集每一行包含 3 个数字,依次为样本的坐标、坐标和标签。数据分布如图 6-8 所示。

multiclass
图 6-8 多分类数据集的数据分布
  1. 关于随机变量存在两个概率分布,证明其 KL 散度总为非负,即,并且当且仅当时,

拓展阅读:广义线性模型

本章介绍的逻辑斯谛回归模型的参数化假设为,并不是传统意义上的线性模型,而是对线性映射又做了某种变换。该变换的目的是使线性的符合问题要求的性质。事实上,除了分类问题之外,还有许多线性模型无法直接应用的场景。例如在研究某一货物的销量和价格的关系时,如果用线性模型去拟合,可能得到“货物价格每上升 1 元,销量减少 10 个”这样的结论。然而,如果该货物原本的售价是 10 元、销量是 100 个,我们用该模型就会得到“货物售价 30 元时,销量是-100 个”这样不现实的结论。这是由于货物销量关于售价并不完全是线性关系。于是,为了拓展线性模型的适用范围,我们在其基础上引入了广义线性模型(generalized linear model,GLM)的概念。前面已经讲过的线性回归模型和逻辑斯谛回归模型,都属于 GLM 的范畴。

在逻辑斯谛回归中,我们假设服从伯努利分布,且关于线性。事实上,普通的线性回归中也隐含了服从正态分布的假设,并且其均值关于线性,从而我们可以用最小二乘法去拟合回归直线。如果我们把服从的分布拓展到指数分布族,就得到了 GLM。指数分布族的定义如下:

其中,称为线性预测子,标量函数、向量函数和标量函数都是已知的。简单起见,下面我们采用其标量形式,并记

通过选取不同的函数就可以得到不同的概率分布。例如,令

就得到高斯分布。令

就得到伯努利分布。读者可以自行代入验证。

在假设了服从的分布后,我们还希望计算的期望和方差,从而可以建立预测模型。指数分布族有非常好的性质,我们省略推导,直接给出其期望和方差分别为:

接下来,我们建立参数化模型预测的期望,即,再通过真实的训练数据来优化参数。这里,我们将高斯分布和伯努利分布代入,来还原线性回归和逻辑斯谛回归的模型。对于高斯分布,有:

得到的结果与线性回归模型相同。对于伯努利分布,有:

得到的结果与逻辑斯谛回归模型相同。可以发现,这里逻辑斯谛函数是由伯努利分布和 GLM 假设自然导出的。

由线性模型到广义线性模型是一个由具象到抽象的过程。普通的线性模型的应用场景非常有限,但将其中的内核抽象出来,得到广义线性模型后,就可以解决所有适用于指数分布族的问题。除了我们详细介绍的线性回归模型和逻辑斯谛回归模型之外,泊松分布、多项分布、分类分布等都可以导出不同的模型,可以在不同的任务中发挥作用。