KNN KD树原理及代码实现

K近邻法

KNN是机器学习的基本算法,也是原理最简单的算法之一,既可以做分类也可以做回归,作为惰性学习算法,KNN不产生模型,算法的原理也很简单,现有一个数据集,里面很有多个有标签的样本,这些样本的共有的特征构成了一个多维空间,当有一个需要预测的样本出现时,我们把这个样本放入到多维空间中,找到离这个样本点最近的k个样本,这些样本叫做最近邻,我们采用少数服从多数,一点一票的原理来判断,在最近邻中最多标签的类别就是这个样本点的标签类别,越相近越相似

算法解析

KNN KD树原理及代码实现_第1张图片
上图是书中给出的算法流程,很好理解,根据距离公式在训练集中找到了与我们需要给出预测的样本点x距离最近的k个点,涵盖这k个点的x的邻域记作N
在使用多数表决的方式来确定预测点的类别,式3.1表示对于每一个数据集的标签Cj,进行I(yi=Cj)求和,返回数目最多的标签类别,就是我们需要预测的样本点x的类别。

距离公式

KNN KD树原理及代码实现_第2张图片

KD树

由于传统KNN算法需要将待预测样本点x与所有样本计算距离,这在数据集较大的情况下非常难办,因为要计算的量实在是太多了,所以就提出了KD树,KD树是二叉树,表示了对k维空间的一个划分,构造KD树的流程就是不断地用垂直于坐标轴的超平面把k维空间切分,构造成了一系列k维超矩阵区域,KD树的每一个节点对应了一个超矩阵区域。KNN KD树原理及代码实现_第3张图片
算法的流程书中介绍的很详细,唯一有出入的就是在切分数据集时坐标轴选择的方式,本文根据样本点每一个特征的数据集的方差大小来选择切分的维度,选择方差大的特征来切分,这样可以提高效率。

搜索KD树

KNN KD树原理及代码实现_第4张图片KNN KD树原理及代码实现_第5张图片
流程如下 根据需要预测的样本点x,在构建好的KD树中从根节点开始找到与x距离最小的叶子节点,将该叶子节点作为暂时的最近点,开始回退,首先会退到该叶子节点的父节点,检查以最近点与样本点x为半径的超球体,是否与该叶子节点的兄弟节点的区域相交,如果相交,则有可能存在更近点,就找到叶子节点的兄弟节点,计算距离是否比最近点要小,如果比最近点要小,则更新最近点;如果不相交,则再回退上一级父节点,如果更新之后的超球体与上一级父节点的另一个子节点的区域相交,则在该子节点的区域内寻找是否有距离更小的点,以此类推。

代码

代码包含了传统的KNN分类器和KD树的建立方法

import numpy as np
import pandas as pd

def KNN(train,test,k):
    m,n=train.shape
    m1,n1=test.shape
    result=[]
    for i in range(m1):
        dist=((train.iloc[:,:-1]-test.iloc[i,:-1])**2).sum(axis=1)
        dist1=pd.DataFrame({'dist':dist,'labels':(train.iloc[:,-1])})
        d2=dist1.sort_values(by='dist')[:k]
        d3=d2.loc[:,'labels'].value_counts()
        result.append(d3.index[0])
    result=pd.Series(result)
    test['predict']=result
    return test

class KD_node:
    def __init__(self, point=None, split=None, LL = None, RR = None):
        self.point = point
        self.split = split
        self.left = LL
        self.right = RR

def KDT(root,dataSet):
    m,n=dataSet.shape
    var_max=0
    if m==0:
        return 
    for i in range(n):
        l=[]
        for j in range(m):
            l.append(dataSet.iloc[j,i])
        var=np.var(l)
        if var>var_max:
            var_max=var
            global split
            split=i
    dataSet_1=dataSet.sort_values(by='{}'.format(split),axis=0)
    point=dataSet_1.values[int(m/2)]
    root=KD_node(point,split)
    root.left=KDT(root.left,dataSet_1.iloc[0:int(m/2)])
    root.right=KDT(root.right,dataSet_1.iloc[int(m/2)+1:])
    return root

最后再贴一份转载来的KD树的查找方法

def findNN(root, query):
    """
    root:KDTree的树根
    query:查询点
    return:返回距离data最近的点NN,同时返回最短距离min_dist
    """
    #初始化为root的节点
    NN = root.point
    min_dist = computeDist(query, NN)
    nodeList = []
    temp_root = root
    ##二分查找建立路径
    while temp_root:
        nodeList.append(temp_root)
        dd = computeDist(query, temp_root.point)
        if min_dist > dd:
            NN = temp_root.point
            min_dist = dd
        #当前节点的划分域
        ss = temp_root.split
        if query[ss] <= temp_root.point[ss]:
            temp_root = temp_root.left
        else:
            temp_root = temp_root.right
    ##回溯查找
    while nodeList:
        #使用list模拟栈,后进先出
        back_point = nodeList.pop()
        ss = back_point.split
        print ("back.point = ", back_point.point)
        ##判断是否需要进入父亲节点的子空间进行搜索
        if abs(query[ss] - back_point.point[ss]) < min_dist:
            if query[ss] <= back_point.point[ss]:
                temp_root = back_point.right
            else:
                temp_root = back_point.left
 
            if temp_root:
                nodeList.append(temp_root)
                curDist = computeDist(query, temp_root.point)
                if min_dist > curDist:
                    min_dist = curDist
                    NN = temp_root.point
    return NN, min_dist
 
 
def computeDist(pt1, pt2):
    """
    计算两个数据点的距离
    return:pt1和pt2之间的距离
    """
    sum = 0.0
    for i in range(len(pt1)):
        sum = sum + (pt1[i] - pt2[i]) * (pt1[i] - pt2[i])
    return np.sqrt(sum)

你可能感兴趣的:(KNN KD树原理及代码实现)