邏輯斯諦迴歸(logistic regression)是統計學習中的經典分類方法,屬於對數線性模型,所以也被稱爲對數機率迴歸。這裏要注意,雖然帶有迴歸的字眼,但是該模型是一種分類算法,邏輯斯諦迴歸是一種線性分類器,針對的是線性可分問題。利用logistic迴歸進行分類的主要思想是:根據現有的數據對分類邊界線建立迴歸公式,以此進行分類。這裏的“迴歸”一詞源於最佳擬合,表示要找到最佳擬合參數集,因此,logistic訓練分類器時的做法就是尋找最佳擬合參數,使用的是最優化方法,接下來我們都會講到。
一、Logistic迴歸函數:
我們先說一個概念,事件的機率(odds),是指該事件發生的概率與該事件不發生的概率的比值。如果事件發生的概率是p,那麼該事件的機率是p/(1-p)。取該事件發生機率的對數,定義爲該事件的對數機率(log odds)或logit函數: 看了幾個博客,我對其取對數的理解大概是這樣:事件發生的概率p的取值範圍爲[0,1],對於這樣的輸入,計算出來的機率只能是非負的(大家可以自己驗證),而通過取對數,便可以將輸出轉換到整個實數範圍內,下面是log函數的在二維座標系中的圖像,依照圖像就會對標黃的那句話有一個形象的瞭解了。
那我們將輸出轉換到整個實數範圍內的目的是什麼呢?因爲這樣,我們就可以將對數機率記爲輸入特徵值的線性表達式 : 其中,p(y =1|x)是條件概率分佈,表示當輸入爲x時,實例被分爲1類的概率,依據此概率我們能得到事件發生的對數機率。但是,我們的初衷是做分類器,簡單點說就是通過輸入特徵來判定該實例屬於哪一類別或者屬於某一類別的概率。所以我們取logit函數的反函數,令的線性組合爲輸入,p爲輸出,經如下推導
公式1就是logistic函數。大家應該對Φ(x)很熟悉,是一個sigmoid函數,類似於階躍函數的S型生長曲線。
上圖給出了sigmoid函數在不同座標尺度下的兩條曲線圖。當x爲0時,sigmoid函數值爲0.5。隨着x的增大,對應的sigmoid函數的值將逼近於1;而隨着x的減小,sigmoid函數的值將逼近於0。而第二幅圖中我們能看到在橫座標的刻度足夠大是,在x=0處sigmoid函數看起來很像階躍函數。
那麼對於公式1,我們可以這樣解釋:爲了實現logistic迴歸分類器,我們可以在每個特徵上都乘以一個迴歸係數,然後把所有的結果值相加,將這個總和帶入sigmoid函數中。進而得到一個範圍在0-1之間的數值。最後設定一個閾值,在大於閾值時判定爲1,否則判定爲0。以上便是邏輯斯諦迴歸算法是思想,公式就是分類器的函數形式。
二、最佳迴歸係數的確定(極大似然估計+最優化方法)
上一節我們已經確定了logisti分類函數,有了分類函數,我們輸入特徵向量就可以得出實例屬於某個類別的概率。但這裏有個問題,權重w(迴歸係數)我們是不確定的。正如我們想的那樣,我們需要求得最佳的迴歸係數,從而使得分類器儘可能的精確。
如何才能獲得最佳的迴歸係數呢?這裏就要用到最優化方法。在很多分類器中,都會將預測值與實際值的誤差的平方和作爲損失函數(代價函數),通過梯度下降算法求得函數的最小值來確定最佳係數。前面我們提到過某件事情發生的概率爲p,在邏輯斯蒂迴歸中所定義的損失函數就是定義一個似然函數做概率的連乘,數值越大越好,也就是某個樣本屬於其真實標記樣本的概率越大越好。如,一個樣本的特徵x所對應的標記爲1,通過邏輯斯蒂迴歸模型之後,會給出該樣本的標記爲1和爲-1的概率分別是多少,我們當然希望模型給出該樣本屬於1的概率越大越好。既然是求最大值,那我們用到的最優化算法就是梯度上升,其實也就是與梯度下降相反而已。
1、我們需要先定義一個最大似然函數L,假定數據集中的每個樣本都是獨立的,其計算公式如下:
L(w)就是我們以上說的對於損失函數的最原始的定義,但我們還要進一步處理,那就是取對數,在進行極大似然估計的時候我們都知道要取對數,那爲什麼我們要取對數呢?
首先,在似然函數值非常小的時候,可能出現數值溢出的情況(簡單點說就是數值在極小的時候因爲無限趨近於0而默認其等於0,具體的可以點解數值移溢出的鏈接看看這篇博客中的講解),使用對數函數降低了這種情況發生的可能性。其次,我們可以將各因子的連乘轉換爲和的形式,利用微積分中的方法,通過加法轉換技巧可以更容易地對函數求導。
2、取似然函數的對數:
這裏似然函數是取最大值的,我們可以直接將其確定爲損失函數然後使用梯度上升算法求最優的迴歸係數。但是既然是損失函數,應該還是取最小值好一點,且我們最耳熟能詳的方法也是梯度下降,很多書中講到這裏也都是用的梯度下降算法,所以我將以上公式取反來使用梯度下降算法來求最小值(說到底都一樣,無非就是取反,一個加一個減)。
爲了更好地理解這一最終損失函數,先了解一下如何計算單個樣本實例的成本:
通過上述公式發現:如果y=0,則第一項爲0;如果y=1,則第二項等於0;
下圖解釋了在不同Φ(z)值時對單一示例樣本進行分類的代價:
從上圖中可以看到,若y=1,在Φ(z)值越大的情況下,紅色的曲線越趨近於0。Φ(z)越大,那麼樣本被正確劃分到1類的概率就越大,這時損失就越小。相對的,Φ(z)越小,意味着樣本越有可能屬於0類,但是我們知道y=1,在樣本屬於1類,所以在分類錯誤的情況下,代價將趨近於無窮。綠線代表着y=0是情況,當然是跟紅線相反,我就不多說了。
接下來我們就要使用梯度下降求最小值了。首先,計算對數似然函數對j個權重的偏導:
在進入下一步之前,先計算一下sigmoid函數的偏導:
將(2)式帶入(1)式中,
我們的目標是求得使損失函數最小化的權重w,所以按梯度下降的方向不斷的更新權重:
由於我們是同時更新所有的權重的,因此可以將更新規則記爲:
其中,定義爲:
所以綜上,我們將梯度下降的更新規則定義爲:
注:如果要用到梯度上升,直接都取反就可以了
三、梯度上升算法(機器學習實戰)
在《機器學習實戰》一書中有講到梯度上升法,個人感覺還是挺通俗易懂的,就直接搬過來了。
梯度上升法基於的思想是:要找到某函數的最大值,最好的方法是沿着該函數的梯度方向探尋。如果梯度記爲,則函數f(x,y)的梯度由下式表示:
這個梯度意味着要沿x的方向移動,沿y的方向移動。其中,函數f(x,y)必須要在待計算的點上有定義並且可微。一個具體的函數例子見下圖。
梯度上升算法到達每個點後都會重新估計移動的方向。從P0開始,計算完該點的梯度,函數就根據梯度移動到下一點P1。在P1點,梯度再次被重新計算,並沿新的梯度方向移動到P2.如此循環迭代,知道滿足通知條件。迭代的過程中,梯度算子總是保證我們能選取到最佳的移動方向
上圖中的梯度上升算法沿梯度方向移動了一步。可以看到,梯度算子總是指向函數值增長最快的方向。這裏所說的是移動方向,而未提到移動量的大小。該量值稱爲步長,記作。用向量來表示的話,梯度上升算法的迭代公式如下:
該公式將一直迭代執行,直至達到某個停止條件爲止,b比如迭代次數達到某個值或者算法達到某個可以允許的誤差範圍。
注:如果是梯度下降法,那就是按梯度上升的反方向迭代公式即可,對應的公式如下:
四、一個簡單的算法
下面這個代碼也是《機器學習實戰》中的,是對以上所講這些東西的簡單實現,用的梯度上升算法,如果大家理解了之前所說的所有的講解,就很容易看懂程序。
// An highlighted block
from numpy import *
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])])
labelMat.append(int(lineArr[2]))
return dataMat, labelMat
def sigmoid(inX):
return 1.0 / (1 + exp(-inX))
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn) # convert to NumPy matrix
labelMat = mat(classLabels).transpose() # convert to NumPy matrix
m, n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n, 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
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] #最佳擬合直線
ax.plot(x, y)
plt.xlabel('X1');
plt.ylabel('X2');
plt.show()
if __name__ =='__main__':
dataArr,labelMat=loadDataSet()
weights=gradAscent(dataArr,labelMat)
plotBestFit(weights.getA())
運行結果:
程序中的最佳擬合直線可能大家會有些疑惑,我說一下。這裏設置了sigmoid函數中的輸入爲0,就如我們在最開始的圖中所看到的,x=0是兩個類的分界線。因此,我們設定,然後解方程得到x1和x2的關係式,即分割線的方程(x0默認等於0)
分類效果還是很不錯的,當然這只是一個簡單的實現,具體的優化算法等大家可以看一些書籍和博客上都有介紹。本人所瞭解到的也就這麼多,有什麼說的不對的地方望見諒,歡迎指正。
參考資料:
1、《機器學習實戰》[美]Peter Harrington 著
2、《統計學習方法》 李航 著
3、《機器學習》 周志華 著
4、https://blog.csdn.net/sinat_29957455/article/details/78944939
5、https://blog.csdn.net/gwplovekimi/article/details/80288964#commentBo