机器学习(4): KNN 算法

1. 综述

     1.1 Cover和Hart在1968年提出了最初的邻近算法
     1.2 分类(classification)算法
     1.3 输入基于实例的学习(instance-based learning), 懒惰学习(lazy learning)
 
 
3. 算法详述
 
     3.1 步骤:
     为了判断未知实例的类别,以所有已知类别的实例作为参照
     选择参数K
     计算未知实例与所有已知实例的距离
     选择最近K个已知实例
     根据少数服从多数的投票法则(majority-voting),让未知实例归类为K个最邻近样本中最多数的类别
 
     3.2 细节:
     关于K
     关于距离的衡量方法:
         3.2.1 Euclidean Distance 定义
 
               
 
4. 算法优缺点:
     4.1 算法优点
          简单
          易于理解
          容易实现
          通过对K的选择可具备丢噪音数据的健壮性
          
 
简单KNN 算法实现:
from sklearn import datasets
from collections import Counter  # 为了做投票
from sklearn.model_selection import train_test_split
import numpy as np

# 导入iris数据
iris = datasets.load_iris()
X = iris.data
y = iris.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=2003)
def euc_dis(instance1, instance2):
    """
    计算两个样本instance1和instance2之间的欧式距离
    instance1: 第一个样本, array型
    instance2: 第二个样本, array型
    """
    # TODO
    dist = np.sqrt(sum((instance1 - instance2)**2))
    return dist


def knn_classify(X, y, testInstance, k):
    """
    给定一个测试数据testInstance, 通过KNN算法来预测它的标签。 
    X: 训练数据的特征
    y: 训练数据的标签
    testInstance: 测试数据,这里假定一个测试数据 array型
    k: 选择多少个neighbors? 
    """
    # TODO  返回testInstance的预测标签 = {0,1,2}
    #时间复杂度 O(N):    N:number of samples
    distances = [euc_dis(x, testInstance) for x in X] #若N很大,计算欧式距离消耗的测试时间非常大的时间,take forever~ (KNN的缺点)

    #argsort: O(NlogN)
    #如何优化? priority queue O(NlogK)
    kneighbors = np.argsort(distances)[:k]
    count = Counter(y[kneighbors])
    #print(count)可以显示出来每个样本出现了多少次
    return count.most_common()[0][0]#取出出现最多的值
# 预测结果。    
predictions = [knn_classify(X_train, y_train, data, 3) for data in X_test]
correct = np.count_nonzero((predictions==y_test)==True)
#accuracy_score(y_test, clf.predict(X_test))
print ("Accuracy is: %.3f" %(correct/len(X_test)))
Accuracy is: 0.921

 

https://zhuanlan.zhihu.com/p/71785246
 
 
大佬总结:

kNN 的花式用法

 

kNN (k-nearest neighbors)作为一个入门级模型,因为既简单又可靠,对非线性问题支持良好,虽然需要保存所有样本,但是仍然活跃在各个领域中,并提供比较稳健的识别结果。

说到这里也许你会讲,kNN 我知道啊,不就是在特征空间中找出最靠近测试样本的 k 个训练样本,然后判断大多数属于某一个类别,那么将它识别为该类别。

这就是书上/网络上大部分介绍 kNN 的说辞,如果仅仅如此,我也不用写这篇文章了。事实上,kNN 用的好,它真能用出一朵花来,越是基础的东西越值得我们好好玩玩,不是么?

第一种:分类

避免有人不知道,还是简单回顾下 kNN 用于分类的基本思想。

针对测试样本 Xu,想要知道它属于哪个分类,就先 for 循环所有训练样本找出离 Xu 最近的 K 个邻居(k=5),然后判断这 K个邻居中,大多数属于哪个类别,就将该类别作为测试样本的预测结果,如上图有4个邻居是红色,1是绿色,那么判断 Xu 的类别为“红色”。

 

第二种:回归

根据样本点,描绘出一条曲线,使得到样本点的误差最小,然后给定任意坐标,返回该曲线上的值,叫做回归。那么 kNN 怎么做回归呢?

你有一系列样本坐标(xi, yi),然后给定一个测试点坐标 x,求回归曲线上对应的 y 值。用 kNN 的话,最简单的做法就是取 k 个离 x 最近的样本坐标,然后对他们的 y 值求平均:

绿色是拟合出来的曲线,用的是 sklearn 里面的 KNeighborsRegressor,可以看得出对非线性回归问题处理的很好,但是还可以再优化一下,k 个邻居中,根据他们离测试点坐标 x 的距离 d 的倒数 1/d 进行加权处理:

w = [ 1 / d[i] for i in range(k) ]
y = sum([ (w[i] * y[i]) for i in range(k) ]) / sum(w)

如果 x 刚好和某样本重合,di = 0 的话,1/d 就正无穷了,那么接取该样本的 y 值,不考虑其他点(sklearn的做法),这样得到的 Y 值就相对比较靠谱了:

这样误差就小多了,前面不考虑距离y值平均的方法在 sklearn 中称为 uniform,后一种用距离做权重的称为 distance。

这曲线拟合的效果非常漂亮,你用梯度下降或者最小二乘法做拟合根本达不到这样的效果,即便支持向量回归 SVR 也做不到这么低的误差率。如果你觉得有些过拟合的话,可以调节 K 的值,比如增加 K 值,可以让曲线更加平滑一些。

更好的做法是 wi 设置为 exp(-d) ,这样 d=0 的时候取值 1,d 无穷大的时候,接近 0:

w[i] = math.exp(-d[i])

这样即 x 和某个训练样本重合或者非常接近也不会把该 wi 弄成无穷大,进而忽略其他样本的权重,避免了 sklearn 里面那种碰到离群点都非要过去绕一圈的问题,曲线就会更平滑。

 

第三种:One-class 识别

One-class 分类/识别又称为:异常点/离群点检测,这个非常有用。假设我们的 app 需要识别5种不同的用户手势,一般的分类器只会告诉你某个动作属于 1-5 哪个类型,但是如果是用户进行一些非手势的普通操作,我们需要识别出来“不属于任何类型”,然后需要在手势模块中不进行任何处理直接忽略掉。

这个事情用传统分类器非常困难,因为负样本是无穷多,多到没法列举所有额外的手势,我们只能收集正样本。这和 0-9 数字手写识别是一样的,比如用户写了个 A 字母,我们需要判断某个输入图像不是 0-9 中任何一个,但是我们除了 0-9 的样本外没法枚举所有例外的可能。

这时候 One-class 识别器一直扮演着举足轻重的作用,我们将 0-9 的所有样本作为“正样本”输入,测试的时候检测检测测试值是否也属于同类别,或者属于非法的负类别。kNN 来做这件事情是非常容易的,我们用 NN-d 的本地密度估计方法:

方法是对待测试样本 z ,现在训练样本中找到一个离他最近的邻居 B,计算 z 到 b 点的距离为 d1,然后再在训练样本中找到一个离 B 最近的点 C,计算 BC 距离为 d2,如果:

d1 <= alpha * d2     # alpha 一般取 1

那么接受 z 样本(识别为正类别),否则拒绝它(识别为负类别)。这个方法比较简单,但是如果局部样本太密集的话,d2 非常小,容易识别为负类别被拒绝。所以更成熟的做法是在训练样本中找到 k 个离 B 最近的样本点 C1 - Ck,然后把 d2 设置成 C1 - Ck 到 B 的距离的平均值。这个方法称为 kNN-d,识别效果比之前只选一个 C 的 NN-d 会好很多。

进一步扩展,你还可以选择 j 个离 z 最近的 B 点,用上面的方法求出 j 个结果,最后投票决定 z 是否被接受,这叫 j-kNN-d 方法,上面说到的方法就是 j = 1 的特殊情况。

对比 SVM 的 ONE_CLASS 检测方法,(j) kNN-d 有接近的识别效果,然而当特征维度增加时,SVM 的 ONE_CLASS 检测精度就会急剧下降,而 (j) kNN-d 模型就能获得更好的结果。

LIBSVM 里的三大用法:分类,回归,ONE_CLASS(离群点检测),同时也是监督学习中的三类主要问题,这里我们全部用 kNN 实现了一遍,如果你样本不是非常多,又不想引入各种包依赖,那么 kNN 是一个最简单可靠的备用方案。

 
posted @ 2017-03-06 14:56  静悟生慧  阅读(575)  评论(0编辑  收藏  举报