K-近邻算法的简介和Python实现
1、k-近邻算法(kNN)简介
k-近邻算法的原理:测量不同特征值之间的距离
k-近邻算法 |
---|
优点:精度高、对异常值不敏感、无数据输入假定 |
缺点:计算复杂度高、空间复杂蹲高 |
适用数据范围:数值型和标称型 |
【注】标称型目标变量的结果只在有限目标集中取值, 如真与假、 动物分类集合 { 爬行类、 鱼类、 哺乳类、 两栖类 } ; 数值型目标变量则可以从无限的数值集合中取值, 如 0.100、 42.001、 1000.743 等。
kNN的工作原理:存在一个带标签(即知道样本集中每一个数据与所属分类的对应关系)的训练样本集S。输入无标签的新数据后,将新数据的每个特征与样本集S中数据对应的特征进行比较,然后通过算法从样本集中提取出与新数据特征最为相似的前k个数据,并统计这k个数据中出现次数最多的分类,将此作为新数据的分类。
【注】一般来说,只选择样本数据集中前k个最相似的数据,这就是kNN中k的出处,通常k取不大于20的整数。
下面,我们利用电影分类的例子,来加深对kNN的了解。我们都知道,爱情片中接吻的镜头是较多的,而动作片中打斗的镜头是偏多的,因此,我们利用kNN来分类爱情片和动作片。下面表1是几部影片的统计结果。
电影名称 | 打斗镜头(x) | 接吻镜头(y) | 电影类型 |
---|---|---|---|
电影1 | 3 | 104 | 爱情片 |
电影2 | 2 | 100 | 爱情片 |
电影3 | 1 | 81 | 爱情片 |
电影4 | 101 | 10 | 动作片 |
电影5 | 99 | 5 | 动作片 |
电影6 | 98 | 2 | 动作片 |
? | 18 | 90 | 未知 |
即使不知道未知电影属于哪种类型,我们也可以通过某种方法计算出来。首先计算未知电影与样本集中其他电影的距离,将打斗镜头记为x,接吻镜头记为y,通过欧式距离公式
计算各个电影与未知电影的距离。具体数值如表2所示。
电影名称 | 与未知电影的距离 |
---|---|
电影1 | 20.5 |
电影2 | 18.7 |
电影3 | 19.2 |
电影4 | 115.3 |
电影5 | 117.4 |
电影6 | 118.9 |
按照距离递增排序,可以找到k个距离最近的电影。假定k=3,则三个最靠近的电影依次是电影2、电影3、电影1。统计这三个靠前的电影的类型(均为爱情片),则未知电影的类型为爱情片。
k-近邻算法的一般流程
(1) 收集数据:可以使用任何方法。
(2) 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
(3) 分析数据:可以使用任何方法。
(4) 训练算法:此步骤不适用于k-近邻算法。
(5) 测试算法:计算错误率。
(6) 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对 计算出的分类执行后续的处理。
2、kNN的python实现
对未知类别属性的数据集中的每个点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离递增次序排序;
(3) 选取与当前点距离最小的k个点;
(4) 确定前k个点所在类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。
下面,我们通过python来实现上述的操作。
def classify0(inX, dataSet, labels, k):
"""
:param inX: 用于分类的输入向量
:param dataSet: 输入的训练样本集
:param labels: 标签向量
:param k: 用于选择最近邻居的数目
:return: inX的分类结果
注:标签向量的元素数目和矩阵dataSet的行数相同
"""
dataSetSize = dataSet.shape[0]
# ===================== 欧氏距离计算公式 =====================
diffMat = tile(inX, (dataSetSize, 1) - dataSet)
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1)
distances = sqDistances ** 0.5
# 计算完所有点之间的距离后,对数据按照从小到大的次序排序。
sortedDistIndices = distances.argsort()
classCount={}
for i in range(k):
# 确定前k个距离最小元素所在的主要分类
voteIlabel = labels[sortedDistIndices[i]]
# 将classCount字典分解为元组列表
classCount[voteIlabel] - classCount.get(voteIlabel, 0) + 1
# 按照第二个元素的次序对元组进行排序(此处的排序为逆序)
sortedClassCount = sorted(classCount.itertems(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]