k-近邻算法通俗来讲就是通过测量计算测试值与不同特征值的欧拉距离(也叫欧几里得距离)来进行分类。它有三个基本要素:k值的选择,距离度量和分类决策规则(本文就选了多数表决的决策规则)。
- 距离度量是两个实例相似程度的反映,本文使用了欧拉距离,但也可以是其它距离如Minkowski距离。
- k值的选择会对最终的分类产生巨大的影响,k值越大判断的区域就越大。由于k值过大过小都会有不良的效果,所以我们可以采用交叉验证法来选取最优的k值。
k-近邻算法(KNN)它的工作原理是:给定样本集,样本集中的每一个数据都有标签,使用标签来区分种类(例如分类一标签为a,分类二标签为b),每一种分类都有自己独一无二的标签,并且样本集中的每一个数据都有一组特征。当出现测试集,测试集中的数据需要被分类时,算法提取到数据后,将这个数据的特征与样本中的每一个数据的特征都进行比较,获得与样本集中每个数据的欧拉距离,并按距离从大到小将样本集数据排列,选取前k个数据(也就是前k个最相似的数据),在前k个数据中选择出现次数最多的分类来作为测试数据的分类。
列举书中简单的例子来理解,根据电影特征来区分电影分类。
电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
电影0 | 3 | 104 | 爱情片 |
电影1 | 2 | 100 | 爱情片 |
电影2 | 1 | 81 | 爱情片 |
电影3 | 101 | 10 | 动作片 |
电影4 | 99 | 5 | 动作片 |
电影5 | 98 | 2 | 动作片 |
新电影 | 1 | 20 | ??? |
代码实现:
from numpy import *
import operator
'''
创建数据集的时候要注意每组特征都对应着标签,自然地,特征的组数于标签的数量就是相等的
'''
def createDataSet():
group = array([[3, 104], [2, 100], [1, 81], [101, 10], [99, 5], [98, 2]])
labels = ['爱情片', '爱情片', '爱情片', '动作片', '动作片', '动作片']
return group, labels
'''
inX是用于分类的输入向量 dataSet是输入的训练样本集 label标签向量 k是选择最近邻居的数码
'''
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={}
print(sortedDistIndicies+1)
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) #itemgetter按第二元素的次序排列元祖
return sortedClassCount[0][0]
if __name__ == '__main__':
group, labels = createDataSet()
test = [1, 20]
test_class = classify0(test, group, labels, 3)
print(test_class)
结果:
[3 2 1 6 5 4]
爱情片
先输出按测试数据与样本数据的欧拉距离从大到小排列的样本分类次序,然后输出测试数据分类。通过这次简单的练习,可以发现k-近邻算法并不需要训练过程,这虽然减少了负担,但是带来的确是计算复杂度高,空间复杂度高。当然不同的数据在不同的样本数据集上的表现是不一样的,分类的结果与样本数据关系密切。
用文本文件导入样本数据代码:
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) #获取文本文件长度
returnMat = zeros((numberOfLines, 3)) #这里的3是样本数据的特征数量,所以返回的矩阵就是3维的
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3]
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector