说明:
作业的所有代码都要基于Python3
学习大纲:https://blog.csdn.net/qq_34243930/article/details/84669684
(所有计划均在学习大纲里)
02 k-近邻算法(第一周)
2.1 k-近邻算法概述
2.2 示例:使用 k-近邻算法改进网站的配对效果
2.3 示例:手写识别系统
第一节学习内容
学习时间: 12/4—12/7
任务1题目: 书籍阅读
任务详解: 阅读《机器学习实战》书籍第二章2.1、2.2、2.3章节
参考资料: 李航《统计学习方法》第3章
作业1: 简要概括 k-近邻算法的原理,优缺点。
作业2: 将本章中“使用 k近邻算法改进网站的配对效果”完整代码键入,并添加详细注释。
作业3: 将本章中“手写识别系统”完整代码键入,并添加详细注释。
第一节学习内容笔记
《第2章-k-近邻算法》
本章第一节基于电影中出现的亲吻、打斗出现的次数,使用k-近邻算法构造程序,自动划分电影的题材类型。我们首先使用电影分类讲解k-近邻算法的基本概念,然后学习如何在其他系统上使用k-近邻算法。
本章介绍第一个机器学习算法:k-近邻算法,它非常有效而且易于掌握。首先,我们将探讨k-近邻算法的基本理论,以及如何使用距离测量的方法分类物品;其次我们将使用Python从文本文件中导入并解析数据;再次,本书讨论了当存在许多数据来源时,如何避免计算距离时可能碰到的一些常见错误;最后,利用实际的例子讲解如何使用k-近邻算法改进约会网站和手写数字识别系统。
2.1 k-近邻算法概述
简单地说,k-近邻算法采用测量不同特征值之间的距离方法进行分类。
优点:
1、精度高
2、对异常数据不敏感(你的类别是由邻居中的大多数决定的,一个异常邻居并不能影响太大)
3、无数据输入假定
4、算法简单,容易理解,无复杂机器学习算法。
缺点:
1、计算复杂度高(需要计算新的数据点与样本集中每个数据的“距离”,以判断是否是前k个邻居)
2、空间复杂度高(巨大的矩阵)。
k-近邻算法(kNN),它的工作原理是:存在一个样本数据集合,也称作训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的新数据后,将新数据的每个特征与样本集中数据对应的特征进行比较,然后算法提取样本集中特征最相似数据(最近邻)的分类标签。一般来说,我们只选择样本数据集中前k个最相似的数据,这就是k-近邻算法中k的出处,通常k是不大于20的整数。最后,选择k个最相似数据中出现次数最多的分类,作为新数据的分类。
本章主要讲解如何在实际环境中应用k-近邻算法,同时涉及如何使用Python工具和相关的机器学习术语。按照1.5节开发机器学习应用的通用步骤,我们使用Python语言开发k-近邻算法的简单应用,以检验算法使用的正确性。
2.1.1 准备:使用 Python 导入数据
首先,创建名为kNN.py的Python模块。在构造完整的k-近邻算法之前,我
们还需要编写一些基本的通用函数,在kNN.py文件中增加下面的代码:
import numpy as np
import operator
def createDataSet():
"""
创建数据集和标签
"""
group = np.array([1.0, 1.1], [1.0, 1.0], [0, 0], [0,0.1])
labels = ['A', 'A', 'B', 'B']
return group, labels
在上面的代码中,我们导入了两个模块:第一个是科学计算包NumPy;第二个是运算符模块,k-近邻算法执行排序操作时将使用这个模块提供的函数。
这里有4组数据,每组数据有两个我们已知的属性或者特征值。上面的 group 矩阵每行包含一个不同的数据,我们可以把它想象为某个日志文件中不同的测量点或者入口。由于人类大脑的限制,我们通常只能可视化处理三维以下的事务。因此为了简单地实现数据可视化,对于每个数据点我们通常只使用两个特征。
向量 labels 包含了每个数据点的标签信息, labels 包含的元素个数等于 group 矩阵行数。
2.1.2 实施 kNN 算法
k-近邻算法的伪代码
该函数的功能是使用k-近邻算法将每组数据划分到某个类中,其伪代码如下:
对未知类别属性的数据集中的每个点依次执行以下操作:
(1) 计算已知类别数据集中的点与当前点之间的距离;
(2) 按照距离递增次序排序;
(3) 选取与当前点距离最小的k个点;
(4) 确定前k个点所在类别的出现频率;
(5) 返回前k个点出现频率最高的类别作为当前点的预测分类。
Python函数 classify0()
classify0() 函数有4个输入参数:用于分类的输入向量是inX,输入的训练样本集为dataSet,标签向量为 labels ,最后的参数 k 表示用于选择最近邻居的数目,其中标签向量的元素数目和矩阵 dataSet 的行数相同。
def classify0(inX, dataSet, labels, k):
"""
k-近邻算法
:param inX: 用于分类的输入向量
:param dataSet:输入的训练样本集
:param labels:标签向量
:param k:用于选择最近邻居的数目
"""
dataSetSize = dataSet.shape[0] # dataSet行数
diffMat = np.tile(inX, (dataSetSize, 1)) - dataSet # 对应项相减
sqDiffMat = diffMat**2 # 平方
sqDistances = sqDiffMat.sum(axis=1) # axis=1:按行,相加
distances = sqDistances**0.5 # 开根号
sortedDistIndicies = distances.argsort() # 排序(返回的是数组值从小到大的索引值)
classCount = {} # 创建空字典
for i in range(k): # 找出最近的k个
voteIlabel = labels[sortedDistIndicies[i]] # 选出来的标签
classCount[voteIlabel] = classCount.get(voteIlabel, 0) + 1 # dict. get()函数返回指定键的值,如果值不在字典中则返回0
sortedClassCount = sorted(classCount.items(), key=operator.itemgetter(1), reverse=True) # 倒序排序
return sortedClassCount[0][0]
【1】距离计算
程序清单2-1使用欧氏距离公式,计算两个向量点xA和xB之间的距离:
【2】排序,选择距离最小的k个点
计算完所有点之间的距离后,可以对数据按照从小到大的次序排序。然后,确定前k个距离最小元素所在的主要分类,输入k总是正整数。
【3】排序,返回发生频率最高的元素标签
最后,将classCount字典分解为元组列表,然后使用程序第二行导入运算符模块的 itemgetter 方法,按照第二个元素的次序对元组进行排序 。此处的排序为逆序,即按照从最大到最小次序排序,最后返回发生频率最高的元素标签。
2.1.3 如何测试分类器
为了测试分类器的效果,我们可以使用已知答案的数据,当然答案不能告诉分类器,检验分类器给出的结果是否符合预期结果。通过大量的测试数据,我们可以得到分类器的错误率——分类器给出错误结果的次数除以测试执行的总数。错误率是常用的评估方法,主要用于评估分类器在某个数据集上的执行效果。完美分类器的错误率为0,最差分类器的错误率是1.0。
2.1 Python 语法解析
1、numpy array数组
一个[]代表一行,[]里面元素个数,代表列数
2、numpy tile铺地砖
# 在列方向上重复[0,0]5次,默认行1次
# (后面是一个数就铺不成地板了 直接在后面延长)
np.tile([0,0],5)
Out[10]: array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
# 在列方向上重复[0,0]1次,行1次
# (有两个中括号 外面那个代表整个地板 里面那个代表一块地砖)
np.tile([0, 0], (1, 1))
Out[11]: array([[0, 0]])
# 在列方向上重复[0,0]1次,行2次
np.tile([0,0],(2,1))
Out[12]:
array([[0, 0],
[0, 0]])
# 在列方向上重复[0,0]1次,行3次
np.tile([0,0],(3,1))
Out[13]:
array([[0, 0],
[0, 0],
[0, 0]])
# 在列方向上重复[0,0]3次,行1次
np.tile([0, 0], (1, 3))
Out[14]: array([[0, 0, 0, 0, 0, 0]])
#在列方向上重复[0,0]3次,行2次
np.tile([0, 0], (2, 3))
Out[15]:
array([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]])
3、两个乘号:x的n次方
4、numpy.argsort()
numpy.argsort() 函数返回的是数组值从小到大的索引值。
返回的是索引值!!!
axis: 沿着它排序数组的轴,如果没有数组会被展开,沿着最后的轴排序, axis=0 按列排序,axis=1 按行排序
5、创建空字典
6、字典(Dictionary) get()方法
dict. get() 函数返回指定键的值,如果值不在字典中返回默认值。
7、sorted() 函数
sorted() 函数对所有可迭代的对象进行排序操作。
sorted(iterable, key=None, reverse=False)
iterable – 可迭代对象。
key – 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
reverse – 排序规则,reverse = True 降序 , reverse = False 升序(默认)。
8、字典(Dictionary) items()方法
Python 字典(Dictionary) items() 函数以列表返回可遍历的(键, 值) 元组数组。
dict.items()
9、operator.itemgetter函数
operator模块提供的itemgetter函数用于获取对象的哪些维的数据,参数为一些序号。
2.2 示例:使用 k-近邻算法改进约会网站的配对效果
2.2.1 准备数据:从文本文件中解析数据
在将上述特征数据输入到分类器之前,必须将待处理数据的格式改变为分类器可以接受的格式。
在kNN.py中创建名为 file2matrix 的函数,以此来处理输入格式问题。该函数的输入为文件名字符串,输出为训练样本矩阵和类标签向量。
将下面的代码增加到kNN.py中。
def file2matrix(filename):
"""
输入文件格式转换
:param filename:待处理文件
"""
love_dictionary = {'largeDoses': 3, 'smallDoses': 2, 'didntLike': 1}
fr = open(filename) # 打开文件操作
arrayOLines = fr.readlines() # 读取所有行并返回一个列表(每一行成为列表中的一个元素)
numberOfLines = len(arrayOLines) # 返回列表元素个数
returnMat = np.zeros((numberOfLines, 3)) # 初始化零填充的矩阵
classLabelVector = [] # prepare labels return
index = 0
for line in arrayOLines:
line = line.strip()
listFromLine = line.split('\t') # 字符串分割
returnMat[index, :] = listFromLine[0:3]
if (listFromLine[-1].isdigit()): # 若文件中是以数字方式存储喜好类别
classLabelVector.append(int(listFromLine[-1]))
else: # 若文件中是以英文存储喜好类别
classLabelVector.append(love_dictionary.get(listFromLine[-1]))
index += 1
return returnMat, classLabelVector
【1】得到文件行数
首先我们需要知道文本文件包含多少行。打开文件,得到文件的行数 。
【2】创建返回的NumPy矩阵
然后创建以零填充的矩阵NumPy (实际上,NumPy是一个二维数组,这里暂时不用考虑其用途)。为了简化处理,我们将该矩阵的另一维度设置为固定值 3 ,你可以按照自己的实际需求增加相应的代码以适应变化的输入值。
【3】解析文件数据到列表
循环处理文件中的每行数据 ,首先使用函数 line.strip() 截取掉所有的回车字符,然后使用tab字符 \t 将上一步得到的整行数据分割成一个元素列表。(Why?使用tab字符 \t 。因为文本里数据就是用Tab字符间距的)
接着,我们选取前3个元素,将它们存储到特征矩阵中。Python语言可以使用索引值-1表示列表中的最后一列元素,利用这种负索引,我们可以很方便地将列表的最后一列存储到向量 classLabelVector 中。
需要注意的是,我们必须明确地通知解释器,告诉它列表中存储的元素值为整型,否则Python语言会将这些元素当作字符串处理。
若kNNTest.py不在kNN.py所属工程文件中(但工程文件与kNNTest.py在同一目录下):
"""
Created on Dec 7, 2018
@author: xpt
"""
from KNN import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels[0:20])
若kNNTest.py在kNN.py所属工程文件中:
"""
Created on Dec 7, 2018
@author: xpt
"""
from kNN import *
datingDataMat, datingLabels = file2matrix('datingTestSet.txt')
print(datingDataMat)
print(datingLabels[0:20])
或者:
现在已经从文本文件中导入了数据,并将其格式化为想要的格式,接着我们需要了解数据的真实含义。当然我们可以直接浏览文本文件,但是这种方法非常不友好,一般来说,我们会采用图形化的方式直观地展示数据。下面就用Python工具来图形化展示数据内容,以便辨识出一些数据模式。
2.2.2 分析数据:使用 Matplotlib 创建散点图
Matplotlib 创建散点图绘制方法参考:https://blog.csdn.net/qq_34243930/article/details/84557429
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
import matplotlib.pyplot as plt
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
plt.show()
由于没有使用样本分类的特征值,我们很难从图2-3中看到任何有用的数据模式信息。
一般来说,我们会采用色彩或其他的记号来标记不同样本分类,以便更好地理解数据信息。Matplotlib库提供的 scatter 函数支持个性化标记散点图上的点。
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
import matplotlib.pyplot as plt
from numpy import *
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
fig = plt.figure()
ax = fig.add_subplot(111)
# ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2])
ax.scatter(datingDataMat[:, 1], datingDataMat[:, 2], 15.0*array(datingLabels), 15.0*array(datingLabels))
plt.show()
上述代码利用变量datingLabels存储的类标签属性,在散点图上绘制了色彩不等、尺寸不同的点。
带有样本分类标签的约会数据散点图。虽然能够比较容易地区分数据点从属类别,但依然很难根据这张图得出结论性信息。
但采用列1datingDataMat[:, 0]
和2datingDataMat[:, 1]
的属性值却可以得到更好的效果,图中清晰地标识了三个不同的样本分类区域,具有不同爱好的人其类别区域也不同:
2.2.3 准备数据:归一化数值
在处理这种不同取值范围的特征值时,我们通常采用的方法是将数值归一化,如将取值范围处理为0到1或者1到1之间。下面的公式可以将任意取值范围的特征值转化为0到1区间内的值:
其中 min 和 max 分别是数据集中的最小特征值和最大特征值。
我们需要在文件kNN.py中增加一个新函数 autoNorm() ,该函数可以自动将数字特征值转化为0到1的区间。
def autoNorm(dataSet):
"""
归一化特征值
:param dataSet: 原数据集
"""
minVals = dataSet.min(0) # min(0)返回该矩阵中每一列的最小值
maxVals = dataSet.max(0) # max(0)返回该矩阵中每一列的最大值
ranges = maxVals - minVals
normDataSet = np.zeros(np.shape(dataSet)) # 创建与dataSet维度相同的全0矩阵
m = dataSet.shape[0] # dataSet行数
normDataSet = dataSet - np.tile(minVals, (m, 1)) # tile铺地砖:1000行1列,(相减)
normDataSet = normDataSet/np.tile(ranges, (m, 1)) # 相除
return normDataSet, ranges, minVals
在函数 autoNorm() 中,我们将每列的最小值放在变量 minVals 中,将最大值放在变量maxVals 中,其中 dataSet.min(0) 中的参数0使得函数可以从列中选取最小值,而不是选取当前行的最小值。然后,函数计算可能的取值范围,并创建新的返回矩阵。正如前面给出的公式,为了归一化特征值,我们必须使用当前值减去最小值,然后除以取值范围。需要注意的是,特征值矩阵有1000×3个值,而 minVals 和 range 的值都为1×3。为了解决这个问题,我们使用NumPy库中 tile() 函数将变量内容复制成输入矩阵同样大小的矩阵。
注意这是具体特征值相除。【而对于某些数值处理软件包, / 可能意味着矩阵除法,但在NumPy库中,矩阵除法需要使用函数linalg.solve(matA,matB) 。】
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
print(normMat)
print(ranges)
print(minVals)
2.2.4 测试算法:作为完整程序验证分类器
机器学习算法一个很重要的工作就是评估算法的正确率,通常我们只提供已有数据的90%作为训练样本来训练分类器,而使用其余的10%数据去测试分类器,检测分类器的正确率。本书后续章节还会介绍一些高级方法完成同样的任务,这里我们还是采用最原始的做法。
需要注意的是,10%的测试数据应该是随机选择的,由于海伦提供的数据并没有按照特定目的来排序,所以我们可以随意选择10%数据而不影响其随机性。
代码里我们定义一个计数器变量,每次分类器错误地分类数据,计数器就加1,程序执行完成之后计数器的结果除以数据点总数即是错误率。
为了测试分类器效果,在kNN.py文件中创建函数 datingClassTest :
def datingClassTest():
"""
测试分类器
:return: error rate错误率
"""
hoRatio = 0.10 # 拿出10%作为测试test数据
datingDataMat, datingLabels = file2matrix('datingTestSet2.txt') # 输入文件格式转换
normMat, ranges, minVals = autoNorm(datingDataMat) # 归一化特征值
m = normMat.shape[0] # normMat归一化特征值后的数据集,行数
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)))
函数 datingClassTest 如程序清单2-4所示,它
【1】首先使用了 file2matrix ()和 autoNorm() 函数从文件中读取数据并将其转换为归一化特征值。
【2】接着计算测试向量的数量,此步决定了normMat 向量中哪些数据用于测试,哪些数据用于分类器的训练样本;
【3】然后将这两部分数据输入到原始kNN分类器函数 classify0 。
【4】最后,函数计算错误率并输出结果。
注意此处我们使用原始分类器,本章花费了大量的篇幅在讲解如何处理数据,如何将数据改造为分类器可以使用的特征值。得到可靠的数据同样重要,本书后续的章节将介绍这个主题。
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
kNN.datingClassTest()
我们可以改变函数datingClassTest 内变量 hoRatio 和变量 k 的值,检测错误率是否随着变量值的变化而增加。依赖于分类算法、数据集和程序设置,分类器的输出结果可能有很大的不同。
这个例子表明我们可以正确地预测分类.
2.2.5 使用算法:构建完整可用系统
上面我们已经在数据上对分类器进行了测试,现在终于可以使用这个分类器为海伦来对人们分类。我们会给海伦一小段程序,通过该程序海伦会在约会网站上找到某个人并输入他的信息。程序会给出她对对方喜欢程度的预测值。
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('datingTestSet2.txt')
normMat, ranges, minVals = autoNorm(datingDataMat)
inArr = np.array([ffMiles, percentTats, iceCream]) # 用于分类的输入向量(也需要归一化:(inArr - minVals)/ranges)
classifierResult = classify0((inArr - minVals)/ranges, normMat, datingLabels, 3)
print("You will probably like this person: %s" % resultList[classifierResult - 1])
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
datingDataMat, datingLabels = kNN.file2matrix('datingTestSet.txt')
normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
kNN.classifyPerson()
2.2 Python 语法解析
1、File readlines() 方法
readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表
例子见:https://blog.csdn.net/qq_34243930/article/details/83748085#t32 中文件读操作部分
2、List len()方法
len(list)返回列表元素个数。
3、字符串分割str.split()
str.split()函数
()里放的是要以什么进行分割
输出的是一个列表list
例子见:https://blog.csdn.net/qq_34243930/article/details/83748085#t32 中字符串分割部分
4、[0:3]指第0,1,2个元素
正向索引:从0开始标!
反向索引:从-1开始标!
区间索引【A:B】注意不包含位置B!!!
正向索引区间从0开始,可以省略0。比如:S[0:3]等价于s[:3]
反向索引:以-1结尾,省略-1表示可以取到最后一位了。
但是这里S[-4:-1]不等价于s[-4:]!!!
例子见:https://blog.csdn.net/qq_34243930/article/details/83748085#t32 中字符串部分
5、 isdigit()方法
isdigit() 方法检测字符串是否只由数字组成。
str.isdigit()
如果字符串只包含数字则返回 True 否则返回 False。
6、List append()方法
append() 方法用于在列表末尾添加新的对象。
list.append(obj)
obj – 添加到列表末尾的对象。
7、plt.figure() :自定义画布大小
figure(num=None, figsize=None, dpi=None, facecolor=None, edgecolor=None, frameon=True)
num:图像编号或名称,数字为编号 ,字符串为名称
figsize:指定figure的宽和高,单位为英寸;
dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80
1英寸等于2.5cm,A4纸是 21*30cm的纸张
facecolor:背景颜色
edgecolor:边框颜色
frameon:是否显示边框
8、plt.subplot() :设置画布划分以及图像在画布上输出的位置
plt.subplot(221):
将figure设置的画布大小分成几个部分,参数‘221’表示2(row)x2(colu),即将画布分成2x2,两行两列的4块区域,1表示选择图形输出的区域在第一块,图形输出区域参数必须在“行x列”范围(此处必须在1和2之间选择)
plt.subplot(111):
如果参数设置为subplot(111),则表示画布整个输出,不分割成小块区域,图形直接输出在整块画布上
9、add_subplot 和subplot区别
10、plt.scatter(x,y)
11、min(),max()的使用
min(0)返回该矩阵中每一列的最小值
min(1)返回该矩阵中每一行的最小值
max(0)返回该矩阵中每一列的最大值
max(1)返回该矩阵中每一行的最大值
12、np.array()、np.shape()、np.ones()、np.zeros()
采用np.array()创建时需要几个维度就要用几个[ ]括起来,这种创建方式要给定数据;
采用np.ones()或np.zeros()创建分别产生全1或全0的数据,
用a.shape会输出你创建时的输入,创建时输入了几个维度输出就会用几个[ ]括起来,shape的返回值是一个元组,里面每个数字表示每一维的长度
13、格式化输出的方法
.format与%
目前为止,我们已经看到如何在数据上构建分类器。这里所有的数据让人看起来都很容易,但是如何在人不太容易看懂的数据上使用分类器呢?从下一节的例子中,我们会看到如何在二进制存储的图像数据上使用kNN。
2.3 示例:手写识别系统
本节我们一步步地构造使用k-近邻分类器的手写识别系统。为了简单起见,这里构造的系统只能识别数字0到9,参见图2-6。需要识别的数字已经使用图形处理软件,处理成具有相同的色彩和大小:宽高是32像素×32像素的黑白图像。尽管采用文本格式存储图像不能有效地利用内存空间,但是为了方便理解,我们还是将图像转换为文本格式。
2.3.1 准备数据:将图像转换为测试向量
实际图像存储在第2章源代码的两个子目录内:目录trainingDigits中包含了大约2000个例子,每个例子的内容如图2-6所示,每个数字大约有200个样本;目录testDigits中包含了大约900个测试数据。我们使用目录trainingDigits中的数据训练分类器,使用目录testDigits中的数据测试分类器的效果。两组数据没有重叠,你可以检查一下这些文件夹的文件是否符合要求。
为了使用前面两个例子的分类器,我们必须将图像格式化处理为一个向量。我们将把一个32×32的二进制图像矩阵转换为1×1024的向量,这样前两节使用的分类器就可以处理数字图像信息了。
我们首先编写一段函数 img2vector ,将图像转换为向量:该函数创建1×1024的NumPy数组,然后打开给定的文件,循环读出文件的前32行,并将每行的头32个字符值存储在NumPy数组中,最后返回数组。
def img2vector(filename):
"""
将图像转换为向量
"""
returnVect = np.zeros((1, 1024)) # 创建1×1024的NumPy数组
fr = open(filename) # 打开文件
for i in range(32): # 循环读出文件32行
lineStr = fr.readline() # 一行一行读
for j in range(32): # 将每行的头32个字符值存储在NumPy数组
returnVect[0, 32*i+j] = int(lineStr[j])
return returnVect
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
testVector = kNN.img2vector('testDigits/0_13.txt')
print(testVector[0, 0:31])
print(testVector[0, 32:63])
注意:文件夹应该放到工程文件夹内
2.3.2 测试算法:使用 k-近邻算法识别手写数字
上节我们已经将数据处理成分类器可以识别的格式,本节我们将这些数据输入到分类器,检测分类器的执行效果。
程序清单2-6所示的函数 handwritingClassTest() 是测试分类器的代码,将其写入kNN.py文件中。
在写入这些代码之前,我们必须确保将 from os import listdir 写入文件的起始部分,这段代码的主要功能是从os模块中导入函数 listdir ,它可以列出给定目录的文件名。
def handwritingClassTest():
hwLabels = []
trainingFileList = listdir('trainingDigits') # 获取训练集目录内容
m = len(trainingFileList) # 目录中文件个数
trainingMat = np.zeros((m, 1024)) # 创建一个m行1024列的训练矩阵
for i in range(m):
fileNameStr = trainingFileList[i]
fileStr = fileNameStr.split('.')[0] # 以'.'分割fileNameStr并获取第一个数据。即0_110.txt的0_110
classNumStr = int(fileStr.split('_')[0]) # 以'_'分割fileStr并获取第一个数据。即0_110的0
hwLabels.append(classNumStr)
trainingMat[i, :] = img2vector('trainingDigits/%s' % fileNameStr)
testFileList = listdir('testDigits') # 获取测试集目录内容
errorCount = 0.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)))
【1】将trainingDigits目录中的文件内容存储在列表中 ,然后可以得到目录中有多少文件,并将其存储在变量 m 中。
【2】接着,代码创建一个 m 行1024列的训练矩阵,该矩阵的每行数
据存储一个图像。我们可以从文件名中解析出分类数字 。该目录下的文件按照规则命名,如文件9_45.txt的分类是9,它是数字9的第45个实例。然后我们可以将类代码存储在 hwLabels 向量中,使用前面讨论的 img2vector 函数载入图像。
在下一步中,我们对testDigits目录中的文件执行相似的操作,不同之处是我们并不将这个目录下的文件载入矩阵中,而是使用 classify0() 函数测试该目录下的每个文件。由于文件中的值已经在0和1之间,本节并不需要使用2.2节的 autoNorm() 函数。
"""
Created on Dec 7, 2018
@author: xpt
"""
import kNN
kNN.handwritingClassTest()
改变变量 k 的值、修改函数 handwriting-ClassTest 随机选取训练样本、改变训练样本的数目,都会对k-近邻算法的错误率产生影响。
实际使用这个算法时,算法的执行效率并不高。因为算法需要为每个测试向量做2000次距离计算,每个距离计算包括了1024个维度浮点运算,总计要执行900次,此外,我们还需要为测试向量准备2MB的存储空间。是否存在一种算法减少存储空间和计算时间的开销呢?
k决策树就是k-近邻算法的优化版,可以节省大量的计算开销。
2.3 Python 语法解析
1、File readline() 方法
readline() 方法用于从文件读取整行,包括 “\n” 字符。如果指定了一个非负数的参数,则返回指定大小的字节数,包括 “\n” 字符
readline(): 一行一行读
例子见 https://blog.csdn.net/qq_34243930/article/details/83748085#t32 中文件读操作部分
2、from os import listdir
主要功能是从os模块中导入函数 listdir ,它可以列出给定目录的文件名。
2.4 本章小结
k-近邻算法是分类数据最简单最有效的算法,本章通过两个例子讲述了如何使用k-近邻算法构造分类器。
k-近邻算法是基于实例的学习,使用算法时我们必须有接近实际数据的训练样本数据。k-近邻算法必须保存全部数据集,如果训练数据集的很大,必须使用大量的存储空间。此外,由于必须对数据集中的每个数据计算距离值,实际使用时可能非常耗时。
k-近邻算法的另一个缺陷是它无法给出任何数据的基础结构信息,因此我们也无法知晓平均实例样本和典型实例样本具有什么特征。下一章我们将使用概率测量方法处理分类问题,该算法可以解决这个问题。
Python:variable in function(argument、function) name should be lowercase 处理方式
用pyCharm时,常会出现警告信息:
function name should be lowercase --函数名应该是小写 字母
argument name should be lowercase --参数名应该是小写字母
variable in function should be lowercase --变量应该是小写字母
全是小写字母,可能与以往的习惯不大一样,将这样的警告忽略的方法如下:
File →Settings→Editor→Inspections→Python→PEP 8 naming convention violation
右下角有个"Ignored errors",增加:
N802
N803
N806
分别对应上面的三种提示。