第5章 Logistic回歸
<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=default"></script>
Logistic 回歸 概述
Logistic 回歸雖然名字叫回歸,但是它是用來(lái)做分類的增炭。其主要思想是: 根據(jù)現(xiàn)有數(shù)據(jù)對(duì)分類邊界線建立回歸公式,以此進(jìn)行分類。
須知概念
Sigmoid 函數(shù)
回歸 概念
假設(shè)現(xiàn)在有一些數(shù)據(jù)點(diǎn)部逮,我們用一條直線對(duì)這些點(diǎn)進(jìn)行擬合(這條直線稱為最佳擬合直線)院促,這個(gè)擬合的過(guò)程就叫做回歸筏养。進(jìn)而可以得到對(duì)這些點(diǎn)的擬合直線方程,那么我們根據(jù)這個(gè)回歸方程常拓,怎么進(jìn)行分類呢渐溶?請(qǐng)看下面。
二值型輸出分類函數(shù)
我們想要的函數(shù)應(yīng)該是: 能接受所有的輸入然后預(yù)測(cè)出類別弄抬。例如茎辐,在兩個(gè)類的情況下,上述函數(shù)輸出 0 或 1.或許你之前接觸過(guò)具有這種性質(zhì)的函數(shù)掂恕,該函數(shù)稱為 海維塞得階躍函數(shù)(Heaviside step function)
拖陆,或者直接稱為 單位階躍函數(shù)
。然而懊亡,海維塞得階躍函數(shù)的問(wèn)題在于: 該函數(shù)在跳躍點(diǎn)上從 0 瞬間跳躍到 1依啰,這個(gè)瞬間跳躍過(guò)程有時(shí)很難處理。幸好店枣,另一個(gè)函數(shù)也有類似的性質(zhì)(可以輸出 0 或者 1 的性質(zhì))速警,且數(shù)學(xué)上更易處理叹誉,這就是 Sigmoid 函數(shù)。 Sigmoid 函數(shù)具體的計(jì)算公式如下:
下圖給出了 Sigmoid 函數(shù)在不同坐標(biāo)尺度下的兩條曲線圖闷旧。當(dāng) x 為 0 時(shí)长豁,Sigmoid 函數(shù)值為 0.5 。隨著 x 的增大忙灼,對(duì)應(yīng)的 Sigmoid 值將逼近于 1 ; 而隨著 x 的減小匠襟, Sigmoid 值將逼近于 0 。如果橫坐標(biāo)刻度足夠大该园, Sigmoid 函數(shù)看起來(lái)很像一個(gè)階躍函數(shù)宅此。
因此,為了實(shí)現(xiàn) Logistic 回歸分類器爬范,我們可以在每個(gè)特征上都乘以一個(gè)回歸系數(shù)(如下公式所示)父腕,然后把所有結(jié)果值相加,將這個(gè)總和代入 Sigmoid 函數(shù)中青瀑,進(jìn)而得到一個(gè)范圍在 0~1 之間的數(shù)值璧亮。任何大于 0.5 的數(shù)據(jù)被分入 1 類,小于 0.5 即被歸入 0 類斥难。所以枝嘶, Logistic 回歸也可以被看成是一種概率估計(jì)。
基于最優(yōu)化方法的回歸系數(shù)確定
Sigmoid 函數(shù)的輸入記為 z 哑诊,由下面公式得到:
.png) ,它表示將這兩個(gè)數(shù)值向量對(duì)應(yīng)元素相乘然后全部加起來(lái)即得到 z 值镀裤。其中的向量 x 是分類器的輸入數(shù)據(jù)竞阐,向量 w 也就是我們要找到的最佳參數(shù)(系數(shù)),從而使得分類器盡可能地精確暑劝。為了尋找該最佳參數(shù)骆莹,需要用到最優(yōu)化理論的一些知識(shí)。我們這里使用的是——梯度上升法担猛。
梯度上升法
梯度的介紹
需要一點(diǎn)點(diǎn)向量方面的數(shù)學(xué)基礎(chǔ)
向量 = 值 + 方向
梯度 = 向量
梯度 = 梯度值 + 梯度方向
梯度上升法的思想
要找到某函數(shù)的最大值幕垦,最好的方法是沿著該函數(shù)的梯度方向探尋。如果梯度記為 ▽ 傅联,則函數(shù) f(x, y) 的梯度由下式表示:
先改,沿 y 的方向移動(dòng)
。其中蒸走,函數(shù)f(x, y) 必須要在待計(jì)算的點(diǎn)上有定義并且可微仇奶。下圖是一個(gè)具體的例子。
上圖展示的载碌,梯度上升算法到達(dá)每個(gè)點(diǎn)后都會(huì)重新估計(jì)移動(dòng)的方向猜嘱。從 P0 開(kāi)始,計(jì)算完該點(diǎn)的梯度嫁艇,函數(shù)就根據(jù)梯度移動(dòng)到下一點(diǎn) P1朗伶。在 P1 點(diǎn),梯度再次被重新計(jì)算步咪,并沿著新的梯度方向移動(dòng)到 P2 论皆。如此循環(huán)迭代,直到滿足停止條件猾漫。迭代過(guò)程中点晴,梯度算子總是保證我們能選取到最佳的移動(dòng)方向。
上圖中的梯度上升算法沿梯度方向移動(dòng)了一步悯周×6剑可以看到,梯度算子總是指向函數(shù)值增長(zhǎng)最快的方向禽翼。這里所說(shuō)的是移動(dòng)方向屠橄,而未提到移動(dòng)量的大小。該量值稱為步長(zhǎng)闰挡,記作 α 锐墙。用向量來(lái)表示的話,梯度上升算法的迭代公式如下:
該公式將一直被迭代執(zhí)行长酗,直至達(dá)到某個(gè)停止條件為止溪北,比如迭代次數(shù)達(dá)到某個(gè)指定值或者算法達(dá)到某個(gè)可以允許的誤差范圍。
介紹一下幾個(gè)相關(guān)的概念:
例如:y = w1x1 + w2x2 + ... + wnxn
梯度:參考上圖的例子夺脾,二維圖像之拨,x方向代表第一個(gè)系數(shù),也就是 w1咧叭,y方向代表第二個(gè)系數(shù)也就是 w2敦锌,這樣的向量就是梯度。
α:上面的梯度算法的迭代公式中的阿爾法佳簸,這個(gè)代表的是移動(dòng)步長(zhǎng)乙墙。移動(dòng)步長(zhǎng)會(huì)影響最終結(jié)果的擬合程度,最好的方法就是隨著迭代次數(shù)更改移動(dòng)步長(zhǎng)生均。
步長(zhǎng)通俗的理解听想,100米,如果我一步走10米马胧,我需要走10步汉买;如果一步走20米,我只需要走5步佩脊。這里的一步走多少米就是步長(zhǎng)的意思蛙粘。
▽f(w):代表沿著梯度變化的方向垫卤。
Note: 我們常聽(tīng)到的是梯度下降算法,它與這里的梯度上升算法是一樣的出牧,只是公式中的加法需要變成減法穴肘。因此,對(duì)應(yīng)的公式可以寫成
梯度上升算法用來(lái)求函數(shù)的最大值舔痕,而梯度下降算法用來(lái)求函數(shù)的最小值评抚。
局部最優(yōu)現(xiàn)象
上圖表示參數(shù) θ 與誤差函數(shù) J(θ) 的關(guān)系圖,紅色的部分是表示 J(θ) 有著比較高的取值伯复,我們需要的是慨代,能夠讓 J(θ) 的值盡量的低。也就是深藍(lán)色的部分啸如。θ0侍匙,θ1 表示 θ 向量的兩個(gè)維度。
可能梯度下降的最終點(diǎn)并非是全局最小點(diǎn)叮雳,可能是一個(gè)局部最小點(diǎn)丈积,如我們上圖中的右邊的梯度下降曲線,描述的是最終到達(dá)一個(gè)局部最小點(diǎn)债鸡,這是我們重新選擇了一個(gè)初始點(diǎn)得到的江滨。
看來(lái)我們這個(gè)算法將會(huì)在很大的程度上被初始點(diǎn)的選擇影響而陷入局部最小點(diǎn)。
Logistic 回歸 原理
Logistic 回歸 工作原理
每個(gè)回歸系數(shù)初始化為 1
重復(fù) R 次:
計(jì)算整個(gè)數(shù)據(jù)集的梯度
使用 步長(zhǎng) x 梯度 更新回歸系數(shù)的向量
返回回歸系數(shù)
Logistic 回歸 開(kāi)發(fā)流程
收集數(shù)據(jù): 采用任意方法收集數(shù)據(jù)
準(zhǔn)備數(shù)據(jù): 由于需要進(jìn)行距離計(jì)算厌均,因此要求數(shù)據(jù)類型為數(shù)值型唬滑。另外,結(jié)構(gòu)化數(shù)據(jù)格式則最佳棺弊。
分析數(shù)據(jù): 采用任意方法對(duì)數(shù)據(jù)進(jìn)行分析晶密。
訓(xùn)練算法: 大部分時(shí)間將用于訓(xùn)練,訓(xùn)練的目的是為了找到最佳的分類回歸系數(shù)模她。
測(cè)試算法: 一旦訓(xùn)練步驟完成稻艰,分類將會(huì)很快。
使用算法: 首先侈净,我們需要輸入一些數(shù)據(jù)尊勿,并將其轉(zhuǎn)換成對(duì)應(yīng)的結(jié)構(gòu)化數(shù)值;接著畜侦,基于訓(xùn)練好的回歸系數(shù)就可以對(duì)這些數(shù)值進(jìn)行簡(jiǎn)單的回歸計(jì)算元扔,判定它們屬于哪個(gè)類別;在這之后旋膳,我們就可以在輸出的類別上做一些其他分析工作澎语。
Logistic 回歸 算法特點(diǎn)
優(yōu)點(diǎn): 計(jì)算代價(jià)不高,易于理解和實(shí)現(xiàn)。
缺點(diǎn): 容易欠擬合擅羞,分類精度可能不高尸变。
適用數(shù)據(jù)類型: 數(shù)值型和標(biāo)稱型數(shù)據(jù)。
附加 方向?qū)?shù)與梯度
Logistic 回歸 項(xiàng)目案例
項(xiàng)目案例1: 使用 Logistic 回歸在簡(jiǎn)單數(shù)據(jù)集上的分類
項(xiàng)目概述
在一個(gè)簡(jiǎn)單的數(shù)據(jù)集上减俏,采用梯度上升法找到 Logistic 回歸分類器在此數(shù)據(jù)集上的最佳回歸系數(shù)
開(kāi)發(fā)流程
收集數(shù)據(jù): 可以使用任何方法
準(zhǔn)備數(shù)據(jù): 由于需要進(jìn)行距離計(jì)算召烂,因此要求數(shù)據(jù)類型為數(shù)值型。另外垄懂,結(jié)構(gòu)化數(shù)據(jù)格式則最佳
分析數(shù)據(jù): 畫出決策邊界
訓(xùn)練算法: 使用梯度上升找到最佳參數(shù)
測(cè)試算法: 使用 Logistic 回歸進(jìn)行分類
使用算法: 對(duì)簡(jiǎn)單數(shù)據(jù)集中數(shù)據(jù)進(jìn)行分類
收集數(shù)據(jù): 可以使用任何方法
我們采用存儲(chǔ)在 TestSet.txt 文本文件中的數(shù)據(jù),存儲(chǔ)格式如下:
-0.017612 14.053064 0
-1.395634 4.662541 1
-0.752157 6.538620 0
-1.322371 7.152853 0
0.423363 11.054677 0
繪制在圖中痛垛,如下圖所示:
準(zhǔn)備數(shù)據(jù): 由于需要進(jìn)行距離計(jì)算草慧,因此要求數(shù)據(jù)類型為數(shù)值型。另外匙头,結(jié)構(gòu)化數(shù)據(jù)格式則最佳
分析數(shù)據(jù): 畫出決策邊界
畫出數(shù)據(jù)集和 Logistic 回歸最佳擬合直線的函數(shù)
def plotBestFit(dataArr, labelMat, weights):
'''
Desc:
將我們得到的數(shù)據(jù)可視化展示出來(lái)
Args:
dataArr:樣本數(shù)據(jù)的特征
labelMat:樣本數(shù)據(jù)的類別標(biāo)簽漫谷,即目標(biāo)變量
weights:回歸系數(shù)
Returns:
None
'''
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的由來(lái),臥槽蹂析,是不是沒(méi)看懂舔示?
首先理論上是這個(gè)樣子的。
dataMat.append([1.0, float(lineArr[0]), float(lineArr[1])])
w0*x0+w1*x1+w2*x2=f(x)
x0最開(kāi)始就設(shè)置為1叻电抚, x2就是我們畫圖的y值惕稻,而f(x)被我們磨合誤差給算到w0,w1,w2身上去了
所以: w0+w1*x+w2*y=0 => y = (-w0-w1*x)/w2
"""
y = (-weights[0]-weights[1]*x)/weights[2]
ax.plot(x, y)
plt.xlabel('X'); plt.ylabel('Y')
plt.show()
訓(xùn)練算法: 使用梯度上升找到最佳參數(shù)
Logistic 回歸梯度上升優(yōu)化算法
# 正常的處理方案
# 兩個(gè)參數(shù):第一個(gè)參數(shù)==> dataMatIn 是一個(gè)2維NumPy數(shù)組,每列分別代表每個(gè)不同的特征蝙叛,每行則代表每個(gè)訓(xùn)練樣本俺祠。
# 第二個(gè)參數(shù)==> classLabels 是類別標(biāo)簽,它是一個(gè) 1*100 的行向量借帘。為了便于矩陣計(jì)算蜘渣,需要將該行向量轉(zhuǎn)換為列向量,做法是將原向量轉(zhuǎn)置肺然,再將它賦值給labelMat蔫缸。
def gradAscent(dataMatIn, classLabels):
# 轉(zhuǎn)化為矩陣[[1,1,2],[1,1,2]....]
dataMatrix = mat(dataMatIn) # 轉(zhuǎn)換為 NumPy 矩陣
# 轉(zhuǎn)化為矩陣[[0,1,0,1,0,1.....]],并轉(zhuǎn)制[[0],[1],[0].....]
# transpose() 行列轉(zhuǎn)置函數(shù)
# 將行向量轉(zhuǎn)化為列向量 => 矩陣的轉(zhuǎn)置
labelMat = mat(classLabels).transpose() # 首先將數(shù)組轉(zhuǎn)換為 NumPy 矩陣际起,然后再將行向量轉(zhuǎn)置為列向量
# m->數(shù)據(jù)量拾碌,樣本數(shù) n->特征數(shù)
m,n = shape(dataMatrix)
# print m, n, '__'*10, shape(dataMatrix.transpose()), '__'*100
# alpha代表向目標(biāo)移動(dòng)的步長(zhǎng)
alpha = 0.001
# 迭代次數(shù)
maxCycles = 500
# 生成一個(gè)長(zhǎng)度和特征數(shù)相同的矩陣,此處n為3 -> [[1],[1],[1]]
# weights 代表回歸系數(shù)街望, 此處的 ones((n,1)) 創(chuàng)建一個(gè)長(zhǎng)度和特征數(shù)相同的矩陣倦沧,其中的數(shù)全部都是 1
weights = ones((n,1))
for k in range(maxCycles): #heavy on matrix operations
# m*3 的矩陣 * 3*1 的單位矩陣 = m*1的矩陣
# 那么乘上單位矩陣的意義,就代表:通過(guò)公式得到的理論值
# 參考地址: 矩陣乘法的本質(zhì)是什么它匕? https://www.zhihu.com/question/21351965/answer/31050145
# print 'dataMatrix====', dataMatrix
# print 'weights====', weights
# n*3 * 3*1 = n*1
h = sigmoid(dataMatrix*weights) # 矩陣乘法
# print 'hhhhhhh====', h
# labelMat是實(shí)際值
error = (labelMat - h) # 向量相減
# 0.001* (3*m)*(m*1) 表示在每一個(gè)列上的一個(gè)誤差情況展融,最后得出 x1,x2,xn的系數(shù)的偏移量
weights = weights + alpha * dataMatrix.transpose() * error # 矩陣乘法,最后得到回歸系數(shù)
return array(weights)
大家看到這兒可能會(huì)有一些疑惑,就是告希,我們?cè)诘懈挛覀兊幕貧w系數(shù)扑浸,后邊的部分是怎么計(jì)算出來(lái)的?為什么會(huì)是 alpha * dataMatrix.transpose() * error ?因?yàn)檫@就是我們所求的梯度燕偶,也就是對(duì) f(w) 對(duì) w 求一階導(dǎo)數(shù)喝噪。具體推導(dǎo)如下:
測(cè)試算法: 使用 Logistic 回歸進(jìn)行分類
def testLR():
# 1.收集并準(zhǔn)備數(shù)據(jù)
dataMat, labelMat = loadDataSet("input/5.Logistic/TestSet.txt")
# print dataMat, '---\n', labelMat
# 2.訓(xùn)練模型, f(x)=a1*x1+b2*x2+..+nn*xn中 (a1,b2, .., nn).T的矩陣值
# 因?yàn)閿?shù)組沒(méi)有是復(fù)制n份指么, array的乘法就是乘法
dataArr = array(dataMat)
# print dataArr
weights = gradAscent(dataArr, labelMat)
# weights = stocGradAscent0(dataArr, labelMat)
# weights = stocGradAscent1(dataArr, labelMat)
# print '*'*30, weights
# 數(shù)據(jù)可視化
plotBestFit(dataArr, labelMat, weights)
使用算法: 對(duì)簡(jiǎn)單數(shù)據(jù)集中數(shù)據(jù)進(jìn)行分類
注意
梯度上升算法在每次更新回歸系數(shù)時(shí)都需要遍歷整個(gè)數(shù)據(jù)集酝惧,該方法在處理 100 個(gè)左右的數(shù)據(jù)集時(shí)尚可,但如果有數(shù)十億樣本和成千上萬(wàn)的特征伯诬,那么該方法的計(jì)算復(fù)雜度就太高了晚唇。一種改進(jìn)方法是一次僅用一個(gè)樣本點(diǎn)來(lái)更新回歸系數(shù),該方法稱為 隨機(jī)梯度上升算法
盗似。由于可以在新樣本到來(lái)時(shí)對(duì)分類器進(jìn)行增量式更新哩陕,因而隨機(jī)梯度上升算法是一個(gè)在線學(xué)習(xí)算法。與 “在線學(xué)習(xí)” 相對(duì)應(yīng)赫舒,一次處理所有數(shù)據(jù)被稱作是 “批處理”悍及。
隨機(jī)梯度上升算法可以寫成如下的偽代碼:
所有回歸系數(shù)初始化為 1
對(duì)數(shù)據(jù)集中每個(gè)樣本
計(jì)算該樣本的梯度
使用 alpha x gradient 更新回歸系數(shù)值
返回回歸系數(shù)值
以下是隨機(jī)梯度上升算法的實(shí)現(xiàn)代碼:
# 隨機(jī)梯度上升
# 梯度上升優(yōu)化算法在每次更新數(shù)據(jù)集時(shí)都需要遍歷整個(gè)數(shù)據(jù)集,計(jì)算復(fù)雜都較高
# 隨機(jī)梯度上升一次只用一個(gè)樣本點(diǎn)來(lái)更新回歸系數(shù)
def stocGradAscent0(dataMatrix, classLabels):
m,n = shape(dataMatrix)
alpha = 0.01
# n*1的矩陣
# 函數(shù)ones創(chuàng)建一個(gè)全1的數(shù)組
weights = ones(n) # 初始化長(zhǎng)度為n的數(shù)組接癌,元素全部為 1
for i in range(m):
# sum(dataMatrix[i]*weights)為了求 f(x)的值心赶, f(x)=a1*x1+b2*x2+..+nn*xn,此處求出的 h 是一個(gè)具體的數(shù)值,而不是一個(gè)矩陣
h = sigmoid(sum(dataMatrix[i]*weights))
# print 'dataMatrix[i]===', dataMatrix[i]
# 計(jì)算真實(shí)類別與預(yù)測(cè)類別之間的差值缺猛,然后按照該差值調(diào)整回歸系數(shù)
error = classLabels[i] - h
# 0.01*(1*1)*(1*n)
print weights, "*"*10 , dataMatrix[i], "*"*10 , error
weights = weights + alpha * error * dataMatrix[i]
return weights
可以看到园担,隨機(jī)梯度上升算法與梯度上升算法在代碼上很相似,但也有一些區(qū)別: 第一枯夜,后者的變量 h 和誤差 error 都是向量弯汰,而前者則全是數(shù)值;第二湖雹,前者沒(méi)有矩陣的轉(zhuǎn)換過(guò)程咏闪,所有變量的數(shù)據(jù)類型都是 NumPy 數(shù)組。
判斷優(yōu)化算法優(yōu)劣的可靠方法是看它是否收斂摔吏,也就是說(shuō)參數(shù)是否達(dá)到了穩(wěn)定值鸽嫂,是否還會(huì)不斷地變化?下圖展示了隨機(jī)梯度上升算法在 200 次迭代過(guò)程中回歸系數(shù)的變化情況征讲。其中的系數(shù)2据某,也就是 X2 只經(jīng)過(guò)了 50 次迭代就達(dá)到了穩(wěn)定值,但系數(shù) 1 和 0 則需要更多次的迭代诗箍。如下圖所示:
針對(duì)這個(gè)問(wèn)題癣籽,我們改進(jìn)了之前的隨機(jī)梯度上升算法,如下:
# 隨機(jī)梯度上升算法(隨機(jī)化)
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m,n = shape(dataMatrix)
weights = ones(n) # 創(chuàng)建與列數(shù)相同的矩陣的系數(shù)矩陣,所有的元素都是1
# 隨機(jī)梯度, 循環(huán)150,觀察是否收斂
for j in range(numIter):
# [0, 1, 2 .. m-1]
dataIndex = range(m)
for i in range(m):
# i和j的不斷增大筷狼,導(dǎo)致alpha的值不斷減少瓶籽,但是不為0
alpha = 4/(1.0+j+i)+0.0001 # alpha 會(huì)隨著迭代不斷減小,但永遠(yuǎn)不會(huì)減小到0埂材,因?yàn)楹筮呥€有一個(gè)常數(shù)項(xiàng)0.0001
# 隨機(jī)產(chǎn)生一個(gè) 0~len()之間的一個(gè)值
# random.uniform(x, y) 方法將隨機(jī)生成下一個(gè)實(shí)數(shù)塑顺,它在[x,y]范圍內(nèi),x是這個(gè)范圍內(nèi)的最小值,y是這個(gè)范圍內(nèi)的最大值俏险。
randIndex = int(random.uniform(0,len(dataIndex)))
# sum(dataMatrix[i]*weights)為了求 f(x)的值严拒, f(x)=a1*x1+b2*x2+..+nn*xn
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
# print weights, '__h=%s' % h, '__'*20, alpha, '__'*20, error, '__'*20, dataMatrix[randIndex]
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
上面的改進(jìn)版隨機(jī)梯度上升算法,我們修改了兩處代碼竖独。
第一處改進(jìn)為 alpha 的值裤唠。alpha 在每次迭代的時(shí)候都會(huì)調(diào)整,這回緩解上面波動(dòng)圖的數(shù)據(jù)波動(dòng)或者高頻波動(dòng)预鬓。另外巧骚,雖然 alpha 會(huì)隨著迭代次數(shù)不斷減少赊颠,但永遠(yuǎn)不會(huì)減小到 0格二,因?yàn)槲覀冊(cè)谟?jì)算公式中添加了一個(gè)常數(shù)項(xiàng)。
第二處修改為 randIndex 更新竣蹦,這里通過(guò)隨機(jī)選取樣本拉來(lái)更新回歸系數(shù)顶猜。這種方法將減少周期性的波動(dòng)。這種方法每次隨機(jī)從列表中選出一個(gè)值痘括,然后從列表中刪掉該值(再進(jìn)行下一次迭代)长窄。
程序運(yùn)行之后能看到類似于下圖的結(jié)果圖。
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/5.Logistic/logistic.py
項(xiàng)目案例2: 從疝氣病癥預(yù)測(cè)病馬的死亡率
項(xiàng)目概述
使用 Logistic 回歸來(lái)預(yù)測(cè)患有疝病的馬的存活問(wèn)題纲菌。疝病是描述馬胃腸痛的術(shù)語(yǔ)挠日。然而,這種病不一定源自馬的胃腸問(wèn)題翰舌,其他問(wèn)題也可能引發(fā)馬疝病嚣潜。這個(gè)數(shù)據(jù)集中包含了醫(yī)院檢測(cè)馬疝病的一些指標(biāo),有的指標(biāo)比較主觀椅贱,有的指標(biāo)難以測(cè)量懂算,例如馬的疼痛級(jí)別。
開(kāi)發(fā)流程
收集數(shù)據(jù): 給定數(shù)據(jù)文件
準(zhǔn)備數(shù)據(jù): 用 Python 解析文本文件并填充缺失值
分析數(shù)據(jù): 可視化并觀察數(shù)據(jù)
訓(xùn)練算法: 使用優(yōu)化算法庇麦,找到最佳的系數(shù)
測(cè)試算法: 為了量化回歸的效果计技,需要觀察錯(cuò)誤率。根據(jù)錯(cuò)誤率決定是否回退到訓(xùn)練階段山橄,
通過(guò)改變迭代的次數(shù)和步長(zhǎng)的參數(shù)來(lái)得到更好的回歸系數(shù)
使用算法: 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的命令行程序來(lái)手機(jī)馬的癥狀并輸出預(yù)測(cè)結(jié)果并非難事垮媒,
這可以作為留給大家的一道習(xí)題
收集數(shù)據(jù): 給定數(shù)據(jù)文件
病馬的訓(xùn)練數(shù)據(jù)已經(jīng)給出來(lái)了,如下形式存儲(chǔ)在文本文件中:
1.000000 1.000000 39.200000 88.000000 20.000000 0.000000 0.000000 4.000000 1.000000 3.000000 4.000000 2.000000 0.000000 0.000000 0.000000 4.000000 2.000000 50.000000 85.000000 2.000000 2.000000 0.000000
2.000000 1.000000 38.300000 40.000000 24.000000 1.000000 1.000000 3.000000 1.000000 3.000000 3.000000 1.000000 0.000000 0.000000 0.000000 1.000000 1.000000 33.000000 6.700000 0.000000 0.000000 1.000000
準(zhǔn)備數(shù)據(jù): 用 Python 解析文本文件并填充缺失值
處理數(shù)據(jù)中的缺失值
假設(shè)有100個(gè)樣本和20個(gè)特征,這些數(shù)據(jù)都是機(jī)器收集回來(lái)的涣澡。若機(jī)器上的某個(gè)傳感器損壞導(dǎo)致一個(gè)特征無(wú)效時(shí)該怎么辦贱呐?此時(shí)是否要扔掉整個(gè)數(shù)據(jù)?這種情況下入桂,另外19個(gè)特征怎么辦奄薇?
它們是否還可以用?答案是肯定的抗愁。因?yàn)橛袝r(shí)候數(shù)據(jù)相當(dāng)昂貴馁蒂,扔掉和重新獲取都是不可取的,所以必須采用一些方法來(lái)解決這個(gè)問(wèn)題蜘腌。
下面給出了一些可選的做法:
- 使用可用特征的均值來(lái)填補(bǔ)缺失值沫屡;
- 使用特殊值來(lái)填補(bǔ)缺失值,如 -1撮珠;
- 忽略有缺失值的樣本沮脖;
- 使用有相似樣本的均值添補(bǔ)缺失值;
- 使用另外的機(jī)器學(xué)習(xí)算法預(yù)測(cè)缺失值芯急。
現(xiàn)在勺届,我們對(duì)下一節(jié)要用的數(shù)據(jù)集進(jìn)行預(yù)處理,使其可以順利地使用分類算法娶耍。在預(yù)處理需要做兩件事:
-
所有的缺失值必須用一個(gè)實(shí)數(shù)值來(lái)替換免姿,因?yàn)槲覀兪褂玫?NumPy 數(shù)據(jù)類型不允許包含缺失值。我們這里選擇實(shí)數(shù) 0 來(lái)替換所有缺失值榕酒,恰好能適用于 Logistic 回歸胚膊。這樣做的直覺(jué)在于,我們需要的是一個(gè)在更新時(shí)不會(huì)影響系數(shù)的值想鹰∥赏瘢回歸系數(shù)的更新公式如下:
weights = weights + alpha * error * dataMatrix[randIndex]
如果 dataMatrix 的某個(gè)特征對(duì)應(yīng)值為 0,那么該特征的系數(shù)將不做更新辑舷,即:
weights = weights
另外喻犁,由于 Sigmoid(0) = 0.5 ,即它對(duì)結(jié)果的預(yù)測(cè)不具有任何傾向性惩妇,因此我們上述做法也不會(huì)對(duì)誤差造成任何影響株汉。基于上述原因歌殃,將缺失值用 0 代替既可以保留現(xiàn)有數(shù)據(jù)乔妈,也不需要對(duì)優(yōu)化算法進(jìn)行修改。此外氓皱,該數(shù)據(jù)集中的特征取值一般不為 0路召,因此在某種意義上說(shuō)它也滿足 “特殊值” 這個(gè)要求勃刨。
如果在測(cè)試數(shù)據(jù)集中發(fā)現(xiàn)了一條數(shù)據(jù)的類別標(biāo)簽已經(jīng)缺失,那么我們的簡(jiǎn)單做法是將該條數(shù)據(jù)丟棄股淡。這是因?yàn)轭悇e標(biāo)簽與特征不同身隐,很難確定采用某個(gè)合適的值來(lái)替換。采用 Logistic 回歸進(jìn)行分類時(shí)這種做法是合理的唯灵,而如果采用類似 kNN 的方法就可能不太可行贾铝。
原始的數(shù)據(jù)集經(jīng)過(guò)預(yù)處理后,保存成兩個(gè)文件: horseColicTest.txt 和 horseColicTraining.txt 埠帕。
分析數(shù)據(jù): 可視化并觀察數(shù)據(jù)
將數(shù)據(jù)使用 MatPlotlib 打印出來(lái)垢揩,觀察數(shù)據(jù)是否是我們想要的格式
訓(xùn)練算法: 使用優(yōu)化算法,找到最佳的系數(shù)
下面給出 原始的梯度上升算法敛瓷,隨機(jī)梯度上升算法叁巨,改進(jìn)版隨機(jī)梯度上升算法 的代碼:
# 正常的處理方案
# 兩個(gè)參數(shù):第一個(gè)參數(shù)==> dataMatIn 是一個(gè)2維NumPy數(shù)組,每列分別代表每個(gè)不同的特征呐籽,每行則代表每個(gè)訓(xùn)練樣本锋勺。
# 第二個(gè)參數(shù)==> classLabels 是類別標(biāo)簽,它是一個(gè) 1*100 的行向量狡蝶。為了便于矩陣計(jì)算庶橱,需要將該行向量轉(zhuǎn)換為列向量,做法是將原向量轉(zhuǎn)置牢酵,再將它賦值給labelMat悬包。
def gradAscent(dataMatIn, classLabels):
# 轉(zhuǎn)化為矩陣[[1,1,2],[1,1,2]....]
dataMatrix = mat(dataMatIn) # 轉(zhuǎn)換為 NumPy 矩陣
# 轉(zhuǎn)化為矩陣[[0,1,0,1,0,1.....]]衙猪,并轉(zhuǎn)制[[0],[1],[0].....]
# transpose() 行列轉(zhuǎn)置函數(shù)
# 將行向量轉(zhuǎn)化為列向量 => 矩陣的轉(zhuǎn)置
labelMat = mat(classLabels).transpose() # 首先將數(shù)組轉(zhuǎn)換為 NumPy 矩陣馍乙,然后再將行向量轉(zhuǎn)置為列向量
# m->數(shù)據(jù)量,樣本數(shù) n->特征數(shù)
m,n = shape(dataMatrix)
# print m, n, '__'*10, shape(dataMatrix.transpose()), '__'*100
# alpha代表向目標(biāo)移動(dòng)的步長(zhǎng)
alpha = 0.001
# 迭代次數(shù)
maxCycles = 500
# 生成一個(gè)長(zhǎng)度和特征數(shù)相同的矩陣垫释,此處n為3 -> [[1],[1],[1]]
# weights 代表回歸系數(shù)丝格, 此處的 ones((n,1)) 創(chuàng)建一個(gè)長(zhǎng)度和特征數(shù)相同的矩陣,其中的數(shù)全部都是 1
weights = ones((n,1))
for k in range(maxCycles): #heavy on matrix operations
# m*3 的矩陣 * 3*1 的單位矩陣 = m*1的矩陣
# 那么乘上單位矩陣的意義棵譬,就代表:通過(guò)公式得到的理論值
# 參考地址: 矩陣乘法的本質(zhì)是什么显蝌? https://www.zhihu.com/question/21351965/answer/31050145
# print 'dataMatrix====', dataMatrix
# print 'weights====', weights
# n*3 * 3*1 = n*1
h = sigmoid(dataMatrix*weights) # 矩陣乘法
# print 'hhhhhhh====', h
# labelMat是實(shí)際值
error = (labelMat - h) # 向量相減
# 0.001* (3*m)*(m*1) 表示在每一個(gè)列上的一個(gè)誤差情況,最后得出 x1,x2,xn的系數(shù)的偏移量
weights = weights + alpha * dataMatrix.transpose() * error # 矩陣乘法订咸,最后得到回歸系數(shù)
return array(weights)
# 隨機(jī)梯度上升
# 梯度上升優(yōu)化算法在每次更新數(shù)據(jù)集時(shí)都需要遍歷整個(gè)數(shù)據(jù)集曼尊,計(jì)算復(fù)雜都較高
# 隨機(jī)梯度上升一次只用一個(gè)樣本點(diǎn)來(lái)更新回歸系數(shù)
def stocGradAscent0(dataMatrix, classLabels):
m,n = shape(dataMatrix)
alpha = 0.01
# n*1的矩陣
# 函數(shù)ones創(chuàng)建一個(gè)全1的數(shù)組
weights = ones(n) # 初始化長(zhǎng)度為n的數(shù)組,元素全部為 1
for i in range(m):
# sum(dataMatrix[i]*weights)為了求 f(x)的值脏嚷, f(x)=a1*x1+b2*x2+..+nn*xn,此處求出的 h 是一個(gè)具體的數(shù)值骆撇,而不是一個(gè)矩陣
h = sigmoid(sum(dataMatrix[i]*weights))
# print 'dataMatrix[i]===', dataMatrix[i]
# 計(jì)算真實(shí)類別與預(yù)測(cè)類別之間的差值,然后按照該差值調(diào)整回歸系數(shù)
error = classLabels[i] - h
# 0.01*(1*1)*(1*n)
print weights, "*"*10 , dataMatrix[i], "*"*10 , error
weights = weights + alpha * error * dataMatrix[i]
return weights
# 隨機(jī)梯度上升算法(隨機(jī)化)
def stocGradAscent1(dataMatrix, classLabels, numIter=150):
m,n = shape(dataMatrix)
weights = ones(n) # 創(chuàng)建與列數(shù)相同的矩陣的系數(shù)矩陣父叙,所有的元素都是1
# 隨機(jī)梯度, 循環(huán)150,觀察是否收斂
for j in range(numIter):
# [0, 1, 2 .. m-1]
dataIndex = range(m)
for i in range(m):
# i和j的不斷增大神郊,導(dǎo)致alpha的值不斷減少肴裙,但是不為0
alpha = 4/(1.0+j+i)+0.0001 # alpha 會(huì)隨著迭代不斷減小,但永遠(yuǎn)不會(huì)減小到0涌乳,因?yàn)楹筮呥€有一個(gè)常數(shù)項(xiàng)0.0001
# 隨機(jī)產(chǎn)生一個(gè) 0~len()之間的一個(gè)值
# random.uniform(x, y) 方法將隨機(jī)生成下一個(gè)實(shí)數(shù),它在[x,y]范圍內(nèi),x是這個(gè)范圍內(nèi)的最小值,y是這個(gè)范圍內(nèi)的最大值逸吵。
randIndex = int(random.uniform(0,len(dataIndex)))
# sum(dataMatrix[i]*weights)為了求 f(x)的值磷账, f(x)=a1*x1+b2*x2+..+nn*xn
h = sigmoid(sum(dataMatrix[randIndex]*weights))
error = classLabels[randIndex] - h
# print weights, '__h=%s' % h, '__'*20, alpha, '__'*20, error, '__'*20, dataMatrix[randIndex]
weights = weights + alpha * error * dataMatrix[randIndex]
del(dataIndex[randIndex])
return weights
測(cè)試算法: 為了量化回歸的效果,需要觀察錯(cuò)誤率蒸辆。根據(jù)錯(cuò)誤率決定是否回退到訓(xùn)練階段烤惊,通過(guò)改變迭代的次數(shù)和步長(zhǎng)的參數(shù)來(lái)得到更好的回歸系數(shù)
Logistic 回歸分類函數(shù)
# 分類函數(shù),根據(jù)回歸系數(shù)和特征向量來(lái)計(jì)算 Sigmoid的值
def classifyVector(inX, weights):
'''
Desc:
最終的分類函數(shù)吁朦,根據(jù)回歸系數(shù)和特征向量來(lái)計(jì)算 Sigmoid 的值柒室,大于0.5函數(shù)返回1,否則返回0
Args:
inX -- 特征向量逗宜,features
weights -- 根據(jù)梯度下降/隨機(jī)梯度下降 計(jì)算得到的回歸系數(shù)
Returns:
如果 prob 計(jì)算大于 0.5 函數(shù)返回 1
否則返回 0
'''
prob = sigmoid(sum(inX*weights))
if prob > 0.5: return 1.0
else: return 0.0
# 打開(kāi)測(cè)試集和訓(xùn)練集,并對(duì)數(shù)據(jù)進(jìn)行格式化處理
def colicTest():
'''
Desc:
打開(kāi)測(cè)試集和訓(xùn)練集雄右,并對(duì)數(shù)據(jù)進(jìn)行格式化處理
Args:
None
Returns:
errorRate -- 分類錯(cuò)誤率
'''
frTrain = open('input/5.Logistic/horseColicTraining.txt')
frTest = open('input/5.Logistic/horseColicTest.txt')
trainingSet = []
trainingLabels = []
# 解析訓(xùn)練數(shù)據(jù)集中的數(shù)據(jù)特征和Labels
# trainingSet 中存儲(chǔ)訓(xùn)練數(shù)據(jù)集的特征,trainingLabels 存儲(chǔ)訓(xùn)練數(shù)據(jù)集的樣本對(duì)應(yīng)的分類標(biāo)簽
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]))
# 使用 改進(jìn)后的 隨機(jī)梯度下降算法 求得在此數(shù)據(jù)集上的最佳回歸系數(shù) trainWeights
trainWeights = stocGradAscent1(array(trainingSet), trainingLabels, 500)
errorCount = 0
numTestVec = 0.0
# 讀取 測(cè)試數(shù)據(jù)集 進(jìn)行測(cè)試纺讲,計(jì)算分類錯(cuò)誤的樣本條數(shù)和最終的錯(cuò)誤率
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
# 調(diào)用 colicTest() 10次并求結(jié)果的平均值
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))
使用算法: 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的命令行程序來(lái)手機(jī)馬的癥狀并輸出預(yù)測(cè)結(jié)果并非難事擂仍,這可以作為留給大家的一道習(xí)題
完整代碼地址: https://github.com/apachecn/MachineLearning/blob/master/src/python/5.Logistic/logistic.py
- 作者:羊三 小瑤
- GitHub地址: https://github.com/apachecn/MachineLearning
- 版權(quán)聲明:歡迎轉(zhuǎn)載學(xué)習(xí) => 請(qǐng)標(biāo)注信息來(lái)源于 ApacheCN