[機器學習實戰-Logistic迴歸]使用Logistic迴歸預測各類實例

[機器學習實戰-Logistic迴歸]使用Logistic迴歸預測各類實例

本實驗代碼已經傳到gitee上,請點擊查收!

Logistic_Exampleshtml

1、實驗目的

  1. 學習Logistic迴歸的基本思想。
  2. Sigmoid函數和Logistic迴歸分類器。
  3. 學習最優化算法--梯度上升算法、隨機梯度上升算法等。
  4. 運用Logistic迴歸預測各類實例。

2、實驗內容與設計思想

實驗內容

  1. 基於Logistic迴歸和Sigmoid函數分類
  2. 基於最優化方法的最佳迴歸係數肯定
  3. 示例1:從疝氣病症預測病馬的死亡率
  4. 示例2:從打鬥數和接吻數預測電影類型(數據自制)
  5. 示例3:從心臟檢查樣本幫助診斷心臟病(數據來源於網絡)
  6. 改進函數封裝使不一樣的樣本數據可使用相同的函數封裝

設計思想

  • Logistic迴歸的通常步驟:
    1. 收集數據:採用任意方法收集數據。
    2. 準備數據:因爲須要進行距離計算,所以要求數據類型爲數值型。另外,結構化數據格式則最佳
    3. 分析數據:採用任意方法對數據進行分析。
    4. 訓練算法:大部分時間將用於訓練,訓練的目的是爲了找到最佳的分類迴歸係數。
    5. 測試算法:一旦訓練步驟完成,分類將會很快。
    6. 使用算法:首先,咱們須要輸入一些數據,並將其轉換成對應的結構化數值;接着,基於訓練好的迴歸係數就能夠對這些數值進行簡單的迴歸計算。斷定它們屬於哪一個類別;在這以後,咱們就能夠輸出的類別上作一些其餘的分析工做。

3、實驗使用環境

  • 操做系統:Microsoft Windows 10
  • 編程環境:Python 3.六、Pycharm、Anaconda

4、實驗步驟和調試過程

4.1 基於Logistic迴歸和Sigmoid函數分類

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

Logistic迴歸算法只能用於預測結果只有兩種狀況(即要麼0,要麼1)的實例。而咱們須要的函數則是能接收全部輸入特徵,最後預測出類別。且函數能穩定在某兩個值之間,且可以平均分配,這裏就引入了數學上的一個函數,即Sigmoid函數python

Sigmoid函數計算公式以下:git

Sigmoid函數公式

Sigmoid函數圖像以下(來源:百度百科-Sigmoid函數):算法

Sigmoid函數圖像

Sigmoid函數特色描述:編程

當z值爲0,Sigmoid函數值爲0.5.隨着z的不斷增大,對應的Sigmoid值將逼近1;而隨着z的減少,Sigmoid值將逼近與0。若是橫座標刻度足夠大,那麼縱觀Sigmoid函數,它看起來很像一個階躍函數。數組

而Sigmoid函數中的z值是須要通過下列計算的,爲了實現Logistic迴歸分類器,咱們能夠在每個特徵上乘以一個迴歸係數,而後把全部的結果值相加,而這個相加的總和就是z值了。再將z值代入到Sigmoid函數中,能夠獲得一個0到1之間的數值,咱們將任何大於0.5的數值歸爲1類,小於0.5的數值被歸爲0類。因此,Logistic迴歸能夠被看作是一種機率估算。網絡

在上面說明了z值的計算方法後,咱們用公式來更直觀地描述z值計算:app

z值計算公式

用向量的寫法,上述公式能夠寫成image-20200428201440804,表示將這兩個數值向量對應元素相乘起來而後所有加起來便是z值。其中向量x是分類器的輸入數據,即特徵值數據;向量w是咱們須要尋找的最佳係數,尋找最佳係數的目的是爲了讓分類器的結果儘量的精確。dom

通過上面分析,Sigmoid函數公式最終形式能夠寫成下面這種形式:機器學習

image-20200428202427318


4.2 基於最優化方法的最佳迴歸係數肯定

上面咱們提到z值計算中,w的值是迴歸係數,而回歸係數決定了預測結果的準確性,爲了獲取最優迴歸係數,咱們須要使用最優化方法。最優化方法這裏學習和使用兩種:梯度上升算法隨機梯度上升算法(是對梯度上升算法的改進,使計算複雜度下降)。

4.2.1 梯度上升算法:

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

算法迭代公式:

image-20200428203614667

其中:

  • w是咱們要求的最佳係數,由於這個公式要迭代屢次,且不斷變大直到獲得一個最佳係數值,因此每次要在w的基礎上加上某個方向的步長。
  • α是步長,即每次的移動量。
  • image-20200428203959064是梯度方向,即每次迭代時w上升最快的方向。

4.2.2 測試算法:使用梯度上升算法找到最佳參數

