KNN

简介

领近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。

KNN方法既可以做分类,也可以做回归,这点和决策树算法相同。KNN做回归和分类的主要区别在于最后做预测时候的决策方式不同。

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

本文所写内容主要为分类算法。

一、KNN算法的基本流程

kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。
KNN算法基本流程
如上图所示,在总体的类别里面共有两类,分别为class1(图中以三角形代替)以及class2(图中用方框代替)。现在有一个点所处的位置为问号处。

  • 假如令K=1,在问号处找到最近邻的1个点为方框,根据投票选择,那么这个目标点所属类别应该为方块所代表的class2。
  • 假如令K=2,在问号处找到最近领的5个点,包含了两个方框和三个三角形,那么这个目标点所属的类别应该为三角所代表的class1。

二、KNN三要素

距离度量k值的选择分类决策规则是k近邻法的三个基本要素。根据选择的距离度量(如曼哈顿距离或欧氏距离),可计算测试实例与训练集中的每个实例点的距离,根据k值选择k个最近邻点,最后根据分类决策规则将测试实例分类。

1. 距离度量

给定测试对象 I t e m Item Item,计算它与训练集中每个对象的距离。

特征空间中的两个实例点的距离是两个实例点相似程度的反映。常用的距离包括了闵可夫斯基距离(Minkowski distance,Lp
Lp​距离)
曼哈顿距离(Manhattan distance,L1)欧氏距离(Euclidean distance,L2),或者从另外的角度看,皮尔逊相关系数,余弦相似度的倒数也可以作为距离的代表。但是KNN中,较为常用的距离度量为欧氏距离。

关于常用的距离度量,可以查看: 常用的距离以及相似度的计算 —https://blog.csdn.net/weixin_45611266/article/details/101619539

2. k值的选择

圈定距离最近的 k k k个训练对象,作为测试对象的近邻。

k值即为选择最近邻的k个点进行观察。K值的选择会对k近邻法的结果产生重大影响。在应用中,k值一般取一个比较小的数值,通常采用交叉验证法来选取最优的k值。

3. 分类决策规则

根据这k近邻归属的主要类别,来对测试对象进行分类。

k近邻法中的分类决策规则往往是投票法,由输入实例的k个邻近的训练实例中的多数类,决定输入实例的类。
但是,在有一些时候,也会为投票的规则加上一个系数,系数为距离的倒数。

三、KNN的算法手写代码实现(Python)

举一个具体的例子:
对某一个电影进行分类:

序号电影名称搞笑镜头拥抱镜头打斗镜头电影类型
1宝贝当家4529喜剧片
2美人鱼21175喜剧片
3澳门风云354911喜剧片
4功夫熊猫339031喜剧片
5谍影重重5257动作片
6叶问33265动作片
7伦敦陷落2355动作片
8我的特工爷爷6421动作片
9奔爱7464爱情片
10夜孔雀9398爱情片
11代理情人9382爱情片
12新步步惊心83417爱情片
13唐人街探案23317

上面数据集中,已知前面12部电影的分类以及所使用的的三个标签:搞笑镜头、拥抱镜头、打斗镜头。已知前面12部电影的特征,以及《唐人街探案》的特征,对最后一部电影《唐人街探案》进行分类。

首先,将《唐人街探案》的特征和其他一部电影的特征求欧氏距离:
E u c l i d e a n   D i s t a n c e ( X , Y ) = ∑ 1 N ( x i − y i ) 2 Euclidean \ Distance(X,Y)= \sqrt{\sum_{1}^N (x_i-y_i)^2} Euclidean Distance(X,Y)=1N(xiyi)2
例如《宝贝当家》与《唐人街探案》的距离为
E u c l i d e a n   D i s t a n c e ( X , Y ) = ( 45 − 23 ) 2 + ( 2 − 3 ) 2 + ( 9 − 17 ) 2 = 23.430749027719962 Euclidean \ Distance(X,Y) = \sqrt{(45-23)^2+(2-3)^2+(9-17)^2}=23.430749027719962 Euclidean Distance(X,Y)=(4523)2+(23)2+(917)2 =23.430749027719962
可得:

序号电影名称与唐人街探案的距离电影类型
0宝贝当家23.430749喜剧片
1美人鱼18.547237喜剧片
2澳门风云332.140317喜剧片
3功夫熊猫321.470911喜剧片
4谍影重重43.874822动作片
5叶问352.009614动作片
6伦敦陷落43.416587动作片
7我的特工爷爷17.492856动作片
8奔爱47.686476爱情片
9夜孔雀39.661064爱情片
10代理情人40.570926爱情片
11新步步惊心34.438351爱情片

假定参数K=5,那么就对上面的距离进行排序,并找出前5个:

序号电影名称与唐人街探案的距离电影类型判断
7我的特工爷爷17.492856动作片
1美人鱼18.547237喜剧片
3功夫熊猫321.470911喜剧片
0宝贝当家23.430749喜剧片
2澳门风云332.140317喜剧片
11新步步惊心34.438351爱情片×
9夜孔雀39.661064爱情片×
10代理情人40.570926爱情片×
6伦敦陷落43.416587动作片×
4谍影重重43.874822动作片×
8奔爱47.686476爱情片×
5叶问352.009614动作片×

统计前5个的数量比:

类型个数
动作片1
喜剧片4
爱情片0

显然,在这里就直接可以将《唐人街探案》归为喜剧片一类

运用代码来判断如下:Python

import pandas as pd
movie_data = [["宝贝当家",45, 2, 9, "喜剧片"],
              ["美人鱼",21, 17, 5, "喜剧片"],
              ["澳门风云3",54, 9, 11, "喜剧片"],
              ["功夫熊猫3",39, 0, 31, "喜剧片"],
              ["谍影重重",5, 2, 57, "动作片"],
              ["叶问3",3, 2, 65, "动作片"],
              ["伦敦陷落",2, 3, 55, "动作片"],
              ["我的特工爷爷",6, 4, 21, "动作片"],
              ["奔爱",7, 46, 4, "爱情片"],
              ["夜孔雀",9, 39, 8, "爱情片"],
              ["代理情人",9, 38, 2, "爱情片"],
              ["新步步惊心",8, 34, 17, "爱情片"],
              ['唐人街探案',23,3,17,'?']]

movie_data = pd.DataFrame(movie_data,columns=['电影名称','搞笑镜头','拥抱镜头','打斗镜头','电影类型'])

def KNNClassifier(k,train_data=movie_data):
    '''
    func : KNN预测电影的类型
    params : 
        k : k值
        train_data : 原始数据
    return : print电影类型
    '''
    feature_data =movie_data.iloc[:-1,1:4] 
    labels = movie_data.iloc[:-1,-1]    # 筛选出训练集的特征和标签
    
    
    new_data=movie_data.iloc[-1,1:4]     # 找出测试集的特征
    
    distance = ((feature_data.iloc[:,:]-new_data)**2).sum(axis = 1)**0.5 # 计算电影之间的欧氏距离
    dist=pd.DataFrame({'distance':distance,'labels':labels})
    
    dr = dist.sort_values(by = 'distance')[:k]    # 找出最近邻的k部电影
    
    re = dr.loc[:,'labels'].value_counts()   # 统计电影的类型的个数
    
    result=re.index[0]   # 找出k值下个数最多的电影类型

    print('{}在K近邻(K值为 {} )的预测下,预测为"{}"'.format(movie_data.iloc[-1,0],k,result))
    
    
if __name__=='__main__':
    KNNClassifier(5)

输出结果:

唐人街探案在K近邻(K值为 5 )的预测下,预测为"喜剧片"

数据来源于 saltriver‘s Blog——K最近邻算法(KNN)

四、KNN算法在SKlearn中的调用

Nearest Neighbors在SKlearn——https://scikit-learn.org/stable/modules/neighbors.html#nearest-neighbors-classification

class sklearn.neighbors.KNeighborsClassifier (n_neighbors=5, weights=’uniform’,
algorithm=’auto’, leaf_size=30, p=2, metric=’minkowski’, metric_params=None, n_jobs=None, **kwargs)

KNN中的重要参数:

  • n_neighbors : k值,即为近邻的个数
  • weights :⽤于决定是否使⽤距离作为惩罚因⼦的参数,默认是 “uniform”(或者是“auto”’)
    可能输⼊的值有:
    “uniform”:表示⼀点⼀票
    “distance”:表示以每个点到测试点的距离的倒数计算该点的距离所占的权重,使得距离测试点更
    近的样本点⽐离测试点更远的样本点具有更⼤的影响⼒。
  • algorithm:用于计算最近邻的算法
    可输入的值:
    ‘ball_tree’ will use :class:BallTree
    ‘kd_tree’ will use :class:KDTree
    ‘brute’对应蛮力实现
    ‘auto’则会在上面三种算法中做权衡,选择一个拟合最好的最优算法。
  • leaf_size:将叶子的大小传递给BallTree或者是KDTree,默认值为30
  • p =2, metric = ’minkowski’:距离度量,默认为p=2的闵式距离,即为欧氏距离

下面举例用乳腺癌数据集来展示KNNSKlearn中的具体调用:

import pandas as pd

from sklearn.neighbors import KNeighborsClassifier       # 读取sklearn的KNN分类器
from sklearn.datasets import load_breast_cancer          # 读取乳腺癌症数据集
from sklearn.model_selection import train_test_split     # 读取训练集测试集的划分模块

读取数据并且划分训练集、测试集

data = load_breast_cancer()
X = data.data
y = data.target

print(data.feature_names)

name = ['平均半径','平均纹理','平均周⻓','平均⾯积',
        '平均光滑度','平均紧凑度','平均凹度',
        '平均凹点','平均对称','平均分形维数',
        '半径误差','纹理误差','周⻓误差','⾯积误差',
        '平滑度误差','紧凑度误差','凹度误差',
        '凹点误差','对称误差','分形维数误差',
        '最差半径','最差纹理', '最差的边界',
        '最差的区域','最差的平滑度', '最差的紧凑性',
        '最差的凹陷','最差的凹点', '最差的对称性','最差的分形维数']

X = pd.DataFrame(X,columns=name)
Xtrain,Xtest,Ytrain,Ytest = train_test_split(X,y,test_size=0.3) # 划分训练集测试集

训练并且获得对测试集进行测试的准确度

clf = KNeighborsClassifier(n_neighbors=4)
clf = clf.fit(Xtrain,Ytrain)
score = clf.score(Xtest,Ytest)
print(score)

# 所得结果为: 0.9005847953216374

# 找出⼀个数据点的最近邻(返回距离以及最近的几个点的距离)
clf.kneighbors(Xtest.iloc[[30,20],:],return_distance=True)
#所得结果如下:
#(array([[15.96377519, 22.40015872, 25.32880508, 28.62115526],
#        [17.52571761, 22.0766338 , 25.89167197, 26.20683157]]),
# array([[ 99, 182,  10,  18],
#        [  5, 396,  41, 329]], dtype=int64))

当然也可以通过学习曲线来找到最优的K值选择

# 学习曲线
import matplotlib.pyplot as plt

score = []
krange = range(1,20)

for i in krange:
    clf = KNeighborsClassifier(n_neighbors=i)
    clf = clf.fit(Xtrain,Ytrain)
    score.append(clf.score(Xtest,Ytest))

plt.plot(krange,score)

五、KNN算法的优缺点

1.优点
  1. 简单,易于理解,易于实现,无需估计参数,无需训练;
  2. 适合对稀有事件进行分类;
  3. 特别适合于多分类问题(multi-modal,对象具有多个类别标签), kNN比SVM的表现要好。
