Is there an elegant way to build a Torch.Tensor
like this from a given set of values?
Here is an 3x3 example, but in my application I would have a matrix of any odd-size.
A function call gen_matrix([a, b, c, d, e, f])
should generate

EDIT: As of now, I implemented the following solution. A more elegant way is desirable without a for
loop. Using plain torch
operations is desirable.
def weights_to_symmetric(weights, N):
assert(weights.ndim == 3)
tensor = torch.zeros((*weights.shape[:2], N, N))
idx = 0
for diag in range(N):
size = N - diag
w = weights[:, :, idx:idx+size]
tensor += torch.diag_embed(w, offset=diag)
if diag > 0:
tensor += torch.diag_embed(w, offset=-diag)
idx += size
return tensor
I’m not sure about pytorch, but gpytorch has something called toeplitz. Maybe that’s what you are looking for ? (look into sym_toeplitz)
Thank you for the suggestion. sym_toeplitz
is a resembles want, but it is not quite the same thing.
from gpytorch import utils
c = torch.tensor([1, 6, 4, 5], dtype=torch.float)
res = utils.toeplitz.sym_toeplitz(c)
res
# tensor([[1., 6., 4., 5.],
# [6., 1., 6., 4.],
# [4., 6., 1., 6.],
# [5., 4., 6., 1.]])
Here is a solution by swag2198 from stackoverflow.
>>> N = 5
>>> vals = torch.arange(N*(N+1)/2) + 1
>>> A = torch.zeros(N, N)
>>> i, j = torch.triu_indices(N, N)
>>> A[i, j] = vals
>>> A.T[i, j] = vals
>>> vals
tensor([ 1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12., 13., 14.,
15.])
>>> A
tensor([[ 1., 2., 3., 4., 5.],
[ 2., 6., 7., 8., 9.],
[ 3., 7., 10., 11., 12.],
[ 4., 8., 11., 13., 14.],
[ 5., 9., 12., 14., 15.]])
It is possible to also use torch.nn.utils.parametrize.register_parametrization
for symmetrizing the matrix on the fly, however it comes to the expense of more parameters, but if this does not induce memory problem is more elegant and faster. Example:
import torch
class SymmetricTensor(torch.nn.Module):
def __init__(self):
super().__init__()
def forward(self, x):
# standard symmetrization equation of tensors.
return 0.5*(x+x.transpose(1,0))
class ToyModel(torch.nn.Module):
def __init__(self,N):
super().__init__()
self.symmetric_param = torch.nn.Parameter(torch.rand(N,N),requires_grad=True)
# This makes the Parameter symmetric_param symmetric
torch.nn.utils.parametrize.register_parametrization(self, "symmetric_param", SymmetricTensor())
def forward(self,input):
# assuming input.shape B x C x H x W, summing over channels index
return torch.einsum('bchw,cj->bjhw',input,self.symmetric_param)
net = ToyModel(4)
xx = torch.rand(2,4,24,24)
out = net(xx)
out.shape
# torch.Size([2, 4, 24, 24])
net.symmetric_param
# output:
tensor([[0.0248, 0.2363, 0.6852, 0.5177],
[0.2363, 0.3860, 0.4304, 0.7496],
[0.6852, 0.4304, 0.6364, 0.3511],
[0.5177, 0.7496, 0.3511, 0.6647]], grad_fn=<MulBackward0>)