摘要:Python 手寫 K 近鄰算法。python
今天開始,我打算寫寫機器學習教程。說實話,相比爬蟲,掌握機器學習競爭力更強些。算法
目前網上大多這類教程對新手都不友好,要麼直接調用 Sklearn 包,要麼滿篇抽象枯燥的算法公式文字,看這些教程你很難入門,而真正適合入門的手寫 Python 代碼教程寥寥無幾。最近看了慕課網 bobo 老師的機器學習課程後,大呼過癮,最好的機器學習教程沒有之一。我打算以他的教程爲基礎並結合本身的理解,從零開始更新機器學習系列推文。數組
第一篇推文先不扯諸如什麼是機器學習、機器學習有哪些算法這些總結性的文章,在你沒有真正知道它是什麼以前,這些看了也不會有印象反而會增長心理負荷。機器學習
我將長驅直入從一個算法實戰開始,就像之前爬蟲教程同樣,當你真正感覺到它的趣味性後,纔會有想去學它的慾望。函數
下面就從一個場景故事開始。學習
在一個酒吧裏,吧檯上擺着十杯幾乎同樣的紅酒,老闆跟你打趣說想不想來玩個遊戲,贏了免費喝酒,輸了付 3 倍酒錢,贏的機率有 50%。你是個愛冒險的人,果斷說玩。spa
老闆接着道:你眼前的這十杯紅酒,每杯略不相同,前五杯屬於「赤霞珠」,後五杯屬於「黑皮諾」。如今,我從新倒一杯酒,你只須要根據剛纔的十杯正確地告訴我它屬於哪一類。.net
聽完你有點心虛:根本不懂酒啊,光靠看和嘗根本區分辨不出來,不過想起本身是搞機器學習的,不禁多了幾分底氣爽快地答應了老闆。rest
你沒有急着品酒而是問了老闆每杯酒的一些具體信息:酒精濃度、顏色深度等,以及一份紙筆。老闆一邊倒一杯新酒,你邊瘋狂打草稿。很快,你告訴老闆這杯新酒應該是「赤霞珠」。code
老闆瞪大了眼下巴也差點驚掉,歷來沒有人一口酒都不嘗就能答對,無數人都是反覆嚐來嚐去,最後以舉棋不定猜錯而結束。你神祕地笑了笑,老闆信守承諾讓你開懷暢飲。你微醺之時,老闆終於忍不住湊向你打探是怎麼作到的。
你炫耀道:無他,但機器學習熟爾。
接下來,咱們就要從這個故事中開始接觸機器學習了,機器學習給不少人的感受就是「難」,因此我編了上面這個故事,就是要引出機器學習的一個最簡單算法:kNN 算法(K-Nearest Neighbor),也叫 K 近鄰算法。
別被「算法」二字嚇到,我保證你只要有高中數學加上一點點 Python 基礎就能學會這個算法。
學會 kNN 算法,只須要三步:
在說 kNN 算法前說兩個概念:樣本和特徵。
上面的每一杯酒稱做一個「樣本」,十杯酒組成一個樣本集。酒精濃度、顏色深度等信息叫做「特徵」。這十杯酒分佈在一個多維特徵空間中。說到空間,咱們最多能感知三維空間,爲了理解方便,咱們假設區分赤霞珠和黑皮諾,只需利用:酒精濃度和顏色深度兩個特徵值。這樣就能在二維座標軸來直觀展現。
橫軸是酒精濃度值,縱軸是顏色深度值。十杯酒在座標軸上造成十個點,綠色的 5 個點表明五杯赤霞珠,紅色的 5 個點表明五杯黑皮諾。能夠看到兩類酒有明顯的界限。老闆新倒的一杯酒是圖中黃色的點。
記得咱們的問題麼?要肯定這杯酒是赤霞珠仍是黑皮諾,答案顯而易見,經過主觀距離判斷它應該屬於赤霞珠。
這就用到了 K 近鄰算法思想。該算法首先須要取一個參數 K,機器學習中給的經驗取值是 3,咱們假設先取 3 ,具體取多少之後再研究。對於每一個新來的點,K 近鄰算法作的事情就是在全部樣本點中尋找離這個新點最近的三個點,統計三個點所屬類別而後投票統計,得票數最多的類別就是新點的類別。
上圖有綠色和紅色兩個類別。離黃色最近的 3 個點都是綠點,因此綠色和紅色類別的投票數是 3:0 ,綠色取勝,因此黃色點就屬於綠色,也就是新的一杯就屬於赤霞珠。
這就是 K 近鄰算法,它的本質就是經過距離判斷兩個樣本是否類似,若是距離夠近就以爲它們類似屬於同一個類別。固然只對比一個樣本是不夠的,偏差會很大,要比較最近的 K 個樣本,看這 K 個 樣本屬於哪一個類別最多就認爲這個新樣本屬於哪一個類別。
是否是很簡單?
再舉一例,老闆又倒了杯酒讓你再猜,你能夠在座標軸中畫出它的位置。離它最近的三個點,是兩個紅點和一個綠點。紅綠比例是 2:1,紅色勝出,因此 K 近鄰算法告訴咱們這杯酒大機率是黑皮諾。
能夠看到 K 近鄰算法就是經過距離來解決分類問題。這裏咱們解決的二分類問題,事實上 K 近鄰算法自然適合解決多分類問題,除此以外,它也適合解決迴歸問題,以後一一細講。
K 近鄰算法基本思想咱們知道了,來看看它背後的數學原理。該算法的「距離」在二維座標軸中就是兩點之間的距離,計算距離的公式有不少,通常經常使用歐拉公式,這個咱們中學就學過:
解釋下就是:空間中 m 和 n 兩個點,它們的距離等於 x y 兩座標差的平方和再開根。
若是在三維座標中,多了個 z 座標,距離計算公式也相同:
當特徵數量有不少個造成多維空間時,再用 x y z 寫就不方便,咱們換一個寫法,用 X 加下角標的方式表示特徵維度,這樣 n 維 空間兩點之間的距離公式能夠寫成:
公式還能夠進一步精簡:
這就是 kNN 算法的數學原理,不難吧?
只要計算出新樣本點與樣本集中的每一個樣本的座標距離,而後排序篩選出距離最短的 3 個點,統計這 3 個點所屬類別,數量佔多的就是新樣本所屬的酒類。
根據歐拉公式,咱們能夠用很基礎的 Python 實現。
首先隨機設置十個樣本點表示十杯酒,我這裏取了 Sklearn 中的葡萄酒數據集的部分樣本點,這個數據集在以後的算法中會常常用到會慢慢介紹。
import numpy as np
X_raw = [[14.23, 5.64],
[13.2 , 4.38],
[13.16, 5.68],
[14.37, 4.80 ],
[13.24, 4.32],
[12.07, 2.76],
[12.43, 3.94],
[11.79, 3. ],
[12.37, 2.12],
[12.04, 2.6 ]]
y_raw = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1]
複製代碼
X_raw 的兩列值分別是顏色深度和酒精濃度值,y_raw 中的 0 表示黑皮諾,1 表示赤霞珠。
新的一杯酒信息:
x_test = np.array([12.8,4.1])
複製代碼
在機器學習中常使用 numpy 的 array 數組而不是列表 list,由於 array 速度快也能執行向量運算,因此在運算以前先把上面的列表轉爲數組:
X_train = np.array(X_raw)
y_train = np.array(y_raw)
複製代碼
有了 X Y 座標就能夠繪製出第一張散點圖:
import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.figure(figsize=(10,6))
plt.scatter(X_train[y_train==1,0],X_train[y_train==1,1],s=100,color=color_g,label='赤霞珠')
plt.scatter(X_train[y_train==0,0],X_train[y_train==0,1],s=100,color=color_r,label='黑皮諾')
plt.scatter(x_test2[0],x_test2[1],s=100,color=color_y) # x_test
plt.xlabel('酒精濃度')
plt.ylabel('顏色深度')
plt.legend(loc='lower right')
plt.tight_layout()
plt.savefig('葡萄酒樣本.png')
複製代碼
接着,根據歐拉公式計算黃色的新樣本點到每一個樣本點的距離:
from math import sqrt
distances = [sqrt(np.sum((x - x_test)**2)) for x in X_train] # 列表推導式
distances
[out]:
[1.7658142597679973,
1.5558920271021373,
2.6135799203391503,
1.9784084512557052,
1.5446682491719705,
0.540092584655631,
0.7294518489934753,
0.4172529209005018,
1.215113163454334,
0.7011419257183239]
複製代碼
上面用到了列表推導式,之前的爬蟲教程中常常用到,若是不熟悉能夠在公衆號搜索「列表推導式」關鍵字複習。
這樣就計算出了黃色點到每一個樣本點的距離,接着找出最近的 3 個點,可使用 np.argsort 函數返回樣本點的索引位置:
sort = np.argsort(distances)
sort
[out]:array([7, 5, 9, 6, 8, 4, 1, 0, 3, 2], dtype=int64)
複製代碼
經過這個索引值就能在 y_train 中找到對應酒的類別,再統計出排名前 3 的就好了:
K = 3
topK = [y_train[i] for i in sort[:K]]
topK
[out]:[1, 1, 1]
複製代碼
能夠看到距離黃色點最近的 3 個點都是綠色的赤霞珠,與剛纔肉眼觀測的結果一致。
到這裏,距離輸出黃色點所屬類別只剩最後一步,使用 Counter 函數統計返回類別值便可:
from collections import Counter
votes = Counter(topK)
votes
[out]:Counter({1: 3})
predict_y = votes.most_common(1)[0][0]
predict_y
[out]:1
複製代碼
最後的分類結果是 1 ,也就是新的一杯酒是赤霞珠。
咱們使用 Python 手寫完成了一個簡易的 kNN 算法,是否是不難?
若是以爲難,來看一個更簡單的方法:調用 sklearn 庫中的 kNN 算法,俗稱調包,只要 5 行代碼就能獲得一樣的結論。
from sklearn.neighbors import KNeighborsClassifier
kNN_classifier = KNeighborsClassifier(n_neighbors=3)
kNN_classifier.fit(X_train,y_train )
x_test = x_test.reshape(1,-1)
kNN_classifier.predict(x_test)[0]
[out]:1
複製代碼
首先從 sklearn 中引入了 kNN 的分類算法函數 KNeighborsClassifier 並創建模型,設置最近的 K 個樣本數量 n_neighbors 爲 3。接下來 fit 訓練模型,最後 predict 預測模型獲得分類結果 1,和咱們剛纔手寫的代碼結果同樣的。
你能夠看到,sklearn 調包雖然簡單,不過做爲初學者最好是懂得它背後的算法原理,而後用 Python 代碼親自實現一遍,這纔是入門機器的正確姿式。
下一篇推文來看看 sklearn 是如何封裝 kNN 算法的,並用 Python 手寫一遍。
本文的 jupyter notebook 代碼,能夠在我公衆號:「高級農民工」後臺回覆「kNN1」獲得,加油!