1.概述
1.1 原理:(测量不同的特征值之间的距离进行分类)
存在样本数据集合,即训练样本集,并且样本集中的每个数据都存在多个特征和标签,即我们知道样本数据和其所属分类,在我们输入没有标签的新数据后,将新数据的每个特征和样本集中的数据对应的特征进行比较,然后根据相应算法(本节选择的是欧氏距离)提取与样本集中特征最相近数据得分类标签。一般选择与样本集中最相近的前k个数据,即k-近邻。最后,选择k个最相似数据中出现次数最多的分类最为新数据的分类。
- 优点: 精度高、对异常值不敏感、无数据输入假定(???)
- 缺点:计算复杂度高,需要大量存储空间,耗时,无法给出任何数据的基础结构信息(???)
- 适用数据范围:数值型(目标变量从无限的数值集中取值,主要做回归分析)和标称型(目标变量从有限的数值集中取值,主要做分类)
1.2 KNN算法
k近邻算法的一般流程
- 收集数据:可以使用任何方法。
- 准备数据:距离计算所需要的数值,最好是结构化的数据格式。
- 分析数据:可以使用任何方法。
- 训练算法:此步骤不适用于k近邻算法。
- 测试算法:计算错误率。
- 使用算法:首先需要输入样本数据和结构化的输出结果,然后运行k近邻算法判定输入数据分别属于哪个分类,最后应用对计算出的分类执行后续的处理。
伪代码:
利用一组测试数据:
1 from numpy import *
2 import operator
3
4 def createDataSet():
5 group = array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])
6 labels = ['A','A','B','B']
7 return group, labels
在jupyter notebook中进行编辑:
K-近邻算法:
使用欧氏距离公式,计算两个向量点xA和xB之间的距离:
如果数据集存在4个特征值,则点(1, 0, 0, 1)与(7, 6, 9, 4)之间的距离计算为:
1 def classify0(inX, dataSet, labels, k):
2 '''
3 :param inX: 用于分类的向量,即新数据
4 :param dataSet: 训练样本集,每个特征的值
5 :param labels: 训练样本的标签向量
6 :param k: 选择的k个最近的数据
7 :return: 最终的分类标签,即发生频率最高的标签
8 '''
9 dataSetSize = dataSet.shape[0] # 矩阵行数,即样本个数
10 diffMat = tile(inX, (dataSetSize,1)) - dataSet # np.tile([0,0],[4,1]) 将[0,0]复制为4行1列,对应位置相减
11 sqDiffMat = diffMat**2
12 sqDistances = sqDiffMat.sum(axis=1) # 按列求和,即求和后,行数不变,列为1
13 distances = sqDistances**0.5
14 sortedDistIndicies = distances.argsort() # 返回数组从小到大排序的索引
15 classCount={}
16 # 得到前k个最近的分类标签,以及出现的频率
17 for i in range(k):
18 voteIlabel = labels[sortedDistIndicies[i]]
19 classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
20 # 将classCount字典分解为元组,并按照第二个元素对元组进行逆序排序
21 sortedClassCount = sorted(classCount.iteritems(), key=operator.itemgetter(1), reverse=True)
22 # 返回频率最高的标签
23 return sortedClassCount[0][0]
预测数据所在分类:
2. 实例
2.1 改进约会网站的配对效果
(1)数据准备:从文本文件中解析数据
前三列为特征数据,最后一列为类别(标签)数据
1 def file2matrix(filename):
2 '''
3 :param filename:
4 :return: 特征值和类别值
5 '''
6 fr = open(filename)
7 arrayOLines = fr.readlines()
8 numberOfLines = len(arrayOLines) # get the number of lines in the file
9 returnMat = zeros((numberOfLines,3)) # prepare matrix to return
10 classLabelVector = [] # prepare labels return
11 index = 0 # 行号
12 for line in arrayOLines:
13 line = line.strip() # strip()默认移除字符串头尾指定字符
14 listFromLine = line.split('\t') # 按一个tab键进行划分
15 returnMat[index,:] = listFromLine[0:3]
16 classLabelVector.append(int(listFromLine[-1]))
17 index += 1
18 return returnMat,classLabelVector
测试结果:
(2)分析数据:用matplotlib画散点图
没有使用样本分类的特征值:
使用第二三列的特征:
使用第一二列的特征:
(3)准备数据:归一化数据
如果想要计算样本3和样本4之间的距离,可以使用下面的方法:
我们很容易发现,上面方程中数字差值最大的属性对计算结果的影响最大
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
newValue = (oldValue - min)/(max - min)
其中min和max分别是数据集中的最小特征值和最大特征值。
1 # 归一化特征值
2 def autoNorm(dataSet):
3 '''
4 :param dataSet:
5 :return: normDataSet:归一化后的特征值
6 ranges:每列最大值和最小值的差值
7 minVals:每列的最小值
8 '''
9 minVals = dataSet.min(0) # axis = 0 列/垂直;axis = 1 行/水平
10 maxVals = dataSet.max(0)
11 ranges = maxVals - minVals
12 normDataSet = zeros(shape(dataSet))
13 m = dataSet.shape[0] # 矩阵行数
14 normDataSet = dataSet - tile(minVals, (m, 1))
15 normDataSet = normDataSet/tile(ranges, (m, 1)) # element wise divide
16 return normDataSet, ranges, minVals
测试结果:
(4)测试算法:验证分类器
1 # 约会网站的测试代码
2 def datingClassTest():
3 hoRatio = 0.10 # 10%的测试数据
4 datingDataMat,datingLabels = file2matrix('datingTestSet2.txt') #load data setfrom file
5 normMat, ranges, minVals = autoNorm(datingDataMat)
6
7 # random.shuffle(normMat) # (自己加的)按行打乱,即可随机选择测试数据,
8 # 这样不行,datingLabels也要同时打乱,并且相对应
9
10 m = normMat.shape[0]
11 numTestVecs = int(m*hoRatio)
12 errorCount = 0.0
13 for i in range(numTestVecs):
14 classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 5)
15 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
16 if classifierResult != datingLabels[i]:
17 errorCount += 1.0
18 print "the total error rate is: %f" % (errorCount/float(numTestVecs))
19 print errorCount
测试结果:
(5)使用算法:构建完整可用系统
1 # 约会网站测试程序
2 def classifyPerson():
3 resultList = ["不喜欢", "魅力一般", "极具魅力"]
4 percentTats = float(raw_input(unicode('玩视频游戏所耗时间百分比: ','utf-8').encode('gb2312')))
5 ffMiles = float(raw_input(unicode("每年获取的飞机常客里程数: ",'utf-8').encode('gb2312')))
6 iceCream = float(raw_input(unicode("每周消费的冰淇淋公斤数: ",'utf-8').encode('gb2312')))
7 datingDataMat, datingLabels = file2matrix('datingTestSet2.txt')
8 normMat, ranges, minVals = autoNorm(datingDataMat)
9 inArr = array([ffMiles, percentTats, iceCream])
10 classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
11 print "你觉得这个男的怎么样:", resultList[classifierResult - 1]
测试结果:
2.2 手写识别系统
(1) 准备数据:将图像转为测试向量
目录trainingDigits中包含了大约2000个例子,如下图所示,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。
为了使用前面两个例子的分类器,我们必须将图像格式化处理为一个向量。我们将把一个32x32的二进制图像矩阵转换为1x1024的向量。
1 def img2vector(filename):
2 returnVect = zeros((1, 1024))
3 fr = open(filename)
4 for i in range(32):
5 lineStr = fr.readline()
6 for j in range(32):
7 returnVect[0, 32*i+j] = int(lineStr[j])
8 return returnVect
测试结果:
(2)测试算法:使用k近邻算法识别手写数字
1 # 测试代码
2 def handwritingClassTest():
3 hwLabels = []
4 trainingFileList = listdir('trainingDigits') # os.listdir()列出给定目录的文件名load the training set
5 m = len(trainingFileList)
6 trainingMat = zeros((m, 1024))
7 for i in range(m):
8 fileNameStr = trainingFileList[i]
9 fileStr = fileNameStr.split('.')[0] # 文件名格式为:5_124.txt ,按“.”分隔,取第一个
10 classNumStr = int(fileStr.split('_')[0]) # 实际的数字
11 hwLabels.append(classNumStr) # hwLabels实际数字列表
12 trainingMat[i,:] = img2vector('trainingDigits/%s' % fileNameStr)
13 testFileList = listdir('testDigits') #iterate through the test set
14 errorCount = 0.0
15 mTest = len(testFileList)
16 for i in range(mTest):
17 fileNameStr = testFileList[i]
18 fileStr = fileNameStr.split('.')[0] #take off .txt
19 classNumStr = int(fileStr.split('_')[0])
20 vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
21 classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
22 print "the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr)
23 if classifierResult != classNumStr:
24 errorCount += 1.0
25 print "\nthe total number of errors is: %d" % errorCount
26 print "\nthe total error rate is: %f" % (errorCount/float(mTest))
测试结果: