标签:ems turn 图像处理 之间 lifo 存储 item 排列 row
声明:文章是读书笔记,所以必然有大部分内容出自《机器学习实战》。外加个人的理解,另外修改了部分代码,并添加了注释
简单地说,k-近邻算法采用测量不同特征值之间距离的方法进行分类。不恰当但是形象地可以表述为近朱者赤,近墨者黑。它有如下特点:
存在一个样本数据集合,也称作训练样本集,并且样本集中的每个数据都存在标签,即我们知道样本集中每一数据与所属分类的对应关系。输入没有标签的数据后,将这个没有标签的数据的每个特征与样本集中的数据对应的特征进行比较,然后算法提取样本中特征最相似的数据(最邻近)的分类标签。一般来说,我们只选择样本数据集中前
例子出自《机器学习实战》,中文版第16页,英文版第19页
下面举一个书本上的例子来说明
我们想使用
电影名称 | 打斗镜头 | 接吻镜头 | 电影类型 |
---|---|---|---|
California Man | 3 | 104 | 爱情片 |
He’s Not Really into Dudes | 2 | 100 | 爱情片 |
Beautiful Woman | 1 | 81 | 爱情片 |
Kevin Longblade | 101 | 10 | 动作片 |
Robo Slayer 3000 | 99 | 5 | 动作片 |
Amped II | 98 | 2 | 动作片 |
? | 18 | 90 | 未知 |
即使不知道未知电影属于哪种类型,我们也可以通过某种方法计算出来。首先要计算未知电影与样本集中其他电影的距离,计算方法很简单,即欧式空间距离(Euclidean Distance),结果如下表所示。
电影名称 | 与未知电影的距离 |
---|---|
California Man | 20.5 |
He’s Not Really into Dudes | 18.7 |
Beautiful Woman | 19.2 |
Kevin Longblade | 115.3 |
Robo Slayer 3000 | 117.4 |
Amped II | 118.9 |
现在我们得到了样本集中所有电影与未知电影的距离,按照距离递增排序,可以找到
我们主要用的编程语言是python,以及一些python增强包。选用python使我们不许要考虑太多的编程细节,能够把重心都放在程序逻辑上。
机器学习最重要的就是数据,首先我们来捏造一些样本数据。将下面的代码保存到名为 kNN.py 的文本文件中:
from numpy import *
def createDataSet():
dataSet = array([[1.0, 1.1], [1.0, 1.0], [0, 0], [0, 0.1]]) # 创建一个2x2的数组
labels = [‘A‘, ‘A‘, ‘B‘, ‘B‘] # 创建一个长度为4的列表
return dataSet, labels
在上面的代码中,我们导入了科学计算包 NumPy,如果你还没有安装 NumPy,请参考我的另外一篇文章《机器学习实战》读书笔记1:NumPy的安装及简单用法。
在上面的代码中,我们创建了一个大小为 2x2 的 NumPy 数组 dataSet,dataSet 的每一行是一个数据项。我们还创建了一个长度为 4 的列表 labels,labels 的每一项对应于 dataSet 的每一行,其对应关系如下表所示:
dataSet[i],即样本 | 特征0 | 特征1 | labels[i],即样本对应的类别 |
---|---|---|---|
dataSet[0] | 1.0 | 1.1 | A |
dataSet[1] | 1.0 | 1.0 | A |
dataSet[2] | 0 | 0 | B |
dataSet[3] | 0 | 0.1 | B |
现在我们打开命令提示符(我使用的是 Ubuntu),检查代码是否能够正常工作:
1. 在代码文件 kNN.py 所在目录下打开命令提示符(终端)
2. 在终端中输入python
打开python(我使用的是python2.7)
3. 导入我们刚才写的代码 kNN.py:import kNN
4. 输入如下命令获得由函数 createDataSet() 捏造的数据样本并保存到变量 gropu 和 labels 中:
>>> group, labels = kNN.createDataSet()
5. 输入 group 和 labels 产看数据内容是否正确:
结果应该如下所示:
>>> group
array([[ 1. , 1.1],
[ 1. , 1. ],
[ 0. , 0. ],
[ 0. , 0.1]])`
>>> labels
[‘A‘, ‘A‘, ‘B‘, ‘B‘]
上述步骤的运行截图:
首先给出 kNN 算法的伪代码。对未知类别属性的数据集中的每个点一次执行以下操作:
下面我们写一个函数 classify0() 来实现上面的步骤:
from operator import itemgetter
# inVec为待分类向量,dataSet和labels为数据集,k是最近点的个数
def classify0(inVec, dataSet, labels, k):
numberOfLines = dataSet.shape[0] # 获得数据集样本数量
diffMat = tile(inVec, (numberOfLines, 1)) - dataSet # 将数据集中每个点都与待分类点相减,即各个特征相减
squareDiffMat = diffMat**2 # 求差的平方
squareDistance = squareDiffMat.sum(axis=1) # 求差的平方的和
distances = squareDistance**0.5 # 对平方和开方得到距离
# 对距离进行排序,argsort()函数默认按升序排列,但只返回下标,不对原数组排序
sortedDistIndicies = distances.argsort()
classCount = {} # 用于保存各个类别出现的次数
for i in range(k): # 统计最近的 k 个点的类别出现的次数
label = labels[sortedDistIndicies[i]]
classCount[label] = classCount.get(label, 0) + 1
# 对类别出现的次数进行排序,sorted()函数默认升序
sortedClassCount = sorted(classCount.iteritems(), key=itemgetter(1), reverse=True)
return sortedClassCount[0][0] # 返回类别出现次数最多的分类名称
相信上面代码的注释已经把代码解释的很清楚了。只需要注意以下几个函数和 numpy 语法的用法:
1、shape()函数:返回数组的尺寸信息,例如:
>>> x = tile((1,2),(3,2))
>>> x.shape[0] # 返回第0维的大小,行数
3
>>> x.shape[1] # 返回第1维的大小,列数
4
>>> shape(x) # 返回尺寸
(3, 4)
2、tile()函数:将数组、列表或元组平铺,返回平铺后的数组,例如 tile([1,2],(3,2)) 将返回如下数组:
>>> tile([1,2],(3,2))
array([[1, 2, 1, 2],
[1, 2, 1, 2],
[1, 2, 1, 2]])
3、sum()函数,示例:
>>> x = tile((1,2),(3,2))
>>> x
array([[1, 2, 1, 2],
[1, 2, 1, 2],
[1, 2, 1, 2]])
>>> x.sum(axis=0)
array([3, 6, 3, 6])
>>> x.sum(axis=1)
array([6, 6, 6])
4、argsort()函数,返回排序后的原来位置的索引。示例:
>>> v = [1, 4, 2, 3]
>>> argsort(v)
array([0, 2, 3, 1])
5、sorted()函数,按参数 key 排序,示例(按字典的键的值降序排列):
>>> d = {‘a‘:2,‘b‘:1,‘c‘:6,‘d‘:-2}
>>> d
{‘a‘: 2, ‘c‘: 6, ‘b‘: 1, ‘d‘: -2}
>>> from operator import itemgetter
>>> sorted(d.iteritems(),key=itemgetter(1),reverse=True)
[(‘c‘, 6), (‘a‘, 2), (‘b‘, 1), (‘d‘, -2)]
具体用法,请参考文档,或百度。
另外,我们在前面提到过,距离计算使用的是欧式距离,其公式如下:
好了,现在我们来试一试
1、首先回到刚才的 python 窗口,输入reload(kNN)
重新加载我们的代码。
2、输入kNN.classify([0,0], group, labels, 3)
来对未知数据点
分类器也有可能出错,即把属于某一类的分到另一类。
此示例出自《机器学习实战》,中文版第20页,英文版第24页
下面是问题的原文:
我的朋友海伦一直使用在线约会网站寻找合适自己的约会对象。尽管约会网站会推荐不同的人选,但她并不是喜欢每一个人。经过一番总结,她发现曾交往过三种类型的人:
(1)不喜欢的人;
(2)魅力一般的人;
(3)极具魅力的人;
尽管发现了上述规律,但海伦依然无法将约会网站推荐的匹配对象归入恰当的分类,她觉得可以在周一到周五约会那些魅力一般的人,而周末则更喜欢与那些极具魅力的人为伴。海伦希望我们的分类软件可以更好地帮助她将匹配对象划分到确切的分类中。此外,海伦还收集了一些约会网站未曾记录的数据信息,她认为这些数据更助于匹配对象的归类。
在约会网站上使用
海伦收集约会数据已经有了一段时间,她把这些数据存放在文本文件datingTestSet2.txt中,每个样本数据占据一行,总共有1000行。海伦的样本主要包括以下3种特征:
部分数据截图如下(第一列为飞行里程,第二列为游戏时间百分比,第三列为冰淇淋公升数):
将下面的代码增加到kNN.py中:
def file2matrix(filename):
f = open(filename) # 打开文件
dataSet = f.readlines() # 读取文件的全部内容
numberOfLines = len(dataSet) # 获得数据集的行数
returnMat = zeros((numberOfLines, 3)) # 创建一个初始值为0,大小为 numberOfLines x 3 的数组
classLabelVector = [] # 用于保存没个数据的类别标签
index = 0
for line in dataSet: # 处理每一行数据
line = line.strip() # 去掉行首尾的空白字符,(包括‘\n‘, ‘\r‘, ‘\t‘, ‘ ‘)
listFromLine = line.split() # 分割每行数据,保存到一个列表中
returnMat[index, :] = listFromLine[0:3] # 将列表中的特征保存到reurnMat中
classLabelVector.append(int(listFromLine[-1])) # 保存分类标签
index += 1
return returnMat, classLabelVector
好了,现在我们回到 python 命令窗口中,输入如下两行命令:
>>> reload(kNN)
>>> datingDataMat, datingLabels = kNN.file2matrix(‘datingTestSet2.txt‘)
reload(kNN)
重新加载了代码。第二行得到了处理好的样本数据,其中 datingDataMat 保存了每一个样本的特征,datingLabels 保存了每一个样本的分类标签。现在我们可以查看一下它们的内容:
>>> datingDataMat
array([[ 4.09200000e+04, 8.32697600e+00, 9.53952000e-01],
[ 1.44880000e+04, 7.15346900e+00, 1.67390400e+00],
[ 2.60520000e+04, 1.44187100e+00, 8.05124000e-01],
...,
[ 2.65750000e+04, 1.06501020e+01, 8.66627000e-01],
[ 4.81110000e+04, 9.13452800e+00, 7.28045000e-01],
[ 4.37570000e+04, 7.88260100e+00, 1.33244600e+00]])
>>> datingLabels[:20]
[3, 2, 1, 1, 1, 1, 3, 3, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 2, 3]
由于 datingLabels 内容有1000个,所以只查看了前20个。现在数据已经准备好了,接下来我们应该对数据进行分析。
关于 Matplotlib 的安装,请参见我的另一篇文章:
在python命令行环境中输入下列命令:
>>> import matplotlib
>>> import matplotlib.pyplot as plt
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,1], datingDataMat[:,2])
>>> plt.show()
输出效果图如下图所示(x轴为玩视频游戏所耗时间百分比,y轴为每周消费的冰淇淋公升数):
>>> from numpy import *
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,1],datingDataMat[:,2],15.0*array(datingLabels),15.0*array(datingLabels))
>>> plt.show()
输出效果图如下图所示:
下面我们将每年获得的飞行常客里程数作为 x 轴,玩视频游戏所耗时间百分比作为 y 轴。看看样本点的分布有何不同。
>>> fig = plt.figure()
>>> ax = fig.add_subplot(111)
>>> ax.scatter(datingDataMat[:,0],datingDataMat[:,1],15.0*array(datingLabels),15.0*array(datingLabels))
>>> plt.show()
输出效果图如下图所示:
另外,为了方便画图,不用每次都输入上面几行代码,我们可以写一个函数 showPlots 来完成同样的功能(将代码添加到 kNN.py 中):
from numpy import *
import matplotlib.pyplot as plt
def showPlots(x, y, labels): # x:x轴数据,y:轴数据,labels:分类标签数据
fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(x ,y, 15.0*array(labels), 15*array(labels))
plt.show()
重新载入 kNN.py ,输入如下代码即可画图:
>>> reload(kNN)
>>> kNN.showPlots(datingDataMat[:,0], datingDataMat[:,1], datingLabels)
下表给出了四组数据:
样本 | 每年获得的飞行常客里程数 | 玩视频游戏所耗时间百分比 | 每周消费的冰淇淋公升数 | 样本对应的类别 |
---|---|---|---|---|
1 | 400 | 0.8 | 0.5 | 1 |
2 | 134,000 | 12 | 0.9 | 3 |
3 | 20,000 | 0 | 1.1 | 2 |
4 | 32,000 | 67 | 0.1 | 2 |
其中 1 代表不喜欢,2 代表魅力一般,3 代表极具魅力
假如我们要计算样本 3 和样本 4 之间的距离,可以用下面的方法:
在处理这种不同取值范围的特征值时,我们需要对数据进行归一化处理。将取值范围处理为 0 到 1 或者 -1 到 1 之间。下面的公式可以将任意取值范围的特征值转化为 0 到 1 区间内的值:
def autoNorm(dataSet):
minVals = dataSet.min(0) # minVals保存每列最小值
maxVals = dataSet.max(0) # maxVals保存每列最大值
ranges = maxVals - minVals # ranges保存每列的取值范围
normedDataSet = zeros(shape(dataSet))
numberOfLines = dataSet.shape[0]
normedDataSet = dataSet - tile(minVals, (numberOfLines, 1))
normedDataSet = normedDataSet / tile(ranges, (numberOfLines, 1))
return normedDataSet, ranges, minVals
dataSet.min(0) 函数可以从列中选取最小值,dataSet.max(0) 同理。
下面在 python 命令行中,重新加载 kNN.py 模块,执行 autoNorm 函数,检查函数的执行结果:
>>> reload(kNN)
>>> normMat, ranges, minVals = kNN.autoNorm(datingDataMat)
>>> normMat
array([[ 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
array([ 9.12730000e+04, 2.09193490e+01, 1.69436100e+00])
>>> minVals
array([ 0. , 0. , 0.001156])
可以看到,特征数据集已经被归一化了。
分类器的错误率公式如下:
def datingClassTest():
testRatio = 0.10 # 测试比例
datingDataMat, datingLabels = file2matrix(‘datingTestSet2.txt‘) # 获得原始数据
normedMat, ranges, minVals = autoNorm(datingDataMat) # 归一化
m = normedMat.shape[0] # 原始数据行数
numTestVecs = int(m*testRatio) # 测试数据行数
errorCount = 0 # 错误分类计数器
for i in range(numTestVecs): # 测试
classifierResult = classify0(normedMat[i,:],
normedMat[numTestVecs:m,:], datingLabels[numTestVecs:m], 4)
print "The classifier came back with: %d, the real answer is: %d" % (classifierResult, datingLabels[i])
if(classifierResult != datingLabels[i]):
errorCount += 1
print "The total error rate is: %f" % (errorCount/float(numTestVecs))
在 python 命令行中重新加载 kNN.py 模块,并执行上面的函数,下面我执行的结果:
>>> reload(kNN)
>>> kNN.datingClassTest()
The classifier came back with: 3, the real answer is: 3
The classifier came back with: 2, the real answer is: 2
.
.
The classifier came back with: 1, the real answer is: 1
The classifier came back with: 3, the real answer is: 3
The classifier came back with: 3, the real answer is: 3
The classifier came back with: 2, the real answer is: 2
The classifier came back with: 1, the real answer is: 1
The classifier came back with: 1, the real answer is: 1
The total error rate is: 0.030000
错误率为 3%,应该算不错了。
下面我们就来构建一个可以使用的分类系统,只需添加一个简单的函数即可实现:
def classifyPerson():
resultList = [‘not at all‘, ‘in small doses‘, ‘in large doses‘]
percentTime = float(raw_input("percentage of time spent playing video games: "))
ffMiles = float(raw_input("frequent flier miles earned per year: "))
iceCream = float(raw_input("liters of ice cream consumed per year: "))
datingDataMat, datingLabels = file2matrix(‘datingTestSet2.txt‘)
normedMat, ranges, minVals = autoNorm(datingDataMat)
inVec = array([ffMiles, percentTime, iceCream])
classifierResult = classify0((inVec-minVals)/ranges, normedMat, datingLabels, 4)
print "You will probably like this person", resultList[classifierResult-1]
上面的代码很简单,就不要注释了吧=。=,下面我们来试一试:
>>> reload(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
Perfect.下面我们用 kNN 来做一个手写识别系统
此示例出自《机器学习实战》,中文版第28页,英文版第33页
为了简单起见,这里构造的系统只能识别数字 0 到 9。数字是由 0 和 1 表示的文本形式。如下图分别是 9、2、7:
书本配备了两个数据集,一个是存储在文件夹 trainingDigits 内的训练集,大约2000个样本;另一个是存储在 testDigits 文件夹内的测试集,大约900个样本。如下图所示。两组数据没有重叠,尺寸均为 32行 x 32列。
def img2vector(filename):
returnVec = zeros((1,1024)) # 用于保存1x1024的向量
f = open(filename) # 打开文件
for i in range(32): # 读取每一行并转换为1x1024的向量
lineStr = f.readline()
for j in range(32): # 处理第i行j列的一个字符
returnVec[0,32*i+j] = int(lineStr[j]) # 字符需要强制类型转换成整数
return returnVec
现在输入如下命令,检查函数是否正常工作:
>>> reload(kNN)
>>> testVec = kNN.img2vector(‘testDigits/0_13.txt‘)
>>> testVec
array([[ 0., 0., 0., ..., 0., 0., 0.]])
>>> testVec[0,0:32]
array([ 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., 0.])
>>> testVec[0,32:64]
array([ 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., 0.])
打开 testDigits 文件夹下的 0_13.txt 与上面的结果对比,说明函数 img2vector 工作正常。注意: 0_13.txt 表示数据集中数字 0 的第 13 样本。下面我们测试一下算法。
上节已经数字的图像处理成分类器可以识别的格式,我们现在就来测试分类去的执行效果。下面的函数 handwritingClassTest() 是测试分类器的代码:
from os import listdir
def handwritingClassTest():
print "Loading data..."
hwLabels = [] # 保存手写数字的分类标签
trainingFileList = listdir(‘trainingDigits‘) # 得到文件夹trainingDigits下的所有文件名
m = len(trainingFileList) # 训练集样本的个数
trainingMat = zeros((m, 1024)) # 保存训练集
for i in range(m):
filenameStr = trainingFileList[i] # 得到文件名
classNum = int(filenameStr.split(‘_‘)[0]) # 得到样本的分类标签
hwLabels.append(classNum)
trainingMat[i,:] = img2vector(‘trainingDigits/%s‘ % filenameStr)
testFileList = listdir(‘testDigits‘) # 得到文件夹testDigits下的所有文件名
errorCount = 0 # 错误分类计数器
mTest = len(testFileList) # 测试集样本个数
for i in range(mTest): # 开始测试
filenameStr = testFileList[i]
classNum = int(filenameStr.split(‘_‘)[0])
testVect = img2vector(‘trainingDigits/%s‘ % filenameStr)
classifierResult = classify0(testVect, trainingMat, hwLabels, 3)
print "The classifier came back with: %d, the real answer is: %d" % (classifierResult, classNum)
if(classifierResult != classNum):
errorCount += 1
print "The total number of errors is: %d" % errorCount
print "The total error rate is: %f" % (errorCount/float(mTest))
上面的注释已经足够清楚了。主要说一下这行代码:
classNum = int(filenameStr.split(‘_‘)[0]) # 得到样本的分类标签
5.1 节中提到了 0_13.txt 表示数据集中数字 0 的第 13 样本。即 0 是样本0_13.txt的标签,其内容如下:
>>> reload(kNN)
>>> kNN.handwritingClassTest()
Loading data...
The classifier came back with: 6, the real answer is: 6
The classifier came back with: 0, the real answer is: 0
.
.
The classifier came back with: 8, the real answer is: 8
The classifier came back with: 8, the real answer is: 8
The classifier came back with: 2, the real answer is: 2
The classifier came back with: 9, the real answer is: 9
The classifier came back with: 5, the real answer is: 5
The total number of errors is: 13
The total error rate is: 0.013742
错误率为 1.3%,看来分类器工作得还不错。
当我把 k 设为1时,发现错误率为0,我想原因大概是每个数字的形状差别都比较大,如果选择最近的一个作为分类标签,那么准确率会非常的高:
>>> reload(kNN)
<module ‘kNN‘ from ‘kNN.pyc‘>
>>> kNN.handwritingClassTest()
Loading data...
The classifier came back with: 6, the real answer is: 6
The classifier came back with: 0, the real answer is: 0
.
.
The classifier came back with: 8, the real answer is: 8
The classifier came back with: 8, the real answer is: 8
The classifier came back with: 2, the real answer is: 2
The classifier came back with: 9, the real answer is: 9
The classifier came back with: 5, the real answer is: 5
The total number of errors is: 0
The total error rate is: 0.000000
kNN算法简单而且准确率高,但是最大的缺点就是既占空间速度又慢。例如上面的手写数字识别系统只是 0-9 十个数字,测试向量就占用了大约 2MB 的空间。而且计算复杂度高,算法要为每个测试向量执行 2000 次距离计算,每次距离计算又包括了 900 次 1024 个维度的浮点运算。除此之外,每次距离计算还需要进行排序等耗时的工作。所以 kNN 的缺点很大。应该算是一种比较不实用的算法,但优点是结果准确。
kNN 算法的另一个缺陷是无法给出任何数据的基础结构信息。而决策树能够解决这个问题,并且速度很快。
标签:ems turn 图像处理 之间 lifo 存储 item 排列 row
原文地址:http://blog.csdn.net/artprog/article/details/54821927