【Machine Learning in Action --5】邏輯回歸(LogisticRegression)


1、概述

  Logistic regression(邏輯回歸)是當前業界比較常用的機器學習方法,用於估計某種事物的可能性。

  在經典之作《數學之美》中也看到了它用於廣告預測,也就是根據某廣告被用 戶點擊的可能性,把最可能被用戶點擊的廣告擺在用戶能看到的地方,然后叫他“你點我啊!”用戶點了,你就有錢收了。這就是為什么我們的電腦現在廣告泛濫的 原因。還有類似的某用戶購買某商品的可能性,某病人患有某種疾病的可能性啊等等。這個世界是隨機的(當然了,人為的確定性系統除外,但也有可能有噪聲或產生錯誤的結果,只是這個錯誤發生的可能性太小了,小到千萬年不遇,小到忽略不計而已),所以萬物的發生都可以用可能性或者幾率(Odds)來表達。“幾率”指的是某事物發生的可能性與不發生的可能性的比值。

       Logistic regression可以用來回歸,也可以用來分類,主要是二分類。

2、基本理論

2.1Logistic regression和Sigmoid函數

  回歸:假設現在有一些數據點,我們用一條直線對這些點進行擬合(該條稱為最佳擬合直線),這個擬合過程就稱作回歸。利用Logistic回歸進行分類的思想是:根據現有數據對分類邊界線建立回歸公式,以此進行分類。這里的“回歸”一詞源於最佳擬合,表示找到最佳擬合參數,使用的是最優化算法。

  Sigmoid函數具體的計算公式如下:

    

          z=w0x0+w1x1+w2x2+...+wnxnz=wTx  其中w是我們要找的最佳參數(系數),x是分類器的輸入數據特征。

  當x為0時,Sigmoid函數值為0.5,隨着x的增大,對應的Sigmoid值將逼近於1;而隨着x的減小,Sigmoid值將逼近於0。如果橫坐標刻度足夠大(如下圖所示),Sigmoid函數看起來很像一個階躍函數。

    

  為了實現Logistic回歸分類器,我們可以在每個特征上都乘以一個回歸系數,然后把所有結果值相加,將這個總和代入Sigmoid函數中,進而得到一個范圍在0-1之間的數值。任何大於0.5的數據被分入1類,小於0.5即被歸入0類。所以,Logistic回歸也可以被看作是一種概率估計。

2.2最優化理論

   由上述問題得到,我們現在的問題變成了:最佳回歸系數時多少?

    z=w0x0+w1x1+w2x2+...+wnxnz=wT

  向量x是分類器的輸入數據,向量w是我們要找的最佳參數(系數),從而使得分類器盡可能地精確,為了尋找最佳參數,需要用到最優化理論的一些知識。

  下面首先介紹梯度上升的最優化方法,我們將學習到如何使用該方法求得數據集的最佳參數。接下來,展示如何繪制梯度上升法產生的決策邊界圖,該圖能將梯度上升法的分類效果可視化地呈現出來。最后我們將學習隨機梯度上升法,以及如何對其進行修改以獲得更好的結果。

2.2.1梯度上升法

  梯度上升法的基於的思想是:要找到某函數的最大值,最好的方法是沿着該函數的梯度方向探尋。則函數f(x,y)的梯度由下式表示:

    

  這個梯度意味着要沿x方向移動,沿y方向移動,其中,函數f(x,y)必須要在待計算的點上有定義並且可微。具體的函數例子如下圖所示:

      

      注釋:梯度上升算法到達每個點后都會重新估計移動的方向。從P0開始,計算完該點的梯度,函數就根據梯度移動到下一點P1。在P1點,梯度再次被重新計算,並沿新的梯度方向移動到P2。如此循環迭代,直到滿足停止條件。迭代過程中,梯度算子總是保證我們能選取到最佳的移動方向。

  可以看到,梯度算子總是指向函數值增長最快的方向。這里所說的是移動方向,而未提到移動量的大小。該量值稱為歩長,記作,用向量來表示的話,梯度上升算法的迭代公式如下:

  

  該公式將一直被迭代執行,直到達到某個停止條件為止,比如迭代次數達到某個指定值或算法達到可以允許的誤差范圍。

  事例(用梯度上升找到最佳參數)

  該數據中有100個樣本點,每個點包含兩個數值型特征:X1和X2。在此數據集上,我們將通過使用梯度上升法找到最佳回歸系數,也就是說擬合出Logistic回歸模型的最佳參數。在以下的數據集中,每行的前兩個值分別是X1和X2,它們是數據特征,第三個值是數據對應的類別標簽,為了方便計算,該函數還將X0的值設為1.0。

  testSet.txt數據集如下:

  

  梯度上升算法代碼如下,建立一個logRegres.py的文件,添加如下代碼:

