领近算法,或者说K最近邻(kNN,k-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是k个最近的邻居的意思,说的是每个样本都可以用它最接近的k个邻居来代表。
KNN方法既可以做分类,也可以做回归,这点和决策树算法相同。KNN做回归和分类的主要区别在于最后做预测时候的决策方式不同。
本文所写内容主要为分类算法。
kNN算法的核心思想是如果一个样本在特征空间中的k个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性。该方法在确定分类决策上只依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。 kNN方法在类别决策时,只与极少量的相邻样本有关。由于kNN方法主要靠周围有限的邻近的样本,而不是靠判别类域的方法来确定所属类别的,因此对于类域的交叉或重叠较多的待分样本集来说,kNN方法较其他方法更为适合。
如上图所示,在总体的类别里面共有两类,分别为class1(图中以三角形代替)以及class2(图中用方框代替)。现在有一个点所处的位置为问号处。
距离度量、k值的选择及分类决策规则是k近邻法的三个基本要素。根据选择的距离度量(如曼哈顿距离或欧氏距离),可计算测试实例与训练集中的每个实例点的距离,根据k值选择k个最近邻点,最后根据分类决策规则将测试实例分类。
给定测试对象 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
圈定距离最近的 k k k个训练对象,作为测试对象的近邻。
k值即为选择最近邻的k个点进行观察。K值的选择会对k近邻法的结果产生重大影响。在应用中,k值一般取一个比较小的数值,通常采用交叉验证法来选取最优的k值。
根据这k近邻归属的主要类别,来对测试对象进行分类。
k近邻法中的分类决策规则往往是投票法,由输入实例的k个邻近的训练实例中的多数类,决定输入实例的类。
但是,在有一些时候,也会为投票的规则加上一个系数,系数为距离的倒数。
举一个具体的例子:
对某一个电影进行分类:
序号 | 电影名称 | 搞笑镜头 | 拥抱镜头 | 打斗镜头 | 电影类型 |
---|---|---|---|---|---|
1 | 宝贝当家 | 45 | 2 | 9 | 喜剧片 |
2 | 美人鱼 | 21 | 17 | 5 | 喜剧片 |
3 | 澳门风云3 | 54 | 9 | 11 | 喜剧片 |
4 | 功夫熊猫3 | 39 | 0 | 31 | 喜剧片 |
5 | 谍影重重 | 5 | 2 | 57 | 动作片 |
6 | 叶问3 | 3 | 2 | 65 | 动作片 |
7 | 伦敦陷落 | 2 | 3 | 55 | 动作片 |
8 | 我的特工爷爷 | 6 | 4 | 21 | 动作片 |
9 | 奔爱 | 7 | 46 | 4 | 爱情片 |
10 | 夜孔雀 | 9 | 39 | 8 | 爱情片 |
11 | 代理情人 | 9 | 38 | 2 | 爱情片 |
12 | 新步步惊心 | 8 | 34 | 17 | 爱情片 |
13 | 唐人街探案 | 23 | 3 | 17 | ? |
上面数据集中,已知前面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)=1∑N(xi−yi)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)=(45−23)2+(2−3)2+(9−17)2=23.430749027719962
可得:
序号 | 电影名称 | 与唐人街探案的距离 | 电影类型 |
---|---|---|---|
0 | 宝贝当家 | 23.430749 | 喜剧片 |
1 | 美人鱼 | 18.547237 | 喜剧片 |
2 | 澳门风云3 | 32.140317 | 喜剧片 |
3 | 功夫熊猫3 | 21.470911 | 喜剧片 |
4 | 谍影重重 | 43.874822 | 动作片 |
5 | 叶问3 | 52.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 | 功夫熊猫3 | 21.470911 | 喜剧片 | √ |
0 | 宝贝当家 | 23.430749 | 喜剧片 | √ |
2 | 澳门风云3 | 32.140317 | 喜剧片 | √ |
11 | 新步步惊心 | 34.438351 | 爱情片 | × |
9 | 夜孔雀 | 39.661064 | 爱情片 | × |
10 | 代理情人 | 40.570926 | 爱情片 | × |
6 | 伦敦陷落 | 43.416587 | 动作片 | × |
4 | 谍影重重 | 43.874822 | 动作片 | × |
8 | 奔爱 | 47.686476 | 爱情片 | × |
5 | 叶问3 | 52.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)
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中的重要参数:
BallTree
KDTree
下面举例用乳腺癌数据集来展示KNN在SKlearn中的具体调用:
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算法 kd树详解
在KNN的算法中,对特征空间进行划分的方法为计算新的输入实例与训练实例之间的距离,因为在特征空间中2个特征实例的相似程度可以用距离来表示。一般我们采用的是欧式距离,也就是说每个新的输入实例都需要与所有的训练实例计算一次距离并排序。当训练集非常大的时候,计算就非常耗时、耗内存,导致算法的效率降低。
为了提高Knn的搜索效率,这里介绍一种可以减少计算距离次数的方法———kd树方法。
kd树(k-dimensional树的简称),是一种对k维空间中的实例点进行存储以便对其进行快速搜索的二叉树结构。利用kd树可以省去对大部分数据点的搜索,从而减少搜索的计算量。
kd 树是每个节点均为k维数值点的二叉树,其上的每个节点代表一个超平面,该超平面垂直于当前划分维度的坐标轴,并在该维度上将空间划分为两部分,一部分在其左子树,另一部分在其右子树。即若当前节点的划分维度为d,其左子树上所有点在d维的坐标值均小于当前值,右子树上所有点在d维的坐标值均大于等于当前值,本定义对其任意子节点均成立。
举例:存在集合(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树将二维空间的划分展示出来:
实例1:
首先假设(2,3)为“当前最近邻点”。最邻近点肯定位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行“回溯”操作:算法沿搜索路径反向查找是否有距离查询点更近的数据点。此例中是由点(2,3)回溯到其父节点(5,4),并判断在该父节点的其他子节点空间中是否有距离查询点更近的数据点,发现该圆并不和超平面y = 4交割,因此不用进入(5,4)节点右子空间中去搜索。
再回溯到(7,2),以(2.1,3.1)为圆心,以0.1414为半径的圆更不会与x = 7超平面交割,因此不用进入(7,2)右子空间进行查找。
至此,搜索路径中的节点已经全部回溯完,结束整个搜索,返回最近邻点(2,3),最近距离为0.1414。
实例2:
首先假设(4,7)为当前最近邻点,计算其与目标查找点的距离为3.202。回溯到(5,4),计算其与查找点之间的距离为3.041,小于3.202,所以“当前最近邻点”变成(5,4)。
以目标点(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的设置。
以后慢慢补充