逻辑回归详解

    与SVM算法功能一样,逻辑回归(Logistic Regression)常用于二分类,SVM是利用内积空间的超平面实现分类,逻辑回归的实现类似于神经网络,确切的说,逻辑回归是只有一个隐藏层、隐藏层只有一个节点的神经网络。逻辑回归使用交叉熵作为损失函数,在讨论信息熵一篇中详细介绍过,交叉熵本质上是最大似然法,两者推导出来的损失函数是一致的。

    从概率学角度来看,二分类模型样本的结果只有两,是一个二项分布:两个互斥事件可分别用X、Y表示,事件X的概率为P(X)=p,则P(Y)=1-p。如有n个样本,X事件发生的次数等于k的概率为:二项分布.gif,根据中心极限定理可知,当n较大时,二项分布可以近似于标准差为方差.gif,数学期望为数学期望.gif的正态分布,即正态分布.gif,正态分布的概率分布函数是一个递增函数,如下图所示:

概率分布函数.png

正态分布的概率密度函数是一个钟形,如下图:

图形.jpg

 逻辑回归用Sigmoid函数:模拟.gif来模拟正态分布概率函数,求导h(z)可得概率密度函数:自定义概率密度函数.gif,导数函数h'(z)图像如下:

Figure_1.png

自定义概率密度函数.gif与正态分布的概率密度函数非常相似的,公式中z是一个实数。假设样本数据有n个特性,即x∈Rn,利用逻辑回归实现分类时,需设定一组参数θ,θ∈Rn,通过内积运算z=θTx,将高维属性数据变成一个实数,将z代入h(z)后得到每一个样本发生的概率,逻辑回归目的是求出参数θ, 与SVM算法一样,当样本数据线性不可分时,z=θTx并不能将样本数据映射为两个不相交的凸集,这种情况下需要使用核函数K(x)将样本数据先变为可分集。

一、最大似然法求参数

    样本数据共有m个,第i个样本为(xi,yi),当yi=1时,代表发生随机事件X,代入概率分布函数有:

概率1.gif                                    (1.1)

yi=0时,代表发生事件Y,可以用P(Y=1)或P(X=0)表示,因此有关系式:

概率2.gif     (1.2)

(1.1)和(1.2)分别对应样本为不同分类时概率,可由通用公式表示:

通用.png

根据上面的公式表,可得到最大似然法的目标函数:

最大似然法.gif

取L(θ)对数,得到等效的目标函数l(θ):

LY.png

函数ltheta.gif最大值即可得到参数θ值,这里将函数ltheta.gif处理一下,ltheta.gif除以-1/m,求函数ltheta——2.gif最小值与求函数ltheta.gif最大值是是等效的:

交叉熵.png

对公式(2)求各个未知分量偏导数,偏导数为0时解方程可得到参数向量θ最优值。对函数F(θ)之前,先来看下Sigmoid函数的导数性质:

求导1.png

 根据上式 可求出:

求导2.png

可以看出Sigmoid函数的导数有较好的规律性,可以用上面两个公式对F(θ)求导数:

梯度.png

利用上式求θ中第j个分量的偏导数:

梯度111.png

接下来根据以上的推导过程,用代码实现逻辑回归功能,下例使用的数据集是鸢尾花数据,鸢尾花数据可以分为三类,分别是山鸢尾 ,北美鸢尾,变色鸢尾;鸢尾花具有4个属性,分别是花萼的长度、花萼的宽度、花瓣的长度,花瓣的宽度。基于本篇是二分类,程序只加载了山鸢尾 ,北美鸢尾两类数据。

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.datasets import make_blobs
import  math
stepLowerlimit=1e-5
def logexp(v):
    return math.log (v,math.e)
#Sigmod函数 计算概率
def probility(x,w):
    #print('内积:%0.4f'%(-np.dot(x,w)))
    return 1/(1+math.e **(-np.dot(x,w)) )

#只含有步长的一元函数
def stepfun(w, g,  x,y,step ):
    value=0
    ws=w-g*step
    for i in range(x.shape[0]):
        if y[i]==1:
            value=value+logexp(probility(x[i,...],ws)  )
        else:
            value = value + logexp(1-probility(x[i, ...], ws) )
    return  -value/x.shape[0]

#试探法求步长系数
def explore618step(w, g,  x,y ):
    r=np.array([[0,10]],dtype=np.float)
    alpha=0.618
    l=1e-5
    a,b,iterNum=r[0,0],r[0,1],0
    #shrinkDirection=1代表右端缩减,shrinkDirection=0代表左端缩减
    shrinkDirection=None
    while b-a >l:
        if shrinkDirection==None:
            lambda_k = a + (1 - alpha) * (b - a)
            mu_k = a + alpha * (b - a)
        if shrinkDirection==1:
            mu_k=lambda_k
            lambda_k = a + (1 - alpha) * (b - a)
        if shrinkDirection==0:
            lambda_k=mu_k
            mu_k = a + alpha * (b - a)
            pass
        s1=stepfun(w, g,  x,y,lambda_k )
        s2 = stepfun(w,   g,   x, y, mu_k )
        if s1<=s2:
            a,b,shrinkDirection=a,mu_k,1
        else:
            a, b ,shrinkDirection= lambda_k, b,0
    step=(b+a )/2
    return step
#逻辑回归实现
def lgr(w,x,y,counter):
    #梯度
    g=np.zeros([x.shape[1]])
    for i in range(y.shape[0]):
        for j in range(x.shape[1]):
            g[j] = g[j] + (y[i] - probility(x[i, ...], w)) * x[i, j]#公式2
    g =-g /y.shape[0]
    distance = math.sqrt(np.dot(g,g.T) )
    g = g  / distance
    step = explore618step(w,  g,   x, y )
    for j in range(x.shape[1]):
        w[j] = w[j] - g[j] * step
    if (step <= stepLowerlimit):
        return w,counter
    else:
        counter=counter+1
        return lgr(w,x,y,counter )

