1. 程式人生 > >K近鄰法之kd樹及其Python實現

K近鄰法之kd樹及其Python實現

作為機器學習中一種基本的分類方法,K近鄰(KNN)法是一種相對簡單的方法。其中一個理由是K近鄰法不需要對訓練集進行學習。然而,不需要對訓練集進行學習,反過來也會造成對測試集進行判定時,計算與空間複雜度的增加。

K近鄰法最簡單的實現方法是對需要分類的目標點,計算出訓練集中每一個點到其的距離(比較常用的有歐氏距離),然後選取K個距離目標最近的點,根據這些點的分類以多數表決的形式來決定目標點的分類。理論上該方法的時間複雜度為O(n)。當n的數量巨大時,時間開銷是巨大的。

kd樹是一種為了提高KNN方法效率的特殊資料結構,它的本質是二叉樹,每一個節點代表著對k維輸入空間上的某一位進行劃分的超平面。構造kd樹相當於不斷使用垂直於座標軸的超平面將K維空間劃分。

構建樹的過程中,依次按順序選擇k維空間的某一個特徵,在該特徵的座標軸上通過某個例項的特徵值確定超平面,該超平面垂直於選擇的座標軸,將當前的超矩形區域劃分為左右兩個子區域,此時輸入例項以選擇的特徵上選擇的點為界,被分在左右兩個超矩形區域中。重複以上過程直到子區域沒有例項為止。

下面以一個例子來說明kd樹的構建過程(例子來自《統計學習方法》-李航):
給定一個二維空間的資料集 T = {(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)}

首先選取k維空間的某一特徵(該例為第一個,理論上先選擇哪一個都是可行的,順序應該也是任意的,為了簡化描述過程,先選擇第一個特徵)。

所有資料集的第一個特徵為{2, 5, 9,4, 8, 7},然後選取某個特徵值來確定超平面。一般情況下選取中點(事實證明選取中點並不是在任何情況下都是最佳的,通過中點構建出的kd樹在搜素時未必效率最高,例如在訓練集中兩個類別之間的相對距離較遠,且某一類的數量遠大於另一類的數量)(偶數個的情況下可以選靠後的點)。該例中中點值為7.以平面x(1) = 7為超平面,將空間分為左右兩部分(左空間小於中點值,右空間大於中點值),左空間點(2,3)(4,7)(5,4),再以第二個特徵值畫超平面,此時第二個特徵值的中點為4,以x(2)= 4為超平面再次劃分左半平面,以此類推。最終得到的劃分如下。

kd樹如下

kd樹構建完畢後,利用kd樹進行k近鄰搜尋。在kd樹上進行近鄰搜尋時,很多時候可以不進入某個父節點的另一個子節點(省去了另一個子節點資料點的查詢)。kd樹查詢的具體演算法如下:

演算法輸入:構造完畢的kd樹,需要分類的目標點x

演算法輸出:目標點x的k近鄰點

演算法過程:

1通過深度優先方法,在kd樹中搜索到目標點的所在的葉節點。(注:該搜尋並不能直接找到最近鄰點)搜尋方法如下,在搜尋每一層的過程中,根據該層的分割特徵的序數,來對目標點的該序數的特徵進行分類(決定是進入左子節點還是右子節點)。如目標點為(6,1),在根節點,分割特徵為x(1),目標點的x(1)為 6,6小於根節點的x(1)= 7,進入左子節點。第二層的分割特徵為x(2),目標點的x(2)為1, 1小於子節點的x(2)= 4,進入左子節點,最終得到葉節點(2,3)。

2 以該葉節點作為當前的NN(最近鄰)點,計算該葉節點與目標點的距離,並設為當前的最小距離。

3 計算該葉節點父節點與目標點的距離,若小於當前的最小距離,則更新當前的最小距離以及當前的NN點(被覆蓋的點先記錄下來)

4 判斷是否要進入父節點的另一個子節點:

判斷方法為:計算父節點在其分割特徵上的值距離目標點在該特徵上的值的距離,若該距離小於當前的最小距離,則進入另一個子節點,否則不進入。

既:檢查另一子節點對應的區域是否與以目標點為球心,以目標點與當前的NN點的距離為半徑的球體相交。若相交則進入,反正不進入。

a)若不進入另一個子節點,則以此父節點視為葉節點,重複步驟3。

b)若進入另一個子節點,則對右邊節點以下的子樹執行步驟1,找到新的葉節點。判斷是否要更新NN點與當前最小距離。隨後以該葉節點開始,重複步驟3。

以此類推,搜尋過程中將不斷向跟節點回退。在向根節點回退的過程中,不必再次進入從中退出的子節點,保證過程不會進入死迴圈。

5 當回退到根節點時(且以根節點與目標點的距離來更新最小距離與NN點後),最後的NN點即為x的最近鄰點。且記錄下來的所有NN點,對應的距離,最小的K個即為K近鄰點。

下面以Python來實現kd樹的生成及搜尋。

##Generate KD tree
def createTree(dataSet, layer = 0, feature = 2):
    length = len(dataSet)
    dataSetCopy = dataSet[:]
    featureNum = layer % feature
    dataSetCopy.sort(key = lambda x:x[featureNum])
    layer += 1
    if length == 0:
        return None
    elif length == 1:
        return {'Value':dataSet[0], 'Layer':layer, 'feature':featureNum,'Left':None, 'Right':None}
    elif length != 1:
        midNum = length // 2
        dataSetLeft = dataSetCopy[:midNum]
        dataSetRight = dataSetCopy[midNum+1:]
        return {'Value':dataSetCopy[midNum], 'Layer':layer, 'feature':featureNum, 'Left':createTree(dataSetLeft, layer)
                , 'Right':createTree(dataSetRight, layer)}
該部分為生成kd樹的函式。生成的樹以字典巢狀字典的形式表示。(每個節點有‘Left’和‘Right’兩個鍵,裡面的值為該分支下的子樹。有“Layer”鍵表示節點在樹的第幾層,“feature”表示該節點分割使用的是例項的第幾個特徵(由於python的特性,程式中的0表示x(1)))。

具體思路:當該函式的輸入僅有一個例項時,返回一個左右子節點均為None的節點(既表示這是一個葉節點),若不僅有一個例項,則根據層數和相應的特徵,將輸入的例項分割成兩個部分,該節點的左子節點為,遞迴本函式的返回值(輸入為例項分割的左半部分),右子節點為輸入為例項分割右半部的本函式的遞迴返回值。

以上面的例子生成的子樹如下:

{'Value': (7, 2), 'Layer': 1, 'feature': 0, 'Left': {'Value': (5, 4), 'Layer': 2, 'feature': 1, 'Left': {'Value': (2, 3), 'Layer': 3, 'feature': 0, 'Left': None, 'Right': None}, 'Right': {'Value': (4, 7), 'Layer': 3, 'feature': 0, 'Left': None, 'Right': None}}, 'Right': {'Value': (9, 6), 'Layer': 2, 'feature': 1, 'Left': {'Value': (8, 1), 'Layer': 3, 'feature': 0, 'Left': None, 'Right': None}, 'Right': None}}

#calculate distance
def calDistance(sourcePoint, targetPoint):
    length = len(targetPoint)
    sum = 0.0
    for i in range(length):
        sum += (sourcePoint[i] - targetPoint[i])**2
    sum = sqrt(sum)
    return sum


#A function use to find a point in KDtree
def findPoint(Tree, value):
    if  Tree !=None and Tree['Value'] == value:
        return Tree
    else:
        if Tree['Left'] != None:
            return findPoint(Tree['Left'], value) or findPoint(Tree['Right'], value)
