1. 程式人生 > >機器學習實戰(四)邏輯迴歸LR(Logistic Regression)

機器學習實戰(四)邏輯迴歸LR(Logistic Regression)

目錄

0. 前言

1. Sigmoid 函式

2. 梯度上升與梯度下降

3. 梯度下降法(Gradient descent)

4. 梯度上升法(Gradient ascent)

5. 梯度下降/上升法的數學推導

6. 隨機梯度下降/上升法(Stochastic gradient descent/ascent)

7. 實戰案例

7.1. 簡單案例

7.2. 病馬死亡率案例


學習完機器學習實戰的邏輯迴歸,簡單的做個筆記。文中部分描述屬於個人消化後的理解,僅供參考。

本篇綜合了先前的文章,如有不理解,可參考:

吳恩達機器學習(二)多元線性迴歸

吳恩達機器學習(四)邏輯迴歸

吳恩達機器學習(五)正則化

吳恩達機器學習(十五)大規模機器學習

所有程式碼和資料可以訪問 我的 github

如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~

0. 前言

邏輯迴歸(Logistic Regression)是一種基於最優化思想的二分類監督學習演算法。

其主要思想是根據現有資料對分類邊界線建立迴歸公式,以此分類

其目的是尋找一個非線性函式 Sigmoid 的最佳擬合引數,求解過程可以通過最優化演算法完成。

  • 優點:計算代價不高,易於理解和實現
  • 缺點:容易造成欠擬合,分類精度不高
  • 適用資料型別:數值型和標稱型資料

1. Sigmoid 函式

線性迴歸中,對資料進行擬合,是在每個特徵上都乘以一個迴歸係數,然後把所有結果再相加,即 h_{\theta}=\theta_{0}x_{0}+\theta_{1}x_{1}+...+\theta_{n}x_{n} ,向量化即 h_{\theta}(x)=\theta^{T}x 。

邏輯迴歸中,再將 h_{\theta}(x)=\theta^{T}x 帶入 Sigmoid 函式,可得到屬於 (0,1) 範圍的一個數值,若大於 0.5 則屬於正類,若小於則反之。

Sigmoid 函式的定義如下:

g(z)=\frac{1}{1+e^{-z}}

Sigmoid 函式的影象如下:

綜合上述,整體公式表示為:

h_{\theta}(x)=g(\theta^{T}x)=\frac{1}{1+e^{-\theta^{T}x}}

2. 梯度上升與梯度下降

梯度:某一函式在該點處的方向導數沿該方向取得最大值,即在該點變化率(斜率)最大。

  • 梯度上升法:尋找某函式的最大值,沿著該函式的梯度方向尋找
  • 梯度下降法:尋找某函式的最小值,沿著該函式的梯度方向尋找

梯度符號記為 \nabla ,f(x) 的梯度可表示為,對各個方向求偏導數:

\nabla f(x)=\begin{bmatrix} \frac{\partial f(x)}{\partial x_1}\\ ...\\ \frac{\partial f(x)}{\partial x_n} \end{bmatrix}

如下圖所示,求最小值,可採用梯度下降法:

梯度下降: x:=x-\alpha\frac{\partial f(x)}{\partial x} ,當 x 在最小值左邊時,導數為負數,x 向右移;當 x 在最小值右邊時,導數為正數,x 向左移。

如下圖所示,求最大值,可採用梯度上升法:

梯度上升: x:=x+\alpha\frac{\partial f(x)}{\partial x} ,當 x 在最小值左邊時,導數為正數,x 向右移;當 x 在最小值右邊時,導數為負數,x 向左移。

3. 梯度下降法(Gradient descent)

梯度下降法的代價函式如下定義:

J(\theta)= -\frac{1}{m}\sum_{i=1}^{m}[y^{(i)}log(h_{\theta}(x^{(i)}))+(1-y^{(i)})log(1-h_{\theta}(x^{(i)}))] + \frac{\lambda}{2m}\sum_{j=1}^{n}\theta_{j}^{2}

代價函式是指,函式值越大,付出的代價越大,即精度越差。

第二項是正則化項,是為了防止過擬合而設定,先忽略這項。

第一項是所有樣本計算結果的求平均,若 y^{(i)}=1 ,則取 -log(h_{\theta}(x^{(i)})) ,若 y^{(i)}=0 ,則取 -log(1-h_{\theta}(x^{(i)})) 。

由上圖易得,y=1 時,若 h_{\theta}(x) 越接近 0 代價越大,y=0 時,若 h_{\theta}(x) 越接近 1 代價越大。

為了降低代價函式 J(\theta) ,可採取梯度下降法,定義如下:

\theta_{j}:=\theta_{j}-\alpha \frac{\partial }{\partial \theta_{j} }J(\theta)\\ \Rightarrow \theta_{j}:=\theta_{j}-\alpha \frac{1}{m}\sum_{i=1}^{m}(h_{\theta}(x^{(i)})-y^{(i)})x_{j}^{(i)}

