本文是作者學(xué)習(xí)機(jī)器學(xué)習(xí)KNN的學(xué)習(xí)記錄與筆記,文章會(huì)從簡(jiǎn)介、kd樹算法、代碼實(shí)現(xiàn)等幾個(gè)方面展開。本文的目的是為了記錄作者的學(xué)習(xí)歷程以及進(jìn)行總結(jié),為之后的復(fù)習(xí)做準(zhǔn)備。當(dāng)然,如果同時(shí)能夠幫組到同樣在入門機(jī)器學(xué)習(xí)的小伙伴當(dāng)然就更好了。
一、簡(jiǎn)介
k近鄰法是一種基本分類與回歸方法,這篇文章只討論分類問題中的k近鄰法。k近鄰法的輸?為實(shí)例的特征向量,對(duì)應(yīng)于特征空間的點(diǎn);輸出為實(shí)例的類別,可以取多類。k近鄰法假設(shè)給定?個(gè)訓(xùn)練數(shù)據(jù)集,其中的實(shí)例類別已定。分類時(shí),對(duì)新的實(shí)例,根據(jù)其k個(gè)最近鄰的訓(xùn)練實(shí) 例的類別,通過多數(shù)表決等?式進(jìn)?預(yù)測(cè)。因此,k近鄰法不具有顯式的學(xué)習(xí)過程。k近鄰法實(shí)際上利? 訓(xùn)練數(shù)據(jù)集對(duì)特征向量空間進(jìn)?劃分,并作為其分類的“模型”。k值的選擇、距離度量及分類決策規(guī)則是k近鄰法的三個(gè)基本要素。k近鄰法1968年由Cover和Hart提出。
1、例子
有一種兔子叫作悲傷(Grief),它們的平均身高是5050厘米,平均體重55公斤。我們拿來一百個(gè)悲傷,分別測(cè)量它們的身高和體重,畫在坐標(biāo)圖上,用綠色方塊表示。還有一種兔子呢,叫作痛苦(Agony)。它們體型比較小,平均身高是3030厘米,平均體重是44公斤。我們將一百個(gè)痛苦的身高和體重畫在同一個(gè)坐標(biāo)圖上,用藍(lán)色三角表示。最后一種兔子叫絕望(Despair)。它們的平均身高45厘米,但體重較輕,平均只有2.5公斤。一百只絕望的數(shù)據(jù)用黃色圓圈表示。

在這些數(shù)據(jù)中,(身高,體重) 的二元組叫做特征(features),兔子的品種則是分類標(biāo)簽(class label)。我們想解決的問題是,給定一個(gè)未知分類的新樣本的所有特征,通過已知數(shù)據(jù)來判斷它的類別。
所以現(xiàn)在我們想解決的問題是,如果有一只新的兔子,我們知道它的身高和體重,然后我們想要知道它屬于這三類的那一類。這個(gè)時(shí)候k鄰近法就派上用場(chǎng)了。
我們對(duì)兔子進(jìn)行建模,使其形成關(guān)于身高體重的特征向量,然后找到離新兔子在向量空間上舉例最近的k個(gè)兔子(k由自己設(shè)定),然后根據(jù)這k個(gè)兔子的類型去預(yù)測(cè)判斷新兔子的類型。
2、算法

2.1、距離度量

一般情況下,我們都是用p=2,也就是歐式距離。
2.2、k值選擇
k值的選擇會(huì)對(duì)k近鄰法的結(jié)果產(chǎn)?重?影響。 如果選擇較?的k值, 就相當(dāng)于?較?的鄰域中的訓(xùn)練實(shí)例進(jìn)?預(yù)測(cè), “學(xué)習(xí)”的近似誤差 (approximation error)會(huì)減?,只有與輸?實(shí)例較近的(相似的)訓(xùn)練實(shí)例才會(huì)對(duì)預(yù)測(cè)結(jié)果起作?。但缺點(diǎn)是“學(xué)習(xí)”的估計(jì)誤差(estimation error)會(huì)增?,預(yù)測(cè)結(jié)果會(huì)對(duì)近鄰的實(shí)例點(diǎn)?常敏感。如果鄰近的實(shí)例點(diǎn)恰巧是噪聲,預(yù)測(cè)就會(huì)出錯(cuò)。換句話說,k值的減?就意味著整體模型變得復(fù)雜,容易發(fā)?過 擬合。
如果選擇較?的k值,就相當(dāng)于?較?鄰域中的訓(xùn)練實(shí)例進(jìn)?預(yù)測(cè)。其優(yōu)點(diǎn)是可以減少學(xué)習(xí)的估計(jì) 誤差。但缺點(diǎn)是學(xué)習(xí)的近似誤差會(huì)增?。這時(shí)與輸?實(shí)例較遠(yuǎn)的(不相似的)訓(xùn)練實(shí)例也會(huì)對(duì)預(yù)測(cè)起作?,使預(yù)測(cè)發(fā)?錯(cuò)誤。k值的增?就意味著整體的模型變得簡(jiǎn)單。
如果k=N,那么?論輸?實(shí)例是什么,都將簡(jiǎn)單地預(yù)測(cè)它屬于在訓(xùn)練實(shí)例中最多的類。這時(shí),模 型過于簡(jiǎn)單,完全忽略訓(xùn)練實(shí)例中的?量有?信息,是不可取的。
在應(yīng)?中,k值?般取?個(gè)?較?的數(shù)值。通常采?交叉驗(yàn)證法來選取最優(yōu)的k值。
3、概率KNN
簡(jiǎn)單的來說,就是結(jié)果是屬于每一類的概率大小,而不是絕對(duì)的屬于某一個(gè)類別。
二、kd樹算法實(shí)現(xiàn)
本來打算貼上算法流程,但是看了一遍又一遍,感覺算法實(shí)在是晦澀難懂。干脆直接貼鏈接上例子。
https://www.joinquant.com/view/community/detail/c2c41c79657cebf8cd871b44ce4f5d97
三、代碼實(shí)現(xiàn)
現(xiàn)導(dǎo)入需要使用到的包
from numpy import random
from sklearn import neighbors
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
生成期望、方差不同的三組隨機(jī)數(shù)據(jù)
x1 = random.normal(50, 6, 200)
y1 = random.normal(5, 0.5, 200)
x2 = random.normal(30,6,200)
y2 = random.normal(4,0.5,200)
x3 = random.normal(45,6,200)
y3 = random.normal(2.5, 0.5, 200)
對(duì)數(shù)據(jù)進(jìn)行可視化
plt.scatter(x1,y1,c='b',marker='s',s=50,alpha=0.8)
plt.scatter(x2,y2,c='r', marker='^', s=50, alpha=0.8)
plt.scatter(x3,y3, c='g', s=50, alpha=0.8)

