K-邻近算法是一种分类算法其核心在于矩阵的运算。
概述:已知一部电影的打斗镜头、接吻镜头来判断电影类型
目前已有数据集包含打斗镜头数、接吻镜头数和其下属的电影类型,要根据该数据集判断新的已知打斗镜头数、接吻镜头数的电影是何种类型,我们可以使用KNN来解决该问题。
具体的思路:
计算未知电影与样本集中所有电影的距离,按从小到达排序,找前k个距离最近的电影,统计其电影类型,将从前k个样本中获得的电影类型按出现次数从大到小排序,出现次数最多的电影类型作为未知电影的类型预测。
图示:
图1为样本集在图中的分布,不同颜色代表不同类型的数据。图二中红色的圆圈代表未知数据的位置。图三是k取3时选取的邻近的3个样本,我们据此可以推测未知数据属于蓝色类型。
代码复现
def classify(inX,dataSet,labels,k):
dataSize=dataSet.shape[0]#获取行数
dataDIff=tile(inX,(dataSize,1))-dataSet#tile是将inX平铺dataSize次
sqDataDiff=dataDIff**2#平方
sqDistance=np.sum(sqDataDiff,axis=1)#算到每一点的距离平方,必须要np才行
distances=sqDistance**0.5#算到每一点的距离
sortDisIndex=distances.argsort()#获取距离从小到达排序后其原本位置的数组
classCount={}
for i in range(k):
className=labels[sortDisIndex[i]]#sortDisIndex[i]获得labels对映位置标签
classCount[className]=classCount.get(className,0)+1#计数
classSort=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)#按照label出现次数大小排序
return classSort[0][0]#返回出现次数最多的标签
这里我们采取的距离算法是欧式距离公式:。函数有四个输入参数:待分类向量为inX,训练集为dataSet,标签向量为labels,选取的邻近向量数目为k。
注意数据集的特征值储存在矩阵dataSet中,而类型储存在标签labels中,labels[i]对应矩阵第i行数据。
实例1:约会网站的配对效果
小美将约会对象分为三类:不喜欢的人、魅力一般的人、极有魅力的人。海伦希望在周一到周五约会魅力一般的人,在周末约会极具魅力的人(为什么能办到有那么多约会对象)。我们借助KNN帮她更好判断约会对象类型。目前我们已有的数据1000行,每个样本包含以下三个特征:
每年获得的飞行里程数
玩游戏所耗时百分比
每周消费的冰激凌公升数
目前数据的提供是txt形式,为了更好地处理数据我们需要将进行格式转换:
def file2matrix(filename):#将数据集从txt形式转化为矩阵形式和标签集
file=open(filename)
lines=file.readlines()#获取样本信息
numberofLines=len(lines)#获取样本个数
matrix=np.zeros((numberofLines,3))#创建预备矩阵存储样本特征信息
labels=[]#存储分类信息
index=0#表示当前正在处理的样本位置
for line in lines:
line=line.strip()#去除前后空格
listFromLine=line.split('\t')#根据空格转化为数组
col=len(listFromLine)
matrix[index,:]=listFromLine[0:3]#将样本特征存储到矩阵中
labels.append(int(listFromLine[-1]))#存储当前样本的类型
index+=1
return labels,matrix
转换完格式以后我们会发现,三者单位并不一致,因此像飞行公里数这样的数字较大的数据对分类结果的影响远大于另外两个特征值,但是对于小美来说这三个特征在她看来同等重要,因此我们需要对数据进行归一化处理,将其值均控制在0—1或-1—1内,归一化公式:
def autoNorm(dataSet):#归一化处理
minVals=dataSet.min(0)#获取每一列的最小值
maxVals=dataSet.max(0)#获取每一列的最大值
ranges=maxVals-minVals
row=dataSet.shape[0]
normDataset=dataSet-tile(minVals,(row,1))
normDataset=normDataset/tile(ranges,(row,1))
return normDataset,ranges,minVals
我们现在已经有了分类器,但是需要进一步评估算法的准确率,因此我们选取10%的数据作为测试数据,剩下的90%为训练集。这10%的数据应该随机选择,因为此样本集并没有排序处理,所以我们可以直接选择前10%的数据作为测试集:
def classTest(dataLabels,dataMat):#原有数据中选择一部分数据作为测试
hoRatio=0.1#取10%的数据
normDataSet=autoNorm(dataMat)#归一化处理
m=normDataSet.shape[0]#需要取的数据数目
numOfTest=int(m*hoRatio)
error=0
for i in range(numOfTest):
label=classify(normDataSet[i,:],normDataSet[numOfTest:m,:],dataLabels[numOfTest:m],7)
print('the classifier came back with %d,the real answer is %d'%(label,dataLabels[i]))
if label!=dataLabels[i]:error+=1
print('the total error rate is:%f'%(error/numOfTest))
测试结果错误率为4.6%我们判定算法较为准确,可以进行实际运用:
def classifyPerson():
result=['not at all','samll doses','large doses']
percentTats=float(input('percentage of time spent playing video games?'))
ffMiles=float(input('frequent fliter miles earned per year?'))
iceCream=float(input('liters of ice cream consumed per year?'))
datingLabels,datingMatrix=file2matrix('datingTestSet2.txt')
normDataSet,range,mins=KNN.autoNorm(datingMatrix)
arr=np.array([percentTats,ffMiles,iceCream])
type=result[KNN.classify((arr-mins)/range,normDataSet,datingLabels,5)-1]#注意这里-1是因为返回的数据为1、2、3类型,但是要对应到result中,index从0开始,所以-1
print('you will probably like this person:',type)
整体代码展示:
#KNN
import operator
import numpy as np
from numpy import tile
def classify(inX,dataSet,labels,k):
dataSize=dataSet.shape[0]#获取行数
dataDIff=tile(inX,(dataSize,1))-dataSet#tile是将inX平铺dataSize次
sqDataDiff=dataDIff**2#平方
sqDistance=np.sum(sqDataDiff,axis=1)#算到每一点的距离平方,必须要np才行
distances=sqDistance**0.5#算到每一点的距离
sortDisIndex=distances.argsort()#获取距离从小到达排序后其原本位置的数组
classCount={}
for i in range(k):
className=labels[sortDisIndex[i]]#sortDisIndex[i]获得labels对映位置标签
classCount[className]=classCount.get(className,0)+1#计数
classSort=sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)#按照label出现次数大小排序
return classSort[0][0]#返回出现次数最多的标签
def autoNorm(dataSet):#归一化处理
minVals=dataSet.min(0)#获取每一列的最小值
maxVals=dataSet.max(0)#获取每一列的最大值
ranges=maxVals-minVals
row=dataSet.shape[0]
normDataset=dataSet-tile(minVals,(row,1))
normDataset=normDataset/tile(ranges,(row,1))
return normDataset,ranges,minVals
def classTest(dataLabels,dataMat):#原有数据中选择一部分数据作为测试
hoRatio=0.1#取10%的数据
normDataSet=autoNorm(dataMat)#归一化处理
m=normDataSet.shape[0]#需要取的数据数目
numOfTest=int(m*hoRatio)
error=0
for i in range(numOfTest):
label=classify(normDataSet[i,:],normDataSet[numOfTest:m,:],dataLabels[numOfTest:m],7)
print('the classifier came back with %d,the real answer is %d'%(label,dataLabels[i]))
if label!=dataLabels[i]:error+=1
print('the total error rate is:%f'%(error/numOfTest))
#DatingTest
import numpy as np
import KNN as KNN
def file2matrix(filename):#将数据集从txt形式转化为矩阵形式和标签集
file=open(filename)
lines=file.readlines()#获取样本信息
numberofLines=len(lines)#获取样本个数
matrix=np.zeros((numberofLines,3))#创建预备矩阵存储样本特征信息
labels=[]#存储分类信息
index=0#表示当前正在处理的样本位置
for line in lines:
line=line.strip()#去除前后空格
listFromLine=line.split('\t')#根据空格转化为数组
col=len(listFromLine)
matrix[index,:]=listFromLine[0:3]#将样本特征存储到矩阵中
labels.append(int(listFromLine[-1]))#存储当前样本的类型
index+=1
return labels,matrix
def classifyPerson():
result=['not at all','samll doses','large doses']
percentTats=float(input('percentage of time spent playing video games?'))
ffMiles=float(input('frequent fliter miles earned per year?'))
iceCream=float(input('liters of ice cream consumed per year?'))
datingLabels,datingMatrix=file2matrix('datingTestSet2.txt')
normDataSet,range,mins=KNN.autoNorm(datingMatrix)
arr=np.array([percentTats,ffMiles,iceCream])
type=result[KNN.classify((arr-mins)/range,normDataSet,datingLabels,5)-1]#注意这里-1是因为返回的数据为1、2、3类型,但是要对应到result中,index从0开始,所以-1
print('you will probably like this person:',type)
classifyPerson()
实例2:手写识别系统
手写识别系统的样本集为:
每一个文件中的样本均为32*32的0,1数字组成,我们可以看出此时图中展示的为数字0。
首先我们需要将每一个文件转化为1*1042的数组。分类器与之前相同,在评估除有些许改动,因为测试集和训练集已经分开就不需要再在原有集合中抽取,变动处及该系统的整体代码如下:
#KNN中改进处
def classTest(dataMat,dataLabels,testData,testLabel):#原有数据+新数据集测试
numOfTest=testData.shape[0]
error=0
for i in range(numOfTest):
label=classify(testData[i,:],dataMat,dataLabels,7)
print('the classifier came back with %s,the real answer is %s'%(label,testLabel[i]))
if label!=testLabel[i]:error+=1
print('the total error rate is:%f'%(error/numOfTest))
#手写预测系统
from os import listdir
import numpy as np
import KNN as KNN
def imgSingleFile(filename):
vect=np.zeros((1,1024))
file=open(filename)
for i in range(32):
line=file.readline()
for j in range(32):
vect[0,32*i+j]=line[j]
return vect
def imgData(dirName):
labels=[]
traningFilelist=listdir(dirName)
count=len(traningFilelist)
traningData=np.zeros((count,1024))
for i in range(count):
fileName=traningFilelist[i]
className=fileName.split('_')[0]
labels.append(className)
traningData[i,:]=imgSingleFile('trainingDigits/%s'%fileName)
return traningData,labels
#
oralData,oralLabel=imgData('trainingDigits')
# testDate,testLabel=imgData('testDigits')
# KNN.classTest(oralData,oralLabel,testDate,testLabel)
def predict():
file=input('please input filename:')
preData=imgSingleFile(file)
label=KNN.classify(preData,oralData,oralLabel,7)
print('the number probably is:', label)
predict()