knn算法以及算法实现

1. knn算法

定义:对于输入变量x,寻找数据集中距离x最近的k个实例,这k个实例中哪个类的数量最多,就认为输入变量x属于该类。

2.距离度量

对于knn算法,我们一般选择欧式距离作为距离度量,当然,对于不同的问题,可能会有不同的选择。

3.k值的选择

k值的选择对于knn的结果具有很大的影响。
如果选择了较小的k值,只有和输入实例较近(相似的)的训练实例才会对预测结果起作用。预测结果会对临近的实例点非常敏感,如果邻近的实例点是噪声,预测就很大程度会出错。换句话说,k值的减小意味着模型变得更加复杂。容易过拟合。
相反,较大的k值意味着简单的模型。优点是可以减少学习的泛化误差。但是学习的近似误差(训练误差)会增大。

#4.knn的python实现#
knn的python实现(利用欧式距离)

import numpy as np
##给出训练数据以及对应的类别
def createDataSet():
    group = np.array([[1.0,2.0],[1.2,0.1],[0.1,1.4],[0.3,3.5]])
    labels = ['A','A','B','B']
    return group,labels

def knn(input,dataset,labels,k):
    #label是新的输入变量
    #dataset是数据集
    #labels是数据集的标记
    #计算欧氏距离
    diff=np.tile(input,(dataset.shape[0],1))-dataset
    squarediff=diff**2
    squarediff=np.sum(squarediff,axis=1)
    dist=squarediff**0.5

    #进行排序,得到index
    sortDistIndex=np.argsort(dist)

    classCount={}
    for i in range(k):
        voteLabel=labels[sortedDistIndex[i]]
        classCount[voteLabel]=classCount.get(voteLabel,0)+1
    maxCount=0
    for key,value in classCount.items():
        if value>maxCount:
            maxCount=value
            classes=key
    return classes
dataSet,labels = createDataSet()
input = array([1.1,0.3])
K = 3
output =knn(input,dataSet,labels,K)
print("测试数据为:",input,"分类结果为:",output)

5.kd树

kd树是一种对k维空间中的实例点进行存储并且快速检索的树形数据结构。下面来看一下kd树的构造和kd树的搜索算法
构造平衡kd树:
对于k维空间数据集T={x1,x2, ,xNx_{1},x_{2},\cdots,x_{N}}
(1)开始:构造根节点。
选择x(1)x^{(1)}为座标轴,以T中所有实例的x(1)x^{(1)}座标的中位数作为切分点。将根节点对应的矩形区域切分成两个子区域。由根节点生成深度为1的左右节点。左子节点对应座标x(1)x^{(1)}小于切分点的区域,右子节点是大于切分点的子区域。
(2)重复:对于深度为j的节点,选择x(L)x^{(L)}为切分的座标轴,L=j(mod k)+1,
(3)直到两个子区域没有实例存在时停止,从而形成kd树的区域划分。
kd树的python代码实现

kd树搜索:
(1)在kd树中找出包含目标点x的叶结点:从根结点出发,递归的向下访问kd树,如果x当前维度的座标小于切分点的座标,则移动到左子结点,否则移动到右子结点。直到到达叶结点为止。
(2)以此叶结点为"当前最近点"
(3)递归的向上回退,每个结点进行一下操作:
(a)如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为"当前最近点"
(b)当前最近点一定处于一个子结点对应的区域,检查该子结点的父节点的另一子结点是否存在更近的点。通过画圆来检测,检测另一区域点到目标点的维度距离是否在以目标点与"当前最近点"间的距离为半径的球体相交。
如果相交,则可能存在更近的点,移动到另一个子结点,接着,递归的进行最近邻搜索;
如果不想交,向上回退。
(4)当回退到根节点时,搜索结束。最后的"当前最近点"就是x的最近邻点

如果实例点是随机分布的,kd树搜索的平均计算复杂度是O(logN),这里N是训练实例树。

import numpy as np

#节点
class Node:
    def __init__(self,data,lchild=None,rchild=None):
        self.data=data
        self.lchild=lchild
        self.rchild=rchild

#kd树
class KdTree:
    def __init__(self):
        self.KdTree=None

    #创建Kd树,返回根节点
    def create(self,dataset,depth):
        #选取座标轴,选择中位数作为划分点。
        if(len(dataset)>0):
            m,n=np.shape(dataset)
            '''
            书中座标轴l=depth(modk)+1,其中k是维度。但是python数组中下标是
            从1开始的。于是座标轴l=depth mod k'''
            axis=depth%n
            #选取座标轴
            midIndex=int(m/2)
            #对数组进行排序
            sortedDataSet=self.sort(dataset,axis)
            node=Node(sortedDataSet[midIndex])# 将节点的数据域设置为中位数
            leftDataSet=sortedDataSet[:midIndex]    
            rightDataSet=sortedDataSet[midIndex+1:]
            print('划分的数据为:',sortedDataSet[midIndex])
            print('左子树的数据为:',leftDataSet)
            print('右子树的数据为:',rightDataSet)
            #递归实现kd树的构建 
            node.lchild=self.create(leftDataSet,depth+1)
            node.rchild=self.create(rightDataSet,depth+1)
            return node
        else:
            return None

    #排序函数
    def sort(self,dataset,axis):
        sortedDataSet=dataset
        m,n=np.shape(sortedDataSet)
        for i in range(m):
            for j in range(0,m-i-1):
                if sortedDataSet[j][axis]>sortedDataSet[j+1][axis]:
                    temp=sortedDataSet[j]
                    sortedDataSet[j]=sortedDataSet[j+1]
                    sortedDataSet[j+1]=temp
        print(sortedDataSet)
        return sortedDataSet


    def preOrder(self,node):#前序遍历
        if node!=None:
            print(node.data)
            self.preOrder(node.lchild)
            self.preOrder(node.rchild)

    def search(self,tree,x):#kd树的搜索
        self.nearestPoint=None#保存最近的点
        self.nearestValue=0#保存最近的值
        def travel(node,depth=0): #递归搜索
            if node !=None: #递归终止条件
                n=len(x)
                axis=depth%n
                if x[axis]<node.data[axis]:#如果数据小于节点的数据,就往左子树搜索,不然就往右子树
                    travel(node.lchild,depth+1)
                else:
                    travel(node.rchild,depth+1)
                #递归完毕之后,往父节点进行回溯
                distance=self.dist(x,node.data)
                print(distance)
                if self.nearestPoint==None:
                    self.nearestPoint=node.data
                    self.nearestValue=distance
                elif self.nearestValue>distance:
                    self.nearestPoint=node.data
                    self.nearestValue=distance

                print(node.data, depth, self.nearestValue, node.data[axis], x[axis])
                #接下来需要去判断是否要去子节点的区域去寻找
                if abs(x[axis]-node.data[axis])<=self.nearestValue:
                    if x[axis]<node.data[axis]:
                        travel(node.rchild,depth+1)
                    else:
                        travel(node.lchild,depth+1)
        travel(tree)
        return self.nearestPoint

    #计算欧氏距离
    def dist(self,x1,x2):
        return ((np.array(x1)-np.array(x2))**2).sum()**0.5

if __name__ == '__main__':
    dataSet = [[2, 3],  
               [5, 4],  
               [9, 6],  
               [4, 7],  
               [8, 1],  
               [7, 2]] 
    x=[5,3]
    kdtree=KdTree()
    tree=kdtree.create(dataSet,0)
    kdtree.preOrder(tree)
    print('---------------')
    print(kdtree.search(tree,x))
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章