假设我有大小为[4, 4]
的矩阵P,该矩阵P划分(块)为4个较小的矩阵[2,2]
。我如何有效将该块矩阵乘以另一个矩阵(不是分区矩阵而是较小的矩阵)?
让我们假设原始矩阵为:
P = [ 1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4]
其中分为子矩阵:
P_1 = [1 1 , P_2 = [2 2 , P_3 = [3 3 P_4 = [4 4
1 1] 2 2] 3 3] 4 4]
现在我们的P是:
P = [P_1 P_2
P_3 p_4]
下一步,我想在P和较小矩阵之间进行逐元素相乘,其大小等于子矩阵的数量:
P * [ 1 0 = [P_1 0 = [1 1 0 0
0 0 ] 0 0] 1 1 0 0
0 0 0 0
0 0 0 0]
答案 0 :(得分:2)
您可以考虑以更有效的方式表示大型块矩阵。
例如,一个块矩阵
P = [ 1 1 2 2
1 1 2 2
3 3 4 4
3 3 4 4]
可以使用
表示a = [ 1 0 b = [ 1 1 0 0 p = [ 1 2
1 0 0 0 1 1 ] 3 4 ]
0 1
0 1 ]
为
P = a @ p @ b
具有({@
代表矩阵乘法)。矩阵a
和b
表示/编码P
的块结构,小的p
表示每个块的值。
现在,如果要将p
与较小的(2x2)矩阵q
相乘,您只需
a @ (p * q) @ b
一个简单的pytorch示例
In [1]: a = torch.tensor([[1., 0], [1., 0], [0., 1], [0, 1]])
In [2]: b = torch.tensor([[1., 1., 0, 0], [0, 0, 1., 1]])
In [3]: p=torch.tensor([[1., 2.], [3., 4.]])
In [4]: q = torch.tensor([[1., 0], [0., 0]])
In [5]: a @ p @ b
Out[5]: tensor([[1., 1., 2., 2.], [1., 1., 2., 2.], [3., 3., 4., 4.], [3., 3., 4., 4.]])
In [6]: a @ (p*q) @ b
Out[6]: tensor([[1., 1., 0., 0.], [1., 1., 0., 0.], [0., 0., 0., 0.], [0., 0., 0., 0.]])
在练习中,给定块的大小,如何有效地生成a
和b
的“结构”矩阵。
答案 1 :(得分:1)
不知道有效的方法,但是您可以尝试以下方法:
方法1:
使用torch.cat()
import torch
def multiply(a, b):
x1 = a[0:2, 0:2]*b[0,0]
x2 = a[0:2, 2:]*b[0,1]
x3 = a[2:, 0:2]*b[1,0]
x4 = a[2:, 2:]*b[1,1]
return torch.cat((torch.cat((x1, x2), 1), torch.cat((x3, x4), 1)), 0)
a = torch.tensor([[1, 1, 2, 2],[1, 1, 2, 2],[3, 3, 4, 4,],[3, 3, 4, 4]])
b = torch.tensor([[1, 0],[0, 0]])
print(multiply(a, b))
输出:
tensor([[1, 1, 0, 0],
[1, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
方法2:
使用torch.nn.functional.pad()
import torch.nn.functional as F
import torch
def multiply(a, b):
b = F.pad(input=b, pad=(1, 1, 1, 1), mode='constant', value=0)
b[0,0] = 1
b[0,1] = 1
b[1,0] = 1
return a*b
a = torch.tensor([[1, 1, 2, 2],[1, 1, 2, 2],[3, 3, 4, 4,],[3, 3, 4, 4]])
b = torch.tensor([[1, 0],[0, 0]])
print(multiply(a, b))
输出:
tensor([[1, 1, 0, 0],
[1, 1, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
答案 2 :(得分:1)
如果矩阵较小,则使用cat
或pad
可能会好。与采用block_mul
实现的解决方案一样,使用因式分解的解决方案非常优雅。
另一种解决方案是将2D块矩阵转换为3D体积,其中每个2D切片都是一个块(P_1, P_2, P_3, P_4
)。然后,使用广播的功能将每个2D切片与一个标量相乘。最后,调整输出的形状。重塑不是立即的,但可行的,可以从numpy移植到https://stackoverflow.com/a/16873755/4892874的pytorch
在Pytorch中:
import torch
h = w = 4
x = torch.ones(h, w)
x[:2, 2:] = 2
x[2:, :2] = 3
x[2:, 2:] = 4
# number of blocks along x and y
nrows=2
ncols=2
vol3d = x.reshape(h//nrows, nrows, -1, ncols)
vol3d = vol3d.permute(0, 2, 1, 3).reshape(-1, nrows, ncols)
out = vol3d * torch.Tensor([1, 0, 0, 0])[:, None, None].float()
# reshape to original
n, nrows, ncols = out.shape
out = out.reshape(h//nrows, -1, nrows, ncols)
out = out.permute(0, 2, 1, 3)
out = out.reshape(h, w)
print(out)
tensor([[1., 1., 0., 0.],
[1., 1., 0., 0.],
[0., 0., 0., 0.],
[0., 0., 0., 0.]])
我还没有针对其他基准进行基准测试,但是这并没有像padding那样消耗额外的内存,并且不会像串联那样执行慢的操作。它还具有易于理解和可视化的优点。
您可以通过玩h, w, nrows, ncols
将其推广到任何情况。
答案 3 :(得分:0)
尽管其他答案可能是解决方案,但这不是一种有效的方法。我提出了另一个解决问题的方法(但仍然不够完美)。当我们的输入为3维或4维时,以下实现需要太多内存。例如,对于20 * 75 * 1024 * 1024的输入大小,以下计算需要大约12gb的内存。
这是我的实现方式:
import tensorflow as tf
tf.enable_eager_execution()
inps = tf.constant([
[1, 1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2],
[3, 3, 3, 3, 4, 4, 4, 4],
[3, 3, 3, 3, 4, 4, 4, 4],
[3, 3, 3, 3, 4, 4, 4, 4],
[3, 3, 3, 3, 4, 4, 4, 4]])
on_cells = tf.constant([[1, 0, 0, 1]])
on_cells = tf.expand_dims(on_cells, axis=-1)
# replicate the value to block-size (4*4)
on_cells = tf.tile(on_cells, [1, 1, 4 * 4])
# reshape to a format for permutation
on_cells = tf.reshape(on_cells, (1, 2, 2, 4, 4))
# permutation
on_cells = tf.transpose(on_cells, [0, 1, 3, 2, 4])
# reshape
on_cells = tf.reshape(on_cells, [1, 8, 8])
# element-wise operation
print(inps * on_cells)
输出:
tf.Tensor(
[[[1 1 1 1 0 0 0 0]
[1 1 1 1 0 0 0 0]
[1 1 1 1 0 0 0 0]
[1 1 1 1 0 0 0 0]
[0 0 0 0 4 4 4 4]
[0 0 0 0 4 4 4 4]
[0 0 0 0 4 4 4 4]
[0 0 0 0 4 4 4 4]]], shape=(1, 8, 8), dtype=int32)
答案 4 :(得分:0)
以下是基于Tensorflow的常规解决方案,适用于任意形状的输入矩阵p
(大)和m
(小),只要p
的大小可以被整除m
在两个轴上的大小。
def block_mul(p, m):
p_x, p_y = p.shape
m_x, m_y = m.shape
m_4d = tf.reshape(m, (m_x, 1, m_y, 1))
m_broadcasted = tf.broadcast_to(m_4d, (m_x, p_x // m_x, m_y, p_y // m_y))
mp = tf.reshape(m_broadcasted, (p_x, p_y))
return p * mp
测试:
import tensorflow as tf
tf.enable_eager_execution()
p = tf.reshape(tf.constant(range(36)), (6, 6))
m = tf.reshape(tf.constant(range(9)), (3, 3))
print(f"p:\n{p}\n")
print(f"m:\n{m}\n")
print(f"block_mul(p, m):\n{block_mul(p, m)}")
输出(Python 3.7.3,Tensorflow 1.13.1):
p:
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]
[12 13 14 15 16 17]
[18 19 20 21 22 23]
[24 25 26 27 28 29]
[30 31 32 33 34 35]]
m:
[[0 1 2]
[3 4 5]
[6 7 8]]
block_mul(p, m):
[[ 0 0 2 3 8 10]
[ 0 0 8 9 20 22]
[ 36 39 56 60 80 85]
[ 54 57 80 84 110 115]
[144 150 182 189 224 232]
[180 186 224 231 272 280]]
使用隐式广播的另一个解决方案如下:
def block_mul2(p, m):
p_x, p_y = p.shape
m_x, m_y = m.shape
p_4d = tf.reshape(p, (m_x, p_x // m_x, m_y, p_y // m_y))
m_4d = tf.reshape(m, (m_x, 1, m_y, 1))
return tf.reshape(p_4d * m_4d, (p_x, p_y))