(代码下载地址:http://download.csdn.net/download/jichun4686/9910040)
目录
简介:
K最近邻(k-Nearest Neighbor,KNN)分类算法,是最简单的机器学习算法之一。即是给定一个训练数据集,对于输入的未知标签的实例,在训练数据集中找到与该实例最邻近的K个实例,则将其分类到K个有大多数的相同标签的标签中。如在下边实线圈中绿色实例属于标签为红色的一类,而在虚线中其属于标签为蓝色的一类,这就要看K的取值大小。一般K<=20,且K为正整数。
k-近邻算法的一般流程:
代码1:
# -*- coding: utf-8 -*- #中文注释记得加这句话
from numpy import * #导入numpy包 用import numpy 也可以,但是之后的使用其中的函数时前边必须加上numpy.方法名
from operator import *
# 创建数据集
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 #返回数据集(数组)和标签(列表)
# k-近邻算法
""""
inX: 用于分类的输入向量
dataSet:训练样本集
labels: 标签向量
k: 选择的最近邻居数目
"""
def classify0(inX, dataSet, labels, k):
#计算距离
dataSetSize = dataSet.shape[0] #dataSetSize为dataSet的行数
diff = tile(inX,(dataSetSize,1)) - dataSet #扩展输入向量,方便计算每一个数据到该向量的距离
sqDiff = diff ** 2 #数组每个元素进行平方
sqDistances = sqDiff.sum(axis=1)#求该数组的行和
distances = sqDistances ** 0.5 #为每行元素进行开方 即求出了每个数据和输入数据的距离
sortedDistIndicies = distances.argsort() #为该距离数组进行升序排序 返回排序结果的下标值
#确定k个点并计算其频率
classCount = {} #声明一个统计K个数据中类别与相应个数的字典
for i in range(k): #i从0到k-1进行循环
votelabel = labels[sortedDistIndicies[i]] #找到i对应下标的类别
classCount[votelabel] = classCount.get(votelabel,0)+1 #该类别如果在字典中存在,则取出其值后加1 如果不存在取默认值再+1
sortedClassCount = sorted(classCount.items(),key=itemgetter(1),reverse=True)#将字典返回为元祖列表 并依据其第二个元素进行降序排序
return sortedClassCount[0][0] #返回频率最高元祖的第一个量即标签
输入
group,labels = createdataset() #得到数据集和标签向量
classify0([0.5,0.5],group,labels,3) #测试[0,0]向量的分类
测试结果1:
此处我做了一些改动,将测试数据变为[0.5,0.5],因为书中提及,要测试的数据不能为分类器所知,但是[0,0]作为训练集中的样本数据已经提供给分类器,所以此处选取[0.5,0.5]。关于测试数据时,可以选取提前知道结果的数据进行输入,可以得到该分类器的错误率,进而可以对该分类器进行评估。
代码2:
创建文件DatingNet.py,程序如下。
from numpy import *
#程序1:将文本记录转换为Numpy的解析程序
#输入:文件名字符串
#输出:训练样本矩阵和类标签向量
def file2matrix(filename):
fr = open(filename)
arrayOfLines = fr.readlines()#得到一个元素为文件每一行的列表
numberOfLines = len(arrayOfLines)#得到文件的行数
numberOfCols = len(arrayOfLines[0].strip().split('\t')) #得到每一行的列数
returnMat = zeros((numberOfLines,numberOfCols-1)) #返回的数据集
classLabelVector = [] #返回的标签向量
index = 0 #为了给返回的数据集方便赋值的自增变量
for line in arrayOfLines:
listFromLine = line.strip().split('\t') #将每一行去掉换行符并且以\t为分隔符分隔为列表
returnMat[index,:] = listFromLine[0:3] #将列表中的0、1、2号元素赋给returnMat的第一行
classLabelVector.append(int(listFromLine[-1])) #-1提取列表中的最后一个元素并将其标为int变量 必须明确的通知解释器 否则python语言会将这些元素做字符串处理
index += 1
return returnMat,classLabelVector
【ps】
1、书中我认为arrayOLines应该是arrayOfLines,故做修改,与书中不一样。
2、注意classLabelVector.append(int(listFromLine[-1])) 这一行代码不要少了int,必须明确告诉解释器,否则会作为字符串处理的。
3、另外列表取值时用[],而不是小括号,否则会产生TypeError: ‘list’ object is not callable的错误,但是百度还百度不到。
建立测试文件test2.py
import DatingNet
datingDataMat,datingLabels = DatingNet.file2matrix('datingTestSet2.txt')
print(datingDataMat)
print(datingLabels[0:20])
【ps】
1、书中输入的文件名为datingTestSet.txt,但是此处应输入datingTestSet2.txt.
2、书中得到的datingDataMat与文本文件中的也是不同。
运行结果2
建立figure_Matplotlib.py。
共绘制五幅图
图一:同书中,是第二三列数据所作图。但是没有色彩或者记号。
图二:同书中,是第二三列数据所作图。有色彩和记号。但是观测不出来数据间的关系。
图三:同书中,是一二列数据所作图。有色彩和记号,可以看出明显分类。
图四:增加,是一三列数据所作图。对比可知其分类也不是很明显。
图五:增加,是三维图,可以进行拖动、旋转,方便得到分类关系,更加便于观察。
【ps】 该代码画三维图时,有一个警告,但是限于自身能力,没有查出来,自己对于画图这块是刚刚接触,接来搞懂了会进行补充的。
代码3:
from numpy import * #导入numpy包,这里要用到numpy中的array
from DatingNet import * #导入产生数据的包
import matplotlib #导入绘图的库
import matplotlib.pyplot as plt #将绘图的函数重命名
from mpl_toolkits.mplot3d import Axes3D #导入3维图像的包
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
fig1 = plt.figure() #创建图形
#————————————————二维------------------------------------------
#第一个子图 是第2个特征和第3个特征的散点图 但是没有颜色标识
ax = fig1.add_subplot(2,2,1)#代表创建1行1列从上到下的第一块的子图
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.xlabel(u'x 玩视频游戏所耗时间半分比')
plt.ylabel(u'y 每周消费的冰淇淋公升数')
plt.title(u'图一(2&&3)')
#定义三个类别的空列表
type1_x = []
type1_y = []
type2_x = []
type2_y = []
type3_x = []
type3_y = []
#第二个子图 是第2个特征和第3个特征的散点图
ax = fig1.add_subplot(2,2,2)#代表创建1行1列从上到下的第二块的子图
#循环获得每个列表中的值
for i in range(len(datingLabels)):
if datingLabels[i] == 1: # 不喜欢
type1_x.append(datingDataMat[i][1])
type1_y.append(datingDataMat[i][2])
if datingLabels[i] == 2: # 魅力一般
type2_x.append(datingDataMat[i][1])
type2_y.append(datingDataMat[i][2])
if datingLabels[i] == 3: # 极具魅力
type3_x.append(datingDataMat[i][1])
type3_y.append(datingDataMat[i][2])
type1 = ax.scatter(type1_x, type1_y, s=20, c='red')
type2 = ax.scatter(type2_x, type2_y, s=40, c='green')
type3 = ax.scatter(type3_x, type3_y, s=50, c='blue')
ax.legend((type1, type2, type3), (u'不喜欢', u'魅力一般', u'极具魅力'), loc=2)#显示图例 1 右上 2左上 3左下 4 右下 逆时针
#ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.xlabel(u'x 玩视频游戏所耗时间半分比')
plt.ylabel(u'y 每周消费的冰淇淋公升数')
plt.title(u'图二(2&&3)')
#第三个子图 是第1个特征和第2个特征的散点图
ax = fig1.add_subplot(2,2,3)#代表创建1行1列从上到下的第三块的子图
#循环获得每个列表中的值
for i in range(len(datingLabels)):
if datingLabels[i] == 1: # 不喜欢
type1_x.append(datingDataMat[i][0])
type1_y.append(datingDataMat[i][1])
if datingLabels[i] == 2: # 魅力一般
type2_x.append(datingDataMat[i][0])
type2_y.append(datingDataMat[i][1])
if datingLabels[i] == 3: # 极具魅力
type3_x.append(datingDataMat[i][0])
type3_y.append(datingDataMat[i][1])
type1 = ax.scatter(type1_x, type1_y, s=20, c='red')
type2 = ax.scatter(type2_x, type2_y, s=40, c='green')
type3 = ax.scatter(type3_x, type3_y, s=50, c='blue')
ax.legend((type1, type2, type3), (u'不喜欢', u'魅力一般', u'极具魅力'), loc=2)#显示图例 1 右上 2左上 3左下 4 右下 逆时针
#ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15.0*array(datingLabels),15.0*array(datingLabels))
plt.xlabel(u'x 每年获取的飞行常客里程数')
plt.ylabel(u'y 玩视频游戏所耗时间半分比')
plt.title(u'图三(1&&2)')
#第四个子图 是第1个特征和第3个特征的散点图
ax = fig1.add_subplot(2,2,4)#代表创建1行1列从上到下的第四块的子图
#循环获得每个列表中的值
for i in range(len(datingLabels)):
if datingLabels[i] == 1: # 不喜欢
type1_x.append(datingDataMat[i][0])
type1_y.append(datingDataMat[i][2])
if datingLabels[i] == 2: # 魅力一般
type2_x.append(datingDataMat[i][0])
type2_y.append(datingDataMat[i][2])
if datingLabels[i] == 3: # 极具魅力
type3_x.append(datingDataMat[i][0])
type3_y.append(datingDataMat[i][2])
type1 = ax.scatter(type1_x, type1_y, s=20, c='red')
type2 = ax.scatter(type2_x, type2_y, s=40, c='green')
type3 = ax.scatter(type3_x, type3_y, s=50, c='blue')
ax.legend((type1, type2, type3), (u'不喜欢', u'魅力一般', u'极具魅力'), loc=2)#显示图例 1 右上 2左上 3左下 4 右下 逆时针
#ax.scatter(datingDataMat[:,0],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
plt.xlabel(u'x 每年获取的飞行常客里程数')
plt.ylabel(u'y 每周消费的冰淇淋公升数')
plt.title(u'图四(1&&3)')
#————————————————三维
#第二个图 是第1个特征和第2个特征和第3个特征的散点图
fig2 = plt.figure() #创建图形
ax = fig2.add_subplot(111)
ax = Axes3D(fig2)
ax.scatter(datingDataMat[:,0],datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels),15.0*array(datingLabels))
ax.set_xlabel(u'x 每年获取的飞行常客里程数')
ax.set_ylabel(u'y 玩视频游戏所耗时间半分比')
ax.set_zlabel(u'z 每周消费的冰淇淋公升数')
plt.title(u'图四(1&&2&&3)')
plt.show()
运行结果3:
图一 – 图四
图五(初始)
图五(旋转后)
由上可见,图像化工具给我们展示数据内容,更加方便去辨识一些数据模型。
归一化数值是为了不让某个特殊的属性对计算结果影响比较大,而应该每一个特征都是等权重的。
转换公式: newValue = (oldValue - min)/(max - min)
代码4:
import DatingNet
datingDataMat,datingLabels = DatingNet.file2matrix('datingTestSet2.txt')
normMat,ranges,minVals = DatingNet.autoNorm(datingDataMat)
print(normMat)
print(ranges)
print(minVals)
运行结果4:
机器学习算法一个重要的工作就是评估算法的正确性,通常我们只提供90%的数据作为训练样本来训练分类器,而使用其余的10%进行测试分类器,以检测分类器的正确性。 在DatingNet.py中创建函数datingClassTest()函数,计算该分类器的错误率。 用到了DatingNet.py中的准备数据file2matrix()、归一化数值autoNorm()函数以及myKNN.py中的classify0()函数。
代码5:
def datingClassTest():
hoRatio = 0.10 #比率
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
normMat,ranges,minVals = autoNorm(datingDataMat)
m = normMat.shape[0]
numTestVecs = int(m*hoRatio) #要进行测试的数据
errorCount = 0
for i in range(numTestVecs):
classifierResult = myKNN.classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
print('the classifier came with back with:%d,the real answer is:%d'% (classifierResult,datingLabels[i]))
if(classifierResult != datingLabels[i]):
errorCount += 1 #统计错误量
print('错误率是:%f'%(errorCount/float(numTestVecs))) #输出错误率
运行结果5:
运行datingNet.py,输入datingClassTest()得到结果如下(与书中不同,与网上多数网友结果相同):
分类器结果是5%,算是一个还不错的结果。所以基本上可以满足约会人对于某一对象的可交往程度的判定。
测试通过后,可以进行应用。在datingNet.py中建立函数classifyPerson()函数,通过在约会网站找到某个人并输入他的信息,程序给对对方喜欢程度的预测值。 因python之后就没有raw_input,而是用input代替,所以代码中与书中不同,如果环境为python3还用raw_input就会产生NameError: name ‘raw_input’ is not defined的错误。
代码6:
#约会网站预测函数
def classifyPerson():
resultList = ['nont 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?'))
inArr = array([ffMiles,percentTats,iceCream])
datingDataMat,datingLabels = file2matrix('datingTestSet2.txt')
normMat,ranges,minVals = autoNorm(datingDataMat)
classifierResult = myKNN.classify0((inArr-minVals)/ranges,normMat,datingLabels,3)
print('you will probably like this person:',resultList[classifierResult-1])
运行结果6:
该系统只能识别数字0-9,图像转换成为了文本的若干文本数据。
建立文件hardWritingSystem.py,在其中写入img2vector()函数。
代码7:
#将一个图像文件转换为一维向量
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
运行结果7:
运行该文件,输入testVector = img2vector(‘digits/trainingDigits/0_0.txt’) 然后输出前32个testVector[0,0:31]。
代码8:
def handWrithingClassTest():
hwLabels = [] #定义一个空列表 记录标签向量
trainingFileList = listdir('digits/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('digits/trainingDigits/%s' % fileNameStr) #创建训练集
#创建测试集和标签
errorCount = 0.0 #统计错误个数
testFileList = listdir('digits/testDigits') #得到存放测试数据的文件列表
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i] #得到文件名和扩展名
fileStr = fileNameStr.split('.')[0] #得到文件名
classNumStr = int(fileStr.split('_')[0])#得到该文件的存储的数字
vectorUnderTest = img2vector('digits/testDigits/%s' % fileNameStr) #得到测试集
classifierResult = myKNN.classify0(vectorUnderTest,trainingMat,hwLabels,3) #计算结果
#print ("the classifier came back with: %d, the read answer is:%d" %(classifierResult,classNumStr) )
if (classifierResult !=classNumStr):#计算错误率
errorCount += 1.0
print ("the total number of errors is %d" % errorCount) #输出错误个数
print ("the total error rate is: %f" % (errorCount/float(mTest)))#输出错误率
运行结果8:
错误率为1.06%,改变k值、训练、测试样本错误率会有所变化。
实际使用这个算法时,执行效率并不是很高,因为数据量太大会产生额外的时间、空间开销。而决策树就是k-近邻的优化版,可以节省大量的计算开销。
k-近邻算法花了好一段时间终于搞定了,其中的一些python知识也详细的去查过,虽然假期的效率并没有太高,但是觉得每一步都走精也是一种收获,下面对k-近邻进行一下总结。
本章通过基础的k-近邻算法和两个例子进行了讲解和说明,主要就是将测试数据或者训练数据转换为要输入矩阵的格式,可能用到了归一化,然后使用k-近邻进行解析数据从而返回预测结果。其中还设计数据的绘图、错误率的计算以及大量的python矩阵知识。关于本章中python的知识,由于内容量太大,我放到了单独的博客中,供以没学过python的人参考。
k-近邻有简单有效的优点、但是时空开销比较大,所以引进下一章可以节省大量的计算开销的决策树。
(代码下载地址:http://download.csdn.net/download/jichun4686/9910040)
【2017.7.25决策树!!!!】