簡體   English   中英

遞歸函數將任意函數應用於任意長度的 N 個數組,形成一個鋸齒狀的 N 維多維數組

[英]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()也適用於更高維度的輸入數組。 廣播方法可以很容易地適應更高維度的輸入,如下所示。


編輯 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

讓我們給定 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM