5. Logistic回归

一、介绍
        Logistic回归是广泛应用的机器学习算法,虽然名字里带“回归”,但是它实际上是一种分类方法,主要用于两分类问题(即输出只有两种,分别代表两个类别)。
面对一个回归或者分类问题,建立代价函数(损失函数),使用最优化算法(梯度上升法、改进的随机梯度上升法),找到最佳拟合参数,将数据拟合到一个logit函数(或者叫做logistic函数)中,得到定性变量y,比如y=0或1,从而预测事件发生的概率。
        
二、线性回归
        从线性回归说,多维空间中存在的样本点,用特征的线性组合去拟合空间中点的分布和轨迹。如下图所示:
                                                                             
        线性回归能对连续值结果进行预测,现实生活中存在另一种分类-----是与否的二分类问题。比如医生需要判断病人是否生病,银行要判断一个人的信用程度是否达到可以给他发信用卡的程度,邮件收件箱要自动对邮件分类为正常邮件和垃圾邮件等等。
    当然,既然能够用线性回归预测出连续值结果,那根据结果设定一个阈值是不是就可以解决这个问题了呢?事实是,对于很标准的情况,确实可以。下图中X为数据点肿瘤的大小,Y为观测结果是否是恶性肿瘤。如hθ(x)所示,构建线性回归模型,然后设定一个阈值0.5,预测hθ(x)≥0.5的这些点为恶性肿瘤,而hθ(x)<0.5为良性肿瘤。    
                                                                                   
    上图中突然有一个wrong的数据点,再根据设定的0.5,得出的分类就是错误的(良性肿瘤错误认为是恶性肿瘤),而现实生活中分类问题的数据,会比上述举例更复杂,借助线性回归+阈值的方式,很难完成一个鲁棒性很好的分类器。
    
