天天看點

03 k近鄰法——Python實作

一、求最近鄰點:

  1. 題目:
    03 k近鄰法——Python實作
    T = { ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 7 ) T , ( 8 , 1 ) T , ( 7 , 2 ) T } T=\begin{Bmatrix} (2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T \end{Bmatrix} T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T​}
  2. Python代碼:
# author yuxy
#方法一:采用坐标軸輪換方式搜尋
import numpy as np
class Node:
    """節點類:用來表示kd樹"""
    def __init__(self, data, left_node, right_node):
        self.data=data
        self.left_tree=left_node
        self.right_tree=right_node
class kdTree():
    """kd樹類:實作kd樹建立、kd樹搜尋"""
    def __init__(self, dataSet, depth):
        self.dataSet=dataSet
        self.depth=depth

    def create_tree(self, dataSet, depth):
        """kd樹建立方法:采用遞歸的方式"""
        if len(dataSet)>0:
            m,n=np.shape(dataSet)  #m行,n列
            middleIndex=int(m / 2)
            axis=depth % n
            sortedData=sorted(dataSet, key=lambda x:x[axis])
            node=Node(sortedData[middleIndex],None,None)
            left_data=sortedData[: middleIndex]
            right_data=sortedData[middleIndex+1 :]
            node.left_node=self.create_tree(left_data,depth+1)
            node.right_node=self.create_tree(right_data, depth+1)
            return node
        else:
            return None
    def show_tree(self, node):
        """輸出樹的節點資訊,采用遞歸的方法"""
        if node != None:
            print("---%s" % node.data)
            print('left_node:')
            self.show_tree(node.left_node)
            print('right_node:')
            self.show_tree(node.right_node)
        else:
            print("None")

    def search_tree(self, kd_tree, x):
        """從kd樹中搜尋x的最近鄰點"""
        self.resultNode=None
        self.minDistance=0
        def search(tree_node, depth=0):
            if tree_node!=None:
                n=len(x)
                axis=depth % n
                if x[axis]<tree_node.data[axis]:
                    search(tree_node.left_node, depth+1)
                else:
                    search(tree_node.right_node, depth+1)
                # 回溯,因為遞歸本身和棧的實作原理相同,先進後出,當葉子節點左右節點都為None時,就依次回溯,輸出父節點
                distance=self.get_distance(x, tree_node.data)
                if self.resultNode==None:
                    self.resultNode=tree_node
                    self.minDistance=distance
                elif distance < self.minDistance:
                    self.resultNode = tree_node
                    self.minDistance = distance
                print(tree_node.data, self.minDistance, x[axis], tree_node.data[axis])

                # 判斷另一個子區域中是否存在更近鄰點
                if self.minDistance>(abs(tree_node.data[axis]-x[axis])):
                    if x[axis]< tree_node.data[axis]:
                        search(tree_node.right_node, depth + 1)
                    else:
                        search(tree_node.left_node, depth + 1)
        search(kd_tree)
        return self.resultNode.data


    def get_distance(self, x, y):
        """擷取x,y之間的歐氏距離"""
        dis=((np.array(x)-np.array(y))**2).sum()**0.5
        return dis


if __name__=='__main__':
    tree_data = [[2, 3], [7, 2], [5, 4], [4, 7], [9, 6], [8, 1]]
    depth=0
    my_tree=kdTree(tree_data, depth)
    my_node=my_tree.create_tree(tree_data, depth)
    # my_tree.show_tree(my_node)
    x=[3,4.5]
    print('x[3,4.5]的最近鄰點為:%s' % my_tree.search_tree(my_node, x))

           

結果圖:

03 k近鄰法——Python實作
# author yuxy
#方法二:采用最大方差劃分實作最近鄰
import numpy as np
class Node:
    """節點類:用來表示kd樹"""
    def __init__(self, data, left_node, right_node):
        self.data=data
        self.left_tree=left_node
        self.right_tree=right_node