def loadDataSet():
    """
    讀取測試文件中的數據,拆分獲得的每一行數據並存入相應的矩陣中,最後返回。
    :return: dataMat, labelMat
        dataMat: 特徵矩陣
        labelMat: 類型矩陣
    """

    # 初始化特徵列表和類型列表
    dataMat = []
    labelMat = []
    # 打開測試數據,默認爲讀方式
    fr = open("data/testSet.txt")
    # 經過readLines()方法能夠得到文件中的全部行信息
    for line in fr.readlines():
        # 將一行中大的信息先經過strip()方法去掉首尾空格
        # 再經過split()方法進行分割,默認分割方式是空格分割
        # 將分割好後的數據存入列表lineArr
        lineArr = line.strip().split()
        # 取出列表lineArr中的數據經過append方法插入到dataMat列表表中
        # 這裏爲了方便計算,將第一列的值都設置爲1.0
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
        # 再取出文件中的最後一列的值做爲類型值存入labelMat列表中
        labelMat.append(int(lineArr[2]))
        """
        列表中的append方法小結:
            從上面兩個列表添加元素的語句能夠看出:
            當咱們須要添加一行數據時,須要加[],表示將插入一行數據。
            當咱們將數據直接插入到某一列的後面時,則不須要加[]。
            且須要注意的是:若是一次要添加好幾個元素時,必須有[],不然會報錯。
        """
    return dataMat, labelMat


def sigmoid(inX):
    """
    sigmoid函數:α(z) = 1 / (1 + e^(-z))
    :param inX: 函數中的參數z
    :return: 返回函數計算結果
    """
    return 1.0 / (1 + exp(-inX))


def gradAscent(dataMatIn, classLabels):
    """
    梯度上升算法
    :param dataMatIn: 特徵值數組
    :param classLabels: 類型值數組
    :return: 返回最佳迴歸係數
    """

    # 經過numpy模塊中的mat方法能夠將列表轉化爲矩陣
    dataMatrix = mat(dataMatIn)
    # transpose()方法是矩陣中的轉置
    labelMatrix = mat(classLabels).transpose()
    # 經過numpy中大的shape方法能夠得到矩陣的行數和列數信息
    # 當矩陣是一維矩陣時,返回的是一個數的值
    # 當矩陣是二維矩陣時,返回的是一個(1*2)的元組,元組第一個元素表示行數,第二個元素表示列數
    m, n = shape(dataMatrix)  # m = 100; n = 3
    alpha = 0.001  # 步長
    maxCycles = 500  # 迭代次數
    # ones()屬於numpy模塊,函數功能是生成一個全部元素都爲1的數組
    # 這裏是生成一個「n行1列」的數組,數組中每個元素值都是1
    weights = ones((n, 1))
    # 循環迭代maxCycles次,尋找最佳參數
    for k in range(maxCycles):
        # dataMatrix * weights 是矩陣乘法
        # 矩陣相乘時注意第一個矩陣的列數要和第二個矩陣的行數相同
        # (m × n) * ( n × 1) = (m × 1) 括號中表示幾行幾列
        # (100 × 3) * (3 × 1) = (100 × 1)
        # 最後獲得一個100行1列的矩陣
        # 該矩陣運算結果爲爲sigmoid函數(α(z) = 1 / (1 + e^(-z)))中的z
        # z的公式爲:z = w0x0 + w1x1 + w2x2 + ... + wnxn
        h = sigmoid(dataMatrix * weights)
        # 計算真實類別與預測類別的差值
        error = labelMatrix - h
        # 按差值error的方向調整迴歸係數
        # 0.01 * (3 × m) * (m × 1)
        # 表示每個列上的一個偏差狀況,最後獲得x1,x2,xn的係數偏移量
        # 矩陣乘法,最後獲得一個更新後的迴歸係數
        # 梯度上升算法公式:w:=w+α▽w f(w)
        # 其中α是步長,▽w f(w)是上升方向
        weights = weights + alpha * dataMatrix.transpose() * error
    return array(weights)

輸出:

if __name__ == '__main__':
    dataMats, classMats = loadDataSet()
    dataArr = array(dataMats)
    weights = array(gradAscent(dataMats, classMats))
    print(weights)  # 輸出最佳係數w的各個值

[[ 4.12414349]
[ 0.48007329]
[-0.6168482 ]]

小結:

  • 梯度上升算法核心函數是gradAscent(dataMatIn, classLabels)函數,在該函數中不斷迭代使得參數w不斷優化,使得最終返回的參數最優。

  • 上述的sigmoid(inX)函數就是咱們使用的Sigmoid函數公式,寫成函數是由於下面咱們會頻繁的使用到這個函數,因此將該公式單獨封裝成一個函數。

  • loadDataSet()函數做用就是將咱們收集到的數據讀取出來,而且將讀取出來的數據格式化存儲到相應的數組中,最後返回供外界使用和分析。

  • 這裏小結一下列表中的append()方法和extend()方法:

    if __name__ == '__main__':
        li = []
        appendLi = [1, 2, 3]
        extendLi = [3, 4, 5]
        li.append(appendLi)
        print(li)
        li.extend(extendLi)
        print(li)

    輸出:

    [[1, 2, 3]]
    [[1, 2, 3], 3, 4, 5]

    能夠看到append()方法是將appendLi這個列表加入到li列表的新的一行,而extend()方法則是將extendLi列表中的數值取出來一個個接在li列表後面。

  • 這裏涉及到的numpy模塊中的新函數(所謂新,是相對於我來講滴):

    • mat():將數組或則列表轉化爲矩陣
    • 矩陣中包含的方法:transpose()方法是矩陣的轉置。
    • 矩陣相乘須要注意:第一個矩陣的列數須要和第二個矩陣的函數相同。
  • line.strip().split()用法:能夠看到這裏是對字符串的切割,字符串line先經過strip()方法去掉了首尾的空格,在經過split()方法進行默認空格切割。兩種方法一鼓作氣,不用分紅兩不寫。

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