def check(w,x,y):
    correctNum=0
    totalNum=0
    for i in range(x.shape[0]):
        p=probility(x[i, ...], w)
        if (p>0.5 and y[i]==1):
            correctNum=correctNum+1
        if (p<0.5 and y[i]==0):
            correctNum=correctNum+1
        totalNum=totalNum+1
    print('正确=%d 总数=%d'%(correctNum,totalNum))
#加载数据集
def loadDataSet():
    iris = datasets.load_iris()
    X = iris['data']
    Y = iris['target']
    x = X[Y != 2]
    y = Y[Y!= 2]
    return x,y

if __name__=='__main__':
    x, y = loadDataSet()
    w = np.zeros(x.shape[1], dtype=np.float)
    w,c = lgr (w, x.copy(), y.copy(),0)
    print('迭代次数=%d'%(c) )
    print('参数',w )
    check(w,x,y)

加载数据集合后利用梯度下降法,求解损失函数F(θ)的最小值,前面已经分析过,F(θ)描述的是交叉熵。求梯度时过程中代码

g[j] = g[j] + (y[i] - probility(x[i, ...], w)) * x[i, j]

是利用公式(2)的推导的结果得到各个参数分量的梯度,得到梯度后利用0.618一维搜索法(一维搜索参考本站文章一维搜索),获得步长系数,程序退出的条件是步长系数小于阈值,代表已经到达最值点,代码运行结果如下图:

运行结果.png

二、正则化解决过拟合问题

    上面的逻辑回归模型是基于训练集,在实际分类时往往不具备泛化能力。介绍决策树时,决策树的去拟合是通过剪枝操作完成的,逻辑回归实现去拟合方法是降低参数的绝对值,参数绝对值大意味着模型偏向于某些属性,这种偏好是为了迎合训练集的数据特征,逻辑回归去拟合目的就是为了消除这种偏好。

    另外,从概率学角度来说,二项分布要用正态分布近似时,有一个隐含的条件:二项分布发生某一个事件时,造成该事件发生的因素可能有多个,正态分布要求在众多因素中,每一个因素都不能起决定性作用。在逻辑回归中,因素可以理解为样本的属性,每一个因素都不能起决定性作用意味着模型不能偏好某一属性,换句话说,所有属性分配权重需均匀。观察上面代码实现的逻辑回归模型,参数绝对值为:11,17,38,21,显然模型是有偏好的,说明模型有过拟合的倾向。

    逻辑回归是利用正则化手段实现去拟合、泛化的功能,正则化的实现借鉴了惩罚函数的思想,具体做法是把参数向量的模、或者称范数,加入到目标函数中,最小化目标函数过程中,要求同时减小参数向量的范数,从而实现降低对某些属性的偏好。根据采用范数的形式,逻辑回归常用的有L1正则化与L2正则化。

2.1  L1正则化

    L1正则化采用了L1范数,即 范数一.gif,L1范数与目标函数结合生成一个新的目标函数:

范数一目标函数.gif

σ称为惩罚因子,在惩罚函数一节中介绍外点惩罚函数法时曾说过,通过增大σ值可使得向量范数不断变小。具体过程为:初始化参数σ,求目标函数最小值,然后增大增大σ值,直到惩罚项范数一惩罚项.gif小于某个阈值时,退出计算得到目标参数。

    L1正则化特点是参数比较稀疏,即参数向量中有大量的参数为0,F(θ)与惩罚项函数范数一惩罚项.gif的等值线如下图所示:

L1.png

圆形曲线是函数F(θ)等值线,正方形是惩罚项函数的等值线,没有加入惩罚项时,F(θ)的最小值可在等值线中心紫色处;加入惩罚项后,需要兼顾惩罚项数值,即正方形的边长不能太长,因此这时不能再取原中心处作为最优解。上图看出,L1正则化倾向于将正方形的顶点处作为最优解,由于顶点同时也在坐标轴上,取最优解时有部分参数为0,即L1正则化最后得到是稀疏的参数向量。

    前面介绍过,逻辑回归利用了中心极限定理,利用正态分布近似二项分布时,需要参数均匀的小,L1正则化对于逻辑回归而言,效果不是非常好,推荐使用L2正则化实现逻辑回归的泛化过程。

2.2  L2正则化

    L2正则化使用范数形式为:范数2.gif,与目标函数结合为新函数:

惩罚函数22.png

L2范数很好理解,它代表到原点的距离,或称为模, F(θ)与惩罚项函数范数二惩罚项.gif的等值线如下图:

L2.png

惩罚项函数等值线可想象为一个圆周,取得最优解时,圆周与F(θ)函数等值线相切于一点,可以看出相比L1正则化,有两点不同:一是L2正则化获得最优解时,各个参数都均匀的小,不会像L1正则化那样将部分参数变为0,L2正则化这个特性是符合逻辑回归需求的;第二个不同是,L1正则化F(θ)的等值线可能会与正方形等值交于一条边的两个顶点,这就造成L1正则化的最优解往往是多个,而L2正则化等值线相切于一点,最优解通常只有一个。

    L1正则化与L2正则化只是选择了不同的范数,核心思想与形式都是一致的,代码实现上区别也不大,本篇介绍L2正则化实现过程,在L2正则化基础上稍加改动即可变为L1正则化,将之前例子加上L2正则化后代码如下:

请点击下面广告后阅读余下文章
上一篇  SVD奇异值分解 下一篇 矩阵/向量/标量间相互求导
评论区