KNN与KDTree--机器学习(2)

1.导读

k近邻法(k-nearnest neighborm,k-NN)是一种基本分类与回归方法。从这个方法的名字上来看基本上就可以知道这个方法是怎么做的了——不需要训练模型,假定已有数据及其标签(自己输入),然后再输入一个未知的目标值,计算一下和这个目标值最近的“K”个数据分别是哪个,然后统计这k个数据的标签,看看哪类最多就以此预测我们的目标值的标签,用术语来说这个叫做“多数表决规则”,这跟操作系统中取内存时的“局部性原理”有点相似,用古语来说就是:物以类聚,人以群分。这个算法就是这么一个道理了。这次的博客将会从距离的计算方法、k值的选择、普通方法实现,kd tree实现这几个方面来进行分享。

2.距离度量

设特征空间   X \ X  X   n \ n  n维实数向量空间   R n \ R^n  Rn,   x i , x j ∈ X \ x_i,x_j \in X  xi,xjX,   x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( n ) ) T \ x_i =(x_i^{(1)},x_i^{(2)},...,x_i^{(n)})^T  xi=(xi(1),xi(2),...,xi(n))T   x j = ( x j ( 1 ) , x j ( 2 ) , . . . , x j ( n ) ) T \ x_j =(x_j^{(1)},x_j^{(2)},...,x_j^{(n)})^T  xj=(xj(1),xj(2),...,xj(n))T,   x i , x j \ x_i,x_j  xi,xj   L p \ L_p  Lp距离定义为:
.

  L p ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ p ) 1 p \ L_p(x_i,x_j)=\big( \displaystyle\sum_{l=1}^{n}|x_i^{(l)}-x_j^{(l)}|^p \big)^{\frac{1}{p}}  Lp(xi,xj)=(l=1nxi(l)xj(l)p)p1
.

这里   p ≥ 1 \ p \ge 1  p1.当   p = 2 \ p=2  p=2时,称为欧氏距离,即
.
  L 2 ( x i , x j ) = ( ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ 2 ) 1 2 \ L_2(x_i,x_j)=\big( \displaystyle\sum_{l=1}^{n}|x_i^{(l)}-x_j^{(l)}|^2 \big)^{\frac{1}{2}}  L2(xi,xj)=(l=1nxi(l)xj(l)2)21
.

  p = 1 \ p=1  p=1时,称为曼哈顿距离,即
.
  L 1 ( x i , x j ) = ∑ l = 1 n ∣ x i ( l ) − x j ( l ) ∣ \ L_1(x_i,x_j)=\displaystyle\sum_{l=1}^{n}|x_i^{(l)}-x_j^{(l)}|  L1(xi,xj)=l=1nxi(l)xj(l)
.

  p = ∞ \ p=\infty  p=,为各个坐标距离的最大值,即
.
  L ∞ ( x i , x j ) = max ⁡ l ∣ x i ( l ) − x j ( l ) ∣ \ L_{\infty}(x_i,x_j)= \displaystyle\max_l |x_i^{(l)}-x_j^{(l)}|  L(xi,xj)=lmaxxi(l)xj(l)
.

其中我们常用的是欧式距离,在二维坐标轴中求两点的距离也是使用的欧氏距离。我们知道了如何在n维实数向量空间中计算距离,那么问题就解决了一大半了。

3.k近邻算法的普通实现

在这里,k值的选择尤为重要,k值的选择会对算法的结果产生重大的影响:

1.k值选择过小,就会对临近的实例点十分敏感,但是如果这个实例点恰巧是噪声(不利因素)的话,就会预测出错,预计误差就会增大——随着k值的减小,意味着模型变得复杂,容易发生过拟合(在已知数据有较好的效果,但是对未知的数据预测效果很差)。
2.k值选择过大,会使得模型变得简单,如果k值等于实例点的值,那么无论输入什么数据,它预测的值都只会是实例中数量较大的那一方。

所以,在实际中,我们会使用交叉验证来确定k值(参考:《统计学习方法》):

# -*- coding: utf-8 -*-
"""
Created on Tue Jun  2 13:29:39 2020

@author: Fricerice
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
iris = load_iris()
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
data = np.array(df.iloc[:100, [0,1, -1]])
X, y = data[:,:-1], data[:,-1]

class KNN:
    
    def __init__(self,X_train,Y_train,n_neighbors=3,p=2 ):
        self.X_train=X_train
        self.Y_train=Y_train
        self.n=n_neighbors
        self.p=p
        self.nn=0
        
    def predict(self,x):
        knnList=[]
        for i in range(self.n):
            distance = np.linalg.norm(self.X_train[i]-x,ord=self.p)
            knnList.append((distance,self.Y_train[i]))
            
        for i in range(self.n,len(self.X_train)):
            distance = np.linalg.norm(self.X_train[i]-x,ord=self.p)
            mindis = min([k[0] for k in knnList])
            if distance < mindis:
                self.nn=(self.X_train[i],i)
            maxIndex = knnList.index(max(knnList,key=lambda x:x[0]))
            if knnList[maxIndex][0] > distance:
                knnList[maxIndex]=(distance,self.Y_train[i])
                
        result =[k[-1] for k in knnList]
#        print(knnList)
        countResult = Counter(result)
#        print(countResult)
#        mindis = [k[0] for k in knnList]
#        print(mindis)
        maxCount = sorted(countResult,key=lambda x:x)[-1]
#        print(maxCount)
        return maxCount
        
    def score(self, X_test, y_test):
        right_count = 0
        for X, y in zip(X_test, y_test):
            label = self.predict(X)
            if label == y:
                right_count += 1
        return right_count / len(X_test)


   
#clf = KNN(X_train, y_train,4)
#print(clf.score(X_test, y_test))

#交叉验证k值
scoresList=[]
for n in range(1,10):#
    scores=[]
    for i in range(5):
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
        clf = KNN(X_train,y_train,n_neighbors=n)
        scores.append(clf.score(X_test, y_test))
        
    scoresList.append(np.mean(scores))
    

print(scoresList)
plt.figure()
plt.plot(range(1,10),scoresList)
plt.xlabel('Value of k for KNN')
plt.ylabel('accuracy')
plt.show()

运行结果:
在这里插入图片描述

4.kdtree的实现以及查询算法

上面普通实现是进行线性扫描,这样效率较低,为了提高搜索的效率,可以考虑使用平衡二叉树来进行数据存储,以减少计算距离的次数。

4.1构造平衡kd树算法:

输入:k维空间数据集   T = { x 1 , x 2 , . . . , x n } \ T=\{x_1,x_2,...,x_n\}  T={x1,x2,...,xn},其中   x i = ( x i ( 1 ) , x i ( 2 ) , . . . , x i ( l ) ) T , i = 1 , 2 , . . . , n \ x_i=(x_i^{(1)},x_i^{(2)},...,x_i^{(l)})^T,i=1,2,...,n  xi=(xi(1),xi(2),...,xi(l))T,i=1,2,...,n,   l \ l  l为数据的第几维
输出:kd树
构造过程:(构造过程和进行二分查找差不多)
(1)构造根结点,根结点的深度为0,维数选择为   l = d e p t h % m + 1 \ l=depth\%m + 1  l=depth%m+1,其中m为输入数据的维数(很多书里面都写成k,容易和k近邻进行混淆),将数据集按照   l \ l  l维进行排序,选择中位数作为本次的结点(后面都是这样做)
(2)为根节点构造左右结点:将数据集从中间切开,各分成两份再各自根据维数(深度为1时选择第2维)排序后选择中位数。
(3)重复上述操作直到数据集的数据都被使用完毕。
例子(参考《统计学习方法》):

数据集:   T = { ( 2 , 3 ) T , ( 5 , 4 ) T , ( 9 , 6 ) T , ( 4 , 7 ) T , ( 8 , 1 ) T , ( 7 , 2 ) T } \ T=\{(2,3)^T,(5,4)^T,(9,6)^T,(4,7)^T,(8,1)^T,(7,2)^T\}  T={(2,3)T,(5,4)T,(9,6)T,(4,7)T,(8,1)T,(7,2)T}

构造结果:
统计学习方法P43

4.2kd树的k近邻搜索

算法实现:
数据结构准备:
kd树、栈1(存储访问的结点)、数组(存储k个最近的)
(1)从根节点开始向下访问结点(按照构造时的维数规则进行比较)直到找到叶节点,然后计算叶节点和目标值的距离作为当前的最小距离(mindis),该叶结点作为最近邻点,并把该最小距离值存储到数组中。
(2)计算叶结点的父节点与目标值的最小距离,与当前最小距离比较,若小于当前最小距离,则替换最近邻点和最近距离值(但是也要把当前值存到上述的数据结构中,以便查找最近k个)
(3)判断是否进入父节点的另一结点:
a.当前叶结点在父节点维数上的距离若小于等于当前最小距离(即目标值和叶结点/父节点距离所形成的超球体是否与父节点的超矩形相交,想象一下刀切苹果的那种样子),则进入,重复(1)
b.若不满足,则将当前父节点当中叶结点,重复(2)

最终根据数组中的值进行判断。

代码实现:

# -*- coding: utf-8 -*-
"""
Created on Tue Jun  2 17:44:59 2020

