逻辑回归(Logistic Regression)详解

逻辑回归(Logistic Regression)详解逻辑回归也称作logistic回归分析,是一种广义的线性回归分析模型,属于机器学习中的监督学习。其推导过程与计算方式类似于回归的过程,但实际上主要是用来解决二分类问题(也可以解决多分类问题)。通过给定的n组数据(训练集)来训练模型,并在训练结束后对给定的一组或多组数据(测试集)进行分类。其中每一组数据都是由p个指标构成。(1)逻辑回归所处理的数据逻辑回归是用来进行分类的。例如,我们给出一个人的[身高,体重]这两个指标,然后判断这个人是属于”胖“还是”瘦“这一类。对于这个问题,我们可以先测量n个

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全家桶1年46,售后保障稳定

逻辑回归也称作logistic回归分析,是一种广义的线性回归分析模型,属于机器学习中的监督学习。其推导过程与计算方式类似于回归的过程,但实际上主要是用来解决二分类问题(也可以解决多分类问题)。通过给定的n组数据(训练集)来训练模型,并在训练结束后对给定的一组或多组数据(测试集)进行分类。其中每一组数据都是由p 个指标构成。

(1)逻辑回归所处理的数据

逻辑回归是用来进行分类的。例如,我们给出一个人的 [身高,体重] 这两个指标,然后判断这个人是属于”胖“还是”瘦“这一类。对于这个问题,我们可以先测量n个人的身高、体重以及对应的指标”胖“,”瘦”,把胖和瘦分别用0和1来表示,把这n组数据输入模型进行训练。训练之后再把待分类的一个人的身高、体重输入模型中,看这个人是属于“胖”还是“瘦”。

如果数据是有两个指标,可以用平面的点来表示数据,其中一个指标为x轴,另一个为y轴;如果数据有三个指标,可以用空间中的点表示数据;如果是p维的话(p>3),就是p维空间中的点。

从本质上来说,逻辑回归训练后的模型是平面的一条直线(p=2),或是平面(p=3),超平面(p>3)。并且这条线或平面把空间中的散点分成两半,属于同一类的数据大多数分布在曲线或平面的同一侧。

逻辑回归(Logistic Regression)详解

 如上图所示,其中点的个数是样本个数,两种颜色代表两种指标。这个直线可以看成经这些样本训练后得出的划分样本的直线。那么对于之后的样本的p1与p2的值,就可以根据这条直线来判断它属于哪一类了。

(2)算法原理

首先,我们处理二分类问题。由于分成两类,我们便让其中一类标签为0,另一类为1。我们需要一个函数,对于输入的每一组数据x^{(i)},都能映射成0~1之间的数。并且如果函数值大于0.5,就判定属于1,否则属于0。而且函数中需要待定参数,通过利用样本训练,使得这个参数能够对训练集中的数据有很准确的预测。

这个函数就是sigmoid函数,形式为\sigma (x)=\frac{1}{1+e^{-x}}。所以在这里我们可以设函数为

h(\textup{x}^{i})=\frac{1}{1+e^{-(\textup{w}^{T}\textup{x}^{i}+b)}}

这里x^{i}是测试集第i个数据,是p维列向量\begin{pmatrix} x^{i}_{1}&x^{i}_{2}&...&x^{i}_{p}\end{}^{T}w是p维列向量\textbf{w}=\begin{pmatrix}w_{1}&w_{2}&...&w_{p} \end{}^{T},为待求参数;b是一个数,也是待求参数。

我们发现,对于w^{T}x+b,其结果是w_{1}x_{1}+w_{2}x_{2}+...+w_{p}x_{p}+b。所以我们可以把

w写成\begin{pmatrix}w_{1}&w_{2}&...&w_{p}&b \end{}^{T},\textbf{x}^{i}写成\begin{pmatrix}x_{1}^{i}&x_{2}^{i}&...&x_{p}^{i}&1 \end{}^{T}w^{T}x+b就可以写成\textbf{w}^{T}\mathbf{x},则:

h(\mathbf{x}^{i})=\frac{1}{1+e^{-\textbf{w}^{T}\textbf{x}^{i}}}

这样就可以把另一个参数b合并到w中。后面推导也方便很多。当然,我们也可以用用第一种形式来做,本质是相同的。之后就是根据训练样本来求参数w了。

(3)求解参数。

这一部分便是逻辑回归的核心问题了。兔兔在下面将给出两种方法。

(1)极大似然估计。

极大似然估计是数理统计中参数估计的一种重要方法。其思想就是一个事件发生了,那么发生这个事件的概率就是最大的。对于样本i,其类别为y_{i}\epsilon (0,1)。对于样本i,可以把h(\mathbf{x}_{i})看成是一种概率。yi对应是1时,概率是h(xi),即xi属于1的可能性;yi对应是0时,概率是1-h(xi),即xi属于0的可能性 。那么它构造极大似然函数

\prod _{i=1}^{i=k}h(x_{i})\prod _{i=k+1}^{n}(1-h(x_{i})).

其中i从0到k是属于类别1的个数k,i从k+1到n是属于类别0的个数n-k。由于y是标签0或1,所以上面的式子也可以写成:

\prod _{i=1}^{n} h(\mathbf{x}_{i})^{y_{i}}(1-h(\textbf{x}_{i}))^{1-y_{i}}

这样无论y是0还是1,其中始终有一项会变成0次方,也就是1,和第一个式子是等价的。

为了方便,我们对式子取对数。因为是求式子的最大值,可以转换成式子乘以负1,之后求最小值。同时对于n个数据,累加后值会很大,之后如果用梯度下降容易导致梯度爆炸。所以可以除以样本总数n。

L(\textbf{w})=\frac{1}{n}\sum _{i=1}^{n} -y_{i}ln(h(\mathbf{x}_{i}))-(1-y_{i})ln(1-h(\textbf{x}_{i}))

求最小值方法很多,机器学习中常用梯度下降系列方法。也可以采用牛顿法,或是求导数为零时w的数值等。

(2)损失函数

逻辑回归中常用交叉熵损失函数,交叉熵损失函数和上面极大似然法得到的损失函数是相同的。这里不再赘述。另一种也可以采用平方损失函数(均方误差),即

J(w)=\frac{1}{n}\sum _{i=1}^{n}\frac{1}{2}(h(x_{i})-y_{i})^{2}

这个是比较直观的。就是让这个预测函数h(xi)与实际的分类1或0越接近越好。也就是损失函数越小越好。求最小值还是用到上面说到的方法。

目前我们这里已经得到了这两种函数。我们以梯度下降为例,即求损失函数的导数。

对于损失函数(1),导数求解过程如下(需要用到矩阵求导)。

逻辑回归(Logistic Regression)详解

对于损失函数(2),推导过程如下:

 逻辑回归(Logistic Regression)详解

(3)算法实现。

兔兔在这里以Dry_Bean_Dataset文件为例。同学们可以在 www.kaggle.com 中下载该数据集,应该比较方便的。

import pandas as pd
data=pd.read_csv('Dry_Bean_Dataset.csv')
df=pd.DataFrame(data)
print(df.columns,df.shape)

Jetbrains全家桶1年46,售后保障稳定

我们先看一下数据集的情况,里面有很多个指标,包括菜豆的区域,周长,长轴长、短轴长等。class代表对应菜豆的种类。我们这里为了直观方便(便于在二维平面表示),就选MajorAxisLength,MinorAxisLength这两个指标。由于是二分类,我们就选SEKER和BARBUNYA这两个种类,即前3349组数据。绘制散点图观察。

import pandas as pd
import matplotlib.pyplot as plt
data=pd.read_csv('Dry_Bean_Dataset.csv')
df=pd.DataFrame(data)
color=[]
for i in df['Class'][0:3349]:
    if i=='SEKER':
        color.append('red')
    else:
        color.append('blue')
plt.scatter(df['MajorAxisLength'][0:3349],df['MinorAxisLength'][0:3349],color=color)
plt.xlabel('MajorAxisLength')
plt.ylabel('MinorAxisLength')
plt.show()

散点图如下:

逻辑回归(Logistic Regression)详解

 可见,该数据是可以用一条直线分成两个部分的。

