機器學習之Logistic迴歸(五)

主要內容
● Sigmoid函數和Logistoc迴歸分類器
● 最優化理論初步
● 梯度下降最優化算法
● 數據中的缺失項處理

我們將介紹最優化算法,並利用他們訓練出一個非線性函數用於分類。

利用Logistic迴歸的主要思想是:根據現有的數據對分類邊界線建立迴歸公式,以此進行分類。
“迴歸”一詞源於最佳擬合,表示要找到最佳擬合參數,使用的是最優化算法。


Logistic迴歸一般過程:
1. 收集數據:採用任意方法收集數據
2. 準備數據:由於需要進行距離計算,因此要求數據類型爲數值型,另外,結構化數據爲最佳。
3. 分析數據:採用任意方法對於數據進行分析
4. 訓練算法:大部分時間將用於訓練,並將其轉換爲對應的結構化數據
5. 測試算法:一旦訓練步驟完成,分類將會很快;
6. 使用算法:第一,我們需要輸入一些數據,並將其轉換爲對應的結構化數值;第二,基於訓練好的迴歸係數就可以對這些數值進行簡單的迴歸計算,判斷他們屬於哪個類別,在這之後,我們就可以輸出的類別上做一些其他分析工作。

5.1 基於logistic迴歸和sigmoid函數的分類

logistic迴歸:
優點:計算代價不高,易於理解和實現;
缺點:容易欠擬合,分類精度可能不高。
適用數據類型:數值型和標稱型數據。


下面進入數學理論部分:

我們想要的是能接受所有的輸入然後預測出類別。該函數被稱之爲 海維塞德階躍遷函數 或者 單位躍遷函數。
有一個有類似於可以輸出0或者1這種性質的函數,就是Sigmoid函數。
Sigmoid計算公式如下:
這裏寫圖片描述

下圖給出了Sigmoid函數在不同座標尺下的曲線圖,當x爲0時,Sigmoid函數值爲0.5 ,隨着x增大,Sigmoid函數的值無限接近於1,隨着x的減小,Sigmoid函數的值無限接近於0 。 實際上,Sigmoid函數看起來像一個階躍函數。

因此,爲了實現 Logistic迴歸分類器,我們可以在每個特徵上都乘以一個迴歸係數,然後把所有的結果值相加,將這個總和代入Sigmoid函數 ,進而得到一個範圍在0~1之間的數值。所有大於0.5的數據被分入1類,所有小於0.5的被分入0類。實際上,Logistic迴歸也可以被看做是一種概率估計。

確定了分類器的函數形式之後,問題就變成了:最佳迴歸係數爲多少?如何確定大小?

這裏寫圖片描述

5.2 基於最優化方法的最佳迴歸係數確定

Sigmoid函數的輸入記爲z,公式如下:
這裏寫圖片描述

其中x是分類器的輸入數據,向量w是我們要找的最佳參數。

爲了尋找最佳參數,我們介紹一下梯度上升的最優化方法。我們將使用梯度上升的最優化方法來尋找最佳參數。

5.2.1 梯度上升法

梯度上升法基本思想:要找到某函數的最大值,最好的方法就是沿着該函數的梯度方向探尋。如果梯度爲 ▽ ,則函數f(x,y)的梯度如下表示:
這裏寫圖片描述

梯度要沿着x的方向移動 (∂f(x+y))/∂x

梯度要沿着y的方向移動 (∂f(x+y))/∂y

這裏寫圖片描述
梯度上升算法的迭代公式:

w: = w + α▽f(w)

5.2.2 訓練算法:使用梯度上升找到最佳參數

圖5-3 有100個樣本點:每個點包含兩個數值型特徵:X1和X2.。我們將通過使用梯度上升法找到最佳迴歸係數,也就是擬合出 Logistic迴歸 模型的最佳參數。

梯度上升法的僞代碼如下:

每個迴歸係數初始化爲1
重複R次:
       計算整個數據集的梯度
       使用alpha * gradient 更新迴歸係數的向量
       返回迴歸係數

下面的代碼是梯度上升算法的具體實現,創建名爲 logRegres.py的文件,輸入代碼:
程序5-1 Logistic迴歸梯度上升最優化算法

import math
from numpy import *

