您的位置:首页 > 其它

逻辑回归模型介绍和程序实现

2017-04-23 10:50 483 查看

逻辑回归原理及实现

   虽然叫做“回归”,但是这个算法是用来解决分类问题的。回归与分类的区别在于:回归所预测的目标量的取值是连续的(例如房屋的价格);而分类所预测的目标变量的取值是离散的(例如判断邮件是否为垃圾邮件)。当然,为了便于理解,我们从二值分类(binary classification)开始,在这类分类问题中,y只能取0或1。更好的理解问题,先举个小例子:假如我们要制作一个垃圾邮件过滤系统,如果一封邮件是垃圾系统,y=1,否则y=0 。给定训练样本集,当然它们的特征和label都已知,我们就是要训练一个分类器,将它们分开。

         回归分析用来描述自变量x和因变量Y之间的关系,或者说自变量X对因变量Y的影响程度,并对因变量Y进行预测。其中因变量是我们希望获得的结果,自变量是影响结果的潜在因素,自变量可以有一个,也可以有多个。一个自变量的叫做一元回归分析,超过一个自变量的叫做多元回归分析。下面是一组广告费用和曝光次数的数据,费用和曝光次数一一对应。其中曝光次数是我们希望知道的结果,费用是影响曝光次数的因素,我们将费用设置为自变量X,将曝光次数设置为因变量Y,通过一元线性回归方程和判定系数可以发现费用(X)对曝光次数(Y)的影响。

1、 逻辑回归模型

    回归是一种极易理解的模型,就相当于y=f(x),表明自变量x与因变量y的关系。最常见问题有如医生治病时的望、闻、问、切,之后判定病人是否生病或生了什么病,其中的望闻问切就是获取自变量x,即特征数据,判断是否生病就相当于获取因变量y,即预测分类。

    最简单的回归是线性回归,在此借用Andrew NG的讲义,有如图1.a所示,X为数据点——肿瘤的大小,Y为观测值——是否是恶性肿瘤。通过构建线性回归模型,如hθ(x)所示,构建线性回归模型后,即可以根据肿瘤大小,预测是否为恶性肿瘤hθ(x)≥.05为恶性,hθ(x)<0.5为良性。



图1 线性回归示例
    然而线性回归的鲁棒性很差,例如在图1.b的数据集上建立回归,因最右边噪点的存在,使回归模型在训练集上表现都很差。这主要是由于线性回归在整个实数域内敏感度一致,而分类范围,需要在[0,1]。逻辑回归就是一种减小预测范围,将预测值限定为[0,1]间的一种回归模型,其回归方程与回归曲线如图2所示。逻辑曲线在z=0时,十分敏感,在z>>0或z<<0处,都不敏感,将预测值限定为(0,1)。



图2 逻辑方程与逻辑曲线
    逻辑回归其实仅为在线性回归的基础上,套用了一个逻辑函数,但也就由于这个逻辑函数,逻辑回归成为了机器学习领域一颗耀眼的明星,更是计算广告学的核心。对于多元逻辑回归,可用如下公式似合分类,其中公式(4)的变换,将在逻辑回归模型参数估计时,化简公式带来很多益处,y={0,1}为分类结果。 



    对于训练数据集,特征数据x={x1, x2, … , xm}和对应的分类数据y={y1, y2, … , ym}。构建逻辑回归模型f(θ),最典型的构建方法便是应用极大似然估计。首先,对于单个样本,其后验概率为:


   

那么,极大似然函数为:


log似然是:



2、 梯度下降

    由第1节可知,求逻辑回归模型f(θ),等价于:


   

采用梯度下降法:


     从而迭代θ至收敛即可:



 

 

2.1 梯度下降算法

梯度下降算法的伪代码如下:

################################################

初始化回归系数为1

重复下面步骤直到收敛{

        计算整个数据集的梯度

        使用alpha x gradient来更新回归系数

}

返回回归系数值

