k-Nearest Neighbors(k近邻)

前言–AI发展到现在,各类算法层出不穷,对于想要达到的目标,完成的任务,我们有很多的方法可以选择,而且以实际经验来看用不同的方法会有不同的结果。但是没有免费的午餐定理(no free lunch theorem,NFL)告诉我们,在没有实际的背景下,没有哪一种算法比随机胡猜的效果好, 所以有的只是基于不同应用背景下的各类算法。

kNN基本思想及python实现
kNN算是在机器学习中最基础的算法了,其算法核心一言以蔽之:所谓近朱者赤,近墨者黑,物以类聚,人以群分。

k-Nearest Neighbors(k近邻)_第1张图片

如果一个样本在特征空间中的k个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。打个比方,如果与你关系好的(或最相似的)排名前k个好朋友大多数都喜欢机器学习,那么我可以做出预测:你本身是非常喜欢机器学习的。

k-Nearest Neighbors(k近邻)_第2张图片

从这里出发,很自然的就会想到,如何排名确定出这的k个好朋友呢?当然了,和复杂的人类不同,数据之间的相似度排名是比较容易,最常用的就是欧式距离—— ∑ ( X i − Y i ) 2 2 \sum \frac{(Xi-Yi)^2}{2} 2(XiYi)2 。有了度量方法,kNN算法也就初步成形了。

k-Nearest Neighbors(k近邻)_第3张图片

简单来说就是计算输入数据D与所有样本点的距离,然后取最小的前k个样本的标签的统计即可。

算法实现:(python)

from numpy import *
import operator#运算符模块

def classify0(inX, dataSet, labels, k):
    dataSetSize = dataSet.shape[0]
    diffMat = tile(inX, (dataSetSize,1)) - dataSet#距离计算使用欧式距离公式
    sqDiffMat = diffMat**2
    sqDistances = sqDiffMat.sum(axis=1)#行向量相加
    distances = sqDistances**0.5
    sortedDistIndicies = distances.argsort()#逆序排序返回下标
    classCount={}          
    for i in range(k):
        voteIlabel = labels[sortedDistIndicies[i]]
        classCount[voteIlabel] = classCount.get(voteIlabel,0)+1  #此处+1是为了计数
    sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#sorted得到结果,其中itemgetters是运算符模块中对元组进行逆序
    return sortedClassCount[0][0]

def createDataSet():
    group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
    labels = ['A','A','B','B']
    return group, labels

group,labels=createDataSet()
print(classify0([0,0],group,labels,3))

k-Nearest Neighbors(k近邻)_第4张图片
可以看出kNN 原理简单而且容易实现,结果精度高,无需估计参数,无需训练模型!!而且不仅像实例那样用于分类,还可以应用到回归问题,比如通过了解与你关系好的k个好朋友一个月的生活开销,可以预测出你一个月的花费。

  • 分类时一般采用多数表决投票法,即训练集里和预测的样本特征最近的K个样本,预测为里面有最多类别数的类别。
  • 回归时,一般是选择平均法,即最近的K个样本的样本输出的平均值作为回归预测值。

####不过凡事有利也必有弊,其实我们也可以发现有关kNN的不少问题

好朋友的判断方法?距离度量的选择。

你可以用你和朋友间共同爱好的数目来排名,也可以用你们认识的时间长短来排名,或者可以用性格是否合拍来判断。同理kNN的距离度量方法也有多种,主要有欧式距离,曼哈顿距离,切比雪夫距离,马氏距离,巴氏距离等等,通常是用欧式距离,但还是得视实际情况选择,如果我想知道你是否喜欢机器学习,那么我就该去了解你朋友是否喜欢机器学习而不是他们是不是爱爬山,比如当变量变多时(高维度)欧式距离的区分能力会随着变量数增多而变差,再比如处理文本分类问题,用余弦度量相似度的反而更合适,效果更好。

好朋友排名太麻烦?kNN时间开销问题。

为了得到结果,我每次都得把你的所有朋友和你一一进行比较才能得出结果!!万一你是一个非常受欢迎的,朋友众多的人呢?优点中提到kNN无需训练,准确的说应该是它没有显式的训练过程,事实上kNN作为懒惰学习(lazy learning)的著名代表,它在训练阶段仅仅是把样本保存起来,等收到测试样本后再进行处理,所以训练时间开销为零。然而处理过程需要计算所有的距离并排名才能求得它的k个最近点,这就造成了kNN计算量大,非常占用内存的问题,对于大规模的数据,效率惨不忍睹。
就样本数据本身来改进,由于kNN善于处理稀有事件和多类别问题(MLkNN)所以适合类域的交叉或重叠较多的。样本容量比较大的数据集。另外可以考虑压缩训练样本量,像浓缩技术(condensing)和编辑技术(editing)等都能大幅减少训练样本量,同时又能很好的保持分类精度。

KD树
就算法的改进,有层次knn(HKNN)和KDtree knn等。它们都是通过对树进行剪枝达到提高搜索效率的目的,hknn的剪枝原理是,如果目标点d与当前最近邻点x的距离,小于d与某结点中心的距离加上该中心的半径,那么该中心中的任何一点到目标点的距离都会大于d与当前最近邻点的距离,从而它们不可能是最近邻点(K近邻问题类似于它),这个结点可以被排除掉。 KDtree是一种便于对k维空间进行快速检索的数据结构,它对样本集所在超平面进行划分成子超平面,原理图如下(感兴趣的可以参见scikit-learn库的neighbors源码,而且scikit-learn还提供了一种球树(BallTree)的方法)
k-Nearest Neighbors(k近邻)_第5张图片
k-Nearest Neighbors(k近邻)_第6张图片
KD树详解:http://blog.csdn.net/app_12062011/article/details/51986805

此外我还想介绍一种精简的最近邻(condensed nearest neighbor)方法。特殊情况k=1时,空间被划分成Voronoi图(Voronoi tesselation)。它以分段线性的方式逼近判别式,并且只需要保存定义判别式的实例。类区域内部的实例不必作为它的同一类最近邻存放,并且它的缺失值不会导致任何错误。
k-Nearest Neighbors(k近邻)_第7张图片
k-Nearest Neighbors(k近邻)_第8张图片
精简最近邻是一种贪心算法,旨在最小化训练误差和存放子集规模度量的复杂度。

取前几个好朋友才比较好呢?k的取值问题。

这个问题是很重要的,因为如果k取值太小,那么结果会对噪音样本点显得异常敏感,比如你的一个好朋友特别不喜欢机器学习。如果k值过大,那么你的朋友们众说纷纭,一会说你喜欢机器学习,一会又说你爱爬山,喜欢打王者,擅长音乐,热爱cos…甚至一些不熟的朋友也会来插上一脚。也就是说这些近邻中可能包含太多的其它类别的点,而且距离较远的点对结果会有影响,使错误可能性增大。那那那…怎么选择比较好??从实际编码来说,一般是采用交叉检验( Cross Validation)来确定(以k=1为基准,交叉验证一般分为:简单交叉验证,double-fold CV(2折交叉);10-fold交叉和LOO(leave one out)CV(留一法交叉)),而且k一般低于训练样本数的平方根,不大于20。有时候也可以采用对距离加权,以降低k值设定的影响。
k-Nearest Neighbors(k近邻)_第9张图片

好朋友重要性不能简单排名就确定?样本不平衡问题。

假如你有一个好朋友和你关系特别特别好,而其他人关系则一般,那么在判断你是否喜欢机器学习的时候,其他不怎么了解你的人占了大多数,所以做出错误判断的可能性极大。也就是说当数据集样本不平衡时(一个类的样本容量很大,而其他类样本容量很小),有可能导致当输入一个新样本时,该样本的k个邻居中大容量类的样本占多数。 由于kNN只计算“最近邻居的”样本,所以误分的可能性很大。所以在这种情况下选取好朋友的投票法就不那么适用了,距离更近的近邻,关系更好的朋友也许更应该决定最终的分类,Citation-kNN应运而出(基于远近加权投票法,越近权重越大。另外还有基于离散度加权,基于综合加权的等等)。
假如还有有些嫉妒你的美色,仇视你的才能,你某些方面的敌人等等也混入了好友名单,那么这些人的观点不一定公平公正和客观,对结果也会有一定的影响。也就是指在训练数据集中,有些样本可能是更值得依赖的。所以可以人为的给不同的样本施加不同的权重,加强值得信赖样本的权重,降低不可信赖样本的权重。

除了目标结果外,其他信息都得不到?kNN的最大的问题!

没错,就是可解释性差。它无法给出任何数据的基础结构信息,无法知道平均实例样本和典型实例样本具有什么特征,即得不到数据的内在含义。但是这也没办法,kNN算法的本身特质已经决定了这一点,如果想知道数据的内在含义,那就只有另辟蹊径,寻找其他的算法了。

kNN应用:
scikit-learn KNneighborsClassifier参数说明:
sklearn.neighbors.KNeighborsClassifier(n_neighbors=5, weights=‘uniform’, algorithm=‘auto’, leaf_size=30, metric=‘minkowski’, p=2,metric_params=None, n_jobs=1)

	n_neighbors:选取最近的k个点,默认为5。
	weights:默认是uniform,均等的权重。参数还可以是distance,不均等的权重,即距离近的点影响更大。或者自定义函数也可以。
	algorithm:快速搜索算法,默认是auto,即自动根据实际来决定。还有ball_tree、kd_tree、brute等方法。ball tree是为克服kd树高维时效率低下而产生,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
	leaf_size:树的大小,默认30。
	metric:距离度量选择,默认是minkowski。
	p:p=1是曼哈顿距离,p=2的欧氏距离。
	metric_params:距离公式的其他关键参数,默认为None。
	n_jobs:并行处理。默认为1,临近点搜索。如果为-1,表示CPU的所有cores都并行工作。

接下来使用Iris(鸢尾花数据集)来小应用一下,这是一个含3种鸢尾花(setosa, versicolor, virginica)花朵的萼片和花瓣的长宽数据集。
k-Nearest Neighbors(k近邻)_第10张图片

先看一下这个数据集

from sklearn import datasets
iris=datasets.load_iris()#加载数据集
print(iris.data,iris.target,iris.target_names)#查看数据集数据和类别

[[ 5.1 3.5 1.4 0.2]
[ 4.9 3. 1.4 0.2]
[ 4.7 3.2 1.3 0.2]
[ 4.6 3.1 1.5 0.2]
[ 5. 3.6 1.4 0.2]}

将这些数据可视化看一下

import matplotlib.pyplot as plt
x=iris.data[:,0]
y=iris.data[:,1]
species=iris.target
x_min,x_max=x.min()-0.5,x.max()+0.5
y_min,y_max=y.min()-0.5,y.max()+0.5

plt.figure()
plt.title('基于iris数据集的kNN')#设置题目
plt.scatter(x,y,c=species)

plt.xlim(x_min,x_max)#设置x轴刻度的取值范围
plt.ylim(y_min,y_max)
plt.xlabel('萼片长度')#设置x轴标签
plt.ylabel('萼片宽度')
plt.xticks(())#设置x轴文本
plt.yticks(())
plt.show()

k-Nearest Neighbors(k近邻)_第11张图片
汉字显示失败??加上一句就ok了。

from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] 
mpl.rcParams['axes.unicode_minus'] = False 

k-Nearest Neighbors(k近邻)_第12张图片

改变x,y查看花瓣数据

x=iris.data[:,2]
y=iris.data[:,3]

k-Nearest Neighbors(k近邻)_第13张图片