def loadDataSet():
    dataMat = []; labelMat = []
    fr = open(r'E:\ML\ML_source_code\mlia\Ch05\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):
    #調用numpy函數可以將數組轉行爲矩陣數據類型
    dataMatrix = mat(dataMatIn)
    #將矩陣轉置
    labelMat = mat(classLabels).transpose()
    #看一下dataMatrix的大小,3*100的矩陣
    m,n = shape(dataMatrix)
    #目標移動步長
    alpha = 0.01
    #迭代次數
    maxCycles =500
    #n已經賦值爲3
    weights = ones((n,1))
    for k in range(maxCycles):
        h = sigmoid(dataMatrix*weights)
        error = (labelMat - h)
        weights = weights + alpha * dataMatrix.transpose() * error
    return weights

接下來測試代碼,載入這個包

In [1]: import os

In [2]: os.chdir('E:\ML\ML_IA\Logistic')

In [3]: import logRegres

In [4]: import logRegres

In [5]: reload(logRegres)
Out[5]: <module 'logRegres' from 'logRegres.py'>

In [6]: a,b= logRegres.loadDataSet()

In [40]: logRegres.gradAscent(a,b)
Out[40]:
matrix([[ 4.12414349],
        [ 0.48007329],
        [-0.6168482 ]])

5.3.2 分析數據:畫出決策邊界

上面解出的迴歸係數,確定了不同類別數據之間的分割線。那麼如何畫出分割線呢?

程序5-2 畫出數據集和Logistic 迴歸最佳擬合直線的曲線

import matplotlib.pyplot as plt
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]) == i:
            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()

我們最終需要解出X1和X2之間的關係式,運行代碼,

In [24]: import logRegres

In [25]: reload(logRegres)
Out[25]: <module 'logRegres' from 'logRegres.py'>

In [26]: a,b = logRegres.loadDataSet()

In [27]: logRegres.gradAscent(a,b)
Out[27]:
matrix([[ 4.12414349],
        [ 0.48007329],
        [-0.6168482 ]])

In [28]: weights = logRegres.gradAscent(a,b)

In [29]: logRegres.plotBestFit(weights.getA())

這裏寫圖片描述

梯度上升算法在500次迭代後得到的Logistic迴歸最佳擬合曲線

最終圖上看上去是錯分了2個點。
儘管例子比較簡單,但是卻需要大量的計算。後面我們將對算法作以改進。

5.2.4 訓練算法:隨機梯度上升

梯度上升算法在每次更新迴歸係數時都需要去遍歷整個數據集,該方法在處理100個左右的數據集時速度可以,如果去處理數億或者幾千萬的特徵,這個方法的複雜度就太高了。
我們改進的方法就是一次僅用一個樣本點來更新迴歸係數,這個方法叫做隨機梯度上升算法。
由於可以在新樣本到來時對分類器進行增量式更新,因而隨機梯度上升算法是一個在線學習算法。與“在線學習”相對應,一次處理的所有數據被稱之爲“批處理”。
隨機梯度上升算法可以寫成如下的僞代碼:

雖有的迴歸係數初始都爲1
對數據集中每個樣本
       計算該樣本的梯度
       使用alpha*gradient更新迴歸係數值
返回歸係數數值

程序5-3 隨機梯度上升算法

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

隨機梯度算法與梯度上升算法有一定的區別:
1.後者的h和誤差error都是向量,而前者則是數值;
2.前者沒有矩陣的轉換過程,所有變量的數據類型都是Numpy數組。

In [38]: import logRegres

In [39]: reload(logRegres)
Out[39]: <module 'logRegres' from 'logRegres.pyc'>

In [40]: a,b = logRegres.loadDataSet()

In [41]: weights = logRegres.stocGradAscent1(array(a),b)

In [42]: weights
Out[42]: array([ 1.01702007,  0.85914348, -0.36579921])

In [43]: logRegres.plotBestFit(weights)

這裏寫圖片描述

隨機梯度上升算法在上述數據集上執行結果,最佳擬合直線並非最佳分類線

本次運行後的曲線如果與上面的圖進行比較,其實不如上面的完美。那麼主要是因爲上面的圖迭代了500次。

因爲數據波動或者高頻波動,且收斂速度較慢,所以我們需要對算法改進。

值得注意的是:主要做了三個方面的改進:
1. alpha在每次迭代的時候都會調整,這會緩解數據波動或者高頻波動。
2. 通過隨機選取樣本來更新迴歸係數,這樣可以減少週期性波動
3. 增加了一個迭代參數作爲第三個參數,算法將按照給的新的參數值進行迭代

程序5-4 改進的隨機梯度算法

