• 统计学习:逻辑回归与交叉熵损失(Pytorch实现)


    1. Logistic 分布和对率回归

    监督学习的模型可以是概率模型或非概率模型,由条件概率分布P(Y|X)P(Y|X)或决 策函数(decision function)Y=f(X)Y=f(X)表示,随具体学习方法而定。对具体的输入xx进行相应的输出预测并得到某个结果时,写作P(y|x)P(y|x)y=f(x)y=f(x)

    我们这里的 Logistic 分类模型是概率模型,模型P(Y|X)P(Y|X)表示给定随机向量XX下,分类标签YY的条件概率分布。这里我们只讨论二分类问题。后面我们介绍多层感知机的时候会介绍多分类问题。道理是一样的。

    先介绍 Logistic 分布。

    Logistic 的分布函数为:

    F(x)=P(X<=x)=11+e(xu)/γ
    F(x)=P(X<=x)=11+e(xu)/γ

    该分布函数的图像为:

    该函数以点(u,1/2)(u,1/2)中心对称,在中心附近增长速度较快,在两端增长速度较慢。

    我们后面对于未归一化的数zz,采用sigmoidsigmoid函数

    σ(z)=11+exp(z)
    σ(z)=11+exp(z)

    将其“压”在(0, 1)之间。这样就可以使zz归一化,一般我们使归一化后的值表示置信度(belief),可以理解成概率,但是与概率有着细微的差别,更体现 “属于××类的把握”的意思。

    对于二分类,标签Y=0Y=0Y=1Y=1,我们将P(Y=0|x)P(Y=0|x)P(Y=1|x)P(Y=1|x)表示如下,也就定义了二项逻辑回归模型:

    P(Y=1|x)=σ(wTx+b)=11+exp((wTx+b)) (1)P(Y=0|x)=1σ(wTx+b)=11+exp(wTx+b)(2)
    P(Y=1|x)=σ(wTx+b)=11+exp((wTx+b)) (1)P(Y=0|x)=1σ(wTx+b)=11+exp(wTx+b)(2)

    现在考察 Logistic 回归模型的特点,一个事件的几率(odds)是指该事件发生的概率与不发生的概率的比值。如果事件发生的概率是pp,那么该事件的几率是p/(1p)p/(1p),该事件的对数几率(log odds,简称对率)或 logit 函数是

    logit(p)=log p/(1p)
    logit(p)=log p/(1p)

    对 Logistic 回归而言,由式子(1)(1)和式子(2)(2)得到:

    logP(Y=1|x)1P(Y=1|x)=wTx+b
    logP(Y=1|x)1P(Y=1|x)=wTx+b

    这玩意在统计学里面称之为“对率回归”,其实就是“Logistic regression 名称”的由来。这里的 Logistic 和“逻辑”没有任何关系,和对率才是有关系的。 可以看出,输出Y=1Y=1的对数几率是由输入xx的线性函数表示的模型,即 Logistic 回归模型。

    2. Logistic 回归模型的参数估计

    我们在《统计推断:极大似然估计、贝叶斯估计与方差偏差分解》)中说过,从概率模型的视角来看,机器模型学习的过程就是概率分布参数估计的过程,这里就是估计条件概率分布P(Y|X)P(Y|X)的参数θθ。对于给定的训练数据集T={{x1,y1},{x2,y2},,{xN,yN}}T={{x1,y1},{x2,y2},,{xN,yN}},其中,xiRnyi{0,1},可以应用极大似然估计法估计模型参数,从而得到Logistic回归模型。

    我们设P(Y=1|x)=π(x)P(Y=0|x)=1π(x),很显然P(Y|x)服从一个伯努利分 布,不过这个伯努利分布很复杂,它的参数p是一个函数π(x)。我们这里采用一个抽象的观念,将函数π(x)看做一个整体,这样P(Y|x)服从二项分布,可以方便我们理解。我们的参数θ=(w,b),不过我们还是采用之前的技巧,将权值向量和输入向量加以扩充,直接记做θ=w

    这样,对于N个样本,定义似然函数:

    L(w|x1,x2,...,xN)=ΠNi=1[π(xi)]yi[1π(xi)]1yi

    因为函数比较复杂,我们采用数值优化进行求解。然而这个函数是非凸的, 如果直接用数值迭代算法作用于次函数,可能陷入局部最优解不能保证到收敛到全局最优解。我们为了将其变为负对数似然函数(想一想为什么要取负号),也就是一个凸函数,方便用梯度下降法、牛顿法进行求解:

    L(w|x1,x2,...,xN)=Ni=1[yilog π(xi)+(1yi)log(1π(xi))]

    配凑 1/N转换为关于数据经验分布期望的无偏估计 ,即Exˆpdatalog pmodel(xi|θ)而不改变目标函数的优化方向。

    1NNi=1[yilog π(xi)+(1yi)log(1π(xi))]

    上面这个式子,就是我们在深度学习里面常用的交叉熵损失函数(cross entropy loss function)的特殊情况。(交叉熵损失函数一般是多分类,这里是二分类)。后面我们会学习,在深度学习领域中,我们用f()表示神经网络对输入x加以的 映射(这里没有激活函数,是线性的映射),f(x)就是输出的条件概率分布P(Y|X=x)。 设类别总数为Kxi为第i个样本的特征向量(特征维度为D),f(xi)输出维度也为Kf(xi)k表示P(yk|xi)。因为是多分类,P(yk|xi)的计算方法就不能通过sigmoid函数来得到了,取而代之是更通用的softmax函数。我们给定输入样本x,设zk=Dj=1wzjxj,其中WRK×D神经网络的权重矩阵D为特征向量维度,K为类别总数。则我们有:

    f(x)k=softmax(z)k=exp(zk)kexp(zk)

    我们规定第i个样本的标签yiK维one-hot向量。则交叉熵函数表述如下:

    1NNi=1Kk=1yiklogf(xi)k

    因为yi为 one-hot 向量,只有一个维度为 1,设这个维度为c(即表示类别c),则交叉熵函数可以化简为:

    1NNi=1logf(xi)c

    很明显,如果f(xi)c越大,表示神经网络预测xi样本类别为第c类的概率越大,这也是目标函数优化的方向——即对于类别为c的样本xi,优化器尽量使样本xi被预测为第c类的概率更大。

    如下为使用梯度下降法求解二分类逻辑回归问题(并采用breast_cancer数据集进行测试,该数据集是二分类数据集):

    from locale import normalize
    import numpy as np
    import random
    import torch
    from sklearn.datasets import load_breast_cancer
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score
    # batch_size表示单批次用于参数估计的样本个数
    # y_pred大小为(batch_size, 1)
    # y大小为(batch_size, ),为类别型变量
    def cross_entropy(y_pred, y):
        y = y.reshape(-1, 1)
        return -(torch.mul(y, torch.log(y_pred)) + torch.mul(1-y, torch.log(1-y_pred))).sum()/y.shape[0]
    
    # 前向函数
    def logistic_f(X, w): 
        z = torch.matmul(X, w).reshape(-1, 1) 
        return 1/(torch.exp(-z) + 1)
    
    # 之前实现的梯度下降法,做了一些小修改
    def gradient_descent(X, w, y, n_iter, eta, loss_func, f):
        for i in range(1, n_iter+1):
            y_pred = f(X, w)
            #print(y_pred)
            loss_v = loss_func(y_pred, y)
            #print(loss_v)
            loss_v.backward() 
            with torch.no_grad(): 
                w -= eta*w.grad
            w.grad.zero_()  
        w_star = w.detach()
        return w_star 
    
    # 迭代次数
    n_iter = 600
    
    # 学习率
    eta = 10 # 因为我们前面loss除了样本总数,这里调大些
    
    if __name__ == '__main__':
    
        X, y = load_breast_cancer(return_X_y=True)
    
        D = X.shape[1]
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=0.3, random_state=0)
        n_train, n_test = X_train.shape[0], X_test.shape[0]
    
        # 初始化权重(含偏置)
        w = torch.tensor(2 * np.random.ranf(size=D + 1) - 1, requires_grad=True)
        print(w)
        
        # 扩充1维
        X_train = torch.tensor(np.concatenate([X_train, np.ones([X_train.shape[0], 1])], axis=1))
        X_train = torch.nn.functional.normalize(X_train) # 先归一化,否则后面sigmoid部分输出无穷趋近于0,导致log()得-inf
        X_test = torch.tensor(np.concatenate([X_test, np.ones([X_test.shape[0], 1])], axis=1))
        X_test = torch.nn.functional.normalize(X_test) # 先归一化,否则后面sigmoid部分输出无穷趋近于0,导致log()得-inf
    
     
        y_train, y_test = torch.tensor(y_train), torch.tensor(y_test)
    
        # 调用梯度下降法对函数进行优化
        # 这里采用单次迭代对所有样本进行估计,后面我们会介绍小批量法减少时间复杂度
        w_star = gradient_descent(X_train, w, y_train, n_iter, eta, cross_entropy, logistic_f)
        print(w_star)
        
        y_pred = logistic_f(X_test, w_star)
        pred_label = torch.where(y_pred < 0.5, 0, 1)
        acc = accuracy_score(y_test, pred_label)
        print("accuracy: %f" % (acc))
    
    
    

    注意输入数据要先归一化到(0, 1),否则后面|Xw+b|过大,以致Xw+b<0时使sigmoid函数的输出无穷趋近于0,最终导致log函数得-inf。我们来看一下算法的输出结果。初始权重向量、最终得到的权重向量和模型精度如下:

    Initial w: tensor([-0.3109, -0.4637, -0.0326,  0.0081, -0.0108, -0.9996, -0.2989,  0.2311,
            -0.9197,  0.7227,  0.2692,  0.4146,  0.6281, -0.8207,  0.3153,  0.9207,
            -0.4418,  0.6558, -0.1684,  0.9044, -0.7847, -0.1003, -0.2369, -0.0608,
            -0.3722,  0.3982, -0.7417, -0.8493,  0.0326,  0.1718, -0.3145],
           dtype=torch.float64, requires_grad=True)
    Final w: tensor([  3.0186,   5.7167,  19.8512,  18.5805,   0.0232,  -1.0004,  -0.3366,
              0.2130,  -0.8558,   0.7494,   0.2977,   0.9121,   0.7568,  -6.9429,
              0.3183,   0.9233,  -0.4391,   0.6570,  -0.1606,   0.9056,   2.5354,
              7.5881,  19.1409, -19.0107,  -0.3286,   0.3691,  -0.8191,  -0.8705,
              0.1200,   0.1985,   0.1274], dtype=torch.float64)
    Final acc: 0.918129
    

    可见模型收敛正常。

    如下是多分类逻辑回归问题,我们需要将sigmoid函数修改为softmax函数,损失函数修改为更为通用的交叉熵函数,根据输出维度变化将权重向量修改为权重矩阵等。我们采用iris数据集进行测试,该数据集是一个3分类数据集。

    from locale import normalize
    import numpy as np
    import random
    import torch
    from sklearn.datasets import load_iris
    from sklearn.model_selection import train_test_split
    from sklearn.metrics import accuracy_score
    # batch_size表示单批次用于参数估计的样本个数
    # n_feature为特征向量维度
    # n_class为类别个数
    # y_pred大小为(batch_size, n_class)
    # y大小为(batch_size, ),为类别型变量
    def cross_entropy(y_pred, y):
        # 这里将y变为(batch_size. 1)形式
        y = y.reshape(-1, 1)
        return -torch.log(torch.gather(y_pred, 1, y)).sum()/ y_pred.shape[0]
    
    # 前向函数,注意,这里的sigmoid改为多分类的softmax函数
    def logistic_f(X, W): 
        z = torch.matmul(X, W)
        return torch.exp(z)/torch.exp(z).sum()
    
    # 之前实现的梯度下降法,做了一些小修改
    def gradient_descent(X, W, y, n_iter, eta, loss_func, f):
        for i in range(1, n_iter+1):
            y_pred = f(X, W)
            loss_v = loss_func(y_pred, y)
            loss_v.backward() 
            with torch.no_grad(): 
                W -= eta*W.grad
            W.grad.zero_()  
        W_star = W.detach()
        return W_star 
    
    # 迭代次数
    n_iter = 1200
    
    # 学习率
    eta = 1 # 因为我们前面loss除了样本总数,这里也要调大些
    
    if __name__ == '__main__':
    
        X, y = load_iris(return_X_y=True)
    
        n_classes = y.max() - y.min() # 分类类别总数
        D = X.shape[1]
        X_train, X_test, y_train, y_test = train_test_split(
            X, y, test_size=0.3, random_state=0)
        n_train, n_test = X_train.shape[0], X_test.shape[0]
    
        # 初始化权重(含偏置)
        W = torch.tensor(2 * np.random.ranf(size=(D + 1, n_classes)), requires_grad=True)
        print("Initial W: %s" % W)
        
        # 扩充1维
        X_train = torch.tensor(np.concatenate([X_train, np.ones([X_train.shape[0], 1])], axis=1))
        X_train = torch.nn.functional.normalize(X_train) # 先归一化,否则后面sigmoid输出无穷趋近于0,导致log()得-inf
        X_test = torch.tensor(np.concatenate([X_test, np.ones([X_test.shape[0], 1])], axis=1))
        X_test = torch.nn.functional.normalize(X_test) # 先归一化,否则后面sigmoid输出无穷趋近于0,导致log()得-inf
    
        print(y_test)
        y_train, y_test = torch.tensor(y_train), torch.tensor(y_test)
    
        # 调用梯度下降法对函数进行优化
        # 这里采用单次迭代对所有样本进行估计,后面我们会介绍小批量法减少时间复杂度
        W_star = gradient_descent(X_train, W, y_train, n_iter, eta, cross_entropy, logistic_f)
        print("Final w: %s" % W_star)
        
        y_pred = logistic_f(X_test, W_star)
        pred_label = torch.argmax(y_pred, axis=1)
    
        acc = accuracy_score(y_test, pred_label)
        print("Final acc: %f" % (acc))
    

    同样需要注意输入数据要先归一化到(0, 1)。我们来看一下算法的输出结果。初始权重向量、最终得到的权重向量和模型精度如下(同样,权重最后一行为偏置向量b。因为是多分类,每一个类别维度k都会对应一个偏置bk)):

    Initial W: tensor([[0.2088, 0.7352, 1.3713],
            [0.6877, 0.1925, 0.7291],
            [0.4245, 1.7149, 1.8838],
            [0.5134, 1.7090, 0.8319],
            [1.8033, 1.8256, 0.1155]], dtype=torch.float64, requires_grad=True)
    Final w: tensor([[ 3.8652,  4.2337, -2.7884],
            [ 5.9122, -3.3024, -3.2518],
            [-7.2260,  3.5287, 10.9562],
            [-3.4692, -1.2400,  7.3598],
            [ 3.5502,  3.5274, -2.3503]], dtype=torch.float64)
    Final acc: 0.933333
    

    可见模型收敛正常。

    参考


    __EOF__

  • 本文作者: 猎户座
  • 本文链接: https://www.cnblogs.com/orion-orion/p/15891850.html
  • 关于博主: 本科CS系蒟蒻,机器学习半吊子,并行计算混子。
  • 版权声明: 欢迎您对我的文章进行转载,但请务必保留原始出处哦(*^▽^*)。
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    学习Golang时遇到的似懂非懂的概念
    玩转Makefile文件
    java入门-基本数据类型
    QGIS编译(跨平台编译)之四十一:Protobuf编译(Windows、Linux、MacOS环境下编译)
    VUE学习二:事件监听(v-on)、条件判断(v-if/v-else-if/v-else)、循环遍历(v-for)
    Dubbo3.0新特性
    C++链表创建、删除、排序、奇偶数和计算输出的课程实践源码
    Pod入门与实战
    Excel 线性回归函数 LINEST
    [每日两题系列]刷算法题咯~~
  • 原文地址:https://www.cnblogs.com/orion-orion/p/15891850.html