初探logistic回歸
優(yōu)點:計算代價低,易于實現(xiàn)。
》缺點:容易欠擬合,分類精度可能不高,只能處理二分類問題且必須線性可分。
<!- more-->
1.1 何為logistic回歸
邏輯回歸的用途十分廣泛,主要用來進(jìn)行預(yù)測,也被看做是一種概率估計。這一點可以參考吳軍老師的《數(shù)學(xué)之美》——第28章 邏輯回歸和搜索廣告,還有劉鵬老師的《計算廣告》。


邏輯回歸模型是將一個事件出現(xiàn)的概率適應(yīng)到一條邏輯曲線上。邏輯曲線是一條S形的曲線,其特點是開始變化快,逐漸減慢,最后飽和。邏輯自回歸的好處是它的變量是從負(fù)無窮到正無窮,而值域范圍限制在0-1之間。我們知道對應(yīng)于[0,1]之間的函數(shù)可以是一個概率函數(shù),這樣邏輯回歸函數(shù)就可以和一個概率分別聯(lián)系起來了。
強調(diào)一下回歸的定義:
假設(shè)現(xiàn)在有一些數(shù)據(jù)點,用一條直線對這些點進(jìn)行擬合的話,這個擬合的過程就是回歸。
本部分會涉及到最優(yōu)化算法,在此做一簡單的介紹:
最優(yōu)化算法有許多種,在維基上可以列出的就有如下多達(dá)15種算法:
共軛梯度法
梯度下降法
線搜索
動態(tài)規(guī)劃
協(xié)同優(yōu)化算法
坐標(biāo)下降法
差異進(jìn)化算法
序列最小優(yōu)化算法
模擬退火法
次梯度法
牛頓法
禁忌搜索法
粒子群演算法
維特比算法
遺傳算法
其中比較常用的是梯度下降法,牛頓法,共軛梯度法,還有啟發(fā)式算法(啟發(fā)式優(yōu)化方法種類繁多,包括經(jīng)典的模擬退火方法、遺傳算法、蟻群算法以及粒子群算法等等)。
最優(yōu)化算法可謂是機器學(xué)習(xí)的基石,大部分的機器學(xué)習(xí)算法的本質(zhì)都是建立優(yōu)化模型,通過最優(yōu)化方法對目標(biāo)函數(shù)(或損失函數(shù))進(jìn)行優(yōu)化,從而訓(xùn)練出最好的模型。
本文探討的logistic回歸的主要思想就是:
根據(jù)現(xiàn)有數(shù)據(jù)對分類邊界線建立回歸公式,以此進(jìn)行數(shù)據(jù)的分類。
可以注意到,我們建立的是回歸公式。“回歸”源于最佳擬合,這意味著我們要找到最佳的參數(shù)集,這個參數(shù)集需要我們通過數(shù)據(jù)訓(xùn)練得到。
在這里,我們訓(xùn)練的方法將使用梯度上升法,還有它的改進(jìn)版本,隨機梯度上升法。
1.2 構(gòu)造分類器
為什么不使用海維賽德階躍函數(shù)(單位階躍函數(shù)):
在跳躍點上瞬間從0跳躍到1,這個過程太激凸...
我們用sigmoid函數(shù),溫和不刺激~
這樣我們就有了基本的構(gòu)件模型,下一步就是函數(shù)的輸入z
我們將特征變量乘以回歸系數(shù)再全部累加得到Z:
z=w0x0 + w1x1 + w2x2 .... + wnxn
其輸出分大于0.5和小于0.5,表示兩個類別,也就實現(xiàn)了分類,確定了分類器的函數(shù)形式,接下來問題就是求最佳回歸系數(shù)。(梯度上升法,隨機梯度上升法)
主要思想:要找到某函數(shù)的最大值,最好的辦法是沿著該函數(shù)的梯度方向探尋。
梯度是矢量,總是沿著函數(shù)值上升最快的方向移動,因此我們沿著梯度方向或者反方向行進(jìn)時,就能達(dá)到一個函數(shù)的最大值(梯度上升法)或者最小值(梯度下降法),因此梯度上升算法就是不斷更新梯度值,直到梯度不再變化或者變化很小,即函數(shù)達(dá)到了最大值
梯度算法的迭代公式為(alpha為步長,即每一步移動量):
而我們常說的梯度下降法就是將其中的加號換為減號。
梯度上升法用來尋找最大值,而梯度下降法用來尋找最小值。
from numpy import *
import matplotlib.pyplot as plt
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):
return 1.0/(1 + exp(-inX))
def gradAscent(dataMatIn, classLabels):
dataMatrix = mat(dataMatIn)
labelMat = mat(classLabels).transpose() #矩陣轉(zhuǎn)置
m,n = shape(dataMatrix)
alpha = 0.001
maxCycles = 500
weights = ones((n, 1))
for k in range(maxCycles):#注意range用法,此處是從0到maxCycles而不含maxCycles
h = sigmoid(dataMatrix*weights)
error = (labelMat - h)
weights = weights + alpha * dataMatrix.transpose()*error
return weights
def plotBestFit(weights):
dataMat, labelMat = loadDataSet()
dataArr = array(dataMat)
n = shape(dataArr)[0]
xcode1 = []; ycode1 = []
xcode2 = []; ycode2 = []
for i in range(n):
if int(labelMat[i]) == 1:
xcode1.append(dataArr[i, 1]); ycode1.append(dataArr[i, 2])
else:
xcode2.append(dataArr[i, 1]); ycode2.append(dataArr[i, 2])
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(xcode1, ycode1, s = 30, c = 'red', marker = 's')
ax.scatter(xcode2, ycode2, 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()
def main():
dataArr, labelMat =loadDataSet()
weights = gradAscent(dataArr, labelMat)
plotBestFit(weights.getA())#getA()將會返回矩陣的array版本
if __name__ == "__main__":
main()
可以看到劃分的結(jié)果:
那么,該如何提升呢?
由于計算最佳擬合曲線的系數(shù)時,梯度上升法都要遍歷整個數(shù)據(jù)集,當(dāng)數(shù)據(jù)量十分巨大時,很難想象性能是多么差,于是我們引入一種在線學(xué)習(xí)方法,說白了就是增量式的更新系數(shù)。
我們叫隨機梯度上升法。
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
結(jié)果如下圖所示:
我們會發(fā)現(xiàn),數(shù)據(jù)的分類比較糟糕,并沒有達(dá)到理想的效果,原因是它的迭代次數(shù)遠(yuǎn)沒有梯度上升法的500次多,需要對其進(jìn)一步修改。
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m,n = shape(dataMatrix)
weights = ones(n) #initialize to all ones
for j in range(numIter):
dataIndex = range(m)
for i in range(m):
alpha = 4/(1.0+j+i)+0.0001 #apha decreases with iteration, does not
randIndex = int(random.uniform(0,len(dataIndex)))#go to 0 because of the constant
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
經(jīng)過修改之后再運行可得結(jié)果如下圖所示:
最后,我們想要預(yù)測一下病馬的死亡率,根據(jù)給出的數(shù)據(jù)文件horseColicTraining.txt和horseColicTest.txt,在看過數(shù)據(jù)之后會發(fā)現(xiàn)這是一個臟數(shù)據(jù),需要對數(shù)據(jù)進(jìn)行預(yù)處理即數(shù)據(jù)清洗,將缺失數(shù)據(jù)項的數(shù)據(jù)進(jìn)行補充,缺失標(biāo)簽的進(jìn)行剔除。
def classifyVector(inX, weights):#計算sigmoid的值,輸入為特征向量和回歸系數(shù)
prob = sigmoid(sum(inX * weights))
if prob > 0.5:
return 1.0
else:
return 0.0
def colicTest():#用于打開測試集和訓(xùn)練集,并對數(shù)據(jù)進(jìn)行格式化處理
frTrain = open('horseColicTraining.txt');
frTest = open('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]))
trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 1000)
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():#調(diào)用colicTest()10次并求平均值
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))
def main():
# dataArr, labelMat =loadDataSet()
# weights = stocGradAscent1(array(dataArr), labelMat)
# plotBestFit(weights)
multiTest()
if __name__ == "__main__":
main()
邏輯回歸的目的在于尋找一開始我們找到的sigmoid階躍函數(shù)的最佳擬合系數(shù),而尋找的方法使用了最優(yōu)化方法(梯度上升法/梯度下降法)。邏輯回歸主要適用于對數(shù)據(jù)進(jìn)行分類,而后在分類的基礎(chǔ)上進(jìn)行進(jìn)一步分析。
(完)