# 改進的隨機梯度算法
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
    m,n = shape(dataMatrix)
    weights = ones(n)
       """
    i 是樣本點的下標,j 是迭代次數
    """
    for j in range(numIter):         
        dataIndex = range(m)
        for i in range(m):
            alpha = 4/(1.0 + j + i) + 0.01  #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

下面所用的計算量更少:

In [43]: reload(logRegres)
Out[43]: <module 'logRegres' from 'logRegres.pyc'>

In [44]: a,b = logRegres.loadDataSet()

In [45]: weights = logRegres.stocGradAscent1(array(a),b)

In [46]: logRegres.plotBestFit(weights)

這裏寫圖片描述

5.3 例子:疝氣病症預測病馬的死亡率

我們將使用logistics迴歸來預測患有疝病的馬的存活問題。該數據有部分指標主觀和難以測量,而且存在30%的缺失值。

步驟:

1.收集數據:給定數據文件;
2.準備數據:用python解析文本文件並填充缺失值;
3.分析數據:可視化並觀察數據;
4.訓練算法:使用最優化算法,得到最佳的係數;
5.測試算法:爲了量化迴歸的效果,我們需要觀察錯誤率。再去根據錯誤率判斷,我們是否需要回退到訓練階段,通過改變迭代的次數和步長等參數來得到更好的迴歸係數;
6.使用算法:實現一個簡單的命令行程序來手機馬的症狀並輸出預測結果。

5.3.1 準備數據:處理數據中的缺失數據

數據的缺失值處理是一個非常棘手的問題,有時候複雜業務環境下數據相當昂貴,我們不能簡單的去丟棄掉,或者重新獲取。
方法:
● 使用可用特徵的均值來填補缺失值 ;
● 使用特殊值來填補缺失值,如-1 ;
● 忽略有缺失值的樣本;
● 使用相似的樣本的均值添補缺失值;
● 使用另外的機器學習算法來預測缺失值;

我們現在要對要用的數據集做預處理。
1.所有的缺失值必須使用一個實數值來替換,因爲我們使用numpy數據類型不允許包含缺失值,這裏選擇實數0來替換所有的缺失值,恰好使用會迴歸。這樣做是因爲需要一個在更新的時候不會影響係數的值。
迴歸係數更新公式如下:

weights = weights + alpha error dataMatrix[randIndex]

2.如果數據集中類別標籤已經缺失,那麼就丟棄。

5.3.2 測試算法:用logistic迴歸進行分類

程序清單5-5 logistic 迴歸分類函數

# 疝氣病預測兵馬死亡率:Logistic迴歸分類器

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

def colicTest():
    frTrain = open(tem_dir + "\\" + "horseColicTraining.txt")
    frTest = open(tem_dir + "\\" + "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]))
    #stocGradAscent1計算迴歸係數
    trainWeights = stocGradAscent1(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(array(lineArr),trainWeights)) != int(currLine[21]):
            errorCount += 1
    errorRate = (float(errorCount)/numTestVec)
    print "the error rate of this test is: %f" %errorRate
    return errorRate

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))

測試代碼,結果如下:

In [46]: reload(logRegres)
Out[46]: <module 'logRegres' from 'logRegres.py'>

In [47]: logRegres.multiTest()
the error rate of this test is: 0.313433
the error rate of this test is: 0.313433
the error rate of this test is: 0.313433
the error rate of this test is: 0.283582
the error rate of this test is: 0.417910
the error rate of this test is: 0.328358
the error rate of this test is: 0.462687
the error rate of this test is: 0.313433
the error rate of this test is: 0.358209
the error rate of this test is: 0.388060
after 10 iterations the average error rate is:0.349254

10次迭代之後的結果是34.9% 。因爲數據本身就有30%的缺失,得到的錯誤率結果並不差。
如果手動去調整 colicTest() 函數中的迭代次數和 stocGradAscent1() 中的步長,那麼最終的平均錯誤率就在20%左右。

5.4 小結
Logistic 迴歸的目的是尋找一個非線性函數sigmoid的最佳擬合參數。求解過程可以由最優化算法來完成。在最優化的算法中,最常用的就是梯度上升算法,梯度上升算法又可以簡化爲隨機梯度上升算法。
隨機梯度上升算法和梯度上升算法的效果相當,但佔用更少的計算資源。此外,隨機梯度是一種在線算法,可以在數據到來時就完成參數的更新,而不需要重新讀取整個數據集來進行批處理運算。
機器學習的一個重要的問題就是如何處理缺失數據,這個問題沒有標準答案,取決於實際的需求。

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