k-近鄰算法代碼詳解及其概述

k-近鄰算法是一種基本分類與迴歸方法,實際上是利用訓練數據集對特徵向量空間進行劃分,並將其作爲一個分類模型!k-近鄰法的三個基本要素是:距離度量,k值選擇和分類決策規則。我主要是通過代碼來學習k-近鄰算法,原理大家可以自行看書,比較簡單!

kNN
收集數據:任何方法
準備數據:距離計算所需要的數值,最好是結構化的數據格式
分析數據:任何方法
訓練算法:此步驟不適用於 k-近鄰算法
測試算法:計算錯誤率
使用算法:輸入樣本數據和結構化的輸出結果,然後運行 k-近鄰算法判斷輸入數據分類屬於哪個分類,最後對計算出的分類執行後續處理

# coding: utf-8
'''
kNN.py
'''
from numpy import *
# 導入科學計算包numpy和運算符模塊operator
import operator
from os import listdir
#查找路徑
from collections import Counter
#專用的容器數據類型來替代python的通用內置容器:dict(字典),list(列表), set(集合)和tuple(元組),計數用的!

def createDataSet():
    """
    創建數據集和標籤
     調用方式
     import kNN
     group, labels = kNN.createDataSet()
    """
    group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
    labels = ['A', 'A', 'B', 'B']
    return group, labels


def classify0(inX, dataSet, labels, k):
    """
    inx[1,2,3]
    DS=[[1,2,3],[1,2,0]]
    inX: 用於分類的輸入向量
    dataSet: 輸入的訓練樣本集
    labels: 標籤向量
    k: 選擇最近鄰居的數目
    注意:labels元素數目和dataSet行數相同;程序使用歐式距離公式.
    預測數據所在分類可在輸入下列命令
    kNN.classify0([0,0], group, labels, 3)
    """

    # -----------實現 classify0() 方法的第一種方式----------------------------------------------------------------------------------------------------------------------------
    # 1. 距離計算
    dataSetSize = dataSet.shape[0] #得到數據集的行數
    # tile生成和訓練樣本對應的矩陣,並與訓練樣本求差
    """
    tile: 列-3表示複製的行數, 行-1/2表示對inx的重複的次數
    In [8]: tile(inx, (3, 1))
    Out[8]:
    array([[1, 2, 3],
        [1, 2, 3],
        [1, 2, 3]])
    In [9]: tile(inx, (3, 2))
    Out[9]:
    array([[1, 2, 3, 1, 2, 3],
        [1, 2, 3, 1, 2, 3],
        [1, 2, 3, 1, 2, 3]])
    """
    diffMat = tile(inX, (dataSetSize, 1)) - dataSet
    """
    歐氏距離: 點到點之間的距離
       第一行: 同一個點 到 dataSet的第一個點的距離。
       第二行: 同一個點 到 dataSet的第二個點的距離。
       ...
       第N行: 同一個點 到 dataSet的第N個點的距離。
    [[1,2,3],[1,2,3]]-[[1,2,3],[1,2,0]]
    (A1-A2)^2+(B1-B2)^2+(c1-c2)^2
    """
    # 取平方
    sqDiffMat = diffMat ** 2
    # 將矩陣的每一行相加
    sqDistances = sqDiffMat.sum(axis=1)
    # 開方
    distances = sqDistances ** 0.5
    # 根據距離排序從小到大的排序,返回對應的索引位置
    # argsort() 是將x中的元素從小到大排列,提取其對應的index(索引),然後輸出到y。
    # 例如:y=array([3,0,2,1,4,5]) 則,x[3]=-1最小,所以y[0]=3;x[5]=9最大,所以y[5]=5。
    # print 'distances=', distances
    sortedDistIndicies = distances.argsort()
    # print 'distances.argsort()=', sortedDistIndicies

    # 2. 選擇距離最小的k個點
    classCount = {}
    for i in range(k):
        # 找到該樣本的類型
        voteIlabel = labels[sortedDistIndicies[i]]
        # 在字典中將該類型加一
        # 字典的get方法
        # 如:list.get(k,d) 其中 get相當於一條if...else...語句,參數k在字典中,字典將返回list[k];如果參數k不在字典中則返回參數d,如果K在字典中則返回k對應的value值
        # l = {5:2,3:4}
        # print l.get(3,0)返回的值是4;
        # Print l.get(1,0)返回值是0;
        classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
    # 3. 排序並返回出現最多的那個類型
    # 字典的 items() 方法,以列表返回可遍歷的(鍵,值)元組數組。
    # 例如:dict = {'Name': 'Zara', 'Age': 7}   print "Value : %s" %  dict.items()   Value : [('Age', 7), ('Name', 'Zara')]
    # sorted 中的第2個參數 key=operator.itemgetter(1) 這個參數的意思是先比較第幾個元素
    # 例如:a=[('b',2),('a',1),('c',0)]  b=sorted(a,key=operator.itemgetter(1)) >>>b=[('c',0),('a',1),('b',2)] 可以看到排序是按照後邊的0,1,2進行排序的,而不是a,b,c
    # b=sorted(a,key=operator.itemgetter(0)) >>>b=[('a',1),('b',2),('c',0)] 這次比較的是前邊的a,b,c而不是0,1,2
    # b=sorted(a,key=opertator.itemgetter(1,0)) >>>b=[('c',0),('a',1),('b',2)] 這個是先比較第2個元素,然後對第一個元素進行排序,形成多級排序。
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
    return sortedClassCount[0][0]

    # ------------------------------------------------------------------------------------------------------------------------------------------
    # 實現 classify0() 方法的第二種方式

    # """
    # 1. 計算距離

    # 歐氏距離: 點到點之間的距離
    #    第一行: 同一個點 到 dataSet的第一個點的距離。
    #    第二行: 同一個點 到 dataSet的第二個點的距離。
    #    ...
    #    第N行: 同一個點 到 dataSet的第N個點的距離。

    # [[1,2,3],[1,2,3]]-[[1,2,3],[1,2,0]]
    # (A1-A2)^2+(B1-B2)^2+(c1-c2)^2

    # inx - dataset 使用了numpy broadcasting,見 https://docs.scipy.org/doc/numpy-1.13.0/user/basics.broadcasting.html
    # np.sum() 函數的使用見 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sum.html
    # """
    #   dist = np.sum((inx - dataset)**2, axis=1)**0.5

    # """
    # 2. k個最近的標籤

    # 對距離排序使用numpy中的argsort函數, 見 https://docs.scipy.org/doc/numpy-1.13.0/reference/generated/numpy.sort.html#numpy.sort
    # 函數返回的是索引,因此取前k個索引使用[0 : k]
    # 將這k個標籤存在列表k_labels中
    # """
    # k_labels = [labels[index] for index in dist.argsort()[0 : k]]
    # """
    # 3. 出現次數最多的標籤即爲最終類別

    # 使用collections.Counter可以統計各個標籤的出現次數,most_common返回出現次數最多的標籤tuple,例如[('lable1', 2)],因此[0][0]可以取出標籤值
    # """
    # label = Counter(k_labels).most_common(1)[0][0]
    # return label

    # ------------------------------------------------------------------------------------------------------------------------------------------


