【机器学习】对数线性模型之Logistic回归、SoftMax回归和最大熵模型

2019-11-04 16:46:37 浏览数 (1)

点击上方蓝色字体,关注AI小白入门哟

跟着博主的脚步,每天进步一点点

本文介绍对数线性分类模型,在线性模型的基础上通过复合函数(sigmoid,softmax,entropy )将其映射到概率区间,使用对数损失构建目标函数。首先以概率的方式解释了logistic回归为什么使用sigmoid函数和对数损失,然后将二分类扩展到多分类,导出sigmoid函数的高维形式softmax函数对应softmax回归,最后最大熵模型可以看作是softmax回归的离散型版本,logistic回归和softmax回归处理数值型分类问题,最大熵模型对应处理离散型分类问题。

作者 | 文杰

编辑 | yuquanle

Logistic回归

A、Logistic回归

分类问题可以看作是在回归函数上的一个分类。一般情况下定义二值函数,然而二值函数构成的损失函数非凸,一般采用sigmoid函数平滑拟合(当然也可以看作是一种软划分,概率划分):从函数图像我们能看出,该函数有很好的特性,适合二分类问题。至于为何选择Sigmoid函数,后面可以从广义线性模型导出为什么是Sigmoid函数。

逻辑回归可以看作是在线性回归的基础上构建的分类模型,理解的角度有多种(最好的当然是概率解释和最小对数损失),而最直接的理解是考虑逻辑回归是将线性回归值离散化。即一个二分类问题(二值函数)如下:

Sigmoid函数:

0-1损失的二分类问题属于一种硬划分,即是与否的划分,而sigmoid函数则将这种硬划分软化,以一定的概率属于某一类(且属于两类的加和为1)。

Sigmoid函数将线性回归值映射到 的概率区间,从函数图像我们能看出,该函数有很好的特性,适合二分类问题。 因此逻辑回归模型如下:

这里对于目标函数的构建不再是最小化函数值与真实值的平方误差了,按分类原则来讲最直接的损失因该是0-1损失,即分类正确没有损失,分类错误损失计数加1。但是0-1损失难以优化,存在弊端。结合sigmoid函数将硬划分转化为概率划分的特点,采用概率的对数损失(概率解释-N次伯努利分布加最大似然估计),其目标函数如下:

同样采用梯度下降的方法有:

又:

所以有:

B、概率解释

逻辑回归的概率解释同线性回归模型一致,只是假设不再是服从高斯分布,而是服从0-1分布,由于 ,假设随机变量y服从伯努利分布是合理的 。即:

所以最大化似然估计有:

logistic采用对数损失(对数似然函数)原因:

1) 从概率解释来看,多次伯努利分布是指数的形式。由于最大似然估计导出的结果是概率连乘,而概率(sigmoid函数)恒小于1,为了防止计算下溢,取对数将连乘转换成连加的形式,而且目标函数和对数函数具备单调性,取对数不会影响目标函数的优化值。

2)从对数损失目标函数来看,取对数之后在求导过程会大大简化计算量。

Softmax回归

A、Softmax回归

Softmax回归可以看作是Logistic回归在多分类上的一个推广。考虑二分类的另一种表示形式:

当logistic回归采用二维表示的话,那么其损失函数如下:

其中,在逻辑回归中两类分别为,二在softmax中采用,两个随机变量组成二维向量表示,当然隐含约束.为了更好的表示多分类问题,将(不一定理解为的取值为,更应该理解为可以取类)多分类问题进行如下表示:

其中向量的第位为1,其他位为,也就是当 时将其映射成向量时对应第位为。采用多维向量表示之后,那么对于每一维就变成了一个单独的二分类问题了,所以softmax函数形式如下:

其中函数值是一个维的向量,同样采用对数损失(N元伯努利分布和最大似然估计),目标函数形式是logistic回归的多维形式。

其中表示第个样本的标签向量化后第维的取值或者.可以看出Softmax的损失是对每一类计算其概率的对数损失,而logistic回归是计算两类的回归,其本质是一样。Logistic回归和Softmax回归都是基于线性回归的分类模型,两者无本质区别,都是从伯努利分结合最大对数似然估计。只是Logistic回归常用于二分类,而Softmax回归常用于多分类。而且Logistic回归在考虑多分类时只考虑类。

概率解释(求导推导):

二分类与多分类可以看作是二元伯努利分布到多元伯努利分布的一个推广,概率解释同Logistic回归一致。详细解释放到广义线性模型中。

B、二分类转多分类思想

对于多分类问题,同样可以借鉴二分类学习方法,在二分类学习基础上采用一些策略以实现多分类,基本思路是“拆解法”,假设N个类别,经典的拆分算法有“一对一”,“一对多”,“多对多”,

一对一的基本思想是从所有类别中选出两类来实现一个两分类学习器,即学习出个二分类器,然后对新样本进行预测时,对这 个分类器进行投票最终决定属于那一类。

