《機器學習實戰》學習筆記七:Logistics迴歸(梯度上升法)
1 Logistics迴歸概念
迴歸是指將一對資料擬合為一條直線的過程,而Logistics迴歸則是將回歸用於分類,其主要思想為:根據現有的資料對分類邊界線建立迴歸公式,依次為依據進行分類,在這裡最關鍵的一步是尋找最佳的擬合引數,這一步將會用到一些最優化的方法,如梯度上升等。
2 一些數學基礎
2.1 Sigmoid函式
本文以二分類為例,說到二分類問題,我們首先想到的是0,1的問題,那麼首先要找到一個函式,一個值為0或1的函式,這裡想到了一個高等數學中遇到的單位階躍函式:它的值只有0和1,但是這個函式存在0到1之間的跳躍,這個跳躍很難處理,慶幸的是,數學上還有另一個函式,和他有類似的性質,那就是Sigmoid函式,Sigmoid函式公式如下:
其影象為:
可見當座標軸足夠大時,Sigmoid就有類似階躍函式的性質
2.2 梯度上升法
Sigmoid的輸入記為z,z由以下公式得出:
若採用向量法上述公式可以寫成:
公式中 為分類器的輸入資料, 為我們需要找到的最佳引數,為了能夠準確地找到這一引數,需要一些最優化的知識,這裡就用到了梯度上升法這一最優化方法。
顧名思義,梯度上升法就是沿著函式梯度的方向向上搜尋,直至找到函式的最大值,也就是我們所需要的最優引數。這這裡需要明確兩個概念:梯度和步長。
梯度:某一函式在該點處的方向導數沿著該方向取得最大值,即函式在該點處沿著該方向(此梯度的方向)變化最快,變化率最大(模的變化)。
梯度的表示式為:
步長:在梯度方向上移動一步的距離,用 表示
由以上兩個變數我們可以得出梯度上升演算法的迭代公式如下:
3 演算法實現
3.1 梯度上升法
def loadDataSet(): #從檔案中讀取資料集
dataMat = [];labelMat = []
fr = open('testSet.txt')
for line in fr.readlines():
lineArr = line.strip().split()
dataMat.append([1.0 ,float(lineArr[0]),float(lineArr[1])])
labelMat.append(int(lineArr[2]))
return dataMat,labelMat
def sigmoid(inX): #sigmoid函式
return 1.0/(1+exp(-inX))
def gradAscent(dataMatIn,classLabels):#梯度上升
dataMatrix = mat(dataMatIn) #轉換成矩陣
labelMat = mat(classLabels).transpose() #轉置
m,n = shape(dataMatrix) #矩陣的維度
alpha = 0.001 #步長
maxCycles = 500 #迴圈次數
weights = ones((n,1)) #n*1的矩陣
for k in range(maxCycles):
h = sigmoid(dataMatrix * weights) # m*1的矩陣
error = (labelMat - h) # m*1的矩陣
weights = weights + alpha * dataMatrix.transpose() * error
# n*m的矩陣乘以m*1的矩陣得到n*1的矩陣,然後以weights相加
return weights
dataArr,LabelMat = loadDataSet()
3.2 繪製資料集和擬合直線
根據上述演算法計算出的結果,繪圖如下:
繪圖程式碼部分:
def plotBestFit(weights):
import matplotlib.pyplot as plt
dataMat,labelMat = loadDataSet()
dataArr = array(dataMat)
n= shape(dataArr)[0]
xcord1 = [];ycord1 = []
xcord2 = [];ycord2 = []
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 = arange(-3.0,3.0,0.1)
y= (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x,y)
plt.xlabel('X1');plt.ylabel('X2');
plt.show()
4 演算法改進
4.1 隨機梯度上升
梯度上升演算法在每次更新迴歸係數時都需要遍歷整個資料集,該方法在處理100個左右的資料時尚可,但是當資料集超過一定的數量時(如有十億樣本和成千上萬的特徵時),那麼該方法的計算複雜度就太高了。一種改進方法是依次僅才採用一個樣本點來更新迴歸係數,該方法稱為隨機梯度上升法。是一種線上學習方法,即可以再新樣本到來時對分類器進行增量式更新。
隨機梯度上升演算法程式碼如下:
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
可以看出隨機梯度上升演算法和梯度上升演算法在程式碼上十分相似,但也有一些區別:第一,梯度上升演算法的變數h和誤差error都是向量,而隨機梯度上升演算法則是數值;第二隨機梯度上升演算法沒有矩陣轉換的過程,所有變數的資料型別都是NumPy陣列,而梯度上升演算法資料型別則是矩陣。
對該演算法繪圖如下:
可以看出在迭代次數少的情況下(隨機梯度演算法迭代了200次,而梯度上升迭代了500次),隨機梯度上升演算法的效果是不如梯度上升的
4.2 改進的隨機梯度上升演算法
隨機梯度上升演算法對於3個不同的係數迭代200次,迴歸係數和迭代次數的關係如下:
可見迴歸係數的值存在週期性的波動,原因是存在一些不能正確分類的樣本點,在迭代的時候會引發係數劇烈的改變,另外演算法的收斂速度也較慢。
在這裡對隨機梯度上升演算法進行了兩處改進:一是時步長alpha在每次迭代的時候都調整一次,這樣做會緩解波動;第二個地方是通過隨機選取樣本來更新迴歸係數。這樣做將會減少週期性的波動,具體的實現程式碼如下:
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n = shape(dataMatrix)
weights = ones(n)
for j in range(numIter):
dataIndex = list(range(m)) #python 3 改法
for i in range(m):
alpha = 4/(1.0+j+i)+0.01 #步長的計算公式
randIndex = int(random.uniform(0,len(dataIndex))) #隨機選擇一個值
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex]-h
weights = weights + alpha*error*dataMatrix[randIndex]
del (dataIndex[randIndex]) #每次迭代都要刪除隨機選出的值
#按書上的寫 python 3 在這裡會報錯
return weights
迴歸係數的變化如下:
可見改進演算法的係數並沒有出現週期性的波動,而且收斂速度也快了很多。
對結果繪圖如下:
在演算法精度上,迭代了150次的改進演算法的精度和迭代500次的梯度上升演算法相當,這個演算法大大地減少了計算量。