def test1():
    """
    第一個例子演示
    """
    group, labels = createDataSet()
    print str(group)
    print str(labels)
    print classify0([0.1, 0.1], group, labels, 3)


# ----------------------------------------------------------------------------------------
def file2matrix(filename):
    """
    導入訓練數據
    :param filename: 數據文件路徑
    :return: 數據矩陣returnMat和對應的類別classLabelVector
    """
    fr = open(filename)
    # 獲得文件中的數據行的行數
    numberOfLines = len(fr.readlines())
    # 生成對應的空矩陣
    # 例如:zeros(2,3)就是生成一個 2*3的矩陣,各個位置上全是 0 
    returnMat = zeros((numberOfLines, 3))  # prepare matrix to return
    classLabelVector = []  # prepare labels return
    fr = open(filename)
    index = 0
    for line in fr.readlines():
        # str.strip([chars]) --返回移除字符串頭尾指定的字符生成的新字符串
        line = line.strip()
        # 以 '\t' 切割字符串
        listFromLine = line.split('\t')
        # 每列的屬性數據
        returnMat[index, :] = listFromLine[0:3]
        # 每列的類別數據,就是 label 標籤數據
        classLabelVector.append(int(listFromLine[-1]))
        index += 1
    # 返回數據矩陣returnMat和對應的類別classLabelVector
    return returnMat, classLabelVector
"""
調用此段代碼使用Matplotlib創建散點圖的方法:
import imp
import kNN
imp.reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet2.txt')  #導入數據,並將其格式化爲想要的數據格式

datingTestSet2.txt:鏈接:https://pan.baidu.com/s/15RcuXEVm17a3aQwc0KXQcw 密碼:2lbh

構建散點圖

import matplotlib
import matplotlib.pyplot as plot
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:1],datingDataMat[:2])
plt.show()
"""

def autoNorm(dataSet):
    """
    歸一化特徵值,消除屬性之間量級不同導致的影響
    :param dataSet: 數據集
    :return: 歸一化後的數據集normDataSet,ranges和minVals即最小值與範圍,並沒有用到
    歸一化公式:
        Y = (X-Xmin)/(Xmax-Xmin)
        其中的 min 和 max 分別是數據集中的最小特徵值和最大特徵值。該函數可以自動將數字特徵值轉化爲0到1的區間。
    """
    # 計算每種屬性的最大值、最小值、範圍
    minVals = dataSet.min(0)
    maxVals = dataSet.max(0)
    # 極差
    ranges = maxVals - minVals
    # -------第一種實現方式---start-------------------------
    normDataSet = zeros(shape(dataSet))
    m = dataSet.shape[0]
    # 生成與最小值之差組成的矩陣
    normDataSet = dataSet - tile(minVals, (m, 1))
    # 將最小值之差除以範圍組成矩陣
    normDataSet = normDataSet / tile(ranges, (m, 1))  # element wise divide
    # -------第一種實現方式---end---------------------------------------------

    # # -------第二種實現方式---start---------------------------------------
    # norm_dataset = (dataset - minvalue) / ranges
    # # -------第二種實現方式---end---------------------------------------------
    return normDataSet, ranges, minVals


def datingClassTest():
    """
    對約會網站的測試方法
    :return: 錯誤數
    """
    # 設置測試數據的的一個比例(訓練數據集比例=1-hoRatio)
    hoRatio = 0.1  # 測試範圍,一部分測試一部分作爲樣本
    # 從文件中加載數據
    datingDataMat, datingLabels = file2matrix('input/2.KNN/datingTestSet2.txt')  # load data setfrom file
    # 歸一化數據
    normMat, ranges, minVals = autoNorm(datingDataMat)
    # m 表示數據的行數,即矩陣的第一維
    m = normMat.shape[0]
    # 設置測試的樣本數量, numTestVecs:m表示訓練樣本的數量
    numTestVecs = int(m * hoRatio)
    print 'numTestVecs=', numTestVecs
    errorCount = 0.0
    for i in range(numTestVecs):
        # 對數據測試
        classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 3)
        print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
        if (classifierResult != datingLabels[i]): errorCount += 1.0
    print "the total error rate is: %f" % (errorCount / float(numTestVecs))
    print errorCount


def img2vector(filename):
    """
    將圖像數據轉換爲向量
    :param filename: 圖片文件 因爲我們的輸入數據的圖片格式是 32 * 32的
    :return: 一維矩陣
    該函數將圖像轉換爲向量:該函數創建 1 * 1024 的NumPy數組,然後打開給定的文件,
    循環讀出文件的前32行,並將每行的頭32個字符值存儲在NumPy數組中,最後返回數組。
    """
    returnVect = zeros((1, 1024))
    fr = open(filename)
    for i in range(32):
        lineStr = fr.readline()
        for j in range(32):
            returnVect[0, 32 * i + j] = int(lineStr[j])
    return returnVect


def handwritingClassTest():
    # 1. 導入數據
    hwLabels = []
    trainingFileList = listdir('input/2.KNN/trainingDigits')  # load the training set
    m = len(trainingFileList)
    trainingMat = zeros((m, 1024))
    # hwLabels存儲0~9對應的index位置, trainingMat存放的每個位置對應的圖片向量
    for i in range(m):
        fileNameStr = trainingFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        hwLabels.append(classNumStr)
        # 將 32*32的矩陣->1*1024的矩陣
        trainingMat[i, :] = img2vector('input/2.KNN/trainingDigits/%s' % fileNameStr)

    # 2. 導入測試數據
    testFileList = listdir('input/2.KNN/testDigits')  # iterate through the test set
    errorCount = 0.0
    mTest = len(testFileList)
    for i in range(mTest):
        fileNameStr = testFileList[i]
        fileStr = fileNameStr.split('.')[0]  # take off .txt
        classNumStr = int(fileStr.split('_')[0])
        vectorUnderTest = img2vector('input/2.KNN/testDigits/%s' % fileNameStr)
        classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
        print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
        if (classifierResult != classNumStr): errorCount += 1.0
    print "\nthe total number of errors is: %d" % errorCount
    print "\nthe total error rate is: %f" % (errorCount / float(mTest))


if __name__ == '__main__': 
#相當於C程序的程序入口,如果把這以上代碼作爲一個模塊使用的話,在加載模塊的時候不會主動運行代碼內容,只有在你調用其中函數的時候纔會運行對應代碼!如果沒有這句話,加載模塊就會運行所有的代碼,輸出所有的結果!
可見參考:https://blog.csdn.net/qq_36501027/article/details/81192585
    # test1()
    # datingClassTest()
    handwritingClassTest()

參考文章:https://github.com/apachecn/AiLearning/blob/dev/blog/ml/2.k-%E8%BF%91%E9%82%BB%E7%AE%97%E6%B3%95.md
k-近鄰算法的實現方法主要是:線性掃描,但是這種方法當訓練集很大的時候就需要優化了,主要的解決辦法就是構建kd樹!
但是俺講不清楚,這有一篇文章講的很好:https://leileiluoluo.com/posts/kdtree-algorithm-and-implementation.html

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