1. 程式人生 > >【機器學習】邏輯迴歸基礎知識+程式碼實現

【機器學習】邏輯迴歸基礎知識+程式碼實現

1. 基本概念

邏輯迴歸用於二分類,將對輸入的線性表示對映到0和1之間,輸出為label為1的概率。

優點:實現代價低,可輸出分類概率。適用於資料線性不可分。

缺點:容易欠擬合,分類精度可能不高,且僅限二分類。

使用資料型別:數值型和標稱資料。

邏輯迴歸本質也是線性迴歸,但是是將線性迴歸對映到0/1分類上,因此邏輯迴歸用於分類。

2. 公式推導

單個輸入樣本為 x =[1,x_1, x_2,...,x_n],第一項為1是為了直接把截距b加入到權重w矩陣中,方便計算。y = [y_1,y_2,...,y_n]為正確的標籤類別。共有m個樣本。

迴歸函式:

\\ \hat {y} = \sigma(wx) \\ \sigma(z) = \frac{1}{1+exp(-z)} \\\sigma(z)' = \sigma(z)(1-\sigma(z))

屬於不同類別的概率:

\\ p(y=1|x) = \hat{y} = \sigma(wx) \\p(y=0| x) = 1-\hat{y} = 1-\sigma(wx)

則分類正確的概率:

p(y|x) = \hat{y}^y * (1-\hat{y})^{(1-y)}

則對於所有樣本,分類正確的最大似然估計為:

P = \prod_{i=1}^{m}p(y^{<i>}|x^{<i>}) = \prod_{i=1}^{m} (\hat{y}^y * (1-\hat{y})^{(1-y)})

取對數:

J = logP =log \prod_{i=1}^{m}p(y^{<i>}|x^{<i>}) = \sum_{i=1}^{m} (ylog\hat{y} + (1-y)log(1-\hat{y}))

即損失函式為上述對數似然函式,我們的目標是最大化對數似然函式(也可以是最小化負對數似然函式)。

已知損失函式關於w的導數為:

\frac{\partial J}{\partial w} = x(y-\hat{y})(推導過程如下圖)(該結果與LMS類似)

由於是最大化問題,則權重更新公式為梯度上升更新公式:

w = w + \alpha \frac{\partial J}{\partial w} 

3. 訓練細節

3.1 梯度上升 vs 隨機梯度上升

梯度上升:在整個資料集(訓練集)上計算一次損失函式,更新一次權重。

隨機梯度上升:對於每個樣本,都更新一次權重。

簡單的梯度上升,由於異常點的存在可能會減緩收斂且造成資料較大的波動。因此引入隨機梯度上升。

3.2 隨機梯度上升改進

1) 進行多輪,即引入迭代次數。提升分類準確率。

2) 隨著訓練的進行,改變步長alpha(類似於深度學習裡面的對學習率的自適應)。

\alpha = \frac{4}{i+j+1} + 0.01

初始時alpha較大,隨著進行論述的增加,alpha減小。加快收斂的同時,可減緩資料的波動。

3) 每次SGA時,隨機選取樣本點用於計算梯度,也減緩了資料的波動。

3.3 缺失資料的處理

若對於樣本資料,每個特徵值缺失,解決方法有:

  • 使用可用特徵均值填補缺失特徵;
  • 使用特殊值來填補缺失特徵,一般取特徵值不會取到的值(比如正常特徵值為整數的話,則可取-1);
  • 使用相似樣本的均值填補缺失特徵;
  • 忽略帶有缺失特徵值的樣本;
  • 使用其他ML方法預測缺失值。

對於邏輯迴歸,

特徵值缺失:一般填補缺失值為0,因為:一方面當x為0時,其對應的特徵係數w不會更新,另一方面因為sigmoid(0) = 0.5, 即為中性概率,不會影響任何一端的判斷。

類別標籤缺失:直接捨棄該條樣本資料。不適用於KNN。

4. 程式碼實現

參考:《機器學習實戰》

原始碼地址以及資料:https://github.com/JieruZhang/MachineLearninginAction_src

from numpy import *
import random
import matplotlib.pyplot as plt
#加在資料集
def loadDataSet():
    dataMat = []
    labelMat = []
    fr = open('testSet.txt')
    for line in fr.readlines():
        lineArr = line.strip().split()
        #為了便於截距b的計算,在資料集首尾加了一項1.0
        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+exp(-inX))

#梯度上升
def gradAscent(dataMatIn, classLabels):
    dataMatrix = mat(dataMatIn)
    labelMat = mat(classLabels).transpose()
    m,n = shape(dataMatrix)
    alpha = 0.001#移動步長,學習率
    maxCircles = 150#迭代次數
    weights = ones((n,1))
    for k in range(maxCircles):
        h = sigmoid(dataMatrix*weights)
        #該處推導見博文
        error = labelMat-h
        weights = weights + alpha*dataMatrix.transpose()*error
    return weights

#隨機梯度上升基本函式
def stoGradAscent0(dataMatrix,classLabels, numIter=150):
    m,n = shape(dataMatrix)
    alpha = 0.001
    weights = ones(n)
    for _ in range(numIter):
        for i in range(m):
            h = sigmoid(sum(dataMatrix[i]*weights))
            error = classLabels[i] - h
            weights = weights + alpha*error*dataMatrix[i]
    return weights

#隨機梯度上升改進函式
#共3處改進:多輪隨機梯度下降,每次更新權重是在隨機選取的樣本電上,步長alpha隨著訓練的進行逐漸減小(開始時較大)。(即自適應學習率)
def stoGradAscent1(dataMatrix, classLabels, numIter=150):
    m, n = shape(dataMatrix)
    weights = ones(n)
    for j in range(numIter):
        for i in range(m):
            alpha = 4/(1.0+i+j) + 0.01
            #隨機選取計算梯度使用的樣本點
            randIndex = random.randint(0,m-1)
            h = sigmoid(sum(dataMatrix[randIndex]*weights))
            error = classLabels[randIndex] - h
            weights = weights + alpha*error*dataMatrix[randIndex]
    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()

#測試不同優化演算法所得到的分類器分類效果
dataArr, labelMat = loadDataSet()
weights0 = gradAscent(dataArr, labelMat)
plotBestFit(weights0.getA())
weights1 = stoGradAscent0(array(dataArr), labelMat)
plotBestFit(weights1)
weights2 = stoGradAscent1(array(dataArr), labelMat)
plotBestFit(weights2)
            
#預測病馬的死亡率
#分類
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):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[21]))
    trainWeights = stoGradAscent1(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()