统计学习方法与Python实现(二)——k近邻法

统计学习方法与Python实现(二)——k近邻法

  iwehdio的博客园:https://www.cnblogs.com/iwehdio/

1、定义

  k近邻法假设给定一个训练数据集,其中的实例类别已定。分类时,对新的实例,根据其k个最近邻的训练实例的类别,通过多数表决的方式进行预测。k近邻法不具有显式的学习过程,而实际上是利用训练数据集对特征空间进行划分,并作为其分类的模型。k近邻法的三个基本要素是 k值的选择、距离度量和分类决策规则。

  k近邻法的模型是将特征空间划分成一些称为单元的子空间,并且每个单元内的点所属的类都被该单元的类标记所唯一确定。

  单元的划分和类标记的确定需要首先对距离进行度量。特征空间中两个实例点的距离是它们之间相似程度的反映。对于n维实数向量的特征空间Rn,两向量xi和xj之间的Lp距离定义为:

  当p=1时,称为曼哈顿距离:

  当p=2时,称为欧氏距离:

  当p=∞时,取值为各个坐标距离的最大值:

  对于k值的选择,如果选择较小的k值,学习的近似误差会减小,但估计误差会增大,对噪声敏感。k值的减小就意味着整体模型变得复杂,容易发生过拟合。如果选择较大的k值,可以减少学习的估计误差,但缺点是学习的近似误差会增大。k值的增大 就意味着整体的模型变得简单。

  在应用中,k值一般取一个较小的数值,并通过交叉验证法来确定最优的k值。

  k近邻法中的分类决策规则往往是多数表决,即由输入实例的k个近邻的训练实例中的多数类决定输入实例的类。多数表决规则等价于经验风险最小化。

2、构造kd树

  实现k近邻法是,主要的问题是如何对训练数据进行快速k近邻搜索。如果使用现行扫描,需要计算输入实例与每一个训练实例的距离,非常耗时。kd树是一种对k维空间中的实例 点进行存储以便对其进行快速检索的树形数据结构,可以提高搜索效率。

  kd树是二叉树,表示对k维空间的一个划分。每次划分需要选定一个坐标轴和一个切分点,以此确定一个超平面对训练实例进行一次划分,并递归直到将实例划分完全。如果切分点每次近似选为该坐标轴上的中位数,则称这样的kd树为平衡kd树,算法流程如下:

  a、构造根结点,其对应于包含整个训练实例T的k维空间。选择x1作为坐标轴,以T中所有实例的x1坐标的中位数作为切分点,切分由通过切分点并与x1轴垂直的超平面实现。由根节点生成深度为1的左右结点,左子结点对应于x1坐标小于切分点的子区域,右子结点对应于x1坐标大于切分点的子区域。落在切分超平面上的实例被保存在根结点。

  b、递归重复。对于深度为 j 的结点,选择xn作为切分的坐标轴,其中n = ( j mod k) + 1,以节点区域中所有实例的xn坐标的中位数作为切分点。其他与a步中相同。

  c、到两个子区域都没有实例点存在时停止,从而形成kd树的区域划分。

3、搜索kd树

  完成对kd树的构造后,对于输入的测试实例,需要对kd树进行搜索,以得到输入实例的类别。以k=1的最近邻为例。给定输入实例,搜索最近邻。首先找到包含目标点的叶结点,其对应于包含目标点的最小子区域。以此叶结点的实例作为当前最近点,则目标点的最近邻一定在以目标点为中心,并通过当前最近点的超球体内部。然后返回当前节点的父结点,如果父结点的另一子结点的子区域与超球体相交,则在此子区域内寻找与目标点更近的实例点。如果存在这样的点,将此点作为新的当前最近点。返回更上一级的父结点,继续上述过程,直到父节点的另一子结点的子区域与超球体不相交,即不存在更近的点。算法流程如下:

  a、从根结点出发,向下访问kd树,找到子区域包含输入实例的叶结点。

  b、以此叶结点作为当前最近点。

  c、递归的向上回退。如果该结点保存的实例点比当前最近点更近,则将此结点更新为当前最近点。如果以目标点为中心,通过当前最近点的的超球体与当前最近点的父结点的另一个子节点对应的子区域相交,则在此子区域中进行搜索与更新。如果不相交,则向上回退。

  d、当回退到根结点时,搜索结束。当前最近点即为最近邻点。

4、kd树的构造的Python实现

  用到的数据集是sk-learn中的iris鸢尾花卉数据集,共150个数据,分为‘setosa‘, ‘versicolor‘, ‘virginica‘三类,数据包含四个特征sepal length(花萼长度)、sepal width(花萼宽度)、petal length(花瓣长度)和petal width(花瓣宽度)。

  本次先从k=1的最近邻法实现k近邻。

  首先,载入数据集并划分训练集和测试集。

from binarytree import *
import numpy as np
from sklearn.datasets import load_iris

# 从sk-learn库载入iris数据集
iris = load_iris()
# dict_keys([‘data‘, ‘target‘, ‘target_names‘, ‘DESCR‘, ‘feature_names‘, ‘filename‘])
# ‘target_names‘: array([‘setosa‘, ‘versicolor‘, ‘virginica‘]

# 设定训练集和测试集大小
train_length = 105
test_length = 45

data = iris[‘data‘]       # shape = (150, 4)
label = iris[‘target‘]    # 0:50,0; 50:100,1; 100:150,2

train_data = np.zeros([train_length, 4])
train_label = np.zeros([train_length])
test_data = np.zeros([test_length, 4])
test_label = np.zeros([test_length])

# 划分训练集和测试集
length = train_length
for j in range(3):
    train_data[j * int(length/3):(j+1) * int(length/3)] = data[j*50:j*50 + int(length/3)]
    train_label[j * int(length/3):(j+1) * int(length/3)] = label[j*50:j*50 + int(length/3)]

length = test_length
for j in range(3):
    test_data[j * int(length/3):(j+1) * int(length/3)] = data[(j+1) * 50 - int(length/3):(j+1) * 50]
    test_label[j * int(length/3):(j+1) * int(length/3)] = label[(j+1) * 50 - int(length/3):(j+1) * 50]

train_index = np.arange(train_length).reshape([train_length, 1])
train_data = np.hstack((train_data, train_index))

  

  然后,构造kd树,二叉树由binarytree库实现。构造策略是,每次将数据四个特征中方差最大的轴作为划分轴,将该轴上特征值小于等于中位数的数据划分到左子树,大于中位数的数据划分到右子树。并记录每次划分时的轴和中位数值。

# 生成kd树
def creat_kd_tree(data, root, turn, log):

    axis = selct_axis(data[:, :-1])
    data = data[data[:, axis].argsort()]    # 按第axis列排序
    mid = data[:, axis].shape[0] // 2
    # 如果多个值在axis上的值与mid_data相同,则全部划分到左结点
    while mid < data.shape[0]-1 and data[mid + 1, axis] == data[mid, axis]:
        mid += 1
    mid_data = data[mid]
    log[int(mid_data[-1])] = (axis, mid_data[axis])
    # 存储左右子树下的结点
    data_left, data_right = [], []
    for temp in data[:mid]:
        data_left.append(temp)
    for temp in data[mid + 1:]:
        data_right.append(temp)
    # 创建新结点并递归
    node = Node(int(mid_data[-1]))
    # print(mid_data[-1], data_left, data_right)
    if turn: root.right = node
    else: root.left = node
    if data_left:
        creat_kd_tree(np.array(data_left), node, 0, log)
    if data_right:
        creat_kd_tree(np.array(data_right), node, 1, log)

# 选择方差最大的轴作为划分对象
def selct_axis(data, num=4):

    index = 0
    all_var = 0
    for i in range(num):
        axis_var = data[:, i].var()
        if all_var < axis_var:
            all_var = axis_var
            index = i
    return index

# kd树的根节点
node_init = Node(-1)
# log中保存了每个值为index的结点的超平面的轴和中位数值
log = [0 for i in range(train_length)]
creat_kd_tree(train_data, node_init, 0, log)
print(node_init.left)

5、kd树的搜索的Python实现

  首先,寻找输入实例所属的子区域的叶节点,并记录路径。然后,根据记录的路径,从叶结点开始,计算以输入实例为球心,最近邻点距离为半径的超球体,与父结点的超平面有无交集。如果有交集,则遍历该父结点下的所有子结点,同时记录遍历过得结点防止重复计算。最后,返回模型中最近邻点的索引和距离。

# 寻找输入实例所属的子区域的叶节点,并记录路径
def find_leave(data, root, log):

    route = [(root.value, root)]
    while 1:
        index = root.value
        # print(index)
        if data[log[index][0]] <= log[index][1]:
            temp = root.left
        else:
            temp = root.right
        if temp is None:
            return route
        else:
            route.append((temp.value, temp))
            root = temp

