笔记|统计学习方法:k近邻算法

k近邻算法(k-NN)是一种基本分类与回归方法,它有三个基本要素,本文将介绍k近邻算法的模型与kd树。

k近邻算法

给定一个训练数据集,对于新输入的实例,在训练数据集中找到与该实例最临近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类。

数学实现

输入:训练数据集
T = { ( x 1 , y 1 ) , ( x 2 , y 2 ) , ⋅ ⋅ ⋅ ( x N , y N ) } T=\{(x_1,y_1),(x_2,y_2),···(x_N,y_N)\} T={(x1,y1),(x2,y2),(xN,yN)}

其中, x i ∈ χ = R n x_i\in\chi=R^n xiχ=Rn , y i ∈ Y = { c 1 , c 2 , ⋅ ⋅ ⋅ , c K } y_i\in Y=\{c_1,c_2,···,c_K\} yiY={c1,c2,,cK}为实例的类别, i = 1 , 2 , ⋅ ⋅ ⋅ N i=1,2,···N i=1,2,N;实例特征向量 x x x
输出:实例x所属的类y。

  • 1.根据给定的距离度量,在训练集 T T T中找出与 x x x最近邻的 k k k个点,涵盖这 k k k个点的 x x x的领域记作 N k ( x ) N_k(x) Nk(x)
  • 2.在 N k ( x ) N_k(x) Nk(x)中根据分类决策规则(如多数表决)决定 x x x的类别 y y y

y = a r g m a x c j ∑ x i ∈ N k ( x ) I ( y i = c j ) , i = 1 , 2 , 3 , ⋅ ⋅ ⋅ , N ; j = 1 , 2 , ⋅ ⋅ ⋅ , K y=argmax_{cj}\sum_{x_i\in N_k(x)}I(y_i=c_j),i=1,2,3,···,N;j=1,2,···,K y=argmaxcjxiNk(x)I(yi=cj),i=1,2,3,,N;j=1,2,,K
式中 I I I为指示函数,即当 y i = c j y_i=c_j yi=cj I I I 1 1 1,否则 I I I为0.
k = 1 k=1 k=1时称为最近邻算法,k近邻法没有显式的学习过程。

模型建立

模型三要素为距离度量 K K K的大小和分类规则

距离度量
  • 闵可夫斯基距离(Minkowski Distance)
    D ( x , y ) = ( ∑ i = 1 m ∣ x i − y i ∣ p ) 1 p D(x,y)=(\sum\limits_{i=1}^m|x_i-y_i|^p)^\frac{1}{p} D(x,y)=(i=1mxiyip)p1
    其中 p ≥ 1 p\geq 1 p1
  • p = 2 p=2 p=2时,是欧式距离。
  • p = 1 p=1 p=1时,是曼哈顿距离。
k k k的选择

k k k的选择会对结果产生重大影响

  • k k k的值过小,极端情况下 k = 1 k=1 k=1,测试实例只和最接近的一个样本有关,训练误差很小,但是如果这个样本恰好是噪点,预测就会出错,即产生了过拟合。
  • 如果 k k k值过大,极端情况 k = n k=n k=n,则会产生欠拟合。

姑通常采用交叉验证法来选取合适的 k k k

分类规则

k k k近邻的分类决策通常是多数表决:由测试样本的 k k k个临近样本的多数类决定测试样本的类别。有如下规则:
给定测试样本 x x x,其最临近的 k k k个训练示例构成的集合 N k ( x ) N_k(x) Nk(x),分类损失函数为 0 − 1 0-1 01型损失,如果涵盖 N k ( x ) N_k(x) Nk(x)区域的类别为 c j c_j cj,则分类误差率为:
1 k ∑ x i ∈ N k ( x ) I { y i ≠ c j } = 1 − 1 k \frac{1}{k}\sum_{x_i\in N_k(x)}I\{y_i\neq c_j\}=1-\frac{1}{k} k1xiNk(x)I{yi=cj}=1k1

∑ x i ∈ N k ( x ) I { y i = c j } \sum_{x_i\in N_k(x)}I\{y_i=c_j\} xiNk(x)I{yi=cj}
要使得分类误差率最小,就是要使 ∑ x i ∈ N k ( x ) I { y i = c j } \sum_{x_i\in N_k(x)}I\{y_i=c_j\} xiNk(x)I{yi=cj}最大,所以多数表决规则等价于误分类绿最小。

实现: k d kd kd

k d kd kd树算法有三步

  1. 构造 k d kd kd
  2. 搜索 k k k近邻
  3. 预测
