简而言之,k-近邻算法采用测量不同特征值之间的距离来进行分类。
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度高、空间复杂度高。
适用数据范围:数值型和标称型(目标变量的结果只在有限目标集中取值)。
算法的工作原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的关系。输入没有标签的新数据后,将数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法的出处。通常k是不大于20的整数,最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
k-近邻算法的一般流程:
(1)收集数据:可以使用任何方法。
(2)准备数据:距离计算所需的数值,最好是结构化的数据格式。
(3)分析数据:可以使用任何方法。
(4)测试算法:计算错误率。
(5)使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k-近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
下面是一个使用k-近邻算法改进约会网站配对效果的实例。
数据中主要包含了3个属性,分别是:(1)每年获得的飞行常客里程数(2)玩视频游戏所耗时间百分比(3)每周消费冰淇淋公升数。以及最后约会的满意程度,1:不喜欢;2:比较喜欢;3:非常喜欢。
部分数据的截图如下:
首先,我们需要创建一个函数,该函数用于解析数据文件中的信息,并最终返回我们所需的训练样本集和对应的类标签向量。函数的具体定义如下:
def file2matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) #获取文件的总行数
returnMat = zeros((numberOfLines,3)) #初始化一个0矩阵
classLabelVector = []
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip() #删除前、后的空白符
listFromLine = line.split('\t') #将3个属性和分类放入列表listFromLine
returnMat[index,:] = listFromLine[0:3] #依次装入了属性值
classLabelVector.append(int(listFromLine[-1]))
#将最后的分类情况添加到classLabelVector中
index += 1
return returnMat,classLabelVector #返回提取到的属性矩阵和分类向量
将数据解析之后,我们可以进行一些可视化的展示,主要代码如下:
#解析文件,获得数据
datingDataMat, datingLables = kNN.file2matrix('E:/PythonItem/DateSet/datingTestSet2.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
#设置图片标题
title = u'属性可视化展示'
title.decode('utf-8')
ax.set_title(title)
#设置坐标轴文字
plt.xlabel(u'玩视频游戏所耗时间百分比')
plt.ylabel(u'每周消耗冰淇淋公升数')
p = ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLables), 15.0*array(datingLables))
plt.show()
运行后的情况如下:
从上图中我们已经可以大致看出三类不同人群的分布区域。
在进行进一步的分析之前,我们需要对数据做归一化处理。因为在三个属性中“飞行里程”这个属性的数值较大,从而也将产生相对于其它两个属性更大的影响。但我们认为这三种属性特征是同等重要的,因此作为三个等权重的特征之一,飞行常客里程数不应该如此严重地影响到结果的判断。
在这里,我们采用如下的公式将数据转化为0到1区间内的值:
newValue= (oldValue - min) / (max - min)
其中,min和max分别是数据集中最小和最大的特征值。下面就是实现上述归一化方法的具体代码:
def autoNorm(dataSet):
#分别取得每一列的最小值和最大值
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
#计算最值间的差值
ranges = maxVals – minVals
#构造行列与输入矩阵相同的0矩阵
normDataSet = zeros(shape(dataSet))
#获取输入矩阵的行数
m = dataSet.shape[0]
#矩阵中每一列的元素减去该列的最小值
normDataSet = dataSet - tile(minVals, (m,1))
#在上一步的基础上再除以对应列的最值差
normDataSet = normDataSet/tile(ranges, (m,1))
#返回归一化后的矩阵、每列的最值差以及每列的最小值
return normDataSet, ranges, minVals
对数据进行归一化后就可以输入分类器进行具体分类操作了,实现的分类器代码如下:
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={}
#根据前k个最小距离的值来判断目标向量所属的分类
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
#统计每个分类在距离最小的k个例子中出现的次数
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#根据字典的值来对字典项进行从大到小的排序
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
#最后返回字典值最大的字段的键,即对应的分类
return sortedClassCount[0][0]
到这里为止,k-近邻算法的主体部分就全部实现了。可以通过如下的代码来测试我们编写的分类器的错误率:
def datingClassTest():
hoRatio = 0.10 #测试数据为10%
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio) #接收测试的向量数
errorCount = 0.0
for i in range(numTestVecs): #数据集中前numTestVecs作为测试集
classifierResult = classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
if (classifierResult != datingLabels[i]): errorCount += 1.0
print "the total error rate is: %f" % (errorCount/float(numTestVecs))
print errorCount
接下来可以构建对应的应用,根据用户输入的信息进行分类。具体代码如下:
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = float(raw_input(\
"percentage of time spent playing video games?"))
ffMiles = float(raw_input("frequent flier miles earned per year?"))
iceCream = float(raw_input("liters of ice cream consumed per week?"))
datingDataMat, datingLabels = file2matrix('datingDataMat')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
#先将输入向量做归一化,然后再进行测试
classifierResult = classify0((inArr-minVals)/ranges, normMat, datingLabels, 3)
print "You will probably like this person:", \
resultList[classifierResult-1]