逻辑斯蒂回归(Logistic)

一、线性回归

1、线性回归的概念

  如果特征值之间存在线性关系就可以使用线性回归建模对其预测结果。

(1)函数模型

这里写图片描述

(2)最小二乘法求解

  何为最小二乘法,其实很简单。我们有很多的给定点,这时候我们需要找出一条线去拟合它,那么我先假设这个线的方程,然后把数据点代入假设的方程得到观测值,求使得实际值与观测值相减的平方和最小的参数。对变量求偏导联立便可求。
这里写图片描述
这里写图片描述

  如果矩阵不满秩时,采用梯度下降法求局部最优解。

2、线性回归的应用

这里写图片描述
这里写图片描述

二、逻辑斯蒂回归

  逻辑回归假设数据服从伯努利分布,通过极大化似然函数的方法,运用梯度下降来求解参数,来达到将数据二分类的目的。

1、逻辑斯蒂分布

  logistic回归其实不是线性回归求预测值的问题,而是二分类问题。首先我们的线性回归模型输出的预测值,是一个实际的数字,那么星我们想将他部署到而分类问题,就需要让输出值转换到0/1就可以了。这里引入一个新的函数sigmoid y = 1 1 + e z ,图像是这样的:
这里写图片描述
  此时我们将线性模型产生的预测值带入sigmoid函数,函数会输出相对应的二分类的概率,具体的训练方法和上面的线性回归是一样的,不同的是误差函数的求导

2、二项逻辑斯蒂回归模型

  首先我们来看最简单的二项logstic regression模型,它是由条件概率分布来表示,即:
这里写图片描述
此处引入几个简单的数学概念:
这里写图片描述
可以看到,Y=1的对数几率是输入x的线性函数,同时我们看到:
这里写图片描述
即输出Y的取值概率是由输入x的线性函数决定的的,回归也就体现在这里。

3、逻辑斯蒂模型求解

  模型求解也就是模型参数的估计。因为逻辑斯蒂回归是典型的统计机器学习方法,所以经常用最大似然估计的方法。
这里写图片描述
这里写图片描述

4、逻辑斯蒂算法demo

#include "logistic.h"

//加载数据。
void Logistic::loadDataset(const string &filename)
{
    ifstream file(filename);
    string line;
    while (getline(file, line))
    {
        istringstream record(line);
        vector<double> data;
        data.push_back(1.0);//对向量加以扩充。
        double temp;
        while (record >> temp)
            data.push_back(temp);
        if (filename.find("train") != string::npos)
        {
            trainLabel.push_back(int(temp));
            data.pop_back();
            trainMat.push_back(data);
        }
        else
        {
            testLabel.push_back(int(temp));
            data.pop_back();
            testMat.push_back(data);
        }
    }
}

//初始化权值向量
void Logistic::setweight()
{
    const int train_sz = trainMat[0].size();
    for (int i = 0; i < train_sz; ++i)
        weight.push_back(1);
}

//分布函数(S型曲线)。
double Logistic::sigmoid(double z)
{
    double ret = 1 / (1 + exp(-z));
    return ret;
}

//计算內积(标量积)。
double Logistic::scalarProduct(vector<double> &w, vector<double> &x)
{
    double ret = 0.0;
    for (int i = 0; i < w.size(); ++i)
        ret += w[i] * x[i];
    return ret;
}

//矩阵转置。
vector<vector<double>> Logistic::matTranspose(vector<vector<double>> &dataMat)
{
    vector<vector<double>> ret(dataMat[0].size(), vector<double>(dataMat.size(), 0));
    for (int i = 0; i < ret.size(); ++i)
        for (int j = 0; j < ret[0].size(); ++j)
            ret[i][j] = dataMat[j][i];
    return ret;
}

//梯度上升法求极大值(极小值是随机梯度下降法)。
void Logistic::gradAscent()
{
    int maxCycles = 500;
    double alpha = 0.001;
    vector<vector<double>> dataMatT = matTranspose(trainMat);
    while (maxCycles > 0)
    {
        vector<double> h;
        vector<double> error;
        for (auto &data : trainMat)
            h.push_back(sigmoid(scalarProduct(data, weight)));
        for (int i = 0; i < trainLabel.size(); ++i)
        {
            double dist = trainLabel[i] - h[i];
            if (abs(dist) < 1e-10)
                dist = 0;
            error.push_back(dist);
        }
        //迭代公式。
        for (int i = 0; i < weight.size(); ++i)
        {
            weight[i] += alpha * scalarProduct(dataMatT[i], error);
        }
        maxCycles--;
    }
}

//随机梯度法。
void Logistic::stocGradAscent()
{
    double alpha = 0.01;
    double h = 0.0;
    int i = 0;
    int j = 0;
    double error = 0.0;
    vector<int> randIndex;
    for (i = 0; i < trainMat.size(); ++i)
        randIndex.push_back(i);
    for (int k = 0; k < numIter; ++k)
    {
        //每次迭代过程中,样本都要被随机打乱,这个也很容易理解,
        //打乱是有效减小样本之间造成的参数更新抵消问题。
        random_shuffle(randIndex.begin(), randIndex.end());
        for (i = 0; i < trainMat.size(); ++i)
        {
            //随着迭代的步骤增多,学习率通常要逐渐减小,这样可以有效避免训练中出现误差震荡情况。
            alpha = 4 / (1 + k + i) + 0.01;
            h = sigmoid(scalarProduct(trainMat[randIndex[i]], weight));
            error = trainLabel[randIndex[i]] - h;
            for (j = 0; j < weight.size(); ++j)
            {
                weight[j] += alpha * error * trainMat[randIndex[i]][j];
            }
        }
    }
}

