主要內容
● Sigmoid函數和Logistoc迴歸分類器
● 最優化理論初步
● 梯度下降最優化算法
● 數據中的缺失項處理
我們將介紹最優化算法,並利用他們訓練出一個非線性函數用於分類。
利用Logistic迴歸的主要思想是:根據現有的數據對分類邊界線建立迴歸公式,以此進行分類。
“迴歸”一詞源於最佳擬合,表示要找到最佳擬合參數,使用的是最優化算法。
Logistic迴歸一般過程:
1. 收集數據:採用任意方法收集數據
2. 準備數據:由於需要進行距離計算,因此要求數據類型爲數值型,另外,結構化數據爲最佳。
3. 分析數據:採用任意方法對於數據進行分析
4. 訓練算法:大部分時間將用於訓練,並將其轉換爲對應的結構化數據
5. 測試算法:一旦訓練步驟完成,分類將會很快;
6. 使用算法:第一,我們需要輸入一些數據,並將其轉換爲對應的結構化數值;第二,基於訓練好的迴歸係數就可以對這些數值進行簡單的迴歸計算,判斷他們屬於哪個類別,在這之後,我們就可以輸出的類別上做一些其他分析工作。
5.1 基於logistic迴歸和sigmoid函數的分類
logistic迴歸:
優點:計算代價不高,易於理解和實現;
缺點:容易欠擬合,分類精度可能不高。
適用數據類型:數值型和標稱型數據。
下面進入數學理論部分:
我們想要的是能接受所有的輸入然後預測出類別。該函數被稱之爲 海維塞德階躍遷函數 或者 單位躍遷函數。
有一個有類似於可以輸出0或者1這種性質的函數,就是Sigmoid函數。
Sigmoid計算公式如下:
下圖給出了Sigmoid函數在不同座標尺下的曲線圖,當x爲0時,Sigmoid函數值爲0.5 ,隨着x增大,Sigmoid函數的值無限接近於1,隨着x的減小,Sigmoid函數的值無限接近於0 。 實際上,Sigmoid函數看起來像一個階躍函數。
因此,爲了實現 Logistic迴歸分類器,我們可以在每個特徵上都乘以一個迴歸係數,然後把所有的結果值相加,將這個總和代入Sigmoid函數 ,進而得到一個範圍在0~1之間的數值。所有大於0.5的數據被分入1類,所有小於0.5的被分入0類。實際上,Logistic迴歸也可以被看做是一種概率估計。
確定了分類器的函數形式之後,問題就變成了:最佳迴歸係數爲多少?如何確定大小?
5.2 基於最優化方法的最佳迴歸係數確定
Sigmoid函數的輸入記爲z,公式如下:
其中x是分類器的輸入數據,向量w是我們要找的最佳參數。
爲了尋找最佳參數,我們介紹一下梯度上升的最優化方法。我們將使用梯度上升的最優化方法來尋找最佳參數。
5.2.1 梯度上升法
梯度上升法基本思想:要找到某函數的最大值,最好的方法就是沿着該函數的梯度方向探尋。如果梯度爲 ▽ ,則函數f(x,y)的梯度如下表示:
梯度要沿着x的方向移動 (∂f(x+y))/∂x
梯度要沿着y的方向移動 (∂f(x+y))/∂y
梯度上升算法的迭代公式:
w: = w + α▽f(w)
5.2.2 訓練算法:使用梯度上升找到最佳參數
圖5-3 有100個樣本點:每個點包含兩個數值型特徵:X1和X2.。我們將通過使用梯度上升法找到最佳迴歸係數,也就是擬合出 Logistic迴歸 模型的最佳參數。
梯度上升法的僞代碼如下:
每個迴歸係數初始化爲1
重複R次:
計算整個數據集的梯度
使用alpha * gradient 更新迴歸係數的向量
返回迴歸係數
下面的代碼是梯度上升算法的具體實現,創建名爲 logRegres.py的文件,輸入代碼:
程序5-1 Logistic迴歸梯度上升最優化算法
import math
from numpy import *
def loadDataSet():
dataMat = []; labelMat = []
fr = open(r'E:\ML\ML_source_code\mlia\Ch05\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):
return 1.0/(1+exp(-inX))
def gradAscent(dataMatIn,classLabels):
#調用numpy函數可以將數組轉行爲矩陣數據類型
dataMatrix = mat(dataMatIn)
#將矩陣轉置
labelMat = mat(classLabels).transpose()
#看一下dataMatrix的大小,3*100的矩陣
m,n = shape(dataMatrix)
#目標移動步長
alpha = 0.01
#迭代次數
maxCycles =500
#n已經賦值爲3
weights = ones((n,1))
for k in range(maxCycles):
h = sigmoid(dataMatrix*weights)
error = (labelMat - h)
weights = weights + alpha * dataMatrix.transpose() * error
return weights
接下來測試代碼,載入這個包
In [1]: import os
In [2]: os.chdir('E:\ML\ML_IA\Logistic')
In [3]: import logRegres
In [4]: import logRegres
In [5]: reload(logRegres)
Out[5]: <module 'logRegres' from 'logRegres.py'>
In [6]: a,b= logRegres.loadDataSet()
In [40]: logRegres.gradAscent(a,b)
Out[40]:
matrix([[ 4.12414349],
[ 0.48007329],
[-0.6168482 ]])
5.3.2 分析數據:畫出決策邊界
上面解出的迴歸係數,確定了不同類別數據之間的分割線。那麼如何畫出分割線呢?
程序5-2 畫出數據集和Logistic 迴歸最佳擬合直線的曲線
import matplotlib.pyplot as plt
def plotBestFit(weights):
dataMat,labelMat = loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcord1 = [] ; ycord1 = []
xcord2 = [] ; ycord2 = []
for i in range(n):
if int(labelMat[i]) == i:
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()
我們最終需要解出X1和X2之間的關係式,運行代碼,
In [24]: import logRegres
In [25]: reload(logRegres)
Out[25]: <module 'logRegres' from 'logRegres.py'>
In [26]: a,b = logRegres.loadDataSet()
In [27]: logRegres.gradAscent(a,b)
Out[27]:
matrix([[ 4.12414349],
[ 0.48007329],
[-0.6168482 ]])
In [28]: weights = logRegres.gradAscent(a,b)
In [29]: logRegres.plotBestFit(weights.getA())
梯度上升算法在500次迭代後得到的Logistic迴歸最佳擬合曲線
最終圖上看上去是錯分了2個點。
儘管例子比較簡單,但是卻需要大量的計算。後面我們將對算法作以改進。
5.2.4 訓練算法:隨機梯度上升
梯度上升算法在每次更新迴歸係數時都需要去遍歷整個數據集,該方法在處理100個左右的數據集時速度可以,如果去處理數億或者幾千萬的特徵,這個方法的複雜度就太高了。
我們改進的方法就是一次僅用一個樣本點來更新迴歸係數,這個方法叫做隨機梯度上升算法。
由於可以在新樣本到來時對分類器進行增量式更新,因而隨機梯度上升算法是一個在線學習算法。與“在線學習”相對應,一次處理的所有數據被稱之爲“批處理”。
隨機梯度上升算法可以寫成如下的僞代碼:
雖有的迴歸係數初始都爲1
對數據集中每個樣本
計算該樣本的梯度
使用alpha*gradient更新迴歸係數值
返回歸係數數值
程序5-3 隨機梯度上升算法
# 隨機梯度上升算法
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
隨機梯度算法與梯度上升算法有一定的區別:
1.後者的h和誤差error都是向量,而前者則是數值;
2.前者沒有矩陣的轉換過程,所有變量的數據類型都是Numpy數組。
In [38]: import logRegres
In [39]: reload(logRegres)
Out[39]: <module 'logRegres' from 'logRegres.pyc'>
In [40]: a,b = logRegres.loadDataSet()
In [41]: weights = logRegres.stocGradAscent1(array(a),b)
In [42]: weights
Out[42]: array([ 1.01702007, 0.85914348, -0.36579921])
In [43]: logRegres.plotBestFit(weights)
隨機梯度上升算法在上述數據集上執行結果,最佳擬合直線並非最佳分類線
本次運行後的曲線如果與上面的圖進行比較,其實不如上面的完美。那麼主要是因爲上面的圖迭代了500次。
因爲數據波動或者高頻波動,且收斂速度較慢,所以我們需要對算法改進。
值得注意的是:主要做了三個方面的改進:
1. alpha在每次迭代的時候都會調整,這會緩解數據波動或者高頻波動。
2. 通過隨機選取樣本來更新迴歸係數,這樣可以減少週期性波動
3. 增加了一個迭代參數作爲第三個參數,算法將按照給的新的參數值進行迭代
程序5-4 改進的隨機梯度算法
# 改進的隨機梯度算法
def stocGradAscent1(dataMatrix,classLabels,numIter=150):
m,n = shape(dataMatrix)
weights = ones(n)
"""
i 是樣本點的下標,j 是迭代次數
"""
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 4/(1.0 + j + i) + 0.01 #alpha每次迭代時需要調整
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])
return weights
下面所用的計算量更少:
In [43]: reload(logRegres)
Out[43]: <module 'logRegres' from 'logRegres.pyc'>
In [44]: a,b = logRegres.loadDataSet()
In [45]: weights = logRegres.stocGradAscent1(array(a),b)
In [46]: logRegres.plotBestFit(weights)
5.3 例子:疝氣病症預測病馬的死亡率
我們將使用logistics迴歸來預測患有疝病的馬的存活問題。該數據有部分指標主觀和難以測量,而且存在30%的缺失值。
步驟:
1.收集數據:給定數據文件;
2.準備數據:用python解析文本文件並填充缺失值;
3.分析數據:可視化並觀察數據;
4.訓練算法:使用最優化算法,得到最佳的係數;
5.測試算法:爲了量化迴歸的效果,我們需要觀察錯誤率。再去根據錯誤率判斷,我們是否需要回退到訓練階段,通過改變迭代的次數和步長等參數來得到更好的迴歸係數;
6.使用算法:實現一個簡單的命令行程序來手機馬的症狀並輸出預測結果。
5.3.1 準備數據:處理數據中的缺失數據
數據的缺失值處理是一個非常棘手的問題,有時候複雜業務環境下數據相當昂貴,我們不能簡單的去丟棄掉,或者重新獲取。
方法:
● 使用可用特徵的均值來填補缺失值 ;
● 使用特殊值來填補缺失值,如-1 ;
● 忽略有缺失值的樣本;
● 使用相似的樣本的均值添補缺失值;
● 使用另外的機器學習算法來預測缺失值;
我們現在要對要用的數據集做預處理。
1.所有的缺失值必須使用一個實數值來替換,因爲我們使用numpy數據類型不允許包含缺失值,這裏選擇實數0來替換所有的缺失值,恰好使用會迴歸。這樣做是因爲需要一個在更新的時候不會影響係數的值。
迴歸係數更新公式如下:
weights = weights + alpha error dataMatrix[randIndex]
2.如果數據集中類別標籤已經缺失,那麼就丟棄。
5.3.2 測試算法:用logistic迴歸進行分類
程序清單5-5 logistic 迴歸分類函數
# 疝氣病預測兵馬死亡率:Logistic迴歸分類器
def classifyVector(inX,weights):
"""
迴歸係數 :inX
特徵向量 :weights
"""
prob = sigmoid(sum(inX*weights))
if prob > 0.5:
return 1.0
else:
return 0.0
def colicTest():
frTrain = open(tem_dir + "\\" + "horseColicTraining.txt")
frTest = open(tem_dir + "\\" + "horseColicTest.txt")
trainingSet = [] ; trainingLabels = []
for line in frTrain.readlines():
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
trainingSet.append(lineArr)
trainingLabels.append(float(currLine[21]))
#stocGradAscent1計算迴歸係數
trainWeights = stocGradAscent1(array(trainingSet),trainingLabels,500)
errorCount = 0; numTestVec = 0.0
for line in frTest.readlines():
numTestVec += 1.0
currLine = line.strip().split('\t')
lineArr = []
for i in range(21):
lineArr.append(float(currLine[i]))
if int(classifyVector(array(lineArr),trainWeights)) != int(currLine[21]):
errorCount += 1
errorRate = (float(errorCount)/numTestVec)
print "the error rate of this test is: %f" %errorRate
return errorRate
def multiTest():
numTests = 10;errorSum = 0.0
for k in range(numTests):
errorSum += colicTest()
print "after %d iterations the average error rate is:%f"
% (numTests,errorSum/float(numTests))
測試代碼,結果如下:
In [46]: reload(logRegres)
Out[46]: <module 'logRegres' from 'logRegres.py'>
In [47]: logRegres.multiTest()
the error rate of this test is: 0.313433
the error rate of this test is: 0.313433
the error rate of this test is: 0.313433
the error rate of this test is: 0.283582
the error rate of this test is: 0.417910
the error rate of this test is: 0.328358
the error rate of this test is: 0.462687
the error rate of this test is: 0.313433
the error rate of this test is: 0.358209
the error rate of this test is: 0.388060
after 10 iterations the average error rate is:0.349254
10次迭代之後的結果是34.9% 。因爲數據本身就有30%的缺失,得到的錯誤率結果並不差。
如果手動去調整 colicTest() 函數中的迭代次數和 stocGradAscent1() 中的步長,那麼最終的平均錯誤率就在20%左右。
5.4 小結
Logistic 迴歸的目的是尋找一個非線性函數sigmoid的最佳擬合參數。求解過程可以由最優化算法來完成。在最優化的算法中,最常用的就是梯度上升算法,梯度上升算法又可以簡化爲隨機梯度上升算法。
隨機梯度上升算法和梯度上升算法的效果相當,但佔用更少的計算資源。此外,隨機梯度是一種在線算法,可以在數據到來時就完成參數的更新,而不需要重新讀取整個數據集來進行批處理運算。
機器學習的一個重要的問題就是如何處理缺失數據,這個問題沒有標準答案,取決於實際的需求。