4. 梯度上升法(Gradient ascent)

梯度上升法求解邏輯迴歸引數,和梯度下降法求解引數原理是一樣的。

只是在梯度上升法中,J(\theta) 定義如下:

J(\theta)= \frac{1}{m}\sum_{i=1}^{m}[y^{(i)}log(h_{\theta}(x^{(i)}))+(1-y^{(i)})log(1-h_{\theta}(x^{(i)}))] + \frac{\lambda}{2m}\sum_{j=1}^{n}\theta_{j}^{2}

與梯度下降法只相差一個負號,所以是求解最大值,採用梯度上升法:

\theta_{j}:=\theta_{j}+\alpha \frac{\partial }{\partial \theta_{j} }J(\theta)\\ \Rightarrow \theta_{j}:=\theta_{j}+\alpha \frac{1}{m}\sum_{i=1}^{m}(y^{(i)}-h_{\theta}(x^{(i)}))x_{j}^{(i)}

5. 梯度下降/上升法的數學推導

以梯度上升法為例子,不考慮正則化項:

\begin{align*} & J(\theta)= \frac{1}{m}\sum_{i=1}^{m}[y^{(i)}log(h_{\theta}(x^{(i)}))+(1-y^{(i)})log(1-h_{\theta}(x^{(i)}))]=\frac{1}{m}\sum_{i=1}^{m}J_{temp}(\theta) \\ & h_{\theta}(x)=g(\theta^Tx)=\frac{1}{1+e^{-\theta^Tx}} \\ \\ & \frac{\partial J_{temp}(\theta)}{\partial \theta_j}=\frac{\partial J_{temp}(\theta)}{\partial g(\theta^Tx)}\cdot\frac{\partial g(\theta^Tx)}{\partial \theta^Tx}\cdot\frac{\partial \theta^Tx}{\partial \theta_j} \end{align*}

對三項分別計算:

\begin{align*} \frac{\partial J_{temp}(\theta)}{\partial g(\theta^Tx)} &= y\cdot\frac{1}{g(\theta^Tx)}+(1-y)\cdot\frac{-1}{1-g(\theta^Tx)} \\ &= y\cdot\frac{1}{g(\theta^Tx)}+(y-1)\cdot\frac{1}{1-g(\theta^Tx)} \end{align*}

\begin{align*} \frac{\partial g(\theta^Tx)}{\partial \theta^Tx} &= \frac{\partial g(z)}{\partial z} \\ &= -\frac{1}{(1+e^{-z})^2}\cdot e^{-z} \cdot (-1) \\ &= \frac{1}{(1+e^{-z})^2}\cdot e^{-z} \\ &= \frac{1}{1+e^{-z}}(1- \frac{1}{1+e^{-z}}) \\ &= g(z)(1-g(z)) \\ &= g(\theta^Tx)(1-g(\theta^Tx)) \end{align*}

\begin{align*} \frac{\partial \theta^Tx}{\partial \theta_j} &= \frac{\partial (\theta_0x_0+\theta_1x_1+...+\theta_nx_n)}{\partial \theta_j} \\ &= x_j \end{align*}

綜合三項可得:

\begin{align*} \frac{\partial J_{temp}(\theta)}{\partial \theta_j} &= \frac{\partial J_{temp}(\theta)}{\partial g(\theta^Tx)}\cdot\frac{\partial g(\theta^Tx)}{\partial \theta^Tx}\cdot\frac{\partial \theta^Tx}{\partial \theta_j} \\ &= (y\cdot\frac{1}{g(\theta^Tx)}+(y-1)\cdot\frac{1}{1-g(\theta^Tx)})\cdot g(\theta^Tx)(1-g(\theta^Tx))\cdot x_j \\ &= (y(1-g(\theta^Tx))+(y-1)g(\theta^Tx))x_j \\ &= (y-g(\theta^Tx))x_j \\ &= (y-h_{\theta}(x))x_j \end{align*}

\begin{align*} \frac{\partial J(\theta)}{\partial \theta_j} &= \frac{1}{m}\sum_{i=1}^{m} \frac{\partial J_{temp}(\theta)}{\partial \theta_j} \\ &= \frac{1}{m}\sum_{i=1}^{m}(y^{(i)}-h_{\theta}(x^{(i)}))x_j^{(i)} \end{align*}

綜上所述,在梯度上升法中:

\theta_{j}:=\theta_{j}+\alpha \frac{\partial }{\partial \theta_{j} }J(\theta)\\ \Rightarrow \theta_{j}:=\theta_{j}+\alpha \frac{1}{m}\sum_{i=1}^{m}(y^{(i)}-h_{\theta}(x^{(i)}))x_{j}^{(i)}

6. 隨機梯度下降/上升法(Stochastic gradient descent/ascent)

以隨機梯度下降法為例子,隨機梯度下降法是對梯度下降法的改進。

