kNN算法解析及應用【內附詳細代碼和數據集】

首先,我們需要了解什麼是“kNN”

kNN英文全稱k Nearest Neighbor,即k近鄰算法

  • 用途:分類問題
  • kNN的工作原理:事先有一個有標籤的樣本數據集,然後輸入沒有標籤的新數據後,將新數據的每個特徵和樣本集裏的數據對應特徵進行比較,最後算法提取樣本集中特徵最相似(最近鄰)數據的分類標籤。一般而言,只取k個最相似數據中出現次數最多的分類作爲新數據的分類。
  • 優點:精度高、對異常值不敏感、無數據輸入假定。
  • 缺點:計算複雜度高、空間複雜度高。
  • 適用的數據範圍:數值型和標稱型。

一通文字理解下來後,下面給一個小例子

首先需要了解歐氏距離,很簡單,就是平面上的兩點之間的距離。

例:點A(x1, y1)與點B(x2, y2)之間的距離爲 D=\sqrt{(x1 - x2)^{2} + (y1 - y2)^{2}}

樣本數據集

樣本編號 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》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章