邏輯迴歸實戰(Python批量梯度下降,隨機梯度下降,從疝氣病症預測病馬的死亡率)

一、邏輯迴歸代碼

邏輯迴歸其實是求一個分類器,是二分類問題,其利用一個sigmoid函數去定義樣本屬於正類的概率,sigmoid函數的輸入值z利用了線性迴歸的wx,所得出的數值是在[0,1]內的概率,即樣本屬於正類的概率值。在實踐中,我們需要從訓練樣本中學習出一個w和b,這就是需要的邏輯迴歸模型,w和b包含在一個權重係數向量w中,w的更新是依據對損失函數即負對數似然函數求偏導數得梯度,然後每輪迭代中w根據負梯度來更新,梯度是一個向量,表示函數值上升最快的方向,由於這裏的損失函數(負對數似然函數)是凸函數,因此在訓練中求使其值最小的w值,就利用到梯度下降法(負梯度方向),當迭代到一定次數後所得到的w即爲模型,預測時將利用sigmoid函數(此時輸入值即wx,w是所得模型,x是預測樣本)求得x屬於正樣本的概率(迴歸),若值大於閾值,即判定爲正類(分類)。

關於迴歸預測模型和損失函數的定義,以及梯度更新公式的推導,
參考:https://mp.weixin.qq.com/s/BJxdDz7DQg5QIWzzgNQrgA
其梯度公式最後少了一個負號

其中每輪迭代中權重係數矩陣weights的更新運用到訓練樣本數據矩陣dataMatrix,訓練樣本標籤矩陣labelMat和訓練樣本預測值矩陣h,這種向量化或矩陣化運算提高計算效率,避免for循環帶來的計算開銷,其矩陣計算的原理如下:這裏樣本特徵是2,加上偏置項1,所以有3個特徵,故其權重向量的維度也是3
在這裏插入圖片描述

完整的邏輯迴歸代碼如下:

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
def loadDataSet(file_name):
    """
    :desc: 加載並解析數據
    :param file_name: 文件名稱,要解析的文件所在路徑
    :return:
        dataMat -- 原始數據的特徵
        labelMat -- 原始數據的標籤,也就是每條樣本對應的類別
    """
    dataMat = []
    labelMat = []
    fr = open(file_name)
    for line in fr.readlines():
        lineArr = line.strip().split()
        if len(lineArr) == 1:
            continue
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])  # 此處爲每個樣本添加一個特徵1.0
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat


# sigmoid躍階函數
def sigmoid(x):

    return 1.0 / (1 + np.exp(-x))

# 批量梯度下降
def gradDescent(dataMatIn, classLabels):
    """
    :param dataMatIn: 是一個2維Numpy數組,每行代表每個訓練樣本,每列分別代表樣本不同特徵
    :param classLabels: 是一個List,是訓練樣本的類別標籤,它是一個1*100的行向量。爲了便於矩陣計算,需要將該行向量轉換爲列向量,
                        即將原向量轉置後賦值給labelMat
    :return: Numpy數組,權重係數
    """
    # 將numpy數組dataMatIn轉化爲numpy矩陣[[1 1 2]
    #                                   [1 1 2]
    #                                    ...  ]
    dataMatrix = np.mat(dataMatIn) # 轉換爲Nupmy矩陣
    # 將標籤List轉化爲行矩陣[[0 1 0 1 0 1 ...]],並轉置成列矩陣[[0]
    #                                                [1]
    #                                                [0]
    #                                                ..]
    labelMat = np.mat(classLabels).transpose()
    # m是訓練樣本個數,n是特徵數
    m, n = np.shape(dataMatrix)
    # alpha代表移動步長
    alpha = 0.001
    # 迭代次數
    maxCycles = 500
    # 初始權重係數矩陣,長度與特徵數相同[[1.]
    #                              [1.]
    #                              [1.]]
    weights = np.ones((n, 1))  # numpy數組
    for k in range(maxCycles):
        h = sigmoid(dataMatrix*weights)  # 對應於訓練樣本的預測值 100*1的列矩陣
        errors = labelMat - h # 100*1的列矩陣
        weights = weights + alpha * dataMatrix.transpose() * errors
    return np.array(weights)


