【机器学习基础】逻辑回归——LogisticRegression

LR算法作为一种比较经典的分类算法,在实际应用和面试中经常受到青睐,虽然在理论方面不是特别复杂,但LR所牵涉的知识点还是比较多的,同时与概率生成模型、神经网络都有着一定的联系,本节就针对这一算法及其所涉及的知识进行详细的回顾。


LogisticRegression

0.前言

  LR是一种经典的成熟算法,在理论方面比较简单,很多资料也有详细的解释和推导,但回过头再看LR算法会有很多全新的认识,本节就从LR的引入到原理推导以及其与神经网络的有何联系串联起来,可以加深对这方面知识的理解。本节首先从概率生成模型引入LR,然后基于二分类推导LR的算法过程,再介绍LR在多分类中的应用,最后介绍LR与神经网络的联系,并通过一些实例对LR进行实现。

1.LR简介

  LR回归是将样本进行线性叠加之后,再通过sigmoid函数,来对样本进行分类的过程,sigmoid函数如下:

   其图形如下:

  可以看到当z>0时,通过sigmoid函数后,σ(z)>0.5,z<0时,σ(z)<0.5;

  那么LR的具体做法就是:

  (1)找到一组参数w、b,求得样本的线性叠加z=wx+b;

  (2)然后将z通过sigmoid函数,如果>0.5,则属于类别1,否则属于类别2;

  然后其训练过程就是寻找参数w、b的过程,后面再说如何进行训练。

2.从概率模型到LogisticRegression

  前面有一节有关概率模型专题,详见//www.cnblogs.com/501731wyb/p/15149456.html,通过假设样本分布,利用最大似然估计来估计样本分布参数,从而求解给定一个样本属于C1和C2的概率,来进行分类的过程,记给定一个未知样本X,属于C1的概率为P(C1|X),那么,根据贝叶斯公式:

  对上面的公式变形,上下同除以分子,得到:

  则有:

  这里注意z的“分子”和“分母”与原式中正好相反,因为有“-”号。

  通过上面LR的简介或者熟悉LR的形式,就可以看出这里的所得到的概率形式与LR及其相似,不同的是,概率形式中的z与原LR的形式wx+b不太一样。

  仅仅上面的变形就说二者有关系稍微有点肤浅,那么究竟一样不一样呢?我们继续往下看:

  根据概率模型那一章节,假设样本C1、C2均服从高斯分布,均值分别为μ1、μ2,方差分别为Σ1和Σ2,根据高斯分布的概率密度函数,那么带入上面z中的P(X|C1)和P(X|C2),则有:

   将上面的式子进行化简并展开:

  在概率模型中训练过程中,我们假设两个类别样本分布的方差Σ1和Σ2共用一个Σ,那么上面的式子可以进一步化简:

  最终得到的z就是上面的式子,由于参数μ1、μ2、Σ是根据样本估计出来的参数,可以看做已知的,那么最终得到的z的形式如下:

  那么合并掉常数项,我们可以看到,z就变成了wx+b的形式,也就是说:

  到这里我们就可以清楚地理解了概率生成模型与LR之间的联系,但虽然形式一样,但还是有区别的,到后面我们再进一步讨论。