#!/usr/bin/python
# -*- coding: utf-8 -*-
from numpy import *
#Logistic回歸梯度上升優化算法
def loadDataSet():
    dataMat = []; labelMat = []
    fr = open('testSet.txt')
    for line in fr.readlines():
        lineArr = line.strip().split()      #最后一行開始讀取
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])  #獲取X0,X1,X2的值
        labelMat.append(int(lineArr[2]))    #獲取最后一列的類別標簽
    return dataMat,labelMat

def sigmoid(inX):
    return 1.0/(1+exp(-inX))

def gradAscent(dataMatIn, classLabels):     #dataMatIn是一個2維Numpy數組
    dataMatrix = mat(dataMatIn)             #convert to NumPy matrix
    labelMat = mat(classLabels).transpose() #convert to NumPy matrix
    m,n = shape(dataMatrix)
    alpha = 0.001
    maxCycles = 500                         #迭代次數為500
    weights = ones((n,1))                   #回歸系數初始化為1  
    for k in range(maxCycles):              #heavy on matrix operations
        h = sigmoid(dataMatrix*weights)     #matrix mult
        error = (labelMat - h)              #vector subtraction
        weights = weights + alpha * dataMatrix.transpose()* error #matrix mult
    return weights

   在python提示符下,寫下面代碼:

>>>import logRegres
>>> dataArr,labelMat=logRegres.loadDataSet()
>>> logRegres.gradAscent(dataArr,labelMat)
matrix([[ 4.12414349],
        [ 0.48007329],
        [-0.6168482 ]])

解釋:

>>> dataMat
matrix([[  1.00000000e+00,  -1.76120000e-02,   1.40530640e+01],
        [  1.00000000e+00,  -1.39563400e+00,   4.66254100e+00],
           ...
        [  1.00000000e+00,   1.38861000e+00,   9.34199700e+00],
        [  1.00000000e+00,   3.17029000e-01,   1.47390250e+01]])
>>> labelMat
matrix([[0],
        [1],
        ...
        [0],
        [0]])

  上面解出了一組回歸系數,它確定了不同類別數據之間的分隔線。那么怎樣畫出該分隔線,從而使得優化過程便於理解,打開logRegres.py添加如下代碼:

#畫出數據即和Logisitic回歸最佳擬合直線的函數
def plotBestFit(weights):
    import matplotlib.pyplot as plt
    dataMat,labelMat=loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0] 
    xcord1 = []; ycord1 = []
    xcord2 = []; ycord2 = []
    for i in range(n):
        if int(labelMat[i])== 1:
            xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2])
        else:
            xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2])
    fig = plt.figure()
    ax = fig.add_subplot(111)
    ax.scatter(xcord1, ycord1, s=30, c='red', marker='s')
    ax.scatter(xcord2, ycord2, s=30, c='green')
    x = arange(-3.0, 3.0, 0.1)
    y = (-weights[0]-weights[1]*x)/weights[2]  #W0X0+W1X1+W2X2=0(X0=1),X2=(-W0-W1X1)/W2
    ax.plot(x, y)
    plt.xlabel('X1'); plt.ylabel('X2');
    plt.show()

   運行程序清單,在python提示符下輸入:

