教程從頭開始在Python中實現k最近鄰居
k近鄰法(或簡稱為kNN)是一種易於理解和實現的演算法,也是一種功能強大的工具。
在本教程中,您將學會使用Python(2.7)從零開始實現k近鄰(k-Nearest Neighbors)演算法。並將這一演算法使用在具體的分類問題上,並對鳶尾花分類問題進行論證。
如果你是一名Python程式設計師,或是一個能夠快速學會python的程式設計師,本教程適合你,當然你還要對如何從頭開始實現k近鄰演算法演算法感興趣。
k-Nearest Neighbors演算法 圖片來自維基百科,保留所有權利
什麼是k近鄰演算法
kNN的模型是整個訓練資料集。當一個不可見的資料例項需要預測時,kNN演算法將通過訓練資料集搜尋k個最相似的例項,並彙總最相似例項的預測屬性,將其作為不可見資料例項的預測返回。
相似性的度量取決於資料的型別。對於實值資料,可以使用歐氏距離。其他型別的資料,如分類或二進位制資料,可以使用漢明距離。
在迴歸問題的情況下,可以返回預測屬性的平均值。在分類的情況下,會返回最可能的類別。
k近鄰演算法如何工作
kNN演算法屬於基於資料例項的競爭學習和即時學習演算法。
基於例項的演算法是那些使用資料例項(或單條資料)對問題進行建模以便做出預測性決策的演算法。kNN演算法是基於例項的演算法的一種極端形式,因為所有的訓練觀察值都保留為模型的一部分。
這是一種競爭學習演算法,因為它在內部使用模型元素(資料例項)之間的競爭來作出預測性決策。資料例項之間的客觀相似性度量使得每個資料例項與“勝利”競爭或者與給定的不可見資料例項最相似並對預測進行貢獻。
即時學習指的是這個演算法直到需要預測的時候才建立一個模型。這是“懶惰”,因為它只在最後一秒工作。這樣做的好處是隻包括與不可見的待預測資料相關的資料,稱為本地化模型。缺點是在較大的訓練資料集上重複相同或類似的搜尋可能使計算量難以承受。
最後,kNN是強大的,因為它不會假設任何關於資料的內容,除了可以在任何兩個例項之間一致地計算距離度量。因此,它被稱為非引數或非線性的,因為它不具有函式形式。
使用測量資料分類鳶尾花
我們將在本教程中使用鳶尾花分類問題作為測試。
問題是由三種不同種類的鳶尾花的150個觀察結果組成。給定的花有4種測量值:萼片長度,萼片寬度,花瓣長度和花瓣寬度,全部以釐米為單位。預測屬性是種類,看看資料例項屬於setosa,versicolor或者virginica三種花的哪一種。
這是一個標準的資料集,其中的物種資料已知所有情況。因此,我們可以將資料分成訓練和測試資料集,並使用預測結果來對我們的演算法實現進行評估。正確的對這個問題的分類準確度要求在90%以,通常是96%或更好。
您可以從iris.data免費下載資料集,也可參閱資源部分了解更多詳情。
如何在Python中實現k近鄰演算法
本教程分為以下幾個步驟:
- 資料處理:從CSV檔案匯入資料集並分割成測試/訓練資料集。
- 相似度:計算兩個資料例項之間的距離。
- 近鄰:找到k個最相似的資料例項。
- 響應:從一組資料例項生成響應。
- 準確性:總結預測的準確性。
- 整合:把演算法各部分整合在一起。
1.處理資料
我們需要做的第一件事是載入我們的資料檔案。資料為CSV格式,沒有標題行或任何引號。我們可以使用open函式開啟檔案,並使用csv庫中的reader函式逐行讀取資料。
import csv
with open('iris.data', 'rb') as csvfile:
lines = csv.reader(csvfile)
for row in lines:
print ', '.join(row)
接下來,我們需要將資料拆分成可被kNN用於預測的訓練資料集,以及可用於評估模型準確性的測試資料集。
我們首先需要將作為字串型別載入的花朵測量值轉換為我們可以使用的數字型別。接下來,我們需要將資料集隨機分成訓練和測試資料集。訓練/測試的比例為67/33,是使用的標準比率。
綜合起來,我們可以定義一個名為loadDataset的函式,它使用提供的檔名載入一個CSV檔案,並使用提供的分割比例隨機地將其分割為火車和測試資料集。
import csv
import random
def loadDataset(filename, split, trainingSet=[] , testSet=[]):
with open(filename, 'rb') as csvfile:
lines = csv.reader(csvfile)
dataset = list(lines)
for x in range(len(dataset)-1):
for y in range(4):
dataset[x][y] = float(dataset[x][y])
if random.random() < split:
trainingSet.append(dataset[x])
else:
testSet.append(dataset[x])
將鳶尾花資料集CSV檔案下載到本地目錄。我們可以用我們的該資料集來測試這個函式,如下所示:
trainingSet=[]
testSet=[]
loadDataset('iris.data', 0.66, trainingSet, testSet)
print 'Train: ' + repr(len(trainingSet))
print 'Test: ' + repr(len(testSet))
2.相似性
為了做出預測,我們需要計算任意兩個給定資料例項之間的相似度。這是必要的,以便我們可以在訓練資料集中為測試資料集的給定成員定位k個最相似的資料例項,從而進行預測。
考慮到花朵的四種測量屬性都是數字型別的,並且具有相同的單位,我們可以直接使用歐幾里得距離度量。這被定義為兩個數字陣列之間的平方差的總和的平方根(再讀幾次,真正理解它)。
此外,我們要控制哪些欄位包含在距離計算中。具體來說,我們只想包含前4個屬性。一種方法是將歐氏距離限制在一個固定的長度,忽略超出的內容。
把所有這些放在一起,我們可以定義euclideanDistance函式如下:
import math
def euclideanDistance(instance1, instance2, length):
distance = 0
for x in range(length):
distance += pow((instance1[x] - instance2[x]), 2)
return math.sqrt(distance)
我們可以用一些示例資料來測試這個函式,如下所示:
data1 = [2, 2, 2, 'a']
data2 = [4, 4, 4, 'b']
distance = euclideanDistance(data1, data2, 3)
print 'Distance: ' + repr(distance)
3.近鄰
現在我們有了一個相似性度量,我們可以用它為一個給定的不可見的例項收集k個最相似的例項。
這是計算所有例項的距離和選擇具有最小距離值的子集的直接過程。
下面是getNeighbors函式,該函式從給定測試例項的訓練集中返回k個最相似的鄰居(使用已定義的euclideanDistance函式)
import operator
def getNeighbors(trainingSet, testInstance, k):
distances = []
length = len(testInstance)-1
for x in range(len(trainingSet)):
dist = euclideanDistance(testInstance, trainingSet[x], length)
distances.append((trainingSet[x], dist))
distances.sort(key=operator.itemgetter(1))
neighbors = []
for x in range(k):
neighbors.append(distances[x][0])
return neighbors
我們可以測試這個函式,如下:
trainSet = [[2, 2, 2, 'a'], [4, 4, 4, 'b']]
testInstance = [5, 5, 5]
k = 1
neighbors = getNeighbors(trainSet, testInstance, 1)
print(neighbors)
4.響應
一旦找到測試例項最相似的鄰居,下一個任務是根據這些鄰居設計一個預測的響應。
我們可以通過允許每個鄰居為他們的類屬性進行投票來做到這一點,並以多數票作為預測。
以下提供了獲得多個鄰居的多數投票答覆的功能。它假定所分種類是每個鄰居的最後一個屬性。
import operator
def getResponse(neighbors):
classVotes = {}
for x in range(len(neighbors)):
response = neighbors[x][-1]
if response in classVotes:
classVotes[response] += 1
else:
classVotes[response] = 1
sortedVotes = sorted(classVotes.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedVotes[0][0]
我們可以用一些測試鄰居來測試這個函式,如下所示:
neighbors = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
response = getResponse(neighbors)
print(response)
這種方法在特定的情況下返回一個響應,但是您可以以特定方式處理這些情況,例如不返回響應或選擇無偏差的隨機響應。
5.準確性
我們已經實現了全部的kNN演算法。剩下的一個重要問題是如何評估預測的準確性。
評估模型準確性的簡單方法是計算所有預測中所有正確預測的比例,稱為分類準確率。
下面是getAccuracy函式,將總體正確預測相加,並以正確分類的百分比形式返回準確性:
def getAccuracy(testSet, predictions):
correct = 0
for x in range(len(testSet)):
if testSet[x][-1] is predictions[x]:
correct += 1
return (correct/float(len(testSet))) * 100.0
我們可以用一個測試資料集和預測來測試這個函式,如下所示:
testSet = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
predictions = ['a', 'a', 'a']
accuracy = getAccuracy(testSet, predictions)
print(accuracy)
6.整合
我們現在擁有演算法的所有元素,我們可以將它們與主函式結合在一起。
下面是在Python中從頭開始實現kNN演算法的完整示例。
# Example of kNN implemented from Scratch in Python
import csv
import random
import math
import operator
def loadDataset(filename, split, trainingSet=[] , testSet=[]):
with open(filename, 'rb') as csvfile:
lines = csv.reader(csvfile)
dataset = list(lines)
for x in range(len(dataset)-1):
for y in range(4):
dataset[x][y] = float(dataset[x][y])
if random.random() < split:
trainingSet.append(dataset[x])
else:
testSet.append(dataset[x])
def euclideanDistance(instance1, instance2, length):
distance = 0
for x in range(length):
distance += pow((instance1[x] - instance2[x]), 2)
return math.sqrt(distance)
def getNeighbors(trainingSet, testInstance, k):
distances = []
length = len(testInstance)-1
for x in range(len(trainingSet)):
dist = euclideanDistance(testInstance, trainingSet[x], length)
distances.append((trainingSet[x], dist))
distances.sort(key=operator.itemgetter(1))
neighbors = []
for x in range(k):
neighbors.append(distances[x][0])
return neighbors
def getResponse(neighbors):
classVotes = {}
for x in range(len(neighbors)):
response = neighbors[x][-1]
if response in classVotes:
classVotes[response] += 1
else:
classVotes[response] = 1
sortedVotes = sorted(classVotes.iteritems(), key=operator.itemgetter(1), reverse=True)
return sortedVotes[0][0]
def getAccuracy(testSet, predictions):
correct = 0
for x in range(len(testSet)):
if testSet[x][-1] == predictions[x]:
correct += 1
return (correct/float(len(testSet))) * 100.0
def main():
# prepare data
trainingSet=[]
testSet=[]
split = 0.67
loadDataset('iris.data', split, trainingSet, testSet)
print 'Train set: ' + repr(len(trainingSet))
print 'Test set: ' + repr(len(testSet))
# generate predictions
predictions=[]
k = 3
for x in range(len(testSet)):
neighbors = getNeighbors(trainingSet, testSet[x], k)
result = getResponse(neighbors)
predictions.append(result)
print('> predicted=' + repr(result) + ', actual=' + repr(testSet[x][-1]))
accuracy = getAccuracy(testSet, predictions)
print('Accuracy: ' + repr(accuracy) + '%')
main()
執行該示例,您將看到每個預測的結果與測試集中的實際類別值相比較。在執行結束時,您將看到模型的準確性。在這種情況下,略高於98%。
...
> predicted='Iris-virginica', actual='Iris-virginica'
> predicted='Iris-virginica', actual='Iris-virginica'
> predicted='Iris-virginica', actual='Iris-virginica'
> predicted='Iris-virginica', actual='Iris-virginica'
> predicted='Iris-virginica', actual='Iris-virginica'
Accuracy: 98.0392156862745%
一些擴充套件問題
本節為您提供繼續擴充套件的一些思路,您可以使用您在本教程中實現的Python程式碼進行嘗試。
- 迴歸:可以使實現適應迴歸問題(預測實值屬性)。總結最接近的例項可能涉及到預測屬性的平均值或中值。
- 規範化:當度量單位在屬性之間不同時,某種屬性可能在對距離度量的貢獻中占主導地位。對於這些型別的問題,在計算相似性之前,您需要將所有資料屬性重新縮放到0-1範圍內(稱為歸一化)。更新模型以支援資料規範化。
- 可選的距離度量:有很多距離度量可用,你甚至可以開發自己的域特定的距離度量,如果你喜歡。實施一個可選的距離測量,如曼哈頓距離或向量點乘。
關於這個演算法還有更多的擴充套件,也許你會想要探索一下。另外兩個思路包括支援與預測的k個最相似例項的距離加權和用於搜尋相似例項的更高階的基於資料樹的結構。
瞭解更多
本節將提供一些資源,您可以使用這些資源來了解有關k鄰近演算法的更多資訊,從演算法的工作原理和工作原理以及在程式碼中實現它們的實際問題兩方面進行了解。
問題
程式碼
本節連結到流行的機器學習庫中kNN的開源實現。如果您正在考慮實施您自己的操作使用方法,請檢視這些內容。
書
你可能有一本或多本關於應用機器學習的書籍。本部分重點介紹機器學習常用的應用書中關於k近鄰法的章節。
- Applied Predictive Modeling, pages 159 and 350.
- Data Mining: Practical Machine Learning Tools and Techniques, Third Edition (The Morgan Kaufmann Series in Data Management Systems), pages 76, 128 and 235.
- Machine Learning for Hackers, Chapter 10.
- Machine Learning in Action, Chapter 2.
- Programming Collective Intelligence: Building Smart Web 2.0 Applications, Chapters 2 and 8 and page 293.
教程摘要
在本教程中,您瞭解了k-Nearest Neighbor演算法,它是如何工作的以及可用於思考演算法並將其與其他演算法關聯的一些比喻。建議您從頭開始在Python中實現kNN演算法,這樣您就可以瞭解每一行程式碼,並且可以調整演算法實現並探索擴充套件以滿足自己的專案需求。
以下是本教程的5個關鍵知識:
- k-最近鄰:一個簡單的演算法來理解和實現,以及一個強大的非引數方法。
- 基於例項的方法:使用資料例項(觀察)對問題進行建模。
- 競爭學習:學習和預測決策是通過模型元素之間的內部競爭來實現的。
- 即時學習:一個模型直到需要時才被構建出來,以便進行預測。
- 相似度量:計算資料例項之間的目標距離度量是該演算法的一個關鍵特徵。
最後,回顧一下,你使用這個教程實現了kNN嗎?你怎麼實現的的?你學會了什麼?