相比线性回归,逻辑回归:
1. 线性回归的结果是一个连续值,值的范围是无法限定的,假设回归的结果都是无法限定的值,那做出一个判定就很难了。能将结果做一个映射,输出(0,1) 的一个概率值,这个问题就很清楚了。有这样一个简单的函数,可以做到这样的映射---sigmoid函数。
说明:sigmoid函数图像可以看出,函数 hθ(x) = g(z): 在z=0的时候.函数值1/2,随着 z 逐渐变小函数值趋于0,z 逐渐变大函数值逐渐趋于1,而这正是一个概率的范围。
           我们定义线性回归的预测函数为hθ(xθTX,那么逻辑回归的输出hθ(x=  g( θTX ),其中 g( z ) 函数就是上述sigmoid函数.
              hθ(x) = g( θTX ) ≥ 0.5, 则 θTX ≥ 0, 意味着类别为 1;
              hθ(x) = g( θTX ) < 0.5, 则 θTX < 0, 意味着类别为 0; 
            所以可以认为 θTX =0 是一个决策边界,当它 >0 或 <0 时,逻辑回归模型预测出不同的分类结果。
      
                                                     
                                                                                                
                            
2. 逻辑回归可以做出直线、圆或者是曲线这样的判定边界,比较好地将两类样本分开。
说明:假设 hθ(x)=g(θ01X12X2),其中θ012分别取-3, 1, 1。则当−3+X1+X2 ≥ 0时, y = 1即类别为1; 则X1+X= 3是一个判定边界,图形表示如下,刚好把图上的两类点区分开来:
                                 
            假设hθ(x) 比较复杂,hθ(x) = g(θ01X12X2+ θ3 X12+ θ4X22) ,其中θ0 1 2,θ3 4 分别取-1, 0, 0,1,1,则当 -1 + x12+x2≥ 0 时,y = 1即类别为1; x12+x22 = 1是一个判定边界,图形表示如下:
                                                   
              所以,只要我们的hθ(x)设计足够合理,准确的说是g(θTx)中θTx足够复杂,能在不同的情形下,拟合出不限于线性的判定边界。
                                                
 
 
三、求解θ
    假设我们有n个独立的训练样本{(x1, y1) ,(x2, y2),…, (xn, yn)},类别y = {0, 1}。那每一个观察到的样本(xi, yi)出现的概率是:              
      
                            
 
    y的取值为0 或 1,所以,上式等价于:
 
                         
 
    那整个样本集,n个独立的样本出现的似然函数为(因为每个样本都是独立的,所以n个样本出现的概率就是它们各自出现的概率相乘):
                    
                           
    
    连乘不好处理,取自然对数,转成相加:
                            
                              wKiom1TEnF7glwX2AABxj9lYg18460.jpg
    经过化简得到:
        
                                    
    该似然函数在这里表示的是:参数取值为θ时,取得当前这个样本集的可能性。使参数为θ的似然函数L(θ)极大化,然后极大值对应的θ就是我们的估计。
    假如需要求如下函数的最大值:
                                           wKioL1TEnTnRW2_ZAAAUUKd9zpQ091.jpg             
    函数的导数为:
                                            wKiom1TEnGGBvHtvAAATGsAEI4c709.jpg
    所以 x=1.5即取得函数的最大值1.25。
 
    同样的,我们对刚刚的对数似然函数logL(θ)即l(θ)求导:
        
                                        wKiom1TEnGHhkXbMAAFCAkpV7Zs421.jpg
    
    然后我们令该导数为0,你会很失望的发现,它无法解析求解。
    终于可以说梯度上升了……利用迭代公式:
                                                 wKiom1TEnGSDl1JuAAAqXOo511s178.jpg
    参数α叫学习率,就是每一步走多远,这个参数蛮关键的。如果设置的太多,那么很容易就在最优值附近徘徊,因为步伐太大了。它带来的好处是能很快的从远离最优值的地方回到最优值附近,只是在最优值附近的时候,它有心无力了。但如果设置的太小,那收敛速度就太慢了,向蜗牛一样,虽然会落在最优的点,但是速度太慢。所以学习率可以改进,开始迭代时,学习率大,慢慢的接近最优值的时候,学习率变小就可以了,即随机梯度下降算法。
 
所以,最终我们计算过程(训练过程)如下:
1. 根据权重和训练样本计算估计值
        
                 
 
2.计算误差                
             
 
3. 迭代更新
                
                
 
迭代停止条件:到达限定迭代次数 或者 误差 <= 设定误差的最小值。
 
说明:以上过程中,n 和 m等价,w 和 θ等价。
 
四、 代码
 1 import numpy as np
 2 import re
 3 from pandas import DataFrame
 4 import time as time
 5 import matplotlib.pyplot as plt
 6 import math
 7 
 8 def get_data(filename):                 #读取数据
 9     f = open(filename)
10     data = DataFrame(columns=['x0','x1','x2','label']) #构造DataFrame存放数据,列名为x与y
11     line = f.readline()
12     line = line.strip()
13     p = re.compile(r'\s+')              #由于数据由若干个空格分隔,构造正则表达式分隔
14     while line:
15         line = line.strip()
16         linedata = p.split(line)
17         data.set_value(len(data),['x0','x1','x2','label'],[1,float(linedata[0]),float(linedata[1]),int(linedata[2])]) #数据存入DataFrame
18         line = f.readline()
19     return np.array(data.loc[:,['x0','x1','x2']]),np.array(data['label'])
20 def sigmoid(x):
21     return 1.0/(1+np.exp(-x))
22 def stocGradAscent(dataMat,labelMat,alpha = 0.01):   #随机梯度上升
23     start_time = time.time()                         #记录程序开始时间
24     m,n = dataMat.shape
25     weights = np.ones((n,1))                         #分配权值为1
26     for i in range(m):
27         h = sigmoid(np.dot(dataMat[i],weights).astype('int64')) #注意:这里两个二维数组做内积后得到的dtype是object,需要转换成int64
28         error = labelMat[i]-h                        #误差
29         weights = weights + alpha*dataMat[i].reshape((3,1))*error #更新权重
30     duration = time.time()-start_time
31     print('time:',duration)
32     return weights
33 def gradAscent(dataMat,labelMat,alpha = 0.01,maxstep = 1000): #批量梯度上升
34     start_time = time.time()
35     m,n = dataMat.shape
36     weights = np.ones((n,1))
37     for i in range(maxstep):
38         h = sigmoid(np.dot(dataMat,weights).astype('int64'))  #这里直接进行矩阵运算
39         labelMat = labelMat.reshape((100,1))                  #label本为一维,转成2维
40         error = labelMat-h                                    #批量计算误差
41         weights = weights + alpha*np.dot(dataMat.T,error)     #更新权重
42     duration = time.time()-start_time
43     print('time:',duration)
44     return weights
45 def betterStoGradAscent(dataMat,labelMat,alpha = 0.01,maxstep = 150):
46     start_time = time.time()
47     m,n = dataMat.shape
48     weights = np.ones((n,1))
49     for j in range(maxstep):
50         for i in range(m):
51             alpha = 4/(1+i+j) + 0.01                         #设置更新率随迭代而减小
52             h = sigmoid(np.dot(dataMat[i],weights).astype('int64'))
53             error = labelMat[i]-h
54             weights = weights + alpha*dataMat[i].reshape((3,1))*error
55     duration = time.time()-start_time
56     print('time:',duration)
57     return weights
58 def show(dataMat, labelMat, weights):
59     #dataMat = np.mat(dataMat)
60     #labelMat = np.mat(labelMat)
61     m,n = dataMat.shape
62     min_x = min(dataMat[:, 1])
63     max_x = max(dataMat[:, 1])
64     xcoord1 = []; ycoord1 = []
65     xcoord2 = []; ycoord2 = []
66     for i in range(m):
67         if int(labelMat[i]) == 0:
68             xcoord1.append(dataMat[i, 1]); ycoord1.append(dataMat[i, 2])
69         elif int(labelMat[i]) == 1:
70             xcoord2.append(dataMat[i, 1]); ycoord2.append(dataMat[i, 2])
71     fig = plt.figure()
72     ax = fig.add_subplot(111)
73     ax.scatter(xcoord1, ycoord1, s=30, c="red", marker="s")
74     ax.scatter(xcoord2, ycoord2, s=30, c="green")
75     x = np.arange(min_x, max_x, 0.1)
76     y = (-float(weights[0]) - float(weights[1])*x) / float(weights[2])
77     ax.plot(x, y)
78     plt.xlabel("x1"); plt.ylabel("x2")
79     plt.show()
80 
81 if __name__=='__main__':
82     dataMat,labelMat = get_data('data1.txt')
83     weights = gradAscent(dataMat,labelMat)
84     show(dataMat,labelMat,weights)

 

 
 
 
 
 
 
 
 
 
 
 
参考:





posted @ 2018-01-27 01:35  骄傲的糖糕  阅读(353)  评论(0编辑  收藏  举报