@author: Frierice
"""
import numpy as np

import pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from collections import Counter
class KdNode:
    def __init__(self,axis,data,rightNode,leftNode):
        self.axis = axis
        self.data = data
        self.rightNode =rightNode
        self.leftNode =leftNode
    
class KDTree(object):
    def __init__(self,data,k=5):
        self.m = len(data[0])-1#划分最大维数,最后一个是标签
        self.stackOfKNN=[]
        self.k =k #最近点数
        self.nn=None #目前最近点
        self.mindis=0
        def createTreeNode(axis,data):
            if len(data)==0:
                return None
            data=sorted(data,key=lambda x:x[axis])#根据划分维数排序
            mid_index = len(data)//2
            point = data[mid_index]
            next_axis = (axis+1) % self.m
            return KdNode(axis,point,createTreeNode(next_axis,data[0:mid_index]),
                          createTreeNode(next_axis,data[mid_index+1:]))
            
        self.root = createTreeNode(0,data)
       
    def pushStack(self,point,nowPoint):
        distance = np.linalg.norm(point[0:-1]-nowPoint[0:-1],ord=2)
        if len(self.stackOfKNN)+1 > self.k :
            maxIndex = self.stackOfKNN.index(max(self.stackOfKNN,key=lambda x:x[0])) 
            if self.stackOfKNN[maxIndex][0] > distance:
                    self.stackOfKNN[maxIndex]=(distance,nowPoint[-1])
        
        else:
            self.stackOfKNN.append((distance,nowPoint[-1]))
    
    def findNode(self,testdata,root):
        stack = []
        if root != None:
            stack.append(root)
        else:
            return -1
        nowpoint = stack[-1]
        flag=0
#        mindis=0
        while stack:
#            if self.nn != None:
#                print(self.nn.data)
            if nowpoint.rightNode == None and nowpoint.leftNode == None:
#                self.nn = nowpoint
                if self.nn !=None:
                    self.mindis = np.linalg.norm(testdata[0:-1]-self.nn.data[0:-1],ord=2)
                    distance = np.linalg.norm(testdata[0:-1]-nowpoint.data[0:-1],ord=2)
                    if distance <= self.mindis:
                        self.nn = nowpoint
                        self.mindis=distance
                else:
                    self.nn = nowpoint
                    self.mindis = np.linalg.norm(testdata[0:-1]-nowpoint.data[0:-1],ord=2)
                self.pushStack(testdata,nowpoint.data)
                flag=1
            if flag==1:
#                self.nn = nowpoint
#                mindis = np.linalg.norm(testdata[0:-1]-nowpoint.data[0:-1],ord=2)
                
                
                self.pushStack(testdata,nowpoint.data)
#                print(nowpoint.data)
                parent = stack.pop(-1)
                distance = np.linalg.norm(testdata[0:-1]-parent.data[0:-1],ord=2)
                if distance <= self.mindis:
                    self.nn = parent
                    self.mindis=distance
                    self.pushStack(testdata,parent.data)
                    
#                nowpoint=parent
                left=False
                if nowpoint==parent.leftNode:
                    left=True
                
                nowpoint = parent
                if left and parent.rightNode!=None:
                    if abs(parent.data[parent.axis] - testdata[parent.axis]) <= self.mindis:
                        self.findNode(testdata,parent.rightNode)
                        
                if not left and parent.leftNode!=None:
                    if abs(parent.data[parent.axis] - testdata[parent.axis]) <= self.mindis:
                        self.findNode(testdata,parent.leftNode)
                    
                    
            else:
                nowAxis = nowpoint.axis
                if testdata[nowAxis] <= nowpoint.data[nowAxis]:
                    if nowpoint.rightNode !=None:
                        stack.append(nowpoint)
                        nowpoint=nowpoint.rightNode
                    else:
                        stack.append(nowpoint)
                        nowpoint=nowpoint.leftNode
                else:
                    if nowpoint.leftNode !=None:
                        stack.append(nowpoint)
                        nowpoint=nowpoint.leftNode
                    else:
                        stack.append(nowpoint)
                        nowpoint=nowpoint.rightNode
                        
    def predict(self,data):
        self.stackOfKNN=[]
        self.findNode(data,self.root)
        result =[k[-1] for k in self.stackOfKNN]
        countResult = Counter(result)
        maxCount = sorted(countResult,key=lambda x:x)[-1]
        return maxCount
    
    def score(self, dataset):
        right_count = 0
        for i in dataset:
            label = self.predict(i)
            if label == i[-1]:
                right_count += 1
        return right_count / len(dataset)
    
iris = load_iris()
#print(iris)
df = pd.DataFrame(iris.data, columns=iris.feature_names)
df['label'] = iris.target
df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
#print(df)
data = np.array(df.iloc[:100, [ 0,1 , -1]])
##X, y = data[:,:-1], data[:,-1]
#
scoresList=[]
for n in range(1,10):#
    scores=[]
    for i in range(5):
        X_train, X_test = train_test_split(data, test_size=0.2)
        clf = KDTree(X_train,n)
        scores.append(clf.score(X_test))
        print(len(clf.stackOfKNN))
        
    scoresList.append(np.mean(scores))
    

print(scoresList)
plt.figure()
plt.plot(range(1,10),scoresList)
plt.xlabel('Value of k for KNN')
plt.ylabel('accuracy')
plt.show()
        
#data=[[2,3,1],[5,4,1],[9,6,1],[4,7,1],[8,1,1],[7,2,1],[10,100,1]]
#data = np.array(data)
#clf=KDTree(data[:-1],5)
#clf.predict(data[-1])
##
##print(data[-1])
#print(clf.mindis)
#print(clf.nn.data)



5.总结

以上就是最近学习的心得和代码的分享,kdtree实现的代码不够完美,希望大家多点交流指错,谢谢。下一个博客会进行朴素贝叶斯法的分享。
参考:《统计学习方法》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值