def plotBestFit(dataArr, labelMat, weights):
    """
    畫出決策邊界
    :param dataArr: 特徵值數組
    :param labelMat: 類型數組
    :param weights: 最佳迴歸係數
    :return:
    """

    # 經過shape函數得到dataArr的行列數,其中[0]即行數
    n = shape(dataArr)[0]
    # xCord1和yCord1是類型爲1的點的x和y座標值
    xCord1 = []
    yCord1 = []
    # xCord2和yCord1是類型爲0的點的x和y座標值
    xCord2 = []
    yCord2 = []
    # 特徵數組的每一行和類型數組的沒每一列一一對應
    for i in range(n):
        # 當類型爲1時,
        # 將特徵數組中的指定行的1和2兩個下標下的值分別做爲x軸和y軸的值
        if int(labelMat[i]) == 1:
            xCord1.append(dataArr[i, 1])
            yCord1.append(dataArr[i, 2])
        # 當類型爲0時,
        # 將特徵數組中的指定行的1和2兩個下標下的值分別做爲x軸和y軸的值
        else:
            xCord2.append(dataArr[i, 1])
            yCord2.append(dataArr[i, 2])
    # figure()操做時建立或者調用畫板
    # 使用時遵循就近原則,全部畫圖操做是在最近一次調用的畫圖板上實現。
    fig = plt.figure()
    # 將fig分紅1×1的網格,在第一個格子中加載ax圖
    # 參數111表示「1×1網格中的第1個表格」
    # 若是參數是211則表示「2行1列的表格的中的第一個表格」
    # 第幾個表格的計算順序爲從左到右,從上到下
    ax = fig.add_subplot(111)
    # 設置散點圖參數
    # 前兩個參數xCord1,yCord1表示散點對應的x和y座標值
    # s=30表示散點大小爲30
    # c='red'表示散點顏色爲紅色
    # marker='s'表示散點的形狀,這裏是正方形
    ax.scatter(xCord1, yCord1, s=30, c='red', marker='s')
    # 同上說明
    ax.scatter(xCord2, yCord2, s=30, c='green')
    # 生成一個[-3.0, 3.0]範圍中間隔每0.1取一個值
    x = arange(-3.0, 3.0, 0.1)
    # y相對於x的函數
    """
    這裏的y是怎麼獲得的呢?
        從dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
        可得:w0*x0+w1*x1+w2*x2 = z
        x0最開始就設置爲1,x2就是咱們畫圖的y值,x1就是咱們畫圖的x值。
        因此:w0 + w1*x + w2*y = 0
        →   y = (-w0 - w1 * x) / w2
    """
    y = (-weights[0] - weights[1] * x) / weights[2]
    # 畫線
    ax.plot(x, y)
    # 設置x軸和y軸的名稱
    plt.xlabel('x1')
    plt.ylabel('x2')
    # 展現圖像
    plt.show()

輸出:

if __name__ == '__main__':
    dataMats, classMats = loadDataSet()
    dataArr = array(dataMats)
    weights = array(gradAscent(dataMats, classMats))
    plotBestFit(dataArr, classMats, weights)  # 調用該函數話決策邊界

輸出圖以下:

image-20200428212740983

小結:

  • 觀察上面的圖示,能夠看出這個分類結果至關不錯,只錯分了三四個點。

  • 畫圖方法小結:(箭頭表示賦值)

    • 調用畫板→fig:plt.figure() 方法。(plt是畫圖工具包咱們取的別名)
  • 劃分畫板→ax:fig.add_subplot(111)方法,其中111表示1*1的畫板中的第1個畫板。

    • 畫圖:畫曲散點圖用ax.sctter()方法,畫曲線圖用ax.plot()方法
  • 設置座標名稱:plt.xlabel('x1')和plt.ylabel('x2'),這裏設置x軸名稱爲x1,y軸名稱爲x2。

    • 顯示圖畫:plt.show()方法。
  • 可是儘管該例子很小且數據集很小,求最佳係數時須要大量的計算(300次乘法)。對於幾百個左右的數據集合還能夠,可是若是是10億個樣本和成千上萬的特徵,那麼這個計算方法的複雜度過高了,甚至可能出不來最佳係數。因此下面引入隨機梯度上升算法

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

def stocGradAscent0(dataMatrix, classLabels):
    """
    隨機梯度上升算法
    :param dataMatrix: 特徵值矩陣
    :param classLabels: 類型數組
    :return: 最佳係數weights
    """

    # m = 100,n = 3
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)
    print(weights)
    # 循環迭代m次,即100次
    for i in range(m):
        # (1 × 3) * (1 × 3) = (1 × 3)
        # 數組相乘,兩個數組的每一個元素對應相乘
        # 最後求和
        # z = w0x0 + w1x1 + wnxn
        # 在將z代入sigmoid函數進行計算
        h = sigmoid(sum(dataMatrix[i] * weights))
        # 計算實際結果和測試結果之間的偏差,按照差值調整迴歸係數
        error = classLabels[i] - h
        # 經過梯度上升算法更新weights
        weights = weights + alpha * error * dataMatrix[i]
    return weights

使用上面的plotBestFit(dataArr, labelMat, weights)函數分析該算法。

輸出:

if __name__ == '__main__':
    dataMats, classMats = loadDataSet()
    dataArr = array(dataMats)
    weights = array(stocGradAscent0(dataArr, classMats))
    plotBestFit(dataArr, classMats, weights)

image-20200428214432452

小結:

  • 經過圖示能夠看出,隨機梯度上升算法錯分了三分之一的樣本。
  • 隨機梯度上升算法與梯度上升算法很相似,可是有一些區別:
    1. 後者的變量h和偏差error都是向量,而前者則全是一個數值;
    2. 前者沒有矩陣轉換的過程,全部變量的數據類型都是Numpy,效率上會較後者快。
  • 由於通過比較此時的隨機梯度算法錯分了三分之一的樣本,咱們經過下面的改進來優化一下該算法。

改進後的隨機梯度上升算法代碼以下:

def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    """
    改進後的隨機梯度上升算法
    :param dataMatrix: 特徵值矩陣
    :param classLabels: 類型數組
    :param numIter: 迭代次數,設置默認值爲150
    :return: 最佳係數weights
    """

    m, n = shape(dataMatrix)
    # 建立與列數相同矩陣,全部元素都爲1
    weights = ones(n)
    # 隨機梯度,循環默認次數爲150次,觀察是否收斂
    for j in range(numIter):
        # 產生列表爲[0, 1, 2 ... m-1]
        dataIndex = list(range(m))
        for i in range(m):
            # i和j不斷增大,致使alpha不斷減少,單上衣不爲0,
            # alpha會隨着迭代不斷的減少,但永遠不會減少到0,由於後面還有一個常數項0.01
            alpha = 4 / (1.0 + j + i) + 0.01
            # 產生一個隨機在0-len()之間的值
            # random.uniform(x, y)方法將隨機生成一個實數,他在[x, y]範圍內,x<=y。
            randIndex = int(random.uniform(0, len(dataIndex)))
            # sum(dataMatrix[randIndex]*weights)是爲了求z值
            # z = w0x0 + w1x1 + ... + wnxn
            h = sigmoid(sum(dataMatrix[randIndex] * weights))
            # 計算實際結果和測試結果之間的偏差,按照差值調整迴歸係數
            error = classLabels[randIndex] - h
            # 經過梯度上升算法更新weights
            weights = weights + alpha * error * dataMatrix[randIndex]
            # 刪除掉這次更新中用到的特徵數據
            del(dataIndex[randIndex])
    return weights

輸出:

if __name__ == '__main__':
    dataMats, classMats = loadDataSet()
    dataArr = array(dataMats)
    weights = array(stocGradAscent1(dataArr, classMats))
    plotBestFit(dataArr, classMats, weights)

image-20200428215537503

小結:

  • 這裏增長了3處代碼來進行改進:
    1. 一方面,alpha在每次迭代的時候都會再進行調整。另外,雖然alpha會隨着迭代次數不斷減少,但永遠不會減少到0,這是由於調整時還存在一個常數項。必須這樣作的緣由是爲了保證屢次迭代後新數據仍然具備必定的影響。
    2. 第二處改進,經過隨機選取樣原本更新迴歸係數。這種方法每次隨機從列表中選取一個值,更新完係數後刪除掉該值(再進行下一次迭代)。
    3. 第三就是該算法還增長了第三個參數,傳入迭代的次數,默認值爲150.
  • 在看此時分析數據獲得的劃分圖,此時劃分只錯分了幾個樣本數據,因此該優化後的隨機梯度算法知足咱們需求,且數據量大的時候不用進行矩陣變化,效率也比較高。

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

這裏咱們將使用Logistic迴歸來預測患有疝氣病症的馬的存活問題。這裏的數據包含299個訓練樣本和67個測試樣本。其中咱們經過21中特徵值(每一個特徵表明什麼咱們能夠不用關心)來進行預測患有疝病的馬的存活率。1表示存活,0表示死亡。

訓練樣本horseColicTraining.txt展現:

image-20200428222848965

測試樣本horseColicTest.txt展現:

image-20200428222929184

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

def classifyVector(inX, weights):
    """
    使用梯度上升算法獲取到的最優係數來計算測試樣本中對應的Sigmoid值。
    其中Sigmoid值大於0.5返回1,小於0.5返回0.
    :param inX: 特徵數組
    :param weights: 最優係數
    :return: 返回分類結果,即1或0
    """

    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0


