首先我们来讨论关于逻辑回归的基本原理,当然,在此过程中,我们也将进一步补充机器学习数学理论基础。
逻辑回归的基本原理,从整体上来划分可以分为两个部分,其一是关于模型方程的构建,也就是方程的基本形态,当然也包括模型的基本性质及其结果解读;其二则是模型参数求解,即在构建完模型之后如何利用数学工具求解最佳参数。而这两部分其实都可以从多个角度出发进行理解,基本划分情况如下
本节我们将先讨论关于逻辑回归的模型构建部分,同时也将讨论模型结果解读和模型可解释性的相关内容,下一节我们将具体讨论关于模型参数求解的相关内容。
# 科学计算模块
import numpy as np
import pandas as pd
# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt
# 自定义模块
from ML_basic_function import *
在Lesson3中我们曾谈到关于线性回归的局限性,这种局限性的根本由模型本身的简单线性结构(自变量加权求和预测因变量)导致的。如果说线性回归是在一个相对严格的条件下建立的简单模型,那么在后续实践应用过程中,人们根据实际情况的不同,在线性回归的基础上又衍生出了种类繁多的线性类模型。其中,有一类线性模型,是在线性回归基础上,在等号的左边或右边加上了一个函数,从而能够让模型更好的捕捉一般规律,此时该模型就被称为广义线性模型,该函数就被称为联系函数。
广义线性模型的提出初衷上还是为了解决非线性相关的预测问题,例如,现在有数据分布如下:
# 数据集特征
np.random.seed(24)
x = np.linspace(0, 4, 20).reshape(-1, 1)
x = np.concatenate((x, np.ones_like(x)), axis=1)
x
#array([[0. , 1. ],
# [0.21052632, 1. ],
# [0.42105263, 1. ],
# [0.63157895, 1. ],
# [0.84210526, 1. ],
# [1.05263158, 1. ],
# [1.26315789, 1. ],
# [1.47368421, 1. ],
# [1.68421053, 1. ],
# [1.89473684, 1. ],
# [2.10526316, 1. ],
# [2.31578947, 1. ],
# [2.52631579, 1. ],
# [2.73684211, 1. ],
# [2.94736842, 1. ],
# [3.15789474, 1. ],
# [3.36842105, 1. ],
# [3.57894737, 1. ],
# [3.78947368, 1. ],
# [4. , 1. ]])
# 数据集标签
y = np.exp(x[:, 0] + 1).reshape(-1, 1)
y
#array([[ 2.71828183],
# [ 3.35525011],
# [ 4.1414776 ],
# [ 5.11193983],
# [ 6.30980809],
# [ 7.78836987],
# [ 9.61339939],
# [ 11.86608357],
# [ 14.64663368],
# [ 18.07874325],
# [ 22.31509059],
# [ 27.54413077],
# [ 33.99847904],
# [ 41.96525883],
# [ 51.79887449],
# [ 63.93677707],
# [ 78.91892444],
# [ 97.41180148],
# [120.23806881],
# [148.4131591 ]])
此时x和y的真实关系为 y = e ( x + 1 ) y=e^{(x+1)} y=e(x+1)但如果以线性方程来进行预测,即:
y = w T ⋅ x + b y= w^T \cdot x + b y=wT⋅x+b
当然,我们可以令 w ^ = [ w 1 , w 2 , . . . w d , b ] T \hat w = [w_1,w_2,...w_d, b]^T w^=[w1,w2,...wd,b]T, x ^ = [ x 1 , x 2 , . . . x d , 1 ] T \hat x = [x_1,x_2,...x_d, 1]^T x^=[x1,x2,...xd,1]T,从而将上述方程改写为:
y = w ^ T ⋅ x ^ y= \hat w^T \cdot \hat x y=w^T⋅x^
则模型输出结果为:
np.linalg.lstsq(x, y, rcond=-1)[0]
#array([[ 30.44214742],
# [-22.37576724]])
即 y = 30.44 x − 22.38 y=30.44x-22.38 y=30.44x−22.38
则模型预测结果为:
yhat = x[:, 0] * 30.44 - 22.38
yhat
#array([-22.38 , -15.97157895, -9.56315789, -3.15473684,
# 3.25368421, 9.66210526, 16.07052632, 22.47894737,
# 28.88736842, 35.29578947, 41.70421053, 48.11263158,
# 54.52105263, 60.92947368, 67.33789474, 73.74631579,
# 80.15473684, 86.56315789, 92.97157895, 99.38 ])
# 观察模型预测和真实结果
plt.plot(x[:, 0], y, 'o')
plt.plot(x[:, 0], yhat, 'r-')
能够发现,线性模型预测结果和真实结果差距较大。但此时如果我们在等号右边加上以 e e e为底的指数运算,也就是将线性方程输出结果进行以 e e e为底的指数运算转换之后去预测y,即将方程改写为 y = e ( w ^ T ⋅ x ^ ) y=e^{(\hat w^T \cdot \hat x)} y=e(w^T⋅x^)
等价于
l n y = w ^ T ⋅ x ^ lny = \hat w^T \cdot \hat x lny=w^T⋅x^
即相当于是线性方程输出结果去预测 y y y取以 e e e为底的对数运算之后的结果。
此时我们可以带入 l n y lny lny进行建模
np.linalg.lstsq(x, np.log(y), rcond=-1)[0]
#array([[1.],
# [1.]])
可得到方程 l n y = x + 1 lny=x+1 lny=x+1等价于 y = e ( x + 1 ) y=e^{(x+1)} y=e(x+1)即解出原方程。
通过上面的过程,我们不难发现,通过在模型左右两端加上某些函数,能够让线性模型也具备捕捉非线性规律的能力。而在上例中,这种捕捉非线性规律的本质,是在方程加入 l n ln ln对数函数之后,能够使得模型的输入空间(特征所在空间)到输出空间(标签所在空间)进行了非线性的函数映射。而这种连接线性方程左右两端、并且实际上能够拓展模型性能的函数,就被称为联系函数,而加入了联系函数的模型也被称为广义线性模型。广义线性模型的一般形式可表示如下:
g ( y ) = w ^ T ⋅ x ^ g(y)=\hat w^T \cdot \hat x g(y)=w^T⋅x^
等价于 y = g − 1 ( w ^ T ⋅ x ^ ) y = g^{-1}(\hat w^T \cdot \hat x) y=g−1(w^T⋅x^)
其中 g ( ⋅ ) g(·) g(⋅)为联系函数(link function), g − 1 ( ⋅ ) g^{-1}(·) g−1(⋅)为联系函数的反函数(如 y = e x 与 l n ( y ) = x y=e^x与ln(y)=x y=ex与ln(y)=x)。而如上例中的情况,也就是当联系函数为自然底数的对数函数时,该模型也被称为对数线性模型(logit linear model)。
注:一般来说广义线性模型要求联系函数必须是单调可微函数。
从广义线性模型的角度出发,当联系函数为 g ( x ) = x g(x)=x g(x)=x时, g ( y ) = y = w ^ T ⋅ x ^ g(y)=y=\hat w^T \cdot \hat x g(y)=y=w^T⋅x^,此时就退化成了线性模型。而能够通过联系函数拓展模型捕捉规律的范围,这也就是广义的由来。
逻辑回归也被称为对数几率回归。接下来,我们从广义线性模型角度理解逻辑回归。
注,logit的是log unit对数单元的简写,和中文中的“逻辑”一词并没有关系。对数几率模型也被称为对数单位模型(log unit model)。
如果我们将对数几率看成是一个函数,并将其作为联系函数,即 g ( y ) = l n y 1 − y g(y)=ln\frac{y}{1-y} g(y)=ln1−yy,则该广义线性模型为:
g ( y ) = l n y 1 − y = w ^ T ⋅ x ^ g(y)=ln\frac{y}{1-y}=\hat w^T \cdot \hat x g(y)=ln1−yy=w^T⋅x^
此时模型就被称为对数几率回归(logistic regression),也被称为逻辑回归。
国内很多学者认为,logistic regression一词和逻辑的含义相差甚远,将其译为逻辑回归并不妥当。课上不对逻辑回归和对数几率回归进行译名是否妥当的讨论。
数理统计分析方法构建逻辑回归时,基本假设要求变量y必须服从伯努利分布(Bernoulli)。
方程左右两端取自然底数:
y 1 − y = e w ^ T ⋅ x ^ \frac{y}{1-y}=e^{\hat w^T \cdot \hat x} 1−yy=ew^T⋅x^
方程左右两端+1可得:
y + ( 1 − y ) 1 − y = 1 1 − y = e w ^ T ⋅ x ^ + 1 \frac{y+(1-y)}{1-y}=\frac{1}{1-y}=e^{\hat w^T \cdot \hat x}+1 1−yy+(1−y)=1−y1=ew^T⋅x^+1
方程左右两端取倒数可得:
1 − y = 1 e w ^ T ⋅ x ^ + 1 1-y=\frac{1}{e^{\hat w^T \cdot \hat x}+1} 1−y=ew^T⋅x^+11
1-方程左右两端可得:
y = 1 − 1 e w ^ T ⋅ x ^ + 1 = e w ^ T ⋅ x ^ e w ^ T ⋅ x ^ + 1 = 1 1 + e − ( w ^ T ⋅ x ^ ) = g − 1 ( w ^ T ⋅ x ^ ) \begin{aligned} y &= 1-\frac{1}{e^{\hat w^T \cdot \hat x}+1}\\ &=\frac{e^{\hat w^T \cdot \hat x}}{e^{\hat w^T \cdot \hat x}+1} \\ &=\frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} = g^{-1}(\hat w^T \cdot \hat x) \end{aligned} y=1−ew^T⋅x^+11=ew^T⋅x^+1ew^T⋅x^=1+e−(w^T⋅x^)1=g−1(w^T⋅x^)
因此,逻辑回归基本模型方程为:
y = 1 1 + e − ( w ^ T ⋅ x ^ ) y = \frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} y=1+e−(w^T⋅x^)1
同时我们也能发现,对对数几率函数的反函数为:
f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1
我们可以简单观察该函数的函数图像:
np.random.seed(24)
x = np.linspace(-10, 10, 100)
y = 1 / (1 + np.exp(-x))
plt.plot(x, y)
能够看出该函数的图像近似S形,这种类似S形的函数,也被称为Sigmoid函数。
注:Sigmoid严格定义是指形如S型的函数,并不是特指某个函数,也就是说,从严格意义来讨论,函数 f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1只能被称为是Sigmoid函数的一种。但实际上,由于该函数是最著名且通用的Sigmoid函数,因此大多数时候,我们在说Sigmoid函数的时候,其实就是在指 f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1函数。后续课上对该概念不做区分,Sigmoid函数即指 f ( x ) = 1 1 + e − x f(x) = \frac{1}{1+e^{-x}} f(x)=1+e−x1函数。
def sigmoid(x):
return (1 / (1 + np.exp(-x)))
sigmoid(10)
#0.9999546021312976
据此可定义Sigmoid导函数的函数:
def sigmoid_deri(x):
return (sigmoid(x)*(1-sigmoid(x)))
sigmoid_deri(10)
#4.5395807735907655e-05
进一步,我们可以绘制Sigmoid导函数图像:
plt.plot(x, sigmoid_deri(x))
我们发现,Sigmoid导函数在实数域上取值大于0,并且函数图像先递增后递减,并在0点取得最大值。据此我们也可以进一步讨论Sigmoid函数性质:
plt.plot(x, sigmoid(x))
由于导函数始终大于0,因此Sigmoid函数始终递增,并且导函数在0点取得最大值,因此Sigmoid在0点变化率最快,而在远离零点的点,Sigmoid导函数取值较小,因此该区间Sigmoid函数变化缓慢。该区间也被称为Sigmoid的饱和区间。
当然,如果从简单探索Sigmoid函数的二阶导函数,其实能够发现,x<0时二阶导函数取值大于0(一阶导函数递增),而x>0时二阶导函数小于0(一阶导函数递减)。因此0点其实也是sigmoid函数的拐点。
函数拐点指的是二阶导函数左右异号的点,从凹凸性来看就是函数凹凸性发生变化的点。
从整体情况来看,逻辑回归在经过Sigmoid函数处理之后,是将线性方程输出结果压缩在了0-1之间,用该结果再来进行回归类的连续数值预测肯定是不合适的了。在实际模型应用过程中,逻辑回归主要应用于二分类问题的预测。
一般来说,我们会将二分类的类别用一个两个分类水平取值的离散变量来代表,两个分类水平分别为0和1。该离散变量也被称为0-1离散变量。
对于逻辑回归输出的(0,1)之间的连续型数值,我们只需要确定一个“阈值”,就可以将其转化为二分类的类别判别结果。通常来说,这个阈值是0.5,即以0.5为界,调整模型输出结果:
y c l a = { 0 , y < 0.5 1 , y ≥ 0.5 y_{c l a}=\left\{\begin{array}{l} 0, y<0.5 \\ 1, y \geq 0.5 \end{array}\right. ycla={0,y<0.51,y≥0.5
其中, y c l a y_{cla} ycla为类别判别结果,而 y y y为逻辑回归方程输出结果 s i g m o i d ( w ^ T ⋅ x ^ ) sigmoid(\hat w^T \cdot \hat x) sigmoid(w^T⋅x^)。例如,有简单数据集如下:
由于只有一个自变量,因此可假设逻辑回归模型如下:
y = 1 1 + e x − 1 y = \frac{1}{1+e^{x-1}} y=1+ex−11
带入数据可进一步计算模型输出结果:
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.]])
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,其实并不一定是严格意义上的概率。不过在目前大多数使用场景中,由于大家希望能够用到模型本身的可解释性,因此还是会将模型结果解读为1发生的概率。尽管这并不是一个严谨的做法,但在机器学习整体的“实证”倾向下,只要业务方接受这种做法、并且能够一定程度指导业务,我们就可以将其解读为概率。 课上我们将逻辑回归输出结果看成近似概率值。
例如在上例中:
我们可以说,第一条样本预测为1的概率为0.27,相比属于类别1,第一条样本更大概率属于类别0;而第二条样本属于类别1的概率高达62%,因此第二条样本我们判别其属于类别1。
并且,根据逻辑回归方程:
y = 1 1 + e − ( 1 − x ) y = \frac{1}{1+e^{-(1-x)}} y=1+e−(1−x)1
可以进一步推导出:
l n y 1 − y = 1 − x ln\frac{y}{1-y} = 1-x ln1−yy=1−x
可解读为x每增加1,样本属于1的概率的对数几率就减少1。(此处应是减少,请注意)
当然,类似的可解释性,也就是自变量变化对因变量变化的贡献程度的解读,对于线性回归同样适用。例如 =+1 ,我们可以解读为, 每增加1,就增加1。
而这种基于自变量系数的可解释性不仅可以用于自变量和因变量之间的解释,还可用于自变量重要性的判别当中,例如,假设逻辑回归方程如下:
l n y 1 − y = x 1 + 2 x 2 − 1 ln\frac{y}{1-y} = x_1+2x_2-1 ln1−yy=x1+2x2−1
则可解读为 x 2 x_2 x2的重要性是 x 1 x_1 x1的两倍, x 2 x_2 x2每增加1的效果(令样本为1的概率的增加)是 x 1 x_1 x1增加1效果的两倍。
本节结束后,我们需要将此前定义的两个函数,也就是Sigmoid函数、Sigmoid导函数即逻辑回归类别判别函数写入ML_basic_function.py中。
如果我们将逻辑回归模型输出结果视作样本属于1类的概率,则可将逻辑回归模型改写成如下形式:
p ( y = 1 ∣ x ^ ; w ^ ) = 1 1 + e − ( w ^ T ⋅ x ^ ) p(y=1|\hat x;\hat w) =\frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} p(y=1∣x^;w^)=1+e−(w^T⋅x^)1
p ( y = 0 ∣ x ^ ; w ^ ) = e − ( w ^ T ⋅ x ^ ) 1 + e − ( w ^ T ⋅ x ^ ) p(y=0|\hat x;\hat w) =\frac{e^{-(\hat w^T \cdot \hat x)}}{1+e^{-(\hat w^T \cdot \hat x)}} p(y=0∣x^;w^)=1+e−(w^T⋅x^)e−(w^T⋅x^)
值得一提的是,此前的讨论都是基于二分类问题(0-1分类问题)展开的讨论,而如果要使用逻辑回归解决多分类,则需要额外掌握一些技术手段。
总的来说,如果要使用逻辑回归解决多分类问题,一般来说有两种方法,其一是将逻辑回归模型改为多分类模型形式,其二则是采用通用的多分类学习方法对建模流程进行改造。其中将逻辑回归模型改写成多分类模型形式并不常用并且求解过程非常复杂,包括Scikit-Learn在内,主流的实现多分类逻辑回归的方法都是采用多分类学习方法。所谓多分类学习方法,则指的是将一些二分类学习器(binary classifier)推广到多分类的场景中,该方法属于包括逻辑回归在内所有二分类器都能使用的通用方法。
值得一提的是,对于多分类学习的讨论,也将为后续集成模型的学习做一定的理论铺垫。
当离散型标签拥有两个以上分类水平时,即对多个(两个以上)分类进行类别预测的问题,被称为多分类问题。例如有如下四分类问题简单数据集:
其中index是每条数据编号,labels是每条数据的标签。
一般来说,用二分类学习器解决多分类问题,基本思想是先拆分后集成,也就是先将数据集进行拆分,然后多个数据集可训练多个模型,然后再对多个模型进行集成。这里所谓集成,指的是使用这多个模型对后续新进来数据的预测方法。
具体来看,依据该思路一般有三种实现策略,分别是“一对一”(One vs Ons,简称OvO)、“一对剩余”(One vs Rest,简称OvR)和“多对多”(Many vs Many,加成MvM)。接下来我们逐个讨论。
OvO的拆分策略比较简单,基本过程是将每个类别对应数据集单独拆分成一个子数据集,然后令其两两组合,再来进行模型训练。例如,对于上述四分类数据集,根据标签类别可将其拆分成四个数据集,然后再进行两两组合,总共有6种组合,也就是 C 4 2 C^2_4 C42种组合。拆分过程如下所示:
然后在这6个新和成的数据集上,我们就能训练6个分类器。当然,如果是N分类问题,则需要训练 C N 2 = N ( N − 1 ) 2 C^2_N=\frac{N(N-1)}{2} CN2=2N(N−1)个模型。
当模型训练完成之后,接下来面对新数据集的预测,可以使用投票法从6个分类器的判别结果中挑选最终判别结果。
根据少数服从多数的投票法能够得出,某条新数据最终应该属于类别1。
和OvO的两两组合不同,OvR策略则是每次将一类的样例作为正例、其他所有数据作为反例来进行数据集拆分。对于上述四分类数据集,OvR策略最终会将其拆分为4个数据集,基本拆分过程如下:
此4个数据集就将训练4个分类器。注意,在OvR的划分策略种,是将rest无差别全都划分为负类。当然,如果数据集总共有N个类别,则在进行数据集划分时总共将拆分成N个数据集。
当成,集成策略和划分策略息息相关,对于OvR方法来说,对于新数据的预测,如果仅有一个分类器将其预测为正例,则新数据集属于该类。若有多个分类器将其预测为正例,则根据分类器本身准确率来进行判断,选取准确率更高的那个分类器的判别结果作为新数据的预测结果。
对于这两种策略来说,尽管OvO需要训练更多的基础分类器,但由于OvO中的每个切分出来的数据集都更小,因此基础分类器训练时间也将更短。因此,综合来看在训练时间开销上,OvO往往要小于OvR。而在性能方面,大多数情况下二者性能类似。
相比于OvO和OvR,MvM是一种更加复杂的策略。MvM要求同时将若干类化为正类、其他类化为负类,并且要求多次划分,再进行集成。一般来说,通常会采用一种名为“纠错输入码”(Error Correcting Output Codes,简称ECOC)的技术来实现MvM过程。
此时对于上述4分类数据集,拆分过程就会变得更加复杂,我们可以任选其中一类作为正类、其余作为负类,也可以任选其中两类作为正类、其余作为负数,以此类推。由此则诞生出了非常多种子数据集,对应也将训练非常多个基础分类器。
当然,将某一类视作正类和将其余三类视作正类的预测结果相同,对调下预测结果即可,此处不用重复划分。
例如,对于上述4分类数据集,则可有如下划分方式:
根据上述划分方式,总共将划分 C 4 1 + C 4 2 = 10 C_4^1+C_4^2=10 C41+C42=10个数据集,对应构建,对应的我们可以构建10个分类器。不过一般来说对于ECOC来说我们不会如此详尽的对数据集进行划分,而是再上述划分结果中挑选部分数据集进行建模,例如就挑选上面显式表示的4个数据集来进行建模,即可构建4个分类器。
由此我们也不难看出OvR实际上是MvM的一种特例。
接下来我们进行模型集成。值得注意的是,如果是以上述方式划分四个数据集,我们可以将每次划分过程中正例或负例的标签所组成的数组视为每一条数据自己的编码。如下所示:
同时,我们使用训练好的四个基础分类器对新数据进行预测,也将产生四个结果,而这四个结果也可构成一个四位的新数据的编码。接下来,我们可以计算新数据的编码和上述不同类别编码之间的距离,从而判断新生成数据应该属于哪一类。
不难发现,如果预测足够准确,编码其实是和类别一一对应的。但如果基础分类器预测类别不够准确,编码和类别并不一定会一一对应,有一种三元编码方式,会将这种情况的某个具体编码改为0(纠错输出码),意为停用类。
当然,距离计算有很多种方法,此处简单进行介绍,假设 x x x和 y y y是两组n维数据如下所示:
x = ( x 1 , x 2 , . . . , x n ) , y = ( y 1 , y 2 , . . . , y n ) x=(x_1, x_2, ..., x_n) ,y=(y_1,y_2,...,y_n) x=(x1,x2,...,xn),y=(y1,y2,...,yn)
则欧式距离计算公式如下:
d ( x , y ) = ∑ i = 1 n ( x i − y i ) 2 d(x, y) = \sqrt{\sum_{i = 1}^{n}(x_i-y_i)^2} d(x,y)=i=1∑n(xi−yi)2
即对应位置元素依次相减后取其平方和再开平方。
街道距离计算公式如下:
d ( x , y ) = ∑ i = 1 n ( ∣ x i − y i ∣ ) d(x, y) =\sum_{i = 1}^{n}(|x_i-y_i|) d(x,y)=i=1∑n(∣xi−yi∣)
即对应位置元素依次相减后取其绝对值的和。
闵可夫斯基距离计算公式如下:
d ( x , y ) = ∑ i = 1 n ( ∣ x i − y i ∣ ) n n d(x, y) = \sqrt[n]{\sum_{i = 1}^{n}(|x_i-y_i|)^n} d(x,y)=ni=1∑n(∣xi−yi∣)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) #按照行求和
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)
#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方法来说,编码越长预测结果越准确,不过编码越长也代表着需要耗费更多的计算资源,并且由于模型本身类别有限,因此数据集划分数量有限,编码长度也会有限。不过一般来说,相比OvR,MvM方法效果会更好。
在模型基本结构构建完成之后,接下来我们开始讨论如何进行逻辑回归的参数估计。所谓参数估计,其实就是模型参数求解的更加具有统计学风格的称呼。根据逻辑回归的基本公式:
y = 1 1 + e − ( w ^ T ⋅ x ^ ) y = \frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} y=1+e−(w^T⋅x^)1
不难看出,逻辑回归的参数其实就是线性方程中的自变量系数和截距。不过由于加入了联系函数,逻辑回归的参数并不能像线性回归一样利用最小二乘法进行快速求解。
当然,和所有的机器学习模型一样,要求解模型参数,就先必须构造损失函数,然后根据损失函数的基本情况寻找优化算法求解。对于逻辑回归来说,课上将介绍两种不同的方法来创建和求解损失函数,两种方法出发点各不相同但却殊途同归:分别是极大似然估计(Maximum Likelihood Estimate)和通过相对熵(relative entropy)构建交叉熵损失函数。
# 科学计算模块
import numpy as np
import pandas as pd
# 绘图模块
import matplotlib as mpl
import matplotlib.pyplot as plt
# 自定义模块
from ML_basic_function import *
比较有趣的一点是,尽管逻辑回归的损失函数构建过程比较复杂,但逻辑回归的损失函数的基本形式比较容易理解。因此首先我们先通过一个简单的例子来讨论关于逻辑回归的参数估计的基本思路,即损失函数构建和求解的一般思路。
现有简单数据集如下:
由于只有一个特征,因此可以构建逻辑回归模型为:
y = s i g m o i d ( w x + b ) = 1 1 + e − ( w x + b ) y=sigmoid(wx+b)=\frac{1}{1+e^{-(wx+b)}} y=sigmoid(wx+b)=1+e−(wx+b)1
我们将模型输出结果视作概率,则分别带入两条数据可得模型输出结果为:
p ( y = 1 ∣ x = 1 ) = 1 1 + e − ( w + b ) p(y=1|x=1)=\frac{1}{1+e^{-(w+b)}} p(y=1∣x=1)=1+e−(w+b)1 p ( y = 1 ∣ x = 3 ) = 1 1 + e − ( 3 w + b ) p(y=1|x=3)=\frac{1}{1+e^{-(3w+b)}} p(y=1∣x=3)=1+e−(3w+b)1
其中 p ( y = 1 ∣ x = 1 ) p(y=1|x=1) p(y=1∣x=1)表示 x x x取值为1时 y y y取值为1的条件概率。而我们知道,两条数据的真实情况为第一条数据 y y y取值为0,而第二条数据 y y y取值为1,因此我们可以计算 p ( y = 0 ∣ x = 1 ) p(y=0|x=1) p(y=0∣x=1)如下:
p ( y = 0 ∣ x = 1 ) = 1 − p ( y = 1 ∣ x = 1 ) = 1 − 1 1 + e − ( w + b ) = e − ( w + b ) 1 + e − ( w + b ) p(y=0|x=1) = 1-p(y=1|x=1)=1-\frac{1}{1+e^{-(w+b)}}=\frac{e^{-(w+b)}}{1+e^{-(w+b)}} p(y=0∣x=1)=1−p(y=1∣x=1)=1−1+e−(w+b)1=1+e−(w+b)e−(w+b)
即
sepal_length | species | 1-predict | 0-predict |
---|---|---|---|
1 | 0 | 1 1 + e − ( w + b ) \frac{1}{1+e^{-(w+b)}} 1+e−(w+b)1 | e − ( w + b ) 1 + e − ( w + b ) \frac{e^{-(w+b)}}{1+e^{-(w+b)}} 1+e−(w+b)e−(w+b) |
3 | 1 | 1 1 + e − ( 3 w + b ) \frac{1}{1+e^{-(3w+b)}} 1+e−(3w+b)1 | e − ( 3 w + b ) 1 + e − ( 3 w + b ) \frac{e^{-(3w+b)}}{1+e^{-(3w+b)}} 1+e−(3w+b)e−(3w+b) |
一般来说,损失函数的构建目标和模型评估指标保持一致(例如SSELoss和SSE),对于大多数分类模型来说,模型预测的准确率都是最基础的评估指标。此处如果我们希望模型预测结果尽可能准确,就等价于希望 p ( y = 0 ∣ x = 1 ) p(y=0|x=1) p(y=0∣x=1)和 p ( y = 1 ∣ x = 1 ) p(y=1|x=1) p(y=1∣x=1)两个概率结果越大越好。该目标可以统一在求下式最大值的过程中:
p ( y = 0 ∣ x = 1 ) ⋅ p ( y = 1 ∣ x = 3 ) p(y=0|x=1)\cdot p(y=1|x=3) p(y=0∣x=1)⋅p(y=1∣x=3)
即我们希望x取1时y取0和x取3时y取1的同时发生的概率越大越好。
此外,考虑到损失函数一般都是求最小值,因此可将上式求最大值转化为对应负数结果求最小值,同时累乘也可以转化为对数相加结果,因此上式求最大值可等价于下式求最小值:
L o g i t L o s s ( w , b ) = − l n ( p ( y = 1 ∣ x = 3 ) ) − l n ( p ( y = 0 ∣ x = 1 ) ) = − l n ( 1 1 + e − ( 3 w + b ) ) − l n ( e − ( w + b ) 1 + e − ( w + b ) ) = l n ( 1 + e − ( 3 w + b ) ) + l n ( 1 + 1 e − ( w + b ) ) = l n ( 1 + e − ( 3 w + b ) + e ( w + b ) + e − 2 w ) \begin{aligned} LogitLoss(w, b)&=-ln(p(y=1|x=3))-ln(p(y=0|x=1)) \\ &=-ln(\frac{1}{1+e^{-(3w+b)}})- ln(\frac{e^{-(w+b)}}{1+e^{-(w+b)}}) \\ &=ln(1+e^{-(3w+b)})+ln(1+\frac{1}{e^{-(w+b)}}) \\ &=ln(1+e^{-(3w+b)}+e^{(w+b)}+e^{-2w}) \end{aligned} LogitLoss(w,b)=−ln(p(y=1∣x=3))−ln(p(y=0∣x=1))=−ln(1+e−(3w+b)1)−ln(1+e−(w+b)e−(w+b))=ln(1+e−(3w+b))+ln(1+e−(w+b)1)=ln(1+e−(3w+b)+e(w+b)+e−2w)
至此我们即构建了一个由两条数据所构成的逻辑回归损失函数。
回顾此前课程内容:损失函数和带入数据量息息相关。
注意,在上述损失函数的构建过程中有两个关键步骤,需要再次提醒。
其一是在将模型高准确率的诉求具象化为 p ( y = 0 ∣ x = 1 ) ⋅ p ( y = 1 ∣ x = 3 ) p(y=0|x=1)\cdot p(y=1|x=3) p(y=0∣x=1)⋅p(y=1∣x=3)参数的过程,此处我们为何不能采用类似SSE的计算思路取构建损失函数,即进行如下运算: ∣ ∣ y − y h a t ∣ ∣ 2 2 = ∣ ∣ y − 1 1 + e − ( w ^ T ⋅ x ^ ) ∣ ∣ 2 2 ||y-yhat||_2^2=||y-\frac{1}{1+e^{-(\hat w^T \cdot \hat x)}}||_2^2 ∣∣y−yhat∣∣22=∣∣y−1+e−(w^T⋅x^)1∣∣22
我们一般不会采用该方法构建损失函数,其根本原因在于,在数学层面上我们可以证明,对于逻辑回归,当y属于0-1分类变量时, ∣ ∣ y − y h a t ∣ ∣ 2 2 ||y-yhat||_2^2 ∣∣y−yhat∣∣22损失函数并不是凸函数,而非凸的损失函数将对后续参数最优解求解造成很大麻烦。而相比之下,概率连乘所构建的损失函数是凸函数,可以快速求解出全域最小值。
其二,在构建损失函数的过程中,我们需要将概率连乘改为对数累加,有一个很重要的原因是,在实际建模运算过程中,尤其是面对大量数据进行损失函数构建过程中,由于有多少条数据就要进行多少次累乘,而累乘的因子又是介于(0,1)之间的数,因此极有可能累乘得到一个非常小的数,而通用的计算框架计算精度有限,即有可能在累乘的过程中损失大量精度,而转化为对数累加之后能够很好的避免该问题的发生。
从数学角度可以证明,按照上述构成构建的逻辑回归损失函数仍然是凸函数,此时我们仍然可以通过对LogitLoss(w,b)求偏导然后令偏导函数等于0、再联立方程组的方式来对参数进行求解。
∂ L o g i t L o s s ( w , b ) ∂ w = 0 \frac{\partial LogitLoss(w,b)}{\partial w}=0 ∂w∂LogitLoss(w,b)=0 ∂ L o g i t L o s s ( w , b ) ∂ w = 0 \frac{\partial LogitLoss(w,b)}{\partial w}=0 ∂w∂LogitLoss(w,b)=0
值得一提的是,上述构建损失函数和求解损失函数的过程,也被称为极大似然估计。接下来我们就将极大似然估计的方法推广到一般过程。
接下来,我们考虑更为一般的情况,围绕逻辑回归方程的一般形式,采用极大似然估计方法进行参数估计:
逻辑回归模型: y = 1 1 + e − ( w ^ T ⋅ x ^ ) y = \frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} y=1+e−(w^T⋅x^)1
其中: w ^ = [ w 1 , w 2 , . . . w d , b ] T , x ^ = [ x 1 , x 2 , . . . x d , 1 ] T \hat w = [w_1,w_2,...w_d, b]^T, \hat x = [x_1,x_2,...x_d, 1]^T w^=[w1,w2,...wd,b]T,x^=[x1,x2,...xd,1]T
求解过程总共分为四个步骤,分别是:
所谓似然函数,可简单理解为前例中累乘的函数。而累乘过程中的每个项,可称为似然项,不难发现,似然项其实和数据是一一对应的,带入多少条数据进行建模,似然函数中就有多少个似然项。
我们知道,对于逻辑回归来说,当 w ^ \hat w w^和 x ^ \hat x x^取得一组之后,既可以有一个概率预测输出结果,即:
p ( y = 1 ∣ x ^ ; w ^ ) = 1 1 + e − ( w ^ T ⋅ x ^ ) p(y=1|\hat x;\hat w) = \frac{1}{1+e^{-(\hat w^T \cdot \hat x)}} p(y=1∣x^;w^)=1+e−(w^T⋅x^)1
而对应 y y y取0的概率为: 1 − p ( y = 1 ∣ x ^ ; w ^ ) = 1 − 1 1 + e − ( w ^ T ⋅ x ^ ) = e − ( w ^ T ⋅ x ^ ) 1 + e − ( w ^ T ⋅ x ^ ) 1-p(y=1|\hat x;\hat w) =1- \frac{1}{1+e^{-(\hat w^T \cdot \hat x)}}=\frac{e^{-(\hat w^T \cdot \hat x)}}{1+e^{-(\hat w^T \cdot \hat x)}} 1−p(y=1∣x^;w^)=1−1+e−(w^T⋅x^)1=1+e−(w^T⋅x^)e−(w^T⋅x^)
我们可以令 p 1 ( x ^ ; w ^ ) = p ( y = 1 ∣ x ^ ; w ^ ) p_1(\hat x;\hat w)=p(y=1|\hat x;\hat w) p1(x^;w^)=p(y=1∣x^;w^) p 0 ( x ^ ; w ^ ) = 1 − p ( y = 1 ∣ x ^ ; w ^ ) p_0(\hat x;\hat w)=1-p(y=1|\hat x;\hat w) p0(x^;w^)=1−p(y=1∣x^;w^)
因此,第 i i i个数据所对应的似然项可以写成:
p 1 ( x ^ ; w ^ ) y i ⋅ p 0 ( x ^ ; w ^ ) ( 1 − y i ) p_1(\hat x;\hat w)^{y_i} \cdot p_0(\hat x;\hat w)^{(1-y_i)} p1(x^;w^)yi⋅p0(x^;w^)(1−yi)
其中, y i y_i yi表示第 i i i条数据对应的类别标签。不难发现,当 y i = 0 y_i=0 yi=0时,代表的是 i i i第条数据标签为0,此时需要带入似然函数的似然项是 p 0 ( x ^ ; w ^ ) p_0(\hat x;\hat w) p0(x^;w^)(因为希望 p 0 p_0 p0的概率更大)。反之,当 y i = 1 y_i=1 yi=1时,代表的是 i i i第条数据标签为1,此时需要带入似然函数的似然项是 p 1 ( x ^ ; w ^ ) p_1(\hat x;\hat w) p1(x^;w^)。上述似然项可以同时满足这两种不同的情况。
接下来,通过似然项的累乘计算极大似然函数:
∏ i = 1 N [ p 1 ( x ^ ; w ^ ) y i ⋅ p 0 ( x ^ ; w ^ ) ( 1 − y i ) ] \prod^N_{i=1}[p_1(\hat x;\hat w)^{y_i} \cdot p_0(\hat x;\hat w)^{(1-y_i)}] i=1∏N[p1(x^;w^)yi⋅p0(x^;w^)(1−yi)]
然后即可在似然函数基础上对其进行(以e为底的)对数转换,为了方便后续利用优化方法求解最小值,同样我们考虑构建负数对数似然函数:
L ( w ^ ) = − l n ( ∏ i = 1 N [ p 1 ( x ^ ; w ^ ) y i ⋅ p 0 ( x ^ ; w ^ ) ( 1 − y i ) ] ) = ∑ i = 1 N [ − y i ⋅ l n ( p 1 ( x ^ ; w ^ ) ) − ( 1 − y i ) ⋅ l n ( p 0 ( x ^ ; w ^ ) ) ] = ∑ i = 1 N [ − y i ⋅ l n ( p 1 ( x ^ ; w ^ ) ) − ( 1 − y i ) ⋅ l n ( 1 − p 1 ( x ^ ; w ^ ) ) ] \begin{aligned} L(\hat w) &= -ln(\prod^N_{i=1}[p_1(\hat x;\hat w)^{y_i} \cdot p_0(\hat x;\hat w)^{(1-y_i)}]) \\ &= \sum^N_{i=1}[-y_i \cdot ln(p_1(\hat x;\hat w))-(1-y_i) \cdot ln(p_0(\hat x;\hat w))] \\ &= \sum^N_{i=1}[-y_i \cdot ln(p_1(\hat x;\hat w))-(1-y_i) \cdot ln(1-p_1(\hat x;\hat w))] \end{aligned} L(w^)=−ln(i=1∏N[p1(x^;w^)yi⋅p0(x^;w^)(1−yi)])=i=1∑N[−yi⋅ln(p1(x^;w^))−(1−yi)⋅ln(p0(x^;w^))]=i=1∑N[−yi⋅ln(p1(x^;w^))−(1−yi)⋅ln(1−p1(x^;w^))]
公式推导致此即可,后续我们将借助该公式进行损失函数求解。
通过一系列数学过程可以证明,通过极大似然估计构建的损失函数是凸函数,此时我们可以采用导数为0联立方程组的方式进行求解,这也是极大似然估计对参数求解的一般方法。但这种方法会涉及大量的导数运算、方程组求解等,并不适用于大规模甚至是超大规模数值运算,因此,在机器学习领域,我们通常会采用一些更加通用的优化方法对逻辑回归的损失函数进行求解,通常来说是牛顿法或者梯度下降算法,其中,梯度下降算法是机器学习中最为通用的求解损失函数的优化算法,我们将在下一小节花费一整节的时间来进行介绍。本节我们将继续介绍另外一种推导逻辑回归损失函数的方法——KL离散度计算法,并介绍有关信息熵、交叉熵等关键概念。
由于模型本身和损失函数构建方式都和线性回归有所不同,逻辑回归的损失函数无法采用最小二乘法进行求解。
接下来,我们介绍另一种构建逻辑回归损失函数的基本思路——借助相对熵(relative entropy,又称KL离散度)构建损失函数。尽管最终损失函数构建结果和极大似然估计相同,但该过程所涉及到的关于信息熵(entropy)、相对熵等概念却是包括EM算法、决策树算法等诸多机器学习算法的理论基础。
通常我们用熵(entropy)来表示随机变量不确定性的度量,或者说系统混乱程度、信息混乱程度。熵的计算公式如下:
H ( X ) = − ∑ i = 1 n p ( x i ) l o g ( p ( x i ) ) H(X) = -\sum^n_{i=1}p(x_i)log(p(x_i)) H(X)=−i=1∑np(xi)log(p(xi))
其中, p ( x i ) p(x_i) p(xi)表示多分类问题中第 i i i个类别出现的概率, n n n表示类别总数,通常来说信息熵的计算都取底数为2,并且规定 l o g 0 = 0 log0=0 log0=0。举例说明信息熵计算过程,假设有二分类数据集1标签如下:
则信息熵的计算过程中 n = 2 n=2 n=2,令 p ( x 1 ) p(x_1) p(x1)表示类别0的概率, p ( x 2 ) p(x_2) p(x2)表示类别1的概率(反之亦然),则 p ( x 1 ) = 1 4 p(x_1)=\frac{1}{4} p(x1)=41 p ( x 2 ) = 3 4 p(x_2)=\frac{3}{4} p(x2)=43
则该数据集的信息熵计算结果如下:
H ( X ) = − ( p ( x 1 ) l o g ( p ( x 1 ) ) + p ( x 2 ) l o g ( p ( x 2 ) ) ) = − ( 1 4 ) l o g ( 1 4 ) − ( 3 4 ) l o g ( 3 4 ) \begin{aligned} H(X) &= -(p(x_1)log(p(x_1))+p(x_2)log(p(x_2))) \\ &=-(\frac{1}{4})log(\frac{1}{4})-(\frac{3}{4})log(\frac{3}{4}) \end{aligned} H(X)=−(p(x1)log(p(x1))+p(x2)log(p(x2)))=−(41)log(41)−(43)log(43)
-1/4 * np.log2(1/4) - 3/4 * np.log2(3/4)
#0.8112781244591328
当然,我们也可以定义信息熵计算函数
def entropy(p):
if p == 0 or p == 1:
ent = 0
else:
ent = -p * np.log2(p) - (1-p) * np.log2(1-p)
return ent
简单测试函数性能:
entropy(1/4)
#0.8112781244591328
同时,在二分类问题中, n = 2 n=2 n=2且 p ( x 1 ) + p ( x 2 ) = 1 p(x_1)+p(x_2)=1 p(x1)+p(x2)=1,我们也可推导二分类的信息熵计算公式为:
H ( X ) = − p ( x ) l o g ( p ( x ) ) − ( 1 − p ( x ) ) l o g ( 1 − p ( x ) ) H(X) = -p(x)log(p(x))-(1-p(x))log(1-p(x)) H(X)=−p(x)log(p(x))−(1−p(x))log(1−p(x))
其中 p ( x ) p(x) p(x)为样本标签为0或1的概率。
可以证明,熵的计算结果在[0,1]之间,并且熵值越大,系统越混乱、信息越混乱。例如,有如下两个数据集,其中数据集2总共4条样本,0、1类各占50%。
对于该数据集,我们可以计算信息熵为 H 1 ( X ) = − ( 1 2 ) l o g ( 1 2 ) − ( 1 2 ) l o g ( 1 2 ) H_1(X) = -(\frac{1}{2})log(\frac{1}{2})-(\frac{1}{2})log(\frac{1}{2}) H1(X)=−(21)log(21)−(21)log(21)
entropy(1/2)
#1.0
此时信息熵达到最高值,也就代表对于上述二分类的数据集,标签随机变量的不确定性已经达到峰值。
进一步我们计算下列数据集3的信息熵:
信息熵计算可得: H 1 ( X ) = − ( 4 4 ) l o g ( 4 4 ) − ( 0 4 ) l o g ( 0 4 ) = 0 H_1(X) = -(\frac{4}{4})log(\frac{4}{4})-(\frac{0}{4})log(\frac{0}{4})=0 H1(X)=−(44)log(44)−(40)log(40)=0
注,信息熵计算中规定 l o g 0 = 0 log0=0 log0=0
entropy(0)
#0
此时信息熵取得最小值,也就代表标签的取值整体呈现非常确定的状态,系统信息规整。
值得一提的是,此时标签本身的信息量也为0,并没有进一步进行预测的必要。
结合上述三个数据集,不难看出,当标签取值不均时信息熵较高,标签取值纯度较高时信息熵较低。假设p为未分类数据集中1样本所占比例,则数据集信息熵随着p变化为变化趋势如下:
p = np.linspace(0, 1, 50)
ent_l = [entropy(x) for x in p]
plt.plot(p, ent_l)
plt.xlabel('P')
plt.ylabel('Entropy')
相对熵也被称为Kullback-Leibler散度(KL散度)或者信息散度(information divergence)。通常用来衡量两个随机变量分布的差异性。假设对同一个随机变量X,有两个单独的概率分布P(x)和Q(x),当X是离散变量时,我们可以通过如下相对熵计算公式来衡量二者差异:
D K L ( P ∣ ∣ Q ) = ∑ i = 1 n P ( x i ) l o g ( P ( x i ) Q ( x i ) ) D_{KL}(P||Q)=\sum ^n_{i=1}P(x_i)log(\frac{P(x_i)}{Q(x_i)}) DKL(P∣∣Q)=i=1∑nP(xi)log(Q(xi)P(xi))
和信息熵类似,相对熵越小,代表Q(x)和P(x)越接近。
从交叉熵的计算公式不难看出,这其实是一种非对称性度量,也就是 D K L ( P ∣ ∣ Q ) ≠ D K L ( Q ∣ ∣ P ) D_{KL}(P||Q)≠D_{KL}(Q||P) DKL(P∣∣Q)=DKL(Q∣∣P)。从本质上来说,相对熵刻画的是用概率分布Q来刻画概率分布P的困难程度,而在机器学习领域,我们一般令Q为模型输出结果,而P为数据集标签真实结果,以此来判断模型输出结果是否足够接近真实情况。
Q为拟合分布P为真实分布,也被称为前向KL散度(forward KL divergence)。
当然,上述相对熵公式等价于:
D K L ( P ∣ ∣ Q ) = ∑ i = 1 n P ( x i ) l o g ( P ( x i ) Q ( x i ) ) = ∑ i = 1 n P ( x i ) l o g ( P ( x i ) ) − ∑ i = 1 n P ( x i ) l o g ( Q ( x i ) ) = − H ( P ( x ) ) + [ − ∑ i = 1 n P ( x i ) l o g ( Q ( x i ) ) ] \begin{aligned} D_{KL}(P||Q)&=\sum ^n_{i=1}P(x_i)log(\frac{P(x_i)}{Q(x_i)}) \\ &=\sum ^n_{i=1}P(x_i)log(P(x_i))-\sum ^n_{i=1}P(x_i)log(Q(x_i)) \\ &=-H(P(x))+[-\sum ^n_{i=1}P(x_i)log(Q(x_i))] \end{aligned} DKL(P∣∣Q)=i=1∑nP(xi)log(Q(xi)P(xi))=i=1∑nP(xi)log(P(xi))−i=1∑nP(xi)log(Q(xi))=−H(P(x))+[−i=1∑nP(xi)log(Q(xi))]
而对于给定数据集,信息熵 H ( P ( X ) ) H(P(X)) H(P(X))是确定的,因此相对熵的大小完全由 − ∑ i = 1 n P ( x i ) l o g ( Q ( x i ) ) -\sum ^n_{i=1}P(x_i)log(Q(x_i)) −∑i=1nP(xi)log(Q(xi))决定。而该式计算结果也被称为交叉熵(cross entropy)计算。
c r o s s _ e n t r o p y ( P , Q ) = − ∑ i = 1 n P ( x i ) l o g ( Q ( x i ) ) cross\_entropy(P,Q) = -\sum ^n_{i=1}P(x_i)log(Q(x_i)) cross_entropy(P,Q)=−i=1∑nP(xi)log(Q(xi))
因此,如果我们希望P、Q二者分布尽可能接近,我们就需要尽可能减少相对熵,但由于相对熵=交叉熵-信息熵,因此我们只能力求减少交叉熵。当然,也正因如此,交叉熵可以作为衡量模型输出分布是否接近真实分布的重要度量方法。
简单总结上述过程要点:
根据吉布斯不等式,相对熵的取值恒大于等于零,当预测分布和真实分布完全一致时相对熵取值为0,此时交叉熵等于数据信息熵,此外只要二者分布不一致,交叉熵的取值都将大于信息熵。
交叉熵的计算公式看似复杂,但实际运算过程比较简单,对于类似逻辑回归模型输出为连续变量,而真实标签为离散变量的数据集,可以举例说明计算过程。例如有数据集情况如下:
我们可以将其改写成如下形式:
其中A、B表示每条样本可能所属的类别。围绕该数据集,第一条数据的交叉熵计算过程如下:
c r o s s _ e n t r o p y = − 0 ∗ l o g ( 0.2 ) − 1 ∗ l o g ( 0.8 ) cross\_entropy = -0 * log(0.2)-1*log(0.8) cross_entropy=−0∗log(0.2)−1∗log(0.8)
-np.log2(0.8)
#0.3219280948873623
再次理解交叉熵计算公式中的叠加是类别的叠加。
上述数据集标签由0-1转化为A、B,也被称为名义型变量的独热编码。
而对于多个数据集,整体交叉熵实际上是每条数据交叉熵的均值。例如上述数据集,整体交叉熵计算结果为:
− 1 ∗ l o g ( 0.8 ) − 1 ∗ l o g ( 0.7 ) − 1 ∗ l o g ( 0.6 ) − 1 ∗ l o g ( 0.7 ) 4 \frac{-1 * log(0.8) -1 * log(0.7) -1 * log(0.6) -1 * log(0.7)}{4} 4−1∗log(0.8)−1∗log(0.7)−1∗log(0.6)−1∗log(0.7)
(-np.log2(0.8)-np.log2(0.7)-np.log2(0.6)-np.log2(0.7)) / 4
#0.5220100086782713
据此,我们可以给出多样本交叉熵计算公式如下:
c r o s s _ e n t r o p y = − 1 m ∑ j m ∑ i n p ( p i j ) l o g ( q i j ) cross\_entropy = -\frac{1}{m}\sum ^m_j \sum^n_ip(p_{ij})log(q_{ij}) cross_entropy=−m1j∑mi∑np(pij)log(qij)
其中m为数据量,n为类别数量。
围绕上述数据集,如果考虑采用极大似然估计来进行计算,我们发现基本计算流程保持一致:
L ( w ^ ) = ∑ i = 1 N [ − y i ⋅ l n ( p 1 ( x ^ ; w ^ ) ) − ( 1 − y i ) ⋅ l n ( 1 − p 1 ( x ^ ; w ^ ) ) ] L(\hat w)= \sum^N_{i=1}[-y_i \cdot ln(p_1(\hat x;\hat w))-(1-y_i) \cdot ln(1-p_1(\hat x;\hat w))] L(w^)=i=1∑N[−yi⋅ln(p1(x^;w^))−(1−yi)⋅ln(1−p1(x^;w^))]
带入数据可得:
− l n ( 0.8 ) − l n ( 0.7 ) − l n ( 0.6 ) − l n ( 0.7 ) -ln(0.8)-ln(0.7)-ln(0.6)-ln(0.7) −ln(0.8)−ln(0.7)−ln(0.6)−ln(0.7)
-np.log(0.8)-np.log(0.7)-np.log(0.6)-np.log(0.7)
#1.4473190629576653
尽管具体数值计算结果有所差异,但基本流程都是类似的——取类别1的概率的对数运算结果进行累加再取负数。因此在实际建模过程中,考虑采用极大似然估计构建损失函数,和采用交叉熵构建损失函数,效果是相同的,二者构建的损失函数都能很好的描绘模型预测结果和真实结果的差异程度。不过在机器学习领域,一般以交叉熵损失函数为主。
据此,我们也可最终推导二分类交叉熵损失函数计算公式,结合极大似然估计的计算公式和交叉熵的基本计算流程,二分类交叉熵损失函数为:
b i n a r y C E ( w ^ ) = − 1 n ∑ i = 1 N [ y i ⋅ l o g ( p 1 ( x ^ ; w ^ ) ) + ( 1 − y i ) ⋅ l o g ( 1 − p 1 ( x ^ ; w ^ ) ) ] binaryCE(\hat w)= -\frac{1}{n}\sum^N_{i=1}[y_i \cdot log(p_1(\hat x;\hat w))+(1-y_i) \cdot log(1-p_1(\hat x;\hat w))] binaryCE(w^)=−n1i=1∑N[yi⋅log(p1(x^;w^))+(1−yi)⋅log(1−p1(x^;w^))]
我们也可以定义一个函数来进行二分类交叉熵损失函数的计算:
def BCE(y, yhat):
"""
二分类交叉熵损失函数
"""
return(-(1/len(y))*np.sum(y*np.log2(yhat)+(1-y)*np.log2(1-yhat)))
简单进行验证
y = np.array([1, 0, 0, 1]).reshape(-1, 1)
yhat = np.array([0.8, 0.3, 0.4, 0.7]).reshape(-1, 1)
BCE(y, yhat)
#0.5220100086782713
至此,我们就完成了完整的逻辑回归损失函数的构建。但正如此前所讨论的一样,对于逻辑回归的损失函数来说,尽管也是凸函数,但无法使用最小二乘法进行求解。在下一节,我们将介绍一种更加通用的求解损失函数的优化算法——梯度下降。并最终完成逻辑回归的参数求解。