1. 程式人生 > >Logistic迴歸之梯度上升優化演算法(四)

Logistic迴歸之梯度上升優化演算法(四)

Logistic迴歸之梯度上升優化演算法(四)

從疝氣病症狀預測病馬的死亡率

1、實戰背景

我們使用Logistic迴歸來預測患疝氣病的馬的存活問題。原始資料集點選這裡下載。資料中一個包含了368個樣本和28個特徵。這種病不一定源自馬的腸胃問題,其他問題也可能引發疝氣病。該資料集中包含了醫院檢測疝氣病的一些指標,有的指標比較主觀,有的指標難以測量。例如馬的疼痛級別。另外需要說明的是,除了部分指標主觀和難以測量外,該資料集有30%的值是缺失的。所以我們得預先對資料集進行處理,再利用Logistic迴歸和隨機梯度上升演算法來預測病馬的死亡率。

2、準備資料

假設有100個樣本和20個特徵,這些資料都是機器手機回來的。若機器上的某個感測器損壞導致一個特徵無效時該怎麼辦?此時是否要扔掉整個資料?這種情況下,另外19個特徵怎麼辦?是否還可用?答案是肯定的。因為有的資料相當昂貴,扔掉和重新獲取都是不可取的,下面給出了一些處理方法。

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

現在我們要進行資料預處理。有兩件事,一、所有的缺失值必須用一個實數值;唉替換,因為我們使用的Numpy資料型別不允許包含缺失值。這裡選擇實數0來替換所有缺失值。這樣做的好處在於更新時不會影響迴歸係數的值,如果選擇實數0來替換缺失值,那麼dataMatrix的某特徵對應值為0,那麼該特徵的係數將不做更新。所有以0代替缺失值在Logistic迴歸中可以滿足這個要求。二、如果在測試資料集中發現一條資料的類別標籤已經缺失,那麼我們的簡單做法是將該條資料丟棄,這是因為類別標籤與特徵不同,很難確定採用某個合適的值來替換。採用Logistic迴歸進行分析時這類做法是合理的。現在我沒有一個乾淨可用的資料集和一個不錯的優化演算法,將二者結合,預測馬的生死問題。

乾淨資料集下載地址:https://github.com/Jack-Cherish/Machine-Learning/tree/master/Logistic

3、使用Python構建Logistic迴歸分類器

使用訓練集的每個特徵用於最優化演算法中得到迴歸係數。把測試集的特徵乘以迴歸係數並求和放入Sigmoid函式中。如果返回值大於0.5,預測標籤為1,否則為0.

import numpy as np
import random
'''
函式說明:sigmoid函式
Parameters:
    inX - 資料
Retruns:
    sigmoid函式
'''
def sigmoid(inX):

    return (1.0/(1+np.exp(-inX)))
'''
函式說明:改進的隨機梯度上升演算法
Parameters:
    dataMatrix - 資料陣列
    classLabels - 資料標籤
    numIter - 迭代次數
Returns:
    weights - 求得的迴歸係數陣列(最優引數)
'''
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
    m,n = np.shape(dataMatrix)#返回dataMatrix大小
    weights = np.ones(n)#初始化迴歸係數
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.01 #降低alpha的大小,每次減小1/(j+i)
            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])
    print(weights)
    return weights
'''
函式說明:使用Python寫得Logistic分類器做預測
Parameters:
    None
Returns:
    None
'''
def colicTest():
    frTrain = open('horseColicTraining.txt')#開啟訓練集
    frTest = open('horseColicTest.txt')#開啟測試集
    trainingSet = []
    trainingLabels = []
    for line in frTrain.readlines():
        currLine = line.strip().split('\t')#strip()為空是,split以'\t'分割字串
        # print(currLine)
        lineArr = []
        for i in range(len(currLine) - 1):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)#處理成一個二維列表
        # print(trainingSet)
        trainingLabels.append(float(currLine[-1])) #存放標籤列表
    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(len(currLine) - 1):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(np.array(lineArr),trainweights)) != int(currLine[-1]):
            errorCount +=1
    errorRate = (float(errorCount)/numTestVec) * 100
    print('測試集錯誤率為: %.2f%%' %errorRate)
'''
函式說明:分類函式
Parameters:
    inX - 特徵向量
    weights - 迴歸係數
Returns:
    分類結果
'''
def classifyVector(inX , weights):
    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0

if __name__ == '__main__':
    np.set_printoptions(suppress=True)  # 關閉科學技術法
    colicTest()


執行結果如下:

結果中有個警告,意思是計算的資料結果益處。這個警告對我們的實驗雖然沒什麼影響,如果要改的話可以用如下程式碼:

def sigmoid(inX):
    if inX >= 0:
        return 1.0 / (1 + np.exp(-inX))
    else:
        return np.exp(inX) / (1 + np.exp(inX))
    # return (1.0/(1+np.exp(-inX)))

程式碼出處:https://blog.csdn.net/cckchina/article/details/79915181。文中也沒有說明原因,如果有知道為什麼的還請在評論中說一下,謝謝。迴歸正題,我們的演算法錯誤率比較高。主要原因是我們的資料集本身就小,而且有很多的資料缺失。因此在面對資料量小的情況下我們可以試試梯度上升演算法。

import numpy as np
import random


'''
函式說明:梯度上升法
Parameters:
    dataMatIn - 資料集
    classLabels - 資料標籤
Returns:
weights.getA() - 求得的權重陣列
'''
def gradAscent(dataMatIn, classLabels):
    dataMatrix = np.mat(dataMatIn)  # 轉換成numpy的mat
    labelMat = np.mat(classLabels).transpose()  # 轉換成numpy的mat,並進行轉置
    m, n = np.shape(dataMatrix)  # 返回dataMatrix的大小。m為行數,n為列數。
    alpha = 0.01  # 移動步長,也就是學習速率,控制更新的幅度。
    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.getA()  # 將矩陣轉換為陣列,並返回


'''
函式說明:sigmoid函式
Parameters:
    inX - 資料
Retruns:
    sigmoid函式
'''
def sigmoid(inX):
    # if inX >= 0:
    #     return 1.0 / (1 + np.exp(-inX))
    # else:
    #     return np.exp(inX) / (1 + np.exp(inX))
    return 1.0/(1+np.exp(-inX))
'''
函式說明:改進的隨機梯度上升演算法
Parameters:
    dataMatrix - 資料陣列
    classLabels - 資料標籤
    numIter - 迭代次數
Returns:
    weights - 求得的迴歸係數陣列(最優引數)
'''
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
    m,n = np.shape(dataMatrix)#返回dataMatrix大小
    weights = np.ones(n)#初始化迴歸係數
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.01 #降低alpha的大小,每次減小1/(j+i)
            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])
    # print(weights)
    return weights
'''
函式說明:使用Python寫得Logistic分類器做預測
Parameters:
    None
Returns:
    None
'''
def colicTest():
    frTrain = open('horseColicTraining.txt')#開啟訓練集
    frTest = open('horseColicTest.txt')#開啟測試集
    trainingSet = []
    trainingLabels = []
    for line in frTrain.readlines():
        currLine = line.strip().split('\t')#strip()為空是,split以'\t'分割字串
        # print(currLine)
        lineArr = []
        for i in range(len(currLine) - 1):
            lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)#處理成一個二維列表
        # print(trainingSet)
        trainingLabels.append(float(currLine[-1])) #存放標籤列表
    trainweights = gradAscent(np.array(trainingSet),trainingLabels)#使用改進的隨機梯度上升演算法
    errorCount = 0
    numTestVec = 0.0
    for line in frTest.readlines():
        numTestVec += 1.0
        currLine = line.strip().split('\t')
        lineArr = []
        for i in range(len(currLine) - 1):
            lineArr.append(float(currLine[i]))
        if int(classifyVector(np.array(lineArr),trainweights[:,0])) != int(currLine[-1]):
            errorCount +=1
    errorRate = (float(errorCount)/numTestVec) * 100
    print('測試集錯誤率為: %.2f%%' %errorRate)
'''
函式說明:分類函式
Parameters:
    inX - 特徵向量
    weights - 迴歸係數
Returns:
    分類結果
'''
def classifyVector(inX , weights):
    prob = sigmoid(sum(inX * weights))

    if prob > 0.5:
        return 1.0
    else:
        return 0.0



if __name__ == '__main__':
    np.set_printoptions(suppress=True)  # 關閉科學技術法
    colicTest()


執行結果如下:

錯誤率有所降低。

  • 資料集較大時,使用隨機梯度上升發
  • 資料集較小是,使用梯度上升演算法

總結

Logistic迴歸的有點在於容易實現,好理解,計算代價不高,速度很快,儲存資源低。缺點在於容易欠擬合,分類經度可能不高。

參考文獻: