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 领域,最常见的两个问题是:

    1. 如果我们想要 Top K 的点,但是该区域的点集数量不足 K,该怎么办?
    2. 如果真实的 Top K 中部分点不在这个区域,该怎么办?

  作者用了两个技巧来解决这个问题:

    1. 使用优先队列(priority queue):将多棵树放入优先队列,逐一处理;并且通过阈值设定的方式,如果查询的点与二叉树中某个节点比较相似,那么就同时走两个分支,而不是只走一个分支;
    2. 使用森林(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

 

posted @ 2022-03-16 23:28  hungry_J  阅读(22)  评论(0)    收藏  举报