[完]機器學習實戰 第五章 Logistic迴歸(Logistic Regression)

Logistic迴歸的目的是尋找一個非線性函數Sigmoid的最佳擬合參數,求解過程可由最優化算法來完成,一般採用梯度上升算法,此算法又可簡化爲隨機梯度上升算法。簡化前後的算法效果相當,但佔用更少的計算資源。並且隨機梯度上升算法是一個在線算法,可在新數據到來時就完成參數的更新,而無需重新讀取整個數據集來進行批處理。機器學習的一個重要問題是處理缺失數據,處理方法取決於實際需求。

假設有一些數據點,可用一條直線對這些點進行擬合(該線稱爲最佳擬合直線),這個擬合的過程就成爲迴歸。Logistic迴歸進行分類的主要思想是:根據現有數據對分類邊界線建立迴歸公式,以此進行分類。

訓練分類器用於尋找最佳擬合參數,也稱爲最佳的分類迴歸係數。Logistic需要距離計算,因此要求數據類型爲數值型,結構化數據格式最佳。

海維賽德階躍函數(Heaviside step function)也稱爲單位階躍函數,此函數的問題在於在跳躍點上從0瞬間跳躍到1,這很難處理。而Sigmoid函數,也具有類似的性質,計算公式如下:

σ(z)=11+ez

爲了實現Logistic迴歸分類器,可以在每個特徵上都乘以一個迴歸係數,然後把所有的結果相加,將這個總和代入Sigmoid函數中,進而得到一個範圍在0~1的數值。大於0.5分入1類,否則歸入0類。

Sigmoid函數的輸入記爲z ,有下面公式得出:

z=w0x0+w1x1+w2x2+...+wnxn

採用向量寫法,上述公式可寫成z=wTx ,其中向量x 是分類器的輸入數據,向量w 是要尋找的最佳迴歸係數。

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

f(x,y)=f(x,y)xf(x,y)y

這個梯度意味着沿x 方向移動f(x,y)x ,沿y 方向移動f(x,y)y 。且函數f(x,y) 在待計算的點上有定義且可微。

梯度算子總是指向函數值增長最快的方向。這裏說的是移動方向,而未提到移動量的大小。該量值稱爲步長,記做α 。用向量表示,梯度上升法的迭代公式如下:

w:=w+αwf(w)

該公式會一直迭代下去,直至達到某個停止條件爲止,比如迭代次數達到某個指定值或算法達到某個可以允許的誤差範圍。

梯度上升法每次更新迴歸係數時都需要遍歷整個數據集,計算複雜度太高。一個改進的方法是一次僅用一個樣本點來更新迴歸係數,該方法稱爲隨機梯度上升算法。由於可以在新樣本到來時對分類器進行增量式更新,因此,隨機上升算法是一個在線學習算法,並且沒有矩陣轉換過程,所有變量的數據類型都是NumPy數組。與“在線學習”相對應,一次處理所有數據被稱爲“批處理”。

隨機梯度上升法,迴歸係數經過大量迭代才能達到穩定值,且在大的波動停止後,仍有小的週期性波動,產生這種現象的原因是存在一些不能正確分類的樣本點(數據集並非線性可分)

改進的隨機梯度上升算法,改進有兩處:1、alpha在每次迭代的時候都會調整,這可緩解數據波動或者高頻振動,雖然alpha隨着迭代次數不斷減小,但永遠不會減小到0,這是因爲alpha=4/(1.0+j+i)+0.01中存在一個常數項。這樣多次迭代之後新數據仍然具有一定的影響力。避免參數嚴格下降也常見有模擬退火算法。2、通過隨機選取樣本來更新迴歸係數,可減少週期性波動。

處理數據中的缺失值:1、使用可用特徵的均值來填補缺失值;2、使用特殊值來填補缺失值,如-1、0,選擇使用0替換所有缺失值,恰好能適用於Logistic迴歸,0在更新時不會影響係數的值;3、忽略有缺失值的樣本;4、使用相似樣本的均值填補缺失值;5、使用另外的機器學習算法預測缺失值。

如果在測試數據集中發現一條數據的類別標籤已經缺失,可簡單將其丟棄。這是因爲類別標籤與特徵不同,很難確定採用某個合適的值來替換。採用Logistic迴歸進行分類這種做法是合理的,如果採用類似kNN的方法就可能不太可行。

使用的函數

函數 功能
mat1.transpose() 求矩陣mat1的轉置
mat(dataMat) 將輸入的數據dataMat轉換成矩陣
plt.xlabel('X1') 設置x軸的文本
plt.ylabel('X2') 設置y軸的文本
mat1.getA() 將mat1轉化成ndarray數組
random.uniform(x,y) 隨機生成一個實數,它在[x,y]範圍內。

程序代碼

# coding=utf-8

import numpy as np