def colicTest():
    """
    測試迴歸係數算法的用於計算疝氣病症預測病馬的死亡率的錯誤率。
    這裏運用的隨機梯度算法來獲取最佳係數w1,w2,...,wn
    :return: 返回這次測試的錯誤率
    """

    # 以默認只讀方式打開訓練數據樣本和測試數據樣本
    frTrain = open('data/horseColicTraining.txt')
    frTest = open('data/horseColicTest.txt')
    trainingSet = []
    trainingLabels = []
    # 讀取訓練數據樣本的每一行
    for line in frTrain.readlines():
        # 去掉首尾空格,並按tab空格數來切割字符串,並將切割後的值存入列表
        currLine = line.strip().split('\t')
        lineArr = []
        # 將21個特徵值依次加入到lineArr列表彙總
        for i in range(21):
            lineArr.append(float(currLine[i]))
        # 再將lineArr列表加入到二維列表trainingSet列表中
        trainingSet.append(lineArr)
        # 將類型值依次接到trainingLabels這個列表的末尾行
        trainingLabels.append(float(currLine[21]))
    # 使用上面寫的改進的隨機梯度算法求得最佳係數,用於下面分類器使用區分類型
    trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 300)
    errorCount = 0
    numTestVec = 0.0
    # 讀取測試數據的每一行
    for line in frTest.readlines():
        # 測試數據數加1
        numTestVec += 1.0
        # 去掉首尾空格,並以tab空格數切割字符串,並將切割後的值存入列表
        currLine = line.strip().split('\t')
        lineArr = []
        # 將21個特徵值依次加入到特徵列表lineArr中
        for i in range(21):
            lineArr.append(float(currLine[i]))
        # 經過上面計算獲得的最佳係數,使用分類器計算lineArr這些特徵下的所屬的類型
        if int(classifyVector(array(lineArr), trainWeights)) != int(currLine[21]):
            # 若是分類器獲得結果和真實結果不符,則錯誤次數加1
            errorCount += 1
    # 經過遍歷得到的全部測試數據量和錯誤次數求得最終的錯誤率
    errorRate = float(errorCount) / numTestVec
    # 輸出錯誤率
    print("測試結果的錯誤率爲:{:.2%}".format(errorRate))
    # 返回錯誤率,用於計算n次錯誤率的平均值
    return errorRate


def multiTest():
    """
    屢次測試算法的錯誤率取平均值,以獲得一個比較有說服力的結果。
    :return:
    """

    numTests = 10
    errorSum = 0.0
    # 經過10次的算法測試,並得到10次錯誤率的總和
    for k in range(numTests):
        errorSum += colicTest()
    # 經過錯誤率總和/10能夠求得10次平均錯誤率並輸出
    print("10次算法測試後平均錯誤率爲:{:.2%}".format(errorSum/float(numTests)))

輸出:

if __name__ == '__main__':
    multiTest()

測試結果的錯誤率爲:29.85%
測試結果的錯誤率爲:32.84%
測試結果的錯誤率爲:35.82%
測試結果的錯誤率爲:40.30%
測試結果的錯誤率爲:34.33%
測試結果的錯誤率爲:46.27%
測試結果的錯誤率爲:32.84%
測試結果的錯誤率爲:37.31%
測試結果的錯誤率爲:29.85%
測試結果的錯誤率爲:50.75%
10次算法測試後平均錯誤率爲:37.01%

小結:

  • classifyVector(inX, weights)函數,是以最佳迴歸係數和特徵向量做爲輸入來計算最終對應的Sigmoid值。若是Sigmoid值大於0.5,函數返回1,不然返回0。注意:這個函數後面其餘實例也會常常用到,由於這個函數至關於一個分類器,能夠獲取到最終的預測結果。
  • colicTest()函數,是用於打開測試集和訓練測試集,並對數據進行格式化處理的函數,函數最終放回測試的錯誤率。
  • multiTest()函數,其功能是調用10次colicTest()函數並求結果的平均值。爲了使最終錯誤率更有說服力。
  • 從上面的結果能夠看到10次迭代後的平均錯誤率爲37.01%。事實上,這個結果並不差,由於樣本數據中實際上有30%的數據缺失。固然,若是調整colicTest()的迭代次數和stochGradAscent1()中的步長,平均錯誤率能夠降到20%左右。
  • 固然後面若是咱們有某些特徵值須要判斷,並使用該算法預測時,咱們只需再增長一個輸入各特徵值的函數,而後調用classifyVector(inX, weights)函數,就能夠預測出某些特徵下疝病馬是否會死亡。

4.4 示例2:從打鬥數和接吻數預測電影類型(數據自制)

從kNN算法裏面的小例子獲得啓發,使用打鬥數和接吻數這兩個特徵最終預測獲得的電影類型只有兩種:愛情片和動做片。因此符合Logistic迴歸算法的使用標準。

特徵說明:打鬥數、接吻數

類型說明:1表示動做片;0表示愛情片

自制數據代碼展現:

# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2020/4/26 17:21
# @Author  : zjw
# @FileName: generateData.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/vanishzeng/


from numpy import *


def generateSomeData(fileName, num):
    trainFile = open(fileName, "w")
    for i in range(num):
        fightCount = float(int(random.uniform(0, 101)))
        kissCount = float(int(random.uniform(0, 101)))
        if fightCount > kissCount:
            label = 1  # 表示動做片
        else:
            label = 0  # 表示愛情片
        trainFile.write(str(fightCount) + "\t" + str(kissCount) + "\t" + str(float(label)) + "\n")
    trainFile.close()


if __name__ == '__main__':
    generateSomeData("data/movieTraining.txt", 200)
    generateSomeData("data/movieTest.txt", 100)

自制數據思路:經過隨機生成打鬥數和接吻數(數量在100之內),判斷當打鬥數大於接吻數時爲動做片,記爲1;當接吻數大於打鬥數時爲愛情片,記爲0。並將數據寫入相應的txt文件中。這裏生成了200個訓練樣本和100個測試樣本。最後,還通過對生成的樣本數據,進行手動修改幾個類別,以達到更加實際現實的樣本數據。

