【问题标题】:Recursive function to apply any function to N arrays of any length to form one jagged multidimensional array of N dimensions递归函数将任何函数应用于任意长度的 N 个数组以形成一个 N 维的锯齿状多维数组
【发布时间】:2019-12-25 18:09:16
【问题描述】:

给定 N 个输入数组,所有 any 长度,我希望能够将函数应用于 every 组合的 all 组合每个数组。

例如:

给定输入数组:

[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]
    ]
]

【问题讨论】:

  • np.outer 将开始
  • 啊哈!我去看看,非常感谢
  • 另一种方法是使用reducenp.multiply.reduce(np.ix_(*[a,b]))。更多信息 - stackoverflow.com/questions/53932846/…
  • 与 np.outer,我相信这只是乘法。我应该指定,我需要能够对每个元素应用任何功能。将编辑问题以解释这一点。
  • 您的问题不完整/令人困惑。 recursive function 是什么意思?您的乘法示例不是递归的,并且从您的 cmets 来看并不像您喜欢的那样普遍。如果你还是 numpy 新手,你应该专注于学习它的核心功能,而不是尝试将通用函数应用于数组。

标签: python arrays python-3.x numpy


【解决方案1】:

使用 np.frompyfunc 创建所需函数的 ufunc 的另一种方法。这是对 n 个参数使用 ufuncs .outer 方法 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.]]])

【讨论】:

    【解决方案2】:

    给定 N 个大小为 n1, n2, ..., nN 的数组。 然后,我们可以将这个问题分解为两个数组的 (N-1) 次计算。 在第一次计算中,计算 n1,n2 的乘积。 让输出为result1。 在第二次计算中,计算 result1,n3 的乘积。 让输出为result2。 . . 在最后一次计算中,计算结果(N-2)的乘积,nN。 设输出为result(N-1)。

    你会知道result1的大小是n2 _ n1, result2 的大小为 n3 _ n2 _ n1。 . . 正如你可以推断的那样,结果(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
    

    【讨论】:

    • 我会重写这个问题,因为我认为我没有很好地解释它。
    【解决方案3】:

    您可以通过广播做到这一点:

    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 函数生成np.ufunc(如@TlsChris's answer 中建议的那样)并使用其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() 仅限于一维输入,但apply_multi_outer() 也适用于更高维的输入数组。广播方法可以很容易地适应更高维度的输入,如下所示。


    编辑 2

    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
    

    【讨论】:

    • 当然可以,但是如何将其应用于具有长度为63, 21, 1, 74 59 等数组的更大数据集...
    • @TomMartin 编辑是否回答了您的问题?它基本上是最后一条评论的代码。
    • 给定五个形状数组 (2, 3, 6, 2, 4),函数返回一个形状数组 (2, 2, 2, 2, 2, 3, 6, 2, 4 ),我希望有相同的形状。是不是我用错了?
    • @TomMartin 这与您在问题中描述的输入完全不同。也许您应该在一个新问题中表达您的担忧?
    【解决方案4】:

    根据我的经验,在大多数情况下,我们并不是在寻找真正通用的解决方案。当然,这样一个通用的解决方案看起来很优雅,也很可取,因为如果我们的需求发生变化,它天生就能够适应——就像他们在编写研究代码时经常发生的那样。

    然而,实际上我们通常在寻找一种易于理解且易于修改的解决方案,以防我们的需求发生变化。

    一个这样的解决方案是使用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]]])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-13
      • 2013-02-24
      • 2018-07-26
      • 2015-10-18
      • 1970-01-01
      相关资源
      最近更新 更多