工作原理:存在一个样本数据集合(也称为训练样本集),并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据以后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。
简单来说,k-近邻算法采用测量不同特征值之间的距离方法进行分类。
优点:精度高、对异常值不敏感、无数据输入假定
缺点:计算复杂度高、空间复杂度高
适用数据范围:数值型和标称型
准备:使用Python导入数据
首先,创建名为kNN.py的Python模块,本文的主要工功能代码都写在这个文件中,测试功能的代码另外写在其他的文件中,然后将其放在一个文件夹中。读者也可以将测试功能的代码都写在kNN.py这个文件中。这可以根据自己的习惯来写。
无论大家采取了何种方法,总之我们已经有了kNN.py这个文件。在构造完整的k-近邻算法之前,我们还需要编写一些基本的通用函数,在kNN.py文件中增加以下代码:
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
在上面的代码中,我们导入了两个模块:第一个是科学计算包NumPy;第二个是运算符模块,k-近邻算法执行排序操作时将使用这个模块提供的函数。
在完成以上的步骤后,我们来测试一下上述代码:
import kNN # 导入kNN模块
group, labels = kNN.createDataSet()
print("group:\n", group)
print("labels:", labels)
测试的结果为:
group:
[[1. 1.1]
[1. 1. ]
[0. 0. ]
[0. 0.1]]
labels: ['A', 'A', 'B', 'B']
在这里我们获得了4组数据,每组数据有两个我们已知的属性或者特征值。上面的group矩阵每行包含一个不同的数据。一般的,为了简单地实现数据可视化,对于每个数据点我们通常只使用两个特征。向量labels包含了每个数据点的标签信息,labels包含的元素个数等于group矩阵行数。
现在我们已经知道Python如何解析数据,如何加载数据,以及kNN算法的工作原理,接下来我们将使用这些方法完成分类任务。
实施kNN分类算法:
我们需要实现这样一个函数:使用k-近邻算法将每组数据划分到某个类中,其中的伪代码如下:
对未知类别属性的数据集的每个点一次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离
(2) 按照距离递增次序排序
(3) 选取与当前点距离最小的k个点
(4) 确定前k个点所在类别的出现频率
(5)返回前k个点出现频率最高的类别作为当前点的预测分类
注意:计算两个向量点之间的距离公式我们用的是欧氏距离公式(例如二维空间的公式如下):
同样以此类推,如果是n维空间的公式:
代码如下:
def classify0(inX, dataSet, labels, k):
"""
:param inX: 用于分类的输入向量
:param dataSet:输入的训练样本集
:param labels:标签向量
:param k:选择最近邻居的数目
注意:labels数目和dataSet行数相同;程序使用欧式距离公式
:return:最后分类的结果
"""
dataSetSize = dataSet.shape[0] # 得到样本集的大小
# tile: 在行方向上重复inX dataSetSize次,在列方向上重复inX 1次.
diffMat = tile(inX, (dataSetSize, 1)) - dataSet
sqDiffMat = diffMat**2
sqDistances = sqDiffMat.sum(axis=1) # 对于二维数组, axis=1表示按行相加 , axis=0表示按列相加
distances = sqDistances**0.5
sortedDistIndicies = distances.argsort() # 将距离从小到大排列,并依次将索引对应排列
classCount = {}
for i in range(k):
# 找到该样本的类型
voteIlabel = labels[sortedDistIndicies[i]]
# 在字典中将该类型 + 1
# 字典中的get方法,如:list.get(k,d) 其中 get相当于一条if...else...语句,参数k在字典中,
# 字典将返回list[k];如果参数k不在字典中则返回参数d,如果K在字典中则返回k对应的value值。
# l = {1:2,3:4}
# print l.get(3,0)返回的值是4;
# print l.get(5,0)返回值是0
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1
# 字典 items() 方法以列表返回可遍历的(键, 值) 元组数组。
# sorted 中的第2个参数 key=operator.itemgetter(1) 这个参数的意思是先比较第几个元素,这个参数是1,故比较的是第二个元素
# 例如:a=[('b',2),('a',1),('c',0)] b=sorted(a,key=operator.itemgetter(1))
# >>>b=[('c',0),('a',1),('b',2)] 可以看到排序是按照后边的0,1,2进行排序的,而不是a,b,c
# b=sorted(a,key=operator.itemgetter(0)) >>>b=[('a',1),('b',2),('c',0)] 这次比较的是前边的a,b,c而不是0,1,2
# b=sorted(a,key=opertator.itemgetter(1,0)) >>>b=[('c',0),('a',1),('b',2)] 这个是先比较第2个元素,然后对第一个元素进行排序,形成多级排序。
sortedClassConut = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 将标签频率进行逆序排列(从大到小)
return sortedClassConut[0][0] # 返回发生频率最高的元素标签
- classfy0()函数有4个输入参数:用于分类的输入向量inX,输入的训练样本集为dataSet,标签向量为labels,最后的参数k表示用于选择最近邻居的数目,其中标签向量的元素数目和矩阵dataSet的行数相同。
- 计算完所有点的距离以后,可以对数据按照从小到大的排序。然后,确定前k个距离最小的元素所在的主要分类,输入k总是正整数。
- 最后将classCount字典分解为元组列表,然后使用程序第二行导入运算模块的itemgetter方法,按照第二个元素的次序对元组进行排序。此处处理的排序为逆序,即按照从最大到最小次序排序,最后返回发生频率最高的元素标签。
为了预测数据所在的分类,输入语句:
import kNN
group, labels = kNN.createDataSet()
test = kNN.classify0([0, 0], group, labels, 3)
print(test)
在此处的输出结果应该是B,大家也可以输入其他的值来测试程序的运行结果。
到现在为止,我们已经构造了第一个分类器,使用这个分类器可以完成很多的分类任务。从这个实例出发,构造使用分类算法将会更加容易。
在明白了k-近邻算法的基本原理之后,我们是不是应该做两个示例来加深自己对这个算法的理解呢?答案是相当有必要的。下面我们做两个简单的示例来加深大家对此算法的理解。
示例一:使用k-近邻算法改进约会网站的配对效果
示例背景:
我的朋友海伦一直使用在线约会网站寻找合适自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
(1)不喜欢的人;
(2)魅力一般的人;
(3)极具魅力的人;
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类,她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外,海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更助于匹配对象的归类。
准备数据:从文本文件中解析数据(数据在此获得,注意:这里获得的数据是整个机器学习实战可能用到的数据文件,请大家一次性下好,在以后的文档中就不再重复添加数据链接了,本次写的是第二章的内容)
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包括以下3种特征:
1.每年获得的飞行常客里程数;
2.玩视频游戏所耗时间百分比;
3.每周消费的冰淇淋公升数;
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。在kNN.py中创建名为file2matrix的函数,以此来处理输入格式问题。该函数的输入为文本文件名字符串,输出为训练样本矩阵和类标签向量。
代码如下:
def file2matrix(filename):
"""
导入训练数据
:param filename:文件路径
:return: 数据矩阵和标签矩阵
"""
fr = open(filename)
arrayOLines = fr.readlines()
numberOfLines = len(arrayOLines) # 得到文件的行数
returnMat = zeros((numberOfLines, 3)) # 创建对应的空矩阵,例如:zeros(2,3)就是生成一个 2*3的矩阵,各个位置上全是0
classLabelVector = [] # prepare labels to return
index = 0
for line in arrayOLines: # 循环处理文件中的每行数据
line = line.strip() # 截去所有回车字符
listFromLine = line.split('\t') # 使用tab字符将整行数据分割成一个元素列表
returnMat[index, :] = listFromLine[0:3] # 选取前三个元素存储在特征矩阵中
classLabelVector.append(int(listFromLine[-1])) # 将列表中的最后一列向量存储到classLabelVector中
index += 1
return returnMat, classLabelVector
从上面的代码可以看到,Python处理文本非常容易。
- 首先我们需要知道文本文件包含多少行。打开文件,得到文件的行数。
- 然后创建以零填充的矩阵NumPy。为了简化处理,我们将该矩阵的另一维度设置为固定数值3,你可以按照自己的实际需求增加相应的代码以适应变化的输入值。循环处理文件中的每行数据。
- 使用函数line.strip()截取掉所有的回车字符,然后使用tab字符\t将上一步得到的整行数据分割成一个元素列表。
- 接着,我们提取前3个元素,将它们存储到特征矩阵中。Python语言可以使用索引值-1表示列表中的最后一个元素,利用这种负索引,我们可以很方便的将列表的最后一列存储到向量classLabelVector中。(需要注意的是:我们必须明确地通知解释器,告诉它列表中存储的元素值为型,否则Python语言会将其当作字符串来处理)
接下来我们来测试这个模块:
import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
print('datingDataMat:\n', datingDataMat)
print()
print('datingLabels[0:20]:', datingLabels[0:20])
最后的结果为:
datingDataMat:
[[4.0920000e+04 8.3269760e+00 9.5395200e-01]
[1.4488000e+04 7.1534690e+00 1.6739040e+00]
[2.6052000e+04 1.4418710e+00 8.0512400e-01]
...
[2.6575000e+04 1.0650102e+01 8.6662700e-01]
[4.8111000e+04 9.1345280e+00 7.2804500e-01]
[4.3757000e+04 7.8826010e+00 1.3324460e+00]]datingLabels[0:20]: [3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]
分析数据:使用Matplotlib创建散点图
首先我们使用Matplotlib制作原始数据的散点图:
from numpy import *
import matplotlib.pyplot as plt
import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
fig = plt.figure()
# 将画布分成1行1列,图像画在从左到右从上到下的第1块
# 例如:ax = fig.add_subplot(349),将画布分割成3行4列,图像画在从左到右从上到下的第9块
ax = fig.add_subplot(111)
# 以第二列和第三列分别为x轴和y轴画散点图,并给予不同的颜色
# scatter(x,y,s=1,c="g",marker="s",linewidths=0)
# s:散列点的大小,c:散列点的颜色,marker:形状,linewidths:边框宽度
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
plt.show() # 画图
输出效果如图所示。散点图使用datingDataMat矩阵的第二、第三列数据,分别表示特征值“玩视频游戏所耗时间百分比”和“每周所消费的冰淇淋公升数”。
由于没有使用样本分类的特征值,我们很难从上图中看出任何有用的数据模式信息。一般来说,我们会采用色彩或者其他记号来标记不同的样本分类,以便更好的观察和分析数据。Matplotlib库提供的scatter函数支持个性化标记散点图上的点。重新输入上面的代码,调用scatter函数时使用下列参数:
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLabels), 15.0*array(datingLabels))
上述代码利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点。
同样的,根据换汤不换药原则,我们也可以使用datingDataMat矩阵的第一和第二属性来展示数据,并且效果更加清晰明了。
准备数据:归一化数值
表1给出了提取的四组数据,如果想要计算样本3和样本4之间的距离,可以使用下面的方法:
玩视频游戏所耗时间百分比 | 每年获得的飞行常客里程数 | 每周消耗的冰淇淋公升数 | 样本分类 |
---|---|---|---|
0.8 | 400 | 0.5 | 1 |
12 | 134000 | 0.9 | 3 |
0 | 20000 | 1.1 | 2 |
67 | 32000 | 0.1 | 2 |
我们很容易发现,上面方程数字差值最大的属性对计算结果的影响最大,也就是说,每年获得的飞行常客里程数对于计算结果的影响远远要大于玩视频游戏所耗时间百分比和每周消耗的冰淇淋公升数对的影响。
因此,为了解决这个问题,我们需要将数值归一化。如将取值范围处理为0到1或者-1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
newValue = (oldValue - min)/(max - min)
其中min和max分别代表数据集中的最小和最大特征值。虽然改变数值取值范围增加了分类器的复杂度,但是为了得到准确的结果,我们别无选择。
下面编写一个函数,该函数的功能为:可以自动将数字特征值转化为0到1的区间。
def autoNorm(dataSet):
"""
归一化特征值,消除属性之间量级不同导致的影响.
归一化公式: Y = (X-Xmin)/ranges--->(Xmax-Xmin)
其中的 min 和 max 分别是数据集中的最小特征值和最大特征值。该函数可以自动将数字特征值转化为0到1的区间。
:param dataSet:输入的数据
:return:归一化后的数据normDataSet,ranges和minVals即为范围与最小值,在这里并没有用到
"""
maxVals = dataSet.max(0) # 找出每列的最大值
minVals = dataSet.min(0) # 找出每列的最小值
ranges = maxVals - minVals # 计算出范围
normDataSet = zeros(shape(dataSet))
# print(normDataSet)
m = dataSet.shape[0] # 得到样本集的大小
normDataSet = dataSet - tile(minVals, (m, 1))
normDataSet = normDataSet/tile(ranges, (m, 1))
return normDataSet, ranges, minVals
- 在函数autoNorm()中,我们将每列的最小值放在minVals中,最大值放在maxVals中,其中dataSet.min(0)中的参数0使得函数可以从列中选取最小值,而不是选取当前行的最小值。
- 函数计算可能的取值范围,并创建新的返回矩阵。(需要注意的是:特征值矩阵有1000*3个值,而minVals和maxVals的值都为1*3。为了解决这个问题,我们使用numpy库中提供的tile()函数将变量内容复制成输入矩阵同样大小的矩阵,注意这是具体特征值相除,而对于某些数值处理软件包,/可能意味着矩阵除法,但是在numpy库中,矩阵除法需要使用函数linalg.solve(matA, matB))
下面开始测试autoNorm()函数:
import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print('normMat:\n', normMat)
print('ranges:', ranges)
print('minVals:', minVals)
测试结果如下:
normMat:
[[0.44832535 0.39805139 0.56233353]
[0.15873259 0.34195467 0.98724416]
[0.28542943 0.06892523 0.47449629]
...
[0.29115949 0.50910294 0.51079493]
[0.52711097 0.43665451 0.4290048 ]
[0.47940793 0.3768091 0.78571804]]
ranges: [9.1273000e+04 2.0919349e+01 1.6943610e+00]
minVals: [0. 0. 0.001156]
测试算法:作为完整程序验证分类器
机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。
为了测试分类器效果,在kNN.py文件中创建函数datingClassTest,该函数是自包含的,你可以在任何时候在Python运行中使用该函数测试分类器的效果。在kNN.py文件中输入以下代码:
def datingClassTest():
"""
:return: 错误数
"""
# 设置测试数据的一个比例(训练数据集比例 = 1 - hoRatio)
hoRatio = 0.1 # 测试范围,一部分测试一部分作为样本
# 从文件中加载数据
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
# 归一化数据
normMat, ranges, minVals = autoNorm(datingDataMat)
# m表示数据的行数,即矩阵的第一维
m = normMat.shape[0]
# 设置测试的样本数量, numTestVecs:m表示训练样本的数量
numTestVecs = int(m*hoRatio)
errorCount = 0.0
for i in range(numTestVecs):
# 对数据进行测试
classifierResult = classify0(normMat[i, :], normMat[numTestVecs:m, :],
datingLabels[numTestVecs:m], 3)
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i]))
if(classifierResult != datingLabels[i]):errorCount += 1.0
print("the total error rate is:%f" % (errorCount/float(numTestVecs)))
print(errorCount)
- 该函数首先使用了file2matrix()和autoNorm()函数从文件中读取是数据并将数据转换为归一化特征值。
- 接着计算测试向量的数量,此步决定了normMat向量中哪些数据用于测试,哪些数据用于分类器的训练样本
- 然后将这两部分数据输入到原始kNN分类器函数classify0。
- 最后,函数计算错误率输出结果
import kNN
kNN.datingClassTest()
输出结果为:
the classifier came back with: 3, the real answer is: 3
the total error rate is:0.000000
0.0
the classifier came back with: 2, the real answer is: 2
the total error rate is:0.000000
0.0
the classifier came back with: 1, the real answer is: 1
the total error rate is:0.000000
.....
the classifier came back with: 1, the real answer is: 1
the total error rate is:0.080000
8.0
分类处理器约会数据集的错误率是8.0%,我们可以改变函数datingClassTest内变量hoRatio和变量k的值,检测错误率是否随着变量值的变化而增加。依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大的不同。
依据这个预测分类,海伦完全可以输入未知对象的属性信息,由分类软件来帮助她判定某一对象的可交往程度:讨厌、一般喜欢、非常喜欢。
使用算法:构建完整可用系统
下面海伦可以在某个约会网站上找到某个人,并且输入他的信息,程序将会给她对对方喜欢程度的预测值。
def classifyPerson():
resultList = ['not 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?"))
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = array([ffMiles, percentTats, iceCream])
classifierRsult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
print("You will probably like this person:", resultList[classifierRsult - 1])
测试:
import kNN
kNN.classifyPerson()
输出的结果为:
percentage of time spent playing video games?10
frequent flier miles earned per year?10000
liters of ice cream consumed per year?0.5
You will probably like this person: in small doses
上述的数据是通过自己手动输入的,大家可以通过输入不同的数据组来进行测试。
到目前为止,我们已经看到如何在数据上构建分类器以及如何通过分类器处理一些简单的数据,下面我们看另外一个栗子~
示例二:手写识别系统
这一次我们将一步步地构造使用k-近邻分类器的手写识别系统。为了简单起见,这里构造的系统只能识别数字0-9.如下图所示:
需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素×32像素的黑白图像。尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理解,我们还是将图像转换为文本格式。
准备数据:将图像转换为测试向量
通过在上一次示例中下载到的数据目录,实际图像存储在两个子目录内:trainingDigist中包含了大约2000个例子,每个例子的内容如上图所示,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。两组数据没有重叠,有兴趣的盆友可以自己检查一下文件是否符合需求。
为了使用前面两个例子的分类器,我们必须将图像格式处理为一个向量,我们将把一个32×32的二进制图形像矩阵转换为1×1024的向量,这样前两节的分类器就可以处理数字图像信息了。
我们首先编写一段函数img2vector,将图像转换为向量:该函数创建1×1024的numpy数组,然后打开给定的文件,循环读出文件的前32行,并将每行的头32个字符值存储在numpy数组中,最后返回给数组。
def img2vector(filename):
"""
将图像数据转化为向量
:param filename: 图片文件 因为我们输入数据的图片格式是32*32的
:return:一维矩阵
该函数将图片转换为向量:该函数创建1*1024的NumPy数组,然后打开给定的文件,
循环读出文件的前32行,并将每行的头32个字符存储在NumPy数组中,最后返回数组.
"""
returnVect = zeros((1, 1024))
fr = open(filename)
for i in range(32):
lineStr = fr.readline() # 循环读出文件的前32行
for j in range(32):
returnVect[0, 32*i+j] = int(lineStr[j]) # 将每行读得的头32个字符值存储在数组中,最后返回数组
return returnVect
测试:
import kNN
testVecor = kNN.img2vector('testDigits/0_13.txt')
print('testVecor[0, 0:31]:\n', testVecor[0, 0:31])
print('testVecor[0, 32:63]:\n', testVecor[0, 32:63])
结果为:
testVecor[0, 0:31]:
[0. 0. 0. 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.]
testVecor[0, 32:63]:
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 0. 0. 0. 0. 0.
0. 0. 0. 0. 0. 0. 0.]
测试算法:使用k-近邻算法识别手写数字
上面我们已经将数据处理成分类器可以识别的格式,接下来我们需要将这些数据输入到分类器,检测分类器的执行结果。在写入代码之前,我们必须确保将from os import listdir 写入文件的起始部分,这段代码的主要功能是从os模块中导入函数listdir,它可以列出给定目录的文件名。
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits')
m = len(trainingFileList)
trainingMat = zeros((m, 1024))
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] # 去除.之后的尾缀.txt
classNumStr = int(fileStr.split('_')[0]) # 去除_之后的尾缀,只留下0-9作为标签
hwLabels.append(classNumStr) # 添加标签
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr) # 将所有的文件夹里的图像都转换为一维矩阵
testFileList = listdir('testDigits')
errorCount = 0
mTest = len(testFileList)
for i in range(mTest):
fileNameStr = testFileList[i]
fileStr = fileNameStr.split('.')[0]
classNumStr = int(fileStr.split('_')[0])
vectorUnderTest = img2vector('testDigits/%s' % fileNameStr)
classifierResult = classify0(vectorUnderTest, trainingMat, hwLabels, 3) # 将待测试数据集和样本数据集进行归类
# 测试一下错误率
print("the classifier came back with: %d, the real answer is: %d" % (classifierResult, classNumStr))
if(classifierResult != classNumStr): errorCount += 1.0
print("\nthe total number of errors is: %d" % errorCount)
print("\nthe total error rate is: %f" % (errorCount/float(mTest)))
- 将trainingDigist目录中的文件内容存储在列表中,然后可以得到目录中有多少文件,并将其存储在变量m中。
- 接着,代码创建一个m行1024列的训练矩阵,该矩阵的每行数据存储一个图像。我们可以从文件名中解析出来分类数字。
- 该目录下的文件名按照规则命名,如文件名9_45.txt的分类是9,它是数字9的第45个实例。
- 然后我们可以将类代码存储在hwLabels向量中,使用前面讨论的img2vector函数载入图像。
- 在下一步中,我们对testDigist目录中的文件执行相似的操作,不同之处是我们并不是将这个目录的文件载入矩阵中,而是使用classify0()函数测试该目录下的每个文件。由于文件中的值已经在0和1之间,所以不需要使用autoNorm()函数。
测试:
import kNN
kNN.handwritingClassTest()
结果为:
the classifier came back with: 0, the real answer is: 0
the total number of errors is: 0
the total error rate is: 0.000000
...
the total error rate is: 0.007400
the classifier came back with: 8, the real answer is: 8the total number of errors is: 7
the total error rate is: 0.007400
the classifier came back with: 6, the real answer is: 8the total number of errors is: 8
...
the classifier came back with: 9, the real answer is: 9
the total number of errors is: 13
the total error rate is: 0.013742
k-近邻算法识别手写数字数据集,错误率约为1.4%。改变变量k的值、修改函数handwritingClassTest随机选取训练样本、改变训练样本的数目,都会对k-近邻算法的错误率产生影响,感兴趣的话可以通过改变这些变量值,观察错误率的变化。
实际使用这个算法时,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了1024个维度浮点运算,总计要执行900次,此外我们还需要为测试向量准备2MB的存储空间。
小结
k-近邻算法是分类数据最简单最有效的算法,k-近邻算法必须保存全部数据集,如果训练数据集很大,必须使用大量的存储空间。此外,由于必须对数据集的每个数据计算距离值,实际使用时可能非常耗时。
k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本具有什么特征。下一次我们将使用概率测量方法处理分类问题,该算法可以解决这个问题。
今天就到这里啦~
bye~