之后,我们需要处理一下数据。把每组数据两个指标及其对应的分类绑在一起(如果用随机梯度下降需要这样处理,批量梯度下降可以不用这样处理),并把每组数据转成列向量。

import numpy as np
import pandas as pd
data=pd.read_csv('Dry_Bean_Dataset.csv')
df=pd.DataFrame(data)
label=[]
for i in df['Class'][0:3349]:
    if i=='SEKER':
        label.append(0)
    else:
        label.append(1)
x1=df['MajorAxisLength'][0:3349]
x2=df['MinorAxisLength'][0:3349]
train_data=list(zip(x1,x2,label))
class Logistic_Regression:
    def __init__(self,traindata,alpha=0.001,circle=1000,batchlength=40):
        self.traindata=traindata #训练数据集
        self.alpha=alpha #学习率
        self.circle=circle #学习次数
        self.batchlength=batchlength #把3349个数据分成多个部分,每个部分有batchlength个数据
        self.w=np.random.normal(size=(3,1)) #随机初始化参数w
    def data_process(self):
        '''做随机梯度下降,打乱数据顺序,并把所有数据分成若干个batch'''
        np.random.shuffle(self.traindata)
        data=[self.traindata[i:i+self.batchlength]
              for i in range(0,len(self.traindata),self.batchlength)]
        return data
    def train1(self):
        '''根据损失函数(1)来进行梯度下降,这里采用随机梯度下降'''
        for i in range(self.circle):
            batches=self.data_process()
            print('the {} epoch'.format(i)) #程序运行时显示执行次数
            for batch in batches:
                d_w=np.zeros(shape=(3,1)) #用来累计w导数值
                for j in batch: #取batch中每一组数据
                    x0=np.r_[j[0:2],1] #把数据中指标取出,后面补1
                    x=np.mat(x0).T #转化成列向量
                    y=j[2] #标签
                    dw=(self.sigmoid(self.w.T*x)-y)[0,0]*x
                    d_w+=dw
                self.w-=self.alpha*d_w/self.batchlength

    def train2(self):
        '''用均方损失函数来进行梯度下降求解'''
        for i in range(self.circle):
            batches=self.data_process()
            print('the {} epoch'.format(i)) #程序运行时显示执行次数
            for batch in batches:
                d_w=np.zeros(shape=(3,1)) #用来累计w导数值
                for j in batch: #取batch中每一组数据
                    x0=np.r_[j[0:2],1] #把数据中指标取出,后面补1
                    x=np.mat(x0).T #转化成列向量
                    y=j[2] #标签
                    dw=((self.sigmoid(self.w.T*x)-y)*self.sigmoid(self.w.T*x)*(1-self.sigmoid(self.w.T*x)))[0,0]*x
                    d_w+=dw
                self.w-=self.alpha*d_w/self.batchlength
   
    def sigmoid(self,x):
        return 1/(1+np.exp(-x))

    def predict(self,x):
        '''测试新数据属于哪一类,x是2维列向量'''
        s=self.sigmoid(self.w.T*x)
        if s>=0.5:
            return 1
        elif s<0.5:
            return 0
if __name__=='__main__':
    regr=Logistic_Regression(traindata=train_data)
    regr.train1() #采用1的方式进行训练

处理过程需要注意数据的类型。例如当行向量与列向量相乘为一个数时,我们计算是一个数,但是numpy 返回是一个1×1的矩阵,这样再与后面向量相乘就会出错,所以需要注意把矩阵转换成数。

那么,对于这个模型如何比较直观地观察效果呢?我们知道,最终x代入h(x)中,值越接近于1说明越可能属于1类,越接近0越属于0类;那么当h(x)=0.5时,这个时候x就在分界线上面了。所以由\frac{1}{1+e^{-\mathbf{w}^{T}\mathbf{x}}}=0.5,最终得出\mathbf{w}^{T}\mathbf{x}=0,即w_{1}x_{1}+w_{2}x_{2}+w_{3}=0。这个便是分界直线方程。

w=regr.w
w1=w[0,0]
w2=w[1,0]
w3=w[2,0]
x=np.arange(190,500)
y=-w1*x/w2-w3/w2
plt.plot(x,y)
plt.show()

为了动态观察直线变换,也是可以把这个部分放在train()函数当中的。

对于上面的运行结果,我们可以发现效果不是很好。一方面运行速度很慢,而且alpha设置稍稍大一点很容易造成sigmoid函数下方溢出现象,另一方面,我们发现直线并未把两组数据完全分割开,甚至是穿过了两个区域。

关于这个现象,兔兔认为:数据集数量较大,每处理一遍数据都要很大的计算量,导致运行速度慢,解决方法是:可以每次circle只从中随机选取部分数据进行训练;对于函数溢出的现象,在于\textbf{w}^{T}\textbf{x}运行过程中变成很小的负数,导致指数运算过大,所以可以把学习率变小,防止一次迭代后w就变成使得\textbf{w}^{T}\textbf{x}很小的数值;直线没有完全分割两个部分,我们可以从数据特征方面来考虑,由于我们人为地随便选两个指标,然后根据这两个指标训练并判断分类标准,这样做是很容易出现问题的,我们不知道两个指标的数据特征与关系,也不清楚其它的指标是否对分类起决定性作用,而且两类数据有很多公共的部分重叠,也同样导致分类出现问题。当我们训练结束后,发现直线虽然穿过两个区域,是斜率为正的直线,但实际上它也把两个区域的核心部分(两类当中数据最集中部分)分成了两个部分,说明这一部分数据起了主要作用,而且逻辑回归训练最终目的也是让损失函数达到最小,说明最终的曲线也是符合要求的。

 为了直观体现逻辑回归的训练特点,兔兔选了iris数据集一部分进行训练,效果如下图所示。

逻辑回归(Logistic Regression)详解

 像上面的情况基本上循环几十次就稳定在最优解附近。如果学习率等参数没有弄好,可能会出现下面情况:

逻辑回归(Logistic Regression)详解

 (4)非线性逻辑回归

与线性的逻辑回归相比,非线性的逻辑回归应用应该更加普遍。例如,当两组数据无法用一条直线或一个平面分割,而是需要曲线或曲面才能分割好。这个时候就可以用非线性的逻辑回归了。比如用一个圆、椭圆、曲线等把两组数据分开。

非线性回归的训练和推导过程和前面的是一致的。只是把x1,x2两个指标做一下处理。这个过程和兔兔之前讲到的非线性回归是一致的(详见:《线性回归(线性拟合)与非线性回归(非线性拟合)原理、推导与算法实现(一)》)。

前面我们发现,我们最后训练得到的曲线方程是w_{1}x_{1}+w_{2}x_{2}+w_{3}=0。那么,如果我们令\mathbf{w}=\begin{pmatrix}w_{0}&w_{1}&w_{2}&w_{3} &w_{4}\end{}^{T},把每组输入的向量x处理成:\mathbf{\mathbf{}x}=\begin{pmatrix}1&x_{1}&x_{2}&x_{1}^{2}&x_{2}^{2} \end{}^{T},也就是类似多项式回归。这样训练之后就可以用形式为w_{0}+w_{1}x_{1}+w_{2}x_{2}+w_{3}x_{1}^{2}+w_{4}x_{2}^2=0这样的曲线方程来分割区域了。对于三维、p维也是如此。我们甚至也可以根据需要调整多项式次数,或者函数形式等,从而达到理想的效果。但是这时要注意过拟合、欠拟合的情况发生,并且需要正则化等处理方法。

(5)逻辑回归的多分类问题。

前面所讲的都是逻辑回归的二分类问题,那么逻辑回归是否可以处理多分类呢?答案是肯定的。这时我们就不再使用sigmoid 函数,而是另一个叫做softmax函数。函数形式如下:

softmax(k,x_{1},x_{2},...,x_{n})=\frac{e^{x_{k}}}{\sum _{i=1}^{n}e^{x_{i}}}

那么h(x)函数就是

h(\textbf{x})=\begin{pmatrix}e^{\textbf{w}_{1}^{T}\textbf{x }}\\e^{\mathbf{w}_{2}^{T}\mathbf{x}}\\..\\e^{\textbf{w}_{k}^{T}\mathbf{x}} \end{}\frac{1}{\sum_{j=1}^{k}e^{\textbf{w}_{j}^{T}\textbf{x}}}=\begin{pmatrix}p(y=1)\\p(y=2)\\..\\p(y=k) \end{}

