Kronnecker 乘积仅在 3 维数组的两个维度上

时间:2021-01-11 03:52:21

标签: python arrays numpy linear-algebra

假设两个矩阵:

a = np.array([[1,2],
              [3,4]])
b = np.array([[5,6],
              [7,8]])

这会给 Kronecker 产品 ab = np.kron(a,b)

array([[ 1,  2,  2,  4],
       [ 3,  4,  6,  8],
       [ 3,  6,  4,  8],
       [ 9, 12, 12, 16]])

现在假设在两个数组中有这些矩阵的三个副本,如下所示:

c = np.stack([a,a,a])
d = np.stack([b,b,b])

我想计算 cd 的 Kronecker 积,使得输出是一个 3 索引数组,对应于 ab 的 3 个副本,即形状为 (3,4,4) .然而,简单地执行 kron(c,d) 输出形状 (9,4,4),它的条目比需要的多,并且无法适当地重新整形。你能帮助了解如何做到这一点吗?

3 个答案:

答案 0 :(得分:0)

这行得通,假设中间列表没有问题(numpy 在分配数组之前需要大小):

import numpy as np

a = np.array([[1, 2],
              [3, 4]])
b = np.array([[5, 6],
              [7, 8]])

c = np.stack([a, a, a])
d = np.stack([b, b, b])

result = np.array(list(np.kron(x, y) for x, y in zip(c, d)))

print(result)
print(result.shape)

输出:

[[[ 5  6 10 12]
  [ 7  8 14 16]
  [15 18 20 24]
  [21 24 28 32]]

 [[ 5  6 10 12]
  [ 7  8 14 16]
  [15 18 20 24]
  [21 24 28 32]]

 [[ 5  6 10 12]
  [ 7  8 14 16]
  [15 18 20 24]
  [21 24 28 32]]]
(3, 4, 4)

答案 1 :(得分:0)

res=np.zeros((3,4,4))
res[:] = np.kron(a,b)

应该可以,向所有 3 个平面广播 kron

kronab 的外积的特殊重排。 A (2,2,2,2) 重新排列为 (4,4)。我在另一篇文章中研究了详细信息:

Why is numpy's kron so fast?

您的 (3,4,4) 可以从 (3,2,2,2,2) 获得,但它不是标准的,因此没有开箱即用的功能。你可以尝试调整我的答案。


In [246]: a = np.array([[1,2],
     ...:               [3,4]])
     ...: b = np.array([[5,6],
     ...:               [7,8]])

In [249]: np.kron(a,b)
Out[249]: 
array([[ 5,  6, 10, 12],
       [ 7,  8, 14, 16],
       [15, 18, 20, 24],
       [21, 24, 28, 32]])

正如我之前展示的,kron 可以通过对 outer 乘积应用转置和形状来生成。我们可以将 einsum 用于外部和转置:

In [253]: np.einsum('ij,kl->ikjl',a,b)     # ikjl instead ijkl
Out[253]: 
array([[[[ 5,  6],
         [10, 12]],

        [[ 7,  8],
         [14, 16]]],


       [[[15, 18],
         [20, 24]],

        [[21, 24],
         [28, 32]]]])
In [254]: np.einsum('ij,kl->ikjl',a,b).reshape(4,4)
Out[254]: 
array([[ 5,  6, 10, 12],
       [ 7,  8, 14, 16],
       [15, 18, 20, 24],
       [21, 24, 28, 32]])

将其推广到数组 (3,2,2) 形状,我们可以添加一个额外的“批次”维度:

In [255]: c = np.stack([a,a,a])
     ...: d = np.stack([b,b,b])
In [256]: c
Out[256]: 
array([[[1, 2],
        [3, 4]],

       [[1, 2],
        [3, 4]],

       [[1, 2],
        [3, 4]]])
In [257]: c.shape
Out[257]: (3, 2, 2)
In [258]: np.einsum('aij,akl->aikjl',c,d).reshape(3,4,4)
Out[258]: 
array([[[ 5,  6, 10, 12],
        [ 7,  8, 14, 16],
        [15, 18, 20, 24],
        [21, 24, 28, 32]],

       [[ 5,  6, 10, 12],
        [ 7,  8, 14, 16],
        [15, 18, 20, 24],
        [21, 24, 28, 32]],

       [[ 5,  6, 10, 12],
        [ 7,  8, 14, 16],
        [15, 18, 20, 24],
        [21, 24, 28, 32]]])

但如果我们知道 cd 只是 ab 的复制,那么广播解决方案更快

In [260]: res = np.zeros((3,4,4),int)
In [261]: res[:] = np.kron(a,b)

甚至更好(无复制品的克朗视图):

np.broadcast_to(np.kron(a,b),(3,4,4))

一些时间:

In [280]: timeit np.einsum('aij,akl->aikjl',c,d).reshape(3,4,4)
10.2 µs ± 22.8 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
In [281]: timeit res=np.zeros((3,4,4),int);res[:] = np.kron(a,b)
47.5 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [282]: timeit np.broadcast_to(np.kron(a,b),(3,4,4))
57.6 µs ± 1.76 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [283]: timeit np.array(list(np.kron(x, y) for x, y in zip(c, d)))
143 µs ± 319 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

我有点惊讶 einsum 的速度有多快。此外,braodcast_to 的速度并不快,尽管这在很大程度上是 kron 的错(我之前的回答显示速度较慢)。

答案 2 :(得分:0)

我的解决方案是这样的

import numpy as np

def kron(a, b):

    ai = a[0]
    bi = b[0]

    b = np.asanyarray(b)
    a = np.array(a, copy=False, subok=True, ndmin=b.ndim)

    ndb, nda = bi.ndim, ai.ndim

    b_size = a.shape[0]

    as_ = ai.shape
    bs = bi.shape

    nd = ndb
    if (ndb != nda):
        if (ndb > nda):
            as_ = (1,) * (ndb - nda) + as_
        else:
            bs = (1,) * (nda - ndb) + bs
            nd = nda

    res = np.einsum("ij,ik->ijk", a.reshape((a.shape[0], -1)), b.reshape((b.shape[0], -1)))
    res = res.reshape((b_size, )+(as_ + bs))
    axis = nd - 1
    result = []
    for i in range(b_size):
        r = res[i]
        for _ in range(nd):
            r = np.concatenate(r, axis=axis)
        result.append(r)
    return np.array(result)

x = np.array([[1,2],
              [3,4]])
y = np.array([[5,6],
              [7,8]])

c = np.stack([x,x,x])
d = np.stack([y,y,y])

k = kron(c, d)
result = np.array(list(np.kron(x, y) for x, y in zip(c, d)))

print(np.allclose(k, result))

正如@hpaulj 在他们的回答中提到的那样,没有内置函数可以执行此操作,您必须对外部产品执行一些非平凡的重塑操作才能使其正常工作。

解决方案已根据 kron in numpy 的实现进行了修改。

我基本上只是在 numpy 实现用于执行连接操作的循环顶部添加了另一个外部循环。这是为了分别处理 cd 元素之间的每个外积。其他的都差不多。

请注意,这不是 numpy 的 kron 实现的泛化,因为这适用于您只想执行简单的 kron 操作的情况。至少不是没有一些额外的修改。为了清楚起见,我也忽略了 numpy 正在处理的一些边缘情况。但是,这应该适用于您的用例。