1、概述
Logistic regression(邏輯回歸)是當前業界比較常用的機器學習方法,用於估計某種事物的可能性。
在經典之作《數學之美》中也看到了它用於廣告預測,也就是根據某廣告被用 戶點擊的可能性,把最可能被用戶點擊的廣告擺在用戶能看到的地方,然后叫他“你點我啊!”用戶點了,你就有錢收了。這就是為什么我們的電腦現在廣告泛濫的 原因。還有類似的某用戶購買某商品的可能性,某病人患有某種疾病的可能性啊等等。這個世界是隨機的(當然了,人為的確定性系統除外,但也有可能有噪聲或產生錯誤的結果,只是這個錯誤發生的可能性太小了,小到千萬年不遇,小到忽略不計而已),所以萬物的發生都可以用可能性或者幾率(Odds)來表達。“幾率”指的是某事物發生的可能性與不發生的可能性的比值。
Logistic regression可以用來回歸,也可以用來分類,主要是二分類。
2、基本理論
2.1Logistic regression和Sigmoid函數
回歸:假設現在有一些數據點,我們用一條直線對這些點進行擬合(該條稱為最佳擬合直線),這個擬合過程就稱作回歸。利用Logistic回歸進行分類的思想是:根據現有數據對分類邊界線建立回歸公式,以此進行分類。這里的“回歸”一詞源於最佳擬合,表示找到最佳擬合參數,使用的是最優化算法。
Sigmoid函數具體的計算公式如下:
z=w0x0+w1x1+w2x2+...+wnxn, z=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+...+wnxn, z=wTx
向量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回歸優缺點
優點:計算代價不高,易於理解和實現
缺點:容易欠擬合,分類精度可能不高
適用數據類型:數值型和標稱型數據