以上兩個函式分別為距離計算函式(歐氏距離),以及根據節點的值,在kd樹中找到對應的節點的函式(包括值,層,分割特徵及左右子節點,準確的說已經不止是該節點,而是一顆子樹)。
#DFS algorithm
def dfs(kdTree, target,tracklist = []):
    tracklistCopy = tracklist[:]
    if not kdTree:
        return None, tracklistCopy
    elif not kdTree['Left']:
        tracklistCopy.append(kdTree['Value'])
        return kdTree['Value'], tracklistCopy
    elif kdTree['Left']:
        pointValue = kdTree['Value']
        feature = kdTree['feature']
        tracklistCopy.append(pointValue)
        #return kdTree['Value'], tracklistCopy
        if target[feature] <= pointValue[feature]:
            return dfs(kdTree['Left'], target, tracklistCopy)
        elif target[feature] > pointValue[feature]:
            return dfs(kdTree['Right'], target, tracklistCopy)
該函式為kd搜尋的一部分,既深度優先演算法部分。函式的返回值為最終找到的葉節點的值,以及從根節點到葉節點的跟蹤路徑(列表結構)。由於在本程式中,通過子節點去搜索父節點的複雜度極高,所以通過跟蹤路徑的方式來找尋父節點。在跟蹤路徑中,每一個值的上一個值,均為該值對應的節點的父節點。
#KDtree search algorithm
def kdTreeSearch(tracklist, target , usedPoint = [] , minDistance = float('inf'), minDistancePoint = None):
    tracklistCopy = tracklist[:]
    usedPointCopy = usedPoint[:]

    if not minDistancePoint:
        minDistancePoint = tracklistCopy[-1]

    if len(tracklistCopy) == 1:
        return minDistancePoint
    else:
        point = findPoint(kdTree, tracklist[-1])

        if calDistance(point['Value'], target) < minDistance:
            minDistance = calDistance(point['Value'], target)
            minDistancePoint = point['Value']
        fatherPoint = findPoint(kdTree, tracklistCopy[-2])
        fatherPointval = fatherPoint['Value']
        fatherPointfea = fatherPoint['feature']

        if calDistance(fatherPoint['Value'], target) < minDistance:
            minDistance = calDistance(fatherPoint['Value'], target)
            minDistancePoint = fatherPoint['Value']

        if point == fatherPoint['Left']:
            anotherPoint = fatherPoint['Right']
        elif point == fatherPoint['Right']:
            anotherPoint = fatherPoint['Left']

        if (anotherPoint == None or anotherPoint['Value'] in usedPointCopy or
            abs(fatherPointval[fatherPointfea] - target[fatherPointfea]) > minDistance):
            usedPoint = tracklistCopy.pop()
            usedPointCopy.append(usedPoint)
            return kdTreeSearch(tracklistCopy, target, usedPointCopy, minDistance, minDistancePoint)
        else:
            usedPoint = tracklistCopy.pop()
            usedPointCopy.append(usedPoint)
            subvalue, subtrackList = dfs(anotherPoint, target)
            tracklistCopy.extend(subtrackList)
            return kdTreeSearch(tracklistCopy, target, usedPointCopy, minDistance, minDistancePoint)
該部分為搜尋函式的剩餘部分,主要思想還是遞迴。函式的返回值為最近鄰點。若需要K近鄰點可以建立一個列表儲存每一個曾出現過的NN點,選出距離最小的K個即可。

完整程式如下:

from math import sqrt
from random import randint


##Generate KD tree
def createTree(dataSet, layer = 0, feature = 2):
    length = len(dataSet)
    dataSetCopy = dataSet[:]
    featureNum = layer % feature
    dataSetCopy.sort(key = lambda x:x[featureNum])
    layer += 1
    if length == 0:
        return None
    elif length == 1:
        return {'Value':dataSet[0], 'Layer':layer, 'feature':featureNum,'Left':None, 'Right':None}
    elif length != 1:
        midNum = length // 2
        dataSetLeft = dataSetCopy[:midNum]
        dataSetRight = dataSetCopy[midNum+1:]
        return {'Value':dataSetCopy[midNum], 'Layer':layer, 'feature':featureNum, 'Left':createTree(dataSetLeft, layer)
                , 'Right':createTree(dataSetRight, layer)}


#calculate distance
def calDistance(sourcePoint, targetPoint):
    length = len(targetPoint)
    sum = 0.0
    for i in range(length):
        sum += (sourcePoint[i] - targetPoint[i])**2
    sum = sqrt(sum)
    return sum

#DFS algorithm
def dfs(kdTree, target,tracklist = []):
    tracklistCopy = tracklist[:]
    if not kdTree:
        return None, tracklistCopy
    elif not kdTree['Left']:
        tracklistCopy.append(kdTree['Value'])
        return kdTree['Value'], tracklistCopy
    elif kdTree['Left']:
        pointValue = kdTree['Value']
        feature = kdTree['feature']
        tracklistCopy.append(pointValue)
        #return kdTree['Value'], tracklistCopy
        if target[feature] <= pointValue[feature]:
            return dfs(kdTree['Left'], target, tracklistCopy)
        elif target[feature] > pointValue[feature]:
            return dfs(kdTree['Right'], target, tracklistCopy)

#A function use to find a point in KDtree
def findPoint(Tree, value):
    if  Tree !=None and Tree['Value'] == value:
        return Tree
    else:
        if Tree['Left'] != None:
            return findPoint(Tree['Left'], value) or findPoint(Tree['Right'], value)

#KDtree search algorithm
def kdTreeSearch(tracklist, target , usedPoint = [] , minDistance = float('inf'), minDistancePoint = None):
    tracklistCopy = tracklist[:]
    usedPointCopy = usedPoint[:]

    if not minDistancePoint:
        minDistancePoint = tracklistCopy[-1]

    if len(tracklistCopy) == 1:
        return minDistancePoint
    else:
        point = findPoint(kdTree, tracklist[-1])

        if calDistance(point['Value'], target) < minDistance:
            minDistance = calDistance(point['Value'], target)
            minDistancePoint = point['Value']
        fatherPoint = findPoint(kdTree, tracklistCopy[-2])
        fatherPointval = fatherPoint['Value']
        fatherPointfea = fatherPoint['feature']

        if calDistance(fatherPoint['Value'], target) < minDistance:
            minDistance = calDistance(fatherPoint['Value'], target)
            minDistancePoint = fatherPoint['Value']

        if point == fatherPoint['Left']:
            anotherPoint = fatherPoint['Right']
        elif point == fatherPoint['Right']:
            anotherPoint = fatherPoint['Left']

        if (anotherPoint == None or anotherPoint['Value'] in usedPointCopy or
            abs(fatherPointval[fatherPointfea] - target[fatherPointfea]) > minDistance):
            usedPoint = tracklistCopy.pop()
            usedPointCopy.append(usedPoint)
            return kdTreeSearch(tracklistCopy, target, usedPointCopy, minDistance, minDistancePoint)
        else:
            usedPoint = tracklistCopy.pop()
            usedPointCopy.append(usedPoint)
            subvalue, subtrackList = dfs(anotherPoint, target)
            tracklistCopy.extend(subtrackList)
            return kdTreeSearch(tracklistCopy, target, usedPointCopy, minDistance, minDistancePoint)

trainingSet = [(2, 3), (5, 4), (9, 6), (4, 7), (8, 1), (7, 2)]
kdTree = createTree(trainingSet)
target = eval(input('Input target point:'))
value, trackList = dfs(kdTree, target)
nnPoint = kdTreeSearch(trackList, target)
print(nnPoint)

輸入目標點,即可返回最近鄰點的值。

該程式仍有大量不足之處,其中一點即為kd書的儲存結構並不是特別的優秀。每次在使用的時候也都需要使用查詢函式去找到value對應的節點,才能獲得節點的各種屬性。這大大增加了時間複雜度。其次,當資料量極大的時候,遞迴的層數過深,空間的複雜度也是很高的。