################################################

      注:因为本文中是求解的Logit回归的代价函数是似然函数,需要最大化似然函数。所以我们要用的是梯度上升算法。但因为其和梯度下降的原理是一样的,只是一个是找最大值,一个是找最小值。找最大值的方向就是梯度的方向,最小值的方向就是梯度的负方向。不影响我们的说明,所以当时自己就忘了改过来了,谢谢评论下面@wxltt的指出。另外,最大似然可以通过取负对数,转化为求最小值。代码里面的注释也是有误的,写的代码是梯度上升,注销成了梯度下降,对大家造成的不便,希望大家海涵。

 

2.2 随机梯度下降SGD (stochastic gradient descent)

      梯度下降算法在每次更新回归系数的时候都需要遍历整个数据集(计算整个数据集的回归误差),该方法对小数据集尚可。但当遇到有数十亿样本和成千上万的特征时,就有点力不从心了,它的计算复杂度太高。改进的方法是一次仅用一个样本点(的回归误差)来更新回归系数。这个方法叫随机梯度下降算法。由于可以在新的样本到来的时候对分类器进行增量的更新(假设我们已经在数据库A上训练好一个分类器h了,那新来一个样本x。对非增量学习算法来说,我们需要把x和数据库A混在一起,组成新的数据库B,再重新训练新的分类器。但对增量学习算法,我们只需要用新样本x来更新已有分类器h的参数即可),所以它属于在线学习算法。与在线学习相对应,一次处理整个数据集的叫“批处理”。

        随机梯度下降算法的伪代码如下:

###############################################

初始化回归系数为1

重复下面步骤直到收敛{

        对数据集中每个样本

               计算该样本的梯度

                使用alpha xgradient来更新回归系数

 }

返回回归系数值

###############################################

 

2.3 改进的随机梯度下降

  1)在每次迭代时,调整更新步长alpha的值。随着迭代的进行,alpha越来越小,这会缓解系数的高频波动(也就是每次迭代系数改变得太大,跳的跨度太大)。当然了,为了避免alpha随着迭代不断减小到接近于0(这时候,系数几乎没有调整,那么迭代也没有意义了),我们约束alpha一定大于一个稍微大点的常数项,具体见代码。

  2)每次迭代,改变样本的优化顺序。也就是随机选择样本来更新回归系数。这样做可以减少周期性的波动,因为样本顺序的改变,使得每次迭代不再形成周期性。

 改进的随机梯度下降算法的伪代码如下:

################################################

初始化回归系数为1

重复下面步骤直到收敛{

       对随机遍历的数据集中的每个样本

              随着迭代的逐渐进行,减小alpha的值

              计算该样本的梯度

              使用alpha x gradient来更新回归系数

    }

返回回归系数值

#################################################

 

3、Python实现

训练数据:



1 -0.017612    14.053064    0
2 -1.395634    4.662541    1
3 -0.752157    6.538620    0
4 -1.322371    7.152853    0
5 0.423363    11.054677    0
6 0.406704    7.067335    1
7 0.667394    12.741452    0
8 -2.460150    6.866805    1
9 0.569411    9.548755    0
10 -0.026632    10.427743    0
11 0.850433    6.920334    1
12 1.347183    13.175500    0
13 1.176813    3.167020    1
14 -1.781871    9.097953    0
15 -0.566606    5.749003    1
16 0.931635    1.589505    1
17 -0.024205    6.151823    1
18 -0.036453    2.690988    1
19 -0.196949    0.444165    1
20 1.014459    5.754399    1
21 1.985298    3.230619    1
22 -1.693453    -0.557540    1
23 -0.576525    11.778922    0
24 -0.346811    -1.678730    1
25 -2.124484    2.672471    1
26 1.217916    9.597015    0
27 -0.733928    9.098687    0
28 -3.642001    -1.618087    1
29 0.315985    3.523953    1
30 1.416614    9.619232    0
31 -0.386323    3.989286    1
32 0.556921    8.294984    1
33 1.224863    11.587360    0
34 -1.347803    -2.406051    1
35 1.196604    4.951851    1
36 0.275221    9.543647    0
37 0.470575    9.332488    0
38 -1.889567    9.542662    0
39 -1.527893    12.150579    0
40 -1.185247    11.309318    0
41 -0.445678    3.297303    1
42 1.042222    6.105155    1
43 -0.618787    10.320986    0
44 1.152083    0.548467    1
45 0.828534    2.676045    1
46 -1.237728    10.549033    0
47 -0.683565    -2.166125    1
48 0.229456    5.921938    1
49 -0.959885    11.555336    0
50 0.492911    10.993324    0
51 0.184992    8.721488    0
52 -0.355715    10.325976    0
53 -0.397822    8.058397    0
54 0.824839    13.730343    0
55 1.507278    5.027866    1
56 0.099671    6.835839    1
57 -0.344008    10.717485    0
58 1.785928    7.718645    1
59 -0.918801    11.560217    0
60 -0.364009    4.747300    1
61 -0.841722    4.119083    1
62 0.490426    1.960539    1
63 -0.007194    9.075792    0
64 0.356107    12.447863    0
65 0.342578    12.281162    0
66 -0.810823    -1.466018    1
67 2.530777    6.476801    1
68 1.296683    11.607559    0
69 0.475487    12.040035    0
70 -0.783277    11.009725    0
71 0.074798    11.023650    0
72 -1.337472    0.468339    1
73 -0.102781    13.763651    0
74 -0.147324    2.874846    1
75 0.518389    9.887035    0
76 1.015399    7.571882    0
77 -1.658086    -0.027255    1
78 1.319944    2.171228    1
79 2.056216    5.019981    1
80 -0.851633    4.375691    1


 

