该算法采用:测量不同特征值之间的距离方法进行分类
优点:精度高、对异常值不敏感、无数据输入假定。
缺点:计算复杂度和空间复杂度都高。
工作原理:
存在样本训练集和对应的标签。输入没有标签的新数据后,将新数据的每个特征与,样本数据的对应特征进行比较,然后提取样本集中,特征最近邻的分类标签。一般选样本集中,前k个最相似的数据。
k-近邻算法的一般流程:
收集数据、准备数据(距离计算,最好是结构化数据格式)、分析数据、测试算法(计算错误率)、使用算法
导入数据,编写创建数据集和标签模块:
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算法:
伪代码:
def classify0(inX, dataSet, labels, k):
# inX为用于分类的输入向量
# 计算距离
dataSetSize = dataSet.shape[0] # 样本集的行数
diffMat = tile(inX, (dataSetSize, 1)) - dataSet # 把inX二维数组化,dataSetSize表示生成数组后的行数,1表示列的倍数。
sqDiffMat = diffMat ** 2
sqDistances = sqDiffMat.sum(axis=1) # 参数等于1的时候,表示矩阵中行之间的数的求和
distances = sqDistances ** 0.5
sortedDistIndicies = distances.argsort() # 对一个数组进行非降序排序
classCount = {}
# 选择距离最小的k个点
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]]
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 按照键值的值,从大到小排序
sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
# 返回最大频率的类标签,sortedClassCount是个元组构成的列表
return sortedClassCount[0][0]
使用欧式距离公式计算所有点的距离,对距离按照从小到大的次序排序,然后确定前k个距离最小元素所在的主要分类。
最后,将classCount字典分解为元组列表,通过运算符模块的itemgetter方法,按照第二元素逆序排序,最后返回,发生频率最高的元素标签。
如何测试分类器:
可以使用多种方法检测分类器的正确率。不同的算法在不同数据集上的表现可能完全不同。
分类器的错误率,分类器给出错误结果的次数,除以测试执行的总数。
错误率,是常用的评估方法,主要用于评估分类器在某个数据集上的执行效果。
示例:使用k-近邻算法改进约会网站的配对效果
分析数据时,可使用Matplotlib画二维扩散图
解析文本文件,导入数据:
def file2matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) # 得到文件行数
returnMat = zeros((numberOfLines, 3)) # 创建返回的numPy矩阵
classLabelVector = []
fr = open(filename)
index = 0
# 解析文件数据到列表
for line in fr.readlines():
line = line.strip() # 截取掉所有的回车字符
listFromLine = line.split('\t')
returnMat[index, :] = listFromLine[0:3] # 选取前3个元素
classLabelVector.append(int(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
Numpy库提供的数组操作,并不支持python自带的数组类型。
分析数据:使用Matplotlib创建散点图:
Matplotlib库提供的scatter函数,支持个性化标记散点图上的点。
import matplotlib.pyplot as plt
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
# 后面两个为标签矩阵,第一个参数标记x轴,第二个标记y轴
ax.scatter(datingDataMat[:, 0], datingDataMat[:, 1], 15.0 * array(datingLabels), 15.0 * array(datingLabels))
plt.show()
准备数据:归一化数值:
因为某一特征值有可能会远大于其他特征值,因此需要数值归一化。将取值范围,处理为0到1,或者-1到1之间。
公式为:
def autoNorm(dataSet):
minVals = dataSet.min(0) # 将每列的最小值,放在变量minVals中,0表示求列的最小值
maxVals = dataSet.max(0) # 将每列的最大值,放在变量maxVals中
ranges = maxVals - minVals
m = dataSet.shape[0]
normDataSet = dataSet - tile(minVals, (m, 1)) # dataSet矩阵有1000x3个值
normDataSet = normDataSet / tile(ranges, (m, 1)) # minVals和range的值都为1x3
return normDataSet, ranges, minVals
在NumPy库中,矩阵除法需要使用函数,linalg.solve(matA, matB)
测试算法:作为完整程序验证分类器:
机器学习算法,一个很重要得工作就是,评估算法的正确率。
通常,使用已有数据的90%作为训练样本来训练分类器,使用其余的10%数据,通过随机选取,去测试分类器。
def datingClassTest():
hoRatio = 0.50 # hold out 10%
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
m = normMat.shape[0] # 1000行
numTestVecs = int(m * hoRatio) # 从1000行中抽取测试集
errorCount = 0.0
for i in range(numTestVecs):
# 参数分别为测试集的一个元素、样本集、标签集、k值
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)
numTestVecs为测试向量的数量,这个决定了normMat向量中,那些数据可用于测试。
检测错误率,依赖于分类算法、数据集和程序设置,如k值设置。
使用算法:构建完整可用系统:
写一段程序,输入一个男生的个人信息,程序会给出,某个女生对对方的喜欢程度的预测值。
def classifyPerson():
resultList = ['not at all', 'in small doses', 'in large doses']
percentTats = 10
ffMiles = 10000
iceCream = 0.5
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
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])
输出是:in small doses
示例:手写识别系统
将把一个32x32的二进制图像矩阵,转换为1x1024的向量。
def img2vector(filename):
returnVect = zeros((1, 1024)) # 创建1x1024的NumPy数组
fr = open(filename)
for i in range(32):
# 循环读出文件的前32行
lineStr = fr.readline()
for j in range(32):
# 将每行的头32个字符值存储在NumPy数组中
returnVect[0, 32 * i + j] = int(lineStr[j])
return returnVect
手写数字识别系统的测试代码:
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') # 列举该目录下的所有文件名
m = len(trainingFileList)
trainingMat = zeros((m, 1024)) # 创建m行1024列的训练矩阵
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] # take off .txt
classNumStr = int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('testDigits') # iterate through the test set
errorCount = 0.0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0] # take off .txt
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)))
改变k值、随机选取训练样本、改变训练样本数目,都会对错误率产生影响。
上述算法执行效率并不高,因为每个测试向量要做2000次距离计算,每个距离计算包括,1024个维度浮点运算。
因此,可用k决策树来优化。
小结:
缺点:1、算法必须保存全部数据集,若训练数据集很大,必须使用大量的存储空间。2、也必须对数据集中每个数据计算距离值。3、无法给出任何数据的基础结构信息,也就无法知晓,平均实例样本和典型实例样本具有什么特征。