從圖中我們可以看出來,三類數(shù)據(jù)分布還是比較明顯。
之后,我們進(jìn)行數(shù)據(jù)的預(yù)處理,并且給數(shù)據(jù)分類到具體標(biāo)簽里
x_val = np.concatenate((x1,x2,x3))
y_val = np.concatenate((y1,y2,y3))
x_diff = max(x_val)-min(x_val)
y_diff = max(y_val)-min(y_val)
x_normalized = [x/(x_diff) for x in x_val]
y_normalized = [y/(y_diff) for y in y_val]
xy_normalized = zip(x_normalized,y_normalized)
xy_train = list(xy_normalized)
labels = [1]*200+[2]*200+[3]*200 # 三類標(biāo)簽,1(藍(lán)色),2(紅色),3(綠色)
開始訓(xùn)練模型
clf = neighbors.KNeighborsClassifier(30) # 設(shè)置k值為30, 其他默認(rèn)
clf.fit(xy_train, labels)
進(jìn)行預(yù)測(cè)
prediction = clf.predict([(50/x_diff, 5/y_diff),(30/x_diff, 3/y_diff)])
print(prediction)

可視化結(jié)果
plt.scatter(x1,y1,c='b',marker='s',s=50,alpha=0.8)
plt.scatter(x2,y2,c='r', marker='^', s=50, alpha=0.8)
plt.scatter(x3,y3, c='g', s=50, alpha=0.8)
plt.scatter(50,5, c='y',s = 50, alpha = 0.8)
plt.scatter(30,3, c='y',s = 50, alpha = 0.8)

我們可以看到,這兩個(gè)黃色的新實(shí)例,第一個(gè)確實(shí)在1(藍(lán)色)中,第二個(gè)感覺比較模糊,在2(紅色)和3(綠色)之間,并沒有很明顯的差別。讓我們看下概率KNN.
prediction_proba = clf.predict_proba([(50/x_diff, 5/y_diff),(30/x_diff, 3/y_diff)])
print(prediction_proba)

我們可以看到第一個(gè)是100%的概率在分類1中,而第二個(gè)實(shí)例分別是77%和23%的概率屬于分類2或者分類3.
接下來,我們?cè)偕梢恍┬碌膶?shí)例來作為測(cè)試集,對(duì)我們的模型進(jìn)行測(cè)試。
x1_test = random.normal(50, 6, 100)
y1_test = random.normal(5, 0.5, 100)
x2_test = random.normal(30,6,100)
y2_test = random.normal(4,0.5,100)
x3_test = random.normal(45,6,100)
y3_test = random.normal(2.5, 0.5, 100)
xy_test_normalized = zip(np.concatenate((x1_test,x2_test,x3_test))/x_diff,\
np.concatenate((y1_test,y2_test,y3_test))/y_diff)
xy_test = list(xy_test_normalized)
labels_test = [1]*100+[2]*100+[3]*100
來看下我們的模型分?jǐn)?shù)
score = clf.score(xy_test, labels_test)
print(score)

可以看到我們的分?jǐn)?shù)還不錯(cuò),主要因?yàn)閿?shù)據(jù)都是我們生成的,規(guī)律比較明顯。以后還是要多用實(shí)際數(shù)據(jù)去試一試。
之前我們提到過,不同的k值會(huì)導(dǎo)致結(jié)果的不同。接下來我們就想看一下不同的k值,結(jié)果有什么不同,哪一個(gè)k值得結(jié)果最好。
record = []
for i in range(1,31):
clf = neighbors.KNeighborsClassifier(i)
clf.fit(xy_train, labels)
score = clf.score(xy_test, labels_test)
record.append(score)
print("when k = %i, score = %f"%(i,score))

k = []
for i, value in enumerate(record):
if value == np.max(record):
k.append(i+1)
print("when k = ", k)
print("the score is the highest = ", np.max(record))

從結(jié)果我們可以看到,最高的分?jǐn)?shù)就是98.67%,很多種不同的k的選擇都會(huì)有這一個(gè)結(jié)果。這個(gè)時(shí)候我們是不是應(yīng)該用較為小的k值呢,去降低算法復(fù)雜度,加快預(yù)算時(shí)間。
參考:
李航博士《統(tǒng)計(jì)學(xué)習(xí)方法》
https://www.joinquant.com/view/community/detail/a98b7021e7391c62f6369207242700b2
https://www.joinquant.com/view/community/detail/c2c41c79657cebf8cd871b44ce4f5d97
https://www.joinquant.com/view/community/detail/bb850ee76d1cae16cc587f29c4439ebd