Python 使用SciKit学习和SciPy的K近邻构建/搜索速度
我有一个大的二维点集,希望能够快速查询该集,以查找二维空间中任意点的k近邻。由于它是低维的,KD树似乎是一种很好的方法。我的初始数据集很少更新,因此查询点的时间对我来说应该比构建时间更重要。但是,每次运行程序时,我都需要重新加载对象,因此我还需要一个可以快速保存和重新加载的结构 两个现成的选择是SciPy和SciKit学习中的KDTree结构。下面,我将介绍其中的两个,以了解在大量列表长度范围内的构建速度和查询速度。我还对SciKit学习结构进行了pickle处理,并显示了从pickle中重新加载对象的时间。在一个图表中对这些进行了比较,下面包含了用于生成计时的代码 正如我在图中所示,从pickle加载要比从头开始构建快半个数量级,这表明KDTree适合我的用例(即频繁重新加载但很少重新构建) 用于比较生成时间的代码:Python 使用SciKit学习和SciPy的K近邻构建/搜索速度,python,scipy,scikit-learn,nearest-neighbor,kdtree,Python,Scipy,Scikit Learn,Nearest Neighbor,Kdtree,我有一个大的二维点集,希望能够快速查询该集,以查找二维空间中任意点的k近邻。由于它是低维的,KD树似乎是一种很好的方法。我的初始数据集很少更新,因此查询点的时间对我来说应该比构建时间更重要。但是,每次运行程序时,我都需要重新加载对象,因此我还需要一个可以快速保存和重新加载的结构 两个现成的选择是SciPy和SciKit学习中的KDTree结构。下面,我将介绍其中的两个,以了解在大量列表长度范围内的构建速度和查询速度。我还对SciKit学习结构进行了pickle处理,并显示了从pickle中重新加
# Profiling the building time for the two KD-tree structures and re-loading from a pickle
import math, timeit, pickle, sklearn.neighbors
the_lengths = [100, 1000, 10000, 100000, 1000000]
theSciPyBuildTime = []
theSklBuildTime = []
theRebuildTime = []
for length in the_lengths:
dim = 5*int(math.sqrt(length))
nTimes = 50
from random import randint
listOfRandom2DPoints = [ [randint(0,dim),randint(0,dim)] for x in range(length)]
setup = """import scipy.spatial
import sklearn.neighbors
length = """ + str(length) + """
dim = """ + str(dim) + """
from random import randint
listOfRandom2DPoints = [ [randint(0,dim),randint(0,dim)] for x in range(length)]"""
theSciPyBuildTime.append( timeit.timeit('scipy.spatial.KDTree(listOfRandom2DPoints, leafsize=20)', setup=setup, number=nTimes)/nTimes )
theSklBuildTime.append( timeit.timeit('sklearn.neighbors.KDTree(listOfRandom2DPoints, leaf_size=20)', setup=setup, number=nTimes)/nTimes )
theTreeSkl = sklearn.neighbors.KDTree(listOfRandom2DPoints, leaf_size=20, metric='euclidean')
f = open('temp.pkl','w')
temp = pickle.dumps(theTreeSkl)
theRebuildTime.append( timeit.timeit('pickle.loads(temp)', 'from __main__ import pickle,temp', number=nTimes)/nTimes )
比较查询时间的代码:
# Profiling the query time for the two KD-tree structures
import scipy.spatial, sklearn.neighbors
the_lengths = [100, 1000, 10000, 100000, 1000000, 10000000]
theSciPyQueryTime = []
theSklQueryTime = []
for length in the_lengths:
dim = 5*int(math.sqrt(length))
nTimes = 50
listOfRandom2DPoints = [ [randint(0,dim),randint(0,dim)] for x in range(length)]
setup = """from __main__ import sciPiTree,sklTree
from random import randint
length = """ + str(length) + """
randPoint = [randint(0,""" + str(dim) + """),randint(0,""" + str(dim) + """)]"""
sciPiTree = scipy.spatial.KDTree(listOfRandom2DPoints, leafsize=20)
sklTree = sklearn.neighbors.KDTree(listOfRandom2DPoints, leaf_size=20)
theSciPyQueryTime.append( timeit.timeit('sciPiTree.query(randPoint,10)', setup=setup, number=nTimes)/nTimes )
theSklQueryTime.append( timeit.timeit('sklTree.query(randPoint,10)', setup=setup, number=nTimes)/nTimes )
问题:
在回答问题之前,我想指出,当您有一个使用大量数字的程序时,您应该始终使用
numpy.array
from来存储此类数据。我不知道Python的哪个版本,您正在使用哪个版本,但我使用的是Python 3.7.3、scikit learn 0.21.3和SciPy 1.3.0。当我运行代码来比较构建时间时,我得到了AttributeError:'list'对象没有属性'size'
。此错误表示listlistOfRandom2DPoints
没有属性size
。问题是sklearn.neights.KDTree
需要numpy.array
,该数组具有属性size
。类scipy.spatial.KDTree
用于Python列表,但正如您在中看到的,第一行是self.data=np.asarray(data)
,这意味着数据将转换为numpy.array
因此,我改变了你的台词:
from random import randint
listOfRandom2DPoints = [ [randint(0,dim),randint(0,dim)] for x in range(length)]
致:
(此更改不影响速度比较,因为在设置代码中进行了更改。)
现在回答您的问题:
sklearn.neights.KDTree
是在()中实现的,而scipy.spatial.KDTree
是用纯Python代码()编写的
(如果你不知道Cython是什么,一个过于简单的解释可能会
Cython使用Python和main编写C代码成为可能
这样做的原因是C比Python快得多)
SciPy库在CythonSciPy.spatial.cKDTree
()中也有实现,它的工作原理与SciPy.spatial.KDTree
相同,如果比较sklearn.neights.KDTree
和SciPy.spatial.cKDTree
的构建时间:
timeit.timeit('scipy.spatial.cKDTree(npListOfRandom2DPoints, leafsize=20)', setup=setup, number=nTimes)
timeit.timeit('sklearn.neighbors.KDTree(npListOfRandom2DPoints, leaf_size=20)', setup=setup, number=nTimes)
构建时间非常相似,当我运行代码时,scipy.spatial.cKDTree
快了一点(大约20%)
由于查询时间的情况非常相似,scipy.spatial.KDTree
(纯Python实现)大约比sklearn.neights.KDTree
(Cython实现)慢十倍,而scipy.spatial.cKDTree
(Cython实现)大约和sklearn.neights.KDTree
一样快。我测试了N=10000000的查询次数,得到了与您相同的结果。查询时间保持不变,与N无关(这意味着scipy.spatial.KDTree
的查询时间对于N=1000和N=1000000是相同的,对于sklearn.neights.KDTree
和scipy.spatial.cKDTree
的查询时间也是相同的)。这是因为查询(搜索)时间复杂度为O(logN),即使对于N=1000000,logN也非常小,因此差异太小,无法测量sklearn.neights.KDTree
(\uuu init\uuu
类的方法)的构建算法的时间复杂度为O(KNlogN)(),因此在您的例子中,它将是O(2NlogN),实际上是O(NlogN)。基于sklearn.neights.KDTree
和scipy.space.cKDTree
非常相似的构建时间,我假设scipy.space.cKDTree
的构建算法也具有O(NlogN)的时间复杂度。我不是最近邻搜索算法的专家,但基于一些在线搜索,我想说,对于低维最近邻搜索算法,这是尽可能快的。如果你去的话,你会看到有和。是精确的方法,它是的子类型。在所有空间划分方法中(仅适用于最近的nei的快速精确方法
timeit.timeit('scipy.spatial.cKDTree(npListOfRandom2DPoints, leafsize=20)', setup=setup, number=nTimes)
timeit.timeit('sklearn.neighbors.KDTree(npListOfRandom2DPoints, leaf_size=20)', setup=setup, number=nTimes)