- k-Nearest Neighbors算法(簡稱KNN算法,k近鄰算法),是一個(gè)邏輯很簡單的分類算法,其核心原理是找到與被測數(shù)據(jù)距離最近的k個(gè)數(shù)據(jù),這k個(gè)數(shù)據(jù)中最多的類別就是預(yù)測數(shù)據(jù)的類別,而這個(gè)距離用的是歐式距離。
- knn算法幾乎沒有訓(xùn)練過程,實(shí)現(xiàn)簡單,但是計(jì)算復(fù)雜性高;空間復(fù)雜性高;一般數(shù)值很大的時(shí)候不用這個(gè),計(jì)算量太大。但是單個(gè)樣本又不能太少,否則容易發(fā)生誤分,最大的缺點(diǎn)是無法給出數(shù)據(jù)的內(nèi)在含義
knn算法python實(shí)現(xiàn)
1. 直接使用python sklearn庫
十來行代碼就能搞定,高效快捷
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
#加載數(shù)據(jù)
iris = datasets.load_iris()
iris_x = iris.data
iris_y = iris.target
#分割數(shù)據(jù),30%用做測試集
x_train, x_test, y_train, y_test = train_test_split(iris_x, iris_y, test_size=0.3)
#訓(xùn)練模型,預(yù)測數(shù)據(jù)
knn = KNeighborsClassifier()
knn.fit(x_train, y_train)
print(knn.predict(x_test))
print(y_test)
其中iris數(shù)據(jù)是分類鳶尾花數(shù)據(jù),sklearn內(nèi)置的,當(dāng)然也可以下載原數(shù)據(jù),鏈接:https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
2. 不使用庫,徒手寫knn實(shí)現(xiàn)
KNN算法分為如下幾步:
數(shù)據(jù)處理:打開CSV文件獲取數(shù)據(jù),將原始數(shù)據(jù)分為測試集/訓(xùn)練集。
相似性度量:計(jì)算每兩個(gè)數(shù)據(jù)實(shí)例之間的距離。
近鄰查找:找到k個(gè)與當(dāng)前數(shù)據(jù)最近的鄰居。
結(jié)果反饋:從近鄰實(shí)例反饋結(jié)果。
精度評估:統(tǒng)計(jì)預(yù)測精度。
主函數(shù):將上述過程串起來。
2.1 數(shù)據(jù)處理
首先加載文件中的數(shù)據(jù)。示例中使用的是csv module中的reader函數(shù)去讀取文件。其實(shí)可以直接按行讀取處理成想要的數(shù)據(jù)類型,不需要引入csv模塊,因人而異。
接下來,將數(shù)據(jù)拆分為kNN用于做預(yù)測的訓(xùn)練集(training dataset)和用來評估模型精度的測試集(test dataset)。。通常訓(xùn)練集/測試集的劃分比例標(biāo)準(zhǔn)為67/33。
將上述步驟合在一起,我們可以定義一個(gè)叫l(wèi)oadDataset的函數(shù),該函數(shù)可以加載指定的CSV文件,并按照指定的比例隨機(jī)分為訓(xùn)練集與測試集。
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])
#測試函數(shù)
trainingSet=[]
testSet=[]
loadDataset('iris.data', 0.66, trainingSet, testSet)
print 'Train: ' , len(trainingSet)
print 'Test: ', len(testSet)
2.2 相似度度量(計(jì)算距離)
為了進(jìn)行預(yù)測我們需要計(jì)算任意兩個(gè)數(shù)據(jù)實(shí)例的相似性。對于給定的每一個(gè)測試集中的數(shù)據(jù)實(shí)例,我們都可以在訓(xùn)練集中找出k個(gè)相似性最高的數(shù)據(jù)實(shí)例,這樣就可以依次進(jìn)行預(yù)測。
假定鳶尾花的4個(gè)測量數(shù)據(jù)都為數(shù)值形式且單位相同,我們可以直接采用歐氏距離(Euclidean distance)進(jìn)行相似性度量。歐式距離定義為:兩組向量對應(yīng)元素之差的平方和再做平方根運(yùn)算。
將上述步驟合在一起,我們可以將euclideanDistance函數(shù)定義為:
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)
#測試函數(shù)
data1 = [2, 2, 2, 'a']
data2 = [4, 4, 4, 'b']
distance = euclideanDistance(data1, data2, 3)
print 'Distance: ' , distance
2.3 近鄰查找
采用相似性度量的方法尋找未知數(shù)據(jù)實(shí)例的k個(gè)相似性最高的實(shí)例。
處理過程直接計(jì)算所有樣本點(diǎn)到給定點(diǎn)的歐式距離,進(jìn)而篩選距離最近的樣本點(diǎn)子集。
下面是getNeighbors函數(shù),該函數(shù)遍歷訓(xùn)練集并返回與測試實(shí)例距離最近的k個(gè)近鄰樣本點(diǎn)(采用已經(jīng)定義好的euclideanDistance函數(shù))。
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
#測試函數(shù)
trainSet = [[2, 2, 2, 'a'], [4, 4, 4, 'b']]
testInstance = [5, 5, 5]
k = 1
neighbors = getNeighbors(trainSet, testInstance, 1)
print(neighbors)
2.4 結(jié)果預(yù)測
我們已經(jīng)找到了測試實(shí)例的最近的鄰居,下一步就是基于這些近鄰做出預(yù)測結(jié)果。
我們可以讓每個(gè)鄰居對測試實(shí)例的類別屬性進(jìn)行投票,最終以票數(shù)多者做為預(yù)測結(jié)果。
下面的函數(shù)提供了從近鄰?fù)镀敝蟹答伓鄶?shù)投票結(jié)果的機(jī)制。該函數(shù)假定每個(gè)鄰居的最后一列為類別屬性。
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]
#測試函數(shù)
neighbors = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
response = getResponse(neighbors)
print(response)
該方法在平局的情況下依然會(huì)有一個(gè)返回結(jié)果,但是我們可以對其特殊處理,例如返回空值或者選擇一個(gè)無偏隨機(jī)結(jié)果
2.5 精度評估
評估模型精度最簡單的方法就是計(jì)算正確預(yù)測結(jié)果數(shù)量占全部預(yù)測結(jié)果數(shù)量的比例,稱為分類精度。下面是getAccuracy函數(shù),該函數(shù)統(tǒng)計(jì)所有的正確預(yù)測并返回正確分類的百分比精度。
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
#測試函數(shù)
testSet = [[1,1,1,'a'], [2,2,2,'a'], [3,3,3,'b']]
predictions = ['a', 'a', 'a']
accuracy = getAccuracy(testSet, predictions)
print(accuracy)
2.6 主函數(shù)(串聯(lián)步驟2.1~2.5)
目前為止,我們已經(jīng)具備了所有的算法組成元素,下面我們將這些元素串起來,組成主函數(shù)。
下面是從零開始實(shí)現(xiàn)的kNN算法完整Python代碼。
# 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) + '%')
if __name__ == '__main__':
main()
參考
http://python.jobbole.com/87407/
https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data
https://blog.csdn.net/Gamer_gyt/article/details/51232210