KNN分类
一、K邻近算法概述
KNN是k nearest neighbor 的简称,即k最邻近,就是找k个最近的实例投票决定新实例的类标。
KNN是一种基于实例的学习算法,它不同于贝叶斯、决策树等算法,KNN不需要训练,当有新的实例出现时,直接在训练数据集中找k个最近的实例,把这个新的实例分配给这k个训练实例中实例数最多类。
KNN也成为懒惰学习,它不需要训练过程,该算法也没有显示学习的过程,并且是是一个无参算法。在类标边界比较整齐的情况下分类的准确率很高。KNN算法需要人为决定K的取值,即找几个最近的实例,k值不同,分类结果的结果也会不同。
优点:理论简单,训练快速精度高、对异常值不敏感
缺点:计算复杂度高、空间复杂度高
训练数据的作用,是生成 KNN 模型。令模型对测试数据进行预测,对比预测结果与测试结果就可以得到模型的准确率,准确率越高,说明模型的泛化能力、性能越好。
(训练数据和测试数据都是打好标记的,也就是这些花的分类是由人工分类好的。)
K近邻算法中,某个样本的类别是与其最近的K个样本类别的众数,某个样本的值(回归问题中)是与其最近的K个样本的平均值。K近邻算法中最重要的三个地方,也是KNN算法的三要素:
(1):k值的选择——(k值的选取我们可以根据交叉检验的方法来选取。)
(2):距离的度量(即何为最近)——(欧式距离,曼哈顿距离,或者是更为一般的Lp距离)
(3):决策规则——(为什么分类用众数,回归用平均值)
二、k-近邻算法的一般流程是:
1. 收集数据
2. 计算待测数据与训练数据之间的距离(一般采用欧式距离)
3. 将计算的距离排序
4. 找出距离最小的k个值
5. 计算找出值中每个类别的频次
6. 返回最高频次的类别
import numpy as np import pandas as pd # 这里直接引入sklearn里的数据集,iris鸢尾花 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split # 切分数据集为训练集和测试集 from sklearn.metrics import accuracy_score # 计算分类预测的准确率 iris = load_iris() df = pd.DataFrame(data = iris.data, columns = iris.feature_names) df['class'] = iris.target df['class'] = df['class'].map({0: iris.target_names[0], 1: iris.target_names[1], 2: iris.target_names[2]}) df.head(10) df.describe() x = iris.data y = iris.target.reshape(-1,1) print(x.shape, y.shape) # 划分训练集和测试集 x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.3, random_state=35, stratify=y) print(x_train.shape, y_train.shape) print(x_test.shape, y_test.shape) # 距离函数定义 def l1_distance(a, b): return np.sum(np.abs(a-b), axis=1) def l2_distance(a, b): return np.sqrt( np.sum((a-b) ** 2, axis=1) ) # 分类器实现 class kNN(object): # 定义一个初始化方法,__init__ 是类的构造方法 def __init__(self, n_neighbors = 1, dist_func = l1_distance): self.n_neighbors = n_neighbors self.dist_func = dist_func # 训练模型方法 def fit(self, x, y): self.x_train = x self.y_train = y # 模型预测方法 def predict(self, x): # 初始化预测分类数组 y_pred = np.zeros( (x.shape[0], 1), dtype=self.y_train.dtype ) # 遍历输入的x数据点,取出每一个数据点的序号i和数据x_test for i, x_test in enumerate(x): # x_test跟所有训练数据计算距离 distances = self.dist_func(self.x_train, x_test) # 得到的距离按照由近到远排序,取出索引值 nn_index = np.argsort(distances) # 选取最近的k个点,保存它们对应的分类类别 nn_y = self.y_train[ nn_index[:self.n_neighbors] ].ravel() # 统计类别中出现频率最高的那个,赋给y_pred[i] y_pred[i] = np.argmax( np.bincount(nn_y) ) return y_pred nn_index = np.argsort(dist) print("dist: ",dist) print("nn_index: ",nn_index) nn_y = y_train[ nn_index[:9] ].ravel() #print(y_train[:8]) print("nn_y: ",nn_y) print(np.bincount(nn_y)) print(np.argmax(np.bincount(nn_y))) # 定义一个knn实例 knn = kNN(n_neighbors = 3) # 训练模型 knn.fit(x_train, y_train) # 传入测试数据,做预测 y_pred = knn.predict(x_test) print(y_test.ravel()) print(y_pred.ravel()) # 求出预测准确率 accuracy = accuracy_score(y_test, y_pred) print("预测准确率: ", accuracy) # 定义一个knn实例 knn = kNN() # 训练模型 knn.fit(x_train, y_train) # 保存结果list result_list = [] # 针对不同的参数选取,做预测 for p in [1, 2]: knn.dist_func = l1_distance if p == 1 else l2_distance # 考虑不同的k取值,步长为2 for k in range(1, 10, 2): knn.n_neighbors = k # 传入测试数据,做预测 y_pred = knn.predict(x_test) # 求出预测准确率 accuracy = accuracy_score(y_test, y_pred) result_list.append([k, 'l1_distance' if p == 1 else 'l2_distance', accuracy]) df = pd.DataFrame(result_list, columns=['k', '距离函数', '预测准确率']) df
也可以通过python中sklearn库直接调用KNeighborsClassifier()
from sklearn.datasets import load_iris iris_dataset = load_iris() from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test = train_test_split( iris_dataset['data'], iris_dataset['target'], random_state=0) from sklearn.neighbors import KNeighborsClassifier #KNN 算法的 K 是指几个最近邻居,可以是1、2、3 或者更多,这里我们构建一个 K = 3 的模型。 knn = KNeighborsClassifier(n_neighbors=3) #并且将训练数据 X_train 和 Y_train 作为参数,构建模型 knn.fit(X_train, y_train) #fit 函数实际上修改的是 knn 对象的内部数据 #使用 knn.predict 方法可以对数据进行预测,为了评估分类器的准确度。 y_pred = knn.predict(X_test) print("Test set predictions:\n {}".format(y_pred)) print("Test set score: {:.2f}".format(np.mean(y_pred == y_test))) # 我们也可以单独对某一数据进行测试 # 尝试一条测试数据 X_try = np.array([[5,4,1,0.7]]) # 对X_try预测结果 prediction = knn.predict(X_try) print("prediction = ",prediction) #即这朵花是山鸢尾
三、K近邻算法的优化
假设我的样本空间是NXM,那么我每次预测一个样本花费的时间是O(NM)+O(KN)。(前者为计算距离的时间,后者为选出K个最近邻居的时间)。在很多大型的场景中N的值会非常的大,采用该算法进行预测花费的时间会非常的长。比较常见的优化方法有:KD树(多维二叉树)。在实际的应用中我们会比较常用Annoy等信息检索中常用的方法。
Annoy算法:
- 用 n 表示现有的文档个数,如果采用暴力搜索的方式,那么每次查询的耗时是
采用合适的数据结构可以有效地减少查询的耗时,在 annoy 算法中,作者采用了二叉树这个数据结构来提升查询的效率,目标是把查询的耗时减少至
- 刚开始的时候,在数据集中随机选择两个点,然后用它们的中垂线来切分整个数据集,于是数据集就被分成了蓝绿两个部分。然后再随机两个平面中各选出一个顶点,再用中垂线进行切分,于是,整个平面就被切成了四份。
- 后续继续采用同样的方式进行切分,直到每一个平面区域最多拥有 K 个点为止。(设K=10)
- 下面,新来的一个点,通过对二叉树的查找,我们可以找到所在的子平面,然后里面最多有 (K = 10 )个点。从二叉树的叶子节点来看,该区域只有 7 个点。
在 ANN 领域,最常见的两个问题是:
- 如果我们想要 Top K 的点,但是该区域的点集数量不足 K,该怎么办?
- 如果真实的 Top K 中部分点不在这个区域,该怎么办?
作者用了两个技巧来解决这个问题:
- 使用优先队列(priority queue):将多棵树放入优先队列,逐一处理;并且通过阈值设定的方式,如果查询的点与二叉树中某个节点比较相似,那么就同时走两个分支,而不是只走一个分支;
- 使用森林(forest of trees):构建多棵树,采用多个树同时搜索的方式,得到候选集 Top M(M > K),然后对这 M 个候选集计算其相似度或者距离,最终进行排序就可以得到近似 Top K 的结果。
总结Annoy算法原理:
构建索引:建立多颗二叉树,每颗二叉树都是随机切分的;
查询方法:
1. 将每一颗树的根节点插入优先队列;
2. 搜索优先队列中的每一颗二叉树,每一颗二叉树都可以得到最多 Top K 的候选集;
3. 删除重复的候选集;
4. 计算候选集与查询点的相似度或者距离;
5. 返回 Top K 的集合
参考链接:https://zhuanlan.zhihu.com/p/454511736

浙公网安备 33010602011771号