然后可以开始fit了。因为Iris数据集是按照种类来排序的,所以先最好用np.random.permutation打乱其元素的顺序。

from sklearn import datasets
import matplotlib.pyplot as plt
import numpy as np
from sklearn.neighbors import KNeighborsClassifier#导入分类器

np.random.seed(0)
iris=datasets.load_iris()

x=iris.data
y=iris.target
i=np.random.permutation(len(iris.data))#打乱顺序

x_train=x[i[:-10]]#前140做训练集,剩余10做测试集
y_train=y[i[:-10]]
x_text=x[i[-10:]]
y_text=y[i[-10:]]

knn=KNeighborsClassifier()
knn.fit(x_train,y_train)
print(knn.predict(x_text),y_text)

[1 2 1 0 0 0 2 1 2 0]
[1 1 1 0 0 0 2 1 2 0]

准确还可以,尝试画出边界看看。

k-Nearest Neighbors(k近邻)_第14张图片

#画边界代码
from sklearn import datasets
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np
from sklearn.neighbors import KNeighborsClassifier#导入分类器
from pylab import *
mpl.rcParams['font.sans-serif'] = ['SimHei'] 
mpl.rcParams['axes.unicode_minus'] = False 

iris=datasets.load_iris()
x=iris.data[:,:2]
y=iris.target

x_min,x_max=x[:,0].min()-0.5,x[:,0].max()+0.5
y_min,y_max=x[:,1].min()-0.5,x[:,1].max()+0.5

cmap_light=ListedColormap(['#AAAAFF','#AAFFAA','#FFAAAA'])
h=0.02
xx,yy=np.meshgrid(np.arange(x_min,x_max,h),np.arange(y_min,y_max,h))

knn=KNeighborsClassifier()
knn.fit(x,y)
z=knn.predict(np.c_[xx.ravel(),yy.ravel()])
z=z.reshape(xx.shape)
plt.figure()
plt.pcolormesh(xx,yy,z,cmap=cmap_light)

plt.title('基于Iris数据集的kNN')
plt.scatter(x[:,0],x[:,1],c=y)
plt.xlim(xx.min(),xx.max())
plt.ylim(yy.min(),yy.max())
plt.xlabel('萼片长度')
plt.ylabel('萼片宽度')
plt.show()

同样的可以画出花瓣长度的分类边界。用sklearn就是这么简单…摊手。

k-Nearest Neighbors(k近邻)_第15张图片

kNN回归
KNeighborsRegressor参数:
KNeighborsRegressor(algorithm=‘auto’, leaf_size=30, metric=‘minkowski’,metric_params=None, n_jobs=1, n_neighbors=5, p=2,weights=‘uniform’)

import numpy as np
import matplotlib.pyplot as plt
from sklearn import neighbors

np.random.seed(0)
X = np.sort(5 * np.random.rand(40, 1), axis=0)
T = np.linspace(0, 5, 500)[:, np.newaxis]
y = np.sin(X).ravel()

y[::5] += 1 * (0.5 - np.random.rand(8))#噪音

n_neighbors = 5

for i, weights in enumerate(['uniform', 'distance']):
    knn = neighbors.KNeighborsRegressor(n_neighbors, weights=weights)
    y_ = knn.fit(X, y).predict(T)

    plt.subplot(2, 1, i + 1)
    plt.scatter(X, y, c='k', label='data')
    plt.plot(T, y_, c='g', label='prediction')
    plt.axis('tight')
    plt.legend()
    plt.title("KNeighborsRegressor (k = %i, weights = '%s')" % (n_neighbors,
                                                                weights))

plt.show()

k-Nearest Neighbors(k近邻)_第16张图片
虽然技术革新一代一代,目前机器学习和深度学习的发展境况空前,各种算法都很奇妙也拥有很好的效果。但是!在实际的开发应用过程中,knn的使用还是非常的广泛,如推荐系统的召回部分等等,所以打基础必不可少。

你可能感兴趣的:(机器学习)