樣本展現:

  • movieTraining.txt訓練樣本展現:

    image-20200428225219348

  • movieTest.txt測試樣本展現:

    image-20200428225207398

測試算法:使用Logistic迴歸預測電影類別(關鍵代碼展現)

def classifyVector(inX, weights):
    """
    使用梯度上升算法獲取到的最優係數來計算測試樣本中對應的Sigmoid值。
    其中Sigmoid值大於0.5返回1,小於0.5返回0.
    :param inX: 特徵數組
    :param weights: 最優係數
    :return: 返回分類結果,即1或0
    """

    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0
    

def movieTest():
    """
    使用Logistic迴歸算法測試判斷電影類別的錯誤率。
    :return: 錯誤率
    """
    trainFile = open("data/movieTraining.txt")
    testFile = open("data/movieTest.txt")
    trainSet = []
    trainLabels = []
    for line in trainFile.readlines():
        lineArr = line.strip().split('\t')
        trainSet.append([float(lineArr[0]), float(lineArr[1])])
        trainLabels.append(float(lineArr[2]))
    trainWeights = stocGradAscent1(array(trainSet), trainLabels, 500)
    errorCount = 0
    allTestCount = 0
    for line in testFile.readlines():
        allTestCount += 1
        lineArr = line.strip().split('\t')
        eigenvalue = [float(lineArr[0]), float(lineArr[1])]
        if classifyVector(eigenvalue, trainWeights) != float(lineArr[2]):
            errorCount += 1
    errorRate = float(errorCount)/float(allTestCount)
    print("錯誤率爲:{:.2%}".format(errorRate))
    return errorRate


def multiTest():
    """
    屢次測試算法的錯誤率取平均值,以獲得一個比較有說服力的結果。
    :return:
    """

    numTests = 10
    errorSum = 0.0
    # 經過10次的算法測試,並得到10次錯誤率的總和
    for k in range(numTests):
        errorSum += movieTest()
    # 經過錯誤率總和/10能夠求得10次平均錯誤率並輸出
    print("10次算法測試後平均錯誤率爲:{:.2%}".format(errorSum/float(numTests)))

輸出:

if __name__ == '__main__':
    multiTest()

錯誤率爲:11.00%
錯誤率爲:6.00%
錯誤率爲:5.00%
錯誤率爲:6.00%
錯誤率爲:5.00%
錯誤率爲:10.00%
錯誤率爲:4.00%
錯誤率爲:7.00%
錯誤率爲:14.00%
錯誤率爲:11.00%
10次算法測試後平均錯誤率爲:7.90%

小結:

  • 這裏測試算法的思路和上面的思路基本一致,只是對一些函數中的內容進行小修小補。
  • 由於這裏的數據是自制的,且特徵數量只有兩個,製做規則比較簡單,且手動修改的類別很少,因此最終獲得的錯誤率並不高,算法的測試結果是使人滿意的。

4.5 示例3:從心臟檢查樣本幫助診斷心臟病(數據來源於網絡)

在這個例子中咱們使用到一批心臟檢查樣本數據(數據來源於網絡:Statlog (Heart) Data Set),這裏我將獲取到的270個樣本數據分紅兩部分,一部分做爲訓練樣本(heartTraining.txt)有200個樣本數據,一部分做爲測試樣本(heartTest.txt)有70個樣本數據。數據各列的特徵(有13個特徵)和是否爲心臟病以下圖所示:

image-20200429113726404

注:這裏中間有些特徵沒有標明,但這並不影響咱們對數據的操做。

這裏爲了使數據便於後面的操做,我將數據集中最後一列數據進行了修改,原來是1表示否,2表示是;修改後1表示是,0表示否。

重構數據集的代碼以下:

# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2020/4/29 9:54
# @Author  : zjw
# @FileName: modifyDataFile.py
# @Software: PyCharm
# @Blog    :https://www.cnblogs.com/vanishzeng/


def modifyData(fileName):
    file = open(fileName, 'r')
    allStr = []
    for line in file.readlines():
        arr = line.strip().split(' ')
        if arr[13] == '1':
            arr[13] = '0'
        else:
            arr[13] = '1'
        s = ''
        for i in range(14):
            s += arr[i] + '\t'
        s += '\n'
        allStr.append(s)
    file.close()
    newFile = open(fileName, 'w')
    newFile.writelines(allStr)
    newFile.close()


if __name__ == '__main__':
    modifyData('data/heartTest.txt')
    modifyData('data/heartTraining.txt')

重構思路:將文本文件中的數據都讀取出來,而後進行切割,將數據中的最後一列中的值經過if判別進行替換。並將修改好後的列表轉化爲一個個字符串,存入到allStr這個總列表中。最後再以寫的方式打開文件,經過writelines()方法一次性將allStr列表寫入文件中。

重構後的文件數據集:

  • 訓練樣本(heartTraining.txt):

    image-20200429114456480

  • 測試樣本(heartTest.txt):

    image-20200429114510477

測試算法:使用Logistic迴歸預測是否爲心臟病(關鍵代碼展現)

def classifyVector(inX, weights):
    """
    使用梯度上升算法獲取到的最優係數來計算測試樣本中對應的Sigmoid值。
    其中Sigmoid值大於0.5返回1,小於0.5返回0.
    :param inX: 特徵數組
    :param weights: 最優係數
    :return: 返回分類結果,即1或0
    """

    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1.0
    else:
        return 0.0
    