测试数据:



1 -1.510047    6.061992    0
2 -1.076637    -3.181888    1
3 1.821096    10.283990    0
4 3.010150    8.401766    1
5 -1.099458    1.688274    1
6 -0.834872    -1.733869    1
7 -0.846637    3.849075    1
8 1.400102    12.628781    0
9 1.752842    5.468166    1
10 0.078557    0.059736    1
11 0.089392    -0.715300    1
12 1.825662    12.693808    0
13 0.197445    9.744638    0
14 0.126117    0.922311    1
15 -0.679797    1.220530    1
16 0.677983    2.556666    1
17 0.761349    10.693862    0
18 -2.168791    0.143632    1
19 1.388610    9.341997    0
20 0.317029    14.739025    0


 

python 代码:

1 #!/usr/bin/python
2 import sys
3 import copy
4 import math
5 import time
6 import random
7 import getopt
8
9 def usage():
10     print '''Help Information:
11     -h, --help: show help information;
12     -r, --train: train file;
13     -t, --test: test file;
14     -k, --ratio: study ratio;
15     -i, --iter: iter num;
16     -p, --type: optimize type:"gradDescent","stocGradDescent","smoothStocGradDescent";
17     '''
18
19 def getparamenter():
20     try:
21         opts, args = getopt.getopt(sys.argv[1:], "hr:t:k:i:p:", ["help","train=","test=","kst=","iter=","type="])
22     except getopt.GetoptError, err:
23         print str(err)
24         usage()
25         sys.exit(1)
26
27     sys.stderr.write("\ntrain.py : a python script for perception training.\n")
28     sys.stderr.write("Copyright 2016 sxron, search, Sogou. \n")
29     sys.stderr.write("Email: shixiang08abc@gmail.com \n\n")
30
31     train = ''
32     test = ''
33     kst = 0.01
34     iter = 100
35     type = 'gradDescent'
36     for i, f in opts:
37         if i in ("-h", "--help"):
38             usage()
39             sys.exit(1)
40         elif i in ("-r", "--train"):
41             train = f
42         elif i in ("-t", "--test"):
43             test = f
44         elif i in ("-k", "--ratio"):
45             kst = float(f)
46         elif i in ("-i", "--iter"):
47             iter = int(f)
48         elif i in ("-p", "--type"):
49             type = f
50         else:
51             assert False, "unknown option"
52
53     print "start trian parameter\ttrain:%s\ttest:%s\tkst:%f\titer:%d\ttype:%s" % (train,test,kst,iter,type)
54
55     return train,test,kst,iter,type
56
57 def loadData(file):
58     data = []
59     label = []
60     fin = open(file,'r')
61     while 1:
62         line = fin.readline()
63         if not line:
64             break
65         tokens = line.strip().split('\t')
66         fea = []
67         try:
68             lab = float(tokens[-1])
69             fea.append(1.0)
70             for i in range(0,len(tokens)-1,1):
71                 value = float(tokens[i])
72                 fea.append(value)
73         except:
74             continue
75         label.append(lab)
76         data.append(fea)
77     return data,label
78
79 def sigmoid(inX):
80     return 1.0/(1+math.exp(-inX))
81
82 def getMatResult(data,weights):
83     result = 0.0
84     for i in range(0,len(data),1):
85         result += data[i]*weights[i]
86     return result
87
88 def trainLogRegress(data,label,iter,kst,type):
89     weights = []
90     for i in range(0,len(data[0]),1):
91         weights.append(1.0)
92
93     for i in range(0,iter,1):
94         errors = []
95         if type=="gradDescent":
96             for k in range(0,len(data),1):
97                 result = getMatResult(data[k],weights)
98                 error = label[k] - sigmoid(result)
99                 errors.append(error)
100             for k in range(0,len(weights),1):
101                  updata = 0.0
102                  for idx in range(0,len(errors),1):
103                      updata += errors[idx]*data[idx][k]
104                  weights[k] += kst*updata
105
106         elif type=="stocGradDescent":
107             for k in range(0,len(data),1):
108                 result = getMatResult(data[k],weights)
109                 error = label[k] - sigmoid(result)
110                 for idx in range(0,len(weights),1):
111                     weights[idx] += kst*error*data[k][idx]
112
113         elif type=="smoothStocGradDescent":
114             dataIndex = range(len(data))
115             for k in range(0,len(data),1):
116                 randIndex = int(random.uniform(0,len(dataIndex)))
117                 result = getMatResult(data[randIndex],weights)
118                 error = label[randIndex] - sigmoid(result)
119                 for idx in range(0,len(weights),1):
120                     weights[idx] += kst*error*data[randIndex][idx]
121         else:
122             print "Not support optimize method type!"
123     return weights
124
125 def testLogRegress(weights,data,label):
126     testNum = 0
127     matchNum = 0
128     for i in range(0,len(data),1):
129         result = getMatResult(data[i],weights)
130         predict = 0
131         if sigmoid(result)>0.5:
132             predict = 1
133         testNum += 1
134         if predict==int(label[i]):
135             matchNum += 1
136     print "testNum:%d\tmatchNum:%d\tratio:%f" % (testNum,matchNum,float(matchNum)/testNum)
137
138 def main():
139     #set parameter
140     train,test,kst,iter,type = getparamenter()
141
142     #load train data
143     trnData,trnLabel = loadData(train)
144     testData,testLabel = loadData(test)
145
146     #train logregress
147     weights = trainLogRegress(trnData,trnLabel,iter,kst,type)
148
149     #test logregress
150     testLogRegress(weights,testData,testLabel)
151
152 if __name__=="__main__":
153     main()