>>> from numpy import *
>>> reload(logRegres)
<module 'logRegres' from 'logRegres.pyc'>
>>> logRegres.plotBestFit(wei)
'''

  這個分類結果相當不錯,從圖上看只錯分了兩到四個點。但是,盡管例子簡單且數據集很小,但是這個方法確需要大量的計算(300次乘法),下一節將對該算法稍作改進,從而使它能用在真實數據集上。

2.2.2隨機梯度上升

  梯度上升算法在每次更新回歸系數時都需要遍歷整個數據集,該方法在處理1001個左右的數據集時尚克,但如果有數十億樣本和成千上萬的特征,那么該方法的計算復雜度就太高了。

  一種改進方法是一次僅用一個樣本點來更新回歸系數,該方法稱為隨機梯度上升算法。由於可以在信仰本到來時對分類器進行增量式更新,因而隨機梯度上升算法是一個在線學習算法。

  隨機梯度上升算法代碼如下,將下面代碼添加到logRegres.py中:

#隨機梯度上升函數
def stocGradAscent0(dataMatrix, classLabels):
    m,n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)   #initialize to all ones
    for i in range(m):
        h = sigmoid(sum(dataMatrix[i]*weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights

   為了驗證該方法的結果,我們在python提示符中輸入以下命令:

>>> from numpy import *
>>> reload(logRegres)
<module 'logRegres' from 'logRegres.py'>
>>> dataArr,labelMat=logRegres.loadDataSet()
>>> weights=logRegres.stocGradAscent0(array(dataArr),labelMat)
>>> logRegres.plotBestFit(weights)

  隨機梯度上升算法與梯度上升算法在代碼上很相似,但有一些區別:

  梯度上升算法:變量h和誤差error都是向量,有矩陣的轉換過程

  隨機梯度上升算法:變量h和誤差error都是數值,沒有矩陣的轉換過程,所有的數據類型都是Numpy數組。

  由上述圖得到的最佳擬合直線圖,但擬合的直線沒有那么完美,這里的分類器錯分了三分之一的樣本。一個判斷優化算法優劣的可靠方法是看它是否收斂,也就是說參數是否達到了穩定值,是否還會不斷地變化。

  對上述隨機梯度上升算法上做了些修改,使其在整個數據集上運行200次,最終繪制的三個回歸系數的變化情況如圖所示:

  上圖展示了隨機梯度上升算法在200次迭代過程中(我們的數據庫有100個樣本,每個樣本都對系數調整一次,所以共有200*100=20000次調整)回歸系數的變化情況。由圖中看到X2只經過了50次迭代就達到了穩定值,但X0和X1則需要更多次的迭代。另外值得注意的是,在大的波動停止后,還有一些小的波動。產生這種現象的原因是存在一些不能正確分類的樣本點(數據集並非線性可分),在每次迭代時會引發系數的劇烈改變。我們期望算法能避免來回波動,從而收斂到某個值。另外,收斂速度也要加快。

2.2.3改進的隨機梯度上升算法

  對上述隨機梯度上升算法,我們做兩處改進來避免上述的波動問題:

1) 在每次迭代時,調整更新步長alpha的值。隨着迭代的進行,alpha越來越小,這會緩解系數的高頻波動(也就是每次迭代系數改變得太大,跳的跨度太 大)。當然了,為了避免alpha隨着迭代不斷減小到接近於0(這時候,系數幾乎沒有調整,那么迭代也沒有意義了),我們約束alpha一定大於一個稍微大點的常數項,具體見代碼。

2)每次迭代,改變樣本的優化順序。也就是隨機選擇樣本來更新回歸系數。這樣做可以減少周期性的波動,因為樣本順序的改變,使得每次迭代不再形成周期性。

  將下述代碼添加到logRegres.py中:

#改進的隨機梯度上升函數
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m,n = shape(dataMatrix)
    weights = ones(n)   #initialize to all ones
    for j in range(numIter):
        dataIndex = range(m)
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.01    #j是迭代次數,i是樣本點的下標,alpha每次迭代時需要調整 
            randIndex = int(random.uniform(0,len(dataIndex))) #隨機選取樣本來更新回歸系數(減少周期波動)
            h = sigmoid(sum(dataMatrix[randIndex]*weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weights

   為了驗證該方法的結果,我們在python提示符中輸入以下命令:

>>> reload(logRegres)
<module 'logRegres' from 'logRegres.py'>
>>> dataArr,labelMat=logRegres.loadDataSet()
>>> weights=logRegres.stocGradAscent1(array(dataArr),labelMat)
>>> logRegres.plotBestFit(weights)

  

  改進算法增加了一個迭代次數作為第三個參數,如果該參數沒有給定的話,算法將默認迭代150次,如果給定,那么算法將按照新的參數值進行迭代。與隨機梯度上升算法中的回歸系數類似,改進的隨機梯度上升算法中的各個回歸系數的變化情況如下:

  比較隨機梯度上升和改進后的梯度上升,可以看到兩點不同:

  1)系數不再出現周期性波動。

  2)系數可以很快的穩定下來,也就是快速收斂。這里只迭代了20次就收斂了。而上面的隨機梯度下降需要迭代200次才能穩定。

3、Logistic回歸的一般過程

  (1)收集數據:采用任何方法收集數據

  (2)准備數據:數據類型是數值型的,另外,結構化數據格式則最佳

  (3)分析數據:采用任意方法對數據進行分析

  (4)訓練算法:大部分時間將用於訓練,訓練的目的是為了找到最佳的分類回歸系數

  (5)測試算法:一旦訓練步驟完成,分類將會很快

  (6)使用算法:首先,我們需要輸入一些數據,並將其轉換成對應的結構化數值;接着,基於訓練好的回歸系數就可以對這些數值進行簡單的回歸計算,判定它們屬於哪個類別;在這之后,我們就可以在輸出的類別上做一些其他分析工作。

4、Logistic回歸優缺點

  優點:計算代價不高,易於理解和實現

  缺點:容易欠擬合,分類精度可能不高

  適用數據類型:數值型和標稱型數據

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM