三、逻辑回归模型输出结果与模型可解释性
从整体情况来看,逻辑回归在经过 Sigmoid 函数处理之后,是将线性方程输出结果压缩在了 0-1 之间,用该结果再来进行回归类的连续数值预测肯定是不合适的了。在实际模型应用过程中,逻辑回归主要应用于二分类问题的预测。
一般来说,我们会将二分类的类别用一个两个分类水平取值的离散变量来代表,两个分类水平分别为 0 和 1。该离散变量也被称为 0-1 离散变量。
连续型输出结果转化为分类预测结果
对于逻辑回归输出的 (0,1) 之间的连续型数值,我们只需要确定一个阈值,就可以将其转化为二分类的类别判别结果。通常来说,这个阈值是 0.5,即以 0.5 为界,调整模型输出结果:
- 带入数据可进一步计算模型输出结果:
x = np.array([2, 0.5]).reshape(-1, 1) sigmoid(1-x) #array([[0.26894142], # [0.62245933]])
- 据此,在阈值为 0.5 的情况下,模型会将第一条判别为 0,第二条结果判别为 1,上述过程代码实现如下:
yhat = sigmoid(1-x) # 模型预测结果 yhat #array([[0.26894142], # [0.62245933]]) ycla = np.zeros_like(yhat) ycla # 类别判别结果 #array([[0.], # [0.]]) thr = 0.5 # 设置阈值 yhat >= thr # 数组判别 #array([[False], # [ True]]) ycla[yhat >= thr] = 1 # 布尔索引 ycla #array([[0.], # [1.]])
即:
sepal_width | species | 方程输出结果 | 类别判别结果 |
2 | 0 | 0.27 | 0 |
0.5 | 1 | 0.62 | 1 |
- 当然,我们也可以将该过程封装为一个函数:
def logit_cla(yhat, thr=0.5): """ 逻辑回归类别输出函数: :param yhat: 模型输出结果 :param thr:阈值 :return ycla:类别判别结果 """ ycla = np.zeros_like(yhat) ycla[yhat >= thr] = 1 return ycla
- 测试函数性能:
logit_cla(yhat) #array([[0.], # [1.]])
关于阈值的选取与 0\1 分类的类别标记:阈值为人工设置的参数,在没有特殊其他要求下,一般取值为 0.5。
关于类别的数值转化,即将哪一类设置为 0 哪一类设置为 1,也完全可以由人工确定,一般来说,我们会将希望被判别或被识别的类设置为 1,例如违约客户、确诊病例等。
逻辑回归输出结果 (y) 是否是概率
决定 y 是否是概率的核心因素,不是模型本身,而是建模流程。
逻辑斯蒂本身也有对应的概率分布,因此输入的自变量其实是可以视作随机变量的,但前提是需要满足一定的分布要求。
如果逻辑回归的建模流程遵照数理统计方法的一般建模流程,即自变量的分布(或者转化之后的分布)满足一定要求(通过检验),则最终模型输出结果就是严格意义上的概率取值。
如果是遵照机器学习建模流程进行建模,在为对自变量进行假设检验下进行模型构建,则由于自变量分布不一定满足条件,因此输出结果不一定为严格意义上的概率。
例如在上例中:
sepal_width | species | 方程输出结果 | 类别判别结果 |
0.5 | 0 | 0.27 | 0 |
2 | 1 | 0.62 | 1 |
我们可以说,第一条样本预测为 1 的概率为 0.38,相比属于类别 1,第一条样本更大概率属于类别 0;而第二条样本属于类别 1 的概率高达 73%,因此第二条样本我们判别其属于类别 1。
并且,根据逻辑回归方程:
- 逻辑回归的概率表示形式
- 如果我们将逻辑回归模型输出结果视作样本属于1类的概率,则可将逻辑回归模型改写成如下形式:
四、多分类学习与多分类逻辑回归
此前的讨论都是基于二分类问题(0-1分类问题)展开的讨论,而如果要使用逻辑回归解决多分类,则需要额外掌握一些技术手段。
总的来说,如果要使用逻辑回归解决多分类问题,一般来说有两种方法,其一是将逻辑回归模型改为多分类模型形式,其二则是采用通用的多分类学习方法对建模流程进行改造。
其中将逻辑回归模型改写成多分类模型形式并不常用并且求解过程非常复杂,包括 Scikit-Learn 在内,主流的实现多分类逻辑回归的方法都是采用多分类学习方法。
多分类学习方法,则指的是将一些二分类学习器(binary classifier)推广到多分类的场景中,该方法属于包括逻辑回归在内所有二分类器都能使用的通用方法。
多分类问题描述
当离散型标签拥有两个以上分类水平时,即对多个(两个以上)分类进行类别预测的问题,被称为多分类问题。例如有如下四分类问题简单数据集:
其中 index 是每条数据编号,labels 是每条数据的标签。
多分类问题解决思路
一般来说,用二分类学习器解决多分类问题,基本思想是先拆分后集成,也就是先将数据集进行拆分,然后多个数据集可训练多个模型,然后再对多个模型进行集成。这里所谓集成,指的是使用这多个模型对后续新进来数据的预测方法。
具体来看,依据该思路一般有三种实现策略,分别是一对一(One vs Ons,简称 OvO)、一对剩余(One vs Rest,简称 OvR)和多对多(Many vs Many,加成 MvM)。
1. OvO 策略
- 拆分策略
OvO 的拆分策略比较简单,基本过程是将每个类别对应数据集单独拆分成一个子数据集,然后令其两两组合,再来进行模型训练。
例如,对于上述四分类数据集,根据标签类别可将其拆分成四个数据集,然后再进行两两组合,总共有 6 种组合,也就是 C 4 2 种组合。
拆分过程如下所示:
- 集成策略
- 当模型训练完成之后,接下来面对新数据集的预测,可以使用投票法从 6 个分类器的判别结果中挑选最终判别结果。
- 根据少数服从多数的投票法能够得出,某条新数据最终应该属于类别 1。
2. OvR 策略
- 拆分策略
- 和 OvO 的两两组合不同,OvR 策略则是每次将一类的样例作为正例、其他所有数据作为反例来进行数据集拆分。
- 对于上述四分类数据集,OvR 策略最终会将其拆分为 4 个数据集,基本拆分过程如下:
此 4 个数据集就将训练 4 个分类器。
注意,在 OvR 的划分策略中,是将 rest 无差别全都划分为负类。当然,如果数据集总共有 N 个类别,则在进行数据集划分时总共将拆分成 N 个数据集。
集成策略
当成,集成策略和划分策略息息相关,对于 OvR 方法来说,对于新数据的预测,如果仅有一个分类器将其预测为正例,则新数据集属于该类。
若有多个分类器将其预测为正例,则根据分类器本身准确率来进行判断,选取准确率更高的那个分类器的判别结果作为新数据的预测结果。
- OvO 和 OvR的比较
- 对于这两种策略来说,尽管 OvO 需要训练更多的基础分类器,但由于 OvO 中的每个切分出来的数据集都更小,因此基础分类器训练时间也将更短。
- 因此,综合来看在训练时间开销上,OvO 往往要小于 OvR。而在性能方面,大多数情况下二者性能类似。
3. MvM 策略
相比于 OvO 和 OvR,MvM 是一种更加复杂的策略。
MvM 要求同时将若干类化为正类、其他类化为负类,并且要求多次划分,再进行集成。一般来说,通常会采用一种名为纠错输出码(Error Correcting Output Codes,简称 ECOC)的技术来实现 MvM 过程。
拆分策略
此时对于上述 4 分类数据集,拆分过程就会变得更加复杂,我们可以任选其中一类作为正类、其余作为负类,也可以任选其中两类作为正类、其余作为负数,以此类推。由此则诞生出了非常多种子数据集,对应也将训练非常多个基础分类器。
当然,将某一类视作正类和将其余三类视作正类的预测结果相同,对调下预测结果即可,此处不用重复划分。
例如,对于上述 4 分类数据集,则可有如下划分方式:
- 同时,我们使用训练好的四个基础分类器对新数据进行预测,也将产生四个结果,而这四个结果也可构成一个四位的新数据的编码。
- 接下来,我们可以计算新数据的编码和上述不同类别编码之间的距离,从而判断新生成数据应该属于哪一类。
不拿发现,如果预测足够准确,编码其实是和类别一一对应的。但如果基础分类器预测类别不够准确,编码和类别并不一定会一一对应,有一种三元编码方式,会将这种情况的某个具体编码改为 0 (纠错输出码),意为停用类。
当然,距离计算有很多种方法,此处简单进行介绍,假设 x 和 y 是两组 n 维数据如下所示:
- 不难发现,其实街道距离和欧式距离都是闵可夫斯基距离的特例。
- 此处以欧式距离为例计算新数据编码和各类编码之间距离。为了方便运算,此处可定义闵可夫斯基距离计算函数如下:
def dist(x, y, cat = 2): """ 闵可夫斯基距离计算函数 """ d1 = np.abs(x - y) if x.ndim > 1 or y.ndim > 1: res1 = np.power(d1, cat).sum(1) # sum(1)将一行的数值累加求和,返回nx1数组 else: res1 = np.power(d1, cat).sum() res = np.power(res1, 1/cat) return res
- 验证函数性能:
x = np.array([1, 2]) y = np.array([2, 3]) dist(x, y) # x与y的欧氏距离 #1.4142135623730951 np.sqrt(2) #返回一个非负平方根 #1.4142135623730951
- 进行编码距离计算:
# 原类别编码矩阵 code_mat = np.array([[1, -1, 1, -1], [-1, -1, 1, -1], [-1, -1, -1, 1], [-1, 1, -1, 1]]) # 预测数据编码 data_code = np.array([1, -1, 1, 1]) dist(code_mat, data_code) #array([2. , 2.82842712, 2.82842712, 3.46410162])
- 也可通过以下方式验证:
np.abs(code_mat - data_code) #array([[0, 0, 0, 2], # [2, 0, 0, 2], # [2, 0, 2, 0], # [2, 2, 2, 0]]) np.sqrt(np.power(np.abs(code_mat - data_code), 2).)sum(1) #array([2. , 2.82842712, 2.82842712, 3.46410162])
不难发现,新样本应该属于第一类。至此,我们就完成了 MvM 的一次多分类预测的全流程。
ECOC 方法评估
对于 ECOC 方法来说,编码越长预测结果越准确,不过编码越长也代表着需要耗费更多的计算资源,并且由于模型本身类别有限。
因此数据集划分数量有限,编码长度也会有限。不过一般来说,相比 OvR,MvM 方法效果会更好。