编程语言
首页 > 编程语言> > python – 在相同长度的1d numpy数组上评估1-d函数数组的高效算法

python – 在相同长度的1d numpy数组上评估1-d函数数组的高效算法

作者:互联网

我有一个(大)长度为N的k个不同函数的数组,以及一个长度为N的abcissa数组.我想评估abcissa中的函数返回一个长度为N的纵坐标数组,而且关键的是,我需要非常快速地完成它.

我在调用np.where时尝试了以下循环,这太慢了:

创建一些假数据来说明问题:

def trivial_functional(i): return lambda x : i*x
k = 250
func_table = [trivial_functional(j) for j in range(k)]
func_table = np.array(func_table) # possibly unnecessary

我们有250个不同功能的表格.现在我创建一个包含这些函数的许多重复条目的大型数组,以及一组应该评估这些函数的相同长度的点.

Npts = 1e6
abcissa_array = np.random.random(Npts)
function_indices = np.random.random_integers(0,len(func_table)-1,Npts)
func_array = func_table[function_indices]

最后,循环遍历数据使用的每个函数,并在相关点集上对其进行评估:

desired_output = np.zeros(Npts)
for func_index in set(function_indices):
    idx = np.where(function_indices==func_index)[0]
    desired_output[idx] = func_table[func_index](abcissa_array[idx])

这个循环在我的笔记本电脑上花了大约0.35秒,这是我的代码中最大的瓶颈.

有没有人看到如何避免对np.where的盲查询调用?是否巧妙地使用了numba来加速这种循环?

解决方法:

这与你的(优秀的!)自我回答几乎完全相同,但有点不那么严格.在我的机器上看起来似乎稍微快一点 – 基于粗略的test大约30ms.

def apply_indexed_fast(array, func_indices, func_table):
    func_argsort = func_indices.argsort()
    func_ranges = list(np.searchsorted(func_indices[func_argsort], range(len(func_table))))
    func_ranges.append(None)
    out = np.zeros_like(array)
    for f, start, end in zip(func_table, func_ranges, func_ranges[1:]):
        ix = func_argsort[start:end]
        out[ix] = f(array[ix])
    return out

与您的一样,这会将一系列argsort索引拆分为块,每个块对应于func_table中的一个函数.然后,它使用每个块为其相应的函数选择输入和输出索引.为了确定块边界,它使用np.searchsorted而不是np.unique – 其中searchsorted(a,b)可以被认为是二进制搜索算法,它返回第一个值的索引等于或大于b中给定的值或值.

然后zip函数简单地并行迭代它的参数,从每个参数返回一个项目,一起收集在一个元组中,并将这些项目串联到一个列表中. (所以zip([1,2,3],[‘a’,’b’,’c’],[‘b’,’c’,’d’])返回[(1,’a’,’ b’),(2,’b’,’c’),(3,’c’,’d’)].)这与for语句的内置“解包”这些元组的能力一起允许一种简洁而富有表现力的方法,可以并行迭代多个序列.

在这种情况下,我用它来迭代func_tables中的函数以及两个不同步的func_ranges副本.这可以确保end变量中func_ranges的项始终比start变量中的项提前一步.通过在func_ranges中附加None,我确保优雅地处理最终的块 – 当任何一个参数用完项时,zip停止,这会切断序列中的最终值.方便的是,None值也可以作为开放式切片索引!

执行相同操作的另一个技巧需要更多行,但内存开销较低,尤其是与itertools等效的zip,izip一起使用时:

range_iter_a = iter(func_ranges)   # create generators that iterate over the 
range_iter_b = iter(func_ranges)   # values in `func_ranges` without making copies
next(range_iter_b, None)           # advance the second generator by one
for f, start, end in itertools.izip(func_table, range_iter_a, range_iter_b):
    ...

但是,这些低开销的基于生成器的方法有时可能比香草列表慢一些.另请注意,在Python 3中,zip的行为更像izip.

标签:python,numpy,scientific-computing,performance,numba
来源: https://codeday.me/bug/20190528/1171457.html