目录
本章内容
1. k-近邻算法概述
1.1 使用python导入数据
1.2 拿到数据
1.3 解析数据
1.4 测试数据
1.5 结果输出
2.使用K-近邻算法改进约会网站的配对效果
2.1 准备数据:从文本文件中解析数据
2.2 分析数据:使用Matplotlib创建散点图
2.3 准备数据:归一化数据
2.4 测试算法:作为完整程序验证分类器
2.5 使用算法:构建完整可用系统
3.手写识别系统
3.1 准备数据:将图像转换为测试向量
3.2 测试算法:使用k-近邻算法识别手写数字
本博客涉及相关代码和数据
提取码: cyf1
- k-近邻分类算法
- 从文本文件中解析和导入数据
- 使用Matplotlib创建扩散图
- 归一化数值
- 优点:精度高、对异常值不敏感、无数据输入假定
- 缺点:计算复杂度高、空间复杂度高
- 使用数据范围:数值型和标称型
- 原理:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中没一个数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似的数据的分类标签。一般来说,只选择样本数据中前K(不大于20)个最相似的数据。选择K个最相似数据中出现次数最多的分类,作为新数据的分类
# 导入相关包
from numpy import *
import operator
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
group,labels=createDataSet()
# inX用于分类的输入向量
# dataSet输入的训练样本集
# labels标签向量
# k最近邻居的数目
def classify(inX,dataSet,labels,k):
dataSetSize=dataSet.shape[0] #训练样本的大小
# 有多少行数据 shape反回一个元组。(行, 列)
# tile(inX, (dataSetSize,1)) 创建一个numpy的array,dataSetSize行,每行数据是inX
# - dataSet 矩阵减法 m*n矩阵A - m*n矩阵B
diffMat=tile(inX,(dataSetSize,1))-dataSet
# 矩阵的每个值平方
sqDiffMat=diffMat**2 #diffMat的平方
# 横向求和,得到一个一维数组,每个值都是和
sqDistances=sqDiffMat.sum(axis=1)
# **代表乘方 *代表乘法
distances=sqDistances**0.5 #sqDistance的0.5次方
# 得到排序后的下标数组
sortedDistIndicies=distances.argsort()
classCount={}
# 只取前K个值
for i in range(k):
# 找到对应的标签
voteIlabel=labels[sortedDistIndicies[i]]
# 如果找到相同标签利用map来计数
classCount[voteIlabel]=classCount.get(voteIlabel,0)+1
# 排序加反转
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True)
return sortedClassCount[0][0]
classify([0,0],group,labels,3)
‘B’
可以看出来,测试的数据[0,0]距离B标志的两个点更近,因此程序判定该点的标签也为B
我的朋友海伦一直使用在线约会网站寻找适合自己的约会对象。尽管约会网站会推荐不同的人选,但她没有从中找到喜欢的人。经过一番总结,他发现曾交往过三种类型的人:
- 不喜欢的人
- 魅力一般的人
- 极具魅力的人
尽管发现了上述规律,蛋海伦依然无法将约会网站推荐的匹配对象归入恰当的分类。他觉得可以在周一到周五约会那些魅力一般的人,二周末则更喜欢与那些极具魅力的人约会。海伦希望有一个分类软件可以更好的帮助他将匹配对象划分到确切的分类中。此外海伦还收集了一些约会网站未曾记录的数据信息,他认为这些数据信息更有助于匹配对象的分类。
- 每年获得的飞行常客里程数
- 玩视频游戏所耗时间百分比
- 每周消费的冰激淋公升数
def file2matrix(filename):
# 打开文件
fr=open(filename)
# 读出文件的所有行
arrayOLines=fr.readlines()
# 得到文件行数
numberOfLines=len(arrayOLines)
# 生成一个 行数*3的数值全部为0的矩阵
returnMat=zeros((numberOfLines,3))
classLabelVector=[]
index=0
for line in arrayOLines:
# strip()函数:移除括号内的字符序列
line=line.strip() #删除行内的空白字符
#利用对其字符将字符串分开
listFromLine=line.split('\t')
# 下表为index的数组等于每一行的前三个字符串
returnMat[index,:]=listFromLine[0:3]
# 在label数组里面存上标签的值
# append()函数追加到后面
classLabelVector.append(listFromLine[-1])
index+=1
# 返回源数据和标签值
return returnMat,classLabelVector
加载数据,其中用到的文本文件在压缩包里:
datingDataMat2,datingLabels2=file2matrix('datingTestSet2.txt')
利用datingDataMat矩阵的第二、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰激淋公升数”的散点图。
import matplotlib
import matplotlib.pyplot as plt
# 使之支持中文字体
from pylab import *
plt.rcParams['font.sans-serif'] = ['SimHei']
# 生成一个图框
# fig=plt.figure()
fig=plt.figure(figsize=(10,6)) #框定图框的大小
# 统计图的标题
plt.title("“玩视频游戏所耗时间百分比”与“每周消费的冰激淋公升数”")
plt.xlabel("玩视频游戏所耗时间百分比") #x轴标签
plt.ylabel("每周消费的冰激淋公升数") #y轴标签
# plt.xticks()和plt.yticks()设置X轴Y轴上取得值及其对应的名字,对于非连续数据
# plt.xticks([0,1],["男","女"])
# 一个1*1的子图
ax=fig.add_subplot(111)
ax.scatter(datingDataMat[:,1],datingDataMat[:,2])
plt.legend(loc = "best")
plt.show
结果:
由于没有使用样本分类的特征值,我们很难从图中看到任何有用的数据模式信息。我们利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点,然后就能够基本上看到数据点所属三个样本的分类区域。
datingLabelInt=[]
for line in datingLabels2:
datingLabelInt.append(int(line))
# datingLabelInt[:20]
# 3:特别喜欢 2:一般喜欢 1:不喜欢
# 生成一个图框
fig=plt.figure(figsize=(10,6))
# 一个1*1的子图
ax=fig.add_subplot(111)
# x值 y值 颜色 粗细
ax.scatter(datingDataMat2[:,1],datingDataMat2[:,2],5.0*array(datingLabelInt),5.0*array(datingLabelInt))
plt.xlabel("玩视频游戏所耗时间百分比") #x轴标签
plt.ylabel("每周消费的冰激淋公升数") #y轴标签
# 显示标签
plt.legend(loc = "best")
# 画图
plt.show()
输出结果:
“玩视频游戏所耗时间百分比”与“每年获取的飞行常客里程数”的散点图:
# 生成一个图框
fig=plt.figure(figsize=(10,6))
# 一个1*1的子图
ax=fig.add_subplot(111)
# x值 y值 颜色 粗细
ax.scatter(datingDataMat2[:,1],datingDataMat2[:,0],5.0*array(datingLabelInt),5.0*array(datingLabelInt))
plt.xlabel("玩视频游戏所耗时间百分比") #x轴标签
plt.ylabel("每年获取的飞行常客里程数") #y轴标签
# 显示标签
plt.legend(loc = "best")
# 画图
plt.show()
结果:
由于“每年获取的飞行常客里程数”的数值远大于其他两个特征值,因此对于计算结果的影响远大于其他两个特征值,在处理不同取值范围的特征值是,我们通常采用的方法是将数值归一化,下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
newValue=(oldValue-min)/(max-min)
归一化特征值:
# 归一化特征值
def autoNorm(dataSet):
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
# 最大值减去最小值,得到跨度
ranges=maxVals-minVals
normDataSet=zeros(shape(dataSet))
m=dataSet.shape[0]
# 当前值减去最小值 得到差值
normDataSet=dataSet-tile(minVals,(m,1))
# 差值除以跨度得到归一化数据
normDataSet=normDataSet/tile(ranges,(m,1))
return normDataSet,ranges,minVals
进行归一化:
normMat,ranges,minVals=autoNorm(datingDataMat2)
normMat
输出结果:
分类器针对约会网站的测试代码:
def datingClassTest():
hoRatio=0.10
datingDataMat,datingLabels=file2matrix("datingTestSet.txt")
normMat,ranges,minVals=autoNorm(datingDataMat)
m=normMat.shape[0]
numTestVecs=int(m*hoRatio)
errorCount=0.0
for i in range(numTestVecs):
classifierResult=classify(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],3)
# print(classifierResult)
print ("the classifier came back with: %s, the real answer is: %s" % (classifierResult, datingLabels[i]))
#统计错误数
if (classifierResult != datingLabels[i]): errorCount += 1.0
# 统计错误率
print ("the total error rate is: %f" % (errorCount/float(numTestVecs)))
print (errorCount)
测试代码:
datingClassTest()
输出结果:
可以看到错误率为5%
def classifyPerson():
percentTats=float(input("玩游戏花费的时间所占的百分比:"))
ffmile=float(input("每年获取的飞行常客里程数:"))
iceCreame=float(input("每周消费的冰激淋公升数:"))
# datingDataMat,datingLabels=file2matrix('datingTestSet2.txt')
normMat,ranges,minVals=autoNorm(datingDataMat)
inArr=array([percentTats,ffmile,iceCreame])
classifileResult=classify((inArr-minVals)/ranges,normMat,datingLabels,3)
print("可能会认为这个人是:",classifileResult)
测试代码:
classifyPerson()
输入的样例为:10,10000,0.5,输出的结果为:
为了简单起见,这里构造的系统只能够识别数字0到9,参加下图。需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32*32像素的黑白图像。尽管采用文本格式存储图像不能有效的利用内存空间,但是为了方便起见,我们还是将图像转换为文本格式。
为了使用前面两个例子的分类器,我们必须将图像格式化处理为一个向量。我们把一个32*32的二进制图像矩阵转换为1*1024的向量,这样前两节使用的分类器就可以处理数字图像信息了。
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
测试数据:
testVector=img2vector('digits/trainingDigits/0_14.txt')
testVector[0,0:30]
结果:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 1., 1., 1., 1., 0., 0.,
0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])
import os
def handwritingClassTeat():
hwLabels=[]
# 打开文件夹
trainingFileList=os.listdir('digits/trainingDigits')
# 得到文件夹里文件的数量
m=len(trainingFileList)
# 定义一个1*1024的数组 默认全部为0
trainingMat=zeros((m,1024))
# 循环遍历文件夹里的每个文件
for i in range(m):
fileNameStr=trainingFileList[i]
# 得到不带后缀的文件名
fileStr=fileNameStr.split('.')[0]
# 得到文件里的数字真实数据
classNumStr=int(fileStr.split('_')[0])
hwLabels.append(classNumStr)
# 调用函数将文件里的32*32的矩阵转化为1*1024的数组
trainingMat[i,:]=img2vector('digits/trainingDigits/%s' %fileNameStr)
# 打开测试数据的文件夹
testFileList=os.listdir('digits/testDigits')
errorCount=0.0
# 记录测试数据文件夹里的文件数量
mTest=len(testFileList)
# 循环遍历测试数据文件
for i in range(mTest):
fileNameStr=testFileList[i]
fileStr=fileNameStr.split('.')[0]
# 得到文件实际数字
classNumStr=int(fileStr.split('_')[0])
# 读取文件里的内容将32*32的矩阵转换为1*1024的数组
vectorUnderTest=img2vector('digits/testDigits/%s'%fileNameStr)
# 利用算法进行分类 取前三个临近的点
classifilerResult=classify(vectorUnderTest,trainingMat,hwLabels,3)
# 输出本次的答案
print('分类来自:%d,真实答案是:%d'%(classifilerResult,classNumStr))
# 记录预测错误的数量
if(classifilerResult!=classNumStr):
errorCount+=1
print('\n错误总数是:%d'% errorCount)
# print(errorCount/float(mTest))
print("\n错误率是:%f"%(errorCount/float(mTest)))
测试代码:
handwritingClassTeat()
结果:
实际使用这个算法的话,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了1024个维度的浮点计算,总计要执行900次,此外,我们呢还需要为测试向量准备2Mb的存储空间。