saiden
saiden

Reputation: 342

Multiply a 3d tensor with a 2d matrix using torch.matmul

I have two tensors in PyTorch, z is a 3d tensor of shape (n_samples, n_features, n_views) in which n_samples is the number of samples in the dataset, n_features is the number of features for each sample, and n_views is the number of different views that describe the same (n_samples, n_features) feature matrix, but with other values.

I have another 2d tensor b, of shape (n_samples, n_views), which purpose is to rescale all the features of the samples across the different views. In other words, it encapsulates the importance of the features of each view for the same sample. For example:

import torch
z = torch.Tensor(([[2,3], [1,1], [4,5]], 
                  [[2,2], [1,2], [7,7]], 
                  [[2,3], [1,1], [4,5]], 
                  [[2,3], [1,1], [4,5]]))

b = torch.Tensor(([1, 0], 
                  [0, 1], 
                  [0.2, 0.8], 
                  [0.5, 0.5]))
print(z.shape, b.shape)

>>>torch.Size([4, 3, 2]) torch.Size([4, 2])

I want to obtain a third tensor r of shape (n_samples, n_features) as a result of operations between z and b. One possible solution is:

b = b.unsqueeze(1)
r = z * b
r = torch.sum(r, dim=-1)
print(r, r.shape)
>>>tensor([[2.0000, 1.0000, 4.0000],
        [2.0000, 2.0000, 7.0000],
        [2.8000, 1.0000, 4.8000],
        [2.5000, 1.0000, 4.5000]]) torch.Size([4, 3])

Is it possible to achieve that same result using torch.matmul()?. I've tried many times to permute the dimensions of the two vector, but to no avail.

Upvotes: 3

Views: 5900

Answers (1)

flawr
flawr

Reputation: 11628

Yes that's possible. If you have mutiple batch dimensions in both operatns, you can use the broadcasting. In this case the last two dimensions of each operand are interpreted as a matrix size. (I recommend looking it up in the documentation.)

So you need an additional dimension for your vectors b, to make them a n x 1 "matrix" (column vector):

# original implementation
b1 = b.unsqueeze(1)
r1 = z * b1
r1 = torch.sum(r1, dim=-1)
print(r1.shape)

# using torch.matmul
r2 = torch.matmul(z, b.unsqueeze(2))[...,0]
print(r2.shape)
print((r1-r2).abs().sum())  # should be zero if we do the same operation

Alternatively, torch.einsum also makes this very straightforward.

# using torch.einsum
r3 = torch.einsum('ijk,ik->ij', z, b)
print((r1-r3).abs().sum())  # should be zero if we do the same operation

einsum is a very powerful operation that can do a lot of things: you can permute tensor dimensions, sum along them, or perform scalar products, all with or without broadcasting. It is derived from the Einstein summation convention mostly used in physics. The rough idea is that you give every dimension of your operans a name, and then, using these names define what the output should look like. I think it is best to read the documentation. In our case we have a 4 x 3 x 2 tensor as well as a 4 x 2 tensor. So the let's call the dimensions of the first tensor ijk. Here i and k should be considered the same as the dimensions of the second tensor, so this one can be described as ik. Finally the output should have clearly be ij (it mus be a 4 x 3 tensor). From this "signature" ijk, ik -> ij it is clear that the dimension i is preserved, and the dimensions k must be "summe/multiplied" away (scalar product).

Upvotes: 2

Related Questions