一、總述
邏輯迴歸是應用很是普遍的一個分類機器學習算法,它將數據擬合到一個logit函數(或者叫作logistic函數)中,從而可以完成對事件發生的機率進行預測。python
二、由來
要說邏輯迴歸,咱們得追溯到線性迴歸,想必你們對線性迴歸都有必定的瞭解,即對於多維空間中存在的樣本點,咱們用特徵的線性組合去擬合空間中點的分佈和軌跡。以下圖所示:git
線性迴歸能對連續值結果進行預測,而現實生活中常見的另一類問題是,分類問題。最簡單的狀況是是與否的二分類問題。好比說醫生須要判斷病人是否生病,銀行要判斷一我的的信用程度是否達到能夠給他發信用卡的程度,郵件收件箱要自動對郵件分類爲正常郵件和垃圾郵件等等。github
固然,咱們最直接的想法是,既然可以用線性迴歸預測出連續值結果,那根據結果設定一個閾值是否是就能夠解決這個問題了呢?事實是,對於很標準的狀況,確實能夠的,這裏咱們套用Andrew Ng老師的課件中的例子,下圖中X爲數據點腫瘤的大小,Y爲觀測結果是不是惡性腫瘤。經過構建線性迴歸模型,如hθ(x)所示,構建線性迴歸模型後,咱們設定一個閾值0.5,預測hθ(x)≥0.5的這些點爲惡性腫瘤,而hθ(x)<0.5爲良性腫瘤。算法
但不少實際的狀況下,咱們須要學習的分類數據並無這麼精準,好比說上述例子中忽然有一個不按套路出牌的數據點出現,以下圖所示:app
你看,如今你再設定0.5,這個斷定閾值就失效了,而現實生活的分類問題的數據,會比例子中這個更爲複雜,而這個時候咱們藉助於線性迴歸+閾值的方式,已經很難完成一個魯棒性很好的分類器了。機器學習
在這樣的場景下,邏輯迴歸就誕生了。它的核心思想是,若是線性迴歸的結果輸出是一個連續值,而值的範圍是沒法限定的,那咱們有沒有辦法把這個結果值映射爲能夠幫助咱們判斷的結果呢。而若是輸出結果是 (0,1) 的一個機率值,這個問題就很清楚了。咱們在數學上找了一圈,還真就找着這樣一個簡單的函數了,就是很神奇的sigmoid函數(以下):函數
若是把sigmoid函數圖像畫出來,是以下的樣子:學習
Sigmoid Logistic Functionspa
從函數圖上能夠看出,函數y=g(z)在z=0的時候取值爲1/2,而隨着z逐漸變小,函數值趨於0,z逐漸變大的同時函數值逐漸趨於1,而這正是一個機率的範圍。.net
因此咱們定義線性迴歸的預測函數爲Y=WTX,那麼邏輯迴歸的輸出Y= g(WTX),其中y=g(z)函數正是上述sigmoid函數(或者簡單叫作S形函數)。
三、斷定邊界
咱們如今再來看看,爲何邏輯迴歸可以解決分類問題。這裏引入一個概念,叫作斷定邊界,能夠理解爲是用以對不一樣類別的數據分割的邊界,邊界的兩旁應該是不一樣類別的數據。
從二維直角座標系中,舉幾個例子,大概是以下這個樣子:
有時候是這個樣子:
甚至多是這個樣子:
上述三幅圖中的紅綠樣本點爲不一樣類別的樣本,而咱們劃出的線,不論是直線、圓或者是曲線,都能比較好地將圖中的兩類樣本分割開來。這就是咱們的斷定邊界,下面咱們來看看,邏輯迴歸是如何根據樣本點得到這些斷定邊界的。
咱們依舊借用Andrew Ng教授的課程中部分例子來說述這個問題。
回到sigmoid函數,咱們發現:
當g(z)≥0.5時, z≥0;
對於hθ(x)=g(θTX)≥0.5, 則θTX≥0, 此時意味着預估y=1;
反之,當預測y = 0時,θTX<0;
因此咱們認爲θTX =0是一個決策邊界,當它大於0或小於0時,邏輯迴歸模型分別預測不一樣的分類結果。
先看第一個例子hθ(x)=g(θ0+θ1X1+θ2X2),其中θ0 ,θ1 ,θ2分別取-3, 1, 1。則當−3+X1+X2≥0時, y = 1; 則X1+X2=3是一個決策邊界,圖形表示以下,恰好把圖上的兩類點區分開來:
例1只是一個線性的決策邊界,當hθ(x)更復雜的時候,咱們能夠獲得非線性的決策邊界,例如:
這時當x12+x22≥1時,咱們斷定y=1,這時的決策邊界是一個圓形,以下圖所示:
因此咱們發現,理論上說,只要咱們的hθ(x)設計足夠合理,準確的說是g(θTx)中θTx足夠複雜,咱們能在不一樣的情形下,擬合出不一樣的斷定邊界,從而把不一樣的樣本點分隔開來。
四、代價函數與梯度降低
咱們經過對斷定邊界的說明,知道會有合適的參數θ使得θTx=0成爲很好的分類斷定邊界,那麼問題就來了,咱們如何斷定咱們的參數θ是否合適,有多合適呢?更進一步,咱們有沒有辦法去求得這樣的合適參數θ呢?
這就是咱們要提到的代價函數與梯度降低了。
所謂的代價函數Cost Function,實際上是一種衡量咱們在這組參數下預估的結果和實際結果差距的函數,好比說線性迴歸的代價函數定義爲:
固然咱們能夠和線性迴歸類比獲得一個代價函數,實際就是上述公式中hθ(x)取爲邏輯迴歸中的g(θTx),可是這會引起代價函數爲「非凸」函數的問題,簡單一點說就是這個函數有不少個局部最低點,以下圖所示:
而咱們但願咱們的代價函數是一個以下圖所示,碗狀結構的凸函數,這樣咱們算法求解到局部最低點,就必定是全局最小值點。
所以,上述的Cost Function對於邏輯迴歸是不可行的,咱們須要其餘形式的Cost Function來保證邏輯迴歸的成本函數是凸函數。
咱們跳過大量的數學推導,直接出結論了,咱們找到了一個適合邏輯迴歸的代價函數:
Andrew Ng老師解釋了一下這個代價函數的合理性,咱們首先看當y=1的狀況:
若是咱們的類別y = 1, 而斷定的hθ(x)=1,則Cost = 0,此時預測的值和真實的值徹底相等,代價本該爲0;而若是判斷hθ(x)→0,代價->∞,這很好地懲罰了最後的結果。
而對於y=0的狀況,以下圖所示,也一樣合理:
下面咱們說說梯度降低,梯度降低算法是調整參數θ使得代價函數J(θ)取得最小值的最基本方法之一。從直觀上理解,就是咱們在碗狀結構的凸函數上取一個初始值,而後挪動這個值一步步靠近最低點的過程,以下圖所示:
咱們先簡化一下邏輯迴歸的代價函數:
從數學上理解,咱們爲了找到最小值點,就應該朝着降低速度最快的方向(導函數/偏導方向)邁進,每次邁進一小步,再看看此時的降低最快方向是哪,再朝着這個方向邁進,直至最低點。
用迭代公式表示出來的最小化J(θ)的梯度降低算法以下:
五、代碼與實現
咱們來一塊兒看兩個具體數據上作邏輯迴歸分類的例子,其中一份數據爲線性斷定邊界,另外一份爲非線性。
示例1。
第一份數據爲data1.txt,部份內容以下:
咱們先來看看數據在空間的分佈,代碼以下。
[python] view plain copy
- from numpy import loadtxt, where
- from pylab import scatter, show, legend, xlabel, ylabel
-
- #load the dataset
- data = loadtxt('/home/HanXiaoyang/data/data1.txt', delimiter=',')
-
- X = data[:, 0:2]
- y = data[:, 2]
-
- pos = where(y == 1)
- neg = where(y == 0)
- scatter(X[pos, 0], X[pos, 1], marker='o', c='b')
- scatter(X[neg, 0], X[neg, 1], marker='x', c='r')
- xlabel('Feature1/Exam 1 score')
- ylabel('Feature2/Exam 2 score')
- legend(['Fail', 'Pass'])
- show()
獲得的結果以下:
下面咱們寫好計算sigmoid函數、代價函數、和梯度降低的程序:
[python] view plain copy
- def sigmoid(X):
- '''''Compute sigmoid function '''
- den =1.0+ e **(-1.0* X)
- gz =1.0/ den
- return gz
- def compute_cost(theta,X,y):
- '''''computes cost given predicted and actual values'''
- m = X.shape[0]#number of training examples
- theta = reshape(theta,(len(theta),1))
-
- J =(1./m)*(-transpose(y).dot(log(sigmoid(X.dot(theta))))- transpose(1-y).dot(log(1-sigmoid(X.dot(theta)))))
-
- grad = transpose((1./m)*transpose(sigmoid(X.dot(theta))- y).dot(X))
- #optimize.fmin expects a single value, so cannot return grad
- return J[0][0]#,grad
- def compute_grad(theta, X, y):
- '''''compute gradient'''
- theta.shape =(1,3)
- grad = zeros(3)
- h = sigmoid(X.dot(theta.T))
- delta = h - y
- l = grad.size
- for i in range(l):
- sumdelta = delta.T.dot(X[:, i])
- grad[i]=(1.0/ m)* sumdelta *-1
- theta.shape =(3,)
- return grad
咱們用梯度降低算法獲得的結果斷定邊界是以下的樣子:
最後咱們使用咱們的斷定邊界對training data作一個預測,而後比對一下準確率:
[python] view plain copy
- def predict(theta, X):
- '''''Predict label using learned logistic regression parameters'''
- m, n = X.shape
- p = zeros(shape=(m,1))
- h = sigmoid(X.dot(theta.T))
- for it in range(0, h.shape[0]):
- if h[it]>0.5:
- p[it,0]=1
- else:
- p[it,0]=0
- return p
- #Compute accuracy on our training set
- p = predict(array(theta), it)
- print'Train Accuracy: %f'%((y[where(p == y)].size / float(y.size))*100.0)
計算出來的結果是89.2%
示例2.
第二份數據爲data2.txt,部份內容以下:
咱們一樣把數據的分佈畫出來,以下:
咱們發如今這個例子中,咱們沒有辦法再用一條直線把兩類樣本點近似分開了,因此咱們打算試試多項式的斷定邊界,那麼咱們先要對給定的兩個feature作一個多項式特徵的映射。好比說,咱們作了以下的一個映射:
代碼以下:
[python] view plain copy
- def map_feature(x1, x2):
- '''''
- Maps the two input features to polonomial features.
- Returns a new feature array with more features of
- X1, X2, X1 ** 2, X2 ** 2, X1*X2, X1*X2 ** 2, etc...
- '''
- x1.shape =(x1.size,1)
- x2.shape =(x2.size,1)
- degree =6
- mapped_fea = ones(shape=(x1[:,0].size,1))
- m, n = mapped_fea.shape
- for i in range(1, degree +1):
- for j in range(i +1):
- r =(x1 **(i - j))*(x2 ** j)
- mapped_fea = append(<span style="font-family: Arial, Helvetica, sans-serif;">mapped_fea</span><span style="font-family: Arial, Helvetica, sans-serif;">, r, axis=1)</span>
- return mapped_fea
- mapped_fea = map_feature(X[:,0], X[:,1])
接着作梯度降低:
[python] view plain copy
- def cost_function_reg(theta, X, y, l):
- '''''Compute the cost and partial derivatives as grads
- '''
- h = sigmoid(X.dot(theta))
- thetaR = theta[1:,0]
- J =(1.0/ m)*((-y.T.dot(log(h)))-((1- y.T).dot(log(1.0- h)))) \
- +(l /(2.0* m))*(thetaR.T.dot(thetaR))
- delta = h - y
- sum_delta = delta.T.dot(X[:,1])
- grad1 =(1.0/ m)* sumdelta
- XR = X[:,1:X.shape[1]]
- sum_delta = delta.T.dot(XR)
- grad =(1.0/ m)*(sum_delta + l * thetaR)
- out = zeros(shape=(grad.shape[0], grad.shape[1]+1))
- out[:,0]= grad1
- out[:,1:]= grad
- return J.flatten(), out.T.flatten()
- m, n = X.shape
- y.shape =(m,1)
- it = map_feature(X[:,0], X[:,1])
- #Initialize theta parameters
- initial_theta = zeros(shape=(it.shape[1],1))
- #Use regularization and set parameter lambda to 1
- l =1
- # Compute and display initial cost and gradient for regularized logistic
- # regression
- cost, grad = cost_function_reg(initial_theta, it, y, l)
- def decorated_cost(theta):
- return cost_function_reg(theta, it, y, l)
- print fmin_bfgs(decorated_cost, initial_theta, maxfun=500)
接着在數據點上畫出斷定邊界:
[python] view plain copy
- #Plot Boundary
- u = linspace(-1,1.5,50)
- v = linspace(-1,1.5,50)
- z = zeros(shape=(len(u), len(v)))
- for i in range(len(u)):
- for j in range(len(v)):
- z[i, j]=(map_feature(array(u[i]), array(v[j])).dot(array(theta)))
- z = z.T
- contour(u, v, z)
- title('lambda = %f'% l)
- xlabel('Microchip Test 1')
- ylabel('Microchip Test 2')
- legend(['y = 1','y = 0','Decision boundary'])
- show()
- def predict(theta, X):
- '''''Predict whether the label
- is 0 or 1 using learned logistic
- regression parameters '''
- m, n = X.shape
- p = zeros(shape=(m,1))
- h = sigmoid(X.dot(theta.T))
- for it in range(0, h.shape[0]):
- if h[it]>0.5:
- p[it,0]=1
- else:
- p[it,0]=0
- return p
- #% Compute accuracy on our training set
- p = predict(array(theta), it)
- print'Train Accuracy: %f'%((y[where(p == y)].size / float(y.size))*100.0)
獲得的結果以下圖所示:
咱們發現咱們獲得的這條曲線確實將兩類點區分開來了。
六、總結
最後咱們總結一下邏輯迴歸。它始於輸出結果爲有實際意義的連續值的線性迴歸,可是線性迴歸對於分類的問題沒有辦法準確而又具有魯棒性地分割,所以咱們設計出了邏輯迴歸這樣一個算法,它的輸出結果表徵了某個樣本屬於某類別的機率。
邏輯迴歸的成功之處在於,將本來輸出結果範圍能夠很是大的θTX 經過sigmoid函數映射到(0,1),從而完成機率的估測。
而直觀地在二維空間理解邏輯迴歸,是sigmoid函數的特性,使得斷定的閾值可以映射爲平面的一條斷定邊界,固然隨着特徵的複雜化,斷定邊界多是多種多樣的樣貌,可是它可以較好地把兩類樣本點分隔開,解決分類問題。
求解邏輯迴歸參數的傳統方法是梯度降低,構造爲凸函數的代價函數後,每次沿着偏導方向(降低速度最快方向)邁進一小部分,直至N次迭代後到達最低點。
七、補充
本文的2份數據可在http://pan.baidu.com/s/1pKxJl1p上下載到,分別爲data1.txt和data2.txt,歡迎你們本身動手嘗試。
關於邏輯迴歸的完整ipython notebook示例代碼能夠在個人github上(https://github.com/HanXiaoyang/ML_examples/tree/master/logistic_regression)下載到,歡迎指正。