# 加載數據
def loadDataSet() :
    dataMat = []; labelMat = []
    fr = open('c:\python27\ml\\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

# 階躍函數--sigmoid()函數 
def sigmoid(inX) :
    return 1.0/(1+np.exp(-inX))

# logistic迴歸梯度上升優化算法
def gradAscent(dataMatIn, classLabels) :
    dataMatrix = np.matrix(dataMatIn)
    labelMat = np.matrix(classLabels).transpose()
    m,n = np.shape(dataMatrix)
    # alpha項目表移動的步長
    alpha = 0.001
    # maxCycles迭代次數
    maxCycles = 500
    weights = np.ones((n,1))
    for k in range(maxCycles) :
        h = sigmoid(dataMatrix*weights)
        error = labelMat - h
        # 梯度上升
        weights = weights + alpha * dataMatrix.transpose() * error
    return weights


# 畫出數據集和Logistic迴歸最佳擬合直線的函數
def plotBestFit(weights) :
    import matplotlib.pyplot as plt
    dataMat, labelMat=loadDataSet()
    dataArr = np.array(dataMat)
    n = np.shape(dataArr)[0]
    xcord1=[]; ycord1=[]
    xcord2=[]; ycord2=[]
    for i in range(n) :
        # 將標籤爲1的數據元素和爲0的分別放在(xcode1,ycode1)、(xcord2,ycord2)
        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')
    # 繪製出w0 + w1*x + w2*y = 0的直線
    x = np.arange(-3.0, 3.0, 0.1)
    y = (-weights[0]-weights[1]*x)/weights[2]
    ax.plot(x, y)
    # x,y軸顯示的文字
    plt.xlabel('X1'); plt.ylabel('X2')
    plt.show()  

# 隨機梯度上升算法
# 參數dataMatrix是numpy數組類型數據,傳入矩陣,需要np.array(matrix)轉換一下
def stocGradAscent0(dataMatrix, classLabels) :
    m,n = np.shape(dataMatrix)
    alpha = 0.01
    weights = np.ones(n)
    # h,error 都是數值,而非向量,一次僅用一個樣本來更新迴歸係數
    for i in range(m) :
        h = sigmoid(sum(dataMatrix[i]*weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights


# 改進的隨機梯度上升算法
def stocGradAscent1(dataMatrix, classLabels, numIter=150) :
    m,n = np.shape(dataMatrix)
    weights = np.ones(n)
    for j in range(numIter) :
        dataIndex = range(m)
        for i in range(m) :
            # alpha每次迭代時需要調整,緩解數據波動或者高頻振動
            alpha = 4/(1.0+j+i) + 0.01
            # 隨機選取更新
            randIndex = int(np.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

# inX, 輸入的特徵向量
# weights, 迴歸係數
def classifyVector(inX, weights) :
    prob = sigmoid(sum(inX*weights))
    if prob > 0.5 : return 1.0
    else : return 0.0

# 打開測試集和訓練集(患疝病的馬的存貨問題),使用測試集進行500迭代的Logistic迴歸,
# 計算出迴歸參數,並根據測試集,得出訓練模型的錯誤率
def colicTest() :
    # 打開測試集和訓練集
    frTrain = open('c:\python27\ml\\horseColicTraining.txt')
    frTest = open('c:\python27\ml\\horseColicTest.txt')
    trainingSet = []; trainingLabels = []
    for line in frTrain.readlines() :
        currLine = line.strip().split('\t')
        lineArr = []
        for i in range(21) :
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[21]))
    trainWeights = stocGradAscent1(np.array(trainingSet), trainingLabels, 500)
    errorCount = 0; numTestVec = 0.0
    for line in frTest.readlines() :
        numTestVec += 1.0
        currLine = line.strip().split('\t')
        lineArr = []
        for i in range(21) : 
            lineArr.append(float(currLine[i]))
        if int(classifyVector(np.array(lineArr), trainWeights)) != int(currLine[21]) : 
            errorCount += 1
    errorRate = (float(errorCount)/numTestVec)
    print "the error rate of this test is: %f" % errorRate
    return errorRate

# 執行10次colicTest()並返回平均值    
def multiTest() :
    numTests = 10; errorSum = 0.0
    for k in range(numTests) :
        errorSum += colicTest()
    print "after %d iterations the average error rate is: %f" \
        % (numTests, errorSum/float(numTests))      

在命令行中執行:

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

# 畫出數據集和決策邊界(Logistic迴歸最佳擬合直線),生成的圖,如末尾圖1
>>> import ml.logRegres as logRegres
>>> dataArr, labelMat=logRegres.loadDataSet()
>>> weights=logRegres.gradAscent(dataArr, labelMat)
>>> logRegres.plotBestFit(weights.getA())

# 隨機梯度上升算法,繪製的擬合直線,如末尾圖2
>>> from numpy import *
>>> import ml.logRegres as logRegres
>>> dataArr, labelMat=logRegres.loadDataSet()
>>> weights=logRegres.stocGradAscent0(array(dataArr), labelMat)
>>> logRegres.plotBestFit(weights)

# 改進的隨機梯度上升算法,繪製的擬合直線,如末尾圖3,圖4
>>> reload(logRegres)
<module 'ml.logRegres' from 'C:\Python27\ml\logRegres.py'>
>>> weights=logRegres.stocGradAscent1(array(dataArr), labelMat)
>>> logRegres.plotBestFit(weights)
>>> weights=logRegres.stocGradAscent1(array(dataArr), labelMat, 500)
>>> logRegres.plotBestFit(weights)

# 患有疝病的馬的存活問題
>>> reload(logRegres)
<module 'ml.logRegres' from 'C:\Python27\ml\logRegres.py'>
>>> logRegres.multiTest()
the error rate of this test is: 0.358209
the error rate of this test is: 0.432836
the error rate of this test is: 0.388060
the error rate of this test is: 0.283582
the error rate of this test is: 0.432836
the error rate of this test is: 0.388060
the error rate of this test is: 0.328358
the error rate of this test is: 0.313433
the error rate of this test is: 0.402985
the error rate of this test is: 0.328358
after 10 iterations the average error rate is: 0.365672


繪製數據集和決策邊界
圖1 繪製數據集和決策邊界
隨機梯度上升算法擬合直線
圖2 隨機梯度上升算法擬合直線
改進的隨機梯度上升算法擬合直線
圖3 改進的隨機梯度上升算法擬合直線(默認迭代150次)
改進的隨機梯度上升算法擬合直線(迭代500次)
圖4 改進的隨機梯度上升算法擬合直線(迭代500次)

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