k d kd kd树的构建
  • 选取 x ( 1 ) x^{(1)} x(1)为坐标轴,以训练集中的所有数据 x ( 1 ) x^{(1)} x(1)坐标中的中位数作为切分点,将超矩形区域切割成两个子区域。将该切分点作为根结点,由根结点生出深度为1的左右子结点,左节点对应 x ( 1 ) x^{(1)} x(1)坐标小于切分点,右结点对应 x ( 1 ) x^{(1)} x(1)坐标大于切分点。
  • 对深度为 j j j的结点,选择 x ( l ) x^{(l)} x(l)为切分坐标轴, l = j ( m o d k ) + 1 l=j(mod k)+ 1 l=j(modk)+1,以该结点区域中训练数据 x ( l ) x^{(l)} x(l)坐标的中位数作为切分点,将区域分为两个子区域,且生成深度为 j + 1 j+1 j+1的左、右子结点。左节点对应 x ( l ) x^{(l)} x(l)坐标小于切分点,右结点对应 x ( l ) x^{(l)} x(l)坐标大于切分点
  • 重复2,直到两个子区域没有数据时停止。
实例参考KNN算法和kd树详解(例子+图示)
k d kd kd的搜索

输入:已构造的 k d kd kd树,目标点 x x x
输出: x x x的最近邻

  1. 在kd树中找出包含目标点 x x x的叶结点从根结点递归地向下访问kd树若目标点 x x x当前纬的坐标小于分切点则移动到左子结点直到子结点为叶子结点为止
  2. 此叶子结点为"当前最近点"。
  3. 递归地向上回退,在每个结点都进行如下操作:
(a)如果该结点保存的实例点比当前最近点距离目标点更近,则以该实例点为“当前最近点”

(b)当前最近点一定存在于该结点一个子结点对应的区域,检查该子结点的父结点的另一个子结点对应的区域是否有更近的点。具体的,检查另一子结点对应的区域是否与以目标点为球心,以目标点与“当前最近点”间的距离为半径的超球体相交。

(c)如果相交,可能在另一个子结点对应的区域内存在距目标点更近的点,移动到另一个子结点。接着,递归地进行最近邻搜索;如果不相交,则向上退回。

  1. 当退回到根结点时,搜索结束。最后一个“当前最近点”即为 x x x的最近邻点。
实例参考KNN算法和kd树详解(例子+图示)

Python代码实现

import numpy as np
class Node:
    def __init__(self, data, lchild = None, rchild = None):
        self.data = data
        self.lchild = lchild
        self.rchild = rchild

class KdTree:
    def __init__(self):
        self.kdTree = None

    def create(self, dataSet, depth):   #创建kd树,返回根结点
        if (len(dataSet) > 0):
            m, n = np.shape(dataSet)    #求出样本行,列
            midIndex = int(m / 2) #中间数的索引位置
            axis = depth % n    #判断以哪个轴划分数据
            sortedDataSet = self.sort(dataSet, axis) #进行排序
            node = Node(sortedDataSet[midIndex]) #将节点数据域设置为中位数,具体参考下书本
            # print sortedDataSet[midIndex]
            leftDataSet = sortedDataSet[: midIndex] #将中位数的左边创建2改副本
            rightDataSet = sortedDataSet[midIndex+1 :]
            print(leftDataSet)
            print(rightDataSet)
            node.lchild = self.create(leftDataSet, depth+1) #将中位数左边样本传入来递归创建树
            node.rchild = self.create(rightDataSet, depth+1)
            return node
        else:
            return None

    def sort(self, dataSet, axis):  #采用冒泡排序,利用aixs作为轴进行划分
        sortDataSet = dataSet[:]    #由于不能破坏原样本,此处建立一个副本
        m, n = np.shape(sortDataSet)
        for i in range(m):
            for j in range(0, m - i - 1):
                if (sortDataSet[j][axis] > sortDataSet[j+1][axis]):
                    temp = sortDataSet[j]
                    sortDataSet[j] = sortDataSet[j+1]
                    sortDataSet[j+1] = temp
        print(sortDataSet)
        return sortDataSet

    def preOrder(self, node):
        if node != None:
            print("tttt->%s" % node.data)
            self.preOrder(node.lchild)
            self.preOrder(node.rchild)

    # def search(self, tree, x):
    #     node = tree
    #     depth = 0
    #     while (node != None):
    #         print node.data
    #         n = len(x)  #特征数
    #         axis = depth % n
    #         if x[axis] < node.data[axis]:
    #             node = node.lchild
    #         else:
    #             node = node.rchild
    #         depth += 1
    def search(self, tree, x):
        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)

                #以下是递归完毕后,往父结点方向回朔
                distNodeAndX = self.dist(x, node.data)  #目标和节点的距离判断
                if (self.nearestPoint == None): #确定当前点,更新最近的点和最近的值
                    self.nearestPoint = node.data
                    self.nearestValue = distNodeAndX
                elif (self.nearestValue > distNodeAndX):
                    self.nearestPoint = node.data
                    self.nearestValue = distNodeAndX

                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
##运行示例:
#初始值设定
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(kdtree.search(tree, x))

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值