2.缺点
  1. 当样本不平衡时,如一个类的样本容量很大,而其他类样本容量很小时,有可能导致当输入一个新样本时,该样本的K个邻居中大容量类的样本占多数。 该算法只计算“最近的”邻居样本,某一类的样本数量很大,那么或者这类样本并不接近目标样本,或者这类样本很靠近目标样本。无论怎样,数量并不能影响运行结果。
  2. 该方法的另一个不足之处是计算量较大,因为对每一个待分类的文本都要计算它到全体已知样本的距离,才能求得它的K个最近邻点。
  3. 抗噪性较弱,对噪声数据(异常值)较为敏感。最近邻分类器基于局部信息进⾏预测,⽽决策树和基于规则的分类器则试图找到⼀个拟合整个输⼊空间的全局模型。正是因为这样的局部分类决策,最近邻分类器(k很⼩时)对噪声⾮常敏感。

五、Kd树

参考文档:
机器学习系列之——Knn算法 kd树详解

KNN的算法中,对特征空间进行划分的方法为计算新的输入实例与训练实例之间的距离,因为在特征空间中2个特征实例的相似程度可以用距离来表示。一般我们采用的是欧式距离,也就是说每个新的输入实例都需要与所有的训练实例计算一次距离并排序。当训练集非常大的时候,计算就非常耗时、耗内存,导致算法的效率降低。

为了提高Knn的搜索效率,这里介绍一种可以减少计算距离次数的方法———kd树方法。

1.Kd树简介

kd树(k-dimensional树的简称),是一种对k维空间中的实例点进行存储以便对其进行快速搜索的二叉树结构。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。

kd 树是每个节点均为k维数值点的二叉树,其上的每个节点代表一个超平面,该超平面垂直于当前划分维度的坐标轴,并在该维度上将空间划分为两部分,一部分在其左子树,另一部分在其右子树。即若当前节点的划分维度为d,其左子树上所有点在d维的坐标值均小于当前值,右子树上所有点在d维的坐标值均大于等于当前值,本定义对其任意子节点均成立。

2.Kd树的构造

举例:存在集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2)。

构建根节点时,此时的切分维度为x,如上点集合在x维从小到大排序为(2,3),(4,7),(5,4),(7,2),(8,1),(9,6);其中值为(7,2)。(注:2,4,5,7,8,9在数学中的中值为(5 + 7)/2=6,但因该算法的中值需在点集合之内,所以中值计算用的是len(points)//2=3, points[3]=(7,2) )

(2,3),(4,7),(5,4)挂在(7,2)节点的左子树,(8,1),(9,6)挂在(7,2)节点的右子树。

构建(7,2)节点的左子树时,点集合(2,3),(4,7),(5,4)此时的切分维度为y,中值为(5,4)作为分割平面,(2,3)挂在其左子树,(4,7)挂在其右子树。

构建(7,2)节点的右子树时,点集合(8,1),(9,6)此时的切分维度也为y,中值为(9,6)作为分割平面,(8,1)挂在其左子树。至此k-d tree构建完成。
Kd树根据上面的步骤,kd树将二维空间的划分展示出来:
kd树二维空间

3.搜索kd树

实例1

首先假设(2,3)为“当前最近邻点”。最邻近点肯定位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行“回溯”操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中是由点(2,3)回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点,发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。

再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。
实例1至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。

实例2

首先假设(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。回溯到(5,4),计算其与查找点之间的距离为3.041,小于3.202,所以“当前最近邻点”变成(5,4)。
实例2-1
以目标点(2,4.5)为圆心,以目标点(2,4.5)到“当前最近邻点”(5,4)的距离(即3.041)为半径作圆,如上图所示。可见该圆和y = 4超平面相交,所以需要进入(5,4)左子空间进行查找,即回溯至(2,3)叶子节点(2,3)距离(2,4.5)比(5,4)要近,所以“当前最近邻点”更新为(2,3),最近距离更新为1.5。
在这里插入图片描述
回溯至(7,2),以(2,4.5)为圆心1.5为半径作圆,并不和x = 7分割超平面交割,如下图所示。至此,搜索路径回溯完。返回最近邻点(2,3),最近距离1.5。

至此,完成了KdTree的了解,在Sklearn中通过调整algorithm参数可以进行KdTree的设置。

以后慢慢补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值