1. 程式人生 > >邏輯迴歸分類鳶尾花和紅酒等級

邏輯迴歸分類鳶尾花和紅酒等級

邏輯迴歸分類鳶尾花和紅酒等級

一、實驗準備

1、實驗內容和目的

  • 使用邏輯迴歸演算法來對鳶尾花進行分類;同時,OJ上還給出了另外一組較強的測試資料,要求對紅酒進行等級的分類

  • 資料集包括訓練資料train.txt和測試資料test.txt;測試資料中,每個樣本包括特定的幾個特徵引數,最後是一個類別標籤,而測試資料中的樣本則只包括了特徵引數

2、實驗原理

  • 邏輯迴歸是一個分類演算法,它可以處理二元分類以及多元分類。雖然它的名字裡面有“迴歸”兩個字,卻不是一個迴歸演算法。個人認為,如此命名的原因在於,雖然邏輯迴歸是分類模型,但是它的原理殘留著迴歸模型的影子

  • 之前學習了線性迴歸,我們知道,線性迴歸的模型是求出輸出特徵向量 Y Y 和輸入樣本矩陣 X X 之間的線性關係係數 θ

    \theta ,其滿足 Y = θ X Y = \theta X
    。此時我們的 Y Y 是連續的,所以它是一個迴歸模型。那如果我們想要 Y Y 是離散的,該怎麼辦呢?一個辦法就是,我們對於這個 Y Y 再做一次函式轉換,變為 g ( Y ) g(Y) 。如果我們另 g ( Y ) g(Y) 的值在某個實數區間的時候為類別A,在另一個實數區間的時候為類別B,以此類推,就得到了一個分類模型

2.1 基於Logistic迴歸和Sigmoid函式的分類
  • 根據上面對邏輯迴歸的綜述,我們需要一個函式,它能夠接受所有的輸入然後預測出類別。例如,在兩個類的情況下,上述函式輸出0或者1。有一個函式剛好滿足這個性質,它就是Sigmoid函式。Sigmoid函式具體的計算公式如下:

    σ ( z ) = 1 1 + e ( z ) \sigma(z) = \frac{1}{1 + \mathrm{e}^{(-z)}}

  • 圖5-1給出了Sigmoid函式在不同座標尺度下的兩條曲線圖。當 x x 為0時,Sigmoid函式值為0.5。隨著 x x 的增大,對應的Sigmoid值將逼近於1;而隨著 x x 的減小,Sigmoid值將逼近於0。如果橫座標刻度足夠大(圖5-1下圖),Sigmoid函式看起來很像一個階躍函式

    在這裡插入圖片描述

  • 因此,為了實現Logistic迴歸分類器,我們可以在每個特徵上都乘以一個迴歸係數,然後把所有的結果值相加,將這個總和代入Sigmoid函式中,進而得到一個範圍在0~1之間的數值。任何大於0.5的資料被分為1類,小於0.5即被分為0類。

  • 確定了分類器的函式形式之後,還剩下一個問題要解決:最佳迴歸係數是多少?如何確定它們的大小?

2.2 基於最優化方法的最佳迴歸係數確定
  • Sigmoid函式的輸入記為 z z ,由下面公式得出:

    z = w 0 x 0 + w 1 x 1 + w 2 x 2 + . . . + w n x n z=w_0x_0+w_1x_1+w_2x_2+...+w_nx_n

  • 如果採用向量的寫法,上述公式可以寫成 z = w T x z=w^Tx ,它表示將這兩個數值向量對應元素相乘然後全部加起來即得到 z z 值。其中的向量 x x 是分類器的輸入資料,向量 w w 也就是我們要找到的最佳引數(係數),從而使得分類器儘可能的精確。為了尋找該最佳引數,就需要用到最優化理論的一些知識

  • 梯度上升法是最優化演算法的一種,它的基本思想是:要找到某函式的最大值,最好的方法是沿著該函式的梯度方向探尋。如下圖所示,梯度上升演算法到達每個點後都會重新估計移動的方向。從 P 0 P0 開始,計算完該點的梯度,函式就根據梯度移動到下一個點 P 1 P1 。在 P 1 P1 點,梯度再次被重新計算,並沿新的梯度方向移動到 P 2 P2 。如此迴圈迭代,直到滿足停止條件

    在這裡插入圖片描述

二、進行實驗

  • OJ上給出了兩組測試資料,測試結果如下:

    在這裡插入圖片描述

1、演算法思路

  • 整體的演算法思路就是使用隨機梯度上升演算法來計算樣本特徵的權重值,然後在該權重值的基礎上使用Sigmoid函式來對測試樣本進行分類

  • 不過有一點特殊的地方,鳶尾花有三個類別,也就是需要進行三分類。這就需要在二分類的基礎上有所修改,我想到的方法就是:針對三種鳶尾花類別,分三次處理訓練資料,之後得到三組權重值,最後用這三組權重值結合測試樣本的特徵值進行計算,通過比較函式值來分類

2、演算法步驟

  • (1) 處理訓練資料,得到特徵引數集和類別標籤集

  • (2) 使用訓練資料進行訓練,得到三組權重值

  • (3) 處理測試資料,取出樣本的特徵值

  • (4) 使用Sigmoid函式,在權重值的基礎上計算測試樣本的函式值

  • (5) 通過比較函式值進行分類

3、程式碼實現

  • 具體的功能實現在程式碼中的註釋均進行了詳細說明
#!/usr/bin/python
# -*- coding utf-8 -*-
# Project: Logistic
# Author: jiangnan
# Mail: [email protected]
# Date: 2018/11/14

import numpy as np

def loadTrainDataSet(feature_count, type):
    """
    函式說明:
        載入和處理訓練資料,分離出每個樣本的特徵引數和類別標籤
    :param
        feature_count: 樣本的特徵個數
    :param
        type: 指定本次處理訓練資料所針對的鳶尾花類別
              0、1、2分別對應Iris-setosa、Iris-versicolor、Iris-virginica
    :return:
    """
    dataMat = []
    labelMat = []
    fr = open("data/train.txt")
    for line in fr.readlines():    # 逐行處理資料
        lineArr = line.strip().split(',')
        currentArr = []

        [currentArr.append(float(x)) for x in lineArr[ :feature_count]]    # 取出每個樣本的特徵引數
        currentArr.append(1.0)  # 將X0的值設定為1.0
        dataMat += [currentArr]   # 將每個樣本的特徵引數加入結果矩陣

        # 接下來結合樣本的所屬類別和type引數的值進行判斷
        # 將所針對的鳶尾花類別標籤置為1
        # 其餘不滿足條件的置為0
        if (lineArr[4] == 'Iris-setosa' and type == 0):
            labelMat.append(1)
        elif (lineArr[4] == 'Iris-versicolor' and type == 1):
            labelMat.append(1)
        if (lineArr[4] == 'Iris-virginica' and type == 2):
            labelMat.append(1)
        else:
            labelMat.append(0)

    # 返回特徵引數集和類別標籤集
    return dataMat, labelMat


def sigmoid(inX):
    """
    函式說明:
        Sigmoid函式,用來進行類別判斷
    :param
        inX: 特徵引數
    :return:
        返回該特徵引數下所對應的函式值
    """
    return 1.0 / (1 + np.exp(-inX))


def LogisticRegression(dataMat, labelMat, numIter = 1000):
    """
    函式說明:
        使用隨機梯度上升演算法來計算樣本特徵的權重
    :param
        dataMat: 訓練資料的特徵引數集
    :param
        labelMat: 訓練資料的類別標籤集
    :param
        numIter: 迭代次數
    :return:
        返回樣本特徵的權重值
    """
    dataMat = np.array(dataMat)
    m, n = np.shape(dataMat)
    weights = np.ones(n)    # 權重矩陣初始化為1
    for i in range(numIter):
        dataIndex = list(range(m))
        alpha = 0.001
        for j in range(m):
            # 隨機取得一個下標值
            # 然後更新對應的迴歸係數值
            randIndex = int(np.random.uniform(0, len(dataIndex)))
            h = sigmoid(sum(dataMat[dataIndex[randIndex]] * weights))
            error = labelMat[dataIndex[randIndex]] - h
            weights = weights + alpha * error * dataMat[dataIndex[randIndex]]
            del(dataIndex[randIndex])

    return weights  # 返回最後的權重值


def classify(inX, weights):
    """
    函式說明:
        呼叫Sigmoid函式,計算樣本在該權重值下的函式值
    :param
        inX: 樣本的特徵引數
    :param
        weights: 權重值
    :return:
        返回Sigmoid函式值
    """
    inX = np.array(inX)
    prob = sigmoid(sum(inX * weights))
    return prob