3.LogisticRegression算法推导

  根据上面的理论,我们希望通过训练数据,找到一组参数w、b,然后能够给定一个未知数据x,通过计算z=wx+b,在通过sigmoid函数,来对x进行正确预测。这里我们为了便于表示,先假设一下σ(z)为函数的形式,即:

  到这里完成了机器学习的三步走中的第一步:找出模型的形式a set of model。

  那么在学习之前,我们需要找出一个衡量模型好坏的标准,即损失函数,到了第二步goodness of function:

  我们希望所有的样本都能被正确分类,即所有样本累积概率最大,类比于概率生成模型,即:

  使上面这个式子最大,注意,这里x3为(1-f(x)),是因为上面我们所求的σ(z)=fw,b(x)是样本x属于Class1的概率,假设是二分类的情况,那么x属于Class2的概率为1-f(x)。

  然后我们通过求加使得上式最大的一组w、b的参数就结束了,也就是:

  那么这个式子具体怎么求呢?同样用到梯度下降,但是上面直接求导不是很容易,要先对上面的式子进行一下转化,令:

  然后两边同取对数,并且由于梯度下降是求最小值,那么两边同取负号:

  那么问题就变成了:

  然而上面的式子还是有点不太直观,根据二分类的特性,label值在属于Class1时取1,在属于Class2时取0,那么:

  于是,L中的每一项我们都可以写成一个通用的形式,成为了:

  这就是LR的损失函数,也称之为交叉熵CrossEntropy,不过上面是0~1分布,形式上稍微有些不同,可以看成有两个分布,分别为:

  那么根据交叉熵的公式:

  就得到了Lr的交叉损失函数。

  接下来就是利用梯度下降对损失函数进行求解的过程了,也就是第三步:find the best function。

  这个损失函数稍微有点复杂,涉及到链式求导法则,我们一块一块来求导数:

  然后对两块复杂的地方进行求导:

   然后再带入上面的式子中进行整理:

  带上前面的加和,并带上负号,规范一下:

  对b的求导也是一样的,不过在后面没有乘以x罢了,那么参数更新就是:

  到这里是不是发现一个问题,仔细观察w的参数更新过程,对比Linear Regression的参数更新过程,可以看到参数的更新方程是一模一样的。

  那为什么在LR中不能直接用均方误差MSE作为损失函数呢?

  这里我们假设使用均方误差函数作为损失函数,那么损失函数为:

   对w进行求导:

  如果对于一个样本,该样本属于类别2,那么y=0,如果更新到一组参数w、b,计算得到fw,b(x)=1,那么此时预测该样本属于类别1,但带入梯度中发现此时梯度已经为0,参数将不再更新。

  就像是下面这张图:

  均方误差损失函数对于分类问题在距离Loss最小的地方也会很平坦,此时很可能陷入鞍点,无法继续更新,导致最终误差很大。

4.生成模型与判别模型

  上面在从概率生成模型引入LR的过程中说到,虽然两者形式上是一样的,但其实还是有较大差别的。

  根据二者的推导过程我们可以看到,在概率生成模型中,我们通过求解出样本的分布参数作为训练过程,即概率生成模型首先估计样本是个什么样的分布,然后再对这个分布的参数进行估计,换句话说,假设    我们确定了样本所属的分布,那么这个分布里的数据都可以是训练数据,所多出来的数据并不影响最终的分布,相当于生成模型自己“脑补”生成出来了一些额外的数据,这种方式称之为生成模型。

  而LR则是通过梯度下降的方式,直接求解出参数,这种方式称之为判别模型,其始终按照真实数据进行训练。

  下面通过一个小例子来说明,假设有一组训练数据如下:

  数据有两个维度,根据这样一组数据,对测试数据进行预测:

  那么利用生成模型和判别模型分别预测的结果是什么呢?

  首先看生成模型,利用朴素贝叶斯进行计算:

  可以看出,利用生成模型计算得到的X属于Class2,而用判别模型因为X与唯一的属于Class1的训练样本一模一样,因此预测结果就是Class1。

  从这里我们可以看出,生成模型在训练时会脑补出一些数据,可能出现一些训练集从来没出现的数据,比如(1,1)。

  但通常由于判别模型解释性强,我们更倾向于相信判别模型,而生成模型是以概率假设为基础的,通常需要更少的训练数据,并且所得到的结果更具有鲁棒性。

5.多分类问题

  上面所说的LogisticRegression是针对二分类问题的,对于多分类后面作为单独的一节进行论述,这里主要说与LR回归类似的多分类回归Softmax回归。

  Softmax回归类似于LogisticRegression,将数据通过w、b线性叠加后,结合softmax函数,即可以实现将其表达为每种类别的概率的形式,softmax函数如下:

  那么上面softmax回归的过程描述如下:

  假设样本有3个类别C1、C2、C3,那么:

  然后计算得到的z1、z2、z3通过softmax函数,计算各个类别的概率,过程如下:

   那么softmax的训练过程就可以类比LogisticRegression,过程如下:

  这里的交叉熵比LR的形式上更简单,因为可以将类别y表示成三维:Class1:[1,0,0]、Class2:[0,1,0],Class3:[0,0,1]。具体过程就不再推导了。

6.LogisticRegression与神经网络联系

  文章开头提到,LR回归与神经网络有一定的联系,具体有着什么样的关系呢?

  LR对于一组二维的数据,我们通过w、b的线性加权后,再通过sigmoid的函数将其进行分类:

  那今天如果有下图这样一组数据,我们是否能够找到一个线性模型,将这两类数据分开呢?

  如果单纯直接对数据进行分类,可能线性模型将不能将其分开,这时,我们可能需要找一些方法,将数据进行转换和重构,然后再进行分类。

  那如果我将上面的二维数据进行转换,两个维度分别表示该点距离(0,0)的距离和距离(1,1)的距离,那么左下角(0,0)的点可以转化为:

    x1=0;(0,0)距离(0,0)的距离:

    x2=√2;(0,0)距离(1,1)的距离;

  那么点(0,0)就转换成为了(0,√2),同理(1,1)转换成为了(√2,0),(0,1)转换成为了(1,1),(1,0)转换成了(1,1),转换后的数据如图:

 

  这样转换后就能通过一条直线将数据分开了,具体的转换方法有很多,比如SVM中的核函数、或者专业领域知识。

  上面的过程就是特征转换后再经分类器对样本分类,假设上面这一组数据经过线性的加权,然后再经过sigmoid函数,对原始数据进行转换,然后再作为LR的输入在进行分类,其过程如图所示:

   举个例子,比如w1=-2,w2=2,b=-1,四个点的都是二维的,每个维度转换后的如图所示:

                      

   然后再将x1’,x2’作为输入,利用LogisticRegression进行分类,如图所示:

                    

  了解神经网络的应该到这里就可以看出来,上面的结构就是神经网络的结构,前半部分是特征提取部分,后半部分是分类的过程。如图所示:

 

  那么里边涉及到的参数w1、w2、b以及LR中的参数w3、w4、b2在神经网络中通过训练数据,可以一起被学习。

到这里LR的理论部分已经介绍完毕了,主要将其与概率生成模型和神经网络联系起来,下面主要对Lr涉及的主函数进行实现一下,然后利用数据集,采用Sklearn自带的LR方法进行实现。

 7.LogisticRegression实现

  数据来源于李宏毅老师的homework,数据下载地址://pan.baidu.com/s/1r07WmyRceBrXvEEKQ_3a-Q,提取码:t7pp

  数据描述的是不同属性人群的收入情况,feature是人物属性,label为收入情况,首先要对数据集进行转换,将非数值型数据都转换为数值型,然后数据分为训练集和测试集:

import numpy as npfrom sklearn.preprocessing import LabelEncoder
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression

data = pd.read_csv('./train.csv', encoding='utf-8')
data = data[data['native_country'] != ' ?']
none_scalar = ['workclass', 'education', 'marital_status', 'occupation', 'relationship', 'race', 'sex', 'native_country']
label_encoder = LabelEncoder()
income = label_encoder.fit_transform(np.array(list(data['income'])))
data['income'] = income
onehot_encoded_dic = {}
for key in none_scalar:
    temp_data = np.array(list(data[key]))
    label_encoder = LabelEncoder()
    integer_encoded = label_encoder.fit_transform(temp_data)
    onehot_encoded_dic[key] = integer_encoded

feature_list = data.columns.to_list()

data_set = []
for i in range(len(data)):
    temp_vector = []
    for j in range(len(feature_list)):
        if feature_list[j] in onehot_encoded_dic.keys():
            temp_vector.extend(onehot_encoded_dic[feature_list[j]][i])
        else:
            temp_vector.append(data.iloc[i, j])
    data_set.append(temp_vector)

data_set = np.array(data_set)
np.random.shuffle(data_set)

trainX, testX, trainY, testY = train_test_split(data_set[:, :-1], data_set[:, -1], test_size=0.3)

   然后先定义两个辅助函数,一个sigmoid函数,另一个是训练过程中用于将数据打散,便于利用batch_size梯度下降算法取数据:

def _shuffle(trainx, trainy):
    randomlist = np.arange(np.shape(trainx)[0])
    np.random.shuffle(randomlist)
    return trainx[randomlist], trainy[randomlist]

def sigmoid(z):
    res = 1/(1 + np.exp(-z))
    return np.clip(res, 1e-8, (1-(1e-8)))

  接下来就是训练的主函数,因为lr算法比较简单,所以同训练部分写在一起:

def train(trainx, trainy):
    # 这里将b也放进了w中,便于求解,只需要求解w的梯度即可
    trainx = np.concatenate((np.ones((np.shape(trainx)[0], 1)), trainx), axis=1)
    epoch = 300
    batch_size = 32
    lr = 0.001
    w = np.zeros((np.shape(trainx)[1]))
    step_num = int(np.floor(len(trainx)/batch_size))
    cost_list = []
    for i in range(1, epoch):
        train_x, train_y = _shuffle(trainx, trainy)
        total_loss = 0.0
        for j in range(1, step_num):
            x = train_x[j*batch_size:(j+1)*batch_size, :]
            y = train_y[j*batch_size:(j+1)*batch_size]
            y_ = sigmoid(np.dot(x, w))
            loss = np.squeeze(y_) - y
            cross_entropy = -1 * (np.dot(y, np.log(y_)) + np.dot((1-y), np.log(1-y_)))/len(x)
            grad = np.dot(x.T, loss)/len(x)
            w = w - lr * grad

            total_loss += cross_entropy
        cost_list.append(total_loss)
        print("epoch:", i)
    return w, cost_list

  最后,写一个计算错误率的函数,用于比较模型预测结果与原数据的差距有多大:

def evaluation2(X, Y, w):
    X = np.concatenate((np.ones((np.shape(X)[0], 1)), X), axis=1)
    y = np.dot(X, w)
    y = np.array([1 if example > 0.5 else 0 for example in list(y)])
    error_count = 0
    for i in range(len(y)):
        if y[i] != Y[i]:
            error_count += 1

    error_rate = error_count/len(y)
    return error_rate

  然后,就可以直接进行训练了:

w, cost_list = train(trainX, trainY)
error_rate2 = evaluation2(trainX, trainY, w)
print('训练集上的错误率为:', error_rate2)
print('测试集上的错误率为:', evaluation2(testX, testY, w))


训练集上的错误率为: 0.217566118656183
测试集上的错误率为: 0.2140921409214092

  然后是用sklearn中LogisticRegression方法对上面的过程进行实现,其实很简单,这里就建立一个默认参数模型,稍后说明LogisticRegression中各个参数:

model = LogisticRegression()
model.fit(trainX, trainY)
print('训练集上的错误率为:', model.score(trainX, trainY))
print('训练集上的错误率为:', model.score(testX, testY))


训练集上的错误率为: 0.7915475339528234
训练集上的错误率为: 0.7883051907442151

  下面就说一下LogisticRegression中的参数:LR的参数官方文档//scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html

“max_iter”:最大迭代次数;

“penalty”:惩罚项,即正则项,默认为L2正则,可选有L1正则,一般来说L2正则化就够了,但如果还是会过拟合,可选择L1正则,或者样本特征维度较大,希望归零一些不重要的特征,也可以选择L1正则;

“solver”:优化方法,默认为‘lgfgs’,即一种拟牛顿法,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数;此外还有:

        ‘liblinear’,坐标轴梯度下降方法来优化损失函数;数据量较小时可以选择这种方法;

        ‘newton-cg’:也是一种牛顿法中的一种;

        ‘sag’:随机平均梯度下降,是‘sgd’的一种随机梯度下降算法的加速版本,sag其实每次计算时,利用了两个梯度的值,一个是前一次迭代的梯度值,另一个是新的梯度值。当然这两个梯度值都只是随机                       选取一个样本来计算。当数据量很大时可以选择这种方法;

        这里要说明的是,penalty选择了l1后,在solver中就只能用liblinear这个优化方法了,因为L1正则化没有连续导数。

        solver的官网文档如下:

     总结而言,liblinear支持L1和L2,只支持OvR做多分类,“lbfgs”, “sag” “newton-cg”只支持L2,支持OvR和MvM做多分类。

