什么是邏輯回歸:
邏輯回歸是離散選擇法模型之一,屬於多重變量分析范疇,是社會學、生物統計學、臨床、數量心理學、計量經濟學、市場營銷等統計實證分析的常用方法。邏輯回歸一般用於二分類(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的參數),就可以得到:
這樣統一起來后,就可以使用矩陣表示了(比起前面展開的線性表示方式,用矩陣表示模型和參數更加簡便,而且矩陣運算的速度也更快):
將上面的式子帶入到(1)式,我們就可以得到邏輯回歸的另一種表示形式了:
此時,可以很清楚的看到,我們后面的行動都是為了確定一個合適的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