下面的程序取自:http://www.tuicool.com/articles/vMzaEvj



以下为一元回归线性方式,其中y是因变量,X是自变量,我们只需求出截距b0和斜率b1就可以获得费用和曝光次数之间的关系,并对曝光次数进行预测。这里我们使用最小二乘法来计算截距b0和斜率b1。最小二乘法通过最小化误差的平方和寻找数据的最佳函数匹配。



下表中是使用最小二乘法计算回归方程的一些必要的计算过程。在表中最左侧的两列分别为自变量X和因变量Y,我们首先计算出自变量和因变量的均值,然后计算每一个观测值与均值的差,以及用于计算回归方程斜率b1所需的数据。



根据表中的数据按公式计算出了回归方程的斜率b1,计算过程如下。斜率表示了自变量和因变量间的关系,斜率为正表示自变量和因变量正相关,斜率为负表示自变量和因变量负相关,斜率为0表示自变量和因变量不相关。



求得斜率b1后,按下面的公式可以求出Y轴的截距b0。



将斜率b1和截距b0代入到回归方程中,通过这个方程我们可以获得自变量和因变量的关系,费用每增加1元,曝光次数会增长7437次。以下为回归方程和图示。





在回归方程的图示中,还有一个R平方,这个值叫做判定系数,用来衡量回归方程是否很好的拟合了样本的数据。判定系数在0-1之间,值越大说明拟合的越好,换句话说就是自变量对因变量的解释度越高。判定系数的计算公式为SST=SSR+SSE,其中SST是总平方和,SSR是回归平方和,SSE是误差平方和。下表为计算判定系数所需三个指标的一些必要的计算过程。



根据前面求得的回归平方和(SSR)和总平方和(SST)求得判定系数为0.94344。



以上为回归方程的计算过程,在根据费用预测曝光数量的场景下,我们可以通过回归方程在已知费用的情况下计算出曝光数量。逻辑回归与回归方程相比在线性回归的基础上增加了一个逻辑函数。例如通过用户的属性和特征来判断用户最终是否会进行购买。其中购买的概率是因变量Y,用户的属性和特征是自变量X。Y值越大说明用户购买的概率越大。这里我们使用事件发生的可能性(odds)来表示购买与未购买的比值。



使用E作为购买事件,P(E)是购买的概率,P(E’)是未购买的概率,Odds(E)是事件E(购买)发生的可能性。



Odds是一个从0到无穷的数字,Odds的值越大,表明事件发生的可能性越大。下面我们要将Odds转化为0-1之间的概率函数。首先对Odds取自然对数,得到logit方程,logit是一个范围在负无穷到正无穷的值。



基于上面的logit方程,获得以下公式:



其中使用π替换了公式中的P(E),π=P(E)。根据指数函数和对数规则获得以下公式:



并最终获得逻辑回归方程:



下面根据逻辑回归方程来计算用户购买的概率,下表是用户注册天数和是否购买的数据,其中注册天数是自变量X,是否购买是自变量Y。我们将购买标记为1,将未购买标记为0。接下来我们将在Excel中通过8个步骤计算出逻辑回归方程的斜率和截距。并通过方程预测新用户是否会购买。



第一步,使用Excel的排序功能对原始数据按因变量Y进行排序,将已购买和未购买的数据分开,使得数据特征更加明显。
第二步,按照Logit方程预设斜率b1和截距b0的值,这里我们将两个值都预设为0.1。后续再通过Excel求最优解。
第三步,按照logit方程,使用之前预设的斜率和截距值计算出L值。



第四步,将L值取自然对数,
第五步,计算P(X)的值,P(X)为事件发生的可能性(Odds)。具体的计算步骤和过程见下图。



第六步,计算每个值的对数似然函数估计值(Log-Likelihood)。方法和过程见下图。
第七步,将对数似然函数值进行汇总。



第八步,使用Excel的规划求解功能,计算最大对数似然函数值。方法和过程见下图。设置汇总的对数似然函数值LL为最大化的目标,预设的斜率b1和截距b0是可变单元格,取消”使无约束变量为非负数”的选项。进行求解。



Excel将自动求出逻辑回归方程中斜率和截距的最优解,结果如下图所示。



求得逻辑回归方程的斜率和截距以后,我们可以将值代入方程,获得一个注册天数与购买概率的预测模型,通过这个模型我们可以对不同注册天数(X)用户的购买概率(Y)进行预测。以下为计算过程。



第一步,输入自变量注册天数(X)的值,这里我们输入50天。
第二步,将输入的X值,以及斜率和截距套入Logit方程,求出L值。
第三步,对L值取自然对数。
第四步,求时间发生可能性P(X)的概率值。
注册天数为50天的用户购买的概率约为17.60%。

我们将所有注册天数的值代入到购买概率预测模型中,获得了一条注册天数对购买概率影响的曲线。从曲线中可以发现,注册天数在较低和较高天数的用户购买概率较为平稳。中间天数用户的购买概率变化较大。



我们继续在上面的计算结果中增加新的自变量“年龄”。以下是原始数据的截图。现在有年龄和注册天数两个自变量和一个因变量。



依照前面的方法计算斜率和截距的最优解,并获得逻辑回归方程,将不同的年龄和注册天数代入到方程中,获得了用户年龄和注册天数对购买的预测模型。我们通过Excel的三维图表来绘制年龄和注册天数对购买概率的影响。



从图中可以看出,购买概率随着注册天数的增加而增长,并且在相同的注册天数下,年龄较小的用户购买概率相对较高。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