談談對機器學習中邏輯回歸的理解(Logistic Regression)


什么是邏輯回歸:

邏輯回歸是離散選擇法模型之一,屬於多重變量分析范疇,是社會學、生物統計學、臨床、數量心理學、計量經濟學、市場營銷等統計實證分析的常用方法。邏輯回歸一般用於二分類(Binary Classification)問題中,給定一些輸入,輸出結果是離散值。例如用邏輯回歸實現一個貓分類器,輸入一張圖片 x ,預測圖片是否為貓,輸出該圖片中存在貓的概率結果 y。從生物學的角度講:就是一個模型對外界的刺激(訓練樣本)做出反應,趨利避害(評價標准)。

假設有一個二分類問題,輸出為y∈{0,1},而線性回歸模型產生的預測值為z=wTx+b是實數值,我們希望有一個理想的階躍函數來幫我們實現z值到0/1值的轉化。
ϕ(z)=⎧⎩⎨00.51if z < 0if z = 0if z > 0
然而該函數不連續,我們希望有一個單調可微的函數來供我們使用,於是便找到了Sigmoid functionSigmoid function來替代。
ϕ(z)=1+e−z
兩者的圖像如下圖所示(圖片出自文獻2)

 

 

圖1:sigmoid & step function

有了Sigmoid fuctionSigmoid fuction之后,由於其取值在[0,1],我們就可以將其視為類11的后驗概率估計p(y=1|x)。說白了,就是如果有了一個測試點x,那么就可以用Sigmoid fuctionSigmoid fuction算出來的結果來當做該點x屬於類別1的概率大小。
於是,非常自然地,我們把Sigmoid fuctionSigmoid fuction計算得到的值大於等於0.5的歸為類別1,小於0.5的歸為類別0。
y^={10if ϕ(z)≥0.5otherwise

圖2:邏輯回歸網絡

邏輯函數的推導:

假設有數據3列10行,其中前兩列為x1和x2的值,第3列表示y的值;10行表示取了10個樣本點。我們可以將這些數據當做訓練模型參數的訓練樣本。

見到訓練樣本就可以比較直觀的理解算法的輸入,以及我們如何利用這些數據來訓練邏輯回歸分類器,進而用訓練好的模型來預測新的樣本(檢測樣本)。

從邏輯回歸的參數形式,我們可以看到邏輯回歸模型中有兩個待定參數a(x的系數)和b(常數項),我們現在給出來的數據有兩個特征x1, x2,因此整個模型就增加了一項:ax1 + cx2 + b。為了形式上的統一,我們使用帶下標的a表示不同的參數(a0表示常數項b並作x0的參數<x0=1>,a1、a2分別表示x1和x2的參數),就可以得到:

 

a0x0+a1x1+a2x2

 

 

這樣統一起來后,就可以使用矩陣表示了(比起前面展開的線性表示方式,用矩陣表示模型和參數更加簡便,而且矩陣運算的速度也更快):

 

[a0a1a2]⎡⎣⎢x0x1x2⎤⎦⎥=aTX

 

 

 將上面的式子帶入到(1)式,我們就可以得到邏輯回歸的另一種表示形式了:

 

p(x;a)=11+eaTX2

 

 此時,可以很清楚的看到,我們后面的行動都是為了確定一個合適的a(一個參數向量),使得對於一個新來的X(也是一個向量),我們可以盡可能准確的給出一個y值,0或者1.

 

注:數據是二維的,也就是說這組觀察樣本中有兩個自變量,即兩個特征(feature)。

邏輯回歸的代價函數
好了,所要用的幾個函數我們都有了,接下來要做的就是根據給定的訓練集,把參數w給求出來了。要找參數w,首先就是得把代價函數(cost function)給定義出來,也就是目標函數。
我們第一個想到的自然是模仿線性回歸的做法,利用誤差平方和來當代價函數。
J(w)=∑i12(ϕ(z(i))−y(i))2
其中,z(i)=wTx(i)+b,i表示第ii個樣本點,y(i)表示第ii個樣本的真實值,ϕ(z(i))表示第ii個樣本的預測值。
這時,如果我們將ϕ(z(i))=11+e−z(i)代入的話,會發現這是一個非凸函數,這就意味着代價函數有着許多的局部最小值,這不利於我們的求解。


圖3:凸函數和非凸函數

那么我們不妨來換一個思路解決這個問題。前面,我們提到了ϕ(z)可以視為類11的后驗估計,所以我們有
p(y=1|x;w)=ϕ(wTx+b)=ϕ(z)
p(y=0|x;w)=1−ϕ(z)
其中,p(y=1|x;w)表示給定w,那么x點y=1的概率大小。
上面兩式可以寫成一般形式
p(y|x;w)=ϕ(z)y(1−ϕ(z))(1−y)
接下來我們就要用極大似然估計來根據給定的訓練集估計出參數w。
L(w)=∏ni=1p(y(i)|x(i);w)=∏ni=1(ϕ(z(i)))y(i)(1−ϕ(z(i)))1−y(i)
為了簡化運算,我們對上面這個等式的兩邊都取一個對數
l(w)=lnL(w)=∑ni=1y(i)ln(ϕ(z(i)))+(1−y(i))ln(1−ϕ(z(i)))
我們現在要求的是使得l(w)l(w)最大的ww。沒錯,我們的代價函數出現了,我們在l(w)前面加個負號不就變成就最小了嗎?不就變成我們代價函數了嗎?
J(w)=−l(w)=−∑ni=1y(i)ln(ϕ(z(i)))+(1−y(i))ln(1−ϕ(z(i)))
為了更好地理解這個代價函數,我們不妨拿一個例子的來看看
J(ϕ(z),y;w)=−yln(ϕ(z))−(1−y)ln(1−ϕ(z))
也就是說
J(ϕ(z),y;w)={−ln(ϕ(z))−ln(1−ϕ(z))if y=1if y=0
我們來看看這是一個怎么樣的函數


圖4:代價函數

從圖中不難看出,如果樣本的值是1的話,估計值ϕ(z)越接近1付出的代價就越小,反之越大;同理,如果樣本的值是0的話,估計值ϕ(z)越接近0付出的代價就越小,反之越大。
利用梯度下降法求參數
在開始梯度下降之前,要這里插一句,sigmoid functionsigmoid function有一個很好的性質就是
ϕ′(z)=ϕ(z)(1−ϕ(z))
下面會用到這個性質。
還有,我們要明確一點,梯度的負方向就是代價函數下降最快的方向。什么?為什么?好,我來說明一下。借助於泰特展開,我們有
f(x+δ)−f(x)≈f′(x)⋅δ
其中,f′(x)和δ為向量,那么這兩者的內積就等於
f′(x)⋅δ=||f′(x)||⋅||δ||⋅cosθ
當θ=π時,也就是δ在f′(x)的負方向上時,取得最小值,也就是下降的最快的方向了~
okay?好,坐穩了,我們要開始下降了。
w:=w+Δw, Δw=−η∇J(w)
沒錯,就是這么下降。沒反應過來?那我再寫詳細一些
wj:=wj+Δwj, Δwj=−η∂J(w)∂wj
其中,wj表示第j個特征的權重;η為學習率,用來控制步長。
重點來了。
∂J(w)wj=−∑ni=1(y(i)1ϕ(z(i))−(1−y(i))11−ϕ(z(i)))∂ϕ(z(i))∂wj=−∑ni=1(y(i)1ϕ(z(i))−(1−y(i))11−ϕ(z(i)))ϕ(z(i))(1−ϕ(z(i)))∂z(i)∂wj=−∑ni=1(y(i)(1−ϕ(z(i)))−(1−y(i))ϕ(z(i)))x(i)j=−∑ni=1(y(i)−ϕ(z(i)))x(i)
所以,在使用梯度下降法更新權重時,只要根據下式即可
wj:=wj+η∑ni=1(y(i)−ϕ(z(i)))x(i)
此式與線性回歸時更新權重用的式子極為相似,也許這也是邏輯回歸要在后面加上回歸兩個字的原因吧。
當然,在樣本量極大的時候,每次更新權重會非常耗費時間,這時可以采用隨機梯度下降法,這時每次迭代時需要將樣本重新打亂,然后用下式不斷更新權重。
wj:=wj+η(y(i)−ϕ(z(i)))x(i)j,for i in range(n)
也就是去掉了求和,而是針對每個樣本點都進行更新。
用python代碼實現邏輯回歸

 

'''
初始化線性函數參數為1
構造sigmoid函數
重復循環I次
    計算數據集梯度
    更新線性函數參數
確定最終的sigmoid函數
輸入訓練(測試)數據集
運用最終sigmoid函數求解分類
'''

# -*- coding:utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt
import random
 
 
def text2num(string):
    """
    :param string: string
    :return: list
    """
    str_list = string.replace("\n", " ").split(" ")
    while '' in str_list:
        str_list.remove('')
    num_list = [float(i) for i in str_list]
    return num_list
 
 
def sigmoid(x):
    """
    :param x: 輸入需要計算的值
    :return: 
    """
    return 1.0 / (1 + np.exp(-x))
 
 
def data_plot(data_list, weight):
    """
    :param data_list:數據點集合 
    :param weight: 參數集合
    :return: null
    """
    x_data = [list(i[0:2]) for i in data_list if i[2] == 0.0]
    y_data = [list(i[0:2]) for i in data_list if i[2] == 1.0]
    x_data = np.reshape(x_data, np.shape(x_data))
    y_data = np.reshape(y_data, np.shape(y_data))
    linear_x = np.arange(-4, 4, 1)
    linear_y = (-weight[0] - weight[1] * linear_x) / weight[2]
    print(linear_y)
    plt.figure(1)
    plt.scatter(x_data[:, 0], x_data[:, 1], c='r')
    plt.scatter(y_data[:, 0], y_data[:, 1], c='g')
    print(linear_x)
    print(linear_y.tolist()[0])
    plt.plot(linear_x, linear_y.tolist()[0])
    plt.show()
 
 
def grad_desc(data_mat, label_mat, rate, times):
    """
    :param data_mat: 數據特征
    :param label_mat: 數據標簽
    :param rate: 速率
    :param times: 循環次數
    :return: 參數
    """
    data_mat = np.mat(data_mat)
    label_mat = np.mat(label_mat)
    m,n = np.shape(data_mat)
    weight = np.ones((n, 1))
    for i in range(times):
        h = sigmoid(data_mat * weight)
        error = h - label_mat
        weight = weight - rate * data_mat.transpose() * error
    return weight
 
 
def random_grad_desc(data_mat, label_mat, rate, times):
    """
    :param data_mat: 數據特征
    :param label_mat: 數據標簽
    :param rate: 速率
    :param times: 循環次數
    :return: 參數
    """
    data_mat = np.mat(data_mat)
    m,n = np.shape(data_mat)
    weight = np.ones((n, 1))
    for i in range(times):
        for j in range(m):
            h = sigmoid(data_mat[j] * weight)
            error = h - label_mat[j]
            weight = weight - rate * data_mat[j].transpose() * error
    return weight
 
 
def improve_random_grad_desc(data_mat, label_mat, times):
    """
    :param data_mat: 數據特征
    :param label_mat: 數據標簽
    :param rate: 速率
    :param times: 循環次數
    :return: 參數
    """
    data_mat = np.mat(data_mat)
    m,n = np.shape(data_mat)
    weight = np.ones((n, 1))
    for i in range(times):
        index_data = [i for i in range(m)]
        for j in range(m):
            rate = 0.0001 + 4 / (i + j + 1)
            index = random.sample(index_data, 1)
            h = sigmoid(data_mat[index] * weight)
            error = h - label_mat[index]
            weight = weight - rate * data_mat[index].transpose() * error
            index_data.remove(index[0])
    return weight
 
 
def main():
    file = open("/Users/chenzu/Documents/code-machine-learning/data/LR", "rb")
    file_lines = file.read().decode("UTF-8")
    data_list = text2num(file_lines)
    data_len = int(len(data_list) / 3)
    data_list = np.reshape(data_list, (data_len, 3))
    data_mat_temp = data_list[:, 0:2]
    data_mat = []
    for i in data_mat_temp:
        data_mat.append([1, i[0], i[1]])
    print(data_mat)
    label_mat = data_list[:, 2:3]
 
 
    #梯度下降求參數
    weight = improve_random_grad_desc(data_mat, label_mat, 500)
    print(weight)
    data_plot(data_list, weight)
 
 
if __name__ == '__main__':
    main()

 

邏輯回歸算法優缺點:

優點:計算代價不高、容易理解和實現

缺點:容易欠擬合,分類精度不高。

適用於數值型和標稱型數據。

標稱型:標稱型目標變量的結果只在有限目標集中取值,如真與假(標稱型目標變量主要用於分類)
數值型:數值型目標變量則可以從無限的數值集合中取值,如0.100,42.001等 (數值型目標變量主要用於回歸分析)

 

 參考:

  斯坦福大學吳恩達的machine learning相關課程

  https://blog.csdn.net/zjuPeco/article/details/77165974

  https://blog.csdn.net/Julialove102123/article/details/78405261


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM