从卷积核生成线性运算矩阵的 Numpy 方法
Numpy way to generate linear operation matrix from a convolution kernel
形状为 (k1, k2, n_channel, n_filter)
的二维卷积核 K
应用于形状为 (m1, m2, n_channel)
的二维向量 A
并生成另一个二维向量 B
,形状为 (m1 - k1 + 1, m2 - k2 + 1, n_filter)
(具有 有效 填充)。
对于每个K
,也确实存在一个形状(m1 - k1 + 1, m2 - k2 + 1, n_filter, m1, m2, n_channel)
的W_K
,使得W_K
和A
的张量点等于 B
。即 B = np.tensordot(W_K, A, 3)
.
我正在尝试找到一个纯 NumPy 解决方案来从 K
生成此 W_K
,而不使用任何 python 循环。
我可以看到 W_K[i,j,f] == np.pad(K[...,f], ((i,m1-i-k1), (j,m2-j-k2)), 'constant', constant_values=0)
或者只是 W_K[i, j, f, i:i+k1, j:j+k2, ...] == K[..., f]
.
我要找的几乎与 Toeplitz 矩阵相似。但我需要多维度的。
循环代码示例:
import numpy as np
# 5x5 image with 3-channels
A = np.random.random((5,5,3))
# 2x2 Conv2D kernel with 2 filters for A
K = np.random.random((2,2,3,2))
# It should be of (4,4,2,5,5,3), but I create this way for convenience. I move the axis at the end.
W_K = np.empty((4,4,5,5,3,2))
for i, j in np.ndindex(4, 4):
W_K[i, j] = np.pad(K, ((i, 5-i-2),(j, 5-j-2), (0, 0), (0, 0)), 'constant', constant_values=0)
# above lines can also be rewritten as
W_K = np.zeros((4,4,5,5,3,2))
for i, j in np.ndindex(4, 4):
W_K[i, j, i:i+2, j:j+2, ...] = K[...]
W_K = np.moveaxis(W_K, -1, 2)
# now I can do
B = np.tensordot(W_K, A, 3)
您想要的东西需要一些 fancy indexing 体操,但编码不是很麻烦。这个想法是创建应用第二个循环示例的 W_K[i, j, i:i+2, j:j+2, ...]
部分的 4 维索引数组。
这里是你的例子的一个稍微修改的版本,只是为了确保一些相关的维度不同(因为这使得错误更容易找到:它们将是正确的错误而不是错误的值):
import numpy as np
# parameter setup
k1, k2, nch, nf = 2, 4, 3, 2
m1, m2 = 5, 6
w1, w2 = m1 - k1 + 1, m2 - k2 + 1
K = np.random.random((k1, k2, nch, nf))
A = np.random.random((m1, m2, nch))
# your loopy version for comparison
W_K = np.zeros((w1, w2, nf, m1, m2, nch))
for i, j in np.ndindex(w1, w2):
W_K[i, j, :, i:i+k1, j:j+k2, ...] = K.transpose(-1, 0, 1, 2)
W_K2 = np.zeros((w1, w2, m1, m2, nch, nf)) # to be transposed back
i,j = np.mgrid[:w1, :w2][..., None, None] # shape (w1, w2, 1, 1)
k,l = np.mgrid[:k1, :k2] # shape (k1, k2) ~ (1, 1, k1, k2)
W_K2[i, j, i+k, j+l, ...] = K
W_K2 = np.moveaxis(W_K2, -1, 2)
print(np.array_equal(W_K, W_K2)) # True
我们首先创建一个索引网格 i,j
,它跨越 W_K
的前两个维度,然后创建两个相似的网格,跨越它的(pre-moveaxis
)第二和第三个维度.通过将两个尾随的单一维度注入前者,我们最终得到 4d 索引数组,它们一起跨越 W_K
.
的前四个维度。
剩下的就是使用原始 K
分配给这个切片,然后移回维度。由于当表达式中的切片(非高级)索引并非彼此相邻时高级索引如何改变行为,因此使用 moveaxis
方法更容易做到这一点。我首先尝试创建具有最终尺寸的 W_K2
,但随后我们会得到具有细微不同行为(特别是不同形状)的 W_K[i, j, :, i+k, j+l, ...]
。
形状为 (k1, k2, n_channel, n_filter)
的二维卷积核 K
应用于形状为 (m1, m2, n_channel)
的二维向量 A
并生成另一个二维向量 B
,形状为 (m1 - k1 + 1, m2 - k2 + 1, n_filter)
(具有 有效 填充)。
对于每个K
,也确实存在一个形状(m1 - k1 + 1, m2 - k2 + 1, n_filter, m1, m2, n_channel)
的W_K
,使得W_K
和A
的张量点等于 B
。即 B = np.tensordot(W_K, A, 3)
.
我正在尝试找到一个纯 NumPy 解决方案来从 K
生成此 W_K
,而不使用任何 python 循环。
我可以看到 W_K[i,j,f] == np.pad(K[...,f], ((i,m1-i-k1), (j,m2-j-k2)), 'constant', constant_values=0)
或者只是 W_K[i, j, f, i:i+k1, j:j+k2, ...] == K[..., f]
.
我要找的几乎与 Toeplitz 矩阵相似。但我需要多维度的。
循环代码示例:
import numpy as np
# 5x5 image with 3-channels
A = np.random.random((5,5,3))
# 2x2 Conv2D kernel with 2 filters for A
K = np.random.random((2,2,3,2))
# It should be of (4,4,2,5,5,3), but I create this way for convenience. I move the axis at the end.
W_K = np.empty((4,4,5,5,3,2))
for i, j in np.ndindex(4, 4):
W_K[i, j] = np.pad(K, ((i, 5-i-2),(j, 5-j-2), (0, 0), (0, 0)), 'constant', constant_values=0)
# above lines can also be rewritten as
W_K = np.zeros((4,4,5,5,3,2))
for i, j in np.ndindex(4, 4):
W_K[i, j, i:i+2, j:j+2, ...] = K[...]
W_K = np.moveaxis(W_K, -1, 2)
# now I can do
B = np.tensordot(W_K, A, 3)
您想要的东西需要一些 fancy indexing 体操,但编码不是很麻烦。这个想法是创建应用第二个循环示例的 W_K[i, j, i:i+2, j:j+2, ...]
部分的 4 维索引数组。
这里是你的例子的一个稍微修改的版本,只是为了确保一些相关的维度不同(因为这使得错误更容易找到:它们将是正确的错误而不是错误的值):
import numpy as np
# parameter setup
k1, k2, nch, nf = 2, 4, 3, 2
m1, m2 = 5, 6
w1, w2 = m1 - k1 + 1, m2 - k2 + 1
K = np.random.random((k1, k2, nch, nf))
A = np.random.random((m1, m2, nch))
# your loopy version for comparison
W_K = np.zeros((w1, w2, nf, m1, m2, nch))
for i, j in np.ndindex(w1, w2):
W_K[i, j, :, i:i+k1, j:j+k2, ...] = K.transpose(-1, 0, 1, 2)
W_K2 = np.zeros((w1, w2, m1, m2, nch, nf)) # to be transposed back
i,j = np.mgrid[:w1, :w2][..., None, None] # shape (w1, w2, 1, 1)
k,l = np.mgrid[:k1, :k2] # shape (k1, k2) ~ (1, 1, k1, k2)
W_K2[i, j, i+k, j+l, ...] = K
W_K2 = np.moveaxis(W_K2, -1, 2)
print(np.array_equal(W_K, W_K2)) # True
我们首先创建一个索引网格 i,j
,它跨越 W_K
的前两个维度,然后创建两个相似的网格,跨越它的(pre-moveaxis
)第二和第三个维度.通过将两个尾随的单一维度注入前者,我们最终得到 4d 索引数组,它们一起跨越 W_K
.
剩下的就是使用原始 K
分配给这个切片,然后移回维度。由于当表达式中的切片(非高级)索引并非彼此相邻时高级索引如何改变行为,因此使用 moveaxis
方法更容易做到这一点。我首先尝试创建具有最终尺寸的 W_K2
,但随后我们会得到具有细微不同行为(特别是不同形状)的 W_K[i, j, :, i+k, j+l, ...]
。