multi-class:该参数是分类的方式,有两个值可以选择:‘ovr’和‘multinomial’,默认为‘ovr’,

      ‘ovr’即一对多的分类,后面会对多分类做一个总结说到这种方法,具体做法是,对于第K类的分类决策,我们把所有第K类的样本作为正例,除了第K类样本以外的所有样本都作为负例,然后在上面                          做二元逻辑回归,得到第K类的分类模型。

      ‘mvm’:如果模型有T类,我们每次在所有的T类样本里面选择两类样本出来,不妨记为T1类和T2类,把所有的输出为T1和T2的样本放在一起,把T1作为正例,T2作为负例,进行二元逻辑回归,得到                        模型参数。我们一共需要T(T-1)/2次分类。

class-weight:样本的类别权重,在分类模型中,经常会遇到两种问题,第一种是误分类的代价很大,就比如合法用户和非法用户,宁愿将合法用户错分到非法用户,然后再通过人工甄别,好过将非法用户误分到                         合法用户类别中,这时可以适当提高非法用户的权重,class_weight={0:0.9, 1: 0.1}.

      另一种是样本严重失衡,比如病例检测中,患病人数远小于不患病人数,假设有10000人检查,患病人数只有0.5%,若不考虑权重,那么准确率也有99.5%,但这样的模型显然没有意义,这时可以                         选择balanced,让类库自动提升患病样本的权重。

sample_weight:对于样本的不均衡,一种方法是上面class_weight设置balanced,另一种就是在fit的时候通过设置每个样本的权重来调整样本权重,在scikit-learn做逻辑回归时,如果上面两种方法都用到了,那                              么样本的真正权重是class_weight*sample_weight。

dual: 对偶方法还是原始方法,默认为False,对偶方法只在求解线性多核的L2正则惩罚中使用,当样本数量>样本特征的时候,dual通常设置为False。

  LogisticRegression中的主要参数就是这些,还有一些其他的参数可以看官方文档中说明。

  对上面的数据集,调整一下使用L1惩罚和solver改为liblinear,正确率略有提升最终结果为:

训练集上的错误率为: 0.8259471050750536
测试集上的错误率为: 0.8217636022514071

 

另外,最近看到一个问题:“比较一下LR模型和GBDT的差别,什么情况下GBDT不如LR模型?”在这里顺便做个总结:

GBDT与LR的区别:

①LR是线性模型,而GBDT是多个弱分类器叠加一起的非线性模型;因此有时为了增强模型的非线性表达能力,通常使用LR模型时,需要做大量的特征工程的工作;

②LR是单模型,而GBDT是集成模型,对于低噪声的数据,GBDT往往比LR的效果更好;

③LR采用梯度下降的等方式进行训练,往往需要对特征进行归一化处理,而GBDT不需要做特征的归一化。

GBDT不如LR的地方:

①LR的解释能力更强,当对模型需要解释时,GBDT往往更加“黑盒”,因为不可能去解释每一棵树,而LR的特征权重能够更直观的看出每个特征的贡献大小,因此LR更具有说服力和营销策略;

②LR的大规模并行训练已经非常成熟,模型迭代和训练速度很快,而GBDT是一种串行的方式,在数据量较大时训练速度较慢;

③对于高维稀疏矩阵,GBDT往往很容易过拟合,因为这些无用信息,在进行决策树训练时,会导致树变得很深,因此会容易过拟合,而LR在训练时可以加入正则化,从而降低特征的权重(L2正则),或者删除不重要的特征(L1正则),从而防止过拟合。

 


 有关逻辑回归的内容到这里就结束了,练习的时候这里说了LR与神经网络的关系,后面就初步回顾一下神经网络有关内容。