# 隨機梯度下降
# 梯度下降算法在每次更新權重係數時都需要遍歷整個數據集,計算複雜度較高
# 隨機梯度下降每次更新權重只需要一個樣本
def stocGradDescent(dataMatrix, classLabels):
    """

    :param dataMatrix: Numpy2維數組
    :param classLabels: List
    :return: numpy數組,權重係數
    """
    m, n = np.shape(dataMatrix) # m是第一維度的元素個數,即樣本數,n是第二維度元素個數,即特徵數
    dataMat = np.mat(dataMatrix)
    alpha = 0.001
    weights = np.ones((n, 1))
    print(np.shape(weights))
    print(np.shape(dataMat[1]))
    for i in range(m):
        h = sigmoid(np.sum(dataMat[i]*weights))  # 取一條樣本與權重係數相乘,這裏的h是一個數值而不再是矩陣了
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMat[i].transpose()
    return np.array(weights)

# 改進隨機梯度下降
def stocGradDescent1(dataMatrix, classLabels, numIter=150):
    """
    :param dataMatrix: Numpy2維數組
    :param classLabels: List
    :param numIter: 迭代次數
    :return:
    """
    m, n = np.shape(dataMatrix)
    dataMat = np.mat(dataMatrix)
    weights = np.ones((n, 1))
    for j in range(numIter):
        dataIndex = list(range(m)) # 存放樣本索引[0 1 2 .. m-1]
        for i in range(m):
            alpha = 4/(1.0+j+i)+0.0001  # alpha 會隨着迭代不斷減小
            # 隨機取一個樣本的索引,其值在[0,len(dataIndex))左閉右開區間範圍內
            randIndex = int(np.random.uniform(0, len(dataIndex)))
            h = sigmoid(np.sum(dataMat[dataIndex[randIndex]]*weights))
            error = classLabels[dataIndex[randIndex]] - h
            weights = weights + alpha * error * dataMat[dataIndex[randIndex]].transpose()
            # 刪除該隨機索引
            del dataIndex[randIndex]
    return np.array(weights)

def plotBestFit(dataArr, labelMat, weights):
    """
    Desc: 將訓練樣本和訓練出的權重係數可視化
    :param dataArr: numpy2維數組,樣本數據的特徵,[[1 1 2] [1 2 1] [1 1 2]]
    :param labelMat: List,樣本數據的類別標籤
    :param weights: numpy數組,權重係數
    :return: None
    """
    n = np.shape(dataArr)[0]  # 樣本個數
    xcord1 = []; ycord1 = []  # 分別存放類別爲1的樣本特徵
    xcord2 = []; ycord2 = []  # 分別存放類別爲2的樣本特徵
    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 = np.arange(-3.0, 3.0, 0.1)
    y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)
    plt.xlabel('X')
    plt.ylabel('Y')
    plt.savefig("D://fig_2.png")
    plt.show()

def testLR():
    #  1.準備訓練數據
    dataMat, labelMat = loadDataSet("D:\\testSet.TXT")
    #  2.訓練模型
    dataArr = np.array(dataMat)
    # weights = gradDescent(dataArr, labelMat)
    weights = stocGradDescent1(dataArr, labelMat)

    print("The final weight is: \n", weights, "\n", type(weights), np.shape(weights))
    # 數據可視化
    plotBestFit(dataArr, labelMat, weights)


#  預測樣本
def classifyVector(x, weights):
    """

    :param x: numpy matrix, 待預測的樣本特徵向量
    :param weights: numpy array, 根據訓練樣本學習由梯度下降得到的權重
    :return: 如果prob大於0.5返回1,否則返回0
    """
    prob = sigmoid(np.sum(x*weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0

if __name__ == "__main__":
    testLR()

其中用於學習權重係數有三種方法:分別是批量梯度下降gradDescent、隨機梯度下降stocGradDescent和改進隨機梯度下降stocGradDescent1。批量梯度下降也就是常說的梯度下降法,梯度下降算法在每次更新迴歸係數時都需要遍歷整個數據集,該方法在處理 100 個左右的數據集時尚可,但如果有數十億樣本和成千上萬的特徵,那麼該方法的計算複雜度就太高了。一種改進方法是一次僅用一個樣本點來更新迴歸係數,該方法稱爲 隨機梯度下降算法。相比之下,批量梯度下降比隨機梯度下降具有更好的學習效果,學習得到的迴歸係數用作預測的效果更準確,下面是兩種算法尋優後的分類超平面:

由於w0*x0+w1*x1+w2*x2=f(x), x0最開始就設置爲1, x2就是我們畫圖的y值,令f(x)=0,
所以: w0+w1*x+w2*y=0 => y = (-w0-w1*x)/w2 即分類超平面直線

批量梯度下降:
在這裏插入圖片描述

隨機梯度下降:
在這裏插入圖片描述

隨機梯度下降每次迭代只用到一條樣本數據,其下降方向並不是全局最優的,也就是不一定向着最低點的方向,因此雖然迭代速度快,但是很容易陷入局部最優,最終的學習效果就沒有批量梯度下降好。

因此對隨機梯度下降作一次改進,代碼裏是stocGradDescent1,第一處改進爲 alpha 的值。alpha 在每次迭代的時候都會調整,會隨着迭代次數不斷減少,但永遠不會減小到 0,因爲在計算公式中添加了一個常數項。

第二處修改爲 randIndex 更新,這裏通過隨機選取樣本來更新迴歸係數。這種方法每次隨機從列表中選出一個值,然後從列表中刪掉該值(再進行下一次迭代)。。

改進隨機梯度下降:
在這裏插入圖片描述

在機器學習領域,梯度下降分爲3類:
1、批量梯度下降BGD:每輪迭代計算所有樣本數據,所有樣本共同決定最優下降方向
優點:逼近全局最優
缺點:計算量大,批量完成,速度慢,樣本數量大時不適用
2、隨機梯度下降SGD:每輪迭代從訓練樣本中抽取一個樣本進行計算更新,每次都不用遍歷所有數據集
優點:速度快,逐條樣本進行計算
缺點:陷入局部最優,下降幅度相對慢,需要迭代更多次數,因爲每次選取的方向不一定是最優
3、小批量的梯度下降MBGD:每輪迭代計算所有數據中的子集,是1和2的中和方案,節省時間,並且尋優也準確

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

使用邏輯迴歸來預測患有疝病的馬的存活問題。疝病是描述馬胃腸痛的術語。然而,這種病不一定源自馬的胃腸問題,其他問題也可能引發馬疝病。這個數據集中包含了醫院檢測馬疝病的一些指標,分爲訓練集和測試集,每條數據 共22個字段,最後一個字段是其類別標籤0或1,項目代碼如下

病馬數據集:
鏈接:https://pan.baidu.com/s/19qLbQ2TL757r0zyJjbLTUQ
提取碼:gw3j

#  預測樣本
def classifyVector(x, weights):
    """

    :param x: numpy matrix, 待預測的樣本特徵向量
    :param weights: numpy array, 根據訓練樣本學習由梯度下降得到的權重
    :return: 如果prob大於0.5返回1,否則返回0
    """
    prob = sigmoid(np.sum(x*weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0


#  從疝氣病症預測病馬的死亡率
def colicTest():
    """
    Desc:讀入測試集和訓練集,並對數據進行格式化處理
    :return: errorRate 分類錯誤率
    """
    frTrain = open('D:\\horseColicTraining.txt')  # 訓練樣本
    frTest = open('D:\\horseColicTest.txt')  # 測試樣本
    trainingSet = [] # [[],[],[],...]
    trainingLabels = []
    for line in frTrain.readlines():
        currLine = line.strip().split('\t') # 得到每一條數據字符串列表(共22個字段,最後一個字段爲標籤)
        lineArr = [] # 存儲每條樣本特徵
        for i in range(len(currLine)-1):
           lineArr.append(float(currLine[i]))
        trainingSet.append(lineArr)
        trainingLabels.append(float(currLine[len(currLine)-1]))
    # 使用改進後的隨機梯度下降算法求最佳權重係數
    # trainWeights = stocGradDescent1(np.array(trainingSet), trainingLabels, 500) # 改進後隨機梯度下降
    # trainWeights = stocGradDescent(np.array(trainingSet), trainingLabels) # 隨機梯度下降
    trainWeights = gradDescent(np.array(trainingSet), trainingLabels) # 梯度下降
    errorCount = 0
    numTestVec = 0.0

    # 讀取測試樣本進行測試,計算分類錯誤的樣本條數和最終的錯誤率
    for line in frTest.readlines():
        numTestVec += 1.0 # 測試樣本數量加1
        currLine = line.strip().split('\t')
        lineArr = [] # 存儲每條樣本特徵
        for i in range(len(currLine)-1):
           lineArr.append(float(currLine[i]))
        if int(classifyVector(np.mat(lineArr), trainWeights)) != int(currLine[len(currLine)-1]):
            errorCount += 1 # 預測錯誤,錯誤數加1

    errorRate = float(errorCount) / numTestVec
    print("測試的錯誤率是: ", errorRate)
    return errorRate
if __name__ == "__main__":
    colicTest()

運行結果:
批量梯度下降的錯誤率:0.29850746268656714
隨機梯度下降的錯誤率:0.47761194029850745
改進隨機梯度的錯誤率:0.3283582089552239

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