相關推薦

K近鄰kd及其Python實現

作為機器學習中一種基本的分類方法,K近鄰(KNN)法是一種相對簡單的方法。其中一個理由是K近鄰法不需要對訓練集進行學習。然而,不需要對訓練集進行學習,反過來也會造成對測試集進行判定時,計算與空間複雜度的增加。 K近鄰法最簡單的實現方法是對需要分類的目標點,計算出訓練集中每一

K近鄰(KNN)演算法、KD及其python實現

1、k近鄰演算法 1.1 KNN基本思想 k近鄰法是基本且簡單的分類與迴歸方法,即對於輸入例項,依據給定的距離度量方式(歐式距離),以及選擇合適的k值(交叉驗證),在樣本集中找到最近鄰新例項的k個樣例,通過k個最近鄰樣例的類別表決出新例項的類別(多數表決)。

統計學習筆記——k近鄰kd

在使用k近鄰法進行分類時,對新的例項,根據其k個最近鄰的訓練例項的類別,通過多數表決的方式進行預測。由於k近鄰模型的特徵空間一般是n維實數向量,所以距離的計算通常採用的是歐式距離。關鍵的是k值的選取,如果k值太小就意味著整體模型變得複雜,容易發生過擬合,即如果鄰近的例項點

機器學習實戰——K-近鄰KD

機器學習實戰專輯part2——K-近鄰法@[適合初學小白超詳細!] 前段時間忙小論文和專利,寫部落格耽擱了好久,終於終於有時間可以繼續寫了,雖然沒記錄在部落格上,但是學習依然沒有鬆懈,廢話不多說,今天歡迎我們的主角登場,K-近鄰法。 一、K-近鄰法基本概念 K-近鄰法也叫KNN是

第三章總結 K近鄰kd

本文 參考自李航博士的《統計學習方法》 為自我理解的簡化版本 3.1 K近鄰演算法 給定一個訓練資料集,對新的輸入例項,在訓練資料集中找到與該例項最鄰近的k個例項,這k個例項的多數屬於某個類,就把該輸入例項分為這個類。 y=argmax∑x

【統計學習方法】k近鄰 kdpython實現

前言 程式碼可在Github上下載:程式碼下載 k近鄰可以算是機器學習中易於理解、實現的一個演算法了,《機器學習實戰》的第一章便是以它作為介紹來入門。而k近鄰的演算法可以簡述為通過遍歷資料集的每個樣本進行距離測量,並找出距離最小的k個點。但是這樣一來一旦樣本數目龐大的時候

機器學習:K近鄰演算法,kd

https://www.cnblogs.com/eyeszjwang/articles/2429382.html kd樹詳解 https://blog.csdn.net/v_JULY_v/article/details/8203674 一、K-近鄰演算法(KNN)概述 

K近鄰演算法(KNN)原理解析及python實現程式碼

KNN演算法是一個有監督的演算法,也就是樣本是有標籤的。KNN可以用於分類,也可以用於迴歸。這裡主要講knn在分類上的原理。KNN的原理很簡單:            放入一個待分類的樣本,使用者指定k的大小,然後計算所有訓練樣本與該樣

機器學習決策演算法python實現

一. 理論基礎 1. 特徵選擇 a. 資訊熵 H(D)=−∑i=0kpilogpi b. 條件熵 H(Y|X)=∑i=0npiH(Y|X=xi) c. 資訊增益 I(D,A)=H(D)−H(D|A) d. 資訊增益比

機器學習入門決策python實現

本次學習利用MT_Train.csv中所給的資料對MT_Test.csv中的資料進行預測,判斷客戶是否會定期存款。根據所學知識,可採用sklearn中的決策樹等方法進行程式設計。歡迎大家一起討論學習進步。 訓練集和測試集連結如下: 一. 設計思路 1.讀取訓練集和測試集檔

統計學習方法筆記(一):K近鄰實現kd

  實現k近鄰演算法時,首要考慮的問題是如何對訓練資料進行快速的k近鄰搜尋。這點在特徵空間的維數大於訓練資料容量時尤為重要。 構造kd樹   kd 樹是一種對k為空間中的例項點進行儲存的一邊對其進行快速檢索的樹形資料結構。kd樹是二叉樹,表示對k維空間的一個劃分(parti

機器學習基礎(四十三)—— kd k 近鄰實現

實現 k 近鄰法時,主要考慮的問題是如何對訓練資料進行快速 k 近鄰搜尋,這點在如下的兩種情況時,顯得尤為必要: (1)特徵空間的維度大 (2)訓練資料的容量很大時 k 近鄰法的最簡單的實現是現行掃描(linear scan),這時需計算輸入例項與每一個

近鄰k-近鄰 KD

最近鄰法和k-近鄰法   下面圖片中只有三種豆,有三個豆是未知的種類,如何判定他們的種類?   提供一種思路,即:未知的豆離哪種豆最近就認為未知豆和該豆是同一種類。由此,我們引出最近鄰演算法的定義:為了判定未知樣本的類別,以全部訓練樣本作為代表點,

統計學習方法c++實現二 k近鄰

統計學習方法c++實現之二 k近鄰演算法 前言 k近鄰演算法可以說概念上很簡單,即:“給定一個訓練資料集,對新的輸入例項,在訓練資料集中找到與這個例項最鄰近的k個例項,這k個例項的多數屬於某個類,就把該輸入分為這個類。”其中我認為距離度量最關鍵,但是距離度量的方法也很簡單,最長用的就是歐氏距離,其他的距離

統計學習筆記K近鄰

K近鄰作為基本的分類和迴歸方法。在分類中,對新的例項,根據k個最近鄰得訓練例項的類別,通過多數表決進行預測。 一、演算法 輸入:,為例項的特徵向量,為例項的類別。 輸出:例項的的所屬的類y。 (1)根據給定距離度量,在訓練集中找出與最近鄰的k個點,涵蓋這k個點的x

SparkMLlib分類算決策學習

2.3 數據預處理 true ray score 嚴重 acc 標準化 lambda SparkMLlib分類算法之決策樹學習 (一) 決策樹的基本概念     決策樹(Decision Tree)是在已知各種情況發生概率的基礎上,通過構成決策樹來求取凈現值的期望值大於等於

SparkMLlib回歸算決策

ria 之間 feature 輸入 修改 決策樹算法 技術 color 實例 SparkMLlib回歸算法之決策樹 (一),決策樹概念 1,決策樹算法(ID3,C4.5 ,CART)之間的比較:   1,ID3算法在選擇根節點和各內部節點中的分支屬性時,采用信息增益作為評價

Fuzzy C Means 算及其 Python 實現——寫得很清楚,見原文

少包 均值 平均值 劃分 gin 及其 end 5% 指數 Fuzzy C Means 算法及其 Python 實現 轉自:http://note4code.com/2015/04/14/fuzzy-c-means-%E7%AE%97%E6%B3%95%E5%8F%8A%E

K近鄰

數據集 量化 學習過程 要求 過程 nbsp k近鄰 實例 數據   K近鄰法是機器學習所有算法中理論最簡單,最好理解的算法。它是一種基本的分類與回歸方法,它的輸入為實例的特征向量,通過計算新數據與訓練數據特征值之間的距離,然後選取K(K>=1)個距離最近的鄰居進行分

(轉)梯度下降及其Python實現

radi 減少 fill 叠代 bbs 方法 風險 ews 展示 梯度下降法(gradient descent),又名最速下降法(steepest descent)是求解無約束最優化問題最常用的方法,它是一種叠代方法,每一步主要的操作是求解目標函數的梯度向量,將當前位置的負