首先,我們需要了解什麼是“kNN”
kNN英文全稱k Nearest Neighbor,即k近鄰算法。
- 用途:分類問題
- kNN的工作原理:事先有一個有標籤的樣本數據集,然後輸入沒有標籤的新數據後,將新數據的每個特徵和樣本集裏的數據對應特徵進行比較,最後算法提取樣本集中特徵最相似(最近鄰)數據的分類標籤。一般而言,只取k個最相似數據中出現次數最多的分類作爲新數據的分類。
- 優點:精度高、對異常值不敏感、無數據輸入假定。
- 缺點:計算複雜度高、空間複雜度高。
- 適用的數據範圍:數值型和標稱型。
一通文字理解下來後,下面給一個小例子
首先需要了解歐氏距離,很簡單,就是平面上的兩點之間的距離。
例:點A(x1, y1)與點B(x2, y2)之間的距離爲
樣本數據集
樣本編號 | X | Y | label |
1 | 1.0 | 1.1 | A |
2 | 1.0 | 1.0 | A |
3 | 0 | 0 | B |
4 | 0 | 0.1 | B |
測試數據
編號 | X | Y | label |
1 | 0.1 | 0.1 | 待分類 |
從直觀層面上就可以看出測試數據距離B類比較近,通過歐氏距離計算也是如此。下面給出kNN算法以及上面小例子的代碼。
import numpy as np
import operator
import matplotlib.pyplot as plt
def kNN(inX, dataSet, labels, k):
'''
kNN算法
:param inX: 需要分類的數據
:param dataSet: 樣本數據集
:param labels: 樣本數據的標籤
:param k: 需要取出的前k個
:return: 最有可能的分類
'''
# 獲取樣本數據的個數
dataSetSize = dataSet.shape[0]
# np.tile函數將待分類的數據inX“廣播”成和樣本數據dataSet一樣的形狀
# 獲取待分類數據inX與樣本數據集中的每個數據之間的差
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
# 平方
sqDiffMat = diffMat**2
# 按行累加
sqDistances = sqDiffMat.sum(axis=1)
# 開方,得出距離(歐氏距離)
distances = sqDistances**0.5
# 從小到大排序,得到的是數據的index
sortedDistIndicies = distances.argsort()
classCount={}
# 取出前k個距離最小的
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
# 累計距離出現的次數,並做成字典
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
# 將字典按值的大小排序,大的在前
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回最有可能的預測值
return sortedClassCount[0][0]
def createDataSet():
group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
labels = ['A','A','B','B']
return group, labels
if __name__ == '__main__':
group, labels = createDataSet()
predict = kNN([0.1,0.1], group, labels, 3)
print(predict) # 預測結果爲B
接下來上實例,使用kNN算法改進約會網站的配對效果
數據集介紹:
總共10000條,包含三個特徵(每年獲得的飛行常客里程數,玩視頻遊戲所耗時間百分比,每週消費的冰淇淋公升數),而標籤類型爲(1代表“不喜歡的人”,2代表“魅力一般的人”,3代表“極具魅力的人”)
部分數據展示如下:(需要下載數據集的見文末)
40920 8.326976 0.953952 3 14488 7.153469 1.673904 2 26052 1.441871 0.805124 1 75136 13.147394 0.428964 1
樣本數據集展示:
下面先給出作圖代碼,其他可以自主更換座標軸以更好的分類:
def show():
n = 1000 # number of points to create
xcord1 = [];
ycord1 = []
xcord2 = [];
ycord2 = []
xcord3 = [];
ycord3 = []
markers = []
colors = []
fw = open('datingTestSet2.txt', 'w')
for i in range(n):
[r0, r1] = np.random.standard_normal(2)
myClass = np.random.uniform(0, 1)
if (myClass <= 0.16):
fFlyer = np.random.uniform(22000, 60000)
tats = 3 + 1.6 * r1
markers.append(20)
colors.append(2.1)
classLabel = 1 # 'didntLike'
xcord1.append(fFlyer);
ycord1.append(tats)
elif ((myClass > 0.16) and (myClass <= 0.33)):
fFlyer = 6000 * r0 + 70000
tats = 10 + 3 * r1 + 2 * r0
markers.append(20)
colors.append(1.1)
classLabel = 1 # 'didntLike'
if (tats < 0): tats = 0
if (fFlyer < 0): fFlyer = 0
xcord1.append(fFlyer);
ycord1.append(tats)
elif ((myClass > 0.33) and (myClass <= 0.66)):
fFlyer = 5000 * r0 + 10000
tats = 3 + 2.8 * r1
markers.append(30)
colors.append(1.1)
classLabel = 2 # 'smallDoses'
if (tats < 0): tats = 0
if (fFlyer < 0): fFlyer = 0
xcord2.append(fFlyer);
ycord2.append(tats)
else:
fFlyer = 10000 * r0 + 35000
tats = 10 + 2.0 * r1
markers.append(50)
colors.append(0.1)
classLabel = 3 # 'largeDoses'
if (tats < 0): tats = 0
if (fFlyer < 0): fFlyer = 0
xcord3.append(fFlyer);
ycord3.append(tats)
fw.close()
fig = plt.figure()
ax = fig.add_subplot(111)
# ax.scatter(xcord,ycord, c=colors, s=markers)
type1 = ax.scatter(xcord1, ycord1, s=20, c='red')
type2 = ax.scatter(xcord2, ycord2, s=30, c='green')
type3 = ax.scatter(xcord3, ycord3, s=50, c='blue')
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標籤
ax.legend([type1, type2, type3], ["不喜歡", "魅力一般", "極具魅力"], loc=2)
ax.axis([-5000, 100000, -2, 25])
plt.xlabel('每年獲取的飛行常客里程數')
plt.ylabel('玩遊戲所耗時間百分比')
plt.show()
圖像展示如下:
下面給出實例測試代碼:
# 使用kNN算法改進約會網站的配對效果
import numpy as np
import pandas as pd
import operator
import matplotlib.pyplot as plt
def file_Matrix(filename):
'''
將源數據規整爲所需要的數據形狀
:param filename: 源數據
:return: returnMat:規整好的數據;classLabelVector:數據類標籤
'''
data = pd.read_csv(filename, sep='\t').as_matrix()
returnMat = data[:,0:3]
classLabelVector = data[:,-1]
return returnMat, classLabelVector
def autoNorm(dataSet):
'''
數據歸一化
:param dataSet:
:return:
'''
# 每列的最小值
minVals = dataSet.min(0)
# 每列的最大值
maxVals = dataSet.max(0)
ranges = maxVals - minVals
# normDataSet = np.zeros(np.shape(dataSet))
m = dataSet.shape[0]
# np.tile(minVals,(m,1))的意思是將一行數據廣播成m行
normDataSet = (dataSet - np.tile(minVals, (m,1))) / np.tile(ranges, (m,1))
# print(normDataSet)
return normDataSet, ranges, minVals
def kNN(inX, dataSet, labels, k):
'''
kNN算法
:param inX: 需要分類的數據
:param dataSet: 樣本數據集
:param labels: 樣本數據的標籤
:param k: 需要取出的前k個
:return: 最有可能的分類
'''
# 獲取樣本數據的個數
dataSetSize = dataSet.shape[0]
# np.tile函數將待分類的數據inX“廣播”成和樣本數據dataSet一樣的形狀
# 獲取待分類數據inX與樣本數據集中的每個數據之間的差
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
# 平方
sqDiffMat = diffMat**2
# 按行累加
sqDistances = sqDiffMat.sum(axis=1)
# 開方,得出距離(歐氏距離)
distances = sqDistances**0.5
# 從小到大排序,得到的是數據的index
sortedDistIndicies = distances.argsort()
classCount={}
# 取出前k個距離最小的
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
# 累計距離出現的次數,並做成字典
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
# 將字典按值的大小排序,大的在前
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回最有可能的預測值
return sortedClassCount[0][0]
def datingClassTest():
hoRatio = 0.10 #hold out 10%
datingDataMat,datingLabels = file_Matrix('datingTestSet2.txt') #load data setfrom file
normMat, ranges, minVals = autoNorm(datingDataMat)
# print(normMat)
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = kNN(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],4)
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
if (classifierResult != datingLabels[i]): errorCount += 1.0
print("測試集個數爲%d ,分類錯誤的個數爲%d"%(numTestVecs, errorCount))
print("the total error rate is: %f" % (errorCount/float(numTestVecs)))
print("accuracy: %f" % ((1-errorCount/float(numTestVecs))*100))
if __name__ == '__main__':
datingClassTest()
測試結果如下:
the classifier came back with: 2, the real answer is: 2
the classifier came back with: 1, the real answer is: 1
... ...
the classifier came back with: 1, the real answer is: 1
the classifier came back with: 2, the real answer is: 2
測試集個數爲99 ,分類錯誤的個數爲4
the total error rate is: 0.040404
accuracy: 95.959596
測試的結果是錯誤率爲4.04%,可以接受。不過還可以自主調節hoRatio和變量k的值,以便讓錯誤率降低。
數據集下載:(百度網盤)
鏈接:https://pan.baidu.com/s/1UErV8D4Hrw557tjj9fyjlQ
提取碼:o80d
參考文獻:《Machine Learning in Action》