def heartTest():
    """
    使用Logistic迴歸算法診斷心臟病的錯誤率。
    :return:錯誤率
    """

    trainFile = open("data/heartTraining.txt")
    testFile = open("data/heartTest.txt")
    trainSet = []
    trainLabels = []
    for line in trainFile.readlines():
        lineArr = line.strip().split(' ')
        temArr = []
        for i in range(13):
            temArr.append(float(lineArr[i]))
        trainSet.append(temArr)
        trainLabels.append(float(lineArr[13]))
    trainWeights = stocGradAscent1(array(trainSet), trainLabels, 100)
    errorCount = 0
    allTestCount = 0
    for line in testFile.readlines():
        allTestCount += 1
        lineArr = line.strip().split(' ')
        eigenvalue = []
        for i in range(13):
            eigenvalue.append(float(lineArr[i]))
        if classifyVector(eigenvalue, trainWeights) != float(lineArr[13]):
            errorCount += 1
    errorRate = float(errorCount) / float(allTestCount)
    print("錯誤率爲:{:.2%}".format(errorRate))
    return errorRate


def multiTest():
    """
    屢次測試算法的錯誤率取平均值,以獲得一個比較有說服力的結果。
    :return:
    """

    numTests = 10
    errorSum = 0.0
    # 經過10次的算法測試,並得到10次錯誤率的總和
    for k in range(numTests):
        errorSum += heartTest()
    # 經過錯誤率總和/10能夠求得10次平均錯誤率並輸出
    print("10次算法測試後平均錯誤率爲:{:.2%}".format(errorSum/float(numTests)))

輸出:

if __name__ == '__main__':
    multiTest()

錯誤率爲:18.57%
錯誤率爲:17.14%
錯誤率爲:15.71%
錯誤率爲:31.43%
錯誤率爲:24.29%
錯誤率爲:12.86%
錯誤率爲:21.43%
錯誤率爲:12.86%
錯誤率爲:32.86%
錯誤率爲:22.86%
10次算法測試後平均錯誤率爲:21.00%

小結:

  • 能夠看到測試算法的錯誤率爲21%,是一個還不錯的結果。由於此次獲得的數據的特徵值較多,且特徵值之間也有所差異。能夠經過增長迭代次數,以及調整步長進一步減低錯誤率。
  • 在數據處理方面,從三個實例能夠看出,基本上是一致的,知識由於特徵值數量的不一樣,以及讀取文件的不一樣,致使代碼上有略微的區別。測試Logistic算法的大體思路都是一致的,測試算法思想:
    1. 讀取訓練樣本中的數據,進行格式化處理;
    2. 將格式化處理後的數據傳入隨機梯度上升算法函數中,獲取到最佳參數
    3. 再讀取測試樣本中的數據,進行格式化處理後,調用分類器函數(傳入樣本特徵和最佳參數),能夠預測出最終特徵。與測試數據中的實際特徵進行比較,計算出錯誤次數。
    4. 最終經過錯誤次數/測試樣本總數求出錯誤率。
    5. 爲了使試驗結果具備說服力,使用了屢次求解錯誤率取平均值的方法。
  • 通過上面的分析,咱們能夠看到在測試算法時,咱們用到了基本一致的步驟,因此想到寫幾個函數,能夠將這些步驟統一塊兒來,經過傳入某些參數來實現對不一樣特徵數據的分析和預測。

4.6 改進函數封裝使不一樣的樣本數據可使用相同的函數封裝

改進函數展現:

def dataTest(trainFileName, testFileName, numOfFeatures):
    """
    函數功能:測試迴歸算法預測數據樣本的錯誤率。
    函數僞代碼:
        1. 讀取訓練樣本中的數據,進行格式化處理;
        2. 將格式化處理後的數據傳入隨機梯度上升算法函數中,獲取到最佳參數。
        3. 再讀取測試樣本中的數據,進行格式化處理後,調用分類器函數(傳入樣本特徵和最佳參數),能夠預測出最終特徵。與測試數據中的實際特徵進行比較,計算出錯誤次數。
        4. 最終經過錯誤次數/測試樣本總數**求出錯誤率。
    :param trainFileName: 訓練樣本的文件路徑/文件名
    :param testFileName: 測試樣本的文件路徑/文件名
    :param numOfFeatures: 樣本所包含的特徵數量
    :return:
    """

    trainFile = open(trainFileName)
    testFile = open(testFileName)
    trainSet = []
    trainLabels = []
    for line in trainFile.readlines():
        lineArr = line.strip().split('\t')
        temArr = []
        for i in range(numOfFeatures):
            temArr.append(float(lineArr[i]))
        trainSet.append(temArr)
        trainLabels.append(float(lineArr[numOfFeatures]))
    trainWeights = stocGradAscent1(array(trainSet), trainLabels, 200)
    errorCount = 0
    allTestCount = 0
    for line in testFile.readlines():
        allTestCount += 1
        lineArr = line.strip().split('\t')
        eigenvalue = []
        for i in range(numOfFeatures):
            eigenvalue.append(float(lineArr[i]))
        if classifyVector(eigenvalue, trainWeights) != float(lineArr[numOfFeatures]):
            errorCount += 1
    errorRate = float(errorCount) / float(allTestCount)
    print("錯誤率爲:{:.2%}".format(errorRate))
    return errorRate


