[英]Recursive function to apply any function to N arrays of any length to form one jagged multidimensional array of N dimensions
给定的N个输入阵列,所有任何长度的,我想能够函数应用到每个阵列的每个组合的所有的组合。
例如:
给定输入数组:
[1, 2] [3, 4, 5] [6, 7, 8, 9]
以及一个返回 N 个元素的乘积的函数
我希望能够对这些元素的每个组合应用一个函数。 在这种情况下,它会产生一个 3 维数组,长度分别为 2、3 和 4。
结果数组如下所示:
[
[
[18, 21, 24, 27],
[24, 28, 32, 36],
[30, 35, 40, 45]
],
[
[36, 42, 48, 54],
[48, 56, 64, 72],
[60, 70, 80, 90]
]
]
你可以通过广播来做到这一点:
import numpy as np
a = np.array([1, 2, 3])
b = np.array([4, 5])
c = a[None, ...] * b[..., None]
print(c)
输出:
[[ 4 8 12]
[ 5 10 15]]
这可以通过制作适当的切片传递给操作数来轻松概括。
这种概括的实现可以是:
import numpy as np
def apply_multi_broadcast_1d(func, dim1_arrs):
n = len(dim1_arrs)
iter_dim1_arrs = iter(dim1_arrs)
slicing = tuple(
slice(None) if j == 0 else None
for j in range(n))
result = next(iter_dim1_arrs)[slicing]
for i, dim1_arr in enumerate(iter_dim1_arrs, 1):
slicing = tuple(
slice(None) if j == i else None
for j in range(n))
result = func(result, dim1_arr[slicing])
return result
dim1_arrs = [np.arange(1, n + 1) for n in range(2, 5)]
print(dim1_arrs)
# [array([1, 2]), array([1, 2, 3]), array([1, 2, 3, 4])]
arr = apply_multi_broadcast_1d(lambda x, y: x * y, dim1_arrs)
print(arr.shape)
# (2, 3, 4)
print(arr)
# [[[ 1 2 3 4]
# [ 2 4 6 8]
# [ 3 6 9 12]]
# [[ 2 4 6 8]
# [ 4 8 12 16]
# [ 6 12 18 24]]]
这里不需要递归,我不确定它有什么好处。
另一种方法是从 Python 函数(如@TlsChris's answer 中提出的)生成一个np.ufunc
并使用其np.ufunc.outer()
方法:
import numpy as np
def apply_multi_outer(func, dim1_arrs):
ufunc = np.frompyfunc(func, 2, 1)
iter_dim1_arrs = iter(dim1_arrs)
result = next(iter_dim1_arrs)
for dim1_arr in iter_dim1_arrs:
result = ufunc.outer(result, dim1_arr)
return result
虽然这会给出相同的结果(对于一维数组),但它比广播方法慢(根据输入大小从轻微到显着)。
此外,虽然apply_multi_broadcast_1d()
仅限于 1-dim 输入,但apply_multi_outer()
也适用于更高维度的输入数组。 广播方法可以很容易地适应更高维度的输入,如下所示。
apply_multi_broadcast_1d()
对 N-dim 输入的概括,包括广播与功能应用程序的分离,如下:
import numpy as np
def multi_broadcast(arrs):
for i, arr in enumerate(arrs):
yield arr[tuple(
slice(None) if j == i else None
for j, arr in enumerate(arrs) for d in arr.shape)]
def apply_multi_broadcast(func, arrs):
gen_arrs = multi_broadcast(arrs)
result = next(gen_arrs)
for i, arr in enumerate(gen_arrs, 1):
result = func(result, arr)
return result
三者的基准测试表明apply_multi_broadcast()
比apply_multi_broadcast_1d()
稍慢,但比apply_multi_outer()
快:
def f(x, y):
return x * y
dim1_arrs = [np.arange(1, n + 1) for n in range(2, 5)]
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast_1d(f, dim1_arrs)))
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast(f, dim1_arrs)))
# True
# True
%timeit apply_multi_broadcast_1d(f, dim1_arrs)
# 100000 loops, best of 3: 7.76 µs per loop
%timeit apply_multi_outer(f, dim1_arrs)
# 100000 loops, best of 3: 9.46 µs per loop
%timeit apply_multi_broadcast(f, dim1_arrs)
# 100000 loops, best of 3: 8.63 µs per loop
dim1_arrs = [np.arange(1, n + 1) for n in range(10, 16)]
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast_1d(f, dim1_arrs)))
print(np.all(apply_multi_outer(f, dim1_arrs) == apply_multi_broadcast(f, dim1_arrs)))
# True
# True
%timeit apply_multi_broadcast_1d(f, dim1_arrs)
# 100 loops, best of 3: 10 ms per loop
%timeit apply_multi_outer(f, dim1_arrs)
# 1 loop, best of 3: 538 ms per loop
%timeit apply_multi_broadcast(f, dim1_arrs)
# 100 loops, best of 3: 10.1 ms per loop
让我们给定 N 个数组,其大小为 n1, n2, ..., nN。 然后,我们可以将这个问题分解为两个数组的 (N-1) 次计算。 在第一次计算中,计算 n1、n2 的乘积。 让输出为result1。 在第二次计算中,计算 result1、n3 的乘积。 让输出为result2。 . . 在最后一次计算中,计算 result(N-2) 的乘积,nN。 让输出为result(N-1)。
你会知道 result1 的大小是 n2 _ n1,result2 的大小是 n3 _ n2 _ n1。 . . 如您所见,result(N-1) 的大小为 n(N) _ n(N-1) _ ... _ n2 * n1。
现在让我们得到两个数组:result(k-1) 和 arr(k)。 然后我们应该从 result(k-1) 和 arr(k) 中得到每个元素的乘积。 原因 result(k-1) 的大小为 n(k-1) _ n(k-2) _ ... _ n1,arr(k) 的大小为 n(k),输出数组 (result(k) ) 的大小应该为 n(k) _ n(k-1) _ ... _ n1。 这意味着这个问题的解决方案是转置 n(k) 和结果 (k-1) 的点积。 因此,该功能应如下所示。
productOfTwoArrays = lambda arr1, arr2: np.dot(arr2.T, arr1)
所以现在我们解决了第一个问题。 剩下的就是将其应用于所有 N 个数组。 所以解决方案可能是迭代的。 让输入数组有 N 个数组。
def productOfNArrays(Narray: list) -> list:
result = Narray[0]
N = len(Narray)
for idx in range(1, N):
result = productOfTwoArrays(result, Narray[idx])
return result
整个代码可能在下面。
def productOfNArrays(Narray: list) -> list:
import numpy as np
productOfTwoArrays = lambda arr1, arr2: np.dot(arr2.T, arr1)
result = Narray[0]
N = len(Narray)
for idx in range(1, N):
result = productOfTwoArrays(result, Narray[idx])
return result
使用 np.frompyfunc 创建所需函数的 ufunc 的另一种方法。 这是使用 ufuncs .outer 方法对 n 个参数应用 n-1 次。
import numpy as np
def testfunc( a, b):
return a*(a+b) + b*b
def apply_func( func, *args, dtype = np.float ):
""" Apply func sequentially to the args
"""
u_func = np.frompyfunc( func, 2, 1) # Create a ufunc from func
result = np.array(args[0])
for vec in args[1:]:
result = u_func.outer( result, vec ) # apply the outer method of the ufunc
# This returns arrays of object type.
return np.array(result, dtype = dtype) # Convert to type and return the result
apply_func(lambda x,y: x*y, [1,2], [3,4,5],[6,7,8,9] )
# array([[[18., 21., 24., 27.],
# [24., 28., 32., 36.],
# [30., 35., 40., 45.]],
# [[36., 42., 48., 54.],
# [48., 56., 64., 72.],
# [60., 70., 80., 90.]]])
apply_func( testfunc, [1,2], [3,4,5],[6,7,8,9])
# array([[[ 283., 309., 337., 367.],
# [ 603., 637., 673., 711.],
# [1183., 1227., 1273., 1321.]],
# [[ 511., 543., 577., 613.],
# [ 988., 1029., 1072., 1117.],
# [1791., 1843., 1897., 1953.]]])
根据我的经验,在大多数情况下,我们并不是在寻找真正通用的解决方案。 当然,这样一个通用的解决方案看起来优雅和可取,因为如果我们的需求发生变化,它本质上能够适应——就像在编写研究代码时经常做的那样。
然而,我们通常实际上是在寻找一种易于理解且易于修改的解决方案,以防我们的需求发生变化。
一种这样的解决方案是使用np.einsum()
:
import numpy as np
a = np.array([1, 2])
b = np.array([3, 4, 5])
c = np.array([6, 7, 8, 9])
np.einsum('a,b,c->abc', a, b, c)
# array([[[18, 21, 24, 27],
# [24, 28, 32, 36],
# [30, 35, 40, 45]],
#
# [[36, 42, 48, 54],
# [48, 56, 64, 72],
# [60, 70, 80, 90]]])
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.