k近邻算法就是采用测量不同特征值之间的距离方法进行分类。
优点:简单好用、精度高、对异常值不敏感、无数据输入假定,可以用作分类也可以回归,离散型数据和数值型数据都可使用
缺点:计算复杂度高、空间复杂度高、计算量很大,一般数值很大的时候不适用,无法给出数据的内在含义
存在一个样本数据集合,也就是训练样本训练集,并且样本训练集中每个都存在标签,就是我们知道样本集中每一个数据和所属分类的对应关系。
输入没有标签的新数据后,将新数据的每个特征与样本集中对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
假设有人统计过电影中打斗镜头和接吻镜头。如下图所示。现在有一个新的电影,如何确定这个电影是爱情片还是动作片呢。
###### 实例讲解knn
在knn的原理中我们说过要计算新的样本与之前的样本之间的距离。在这里就是计算未知电影和其他电影之间的距离。
那么距离如何计算呢?
计算距离的公式就是简单的计算两个点之间的距离:
计算完距离之后就可以将数据可视化。以x轴作为电影的打斗镜头,y轴作为电影中的接吻镜头。
根据经验来看,我们未知的那个电影很可能是个动作片。
但是事实真的如此吗?
我们计算出来各个点距离未知电影的距离分别如下:
(101,20)->动作片(108,5)的距离约为16.55
(101,20)->动作片(115,8)的距离约为18.44
(101,20)->爱情片(5,89)的距离约为118.22
(101,20)->爱情片(1,101)的距离约为128.69。
import numpy as np
def createDataSet():
##放入四个电影的数据
group=np.array([[1,101],[5,89],[108,5],[115,8]])
#标签
labels=['爱情片','爱情片','动作片','动作片']
return group,labels
if __name__ == '__main__':
group,labels=createDataSet()
print(group)
print(labels)
(2)k-近邻算法编程
import numpy as np
import operator
def createDataSet():
#四组二维特征
group = np.array([[1,101],[5,89],[108,5],[115,8]])
#四组特征的标签
labels = ['爱情片','爱情片','动作片','动作片']
return group, labels
def classify0(inX, dataSet, labels, k):
#numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
#在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
#二维特征相减后平方
sqDiffMat = diffMat**2
#sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
#开方,计算出距离
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
#定一个记录类别次数的字典
classCount = {}
for i in range(k):
#取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
#dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
#计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#python3中用items()替换python2中的iteritems()
#key=operator.itemgetter(1)根据字典的值进行排序
#key=operator.itemgetter(0)根据字典的键进行排序
#reverse降序排序字典
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
if __name__ == '__main__':
#创建数据集
group, labels = createDataSet()
#测试集
test = [101,20]
#kNN分类
test_class = classify0(test, group, labels, 3)
#打印分类结果
print(test_class)
通过大量的测试数据得到分类器的错误率。通过错误率来对分类器进行评估。
海伦已知使用在线约会网站寻找适合自己的约会对象。网站给她推荐了一些不同的人选。她从过往的交往对象中发现自己曾经交往三种类型的人。一种是不喜欢的人,一种是魅力一般的人,一种是极具魅力的人。但是海伦还是无法将网站上推荐的那些人进行恰当的分类。因此海伦收集了一些约会网站未曾记录的数据信息。这些数据更有助于匹配对象的归类。
数据存放在datingTestSet.txt中,每个数据占一行,一共1000行。数据中包含三种特征:每年获得的飞行常客里程数 玩视频游戏所耗时间百分比 每周消费的冰淇淋公升数 。
因为数据是放在txt文本里面,我们需要将数据进行简单的处理。
import numpy as np
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
'''
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力
Parameters:
filename - 文件名
Returns:
returnMat - 特征矩阵
classLabelVector - 分类Label向量
'''
def file2matrix(filename):
#打开文件
fr=open(filename)
#读取文件的所有内容
arrayOLines=fr.readlines()
#得到文件行数
numberOfLines=len(arrayOLines)
##返回数据numberOfLines行,3列
returnMat=np.zeros((numberOfLines,3))
#返回的分类标签向量
classLabelVector=[]
#行的索引值
index=0
for line in arrayOLines:
##s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
line=line.strip()
##使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。
listFromLine=line.split('\t')
#将数据前三列提取出来,放到returnMat的numpy矩阵李,也就是特征矩阵
returnMat[index,:]=listFromLine[0:3]
##根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
if listFromLine[-1]== 'didntLike':
classLabelVector.append(1)
elif listFromLine[-1] == 'smallDoses':
classLabelVector.append(2)
elif listFromLine[-1] == 'largeDoses':
classLabelVector.append(3)
index += 1
return returnMat,classLabelVector
'''
数据可视化
Parameters:
datingDataMat - 特征矩阵
datingLabels - 分类Label
Returns:
无
'''
def showdatas(datingDataMat,datingLabels):
#设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
#将fig画布分割成一行一列,不共享x、y轴,大小为(13,8)
#将nrow=2,nclow=2时,代表fig画布被分成四个区域,axs[0][0]表示第一行第一个区域
fig, axs=plt.subplots(nrows=2,ncols=2,sharex=False,sharey=False,figsize=(13,8))
numberOfLabels=len(datingLabels)
LabelsColors=[]
for i in datingLabels:
if i == 1:
LabelsColors.append('black')
if i == 2:
LabelsColors.append('orange')
if i == 3:
LabelsColors.append('red')
##画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占', FontProperties=font)
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰激淋公升数', FontProperties=font)
axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数', FontProperties=font)
axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
# 设置图例
didntLike = mlines.Line2D([], [], color='black', marker='.',
markersize=6, label='didntLike')
smallDoses = mlines.Line2D([], [], color='orange', marker='.',
markersize=6, label='smallDoses')
largeDoses = mlines.Line2D([], [], color='red', marker='.',
markersize=6, label='largeDoses')
# 添加图例
axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
# 显示图片
plt.show()
if __name__ == '__main__':
#打开的文件名
filename='datingTestSet.txt'
#处理数据
datingDataMat,datingLabels=file2matrix(filename)
#print(datingDataMat)
#print(datingLabels)
showdatas(datingDataMat,datingLabels)
从以上可视化的图片可以看到海伦更喜欢每周消耗冰淇凌多的男人,而不是每年获得飞行常客里程数多的男人。具体的要求是:要经常玩游戏,还得时不时的去旅行。
但是你也可以看到我们的样本中的数据每年获得的飞行常用里程数和每周消费的冰淇凌公升数之间差距太大。数字差值大的属性对计算结果的影响很大。在这里就是说每年获得的飞行常客里程数对于计算结果的影响程度要大于玩视频和吃冰淇凌的影响。但是实际上这三种特征他们的影响程度都是相同的。为了减小这种因为数值上差距过大引起的影响,我们这里采取数据归一化。数据归一化的目的就是将数据的取值范围处理到0-1之间或者-1到1之间。公式如下:
这里oldValue是指之前的数据值,max是指这个特征中最大的那个数据值,min指的是这个特征中最小的那个数据值。
'''
函数:数据归一化
Parameters:
dataSet - 特征矩阵
Returns:
normDataSet - 归一化后的特征矩阵
ranges - 数据范围
minVals - 数据最小值
'''
def autoNorm(dataSet):
#拿到数据的最大小值
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
#最大值和最小值之间的范围
ranges=maxVals-minVals
#返回一个dataset形状的一个全0矩阵
normDataSet=np.zeros(np.shape(dataSet))
#返回dateSet的行数
m=dataSet.shape[0]
#原始值减去最小值
normDataSet=dataSet-np.tile(minVals,(m,1))
#得到归一化的数据
normDataSet=normDataSet/np.tile(ranges,(m,1))
#返回归一化结果,数据范围和最小值
return normDataSet,ranges,minVals
if __name__ == '__main__':
#打开的文件名
filename='datingTestSet.txt'
#处理数据
datingDataMat,datingLabels=file2matrix(filename)
#print(datingDataMat)
#print(datingLabels)
#showdatas(datingDataMat,datingLabels)
normDataSet,ranges,minVals=autoNorm(datingDataMat)
print(normDataSet)
print(ranges)
print(minVals)
以上完成了数据的预处理。
机器学习算法中一个重要的步骤就是评估算法的正确性。正常来说用数据的90%作为训练样本分类,用数据的10%作为测试分类器。
import numpy as np
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import operator
'''
函数说明:打开并解析文件,对数据进行分类:1代表不喜欢,2代表魅力一般,3代表极具魅力
Parameters:
filename - 文件名
Returns:
returnMat - 特征矩阵
classLabelVector - 分类Label向量
'''
def file2matrix(filename):
#打开文件
fr=open(filename)
#读取文件的所有内容
arrayOLines=fr.readlines()
#得到文件行数
numberOfLines=len(arrayOLines)
##返回数据numberOfLines行,3列
returnMat=np.zeros((numberOfLines,3))
#返回的分类标签向量
classLabelVector=[]
#行的索引值
index=0
for line in arrayOLines:
##s.strip(rm),当rm空时,默认删除空白符(包括'\n','\r','\t',' ')
line=line.strip()
##使用s.split(str="",num=string,cout(str))将字符串根据'\t'分隔符进行切片。
listFromLine=line.split('\t')
#将数据前三列提取出来,放到returnMat的numpy矩阵李,也就是特征矩阵
returnMat[index,:]=listFromLine[0:3]
##根据文本中标记的喜欢的程度进行分类,1代表不喜欢,2代表魅力一般,3代表极具魅力
if listFromLine[-1]== 'didntLike':
classLabelVector.append(1)
elif listFromLine[-1] == 'smallDoses':
classLabelVector.append(2)
elif listFromLine[-1] == 'largeDoses':
classLabelVector.append(3)
index += 1
return returnMat,classLabelVector
'''
数据可视化
Parameters:
datingDataMat - 特征矩阵
datingLabels - 分类Label
Returns:
无
'''
def showdatas(datingDataMat,datingLabels):
#设置汉字格式
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
#将fig画布分割成一行一列,不共享x、y轴,大小为(13,8)
#将nrow=2,nclow=2时,代表fig画布被分成四个区域,axs[0][0]表示第一行第一个区域
fig, axs=plt.subplots(nrows=2,ncols=2,sharex=False,sharey=False,figsize=(13,8))
numberOfLabels=len(datingLabels)
LabelsColors=[]
for i in datingLabels:
if i == 1:
LabelsColors.append('black')
if i == 2:
LabelsColors.append('orange')
if i == 3:
LabelsColors.append('red')
##画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第二列(玩游戏)数据画散点数据,散点大小为15,透明度为0.5
axs[0][0].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 1], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs0_title_text = axs[0][0].set_title(u'每年获得的飞行常客里程数与玩视频游戏所消耗时间占比', FontProperties=font)
axs0_xlabel_text = axs[0][0].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs0_ylabel_text = axs[0][0].set_ylabel(u'玩视频游戏所消耗时间占', FontProperties=font)
plt.setp(axs0_title_text, size=9, weight='bold', color='red')
plt.setp(axs0_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs0_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第一(飞行常客例程)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[0][1].scatter(x=datingDataMat[:, 0], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs1_title_text = axs[0][1].set_title(u'每年获得的飞行常客里程数与每周消费的冰激淋公升数', FontProperties=font)
axs1_xlabel_text = axs[0][1].set_xlabel(u'每年获得的飞行常客里程数', FontProperties=font)
axs1_ylabel_text = axs[0][1].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs1_title_text, size=9, weight='bold', color='red')
plt.setp(axs1_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs1_ylabel_text, size=7, weight='bold', color='black')
# 画出散点图,以datingDataMat矩阵的第二(玩游戏)、第三列(冰激凌)数据画散点数据,散点大小为15,透明度为0.5
axs[1][0].scatter(x=datingDataMat[:, 1], y=datingDataMat[:, 2], color=LabelsColors, s=15, alpha=.5)
# 设置标题,x轴label,y轴label
axs2_title_text = axs[1][0].set_title(u'玩视频游戏所消耗时间占比与每周消费的冰激淋公升数', FontProperties=font)
axs2_xlabel_text = axs[1][0].set_xlabel(u'玩视频游戏所消耗时间占比', FontProperties=font)
axs2_ylabel_text = axs[1][0].set_ylabel(u'每周消费的冰激淋公升数', FontProperties=font)
plt.setp(axs2_title_text, size=9, weight='bold', color='red')
plt.setp(axs2_xlabel_text, size=7, weight='bold', color='black')
plt.setp(axs2_ylabel_text, size=7, weight='bold', color='black')
# 设置图例
didntLike = mlines.Line2D([], [], color='black', marker='.',
markersize=6, label='didntLike')
smallDoses = mlines.Line2D([], [], color='orange', marker='.',
markersize=6, label='smallDoses')
largeDoses = mlines.Line2D([], [], color='red', marker='.',
markersize=6, label='largeDoses')
# 添加图例
axs[0][0].legend(handles=[didntLike, smallDoses, largeDoses])
axs[0][1].legend(handles=[didntLike, smallDoses, largeDoses])
axs[1][0].legend(handles=[didntLike, smallDoses, largeDoses])
# 显示图片
plt.show()
def classify0(inX, dataSet, labels, k):
#numpy函数shape[0]返回dataSet的行数
dataSetSize = dataSet.shape[0]
#在列向量方向上重复inX共1次(横向),行向量方向上重复inX共dataSetSize次(纵向)
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet
#二维特征相减后平方
sqDiffMat = diffMat**2
#sum()所有元素相加,sum(0)列相加,sum(1)行相加
sqDistances = sqDiffMat.sum(axis=1)
#开方,计算出距离
distances = sqDistances**0.5
#返回distances中元素从小到大排序后的索引值
sortedDistIndices = distances.argsort()
#定一个记录类别次数的字典
classCount = {}
for i in range(k):
#取出前k个元素的类别
voteIlabel = labels[sortedDistIndices[i]]
#dict.get(key,default=None),字典的get()方法,返回指定键的值,如果值不在字典中返回默认值。
#计算类别次数
classCount[voteIlabel] = classCount.get(voteIlabel,0) + 1
#python3中用items()替换python2中的iteritems()
#key=operator.itemgetter(1)根据字典的值进行排序
#key=operator.itemgetter(0)根据字典的键进行排序
#reverse降序排序字典
sortedClassCount = sorted(classCount.items(),key=operator.itemgetter(1),reverse=True)
#返回次数最多的类别,即所要分类的类别
return sortedClassCount[0][0]
'''
函数:数据归一化
Parameters:
dataSet - 特征矩阵
Returns:
normDataSet - 归一化后的特征矩阵
ranges - 数据范围
minVals - 数据最小值
'''
def autoNorm(dataSet):
#拿到数据的最大小值
minVals=dataSet.min(0)
maxVals=dataSet.max(0)
#最大值和最小值之间的范围
ranges=maxVals-minVals
#返回一个dataset形状的一个全0矩阵
normDataSet=np.zeros(np.shape(dataSet))
#返回dateSet的行数
m=dataSet.shape[0]
#原始值减去最小值
normDataSet=dataSet-np.tile(minVals,(m,1))
#得到归一化的数据
normDataSet=normDataSet/np.tile(ranges,(m,1))
#返回归一化结果,数据范围和最小值
return normDataSet,ranges,minVals
'''
作用:分类器测试
Parameters:
无
Returns:
normDataSet - 归一化后的特征矩阵
ranges - 数据范围
minVals - 数据最小值
'''
def datingClassTest():
#打开的文件名
filename="datingTestSet.txt"
datingDataMat,datingLabels=file2matrix(filename)
#拿到10%的数据
hoRatio=0.10
#数据归一化,拿到故意话的矩阵,数据范围,数据最小值
normMat,ranges,minVals=autoNorm(datingDataMat)
#返回normMat的行数
m=normMat.shape[0]
#测试数据的个数
numTestVecs=int(m*hoRatio)
#分类错误计数
errorCount=0.0
for i in range(numTestVecs):
#前numTestVecs个是测试集,其余都是训练集
classifierResult=classify0(normMat[i,:],normMat[numTestVecs:m,:],datingLabels[numTestVecs:m],4)
print("分类结果:%d\t真实类别:%d"%(classifierResult,datingLabels[i]))
if classifierResult != datingLabels[i]:
errorCount += 1.0
print("错误率:%f%%"%(errorCount/float(numTestVecs)*100))
if __name__ == '__main__':
datingClassTest()
#打开的文件名
# filename='datingTestSet.txt'
#处理数据
#datingDataMat,datingLabels=file2matrix(filename)
#print(datingDataMat)
#print(datingLabels)
# #showdatas(datingDataMat,datingLabels)
# normDataSet,ranges,minVals=autoNorm(datingDataMat)
# # print(normDataSet)
# print(ranges)
# print(minVals)
正常来说4%的结果已经是不错的了,但是还可以改变函数参数中的k的值以及训练集占的比重来进行测试。
海伦可以根据自己输入的信息来预测她对这个男人的喜爱程度。
def classifyPerson():
#输出结果
resultList=['不喜欢','有点喜欢','很喜欢']
#男友的三种数据输入进去
precentTats=float(input("玩游戏视频所消耗的时间的百分比:"))
ffMiles=float(input("每年获得的飞行常客里程数:"))
iceCream=float(input("每周消费的冰淇凌的公升数:"))
#打开的文件名
filename="datingTestSet.txt"
#处理数据
datingDataMat,datingLabels=file2matrix(filename)
##数据集归一化
normMat,ranges,minVals=autoNorm(datingDataMat)
#测试集
inArr=np.array([ffMiles,precentTats,iceCream])
#测试集归一化
norminArr=(inArr-minVals)/ranges
#得到分类结果
classifierResult=classify0(norminArr,normMat,datingLabels,3)
#打印结果
print("你可能%s这个人"%(resultList[classifierResult-1]))
if __name__ == '__main__':
classifyPerson()
sklearn中使用较多的机器学习的方式:
>>> X = [[0], [1], [2], [3]]
>>> y = [0, 0, 1, 1]
>>> from sklearn.neighbors import KNeighborsClassifier
>>> neigh = KNeighborsClassifier(n_neighbors=3)
>>> neigh.fit(X, y)
KNeighborsClassifier(...)
>>> print(neigh.predict([[1.1]]))
[0]
>>> print(neigh.predict_proba([[0.9]]))
[[0.66666667 0.33333333]]
以上是官网给出的例子。
KNneighborsClassifier参数说明:
n_neighbors:默认为5,就是k-NN的k的值,选取最近的k个点。
weights:默认是uniform,参数可以是uniform、distance,也可以是用户自己定义的函数。uniform是均等的权重,就说所有的邻近点的权重都是相等的。distance是不均等的权重,距离近的点比距离远的点的影响大。用户自定义的函数,接收距离的数组,返回一组维数相同的权重。
algorithm:快速k近邻搜索算法,默认参数为auto,可以理解为算法自己决定合适的搜索算法。除此之外,用户也可以自己指定搜索算法ball_tree、kd_tree、brute方法进行搜索,brute是蛮力搜索,也就是线性扫描,当训练集很大时,计算非常耗时。kd_tree,构造kd树存储数据以便对其进行快速检索的树形数据结构,kd树也就是数据结构中的二叉树。以中值切分构造的树,每个结点是一个超矩形,在维数小于20时效率高。ball tree是为了克服kd树高纬失效而发明的,其构造过程是以质心C和半径r分割样本空间,每个节点是一个超球体。
leaf_size:默认是30,这个是构造的kd树和ball树的大小。这个值的设置会影响树构建的速度和搜索速度,同样也影响着存储树所需的内存大小。需要根据问题的性质选择最优的大小。
metric:用于距离度量,默认度量是minkowski,也就是p=2的欧氏距离(欧几里德度量)。
p:距离度量公式。在上小结,我们使用欧氏距离公式进行距离度量。除此之外,还有其他的度量方法,例如曼哈顿距离。这个参数默认为2,也就是默认使用欧式距离公式进行距离度量。也可以设置为1,使用曼哈顿距离公式进行距离度量。
metric_params:距离公式的其他关键参数,这个可以不管,使用默认的None即可。
n_jobs:并行处理设置。默认为1,临近点搜索并行工作数。如果为-1,那么CPU的所有cores都用于并行工作。
import numpy as np
import operator
from os import listdir
from sklearn.neighbors import KNeighborsClassifier as KNN
'''
作用:将32x32的图像转化成1x1024向量
Parameters:
filename - 文件名
Returns:
returnVect - 返回的二进制图像的1x1024向量
'''
def img2vector(filename):
#创建1x1024零向量
returnVect=np.zeros((1,1024))
#打开文件
fr=open(filename)
#按行读取文件
for i in range(32):
#读一行数据
lineStr=fr.readline()
#每一行的前32个元素加到returnVect中
for j in range(32):
returnVect[0,32*i+j]=int(lineStr[j])
##返回转换后的1x1024向量
return returnVect
'''
说明:手写数字分类测试
Parameters:
无
Returns:
无
'''
def handwritingClassTest():
#测试集的labels
hwLabels=[]
#返回trainingDigits目录下的文件名
trainingFileList=listdir('trainingDigits')
#返回文件夹下文件的个数
m=len(trainingFileList)
#初始化训练的Mat矩阵和测试集
trainingMat=np.zeros((m,1024))
#从文件名中解析出训练集的类别
for i in range(m):
#获得文件的名字
fileNameStr=trainingFileList[i]
#拿到分类的数字
classNumber=int(fileNameStr.split('_')[0])
#将类别加到hwLabels里面
hwLabels.append(classNumber)
#将每一个文件数据存到trainingMat矩阵里面
trainingMat[i,:]=img2vector('trainingDigits/%s'%(fileNameStr))
##构建knn分类器
neigh=KNN(n_neighbors=3,algorithm='auto')
##拟合模型,trainingMat是训练矩阵,hwLabels是标签
neigh.fit(trainingMat,hwLabels)
#拿到testDigits目录下的文件列表
testFileList=listdir('testDigits')
#错误检测计数
errorCount=0.0
#统计测试数据数量
mTest=len(testFileList)
#进行分类测试
for i in range(mTest):
#获得文件的名字
fileNameStr=testFileList[i]
#获得分类的数字
classNumber=int(fileNameStr.split('_')[0])
#获得测试集的1x1024向量,用来训练
vectorUnderTest=img2vector('testDigits/%s'%(fileNameStr))
#获得预测结果
# classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3)
##自己写的knn
##官方的
classifierResult=neigh.predict(vectorUnderTest)
print("分类返回结果为%d\t真实结果为%d" % (classifierResult, classNumber))
if (classifierResult != classNumber):
errorCount += 1.0
print("总共错了%d个数据\n错误率为%f%%" % (errorCount, errorCount / mTest * 100))
if __name__ == '__main__':
handwritingClassTest()