def solve():
    """
    函式說明:
        綜合呼叫上述函式進行分類
    """

    # 第一次處理訓練資料
    # 針對屬於Iris-setosa類別的鳶尾花
    # 計算並輸出樣本特徵的權重值
    dataMat_0, labelMat_0 = loadTrainDataSet(4, 0)
    weights_0 = LogisticRegression(dataMat_0, labelMat_0)
    print(weights_0)

    # 第二次處理訓練資料
    # 針對屬於Iris-versicolor類別的鳶尾花
    # 計算並輸出樣本特徵的權重值
    dataMat_1, labelMat_1 = loadTrainDataSet(4, 1)
    weights_1 = LogisticRegression(dataMat_1, labelMat_1)
    print(weights_1)

    # 第三次處理訓練資料
    # 針對屬於Iris-virginica類別的鳶尾花
    # 計算並輸出樣本特徵的權重值
    dataMat_2, labelMat_2 = loadTrainDataSet(4, 2)
    weights_2 = LogisticRegression(dataMat_2, labelMat_2)
    print(weights_2)

    # 對測試資料中的樣本進行分類
    fr = open("data/test.txt")
    for line in fr.readlines():
        # 提取測試樣本的特徵引數
        lineArr = line.strip().split(',')
        currentArr = []
        [currentArr.append(float(x)) for x in lineArr[:4]]
        currentArr.append(1.0)

        # 分別使用三組權重值進行計算
        # 得到對應的的三個函式值
        prob_0 = classify(currentArr, weights_0)
        prob_1 = classify(currentArr, weights_1)
        prob_2 = classify(currentArr, weights_2)

        # 比較三個函式值
        # 找到使得函式值最大的那組權重值
        # 將樣本分類為該組權重值對應的類別
        if(prob_0 > prob_1 and prob_0 > prob_2):
            print('Iris-setosa')
        elif(prob_1 > prob_0 and prob_1 > prob_2):
            print('Iris-versicolor')
        else:
            print('Iris-virginica')
    fr.close()


if __name__ == '__main__':
    solve()

4、總結

  • 邏輯迴歸的優缺點

    • 優點:計算代價不高,易於理解和實現

    • 缺點:容易欠擬合,分類精度可能不高

附錄

  • OJ上多給出了一組測試資料,要求對紅酒的等級進行分類,和鳶尾花相比,只是樣本的特徵個數不同,因此只需要在上述程式碼的基礎上進行小修改即可
#!/usr/bin/python
# -*- coding utf-8 -*-
# Project: Logistic
# Author: jiangnan
# Mail: [email protected]
# Date: 2018/11/14

import numpy as np

def loadTrainDataSet(feature_count, type):
    dataMat = []
    labelMat = []
    fr = open("train.txt")
    for line in fr.readlines():
        lineArr = line.strip().split(',')
        currentArr = []

        [currentArr.append(float(x)) for x in lineArr[ :feature_count]]
        currentArr.append(1.0)
        dataMat += [currentArr]

        if (lineArr[13] == '1' and type == 0):
            labelMat.append(1)
        elif (lineArr[13] == '2' and type == 1):
            labelMat.append(1)
        if (lineArr[13] == '3' and type == 2):
            labelMat.append(1)
        else:
            labelMat.append(0)

    return dataMat, labelMat


def sigmoid(inX):
    return 1.0 / (1 + np.exp(-inX))


def LogisticRegression(dataMat, labelMat, numIter = 2000):
    dataMat = np.array(dataMat)
    m, n = np.shape(dataMat)
    weights = np.ones(n)
    for i in range(numIter):
        dataIndex = list(range(m))
        alpha = 0.001
        for j in range(m):
            randIndex = int(np.random.uniform(0, len(dataIndex)))
            h = sigmoid(sum(dataMat[dataIndex[randIndex]] * weights))
            error = labelMat[dataIndex[randIndex]] - h
            weights = weights + alpha * error * dataMat[dataIndex[randIndex]]
            del(dataIndex[randIndex])

    return weights


def classify(inX, weights):
    inX = np.array(inX)
    prob = sigmoid(sum(inX * weights))
    return prob


def solve():
    dataMat_0, labelMat_0 = loadTrainDataSet(13, 0) # 修改了特徵引數的個數
    weights_0 = LogisticRegression(dataMat_0, labelMat_0)
    print(weights_0)

    dataMat_1, labelMat_1 = loadTrainDataSet(13, 1)
    weights_1 = LogisticRegression(dataMat_1, labelMat_1)
    print(weights_1)

    dataMat_2, labelMat_2 = lo