zoukankan      html  css  js  c++  java
  • Logistic回归

    Logistic回归

    Part I: 线性回归

      线性回归很常见,给你一堆点,作出一条直线,尽可能去拟合这些点。对于多维的数据,设特征为xi,设函数h(θ)=θ+θ1x1+θ2x2+....θnxn为拟合的线性函数,其实就是内积,实际上就是y=wTx+b

    那么如何确定这些θ参数(parament)才能保证拟合比较好呢?

    1.1 使用最小二乘法的目标函数

    我们易有这样的二次目标函数:J=minmi=112(h(θ)yi)2 ,m即所有数据条数。

    该目标函数的意义在于:误差最小化。这就是最小二乘法。

    h(θ) 中的h即是hypothesis(假设的意思),h函数是我们的算出结果,yi是我们的实际结果,平方的作用有两个,一是是代替ABS,二方便求导。

    想要实现目标函数,然后求出θ,最传统的是矩阵方法(最小二乘法拟合最常用的数学方法),θ=(XTX)1XTY

    该式子来自于二维最小二乘法(一次拟合、忽视偏置=就一个方程):θNi=1x2i=Ni=1xiyi

    如果你是使用MATLAB之类的话,那么就是几行代码的事。如果是C/JAVA,还是乖乖使用下面的梯度下降算法(Gradient Descent)。

    值得注意的事,矩阵方法要计算矩阵的逆(慢、而且可能不存在、且编程麻烦),复杂度O(n^3),在数据量较大的时候,效率会被梯度下降算法爆掉。

    1.2  梯度下降算法

    计算机是很笨的。如果想要确定某个值,那么有一种很笨的方法:迭代法。

    设定一个边界初值(最大or最小),然后一小步一小步的去变化这个值并验证,知道最后逼近最优值,停止迭代。

    由于目标函数是最小化,我们有这个古老的算法。

     θnewi=θoldiαθJ(θ)

    至于原理,参照wiki的解释,这样调整θ,会使得目标函数不停的收敛,同时偏导的梯度控制了收敛的速度。

    首先把各个θ设为零,让其从一个平衡状态,在迭代过程中缓慢修改。

    减小的量由alpha(学习率,由数据大小确定,既要防止调整过大,也要防止调整过小)*目标函数J的偏导数构成。

    经验规则表明,对于Tanh/Logistic激活函数,初始学习率在0.1是个不错的选择,如果训练过程中迅速过拟合,或出现NaN,表明学习率过大。

    降一个量级或除以2试试。对于线性激活函数,如果误差波动较大,可以适当的降低量级。

    调整各个θ的同时,目标函数J的计算值也在减小,向局部最小值移动。

    同时梯度的下降速度由疾变缓,在最后的迭代过程中,梯度将会趋于0,从而能够保证较为精确的逼近目标值。这样,既保证了目标函数的最小化,又求出的参数θ。

    1.3 偏导数的处理

    假设只有一条数据。目标函数J的偏导数你可以化简一下。链式法则求导。

     

    最后的求导结果是一个非常神奇的式子:(h(θ)y)xi

    如果是多条数据呢?累加偏导结果即可。

     θnewi=θoldimj=1α(hθ(xj)yj)xji

    由此诞生了三种常用训练梯度算法。

    1.4 三种梯度训练梯度方法

    一、批梯度算法(BGD,Batch Gradient Descent)

    正如上面的式子,先扫每个维度,然后扫全部数据累计θ的变化值

    for(i:维度)

      for(j:数据条数)

          θnewi=θoldimj=1α(h(θ)yj)xji

    每次迭代复杂度O(n^3), 下场是:慢。

    二、随机梯度算法(SGD,Stochastic Gradient Descent)

    它的改良就是,调整循环顺序,每次只取一条数据作为误差,将h(θ)预处理,去求梯度,而不是放在两层循环下变成三层循环去求个总梯度。

    for(j:数据条数)

       calch(θ)

       for(i: 维度)

         θnewi=θoldiα(h(θ)yj)xji

    每次迭代复杂度O(n^2)

    PS. 关于常量b的求法,即对b的偏导*alpha,b=α(h(θ)yj)

    这种算法偷工减了一层for,并且带来了更少的迭代次数(实测,批:50次,随:35次)下场就是,最后的近似值没有批梯度精确,不过够用了。

    三、迷你批梯度算法(mBGD,Mini-Batch Gradient Descent

    深度学习中使用。每次取一小段数据做完全梯度,而不是只用一个。在速度和精度之间做了一个均衡。

    SGD是BatchSize=1的特例,即每次更新只对一个样本误差取梯度。BGD是BatchSize=ExampleSize的特例,每次更新对全部样本取梯度。

    为了平衡计算,mini-batch里把跑完全样本梯度一次称为epoch,跑一个batch称为iter。

    训练方法具体参考:Theano深度学习分析

    1.5 迭代问题

    如何确定迭代的次数?我们大可以设个500,让它一直算下去,然而还是有一些停止技巧。

    ①目标函数收敛判断法(浅层学习)

    迭代停止条件目前分为两个。设精度是0.001

    一、近似达到目标函数的局部最小值,即变化小于0.001

    二、各个参数Θi的变化小于0.001(可选)

    一般达到这两个条件,迭代就可以考虑停止了。

    ②交叉验证&Early-Stopping(深度学习)

    在Deep Learning里,单纯看目标函数是不靠谱的,我们只能根据目标函数下降,来判断学习的有效进行。

    但是我们并不能知道学习情况。尤其是加入L1/L2惩罚后,目标函数的比例会出现问题。

    更严重的是,过拟合情况很难从目标函数中发现。有时候目标函数还在下降,其实已经过拟合很严重了。

    这时候就需要Early-Stopping,所以引入交叉验证手段(需要对训练样本划分验证集),具体参考:Theano深度学习分析

    其中,每次epoch,测一次验证集,根据验证集错误率下降情况,判断过拟合,这是训练深度神经网络的基本功。

    下图是训练一个人脸检测的CNN,加了L2惩罚,似然函数比往常大了一个量级,交叉验证直观反应了训练情况

    1.6 优化:局部加权回归

    拟合时,我们希望离预测点比较远的点分配比较小的权重,而离的比较近的点分配比较大的权重。

    这样,拟合时,拟合的出的线将不是一条单调的直线(容易欠拟合,不能很好适应数据特征),而是一条随数据摆动的曲线。

    这样就带来新的问题:过拟合,即随着数据不停摆动(可以理解成把所有数据点用折线连起来),容易受到噪声数据影响,导致拟合效果奇差。

    因此,局部加权回归比较吃数据,也比较难调整,用不好基本就完蛋,但是某些大师很喜欢用(来自Andrew NG的吐槽)。

    根据距离远近分配权重由高斯核函数(又名径向基核函数)完成。

    f=e∣∣xix∣∣22k2

    距离采用的是欧拉距离(同KNN),k值是这个核函数唯一需要调整的地方。

    通常取值有0.001,0.01,0.1,1,10,k越小,离预测点越近的点越容易分配到比较大的权重,就越容易过拟合。

    加权方法:

    for(每一个预测点)

      for(每条样本数据)

         计算该条数据的加权xnewi=fxoldi

    然后利用加权过后的xnewi进行拟合

    Part Ⅱ  Logistic回归

    Logistic回归中文又叫逻辑回归。它和线性回归的最大区别在于:它将线性回归结果,通过Logistic函数生成概率,从而进行0/1分类。

    尽管Logistic函数是非线性的(S形,平滑),然而它的功能只是生成概率,而不是非线性划分数据。

    所以Logistic回归只对线性可分的数据的效果比较好,对于线性不可分数据,需要使用含有将数据映射到高层空间的,BP神经网络网络或SVM神经网络。

    2.1 概率论与目标函数设计的原理

    已有xji,若求出θ,则yj即可回归算出。这是一个事实。

    那么假如用P(yj|xji;θ)表示拟合出yj的概率,当然这个概率不是说高就高,说低就低的,它近似服从正态(高斯)分布。

    我们的任务是设计很科学的θ,使得利用样本数据造成的概率分布尽可能接近正态分布,也就是说估计出θ的近似值。

    这样的模型称之为判别模型(Discriminate Model),判别模型可以用于分类/回归。

    假设P(yj|xji;θ)服从正态分布,那么就有了概率密度函数,利用概率密度函数,反过来设计一个对θ的似然估计函数L(θ)

    于是最终任务便变成:基于假设下,利用最大似然估计方法求出θ

    由于最大化L(θ)式子与最小二乘法的式子很接近,于是近似认为最大似然方法等效于最小二乘方法,这便是最小二乘法的概率论角度的解释。

    2.2 误差服从二项分布的目标函数

    由于Logistic回归用于二类分类时,y的取值非0,即1。上述的正态分布,则变成二项分布。

    由二项分布,有如下的概率分布。

    P(y=0|x;θ)=h(θ)

    P(y=1|x;θ)=1h(θ)

    合并这两个分布为一个式子P(y|x;θ),即概率分布函数,这里由于y取值的特殊性,所以有以下优美的指数式子(方便取对数似然函数)

    于是有关于y的概率密度函数,取对参数θ的似然估计,就是估计出最准确的θ,累乘概率密度函数。

     

    老样子,log取对数,化累乘为累加

     

    求偏导数。

     

    然后你就会发现这个偏导结果怎么那么耳熟?怎么和那个回归的最小二乘法J函数的偏导结果差不多。

    真是个优美的式子。于是你又可以用梯度下降算法了。

    不同的是,这次你得让似然函数取最大值,也就是说,参数θ初始设为0,然后慢慢增加,直到似然函数取得最大值。这其实是梯度上升算法

    2.3  概率映射Logistic函数——化回归为分类,Logistic回归之本质

    关于分类与回归,不同在于y的取值类型。连续型叫回归,离散型叫分类。当然一般分类指的是二类分类问题。回归的方法固然可以拿来回归分类问题。

    一个简单的方法就是对于最后回归出的连续型y,加上阶跃函数(负0正1),转化为离散型y。这就是60年代盛行的线性神经网络,即Rosenblatt感知器。

    Logistic回归在线性回归+阶跃函数的基础做了改良,对线性回归结果使用了Logistic-Sigmoid函数,这样h(Θ)=Sigmoid(内积)。

     

    Logistic函数值域[0,1],非线性双端饱和,平滑,目前广泛用于概率生成函数 。可以参考:限制Boltzmann机

    有效定义域范围[-3,3],所以要对输入进行进行处理,尽量缩放至[-1,1]。

    有一个非常容易混淆的地方,就是神经网络的Sigmoid隐层激活函数和这里概率生成函数作用是不同的。

    Sigmoid除了有良好的概率生成能力之外,还有非线性加强输入的能力(中央区域强化信号,两侧区域弱化信号)

    在神经网络中会用来做激活函数。可以参考:ReLu激活函数

    2.4 迭代问题

    迭代终止条件最重要的是保证似然函数的近似值最大且收敛。

     

    也就是说,每次迭代时,都要算一算这个似然目标函数的值,不出意外,应该是逐步增长,速度由快到慢,最后收敛于一个值的。注意这里h(Θ)要经过sigmoid函数处理,不然log会爆的。

    2.5  C++代码

    #include "cstdio"
    #include "iostream"
    #include "fstream"
    #include "vector"
    #include "sstream"
    #include "string"
    #include "math.h"
    using namespace std;
    #define N 500
    #define delta 0.0001
    #define alpha 0.1
    #define cin fin
    struct Data
    {
        vector<int> feature;
        int y;
        Data(vector<int> feature,int y):feature(feature),y(y) {}
    };
    struct Parament
    {
        vector<double> w;
        double b;
        Parament() {}
        Parament(vector<double> w,double b):w(w),b(b) {}
    };
    vector<Data> dataSet;
    Parament parament;
    void read()
    {
        ifstream fin("traindata.txt");
        int id,fea,cls,cnt=0;
        string line;
        while(getline(cin,line))
        {
            stringstream sin(line);
            vector<int> feature;
            sin>>id;
            while(sin>>fea) feature.push_back(fea);
            cls=feature.back();feature.pop_back();
            dataSet.push_back(Data(feature,cls));
        }
        parament=Parament(vector<double>(dataSet[0].feature.size(),0.0),0.0);
    }
    double calcInner(Parament param,Data data)
    {
        double ret=0.0;
        for(int i=0;i<data.feature.size();i++) ret+=(param.w[i]*data.feature[i]);
        return ret+param.b;
    }
    double sigmoid(Parament param,Data data)
    {
        double inner=calcInner(param,data);
        return exp(inner)/(1+exp(inner));
    }
    double calcLW(Parament param)
    {
        double ret=0.0;
        for(int i=0;i<dataSet.size();i++)
        {
            double h=sigmoid(param,dataSet[i]);
            ret+=(dataSet[i].y*log(h))+(1-dataSet[i].y)*log(1-h);
        }
        return ret;
    }
    void gradient(Parament &param,int iter)
    {
        /*batch
        for(int i=0;i<param.w.size();i++)
        {
            double ret=0.0;
            for(int j=0;j<dataSet.size();j++)
            {
                double ALPHA=(double)0.1/(iter+j+1)+0.1;
                ret+=ALPHA*(dataSet[j].y-sigmoid(param,dataSet[j]))*dataSet[j].feature[i];
            }
            param.w[i]+=ret;
        }
        for(int i=0;i<dataSet.size();i++) ret+=alpha*(dataSet[i].y-sigmoid(param,dataSet[i]));
        */
        //random
        for(int j=0;j<dataSet.size();j++)
        {
            double ret=0.0,h=sigmoid(param,dataSet[j]);
            double ALPHA=(double)0.1/(iter+j+1)+0.1;
            for(int i=0;i<param.w.size();i++)
               param.w[i]+=ALPHA*(dataSet[j].y-h)*dataSet[j].feature[i];
            param.b+=alpha*(dataSet[j].y-h);
        }
    }
    bool samewb(Parament p1,Parament p2)
    {
        for(int i=0;i<p1.w.size();i++)
             if(fabs(p2.w[i]-p1.w[i])>delta) return false;
        if(fabs(p2.b-p1.b)>delta) return false;
        return true;
    }
    void classify()
    {
        ifstream fin("testdata.txt");
        int id,fea,cls;
        string line;
        while(getline(cin,line))
        {
            stringstream sin(line);
            vector<int> feature;
            sin>>id;
            while(sin>>fea) feature.push_back(fea);
            cls=feature.back();feature.pop_back();
            double p1=sigmoid(parament,Data(feature,cls)),p0=1-p1;
            cout<<"id:"<<id<<"  origin:"<<cls<<" classify:";
            if(p1>=p0) cout<<" 1"<<endl;
            else cout<<" 0"<<endl;
        }
    }
    void mainProcess()
    {
        double objLW=calcLW(parament),newLW;
        Parament old=parament;
        int iter=0;
        gradient(parament,iter);
        newLW=calcLW(parament);
        while(fabs(newLW-objLW)>delta||!samewb(old,parament))
        {
            objLW=newLW;
            old=parament;
            gradient(parament,iter);
            newLW=calcLW(parament);
            iter++;
            if(iter%5==0) cout<<"iter: "<<iter<<"  target value: "<<newLW<<endl;
        }
        cout<<endl<<endl;
    }
    int main()
    {
        read();
        mainProcess();
        classify();
    }

    复制代码
    #include "cstdio"
    #include "iostream"
    #include "fstream"
    #include "vector"
    #include "sstream"
    #include "string"
    #include "math.h"
    using namespace std;
    #define N 500
    #define delta 0.0001
    #define alpha 0.1
    #define cin fin
    struct Data
    {
        vector<int> feature;
        int y;
        Data(vector<int> feature,int y):feature(feature),y(y) {}
    };
    struct Parament
    {
        vector<double> w;
        double b;
        Parament() {}
        Parament(vector<double> w,double b):w(w),b(b) {}
    };
    vector<Data> dataSet;
    Parament parament;
    void read()
    {
        ifstream fin("traindata.txt");
        int id,fea,cls,cnt=0;
        string line;
        while(getline(cin,line))
        {
            stringstream sin(line);
            vector<int> feature;
            sin>>id;
            while(sin>>fea) feature.push_back(fea);
            cls=feature.back();feature.pop_back();
            dataSet.push_back(Data(feature,cls));
        }
        parament=Parament(vector<double>(dataSet[0].feature.size(),0.0),0.0);
    }
    double calcInner(Parament param,Data data)
    {
        double ret=0.0;
        for(int i=0;i<data.feature.size();i++) ret+=(param.w[i]*data.feature[i]);
        return ret+param.b;
    }
    double sigmoid(Parament param,Data data)
    {
        double inner=calcInner(param,data);
        return exp(inner)/(1+exp(inner));
    }
    double calcLW(Parament param)
    {
        double ret=0.0;
        for(int i=0;i<dataSet.size();i++)
        {
            double h=sigmoid(param,dataSet[i]);
            ret+=(dataSet[i].y*log(h))+(1-dataSet[i].y)*log(1-h);
        }
        return ret;
    }
    void gradient(Parament &param,int iter)
    {
        /*batch
        for(int i=0;i<param.w.size();i++)
        {
            double ret=0.0;
            for(int j=0;j<dataSet.size();j++)
            {
                double ALPHA=(double)0.1/(iter+j+1)+0.1;
                ret+=ALPHA*(dataSet[j].y-sigmoid(param,dataSet[j]))*dataSet[j].feature[i];
            }
            param.w[i]+=ret;
        }
        for(int i=0;i<dataSet.size();i++) ret+=alpha*(dataSet[i].y-sigmoid(param,dataSet[i]));
        */
        //random
        for(int j=0;j<dataSet.size();j++)
        {
            double ret=0.0,h=sigmoid(param,dataSet[j]);
            double ALPHA=(double)0.1/(iter+j+1)+0.1;
            for(int i=0;i<param.w.size();i++)
               param.w[i]+=ALPHA*(dataSet[j].y-h)*dataSet[j].feature[i];
            param.b+=alpha*(dataSet[j].y-h);
        }
    }
    bool samewb(Parament p1,Parament p2)
    {
        for(int i=0;i<p1.w.size();i++)
             if(fabs(p2.w[i]-p1.w[i])>delta) return false;
        if(fabs(p2.b-p1.b)>delta) return false;
        return true;
    }
    void classify()
    {
        ifstream fin("testdata.txt");
        int id,fea,cls;
        string line;
        while(getline(cin,line))
        {
            stringstream sin(line);
            vector<int> feature;
            sin>>id;
            while(sin>>fea) feature.push_back(fea);
            cls=feature.back();feature.pop_back();
            double p1=sigmoid(parament,Data(feature,cls)),p0=1-p1;
            cout<<"id:"<<id<<"  origin:"<<cls<<" classify:";
            if(p1>=p0) cout<<" 1"<<endl;
            else cout<<" 0"<<endl;
        }
    }
    void mainProcess()
    {
        double objLW=calcLW(parament),newLW;
        Parament old=parament;
        int iter=0;
        gradient(parament,iter);
        newLW=calcLW(parament);
        while(fabs(newLW-objLW)>delta||!samewb(old,parament))
        {
            objLW=newLW;
            old=parament;
            gradient(parament,iter);
            newLW=calcLW(parament);
            iter++;
            if(iter%5==0) cout<<"iter: "<<iter<<"  target value: "<<newLW<<endl;
        }
        cout<<endl<<endl;
    }
    int main()
    {
        read();
        mainProcess();
        classify();
    }
  • 相关阅读:
    NYoj 素数环(深搜入门)
    深搜和广搜
    hdu 3449 (有依赖的01背包)
    hdu 1712 (分组背包入门)
    sql数据库常用语句总结
    常用工具和API的网站收集
    23种设计模式
    sql 联合查询并更新
    sql 去除重复记录
    读<你必须知道的.NET>IL指令笔记
  • 原文地址:https://www.cnblogs.com/yymn/p/4589578.html
Copyright © 2011-2022 走看看