k近邻法(k-nearest neighbor, k-NN)是1967年由Cover T和Hart P提出的一种基本分类与回归方法。它的工作原理是:存在一个样本数据集合,也称作为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一个数据与所属分类的对应关系。输入没有标签的新数据后,将新的数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
1.导入算法所用的numpy和operate模块。
import numpy as np
import operator
2.创建最简单的带标签的数据集,并将其封装在函数creatDateSet()中。
def creatDateSet():
group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])#创建一个数组
labels = ['A','A','B','B']
return group,labels
3.实现最简单的KNN算法,并将其封装在函数classify0()中。
①.首先定义classify0()函数,有四个参数,inx表示待分类的新数据,dataset表示已知标签的数据集,labels为数据对应的标签,k表示待分类样本的分类取决于k个最相近的数据。
def classify0(inX, dataSet, labels, k):
②.函数的实现:首先把数据集中的点放入矩阵中,然后复制n倍(等同于dateset中样本的数目),再与dateset中的数据求距离。相当于将待分类数据与所有样本的距离存储在一个数组distances中。
dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
#将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组
之后返回距离从小到大排列后的索引,放入数组sortedDistIndicies中。
sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
创建字典classCount,统计距离待分类样本最相似的k个样本的类别及每类的样本个数,最后返回最相似的那个类别。
classCount={}#创建字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#例如{'B': 2, 'A': 1};统计了最近的k个点分别有多少属于哪类
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
return sortedClassCount[0][0]
4.综上:完整代码如下
import numpy as np
import operator
def creatDateSet():
group = np.array([[1.0,1.1],[1.0,1.0],[0,0],[0,0.1]])#创建一个数组
labels = ['A','A','B','B']
return group,labels
def classify0(inX, dataSet, labels, k):
#inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
#将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组
sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
classCount={}#创建字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
return sortedClassCount[0][0]
def main():
group,labels = creatDateSet()
result = classify0([0,0], group ,labels,3)
print(result)
if __name__ == '__main__':
main()
5.运行结果:
E:\anaconda\envs\tf1\python.exe "C:/Users/范淑卷/Desktop/ML实战练习代码/P19 最简单的KNN算法/knn.py"
B
Process finished with exit code 0
得知待分类的点[0,0]应该为类别‘B’。
6.学习心得:
①.KNN算法的实现;
②.numpy模块下tile函数用来形成矩阵,从而便于用矩阵对特征运算;
③.shape函数用来返回矩阵或者数组的大小;
④.numpy模块下的argsort函数用于返回数组的索引;
⑤.内置的sorted函数实现对字典类型数据的排序。
分析:该场景的例子共有三个特征值:1.每年获得的飞行常客里程数;2.玩游戏所耗时间百分比;3.每周消费的冰淇淋公升数。但是由于特征1比另外两个特征的数值要大很多,所以直接计算时特征1势必会成为影响分类的决定性因素,然而本题的三个特征同等重要,所以首先需要对数据进行归一化处理。又因为只给了一组数据,所以我们要挑选一部分作为测试数据(本例题挑选了前百分之10的数据作为测试)。
1.首先,先将txt文件中的数据导入,并封装在函数file2matrix()中。
def file2matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) #get the number of lines in the file
returnMat = np.zeros((numberOfLines,3)) #prepare matrix to return
classLabelVector = [] #prepare labels return
fr.close()
fr = open(filename)
index = 0
for line in fr.readlines():#按行读入
line = line.strip()#去掉每一行回车键
listFromLine = line.split('\t')#按制表符分隔
returnMat[index,0:3] = listFromLine[0:3]#将特征存入returnMat中
classLabelVector.append(int(listFromLine[-1]))#将标签存入classLabelVector中
#此处的listFromLine[-1]用listFromLine[3]也可以,不过一般习惯用-1表示数组中的最后一个
index += 1#遍历文件,index用来控制数据特征在returnMat中的位置
fr.close()
return returnMat,classLabelVector#返回数据特征矩阵和标签数组
程序中,下面代码的作用是去掉txt文件中所有的回车键,并根据制表符分隔读取的每一行。
line = line.strip()#去掉每一行最后的回车键
listFromLine = line.split('\t')
2.在将数据导入之后,应当对数据进行归一化处理,归一化后的数据的各特征分布在0到1之间.
def autoNorm(dataSet):
minVals = dataSet.min(0)#dataset中的最小值
maxVals = dataSet.max(0)#dataset中的最大值
ranges = maxVals - minVals#最大差(为了将结果归一化到零到壹之间)
# normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]#dataset中的数据量
normDataSet = dataSet - np.tile(minVals, (m,1))#将该区间规范到0开始
normDataSet = normDataSet/ np.tile(ranges, (m,1)) #将结果归一化到零到壹之间
return normDataSet, ranges, minVals
#返回归一化后的数据集,数据极限差值和最小值(归一化前)
3.实现kNN算法,类比实战一中的实现。
def classify0(inX, dataSet, labels, k):
#inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
#将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组
sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
classCount={}#创建字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
return sortedClassCount[0][0]
4.将txt文件中的前百分之10数据用来测试,返回每个数据的分类情况以及测试数据的错误个数和错误率。
def datingClassTest():
hoRatio = 0.10 # hold out 10%
datingDataMat, datingLabels = file2matrix('E:\机器学习算法刻意练习\机器学习实战书电子版'
'\machinelearninginaction\Ch02\datingTestSet2.txt')# 导入数据
normMat, ranges, minVals = autoNorm(datingDataMat)#首先先归一化
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)#拿出百分之10的数据用于测试
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 5)
print("the classifier came back with:{}, the real answer is: {}" .format(classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("the total error rate is: {}" .format(errorCount / float(numTestVecs)))
print(errorCount)
5.综上,实战二之约会网站的完整代码如下(此例中取的k=5):
import numpy as np
import matplotlib.pyplot as plt
import operator
def file2matrix(filename):
fr = open(filename)
numberOfLines = len(fr.readlines()) #get the number of lines in the file
returnMat = np.zeros((numberOfLines,3)) #prepare matrix to return
classLabelVector = [] #prepare labels return
fr.close()
fr = open(filename)
index = 0
for line in fr.readlines():
line = line.strip()#去掉所有回车键
listFromLine = line.split('\t')
returnMat[index,0:3] = listFromLine[0:3]#将特征存入returnMat中
classLabelVector.append(int(listFromLine[-1]))#将标签存入classLabelVector中
index += 1
fr.close()
return returnMat,classLabelVector
def autoNorm(dataSet):
minVals = dataSet.min(0)#dataset中的最小值
maxVals = dataSet.max(0)#dataset中的最大值
ranges = maxVals - minVals#最大差(为了将结果归一化到零到壹之间)
# normDataSet = zeros(shape(dataSet))
m = dataSet.shape[0]#dataset中的数据量
normDataSet = dataSet - np.tile(minVals, (m,1))#将该区间规范到0开始
normDataSet = normDataSet/ np.tile(ranges, (m,1)) #将结果归一化到零到壹之间
return normDataSet, ranges, minVals
#返回归一化后的数据集,最大最小差和最小值(归一化前)
def classify0(inX, dataSet, labels, k):
#inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
#将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组
sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
classCount={}#创建字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
return sortedClassCount[0][0]
def datingClassTest():
hoRatio = 0.10 # hold out 10%
datingDataMat, datingLabels = file2matrix('E:\机器学习算法刻意练习\机器学习实战书电子版'
'\machinelearninginaction\Ch02\datingTestSet2.txt')# 导入数据
normMat, ranges, minVals = autoNorm(datingDataMat)#首先先归一化
m = normMat.shape[0]
numTestVecs = int(m * hoRatio)#拿出百分之10的数据用于测试
errorCount = 0.0
for i in range(numTestVecs):
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :], datingLabels[numTestVecs:m], 5)
print("the classifier came back with:{}, the real answer is: {}" .format(classifierResult, datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("the total error rate is: {}" .format(errorCount / float(numTestVecs)))
print(errorCount)
def main():
datingClassTest()
if __name__ == '__main__':
main()
6.运行的部分结果如下(测试集数据个数过多,无法一一列出):
the classifier came back with:3, the real answer is: 3
the classifier came back with:2, the real answer is: 2
the classifier came back with:2, the real answer is: 1
the classifier came back with:1, the real answer is: 1
the total error rate is: 0.05
5.0
Process finished with exit code 0
可知KNN在测试集上的错误率为5%。
7.学习心得:
①.如何将单文件的数据的特征与标签存储在矩阵和数组中;
②.数据的归一化处理;
③.取数据中的一部分作为测试数据。
分析:具体的预测分类实现方法与前两个实例没什么区别,最大的难点可能是如何将不同文件中的各类“图形文件”的特征和标签读入。
1.重点and难点:多文件的读入
此题与实战二不同。实战二是将三个特征与标签放入了文件的每一行中,每一行包含了三个特征与分类标签;而这个题每一个txt文件都包含了32×32个特征值,而每一个样本的类别则是文件名字的第一个数字。因此要将其分两步读入,首先完成对单文件特征的读入,再次完成多文件特征与类别标签的读入。
①单文件特征的读入:将32×32的特征转换存储到1×1024的矩阵中,每一行都是一个数据的所有特征值。
def img2vector(filename):
returnVect = np.zeros((1,1024))#创建一个1*1024的矩阵
fr = open(filename)
for i in range(32): #依次读入每个文件的32行数据
lineStr = fr.readline()
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])#读入每行的32个字符,并转化为int型存储在矩阵returnVect中
fr.close()
return returnVect
②类别标签的读入:用listdir函数遍历多个txt文件存储的文件夹,获得各个txt文件的名字,然后通过split函数获得文件名的第一个数字(即该数据的类别),存储在列表hwLabels中。
hwLabels = []
trainingFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits') #样本文件的文件名
m = len(trainingFileList) #m表示有多少训练文件(数据样本)
trainingMat = np.zeros((m,1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] #去掉文件名后的 .txt
classNumStr = int(fileStr.split('_')[0])#得到该文件数据的类别
hwLabels.append(classNumStr)#存储所有文件的类别
数据集与测试集读取方法想同,在此不再赘述。
多文件数据特征的读入:路径前半部分相同,可以固定为’E:\机器学习算法刻意练习\算法所用数据\2.KNN\trainingDigits\’,最后的即txt文件的文件名,可以通过遍历由listdir函数获得的列表获得。
for i in range(m):
fileNameStr = trainingFileList[i]
trainingMat[i,:] = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr)
#将所有训练数据文件依次读入并存储在矩阵trainingMat中
2.KNN算法同上,不再赘述。
3.最后通过比较测试数据的真实分类与预测分类给出KNN算法预测错误的个数以及预测错误率。(此题取K=5)
mTest = len(testFileList)#表示有多少个测试文件(数据)
for i in range(mTest):
fileNameStr2 = testFileList[i]
fileStr2 = fileNameStr2.split('.')[0] #take off .txt
classNumStr2 = int(fileStr2.split('_')[0])
vectorUnderTest = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr2)
# 将所有训练测试文件依次读入并存储在矩阵trainingMat中
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 5)
print("the classifier came back with:{}, the real answer is:{}".format(classifierResult, classNumStr2))
if classifierResult != classNumStr2: #如果分类与KNN预测的结果不一样,则错误个数++
errorCount += 1.0
print("the total number of errors is: {}" .format(errorCount))
print("the total error rate is:{}" .format(errorCount/float(mTest)))
4.综上:完整的程序代码如下:
import numpy as np
import matplotlib.pyplot as plt
import operator
from os import listdir
def img2vector(filename):
returnVect = np.zeros((1,1024))#创建一个1024维度的数组
fr = open(filename)
for i in range(32): #依次读入每个文件的32行数据
lineStr = fr.readline()
for j in range(32):
returnVect[0,32*i+j] = int(lineStr[j])#读入每行的32个字符,并转化为int型存储在数组returnVect中
fr.close()
return returnVect
def classify0(inX, dataSet, labels, k):
#inX是待分类的样本,dataset为数据集,labels为样本数据对应的标签,k为距离最近的k个样本
dataSetSize = dataSet.shape[0]#第一维的个数(多少行)
diffMat = np.tile(inX, (dataSetSize,1)) - dataSet
#将inX重复1次形成datasetsize行的数组,与原来的dataset的坐标差值
sqDiffMat = diffMat**2#坐标差的平方
sqDistances = sqDiffMat.sum(axis = 1)#距离的平方
distances = sqDistances**0.5#一个数组
sortedDistIndicies = np.argsort(distances)#返回的数组从小到大排序后对应的数组索引
classCount={}#创建字典
for i in range(k):
voteIlabel = labels[sortedDistIndicies[i]] #距离最近的k个点分别是哪一类
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1#{'B': 2, 'A': 1}统计了最近的k个点分别有多少属于哪类
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)#按字典内容降序排列
return sortedClassCount[0][0]
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits') #样本文件的文件名
m = len(trainingFileList) #m表示有多少训练文件(数据样本)
trainingMat = np.zeros((m,1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] #去掉文件名后的 .txt
classNumStr = int(fileStr.split('_')[0])#得到该文件数据的类别
hwLabels.append(classNumStr)#存储所有文件的类别
trainingMat[i,:] = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr)
#将所有训练数据文件依次读入并存储在矩阵trainingMat中
testFileList = listdir('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\testDigits') #iterate through the test set
errorCount = 0.0
mTest = len(testFileList)#表示有多少个测试文件(数据)
for i in range(mTest):
fileNameStr2 = testFileList[i]
fileStr2 = fileNameStr2.split('.')[0] #take off .txt
classNumStr2 = int(fileStr2.split('_')[0])
vectorUnderTest = img2vector('E:\机器学习算法刻意练习\算法所用数据\\2.KNN\\trainingDigits\\'+fileNameStr2)
# 将所有训练测试文件依次读入并存储在矩阵trainingMat中
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 5)
print("the classifier came back with:{}, the real answer is:{}".format(classifierResult, classNumStr2))
if classifierResult != classNumStr2: #如果分类与KNN预测的结果不一样,则错误个数++
errorCount += 1.0
print("the total number of errors is: {}" .format(errorCount))
print("the total error rate is:{}" .format(errorCount/float(mTest)))
def main():
handwritingClassTest()
if __name__ == '__main__':
main()
5.运行的部分结果为:
the classifier came back with:9, the real answer is:9
the classifier came back with:9, the real answer is:9
the classifier came back with:9, the real answer is:9
the total number of errors is: 17.0
the total error rate is:0.017970401691331923
Process finished with exit code 0
可知共有17个数据预测错误,错误率为1.797%。
6.学习心得:
①.多文件的遍历与特征读取;
②.多文件的遍历与分类标签的读取。