[手推算法]KNN总结
k近邻定义
K近邻算法,即K-Nearest Neighbor algorithm,简称KNN算法,可以根据字面意思理解为:K个最近的邻居。因为k近邻是分类算法,找到最近的邻居就能知道自己所在的类别。
用途
k近邻用于解决分类问题。因为需要计算特征之间的距离,所以需要将数据集中的特征数据变成数值型和标称型。
k近邻算法思想:
1.计算出当前未知类型样本点与训练集中所有点的距离(欧氏距离);
2.按照距离大小,将训练集中的点递增排序;
3.选取前k个样本点,计算出前k个样本每个类别出现的频率;
4.频率最高的类别即为当前未知样本预测分类;
由于此算法是计算未知样本与每一个训练点的距离,所以时间复杂度为O(n),之后将学习kd-tree,以降低时间复杂度
import numpy as np import pandas as pd import operator
def create_dataset(): # 四组二维 X = np.array([[1,101],[5,89],[108,5],[155,8]]) y = ['爱情片', '爱情片', '动作片', '动作片'] return X, y
【knn函数注释】
参数说明: test为测试样本(一个向量);
train为训练集(训练样本的特征组成的矩阵);
labels为标签(训练集中每一个样本对应的类型组成的向量);
k为参数,表示选取距离最近的k个样本
函数说明: train为[[1,101],[5,89],[108,5],[115,8]],
test为1个样本[10,45],
这里的操作是将test平铺成[[10,45],[10,45],[10,45],[10,45]],
这样一来,train和test相减得到的矩阵,就可以用于我们计算测试样本到每一个训练样本的距离
def knn(test,train,labels,k): num_train_samples = train.shape[0] test = np.tile(test,(num_train_samples,1)) # np.tile将test复制num_train_samples行 复制1列 diff_set = train-test distance = np.sqrt((diff_set ** 2).sum(axis=1)) # sum(axis=1)表示每一行的所有列求和 sorted_distance_indicies = np.argsort(distance) # 返回数组 [1,0,2,3] 表示下标为1的元素值最小,其次是0,以此类推 class_count = {} # 该字典用来存钱k个元素的类别,以及出现次数 for i in range(k): label = labels[sorted_distance_indicies[i]] # dict.get(key,default=None) #字典的get方法返回指定键的值,如果不在字典中返回默认0 class_count[label] = class_count.get(label,0)+1 print(class_count[label]) # 字典按照value值 降序排列 # key = operator.itemgetter(1) 根据字典的值进行排序 # key = operator.itemgetter(0) 根据字典的键进行排序 # reverse 降序排序字典 sorted_class_count = sorted(class_count.items(),key=operator.itemgetter(1),reverse=True) # 如[('爱情片', 2), ('动作片', 1)] return sorted_class_count[0][0]
train,labels = create_dataset() test = [50,10] knn(test,train,labels,k=3)
KD Tree算法介绍
1. KD Tree算法介绍: KD树就是K个特征维度的树,注意这里的K和KNN中的K的意思不同。KNN中的K代表最近的K个样本,KD树中的K代表样本特征的维数
2. KD Tree算法步骤: 1.建立KD树 2.搜索最近邻 3.KD树预测
3. 如何建立KD Tree:
(1) 从m个样本的n维特征中,分别计算n个特征的方差,选取方差值最大的那一维特征,计算其中位数,中位数所在的那一个样本点,作为根节点;
(2) 将其余样本点划分为左右子树,左子树是取值小于父节点的分类特征取值的所有样本,右子树反之;
(3) 左右字树重复(1)操作,直至所有数据都被建立到KD Tree的节点上;
4. 如何搜索目标点的最近邻点:
(1) 从树的根节点开始,比较当前节点的分割特征值与目标节点的对应值的大小,决定向左或者向右移动;
(2) 待测节点向下搜索,直至到达KD Tree的叶子节点,将该叶子节点保存为“当前最近邻节点”;
(3) 开始回溯过程,即从叶子节点返回到树的根节点,回溯过程如下:
a. 如果当前节点比“当前最近邻节点”距目标节点更近的话,则保存该节点为“当前最近邻节点”;
b. 检查另一个子节点对应的空间是否与 以目标点为球心、目标点与“当前最邻近点”的距离为半径的超球体相交;
如果相交的话,可能在另一个子节点对应的区域,存在点距离目标点更近,于是回溯到另一个子节点去,重新计算最邻近点;
如果不相交的话,直接向上回退;
(4) 当最后一次回退到根节点时,搜索结束,最后的"当前最邻近点"即为目标点的最邻近点
推荐结合《统计学习方法》P44 例3.3 来了解具体搜索过程
5. KD树预测:
(1) 在上述KD树搜索最近邻的基础上,我们获取到了第一个最近邻的样本点,并把它标注为已选;
(2) 开始第二轮KD树最近邻搜索过程,忽略“已选”的节点,获得第二个最近邻的样本点,继续标注为已选;
(3) 重复k轮,这样就得到了目标节点的K个最近邻;
(4) 分类任务就采用用多数表决法,回归任务就用平均数或其他方式;
6. KD树的时间复杂度是处于O(logN)与O(N),这里的N是训练的样本数,KD树更适用于训练样本数远大于样本维度时的情况;
当样本数与维度差不多时,kd-tree搜索与暴力搜索(线性扫描)效率没什么区别
【这里只是了解了kd-tree的算法原理,没有手动实现kd-tree的代码】
【补充】本文的构建KD树算法思路与李航书中不同的原因:
李航书中的knn是最早出现的KNN算法的原理,kd树建立就是特征轮流来。后来对kd树的一个重大改进就是特征的选取,用方差选择后建立的kd树搜索近邻效率更高。
因此现在主流knn类库中kd树建立都不再是轮流而是基于方差选择了了。
如果最大特征方差有相同的话就随机选择一个即可。
KNN如何选择合适的K值: 如果选择较小的K值,就相当于用较小的邻域内的样本点进行预测,如果碰巧里面噪声较多,预测结果就会出错,导致过拟合;
推荐方法: step 1:用交叉验证法,将样本集分为训练集和验证集(如随机分成5份,4份用于训练,1份用于验证),轮流选取一份当验证集,其余几份用来训练, 统计每一轮的正确率,累计求均值,得到的结果即为当前K值的分类准确率;
step 2:更换K值,重复step 1,最终选取平均预测准确率最高的k值作为模型的k值
KNN 算法的优缺点: 1.优点:
(1) 理论成熟,思想简单,可处理数值型或者离散型数据,既可以用来做分类也可以用来做回归
(2) 可用于非线性分类(线性不可分的样本集)
(3) 训练时间复杂度比支持向量机之类的算法低,仅为O(n),kd-tree为O(log n)
(4) 受异常值(离群点)的影响小
2.缺点:
(1) 计算量大,尤其是特征数非常多的时候
(2) 样本分布不平衡的时候,对于小类别的预测准确性低
(3) 使用懒散学习方法,基本上不学习,每次预测都要计算一遍在样本集中的最邻近点