目录
1.1 KNN算法的简介
1.2 KNN算法的工作原理
1.3 创建数据集并导入数据
1.4 从文本文件中解析数据
1.5 示例:手写识别系统
1.5.1 准备数据:将图像转换为测试向量
1.5.1 测试算法:使用k-近邻算法识别手写数字
1.6 示例:使用k-近邻算法改进约会网站的配对效果
1.6.1 准备数据:从文本文件中解析数据
1.6.2 准备数据:归一化数值
1.6.3 测试算法:作为完整程序验证分类器
1.6.4 使用算法:构建完整可用系统
KNN(K- Nearest Neighbor)法即K最邻近法,最初由 Cover和Hart于1968年提出,是一个理论上比较成熟的方法,也是最简单的机器学习算法之一。所谓K最近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它最接近的K个邻近值来代表。近邻算法就是将数据集合中每一个记录进行分类的方法该方法的思路非常简单直观:如果一个样本在特征空间中的K个最相似(即特征空间中最邻近)的样本中的大多数属于某一个类别,则该样本也属于这个类别。
存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似的数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
kNN.py
from numpy import *
import operator
def createDataSet():
group = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]])
labels = ['A', 'A', 'B', 'B']
return group, labels
之后在kNN.py的目录下打开cmd,输入python.exe进入Python交互式开发环境。接着输入下列命令导入上面编辑的程序模块:
import kNN
为了确保输入相同的数据集,kNN模块中定义了函数createDataSet,在Python命令提示符下输入下属命令:
group,labels = kNN.createDataSet()
上述命令创建了变量group和labels,在Python命令提示符下,输入变量的名字以检验是否正确的定义变量。接下来我们将使用这些方法完成分类任务。
将下面代码增加到kNN.py中。
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 = {}
for i in range (k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 #选择距离最小的k个点
sortedClassCount = sorted(classCount.items(), key = operator.itemgetter(1), reverse = True) #排序
return sortedClassCount[0] [0]
classify0()函数有4个输入参数:用于分类的输入向量是inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目,其中标签向量的元素数目和矩阵dataSet的行数相同。
为了预测数据所在分类,在Python提示符中输入下列命令:
kNN.classify0([0,0], group, labels, 3)
输出结果应该是B,我们可以改变输入[0,0]为其他值,测试运行结果。
我们使用了两个数据集,其中目录trainingDigits中包含了大约2000个例子,每个例子的内容如下图所示,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果,两组数据没有覆盖。
我们首先编写一段函数img2vector,将图像转换为向量:该函数创建1 × 1024的Numpy数组,然后打开给定的文件,循环独处文件的前32行,并将每行的头32个字符值储存在Numpy数组中,最后返回数组。
def img2vector(filename):
returnVect = zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline()
for j in range(32):
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
将上述代码输入到kNN.py中,在Python命令行中输入下列命令测试img2vector函数,然后与文本编辑器打开的文件进行比较:
testVector = kNN.img2vector('testDigits/0_13.txt')
下面程序包含函数handwritingClassTest()是测试分类器的代码,将其写入kNN.py文件中。在写入这些代码之前,我们必须确保from os import listdir写入文件的起始部分。
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') #获取目录内容
m = len(trainingFileList)
trainingMat = zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0]) #从文件名解析分类数字
hwLabels.append(classNumStr)
trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('testDigits')
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
print ("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
if(classifierResult) != classNumStr: errorCount += 1.0
print ("\nthe total number of errors is: %d" % errorCount)
print ("\nthe total error rate is: %f" % (errorCount/float(mTest)))(mTest)))
之后在Python命令提示符中输入kNN.handwritingClassTest(),测试该函数的输出结果。
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
将下面的代码增加到kNN.py中。
def file2matrix(filename):
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines)
returnMat = zeros((numberOfLines,3))
classLabelVector = []
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t')
returnMat[index,:] = listFromLine[0:3]
classLabelVector.append(listFromLine[-1])
index += 1
return returnMat, classLabelVector
在Python命令提示符下输入下面命令:
reload(kNN)
datingDataMat,datingLabels = kNN.file2matrix('datingTestSet.txt')
datingDataMat
datingLabels[0:20]
之后我们可以看到这样的结果
现在已经从文本文件中导入了数据,并将其格式化为想要的格式。
我们很容易发现,计算距离时数字差值最大的属性对计算结果的影响最大,也就是说在这个例子中,获取的飞行常客里程数对于计算结果的影响将远远大于表2-3中其他两个特征的影响。但这三个特征同等重要。在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
newValue = (oldValue - min) / (max - min)
其中min和max分别是数据集中的最小特征值和最大特征值。我们需要在kNN.py中增加一个新函数autoNorm(),该函数可以自动将数字特征值转化为0到1的区间。
def autoNorm(dataSet):
minVals = dataSet.min(0)
maxVals = dataSet.max(0)
ranges = maxVals - minVals
normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet/tile(ranges, (m, 1))
return normDataSet, ranges, minVals
在Python命令提示符下,输入以下代码,重新加载kNN.py模块,执行autoNorm函数,检测函数的执行结果:
from imp import reload
reload(kNN)
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
normMat
ranges
minVals
我们可以看到这样的结果:
为了测试分类器效果,在kNN中创建函数datingClassTest。
def datingClassTest():
hoRatio = 0.10
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i,:], normMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 3)
print ("the classifier came back with: %s, the real answer is %s" % (classifierResult, datingLabels[i]))
if(classifierResult != datingLabels[i]):
errorCount += 1.0
print ("the total error rate is : %f" % (errorCount / float(numTestVecs)))
这个函数首先使用了file2matrix和autoNorm()函数从文件中读取数据并将其转换为归一化特征值。接着计算测试向量的数量。然后将这两部分数据输入到原始kNN分类器函数classify0。最后,函数计算错误率并输出结果。
在Python命令提示符下重新加载kNN模块,并输入kNN.datingClassTest(),执行分类器测试程序,我们将得到下面的输出结果:
上面我们已经在数据上对分类器进行了测试,现在终于可以使用这个分类器为海伦来对人们进行分类。我们可以在约会网站上找到某个人并输入他的信息。程序会给出她对对方喜欢程度的预测值。
将下列代码加入到kNN.py中并重新载入kNN:
def classifyPerson():
resultList = ['not at all' , 'in small doses', 'in large doses']
percentTats = float(input("percentage of time spent playing video games?"))
ffMiles = float(input("frequent flier miles earned per year?"))
iceCream = float(input("liters of ice cream consumed per year?"))
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierResult = int(classify0((inArr - minVals) / ranges, normMat, datingLabels, 3))
print ("You will probably like this person: ", resultList[classifierResult - 1])
在Python命令提示符下输入kNN.classifyPerson(),了解程序的实际运行效果。