【发布时间】:2019-10-06 03:48:14
【问题描述】:
在收集一些样本的循环中,我需要不时地获取一些关于它们的排序索引的统计信息,argsort 正好返回我需要的信息。但是,每次迭代只添加一个样本,并且不断将整个样本数组传递给 argsort 函数是一种巨大的资源浪费,特别是因为样本数组非常庞大。难道没有等效于 argsort 的增量高效技术吗?
我相信一个有效的增量 argsort 函数可以通过维护一个有序的样本列表来实现,一旦新样本到达,该列表可以是 searched 用于正确的 insertion 索引。然后可以利用这些索引来维护样本列表的顺序以及生成增量的类似 argsort 的所需输出。
到目前为止,我已经使用了@Divakar 的searchsorted2d 函数,稍作修改以获得插入索引,并构建了一些例程,如果在每次插入样本后调用它可以获得所需的输出(b = 1)。
然而,这是低效的,我想在收集第 k 个样本后调用例程(例如b = 10)。 在批量插入的情况下,searchsorted2d 似乎返回了不正确的索引,这就是我停止了!
import time
import numpy as np
# By Divakar
# See https://stackoverflow.com/a/40588862
def searchsorted2d(a, b):
m, n = a.shape
max_num = np.maximum(a.max() - a.min(), b.max() - b.min()) + 1
r = max_num * np.arange(m)[:,np.newaxis]
p = np.searchsorted((a + r).ravel(), (b + r).ravel()).reshape(b.shape)
return p #- n * (np.arange(m)[:,np.newaxis])
# The following works with batch size b = 1,
# but that is not efficient ...
# Can we make it work for any b > 0 value?
class incremental(object):
def __init__(self, shape):
# Express each row offset
self.ranks_offset = np.tile(np.arange(shape[1]).reshape(1, -1),
(shape[0], 1))
# Storage for sorted samples
self.a_sorted = np.empty((shape[0], 0))
# Storage for sort indices
self.a_ranks = np.empty((shape[0], 0), np.int)
def argsort(self, a):
if self.a_sorted.shape[1] == 0: # Use np.argsort for initialization
self.a_ranks = a.argsort(axis=1)
self.a_sorted = np.take_along_axis(a, self.a_ranks, 1)
else: # In later itterations,
# searchsorted the input increment
indices = searchsorted2d(self.a_sorted, a)
# insert the stack pos to track the sorting indices
self.a_ranks = np.insert(self.a_ranks, indices.ravel(),
self.ranks_offset.ravel() +
self.a_ranks.shape[1]).reshape((n, -1))
# insert the increments to maintain a sorted input array
self.a_sorted = np.insert(self.a_sorted, indices.ravel(),
a.ravel()).reshape((n, -1))
return self.a_ranks
M = 1000 # number of iterations
n = 16 # vector size
b = 10 # vectors batch size
# Storage for samples
samples = np.zeros((n, M)) * np.nan
# The proposed approach
inc = incremental((n, b))
c = 0 # iterations counter
tick = time.time()
while c < M:
if c % b == 0: # Perform batch computations
#sample_ranks = samples[:,:c].argsort(axis=1)
sample_ranks = inc.argsort(samples[:,max(0,c-b):c]) # Incremental argsort
######################################################
# Utilize sample_ranks in some magic statistics here #
######################################################
samples[:,c] = np.random.rand(n) # collect a sample
c += 1 # increment the counter
tock = time.time()
last = ((c-1) // b) * b
sample_ranks_GT = samples[:,:last].argsort(axis=1) # Ground truth
print('Compatibility: {0:.1f}%'.format(
100 * np.count_nonzero(sample_ranks == sample_ranks_GT) / sample_ranks.size))
print('Elapsed time: {0:.1f}ms'.format(
(tock - tick) * 1000))
我希望与 argsort 函数 100% 兼容,但它需要比调用 argsort 更有效。至于增量方法的执行时间,对于给定的示例,似乎 15 毫秒左右应该绰绰有余。 到目前为止,任何一种探索的技术都只能满足这两个条件中的一个。
长话短说,上面显示的算法似乎是order-statistic tree 的一种变体,用于估计数据等级,但是当批量添加样本时(
b > 1)它无法这样做。到目前为止,它仅在一个一个插入样本时有效(b = 1)。但是,每次调用insert 时都会复制数组,这会导致巨大的开销并形成瓶颈,因此应批量添加样本而不是单独添加样本。
能否介绍一下更高效的增量argsort算法,或者至少弄清楚上面的如何支持批量插入(b > 1)?
如果你选择从我停止的地方开始,那么问题可以归结为修复以下快照中的错误:
import numpy as np
# By Divakar
# See https://stackoverflow.com/a/40588862
def searchsorted2d(a, b):
m, n = a.shape
max_num = np.maximum(a.max() - a.min(), b.max() - b.min()) + 1
r = max_num * np.arange(m)[:,np.newaxis]
p = np.searchsorted((a + r).ravel(), (b + r).ravel()).reshape(b.shape)
# It seems the bug is around here...
#return p - b.shape[0] * np.arange(b.shape[1])[np.newaxis]
#return p - b.shape[1] * np.arange(b.shape[0])[:,np.newaxis]
return p
n = 16 # vector size
b = 2 # vectors batch size
a = np.random.rand(n, 1) # Samples array
a_ranks = a.argsort(axis=1) # Initial ranks
a_sorted = np.take_along_axis(a, a_ranks, 1) # Initial sorted array
new_data = np.random.rand(n, b) # New block to append into the samples array
a = np.hstack((a, new_data)) #Append new block
indices = searchsorted2d(a_sorted, new_data) # Compute insertion indices
ranks_offset = np.tile(np.arange(b).reshape(1, -1), (a_ranks.shape[0], 1)) + a_ranks.shape[1] # Ranks to insert
a_ranks = np.insert(a_ranks, indices.ravel(), ranks_offset.ravel()).reshape((n, -1)) # Insert ransk according to their indices
a_ransk_GT = a.argsort(axis=1) # Ranks ground truth
mask = (a_ranks == a_ransk_GT)
print(mask) #Why they are not all True?
assert(np.all(mask)), 'Oops!' #This should not fail, but it does :(
似乎批量插入比我最初的想法更复杂,而且
searchsorted2d 不应该受到指责。以排序数组a = [ 1, 2, 5 ] 为例,要插入两个新元素block = [3, 4]。如果我们迭代并插入,那么np.searchsorted(a, block[i]) 将返回[2] 和[3],这样就可以了。然而,如果我们调用np.searchsorted(a, block)(期望的行为——相当于没有插入的迭代),我们会得到[2, 2]。这对于实现增量argsort 是有问题的,因为即使np.searchsorted(a, block[::-1]) 也会导致相同的结果。任何想法?
【问题讨论】:
-
对于您的实际用例,哪些步骤是瓶颈(如果您进行了分析)?
-
这是order-statistic tree解决的标准问题。
-
@Divakar
insert是b = 1案例的瓶颈,其次是searchsorted2d。对于任何b > 1,结果都是不正确的! -
@DavisHerring 我找不到给定技术的 Python 实现。如果您能指出这样的实现,我将不胜感激。
-
@Sahloul:询问现有实现是题外话; “写一个顺序统计树”在这里就足够了。
标签: python arrays numpy sorting search