一对多的基本思想是把所有类别进行二分类,即属于类和非两类,这样我们就需要N个分类器,然后对新样本进行预测时,与每一个分类器比较,最终决定属于哪一类。这其实就是Softmax的思想,也是SVM多分类的思想。

最大熵模型

很奇怪,为什么会把最大熵模型放到这,原因很简单,它和Logistic回归和SoftMax回归实在是惊人的相似,同属于对数线性模型。

A、熵的概念

信息熵:熵是一种对随机变量不确定性的度量,不确定性越大,熵越大。若随机变量退化成定值,熵为0。均匀分布是“最不确定”的分布 。

假设离散随机变量X的概率分布为,则其熵为:

其中熵满足不等式,为取值数。

联合熵:对于多个随机变量的不确定性可以用联合熵度量。

假设离散随机变量的联合概率分布为,则其熵为:

条件熵:在给定条件下描述随机变量的不确定性。

假设离散随机变量,在给定的条件下的不确定性为条件熵H(X|Y),也就等于。

互信息:衡量两个随机变量相关性的大小。

相对熵(KL散度):衡量对于同一个随机变量两个概率分布的差异性。

有互信息和相对熵的定义有下式:

关于熵的介绍就到此,不细究,虽然上面的这些定义在机器学习中都会遇到,不过后面涉及到的主要还是熵和条件熵,互信息。

B、最大熵模型

最大熵原理是概率模型学习中的一个准则。最大熵原理认为,学习概率模型时,在所有可能的概率模型分布中(满足所有条件下),熵最大的模型是最好的模型。熵最大即为最均匀的分布,从某种角度讲均匀分布总是符合我们理解的损失风险最小,也就是“不要不所有的鸡蛋放到一个篮子里,均匀的放置”。

给定训练集:

假设表示输入,表示输出,分类模型是一个以条件概率分布输出,也就是说在满足条件的所有可能集中,条件熵最大的模型即为最好的模型。其中条件为隐藏在数据的期望。

一般来讲,最大熵模型常用于处理离散化数据集,定义随机变量的特征模板,从数据中统计他们的期望作为最大熵模型的条件

特征函数:

和满足某一事实否则

约束条件:对于任意的特征函数,我们可以统计其在数据中的经验分布的期望:

特征函数关于模型和先验的条件期望:

所以,满足约束条件的模型集合为:

因此最大熵模型的形式化表示如下:

由拉格让日乘子法,引入拉格让日乘子,定义拉格让日函数:

根据拉格朗日乘子法,,当且仅当满足拉格朗日乘子法的所有必要条件等式成立,原问题也就是一个最小化最大问题:

里层是最大化,外层的最小化。

对偶问题是:

求解对偶问题,第一步最小化内部:

是关于的函数,最优解记为:

那么外层最大化目标函数为:

为了求解,根据KKT条件对求偏导:

求解得:

这里,虽然我们不知道,但是由于,所以分母一定是对的所有可能的归一化因子:

因此,的最优解为:

代回,我们可以得到最终的分类模型,同样我们发现最大熵模型也是一个对数线性模型。

回顾对偶函数,内部最小化求解得到了,回到外部目标,将代回拉格朗日函数有:

C、概率解释

已知训练集的经验概率分布,条件概率分布的对数似然函数为:

其中,我们发现对数似然函数与条件熵的形式一致,最大熵模型目标函数前面有负号(这与最大化对数似然函数完全相反),同时最大熵模型中有约束条件。也正是因为约束条件,我们将原问题转化为对偶问题后发现,在满足约束条件的对偶函数的极大化等价于最大化对数似然函数。

当条件概率满足约束条件,在对偶问题求解过程中我们有:

代入到对数似然函数,同样有:

最后,我们再来看对偶函数表达式,我们发现,第一项其实是的联合熵,第二项是的信息熵,回看熵的示意图,我们发现,我们的目标还是最大化条件熵。

下面再来对比下Logistic回归,SoftMax回归,最大熵模型:

1)同属于对数线性模型。

2)Logistic回归和SoftMax回归都基于条件概率,满足一个伯努利分布,N重伯努利分布;而最大熵模型以期望为准,没有该假设。

3)由于都采用线性模型,三者都假设特征之间是独立的。

最大熵模型的优化问题:

最大熵模型从拉格朗日乘子法最大化对偶函数,还是从最大化对数似然函数,其目标函数如下:

常用的梯度优化算法都可以,另外对于最大熵模型也有专门的算法有GIS IIS 算法 。

代码实战

A、Logistic回归