# 寻找最近邻点
def find_neibor(simple, route, log):

    # 初始化最近邻点和距离
    near = route[-1][0]
    dst = np.linalg.norm((simple - train_data[near, :-1]))

    # 记录已经遍历过的结点
    save = []
    # 从后往前返回父结点
    for fa in route[:-1][::-1]:

        # 如果父结点的超平面与以输入实例为球心,最近邻点距离为半径的超球体有交集,则遍历其所有子结点
        if abs(log[fa[0]][1] - simple[log[fa[0]][0]]) < dst:
            child = []
            get_child(fa[1], child)
            for choic in child:
                if choic not in save:
                    dst0 = np.linalg.norm((simple - train_data[choic, :-1]))
                    save.append(choic)
                    if dst0 < dst:
                        dst = dst0
                        near = fa[0]
    return near, dst

# 返回父结点的所有子结点的值的列表
def get_child(root, child):

    if root is None:
        return 0
    else:
        child.append(root.value)
        get_child(root.left, child)
        get_child(root.right, child)

  

  最后,在测试集上进行测试。

# 测试准确率
def acc(ans, label):

    counter = 0
    for index, num in enumerate(ans):
        if num == label[index]: counter += 1
    return counter / len(ans)

# 训练集
for n in range(train_length):
    valid_simple = train_data[n, :-1]
    rou = find_leave(valid_simple, node_init.left, log)
    valid_point, zero_true_distance = find_neibor(valid_simple, rou, log)
    # print(rou)
    # print(point, distance)

# 测试
ans = []
for n in range(test_length):
    test_simple = test_data[n]
    # rou记录了到输入实例叶结点的路径
    rou = find_leave(test_simple, node_init.left, log)
    test_point, test_distance = find_neibor(test_simple, rou, log)
    ans.append(train_label[test_point])
    # print(rou)
    print(test_point, test_distance, train_label[test_point])

print(‘test_acc‘, acc(ans, test_label))

  

  在训练集上,每个输入都可以找到自己对应距离为0的结点。在测试集中,准确率为1,部分测试结果如下。第一列为最近邻点的索引,第二列为距离,第三列为分类结果。

6、其他问题

  a、如何从k=1的最近邻法拓展到k为其他值下的k近邻法?

    可以用长度为k的排序列表来实现。首先,先以关系最近的k个父结点和兄弟结点初始化排序列表。然后,按与最近邻法相同的算法,每次用排序列表中距离最大的值进行比较(也可能出现新值的距离比原来列表中的多个值都小的情况)。最后,当距离最大的值的超球体都与父结点的超平面无交集时,返回排序列表作为最近的k个值进行投票。

  b、为什么用到超平面的距离代替超球体与其他结点的区域是否有交集?

    因为计算点到区域的距离比较复杂,用到超平面的距离来代替超球体与超区域的问题是充分的,而且易于计算。

  c、对于如手写数字集mnist类似的,数据值为0,1二值化的数据集,如何进行kd树的中位数划分?

    (所以用了iris...)

参考:李航 《统计学习方法(第二版)》

iwehdio的博客园:https://www.cnblogs.com/iwehdio/

原文地址:https://www.cnblogs.com/iwehdio/p/12005068.html

时间: 2024-09-30 06:56:55

统计学习方法与Python实现(二)——k近邻法的相关文章

李航统计学习方法——算法2——k近邻法

一.K近邻算法 k近邻法(k-nearest neighbor,k-NN)是一种基本分类与回归方法,输入实例的特征向量,输出实例的类别,其中类别可取多类 二.k近邻模型 2.1 距离度量 距离定义: (1)当p=1,称为曼哈顿距离 (2)当p=2,称为欧式距离 (3)当p取无穷大时,它是各个坐标距离的最大值 max|xi-xj| 注意:p值的选择会影响分类结果,例如二维空间的三个点 x1=(1,1),x2=(5,1), x3=(4,4) 由于x1和x2只有第二维上不同,不管p值如何变化,Lp始终

统计学习方法 (第3章)K近邻法 学习笔记

第3章 K近邻法 k近邻算法简单.直观:给定一个训练数据集,对新的输入实例,在训练数据集中找到与该实例最邻近的k个实例,这k个实例的多数属于某个类,就把该输入实例分为这个类.当K=1时,又称为最近邻算法,这时候就是将训练数据集中与x最邻近点作为x的类. 3.1 k近邻模型 模型由三个基本要素--距离度量.k值得选择.和分类决策规则决定. 3.1.1 距离度量 p=2时,称为欧式距离,p=1时,称为曼哈顿距离. 3.1.2 k值的选择 k 值的选择会对k 近邻法的结果产生重大影响.如果选择较小的k

《统计学习方法》第三章,k 近邻法

? k 近邻法来分类,用到了 kd 树的建立和搜索 ● 代码 1 import numpy as np 2 import matplotlib.pyplot as plt 3 from mpl_toolkits.mplot3d import Axes3D 4 from mpl_toolkits.mplot3d.art3d import Poly3DCollection 5 from matplotlib.patches import Rectangle 6 import operator 7 i

统计学习方法与Python实现(三)——朴素贝叶斯法

统计学习方法与Python实现(三)——朴素贝叶斯法 iwehdio的博客园:https://www.cnblogs.com/iwehdio/ 1.定义 朴素贝叶斯法是基于贝叶斯定理与特征条件独立假设的分类方法. 对于给定的训练数据集,首先基于特征条件独立假设学习输入输出的联合概率分布.然后基于此模型,对给定的输入x,利用贝叶斯定理求出后验概率最大的输出y,从而进行决策分类. 朴素贝叶斯法学习到的是生成数据的机制,属于生成模型. 设Ω为试验E的样本空间,A为E的事件,B1~Bn为Ω的一个划分,则

K近邻法(KNN)原理小结

K近邻法(k-nearst neighbors,KNN)是一种很基本的机器学习方法了,在我们平常的生活中也会不自主的应用.比如,我们判断一个人的人品,只需要观察他来往最密切的几个人的人品好坏就可以得出了.这里就运用了KNN的思想.KNN方法既可以做分类,也可以做回归,这点和决策树算法相同. KNN做回归和分类的主要区别在于最后做预测时候的决策方式不同.KNN做分类预测时,一般是选择多数表决法,即训练集里和预测的样本特征最近的K个样本,预测为里面有最多类别数的类别.而KNN做回归时,一般是选择平均

scikit-learn K近邻法类库使用小结

在K近邻法(KNN)原理小结这篇文章,我们讨论了KNN的原理和优缺点,这里我们就从实践出发,对scikit-learn 中KNN相关的类库使用做一个小结.主要关注于类库调参时的一个经验总结. 一.scikit-learn 中KNN相关的类库概述 在scikit-learn 中,与近邻法这一大类相关的类库都在sklearn.neighbors包之中.KNN分类树的类是KNeighborsClassifier,KNN回归树的类是KNeighborsRegressor.除此之外,还有KNN的扩展,即限

3.K近邻法

1. k 近邻算法k近邻法(k-nearest neighbor, k-NN) 是一种基本分类与回归方法.  k近邻法的输入为实例的特征向量, 对应于特征空间的点: 输出为实例的类别, 可以取多类. k近邻法假设给定一个训练数据集, 其中的实例类别已定. 分类时, 对新的实例, 根据其k个最近邻的训练实例的类别, 通过多数表决等方式进行预测.因此, k近邻法不具有显式的学习过程. k近邻法实际上利用训练数据集对特征向量空间进行划分, 并作为其分类的“模型”. k值的选择. 距离度量及分类决策规则

用Python从零开始实现K近邻算法

K近邻算法 (或简称kNN)是易于理解和实现的算法,而且是你解决问题的强大工具. http://python.jobbole.com/87407/ 在本教程中,你将基于Python(2.7)从零开始实现kNN算法.该实现主要针对分类问题,将会用鸢尾花分类问题来演示. 这篇教程主要针对Python程序员,或者你可以快速上手Python,并且对如何从零实现kNN算法感兴趣. kNN算法图片,来自Wikipedia,保留所有权利 什么是kNN kNN算法的模型就是整个训练数据集.当需要对一个未知数据实

【黎明传数==&gt;机器学习速成宝典】模型篇04——k近邻法【kNN】(附python代码)

目录 什么是k近邻算法 模型的三个基本要素 构造kd树 搜索kd树 Python代码(sklearn库) 什么K近邻算法(k-Nearest Neighbor,kNN) 引例 假设有数据集,其中前6部是训练集(有属性值和标记),我们根据训练集训练一个KNN模型,预测最后一部影片的电影类型. 首先,将训练集中的所有样例画入坐标系,也将待测样例画入 然后计算待测分类的电影与所有已知分类的电影的欧式距离 接着,将这些电影按照距离升序排序,取前k个电影,假设k=3,那么我们得到的电影依次是<He's N