前言
本文將介紹機器學習分類演算法中的Logistic迴歸分類演算法並給出虛擬碼,Python程式碼實現。
(說明:從本文開始,將接觸到最優化演算法相關的學習。旨在將這些最優化的演算法用於訓練出一個非線性的函式,以用於分類。)
演算法原理
首先要提到的概念是迴歸。
對於迴歸這個概念,在以後的文章會有系統而深入的學習。簡單的說,迴歸就是用一條線對N多資料點進行一個擬合,這個擬合的過程就叫做迴歸。
Logistic迴歸分類演算法就是對資料集建立迴歸公式,以此進行分類。
而至於如何尋找最佳迴歸係數,或者說是分類器的訓練,就需要使用到最優化演算法了。
迴歸分類器的形式
基本形式是用每個特徵都乘以一個迴歸係數,然後把所有的結果值相加。
這樣算出的很多結果都是連續的,不利於分類,故可以將結果再帶入到一個Sigmoid函式以得到一些比較離散的分類結果。
Sigmoid函式的輪廓如下:
這樣,計算的結果會是一個0-1的值。進而0.5以上歸為一類,以下歸為一類即可。(一般的邏輯迴歸只能解決兩個分類的問題)
接下來的工作重點就轉移到了最佳迴歸係數的確定了。
最佳迴歸係數的確定
確定最佳迴歸係數的過程,也就是對資料集進行訓練的過程。
求最佳迴歸係數的步驟如下:
1. 列出分類函式:
(θ 指回歸係數,在實踐中往往會再對結果進行一個Sigmoid轉換)
2. 給出分類函式對應的錯誤估計函式:
(m為樣本個數)
只有當某個θ向量使上面的錯誤估計函式J(θ)取得最小值的時候,這個θ向量才是最佳迴歸係數向量。
3. 採用梯度下降法或者最小二乘法求錯誤函式取得最小值的時候θ的取值:
為表述方便,上式僅為一個樣本的情況,實際中要綜合多個樣本的情況需要進行一個求和 (除非你使用後面會介紹的隨機梯度上升演算法),具體請參考下面的程式碼實現部分。
將步驟 2 中的錯誤函式加上負號,就可以把問題轉換為求極大值,梯度下降法轉換為梯度上升法。
更詳盡的推導部分,在以後專門分析迴歸的文章中給出。
基於梯度上升法的最佳迴歸引數擬合
梯度上升法求最大值的核心思想是將自變數沿著目標函式的梯度方向移動,一直移動到指定的次數或者說某個允許的誤差範圍。
基於梯度上升法的分類虛擬碼:
1 每個迴歸係數初始化為1 2 重複R次: 3 計算整個資料集的梯度 4 使用 alpha * gradient 更新迴歸係數向量 5 返回迴歸係數
下面給出完整的實現及測試程式碼(用到的資料集檔案共 4 列,前 3 列為特徵,最後一列為分類結果):
1 #!/usr/bin/env python 2 # -*- coding:UTF-8 -*- 3 4 ''' 5 Created on 20**-**-** 6 7 @author: fangmeng 8 ''' 9 10 import numpy 11 12 #===================================== 13 # 輸入: 14 # 空 15 # 輸出: 16 # dataMat: 測試資料集 17 # labelMat: 測試分類標籤集 18 #===================================== 19 def loadDataSet(): 20 '建立測試資料集,分類標籤集並返回。' 21 22 # 測試資料集 23 dataMat = []; 24 # 測試分類標籤集 25 labelMat = [] 26 # 文字資料來源 27 fr = open('/home/fangmeng/testSet.txt') 28 29 # 載入資料 30 for line in fr.readlines(): 31 lineArr = line.strip().split() 32 dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])]) 33 labelMat.append(int(lineArr[2])) 34 35 return dataMat,labelMat 36 37 38 #===================================== 39 # 輸入: 40 # inX: 目標轉換向量 41 # 輸出: 42 # 1.0/(1+numpy.exp(-inX)): 轉換結果 43 #===================================== 44 def sigmoid(inX): 45 'sigmoid轉換函式' 46 47 return 1.0/(1+numpy.exp(-inX)) 48 49 #===================================== 50 # 輸入: 51 # dataMatIn: 資料集 52 # classLabels: 分類標籤集 53 # 輸出: 54 # weights: 最佳擬合引數向量 55 #===================================== 56 def gradAscent(dataMatIn, classLabels): 57 '基於梯度上升法的logistic迴歸分類器' 58 59 # 將資料集,分類標籤集存入矩陣型別。 60 dataMatrix = numpy.mat(dataMatIn) 61 labelMat = numpy.mat(classLabels).transpose() 62 63 # 上升步長度 64 alpha = 0.001 65 # 迭代次數 66 maxCycles = 500 67 # 初始化迴歸引數向量 68 m,n = numpy.shape(dataMatrix) 69 weights = numpy.ones((n,1)) 70 71 # 對迴歸係數進行maxCycles次梯度上升 72 for k in range(maxCycles): 73 h = sigmoid(dataMatrix*weights) 74 error = (labelMat - h) 75 weights = weights + alpha * dataMatrix.transpose()* error 76 77 return weights 78 79 def test(): 80 '測試' 81 82 dataArr, labelMat = loadDataSet() 83 print gradAscent(dataArr, labelMat) 84 85 if __name__ == '__main__': 86 test()
測試結果:
擬合結果展示
可使用matplotlib畫決策邊界,用作分析擬合結果是否達到預期。
繪製及測試部分程式碼如下所示:
1 #====================================== 2 # 輸入: 3 # weights: 迴歸係數向量 4 # 輸出: 5 # 圖形化的決策邊界演示 6 #====================================== 7 def plotBestFit(weights): 8 '決策邊界演示' 9 10 import matplotlib.pyplot as plt 11 # 獲取資料集 分類標籤集 12 dataMat,labelMat=loadDataSet() 13 dataArr = numpy.array(dataMat) 14 15 # 兩種分類下的兩種特徵列表 16 xcord1 = []; ycord1 = [] 17 xcord2 = []; ycord2 = [] 18 for i in range(numpy.shape(dataArr)[0]): 19 if int(labelMat[i])== 1: 20 xcord1.append(dataArr[i,1]); ycord1.append(dataArr[i,2]) 21 else: 22 xcord2.append(dataArr[i,1]); ycord2.append(dataArr[i,2]) 23 24 fig = plt.figure() 25 ax = fig.add_subplot(111) 26 ax.scatter(xcord1, ycord1, s=30, c='red', marker='s') 27 ax.scatter(xcord2, ycord2, s=30, c='green') 28 29 # 繪製決策邊界 30 x = numpy.arange(-3.0, 3.0, 0.1) 31 y = (-weights[0]-weights[1]*x)/weights[2] 32 ax.plot(x, y) 33 34 plt.xlabel('X1'); plt.ylabel('X2'); 35 plt.show() 36 37 def test(): 38 '測試' 39 40 dataArr, labelMat = loadDataSet() 41 weights = gradAscent(dataArr, labelMat) 42 plotBestFit(weights.getA())
測試結果:
更好的求最值方法 - 隨機梯度上升
Logistic迴歸的本質其實就是求擬合引數,而求擬合引數最核心的就是求錯誤估計函式的最小值。
仔細分析上面的程式碼,會發現該分類的效率做多的耗費也是在求最值上面。由於每次都要用所有資料來計算梯度,導致資料集非常大的時候,該演算法很不給力。
實踐表明,每次僅僅用一個樣本點來更新迴歸係數,當所有樣本算完,也能達到相似的效果(僅僅是相似,或者說接近)。
由於可以在每個樣本到達的時候對分類器進行增量式更新,因此隨機梯度上升演算法其實是一個線上學習演算法。
基於隨機梯度上升的分類虛擬碼:
1 所有迴歸係數初始化為1 2 對資料集中的每個樣本: 3 計算該樣本的梯度 4 使用 alpha * gradient 更新迴歸係數值 5 返回迴歸係數值
請對比上面的基本梯度上升演算法進行理解學習。
優化之後的分類演算法函式如下:
1 #===================================== 2 # 輸入: 3 # dataMatIn: 資料集(注意是向量型別) 4 # classLabels: 分類標籤集 5 # 輸出: 6 # weights: 最佳擬合引數向量 7 #===================================== 8 def stocGradAscent0(dataMatrix, classLabels): 9 '基於隨機梯度上升法的logistic迴歸分類器' 10 11 m,n = numpy.shape(dataMatrix) 12 # 上升步長度 13 alpha = 0.01 14 # 初始化迴歸引數向量 15 weights = numpy.ones(n) 16 17 # 對迴歸係數進行樣本數量次數的梯度上升,每次上升僅用一個樣本。 18 for i in range(m): 19 h = sigmoid(sum(dataMatrix[i]*weights)) 20 error = classLabels[i] - h 21 weights = weights + alpha * error * dataMatrix[i] 22 return weights
你也許會疑惑 "不是說隨機梯度上升演算法嗎?隨機呢?",不用急,很快就會提到這個。
分析優化後的程式碼可以看到兩個區別:一個是當前分類結果變數h和誤差變數都是數值型別(之前為向量型別),二是無矩陣轉換過程,資料集為numpy向量的陣列型別。
測試結果:
對比優化前的演算法結果,可以看出分類錯誤率更高了。
優化後的效果反而更差了?這樣說有點不公平,因為優化後的演算法只是迭代了100次,而優化前的足足有500次。
那麼接下來可以進一步優化,理論依據為:增加迭代計算的次數,當達到一個接近收斂或者已經收斂的狀態時,停止迭代。
那麼如何做到這點呢?
第一是要動態的選定步長,第二,就是每次隨機的選定樣本,這就是為什麼叫做隨機梯度上升演算法了。
最終修改後的分類器如下:
1 #===================================== 2 # 輸入: 3 # dataMatIn: 資料集(注意是向量型別) 4 # classLabels: 分類標籤集 5 # 輸出: 6 # weights: 最佳擬合引數向量 7 #===================================== 8 def stocGradAscent1(dataMatrix, classLabels, numIter=150): 9 '基於隨機梯度上升法的logistic迴歸分類器' 10 11 m,n = numpy.shape(dataMatrix) 12 weights = numpy.ones(n) 13 14 # 迭代次數控制 15 for j in range(numIter): 16 # 樣本選擇集 17 dataIndex = range(m) 18 # 隨機選取樣本遍歷一輪 19 for i in range(m): 20 # 動態修正步長 21 alpha = 4/(1.0+j+i)+0.0001 22 # 隨機選取變數進行梯度上升 23 randIndex = int(numpy.random.uniform(0,len(dataIndex))) 24 h = sigmoid(sum(dataMatrix[randIndex]*weights)) 25 error = classLabels[randIndex] - h 26 weights = weights + alpha * error * dataMatrix[randIndex] 27 # 從選擇集中刪除已經使用過的樣本 28 del(dataIndex[randIndex]) 29 return weights
執行結果:
這次,和最原始的基於梯度上升法分類器的結果就差不多了。但是迭代次數大大的減少了。
網上也有一些非常嚴格的證明隨機梯度上升演算法的收斂速度更快(相比基礎梯度上升演算法)的證明,有興趣的讀者可以查詢相關論文。
小結
1. 邏輯迴歸的計算代價不高,是很常用的分類演算法。集中基於隨機梯度上升的邏輯迴歸分類器能夠支援線上學習。
2. 但邏輯迴歸演算法缺點很明顯 - 一般只能解決兩個類的分類問題。
3. 另外邏輯迴歸容易欠擬合,導致分類的精度不高。