代码语言:javascript复制
int LogReg()
{
    const char *file="data\LogReg.txt";
    const string model="gradAscent";
    const double alpha=0.01;
    Matrix x;
    cout<<"loadData"<<endl;
    cout<<"----------------------"<<endl;
    x.LoadData(file);
    Matrix y;
    y=x.getOneCol(x.col-1);
    x.deleteOneCol(x.col-1);

    if(model=="gradAscent")
        gradAscent_Log(x,y);
    if(model=="stoGradAscent")
        stoGradAscent_Log(x,y);

    return 0;
}

  int gradAscent_Log(Matrix x,Matrix y)  
      {
          if(y.col!=1)
          {
              cout<<"logReg is two class"<<endl;
              return -1;
          }
          Matrix weights(x.col,y.col,0.1,"T");
          Matrix xT = x.transposeMatrix();
  
          float alpha=0.01;///迭代步长
          float error=0;///记录错误率
          int iter=0;
          int i,j;
          Matrix z(y.row,y.col,0,"T");//最好确定矩阵的大小
          Matrix grad(x.col,y.col,0,"T");
          for(iter=0; iter<5000; iter  )
          {
              z = x * weights;
              for(i=0; i<z.row; i  )
              {
                  z.data[i][0]=sigmoid(z.data[i][0]);
              }
              z = y - z;
              error=0;
              for(i=0; i<x.row; i  )///统计错误率
                  error =z.data[i][0];
              grad = xT * z;///计算负梯度方向
              for(i=0; i<grad.row; i  )
                  grad.data[i][0]*= alpha;///负梯度方向与步长的乘积确定迭代值
              weights = weights   grad;///往负梯度方向走一个步长
          }
  
          /**
          验证算法的正确性
          **/  
          int er1=0,er2=0;
          Matrix train=x * weights;
          cout<<"test"<<endl;
          for(i=0; i<y.row; i  )
          {
              if(train.data[i][0]>0)
              {
                  cout<<1-y.data[i][0]<<endl;
                  er1 =(1-y.data[i][0]);
              }
              else  
              {
                  cout<<0-y.data[i][0]<<endl;
                  er2-=(0-y.data[i][0]);
              }
          }
      }

B、SoftMax回归

代码语言:javascript复制
int SoftMaxReg()
{
    const char *file="data\LogReg.txt";
    const string model="gradAscent";
    const double alpha=0.01;
    Matrix x;
    cout<<"loadData"<<endl;
    cout<<"----------------------"<<endl;
    x.LoadData(file);
    Matrix y;
    y=x.getOneCol(x.col-1);
    y=one_hot(y,2);
    x.deleteOneCol(x.col-1);

    if(model=="gradAscent")
        gradAscent_SoftMax(x,y);
    if(model=="stoGradAscent")
        stoGradAscent_SoftMax(x,y);

    return 0;
}


  /**
      随机梯度下降与梯度下降法不同的是在负梯度方向的确定,梯度下降是根据所有的样本来确定负梯度方向,
      而随机梯度下降每次只看一个样本点来确定负梯度方向,虽然不完全可信,但随着迭代次数增加,同样收敛
        
 **/  
int stoGradAscent_SoftMax(Matrix x,Matrix y)//随机梯度下降每一次选择m个样本进行求梯度下降方向,该代码中只选择一个样本进行求解梯度下降方向与数值
      {
          Matrix xOneRow(1,x.col,0,"T");
          Matrix xOneRowT(x.col,1,0,"T");
  
          Matrix weights(x.col,y.col,0.1,"T");
          Matrix z(1,y.col,0,"T");//最好确定矩阵的大小
          Matrix grad(x.col,y.col,0,"T");
          double zRowSum=0;
          double alpha=0.001;///步长
          double error;
          int i,j,k,iter;
          for(iter=0; iter<5000; iter  )
          {
              for(i=0; i<x.row; i  )
              {
                  xOneRow=x.getOneRow(i);///随机选择一个样本点,这里没有作随机选择,而是按序选择
                  z = xOneRow * weights;
                  zRowSum=0;
                  for(j=0;j<z.col;j  )
                  {
                      z.data[0][j]=sigmoid(z.data[0][j]);
                      zRowSum =z.data[0][j];//求和
                  }
                  for(j=0;j<z.col;j  )
                  {
                      z.data[0][j]/=zRowSum;//归一化
                      if(iter00==0)
                          cout<<z.data[0][j] <<" s ";
                  }
                  if(iter00==0)
                      cout<<endl;
                  for(j=0;j<y.col;j  )
                  {
                      z.data[0][j]=y.data[i][j]-z.data[0][j];
                  }
                  xOneRowT = xOneRow.transposeMatrix();
                  grad = xOneRowT * z;///根据一样样本的预测误差来确定负梯度方向
                  for(k=0; k<grad.row;k  )
                  {
                      for(j=0;j<grad.col; j  )
                      {
                          grad.data[k][j]*= alpha;///负梯度方向与步长的乘积确定迭代值
                      }
                  }
                  weights = weights   grad; ///迭代
              }
          }
          //验证算法的正确性
          /**
          验证算法的正确性
          **/  
          Matrix test=x * weights;
          cout<<"test"<<endl;
          for(i=0; i<y.row; i  )
          {
              if(test.data[i][0]>test.data[i][1])
                  cout<<0-y.data[i][1]<<" ";
              else  
                  cout<<1-y.data[i][1]<<" ";
              cout<<endl;
          }
      }

0 人点赞