機器學習-logistic迴歸原理與實現

一、什麼是logistic迴歸?

logistic迴歸又叫對數機率迴歸,適合數值型的二值型輸出的擬合,它其實是一個分類模型,比如根據患者的醫療數據判斷它是否能被治癒。

二、logistic迴歸數學原理與算法實現

我們考慮1個輸入的nn維數據x=(x1,x2,,xn)x=(x_1,x_2,\ldots,x_n),我們對輸入數據進行線性加權得到
g(x)=w0+w1x1++wnxn=wTx g(x)=w_{0}+w_{1} x_{1}+\ldots+w_{n} x_{n}=w^{T}x
前面說到,logistic迴歸用於而分類,假設得到的類別爲0或者1,那麼可以使用sigmoid函數處理輸入,這個函數類似於階躍函數但是又是連續型函數,看下這個函數長什麼樣

sigmoid

g(x)g(x)作爲sigmoid函數的輸入,得到
f(x)=11+eg(x) f(x)=\frac{1}{1+e^{-g(x)}}
上式做個簡單的變換
lnf(x)1f(x)=wTx \ln\frac{f(x)}{1-f(x)}=w^Tx
f(x)f(x)視爲類後驗概率估計P(y=1x)P(y=1|x),則上式可以重寫爲
lnP(y=1x)P(y=0x)=wTx \ln \frac{P(y=1 | x)}{P(y=0 | x)}=w^{T}x
那麼從而可以得到
P(y=1x)=f(x)P(y=0x)=1f(x) P(y=1 | x)=f(x)\\ P(y=0 | x)=1-f(x)
所以得到一個觀測值的概率爲
P(yx,w)=[f(x)]y[1f(x)]1y P(y | x,w)=[f(x)]^y \cdot [1-f(x)]^{1-y}
設輸入數據爲X=[x11x12x1nx21x22x2nxm1xm2xmn]={x1,x2,,xm}X=\left[\begin{aligned}&{x_{11}} & {x_{12}} & {\dots} & {x_{1 n}} \\ &{x_{21}} &{x_{22}} & {\dots} & {x_{2 n}} \\ {\vdots} & {\vdots} & {\vdots} & {\dots} & {\vdots} \\ &{x_{m 1}} & {x_{m 2}} & {\dots} & {x_{m n}}\end{aligned}\right]=\{x_1,x_2,\ldots,x_m\}y=[cy1y2ym]y=\left[\begin{aligned}{c}{y_{1}} \\ {y_{2}} \\ {\vdots} \\ {y_{m}}\end{aligned}\right]xix_i表示第ii個輸入數據,上式的似然函數爲
L(w)=i=1m[f(xi)]yi[1f(xi)]1yi L(w)=\prod_{i=1}^{m}\left[f\left(x_{i}\right)\right]^{y_{i}}\left[1-f\left(x_{i}\right)\right]^{1-y_{i}}
然後我們的目標是求出使這一似然函數的值最大的參數估計,最大似然估計就是求出參數w0,w1,,wnw_0,w_1,\ldots,w_n,使得上式取得最大值,對上式兩邊取對數得到
lnL(w)=i=1m(yiln[f(xi)]+(1yi)ln[1f(xi)]) \ln L(w)=\sum_{i=1}^{m}\left(y_{i} \ln \left[f\left(x_{i}\right)\right]+\left(1-y_{i}\right) \ln \left[1-f\left(x_{i}\right)\right]\right)

2.1 梯度上升法估計參數

我們考慮lnL(w)\ln L(w)中間的一部分,對wkw_k求導得到
(yiln[f(xi)]+(1yi)ln[1f(xi)])=yif(xi)[f(xi)]+(1yi)[f(xi)]1f(xi)=[yif(xi)1yi1f(xi)][f(xi)]=(f(xi)yi)g(x)=xik[f(xi)yi] \begin{aligned} & {\left(y_{i} \ln \left[f\left(x_{i}\right)\right]+\left(1-y_{i}\right) \ln \left[1-f\left(x_{i}\right)\right]\right)^{\prime}} \\ & {=\frac{y_{i}}{f\left(x_{i}\right)} \cdot\left[f\left(x_{i}\right)\right]^{\prime}+\left(1-y_{i}\right) \cdot \frac{-\left[f\left(x_{i}\right)\right]^{\prime}}{1-f\left(x_{i}\right)}} \\ & {=\left[\frac{y_{i}}{f\left(x_{i}\right)}-\frac{1-y_{i}}{1-f\left(x_{i}\right)}\right] \cdot\left[f\left(x_{i}\right)\right]^{\prime}} \\ & {=\left(f\left(x_{i}\right)-y_{i}\right) g^{\prime}(x)} \\ & {=x_{i k}\left[f\left(x_{i}\right)-y_{i}\right]} \end{aligned}
那麼
lnL(wk)wk=i=1mxik[f(xi)yi]=0 \frac{\partial \ln L\left(w_{k}\right)}{\partial w_{k}}=\sum_{i=1}^{m} x_{ik}\left[f\left(x_{i}\right)-y_{i}\right]=0
我們使用**梯度上升法(Gradient ascent method)**求解參數ww,其迭代公式爲
w=w+αlnL(w) w=w+\alpha \nabla\ln L(w)
梯度已經在上面計算過了,即
lnL(w)=lnL(w)w \nabla\ln L(w)=\frac{\partial \ln L\left(w\right)}{\partial w}
定義損失誤差爲
E=f(x)y E=f(x)-y
所以我們現在可以得到
w=w+αi=1mxik[f(xi)yi]=w+αXTE w=w+\alpha \sum_{i=1}^{m} x_{ik}\left[f\left(x_{i}\right)-y_{i}\right]=w+ \alpha X^TE

代碼實現

from numpy import *
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties


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(dataMatrix, classLabels):
    dataMatrix = mat(dataMatrix)  # convert to NumPy matrix
    labelMat = mat(classLabels).transpose()  # convert to NumPy matrix
    m, n = shape(dataMatrix)
    alpha = 0.01
    maxCycles = 500
    weights = ones((n, 1))
    weights_list = list()
    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
        weights_list.append(weights)
    return weights, weights_list                                            

    
def plot_weights(weights_list):
    font = FontProperties(fname=r"/System/Library/Fonts/PingFang.ttc", size=12)
    fig = plt.figure(figsize=(8, 8))
    x = range(len(weights_list))
    w0 = [item[0, 0] for item in weights_list]
    w1 = [item[1, 0] for item in weights_list]
    w2 = [item[2, 0] for item in weights_list]
    plt.subplot(311)
    plt.plot(x, w0, 'r-', label="w0")
    plt.ylabel("w0")
    plt.subplot(312)
    plt.plot(x, w1, 'g-', label="w1")
    plt.ylabel("w1")
    plt.subplot(313)
    plt.plot(x, w2, 'b-', label="w2")
    plt.ylabel("w2")
    plt.xlabel("迭代次數", FontProperties=font)
    plt.show()


def plotBestFit(weights):
    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(figsize=(6, 4))
    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.transpose())
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()


def plot_sigmoid():
    x = arange(-60.0, 60.0, 1)
    y = sigmoid(x)
    fig = plt.figure(figsize=(8, 4))
    plt.xlabel('x')
    plt.ylabel('sigmoid(x)')
    plt.plot(x, y.transpose())
    plt.show()


if __name__ == "__main__":
    data_mat, label_mat = loadDataSet()
    weights, weights_list = gradAscent(data_mat, label_mat)
    #plot_weights(weights_list)
    #plotBestFit(weights)
    #plot_sigmoid()

注意:在上程序的第45行使用的是w0+w1x1+w2x2=0w_0+w_1x_1+w_2x_2=0等式,而不是w0+w1x1+w2x2=1w_0+w_1x_1+w_2x_2=1,爲什麼呢?我們觀察sigmoid函數可以發現0值是函數決策邊界,而g(x)=w0+w1x1+w2x2g(x)=w_0+w_1x_1+w_2x_2又是sigmoid函數的輸入,所以另g(x)=0g(x)=0便可以得到分類邊界線。

上述程序的運行結果如下圖所示。

logistic regression

迭代次數與最終參數的變化如下圖所示。
logistic iteration

2.2 改進的隨機梯度上升法

梯度上升算法在每次更新迴歸係數(最優參數)時,都需要遍歷整個數據集。假設,我們使用的數據集一共有100個樣本,迴歸參數有 3 個,那麼dataMatrix就是一個100×3的矩陣。每次計算h的時候,都要計算dataMatrix×weights這個矩陣乘法運算,要進行100×3次乘法運算和100×2次加法運算。同理,更新迴歸係數(最優參數)weights時,也需要用到整個數據集,要進行矩陣乘法運算。總而言之,該方法處理100個左右的數據集時尚可,但如果有數十億樣本和成千上萬的特徵,那麼該方法的計算複雜度就太高了。因此,需要對算法進行改進,我們每次更新迴歸係數(最優參數)的時候,能不能不用所有樣本呢?一次只用一個樣本點去更新迴歸係數(最優參數)?這樣就可以有效減少計算量了,這種方法就叫做隨機梯度上升算法(Stochastic gradient ascent)

def stocGradAscent(dataMatrix, classLabels, numIter=150):
    dataMatrix = mat(dataMatrix)
    labelMat = mat(classLabels).transpose()
    m,n = np.shape(dataMatrix)                                                
    weights = np.ones((n,1))  
    weights_list = list()                                                    
    for j in range(numIter):                                           
        dataIndex = list(range(m))
        for i in range(m):           
            alpha = 4/(1.0+j+i)+0.01            
            randIndex = int(random.uniform(0,len(dataIndex)))              
            h = sigmoid(sum(dataMatrix[randIndex]*weights))                   
            error = classLabels[randIndex] - h                               
            weights = weights + alpha * dataMatrix[randIndex].transpose() * error 
            del(dataIndex[randIndex])  
            weights_list.append(weights)                                      
    return weights, weights_list 

該算法第一個改進之處在於,alpha在每次迭代的時候都會調整,並且,雖然alpha會隨着迭代次數不斷減小,但永遠不會減小到0,因爲這裏還存在一個常數項。必須這樣做的原因是爲了保證在多次迭代之後新數據仍然具有一定的影響。如果需要處理的問題是動態變化的,那麼可以適當加大上述常數項,來確保新的值獲得更大的迴歸係數。另一點值得注意的是,在降低alpha的函數中,alpha每次減少1/(j+i)1/(j+i),其中j是迭代次數,i是樣本點的下標。第二個改進的地方在於跟新迴歸係數(最優參數)時,只使用一個樣本點,並且選擇的樣本點是隨機的,每次迭代不使用已經用過的樣本點。這樣的方法,就有效地減少了計算量,並保證了迴歸效果。

使用隨機梯度上升法獲得的分類結果如下圖所示

Stochastic gradient ascent

迭代次數與最終參數的變化如下圖所示,可以看到,在 8000 次以後,各參數基本趨於穩定,這個過程大約迭代了整個矩陣 80次,相比較於原先的 300 多次,大幅減小了迭代週期。

Stochastic gradient iteration

三、sklearn算法實現

sklearn實現 logistic 迴歸使用的是linear_model模型,還是使用上述的數據,代碼如下

def logistic_lib(dataMatrix, classLabels):
    from sklearn import linear_model
    model_logistic_regression = linear_model.LogisticRegression(solver='liblinear',max_iter=10)
    classifier = model_logistic_regression.fit(dataMatrix, classLabels)
    # 迴歸係數
    print(model_logistic_regression.coef_)
    # 截距
    print(model_logistic_regression.intercept_)
    # 準確率
    accurcy = classifier.score(dataMatrix, classLabels) * 100
    print(accurcy)

輸出結果如下

[[ 2.45317293  0.51690909 -0.71377635]]
[2.45317293]
97.0

參考

[1] CSDN博客-logistic迴歸原理解析–一步步理解
[2] CSDN博客-logistic迴歸原理及公式推導
[3] 李航. 統計學習方法, 清華大學出版社

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章