//分类。
int Logistic::classify(vector<double> &data, vector<double> &weights)
{
    //对数几率是线性模型。
    if (scalarProduct(data, weights) > 0.5)
        return 1;
    else
        return 0;
}

//返回错误率。
void Logistic::acc_result()
{
    int accnum = 0;
    double dataSize = testMat.size();
    for (int i = 0; i < dataSize; ++i)
    {
        if (classify(testMat[i], weight) == testLabel[i])
            accnum++;
    }
    cout << "accuracy: " << accnum*100.0 / dataSize << "%" << endl;
    cout << "classification: " << accnum << " / " << dataSize << endl;
}

5、多项逻辑斯蒂回归模型

这里写图片描述

三、逻辑斯蒂回归算法的优缺点及常见问题

1、优点

1. 形式简单,模型的可解释性非常好。从特征的权重可以看到不同的特征对最后结果的影响,某个特征的权重值比较高,那么这个特征最后对结果的影响会比较大;

2. 模型效果不错。在工程上是可以接受的(作为baseline),如果特征工程做的好,效果不会太差,并且特征工程可以大家并行开发,大大加快开发的速度;

3. 训练速度较快。分类的时候,计算量仅仅只和特征的数目相关。并且逻辑回归的分布式优化sgd发展比较成熟,训练的速度可以通过堆机器进一步提高,这样我们可以在短时间内迭代好几个版本的模型;

4. 资源占用小,尤其是内存。因为只需要存储各个维度的特征值;

5. 方便输出结果调整。逻辑回归可以很方便的得到最后的分类结果,因为输出的是每个样本的概率分数,我们可以很容易的对这些概率分数进行cutoff,也就是划分阈值(大于某个阈值的是一类,小于某个阈值的是一类)。

2、缺点

1. 准确率并不是很高。因为形式非常的简单(非常类似线性模型),很难去拟合数据的真实分布;

2. 很难处理数据不平衡的问题。举个例子:如果我们对于一个正负样本非常不平衡的问题比如正负样本比 10000:1.我们把所有样本都预测为正也能使损失函数的值比较小。但是作为一个分类器,它对正负样本的区分能力不会很好;

3. 处理非线性数据较麻烦。逻辑回归在不引入其他方法的情况下,只能处理线性可分的数据,或者进一步说,处理二分类的问题;

4. 逻辑回归本身无法筛选特征。有时候,我们会用gbdt来筛选特征,然后再上逻辑回归。

3、常见问题

(1)逻辑回归的损失函数为什么要使用极大似然函数作为损失函数?

  损失函数一般有四种,平方损失函数,对数损失函数,HingeLoss0-1损失函数,绝对值损失函数。将极大似然函数取对数以后等同于对数损失函数。在逻辑回归这个模型下,对数损失函数的训练求解参数的速度是比较快的。
  因为目标是要让预测为正的的概率最大,且预测为负的概率也最大,即每一个样本预测都要得到最大的概率,将所有的样本预测后的概率进行相乘都最大,这就能到似然函数了。

(2)逻辑回归在训练的过程当中,如果有很多的特征高度相关或者说有一个特征重复了100遍,会造成怎样的影响?

  如果在损失函数最终收敛的情况下,其实就算有很多特征高度相关也不会影响分类器的效果。

(3)为什么我们还是会在训练的过程当中将高度相关的特征去掉?

  去掉高度相关的特征会让模型的可解释性更好,可以大大提高训练的速度。如果模型当中有很多特征高度相关的话,就算损失函数本身收敛了,但实际上参数是没有收敛的,这样会拉低训练的速度。其次是特征多了,本身就会增大训练的时间。

(4)使用L1L2正则化,为什么可以降低模型的复杂度?

  模型越复杂,越容易过拟合,这大家都知道,加上L1正则化给了模型的拉普拉斯先验,加上L2正则化给了模型的高斯先验。从参数的角度来看,L1得到稀疏解,去掉一部分特征降低模型复杂度。L2得到较小的参数,如果参数很大,样本稍微变动一点,值就有很大偏差,这当然不是我们想看到的,相当于降低每个特征的权重

(5)为什么L1能得到稀疏解呢?

  L1正则化是L1范数而来,投到坐标图里面,是棱型的,最优解在坐标轴上取到,所以某些部分的特征的系数就为0。

(6)L1正则化不可导,怎么求解?

  坐标轴下降法(按照每个坐标轴一个个使其收敛),最小角回归(是一个逐步的过程,每一步都选择一个相关性很大的特征,总的运算步数只和特征的数目有关,和训练集的大小无关)

参考:https://blog.csdn.net/fort110/article/details/79370058
https://www.cnblogs.com/GuoJiaSheng/p/3928160.html
https://blog.csdn.net/kesonyk/article/details/46315553
https://blog.csdn.net/lingzidong/article/details/75363350
https://blog.csdn.net/dp_BUPT/article/details/50560789
https://blog.csdn.net/u013044811/article/details/76861529
https://blog.csdn.net/handsome_sheng/article/details/76359565
https://blog.csdn.net/u013044811/article/details/76861529

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/80848958