1. 程式人生 > >《機器學習實戰》學習筆記之第五章—— Logistic迴歸

《機器學習實戰》學習筆記之第五章—— Logistic迴歸

第五章 Logistic迴歸

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

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

迴歸:對一些資料點,演算法訓練出直線引數,得到最佳擬合直線,能夠對這些點很好的擬合。

訓練分類器主要是尋找最佳擬合引數,故為最優化演算法。

5.1 基於Logistic迴歸和sigmoid函式的分類

實現Logistic迴歸分類器:在每個特徵上都乘以一個迴歸係數,然後把所有的結果值相加,總和帶入sigmoid函式,其結果大於0.5分為第0類,結果小於0.5分為第0類。

Sigmoid函式公式:

sigmoid函式具有很好的性質,如其導數可以用其本身表示等等。

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

sigmoid函式輸入z:


其可以寫成z=w.T*x,向量x為分類器的輸入資料, w為訓練器尋找的最佳引數。

梯度上升法:

思想:要找到某函式的最大值,最好的方法是沿著該函式的梯度方向探尋。

函式f(x,y)的梯度:

沿x的方向移動,沿y的方向移動,最後能夠到達最優點,但是f(x,y)在待計算點需要有定義並且可微

梯度運算元總是指向函式值增長最快的方向。移動方向為梯度方向,移動量大小需要乘以一個引數,稱之為步長。引數迭代公式為:


公式可一直執行,直到某個條件停止為止。如迭代次數或者演算法達到某個可以允許的誤差範圍。

訓練演算法:使用梯度上升找到最佳引數。

梯度上升法虛擬碼:


基於上面的內容,我們來看一個Logistic迴歸分類器的應用例子,從圖5-3可以看到我們採的資料集。
資料點:


5.2.2訓練演算法:使用梯度上升找到最佳引數(python):

# -*- coding: utf-8 -*-

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])])   #X0=1,X1,X2
        #print(dataMat)
labelMat.append(int(lineArr[2])) #label
        #print (labelMat)
    #print(dataMat,labelMat)
return dataMat, labelMat
def sigmoid(inX):
    return longfloat(1.0/(1+exp(-inX)))
#梯度上升
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)             #convert to NumPy matrix
labelMat = mat(classLabels).transpose() #convert to NumPy matrix,and 轉置
m, n = shape(dataMatrix)                #行數m=100,列數n=3
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      #3*1
def plotBestFit(weights):
    import matplotlib.pyplot as plt
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0]    #行數n
xcord1 = []; ycord1 = []
    xcord2 = []; ycord2 = []
    #統計label
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)      #(-3,3)之間間隔0.1
    # 擬合曲線為0 = w0*x0+w1*x1+w2*x2, 故x2 = (-w0*x0-w1*x1)/w2, x0為1,x1為x, x2為y,故有
y = (-weights[0]-weights[1]*x)/weights[2]     #z等於0,是sigmiod函式的分界線
ax.plot(x, y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()
#測試程式
datamat, labels = loadDataSet()
weights = gradAscent(datamat, labels)
#x為array格式,weights為matrix格式,故需要呼叫getA()方法,其將matrix()格式矩陣轉為array()格式
#getA()方法,其將matrix()格式矩陣轉為array()格式,type(weights),type(weights.getA())可觀察到。
plotBestFit(weights.getA())
輸出結果,最優分界線:


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

梯度上升演算法中,每次更新迴歸係數需要遍歷整個資料集。資料量若是大了,計算複雜度較高。

改進方法:一次僅用一個樣本點更新迴歸係數,這便是隨機梯度上升演算法。

虛擬碼:

程式碼:

#隨機梯度上升
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
weights0 = stocGradAscent0(array(datamat),labels)
plotBestFit(weights0)

程式碼結果顯示:


Figure 5-4: 隨機梯度上升演算法分割線

結果顯示其效果還不如梯度上升演算法,不過不一樣,梯度上升演算法,500次迭代每次都用上了所有資料,而隨機梯度上升演算法總共也只用了500次。需要對其進行改進:

#改進的隨機梯度上升
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.0001    #apha decreases with iteration, does not 
randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
h = sigmoid(sum(dataMatrix[randIndex]*weights))
            #print(dataMatrix[randIndex], weights)
error = classLabels[randIndex] - h
            weights = weights + alpha * error * dataMatrix[randIndex]
            del(dataIndex[randIndex])
    return weights
weights1 = stocGradAscent1(array(datamat), labels)
plotBestFit(weights1)

Figure 5-5: 改進的隨機梯度上升演算法分割線

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

準備資料:處理資料中的缺失值

可選做法:

  • 使用可用特徵的均值來填補缺失值
  • 使用特殊值來填補缺失值,如-1
  • 忽略有缺失值的樣本
  • 使用相似樣本的均值添補缺失值
  • 使用另外的機器學習演算法預測缺失值

測試演算法:用Logistic迴歸進行分類

def classifyVector(inX, weights):
    prob = sigmoid(sum(inX*weights))
    if prob > 0.5:
        return 1.0
else:
        return 0.0
def colicTest():
    frTrain = open('horseColicTraining.txt')
    frTest = open('horseColicTest.txt')
    trainingSet = []; trainingLabels = []
    for line in frTrain.readlines():
        currLine = line.strip().split('\t')
        lineArr =[]
        for i in range(21):                            #21個特徵,1個label
lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[21]))
    #print(trainingSet)
trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 1000)
    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))

multiTest()

Figure 5-7: 測試結果

5.4 小結

Logistic迴歸:

Logistic迴歸目的是尋找一個非線性函式Sigmoid 的最佳擬合引數,求解過程可以由最優化演算法來完成。在最優化演算法中,最常用的就是梯度上升演算法,而梯度上升演算法又可以簡化為隨機梯度上升演算法。
隨機梯度上升演算法與梯度上升演算法的效果相當,但佔用更少的計算資源。此外,隨機梯度上升是一個線上演算法,它可以在新資料到來時就完成引數更新,而不需要重新讀取整個資料集來進行批處理運算。