K近邻算法
对新的实例取最近的K个训练实例,多数表决来进行预测。
基础算法
例如图中的情况:
种类:红色,蓝色
新输入: 绿色
判断新输入的类型,我们取:k=3 的时候以新输入(绿色)到各个点距离最短的三个点为判断依据,如图中此时红色:2,蓝色:1,因此决策判断新输入类型应该是红色。
K的取值会对新输入实例的判断类型产生重大影响,图中的k=5的时候,蓝色>红色,此时就会判断新输入实例为蓝色。
(较小的k值,学习的近似误差减小,但是估计误差会增大,意味着整体模型变得复杂,容易过拟合。
较大的k值,学习的近似误差增大,但是估计误差会减小,意味着整体模型变得简单。
在应用中k值一般取一个比较小的数值。采用交叉验证选取最优的k值。)
距离度量
空间的维度p
p=1曼哈顿距离,p=2欧式距离,p=∞取各个坐标距离的最大值。
在训练实例完成输入后,模型此时是确定的对整个特征空间完成划分,不同的空间对应的不同的种类。
kd树
kd树是一种对k维空间中的实例点进行存储以便对其进行快速检索的树形数据结构。
kd树的构造:
构造根结点,使根结点对应于 𝑘 维空间中包含所有实例点的超矩形区域;通过下面的递归方法,不断地对 𝑘 维空间进行切分,生成子结点。
1、在超矩形区域(结点)上选择一个坐标轴和在此坐标轴上的一个切分点,确定一个超平面,
2、这个超平面通过选定的切分点并垂直于选定的坐标轴,将当前超矩形区域切分为左右两个子区域 (子结点);
3、这时,实例被分到两个子区域。
直到子区域没有实例时终止。
kd树的搜索:
在kd树中搜索一个节点和在二叉搜索树中极其类似。
1、如果当前维度的值节点小则转左,大则转右,进行下一个维度的搜索,将最终得到的叶子节点设为当前最近点。
2、和二叉搜索树不同,kd树的查找还需要一个递归回退的过程。从当前最近点开始,依次回退并检查兄弟节点是否存在有比当前最近点更近的点。这一操作仅需比较目标点和分离超平面的距离和当前最近的距离即可,若前者小,则有可能存在更近点,须递归访问,直到最后回退到根节点即完成了搜索。
代码参考https://github.com/cherichy/statistical_learning/blob/master/python/KNN.py
import numpy as np
import heapq
class Node:
def __init__(self, data, sp=0, left=None, right=None):
self.data = data
self.sp = sp
self.left = left
self.right = right
#结点
class KDTree:
def __init__(self, data):
k = data.shape[1]
def create(dataset, sp):
if len(dataset) == 0:
return None
# sort by current dimension
dataset = sorted(dataset, key=lambda x: x[sp])
mid = len(dataset)//2
# split by the median
dat = dataset[mid]
return Node(dat, sp, create(dataset[:mid], (sp+1) % k),\
create(dataset[mid+1:], (sp+1) % k))
self.root = create(data, 0)
def nearest(self, x, near_k=1, p=2):
# use the max heap builtin library heapq
# init the elements with -inf, and use the minus distance for comparison
# the top of the max heap is the min distance.
self.knn = [(-np.inf, None)]*near_k
def visit(node):
if not node == None:
# cal the distance to the split point, i.e. the hyperplane
dis = x[node.sp] - node.data[node.sp]
# visit the child node recursively
# if returned, we get the current nearest point
visit(node.left if dis < 0 else node.right)
# cal the distance to the current nearest point
curr_dis = np.linalg.norm(x-node.data, p)
# push the minus distance to the heap
heapq.heappushpop(self.knn, (-curr_dis, node))
# compare the distance to the hyperplane with the min distance
# if less, visit another node.
if -(self.knn[0][0]) > abs(dis):
visit(node.right if dis < 0 else node.left)
visit(self.root)
self.knn = np.array(
[i[1].data for i in heapq.nlargest(near_k, self.knn)])
return self.knn
if __name__ == "__main__":
from pylab import *
data = array([[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]])
kdtree = KDTree(data)
target = array([7.5, 3])
kdtree.nearest(target, 2)
plot(*data.T, 'o')
plot(*target.T, '.r')
plot(*kdtree.knn.T, 'r+')
show()