def multiTest1(trainFileName, testFileName, numOfFeatures):
    """
    屢次測試算法的錯誤率取平均值,以獲得一個比較有說服力的結果。
    :return:
    """

    numTests = 10
    errorSum = 0.0
    # 經過10次的算法測試,並得到10次錯誤率的總和
    for k in range(numTests):
        errorSum += dataTest(trainFileName, testFileName, numOfFeatures)
    # 經過錯誤率總和/10能夠求得10次平均錯誤率並輸出
    print("10次算法測試後平均錯誤率爲:{:.2%}".format(errorSum/float(numTests)))

輸出:

if __name__ == '__main__':
    print("示例1:示例1:從疝氣病症預測病馬的死亡率")
    multiTest1('data/horseColicTraining.txt', 'data/horseColicTest.txt', 21)
    print("\n示例2:從打鬥數和接吻數預測電影類型")
    multiTest1('data/movieTraining.txt', 'data/movieTest.txt', 2)
    print("\n示例3:從心臟檢查樣本幫助診斷心臟病")
    multiTest1('data/heartTraining.txt', 'data/heartTest.txt', 13)

示例1:從疝氣病症預測病馬的死亡率
錯誤率爲:32.84%
錯誤率爲:29.85%
錯誤率爲:29.85%
錯誤率爲:37.31%
錯誤率爲:29.85%
錯誤率爲:34.33%
錯誤率爲:31.34%
錯誤率爲:29.85%
錯誤率爲:29.85%
錯誤率爲:31.34%
10次算法測試後平均錯誤率爲:31.64%

示例2:從打鬥數和接吻數預測電影類型
錯誤率爲:14.00%
錯誤率爲:6.00%
錯誤率爲:14.00%
錯誤率爲:3.00%
錯誤率爲:6.00%
錯誤率爲:11.00%
錯誤率爲:5.00%
錯誤率爲:11.00%
錯誤率爲:5.00%
錯誤率爲:5.00%
10次算法測試後平均錯誤率爲:8.00%

示例3:從心臟檢查樣本幫助診斷心臟病
錯誤率爲:20.00%
錯誤率爲:22.86%
錯誤率爲:17.14%
錯誤率爲:22.86%
錯誤率爲:18.57%
錯誤率爲:18.57%
錯誤率爲:35.71%
錯誤率爲:22.86%
錯誤率爲:35.71%
錯誤率爲:18.57%
10次算法測試後平均錯誤率爲:23.29%

小結:

  • 經過函數封裝後,就能夠直接傳入相應的文件名和特徵數便可測試不一樣的樣本。解決了上面的代碼冗餘。
  • 函數封裝的思路主要是對上面寫的測試函數進行重構,重構的位置有兩個地方:
    1. 將colicTest()、movieTest()、heartTest()三個函數統一爲dataTest(trainFileName, testFileName, numOfFeatures)這個函數,增長了三個參數的目的是,原來三個函數中的不一樣的地方就是不一樣的文件不一樣的特徵數量,因此以參數的形式傳遞進來,即便有所不一樣,可是以函數內參數形式調用便可實現相同的功能。
    2. 將原來的multiTest()函數重構爲multiTest1(trainFileName, testFileName, numOfFeatures)這個函數,也是增長了和上面一樣的三個參數,主要是由於在這個函數中要調用dataTest()這個函數,因此須要須要經過multiTest1這個函數間接幫忙傳遞參數。
  • 經過該函數封裝後,後面當咱們須要測試新的數據使,只要告訴我文件所在位置和文件名,以及數據的特徵數量,我就能夠調用multiTest1()函數很快的計算出錯誤率,無需再寫新的函數進行測試,極大的提升了效率

5、實驗總結

  1. Logistic迴歸算法的目的是尋找一個非線性函數Sigmoid的最佳擬合參數,求解過程可使用最優化算法來完成。

  2. 最優化算法中,最經常使用的是梯度上升算法。梯度上升算法能夠簡化爲效率比較高的隨機梯度上升算法。

  3. 改進後的隨機梯度上升算法的效果和梯度上升算法效果至關,可是佔用更少的計算資源且效率更高。

  4. Sigmoid函數:

    Sigmoid函數公式

  5. 函數封裝很重要,能夠解決代碼冗餘問題,此外也能夠提升開發效率,沒必要每次一有新數據,就要從新寫新的函數來知足要求。好的代碼封裝,後續只要調用相應的函數就能夠完成指定的目標。

  6. numpy相關函數:

    1. mat():將數組或列表轉爲矩陣形式。
    2. mat.transpose():矩陣轉置

6、參考資料

  1. 《機器學習實戰》 Peter Harrington (做者) 李銳 , 李鵬 , 曲亞東 , 王斌 (譯者) 第2章 k-近鄰算法
  2. 預測心臟病的數據來源:http://archive.ics.uci.edu/ml/datasets/Concrete+Compressive+Strength
  3. 機器學習代碼實戰:使用邏輯迴歸幫助診斷心臟病
  4. Sigmoid函數
  5. 【機器學習筆記1】Logistic迴歸總結

版權聲明:歡迎轉載=>請標註信息來源於 Vanish丶博客園

相關文章
相關標籤/搜索