这里同样,我们把k个类用数字1,2…..k来表示,在sigmoid 函数当中,函数值表示概率。在这里也是如此,x经过h函数处理后,得到向量里面对应位置(分类)的数值就是取对应位置(分类)的概率。例如,对于三分类问题,如果向量中p(y=1)=0.7,p(y=2)=0.2,p(y=3)=0.1,那么x属于1类的概率最大,故判别为1类。

它的推导过程于前面类似,同样构造损失函数,求损失函数对w的导数,做梯度下降处理计算。

逻辑回归(Logistic Regression)详解

 

这里用的是均方损失,推导过程如上所示,对各个w求偏导,之后相应的w做梯度下降即可。

(6)总结。

逻辑回归可以分为线性与非线性,也可以根据类的个数分为二分类与多分类问题,使用时需要灵活应用,能够构造损失函数并求梯度,同时能够用算法实现并进行训练预测。

事实上,细心的同学会发现,在逻辑回归中,我们发现是多个输入(即p个指标),最终输出一个结果(0或1),处理过程是输入乘上权重w加偏置b(本文权重w与偏置b都合并到w中了),再对结果用sigmoid 函数处理,这个过程其实很接近于神经网络了,而逻辑回归的模型更接近于感知机。对于神经网络,它不只有输入和输出两层,而且增加了更多的隐藏层,每一层的处理结果都作为下一层的输入,那么它的损失函数与梯度的求解也将更加复杂,模型也复杂许多。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/210031.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • VS2015 密钥_vs离线激活2015

    VS2015 密钥_vs离线激活2015企业版:HM6NR-QXX7C-DFW2Y-8B82K-WTYJV (一般我们都是安装的企业版)专业版:HMGNV-WCYXV-X7G9W-YCX63-B98R2

  • 学计算机我后悔了(计算机专业初级书籍)

    大家好,我是小林哥。平日里,大家都喊程序员加班多很辛苦,动不动就掉头发,但干的还是很香的,毕竟大多数公司钱还是给的很到位的,今年毕业应届生的我见到好多动不动就月薪20K~30K的,真让人两眼泪酸酸,当然这离不开他们大学期间的努力。讲真,没什么家庭背景的人,选择当程序员确实是比较好的选择了,原因有二:首先,当今互联网、AI人工智能、大数据等都是高速发展的行业,自然人才需求很多,薪资也相对其他传统行业高;第二,纯粹看你技术能力,只要自己愿意付出努力,技术能力肯定会慢慢提高上来,而且现在比起几十年

  • 查找-散列查找

    查找-散列查找1.散列的相关概念散列技术是在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使得每个关键字key对应一个存储位置f(key)。查找时,根据这个确定的对应关系找到给定值key的映射f(key),若查找集合中存在这个记录,则必定在f(key)的位置上。这里我们把这种对应关系f称为散列函数,又称为哈希(Hash)函数。按这个思想,采用散列技术将记录存储在一块连续的存储空间中,这块连续存储空间称为

  • Java单例模式的不同写法(懒汉式、饿汉式、双检锁、静态内部类、枚举)[通俗易懂]

    Java单例模式的不同写法(懒汉式、饿汉式、双检锁、静态内部类、枚举)[通俗易懂]Java中单例(Singleton)模式是一种广泛使用的设计模式。单例模式的主要作用是保证在Java程序中,某个类只有一个实例存在。一些管理器和控制器常被设计成单例模式。单例模式好处:它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间; 能够避免由于操作多个实例导致的逻辑错误。 如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用…

  • python 随机函数的具体各种使用

    python 随机函数的具体各种使用

    2021年11月10日
  • java怎么写工业上位机软件_上位机软件类编写心得

    java怎么写工业上位机软件_上位机软件类编写心得usingSystem.Data;usingSystem.Drawing;usingSystem.Linq;usingSystem.Text;usingSystem.Windows.Forms;usingSystem.IO.Ports;usingSystem.IO;usingSystem.Xml;namespacelesson{publicpartialclassSComA…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号