❶ KNN算法-4-算法优化-KD树
KNN算法的重要步骤是对所有的实例点进行快速k近邻搜索。如果采用线性扫描(linear scan),要计算输入点与每一个点的距离,时间复杂度非常高。因此在查询操作时,可以使用kd树对查询操作进行优化。
Kd-树是K-dimension tree的缩写,是对数据点在k维空间(如二维(x,y),三维(x,y,z),k维(x1,y,z..))中划分的一种数据结构,主要应用于多维空间关键数据的搜索(如:范围搜索和最近邻搜索)。本质上说,Kd-树就是一种平衡二叉树。
k-d tree是每个节点均为k维样本点的二叉树,其上的每个样本点代表一个超平面,该超平面垂直于当前划分维度的坐标轴,并在该维度上将空间划分为两部分,一部分在其左子树,另一部分在其右子树。即若当前节点的划分维度为d,其左子树上所有点在d维的坐标值均小于当前值,右子树上所有点在d维的坐标值均大于等于当前值,本定义对其任意子节点均成立。
必须搞清楚的是,k-d树是一种空间划分树,说白了,就是把整个空间划分为特定的几个部分,然后在特定空间的部分内进行相关搜索操作。想象一个三维(多维有点为难你的想象力了)空间,kd树按照一定的划分规则把这个三维空间划分了多个空间,如下图所示:
首先,边框为红色的竖直平面将整个空间划分为两部分,此两部分又分别被边框为绿色的水平平面划分为上下两部分。最后此4个子空间又分别被边框为蓝色的竖直平面分割为两部分,变为8个子空间,此8个子空间即为叶子节点。
常规的k-d tree的构建过程为:
对于构建过程,有两个优化点:
例子:采用常规的构建方式,以二维平面点(x,y)的集合(2,3),(5,4),(9,6),(4,7),(8,1),(7,2) 为例结合下图来说明k-d tree的构建过程:
如上算法所述,kd树的构建是一个递归过程,我们对左子空间和右子空间内的数据重复根节点的过程就可以得到一级子节点(5,4)和(9,6),同时将空间和数据集进一步细分,如此往复直到空间中只包含一个数据点。
如之前所述,kd树中,kd代表k-dimension,每个节点即为一个k维的点。每个非叶节点可以想象为一个分割超平面,用垂直于坐标轴的超平面将空间分为两个部分,这样递归的从根节点不停的划分,直到没有实例为止。经典的构造k-d tree的规则如下:
kd树的检索是KNN算法至关重要的一步,给定点p,查询数据集中与其距离最近点的过程即为最近邻搜索。
如在构建好的k-d tree上搜索(3,5)的最近邻时,对二维空间的最近邻搜索过程作分析。
首先从根节点(7,2)出发,将当前最近邻设为(7,2),对该k-d tree作深度优先遍历。
以(3,5)为圆心,其到(7,2)的距离为半径画圆(多维空间为超球面),可以看出(8,1)右侧的区域与该圆不相交,所以(8,1)的右子树全部忽略。
接着走到(7,2)左子树根节点(5,4),与原最近邻对比距离后,更新当前最近邻为(5,4)。
以(3,5)为圆心,其到(5,4)的距离为半径画圆,发现(7,2)右侧的区域与该圆不相交,忽略该侧所有节点,这样(7,2)的整个右子树被标记为已忽略。
遍历完(5,4)的左右叶子节点,发现与当前最优距离相等,不更新最近邻。所以(3,5)的最近邻为(5,4)。
举例:查询点(2.1,3.1)
星号表示要查询的点(2.1,3.1)。通过二叉搜索,顺着搜索路径很快就能找到最邻近的近似点,也就是叶子节点(2,3)。而找到的叶子节点并不一定就是最邻近的,最邻近肯定距离查询点更近,应该位于以查询点为圆心且通过叶子节点的圆域内。为了找到真正的最近邻,还需要进行相关的‘回溯'操作。也就是说,算法首先沿搜索路径反向查找是否有距离查询点更近的数据点。
举例:查询点(2,4.5)
一个复杂点了例子如查找点为(2,4.5),具体步骤依次如下:
上述两次实例表明,当查询点的邻域与分割超平面两侧空间交割时,需要查找另一侧子空间,导致检索过程复杂,效率下降。
一般来讲,最临近搜索只需要检测几个叶子结点即可,如下图所示:
但是,如果当实例点的分布比较糟糕时,几乎要遍历所有的结点,如下所示:
研究表明N个节点的K维k-d树搜索过程时间复杂度为: 。
同时,以上为了介绍方便,讨论的是二维或三维情形。但在实际的应用中,如SIFT特征矢量128维,SURF特征矢量64维,维度都比较大,直接利用k-d树快速检索(维数不超过20)的性能急剧下降,几乎接近贪婪线性扫描。假设数据集的维数为D,一般来说要求数据的规模N满足N»2D,才能达到高效的搜索。
Sklearn中有KDTree的实现,仅构建了一个二维空间的k-d tree,然后对其作k近邻搜索及指定半径的范围搜索。多维空间的检索,调用方式与此例相差无多。
❷ K-近邻算法(KNN)
简单地说,K-近邻算法采用测量不同特征值之间的距离方法进行分类。
欧氏距离是最常见的距离度量,衡量的是多维空间中各个点之间的绝对距离。公式如下:
身高、体重、鞋子尺码数据对应性别
导包,机器学习的算法KNN、数据鸢尾花
获取训练样本 datasets.load_iris()
画图研究前两个特征和分类之间的关系(二维散点图只能展示两个维度)
第二步预测数据:所预测的数据,自己创造,就是上面所显示图片的背景点
生成预测数据
对数据进行预测
ocr 光学字符识别(Optical Character Recognition) 我们先做一个基础班:识别数字
❸ 大数据算法:分类算法
KNN算法,即K近邻(K Nearest Neighbour)算法,是一种基本的分类算法。其主要原理是:对于一个需要分类的数据,将其和一组已经分类标注好的样本集合进行比较,得到距离最近的K个样本,K个样本最多归属的类别,就是这个需要分类数据的类别。下面我给你画了一个KNN算法的原理图。
图中,红蓝绿三种颜色的点为样本数据,分属三种类别 、 、 。对于待分类点 ,计算和它距离最近的5个点(即K为5),这5个点最多归属的类别为 (4个点归属 ,1个点归属 ),那么 的类别被分类为 。
KNN的算法流程也非常简单,请看下面的流程图。
KNN算法是一种非常简单实用的分类算法,可用于各种分类的场景,比如新闻分类、商品分类等,甚至可用于简单的文字识别。对于新闻分类,可以提前对若干新闻进行人工标注,标好新闻类别,计算好特征向量。对于一篇未分类的新闻,计算其特征向量后,跟所有已标注新闻进行距离计算,然后进一步利用KNN算法进行自动分类。
读到这你肯定会问,如何计算数据的距离呢?如何获得新闻的特征向量呢?
KNN算法的关键是要比较需要分类的数据与样本数据之间的距离,这在机器学习中通常的做法是:提取数据的特征值,根据特征值组成一个n维实数向量空间(这个空间也被称作特征空间),然后计算向量之间的空间距离。空间之间的距离计算方法有很多种,常用的有欧氏距离、余弦距离等。
对于数据 和 ,若其特征空间为n维实数向量空间 ,即 , ,则其欧氏距离计算公式为
这个欧式距离公式其实我们在初中的时候就学过,平面几何和立体几何里两个点之间的距离,也是用这个公式计算出来的,只是平面几何(二维几何)里的n=2,立体几何(三维几何)里的n=3,而机器学习需要面对的每个数据都可能有n维的维度,即每个数据有n个特征值。但是不管特征值n是多少,两个数据之间的空间距离的计算公式还是这个欧氏计算公式。大多数机器学习算法都需要计算数据之间的距离,因此掌握数据的距离计算公式是掌握机器学习算法的基础。
欧氏距离是最常用的数据计算公式,但是在文本数据以及用户评价数据的机器学习中,更常用的距离计算方法是余弦相似度。
余弦相似度的值越接近1表示其越相似,越接近0表示其差异越大,使用余弦相似度可以消除数据的某些冗余信息,某些情况下更贴近数据的本质。我举个简单的例子,比如两篇文章的特征值都是:“大数据”“机器学习”和“极客时间”,A文章的特征向量为(3, 3, 3),即这三个词出现次数都是3;B文章的特征向量为(6, 6, 6),即这三个词出现次数都是6。如果光看特征向量,这两个向量差别很大,如果用欧氏距离计算确实也很大,但是这两篇文章其实非常相似,只是篇幅不同而已,它们的余弦相似度为1,表示非常相似。
余弦相似度其实是计算向量的夹角,而欧氏距离公式是计算空间距离。余弦相似度更关注数据的相似性,比如两个用户给两件商品的打分分别是(3, 3)和(4, 4),那么两个用户对两件商品的喜好是相似的,这种情况下,余弦相似度比欧氏距离更合理。
我们知道了机器学习的算法需要计算距离,而计算距离需要还知道数据的特征向量,因此提取数据的特征向量是机器学习工程师们的重要工作,有时候甚至是最重要的工作。不同的数据以及不同的应用场景需要提取不同的特征值,我们以比较常见的文本数据为例,看看如何提取文本特征向量。
文本数据的特征值就是提取文本关键词,TF-IDF算法是比较常用且直观的一种文本关键词提取算法。这种算法是由TF和IDF两部分构成。
TF是词频(Term Frequency),表示某个单词在文档中出现的频率,一个单词在一个文档中出现的越频繁,TF值越高。
词频:
IDF是逆文档频率(Inverse Document Frequency),表示这个单词在所有文档中的稀缺程度,越少文档出现这个词,IDF值越高。
逆文档频率:
TF与IDF的乘积就是TF-IDF。
所以如果一个词在某一个文档中频繁出现,但在所有文档中却很少出现,那么这个词很可能就是这个文档的关键词。比如一篇关于原子能的技术文章,“核裂变”“放射性”“半衰期”等词汇会在这篇文档中频繁出现,即TF很高;但是在所有文档中出现的频率却比较低,即IDF也比较高。因此这几个词的TF-IDF值就会很高,就可能是这篇文档的关键词。如果这是一篇关于中国原子能的文章,也许“中国”这个词也会频繁出现,即TF也很高,但是“中国”也在很多文档中出现,那么IDF就会比较低,最后“中国”这个词的TF-IDF就很低,不会成为这个文档的关键词。
提取出关键词以后,就可以利用关键词的词频构造特征向量,比如上面例子关于原子能的文章,“核裂变”“放射性”“半衰期”这三个词是特征值,分别出现次数为12、9、4。那么这篇文章的特征向量就是(12, 9, 4),再利用前面提到的空间距离计算公式计算与其他文档的距离,结合KNN算法就可以实现文档的自动分类。
贝叶斯公式是一种基于条件概率的分类算法,如果我们已经知道A和B的发生概率,并且知道了B发生情况下A发生的概率,可以用贝叶斯公式计算A发生的情况下B发生的概率。事实上,我们可以根据A的情况,即输入数据,判断B的概率,即B的可能性,进而进行分类。
举个例子:假设一所学校里男生占60%,女生占40%。男生总是穿长裤,女生则一半穿长裤一半穿裙子。假设你走在校园中,迎面走来一个穿长裤的学生,你能够推断出这个穿长裤学生是男生的概率是多少吗?
答案是75%,具体算法是:
这个算法就利用了贝叶斯公式,贝叶斯公式的写法是:
意思是A发生的条件下B发生的概率,等于B发生的条件下A发生的概率,乘以B发生的概率,除以A发生的概率。还是上面这个例子,如果我问你迎面走来穿裙子的学生是女生的概率是多少。同样带入贝叶斯公式,可以计算出是女生的概率为100%。其实这个结果我们根据常识也能推断出来,但是很多时候,常识受各种因素的干扰,会出现偏差。比如有人看到一篇博士生给初中学历老板打工的新闻,就感叹读书无用。事实上,只是少见多怪,样本量太少而已。而大量数据的统计规律则能准确反映事物的分类概率。
贝叶斯分类的一个典型的应用场合是垃圾邮件分类,通过对样本邮件的统计,我们知道每个词在邮件中出现的概率 ,我们也知道正常邮件概率 和垃圾邮件的概率 ,还可以统计出垃圾邮件中各个词的出现概率 ,那么现在一封新邮件到来,我们就可以根据邮件中出现的词,计算 ,即得到这些词出现情况下,邮件为垃圾邮件的概率,进而判断邮件是否为垃圾邮件。
现实中,贝叶斯公式等号右边的概率,我们可以通过对大数据的统计获得,当有新的数据到来的时候,我们就可以带入上面的贝叶斯公式计算其概率。而如果我们设定概率超过某个值就认为其会发生,那么我们就对这个数据进行了分类和预测,具体过程如下图所示。
训练样本就是我们的原始数据,有时候原始数据并不包含我们想要计算的维度数据,比如我们想用贝叶斯公式自动分类垃圾邮件,那么首先要对原始邮件进行标注,需要标注哪些邮件是正常邮件、哪些邮件是垃圾邮件。这一类需要对数据进行标注才能进行的机器学习训练也叫作有监督的机器学习。
❹ 简单数字识别(knn算法)
knn算法,即k-NearestNeighbor,后面的nn意思是最近邻的意思,前面的k是前k个的意思,就是找到前k个离得最近的元素
离得最近这个词具体实现有很多种,我使用的是欧式几何中的距离公式
二维中两点x(x1,y1),y(x2,y2)间距离公式为sqrt( (x1-x2)^2+(y1-y2)^2 )
推广到n维就是
x(x1,x2, … ,xn),y(y1,y2, … ,yn)
sqrt [ ∑( x[i] - y[i] )^2 ] (i=1,2, … ,n)
knn算法是要计算距离的,也就是数字之间的运算,而图像是png,jpg这种格式,并不是数字也不能直接参与运算,所以我们需要进行一下转换
如图所示一个数字8,首先要确定的是这一步我做的是一个最简单的转换,因为我假定背景和图之间是没有杂物的,而且整个图只有一个数字(0-9)如果遇到其他情况,比如背景色不纯或者有其他干扰图像需要重新设计转换函数
接下来就是最简单的转换,将图片白色部分(背景)变0,有图像的部分变1。转换后的大小要合适,太小会影响识别准确度,太大会增加计算量。所以我用的是书上的32*32,转换后结果如图所示
这样一来,图片就变成了能进行计算的数字了。
接下来我们需要创建一个库,这个库里面存着0-9这些数字的各种类似上图的实例。因为我们待识别的图像要进行对比,选出前k个最近的,比较的对象就是我们的库。假定库中有0-9十个数字,每个数字各有100个这种由0和1表示的实例,那么我们就有了一共1000个实例。
最后一步就是进行对比,利用开头说的欧式几何距离计算公式,首先这个32*32的方阵要转换成一个1*1024的1024维坐标表示,然后拿这个待识别的图像和库中的1000个实例进行距离计算,选出前k个距离最近的。比如50个,这50个里面出现次数最多的数字除以50就是结果数字的概率。比如50个里面数字8出现40次,那么待识别数字是8的可能性就是40/50 = 80%
个人理解:
只能识别单个数字,背景不能有干扰。如果想多数字识别或者背景有干扰需要针对具体情况考虑具体的图像转01的方法。
数字识别非常依赖库中的图像,库中的图像的样子严重影响图像的识别(因为我们是和库中的一一对比找出距离最近的前k个),所以数字的粗细,高低,胖瘦等待都是决定性因素,建库时一定全面考虑数字的可能样子
计算量比较大,待识别图像要和库中所有实例一一计算,如果使用32*32,就已经是1024维了。如果库中有1000个,那就是1024维向量之间的1000次计算,图像更清晰,库更丰富只会使计算量更大
对于其他可以直接计算距离的数值型问题,可以用欧式距离,也可以用其他能代表距离的计算公式,对于非数值型的问题需要进行合适的转换,转换方式很重要,我觉得首先信息不能丢失,其次要精确不能模糊,要实现图片转换前后是一对一的关系
参考资料:机器学习实战 [美] Peter Harrington 人民邮电出版社
python源码
import numpy
import os
from PIL import Image
import heapq
from collections import Counter
def pictureconvert(filename1,filename2,size=(32,32)):
#filename1待识别图像,filename2 待识别图像转换为01txt文件输出,size图像大小,默认32*32
image_file = Image.open(filename1)
image_file = image_file.resize(size)
width,height = image_file.size
f1 = open(filename1,'r')
f2 = open(filename2,'w')
for i in range(height):
for j in range(width):
pixel = image_file.getpixel((j,i))
pixel = pixel[0] + pixel[1] + pixel[2]
if(pixel == 0):
pixel = 0
elif(pixel != 765 and pixel != 0):
pixel = 1
# 0代表黑色(无图像),255代表白色(有图像)
# 0/255 = 0,255/255 = 1
f2.write(str(pixel))
if(j == width-1):
f2.write('\n')
f1.close()
f2.close()
def imgvector(filename):
#filename将待识别图像的01txt文件转换为向量
vector = numpy.zeros((1,1024),numpy.int)
with open(filename) as f:
for i in range(0,32):
linestr = f.readline()
for j in range(0,32):
vector[0,32*i+j] = int(linestr[j])
return vector
def compare(filename1,filename2):
#compare直接读取资源库识别
#filename1资源库目录,filename2 待识别图像01txt文档路径
trainingfilelist = os.listdir(filename1)
m = len(trainingfilelist)
labelvector = []
trainingmatrix = numpy.zeros((m, 1024), numpy.int8)
for i in range(0,m):
filenamestr = trainingfilelist[i]
filestr = filenamestr.split('.')[0]
classnumber = int(filestr.split('_')[0])
labelvector.append(classnumber)
trainingmatrix[i,:] = imgvector(filename1 + '/' + filenamestr)
textvector = imgvector(filename2)
resultdistance = numpy.zeros((1,m))
result = []
for i in range(0,m):
resultdistance[0,i] = numpy.vdot(textvector[0],trainingmatrix[i])
resultindices = heapq.nlargest(50,range(0,len(resultdistance[0])),resultdistance[0].take)
for i in resultindices:
result.append(labelvector[i])
number = Counter(result).most_common(1)
print('此数字是',number[0][0],'的可能性是','%.2f%%' % ((number[0][1]/len(result))*100))
def distinguish(filename1,filename2,filename3,size=(32,32)):
# filename1 png,jpg等格式原始图像路径,filename2 原始图像转换成01txt文件路径,filename3 资源库路径
pictureconvert(filename1,filename2,size)
compare(filename3,filename2)
url1 = "/Users/wang/Desktop/number.png"
url2 = "/Users/wang/Desktop/number.txt"
traininglibrary = "/Users/wang/Documents/trainingDigits"
distinguish(url1,url2,traininglibrary)