class kdTree():
    """kd樹類:實作kd樹建立、kd樹搜尋"""
    def __init__(self, dataSet, depth):
        self.dataSet=dataSet
        self.depth=depth

    def create_tree(self, dataSet, depth):
        """kd樹建立方法:采用遞歸的方式"""
        if len(dataSet)>0:
            m,n=np.shape(dataSet)  #m行,n列
            middleIndex=int(m / 2)
            # axis=depth % n
            axis=self.variance(dataSet)
            sortedData=sorted(dataSet, key=lambda x:x[axis])
            node=Node(sortedData[middleIndex],None,None)
            left_data=sortedData[: middleIndex]
            right_data=sortedData[middleIndex+1 :]
            node.left_node=self.create_tree(left_data,depth+1)
            node.right_node=self.create_tree(right_data, depth+1)
            return node
        else:
            return None
    def show_tree(self, node):
        """輸出樹的節點資訊,采用遞歸的方法"""
        if node != None:
            print("---%s" % node.data)
            print('left_node:')
            self.show_tree(node.left_node)
            print('right_node:')
            self.show_tree(node.right_node)
        else:
            print("None")

    def search_tree(self, kd_tree, dataSet, x):
        """從kd樹中搜尋x的最近鄰點"""
        self.resultNode=None
        self.minDistance=0
        def search(tree_node, dataSet, depth=0):
            if tree_node!=None:
                m, n = np.shape(dataSet)  # m行,n列
                middleIndex = int(m / 2)
                axis = self.variance(dataSet)
                sortedData = sorted(dataSet, key=lambda x: x[axis])
                left_data = sortedData[: middleIndex]
                right_data = sortedData[middleIndex + 1:]

                if x[axis]<tree_node.data[axis]:
                    search(tree_node.left_node, left_data, depth+1)
                else:
                    search(tree_node.right_node, right_data, depth+1)
                # 回溯,因為遞歸本身和棧的實作原理相同,先進後出,當葉子節點左右節點都為None時,就依次回溯,輸出父節點
                distance=self.get_distance(x, tree_node.data)
                if self.resultNode==None:
                    self.resultNode=tree_node
                    self.minDistance=distance
                elif distance < self.minDistance:
                    self.resultNode = tree_node
                    self.minDistance = distance
                print(tree_node.data, self.minDistance, x[axis], tree_node.data[axis])

                # 判斷另一個子區域中是否存在更近鄰點
                if self.minDistance>(abs(tree_node.data[axis]-x[axis])):
                    if x[axis]< tree_node.data[axis]:
                        search(tree_node.right_node, right_data,depth + 1)
                    else:
                        search(tree_node.left_node, left_data, depth + 1)
        search(kd_tree, dataSet)
        return self.resultNode.data


    def get_distance(self, x, y):
        """擷取x,y之間的歐氏距離"""
        dis=((np.array(x)-np.array(y))**2).sum()**0.5
        return dis

    def variance(self, xList):
        """計算二維數組中每維的方差"""
        x=0
        y=1
        coordinate=x
        data_x=[]
        data_y=[]
        i=0
        while i < len(xList):
            data_x.append(xList[i][0])
            data_y.append(xList[i][1])
            i=i+1
        #計算方差
        avg_x=self.get_average(data_x)
        avg_y=self.get_average(data_y)
        #計算方差
        j=0
        va_x=0
        va_y=0
        while j < len(xList):
            va_x=va_x+(abs(data_x[j] - avg_x) ** 2)
            va_y= va_y + (abs(data_y[j] - avg_y) ** 2)
            j = j+1
        if va_x > va_y:
            coordinate=x
        else:
            coordinate=y
        return coordinate

    def get_average(self, data):
        """計算數組元素的平均值"""
        i=0
        sum=0
        while i < len(data):
            sum=sum+data[i]
            i=i+1
        avg=(1.0 * sum) / len(data)
        return avg

if __name__=='__main__':
    tree_data = [[2, 3], [7, 2], [5, 4], [4, 7], [9, 6], [8, 1]]
    depth=0
    my_tree=kdTree(tree_data, depth)
    my_node=my_tree.create_tree(tree_data, depth)
    # my_tree.show_tree(my_node)
    x=[3,4.5]
    print('x[3,4.5]的最近鄰點為:%s' % my_tree.search_tree(my_node, tree_data, x))
    # print(my_node.data)
           

二、求k近鄰點:

  1. 題目同上,查找 x = [ 3 , 4.5 ] x=[3,4.5] x=[3,4.5] 的 2 2 2 近鄰點。
  2. 算法思想:

    輸入:已構造的kdkdkd樹,目标點xxx.

    輸出:x的k近鄰.

    (1)在 kd 樹中找出包含目标點 x 的葉結點:從根結點出發,遞歸地向下通路樹。若目标點 x目前維的坐标小于切分點的坐标,則移動到左子結點,否則移動到右子結點,直到子結點為葉結點為止;

    (2)如果 “目前 K近鄰點集” 元素數量小于K或者葉節點距離小于 “目前 K近鄰點集” 中最遠點距離,那麼将葉節點插入 “目前 K近鄰點集”;

    (3)遞歸地向上回退,在每個結點進行以下操作:

    (a) 如果 “目前 K近鄰點集” 元素數量小于K或者目前節點距離小于 “目前 K近鄰點集” 中最遠點距離,那麼将該節點插入 “目前 K近鄰點集”;

    (b) 檢查另一子結點對應的區域是否與以目标點為球心、以目标點與于 “目前 K近鄰點集” 中最遠點間的距離為半徑的超球體相交。

    如果相交,可能在另一個子結點對應的區域記憶體在距目标點更近的點,移動到另一個子結點。接着,遞歸地進行最近鄰搜尋;

    如果不相交,向上回退;

    (4)當回退到根結點時,搜尋結束。最後的 “目前 K近鄰點集” 即為 x 的 K 近鄰點集。

  3. Python代碼:
# author yuxy
# 求k近鄰點
import numpy as np
class Node:
    """節點類:用來表示kd樹"""
    def __init__(self, data, left_node, right_node):
        self.data=data
        self.left_tree=left_node
        self.right_tree=right_node
class kdTree():
    """kd樹類:實作kd樹建立、kd樹搜尋"""
    def __init__(self, dataSet, depth):
        self.dataSet=dataSet
        self.depth=depth

    def create_tree(self, dataSet, depth):
        """kd樹建立方法:采用遞歸的方式"""
        if len(dataSet)>0:
            m,n=np.shape(dataSet)  #m行,n列
            middleIndex=int(m / 2)
            axis=depth % n
            sortedData=sorted(dataSet, key=lambda x:x[axis])
            node=Node(sortedData[middleIndex],None,None)
            left_data=sortedData[: middleIndex]
            right_data=sortedData[middleIndex+1 :]
            node.left_node=self.create_tree(left_data,depth+1)
            node.right_node=self.create_tree(right_data, depth+1)
            return node
        else:
            return None

    def search_knn(self, dataSet, x, k, depth):
        """通過查找kd樹,查找k個近鄰點"""
        self.listNode=[]        #記錄k近鄰點序列
        self.length=[]          #記錄數組中距離目标點x的最遠距離
        self.maxLength=[0,0]    #0:最大值下标, 1:最大值
        self.index=1
        self.init=1
        def search(node_tree, k, depth=0):
            if node_tree!=None:
                dimension=len(x)
                axis=depth % dimension
                if x[axis]< node_tree.data[axis]:
                    #移動到左子節點
                    search(node_tree.left_node, k, depth+1)
                else:
                    search(node_tree.right_node, k, depth+1)
                #回溯,退棧得到葉子節點、根節點
                distance = self.get_distance(x, node_tree.data)
                if self.init == 1:  #第一次
                    self.listNode.append(node_tree.data)
                    self.length.append(distance)
                    self.maxLength[1]=distance
                    self.maxLength[0]=self.index
                    self.index=self.index+1
                    self.init=self.init+1
                elif len(self.listNode)< k:
                    self.listNode.append(node_tree.data)
                    self.length.append(distance)
                    self.maxLength[1]=self.get_maxDistance(self.length)[1] #最大長度
                    self.maxLength[0]=self.get_maxDistance(self.length)[0] #最大長度的下标
                    self.index = self.index + 1
                elif distance < self.maxLength[1]:  #length數組已經滿了,需要根據長度值更新數組
                    self.listNode[self.maxLength[0]]=node_tree.data
                    self.length[self.maxLength[0]] = distance
                    self.maxLength[1] = self.get_maxDistance(self.length)[1]  # 最大長度
                    self.maxLength[0] = self.get_maxDistance(self.length)[0]  # 最大長度的下标

                #進行遞歸操作了
                # i=0
                # while i < len(self.listNode):
                print('listNode:%s length=%s' % (self.listNode, self.length))
                    # i=i+1
                if abs(x[axis]-node_tree.data[axis])< self.maxLength[1]:
                    if x[axis]< node_tree.data[axis]:
                        search(node_tree.right_node, k, depth+1)
                    else:
                        search(node_tree.left_node, k, depth + 1)
        search(dataSet, k)
        return self.listNode

    def get_maxDistance(self, length_list):
        """擷取最大距離和最大距離的下标"""
        i=0
        max=0
        index=0
        while i < len(length_list):
            if length_list[i]> max:
                max=length_list[i]
                index=i
            i=i+1
        return [index, max]


    def get_distance(self, x, y):
        """擷取x和y之間的歐氏距離"""
        dis=((np.array(x)-np.array(y))**2).sum() **0.5
        return dis

    def show_tree(self, node):
        """輸出樹的節點資訊,采用遞歸的方法"""
        if node != None:
            print("---%s" % node.data)
            print('left_node:')
            self.show_tree(node.left_node)
            print('right_node:')
            self.show_tree(node.right_node)
        else:
            print("None")

if __name__=='__main__':
    tree_data = [[2, 3], [7, 2], [5, 4], [4, 7], [9, 6], [8, 1]]
    depth=0
    my_tree=kdTree(tree_data, depth)
    my_node=my_tree.create_tree(tree_data, depth)
    # my_tree.show_tree(my_node)
    x=[3,4.5]
    k=2
    print('x[3,4.5]的 %i 近鄰點為:%s' % (k, my_tree.search_knn(my_node, x, k, depth)))
           
  1. 運作結果:
    03 k近鄰法——Python實作
    【如若有問題,請留言交流!】

繼續閱讀