梯度下降法中,每次更新引數都需要遍歷整個資料集,又稱作 Batch gradient descent 。

隨機梯度下降法中,每次更新引數只隨機選擇一個樣本點,通過每次更新引數的迭代而選擇不同的樣本點。

演算法流程如下:

隨機梯度下降/上升法是一個線上演算法,可以在新資料來臨的時候直接更新引數,而不用遍歷整個資料集。 

7. 實戰案例

以下將展示書中案例的程式碼段,所有程式碼和資料可以在github中下載:

7.1. 簡單案例

# coding:utf-8
from numpy import *
import matplotlib.pyplot as plt

"""
簡單案例
"""


# sigmoid函式
def sigmoid(inX):
    return 1.0 / (1 + exp(-inX))


# 梯度上升求解J(\theta)
# Batch gradient ascent
def gradAscent(dataMatIn, classLabels):
    # 轉換為矩陣
    dataMatrix = mat(dataMatIn)
    # 轉換為矩陣後轉置,表示為列向量
    labelMat = mat(classLabels).transpose()
    m, n = shape(dataMatrix)
    alpha = 0.001
    maxCycles = 500
    # \theta 表示為列向量
    weights = ones((n, 1))
    # 梯度上升迭代
    for k in range(maxCycles):
        h = sigmoid(dataMatrix * weights)
        error = (labelMat - h)
        weights = weights + alpha * dataMatrix.transpose() * error
    return weights


# 載入資料集
def loadDataSet():
    dataMat = []
    labelMat = []
    fr = open('testSet.txt')
    for line in fr.readlines():
        # 去除頭尾空字元,按照空格分割字串
        lineArr = line.strip().split()
        # 新增偏置位 w_0(\theta_0) 相乘的 x_0 = 1.0
        dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
        labelMat.append(int(lineArr[2]))
    return dataMat, labelMat


# 畫出資料的分界線
def plotBestFit(weights):
    dataMat, labelMat = loadDataSet()
    dataArr = array(dataMat)
    n = shape(dataArr)[0]
    xcord1 = []
    ycord1 = []
    xcord2 = []
    ycord2 = []
    # 遍歷每一條資料,根據類別將x1, x2分別插入不同的List中
    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 = array(arange(-3.0, 3.0, 0.1))
    # x=x1, y=x2 表示的是 w0x0+w1x1+w2x2 = 0 的直線
    y = (-weights[0] - weights[1] * x) / weights[2]
    ax.plot(x, y)
    plt.xlabel('X1')
    plt.ylabel('X2')
    plt.show()


if __name__ == '__main__':
    dataArr, labelMat = loadDataSet()
    weights = gradAscent(dataArr, labelMat)
    # getA():
    #   將矩陣轉換為ndarray
    plotBestFit(weights.getA())

7.2. 病馬死亡率案例

# coding:utf-8
from numpy import *

"""
病馬死亡率案例
"""


# sigmoid函式
def sigmoid(inX):
    return 1.0 / (1 + exp(-inX))


# 隨機梯度上升
# stochastic gradient ascent
def stocGradAscent0(dataMatrix, classLabels):
    m, n = shape(dataMatrix)
    alpha = 0.01
    weights = ones(n)
    # 遍歷每一個數據
    for i in range(m):
        # 陣列對應元素相乘再相加
        h = sigmoid(sum(dataMatrix[i] * weights))
        error = classLabels[i] - h
        weights = weights + alpha * error * dataMatrix[i]
    return weights


# 改進後的隨機梯度上升
# 學習率隨迭代次數減少
# 隨機選擇樣本
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
    m, n = shape(dataMatrix)
    weights = ones(n)
    for j in range(numIter):
        dataIndex = list(range(m))
        for i in range(m):
            # 學習率降低
            alpha = 4 / (1.0 + j + i) + 0.0001
            randIndex = int(random.uniform(0, len(dataIndex)))
            h = sigmoid(sum(dataMatrix[dataIndex[randIndex]] * weights))
            error = classLabels[dataIndex[randIndex]] - h
            weights = weights + alpha * error * dataMatrix[dataIndex[randIndex]]
            del (dataIndex[randIndex])
    return weights


# 分類函式
def classifyVector(inX, weights):
    prob = sigmoid(sum(inX * weights))
    if prob > 0.5:
        return 1
    else:
        return 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 = stocGradAscent1(array(trainingSet), trainingLabels, 1000)
    correctCount = 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]):
            correctCount += 1
    accuracy = (float(correctCount) / numTestVec)
    print("the accuracy of this test is: %f" % accuracy)
    return accuracy


# 多次測試分類器
def multiTest():
    numTests = 10
    correctSum = 0.0
    for k in range(numTests):
        correctSum += colicTest()
    print("after %d iterations the average accuracy is: %f" % (numTests, correctSum / float(numTests)))


if __name__ == '__main__':
    multiTest()

如果這篇文章對你有一點小小的幫助,請給個關注喔~我會非常開心的~