k-近邻(k-Nearest Neighbor,简称kNN)是一种常用的监督学习方法,其工作机制非常简单:给定测试样本,基于某种距离度量找出训练集中与其最靠近的k个训练样本,然后基于这k个“邻居”的信息来进行预测,选择这k个样本中出现最多的类别标记作为预测结果。当k取不同值时,分类结果会有显著不同。另一方面,若采用不同的距离计算方式,则找出的“近邻”可能有显著差别,从而也会导致分类结果有显著不同。
如果概念太过于抽象,我们可以举个关于集美大学的例子来理解k-近邻算法:
集大建筑 | 午餐时间人数 | 工作时间人数 | 建筑类型 |
---|---|---|---|
万人 | 1000 | 50 | 餐厅 |
陆大 | 30 | 800 | 教学楼 |
西苑 | 600 | 30 | 餐厅 |
建发 | 20 | 2000 | 教学楼 |
ps:这里容我简单介绍一下,万人食堂、西苑食堂顾名思义是集美大学本部两个主要的食堂,陆大楼是计算机工程学院的楼,与建发楼一样,是学生上课的地方,不同的是陆大五楼是学院领导、辅导员的基地,下面还有各种实验室,而建发楼就可以说是完完全全的教学楼了,为了方便,我们将陆大楼也看作是教学楼。
我们可以使用k-近邻算法分类集美大学的某一个建筑是餐厅还是教学楼。
具体怎么分类呢?
上表是我们已有的数据集,也就是训练样本。这个数据集有两个特征,即午餐时间人数和工作时间人数,我们也知道每个建筑的所属类型,即分类标签。
人是铁饭是钢,一顿不吃饿的慌,作为一名集大的学生,用肉眼可以很明确地观察到,饭点大家都跑去万人、西苑(当然也有某些卷王不干饭...),上课时间大家都跑去陆大、建发(当然也有某些卷王没课跑去食堂开卷...可能图书馆没位置了...怎么这么多卷王...)。
如果现在给我一个建筑,告诉我午餐时间人数和工作时间人数,我可以根据这些判断这个建筑属于食堂还是教学楼,午餐时间人数多而工作时间人数少的是食堂,反之则是教学楼。k-近邻算法也可以像我们人一样做到这一点。
ps:这里会一个问题,假如我拿一个新的建筑来做测试,午餐时间人数和工作时间人数都是1000,这个时候我知道这个建筑可能是陈延奎图书馆也可能是嘉庚图书馆,其类型属于图书馆,而k-近邻算法不会,因为在它眼里,建筑类型只有食堂和教学楼,它会提取样本集中特征最相似数据(最邻近)的分类标签,得到的结果可能是食堂,也可能是教学楼,但绝不会是图书馆。当然,这些取决于数据集的大小以及最近邻的判断标准等因素。
一般而言,从开始,随着k的逐渐增大,k近邻算法的分类效果会逐渐提升;在增大到某个值后,随着的进一步增大,k近邻算法的分类效果会逐渐下降。
k值较小,相当于用较小的邻域中的训练实例进行预测,只有距离近的(相似的)起作用
k值较大,这时距离远的(不相似的)也会起作用
k值选择
下面给出两种常见的距离度量方式
欧式距离也称欧几里得距离,是最常见的距离度量,衡量的是多维空间中两个点之间的绝对距离。
n维空间点间的欧式距离(两个n维向量):
在曼哈顿街区要从一个十字路口开车到另一个十字路口,驾驶距离显然不是两点间的直线距离。这个实际驾驶距离就是“曼哈顿距离”。曼哈顿距离也称为“城市街区距离”(City Block distance)。
n维空间点间的曼哈顿距离(两个n维向量):
如何判断黄点标记的禹洲楼所属的类别呢?
可以从散点图大致推断,这个黄点标记的建筑可能属于教学楼,因为距离已知的两个蓝点更近。k-近邻算法同样是这个原理,专业点的话叫距离度量。这个例子是简单的二维样例,我们可以采用欧氏距离公式计算距离。
通过计算,我们可以得到如下结果:
现在k值取3,那么按距离依次排序的三个点分别是教学楼(1000,100)、教学楼(850,20)、食堂(30,800)、食堂(50,1000)。在这三个点中,教学楼出现的频率为2/3,食堂出现的频率为1/3,所以该黄点标记的建筑为教学楼。这个判别过程就是k-近邻算法。
我们对上面的例子进行Python的实现
创建数据集并打印查看创建结果
# -*- coding = utf-8 -*-
# @Time : 2022/10/26 14:20
# @Author : Nch
# @File : kNN.py
# @Software : PyCharm
import numpy as np
def createDataSet():
#四组二维特征
features = np.array([[1000,100],[850,20],[30,800],[50,1000]])
#四组特征的标签
labels = ['教学楼','教学楼','食堂','食堂']
return features, labels
#创建数据集
features,labels = createDataSet()
#打印数据集
print(features,'\n',labels)
运行结果如下,创建数据集成功
根据欧氏公式计算距离,选择距离最小的前k个点,并返回分类结果
def classify0(inX, dataSet, labels, k):
# numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
# 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 二维特征相减后平方
sqDiffMat = diffMat ** 2
# sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# 开方,计算出距离
distances = sqDistances ** 0.5
# 返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
# 定一个记录类别次数的字典
classCount = {}
for i in range(k):
# 取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
# dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
# 计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# python3中用items()替换python2中的iteritems()
# key=operator.itemgetter(1)根据字典的值进行排序
# key=operator.itemgetter(0)根据字典的键进行排序
# reverse降序排序字典
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
# -*- coding = utf-8 -*-
# @Time : 2022/10/26 14:20
# @Author : Nch
# @File : kNN.py
# @Software : PyCharm
import numpy as np
import operator
def createDataSet():
#四组二维特征
features = np.array([[1000,100],[850,20],[30,800],[50,1000]])
#四组特征的标签
labels = ['教学楼','教学楼','食堂','食堂']
return features, labels
#创建数据集
# features,labels = createDataSet()
#打印数据集
# print(features,'\n',labels)
def classify0(inX, dataSet, labels, k):
# numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
# 在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
# 二维特征相减后平方
sqDiffMat = diffMat ** 2
# sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
# 开方,计算出距离
distances = sqDistances ** 0.5
# 返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
# 定一个记录类别次数的字典
classCount = {}
for i in range(k):
# 取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
# dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
# 计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# python3中用items()替换python2中的iteritems()
# key=operator.itemgetter(1)根据字典的值进行排序
# key=operator.itemgetter(0)根据字典的键进行排序
# reverse降序排序字典
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
# 返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
features,labels = createDataSet()
test = [980,150]
test_class = classify0(test,features,labels,3)
print(test_class)
运行结果如下,根据我们给出的测试集和算法,可以判断禹洲楼是教学楼。
希望以上内容对读者有帮助,大家一起进步! 对集美大学感兴趣的也欢迎来咨询我!