Demystifying torch.sparse.spdiags: Creating Sparse Diagonal Matrices in PyTorch
torch.sparse.spdiags Function
This function constructs a sparse matrix from diagonals and offsets. It's particularly useful when you want to represent matrices where most elements are zero, improving memory efficiency and computational speed.
Parameters
shape
(tuple of 2 ints): A tuple specifying the desired shape(rows, cols)
of the output sparse matrix.offsets
(Tensor): A 1D integer tensor of lengthnum_diagonals
specifying the offsets of the diagonals from the main diagonal. Positive offsets represent diagonals above the main diagonal, and negative offsets represent diagonals below.diagonals
(Tensor): A tensor of shape(num_diagonals, min(shape))
storing the diagonals row-wise.num_diagonals
is the number of diagonals you want to include in the sparse matrix, andmin(shape)
is the minimum dimension (rows or columns) of the desired output matrix.
Example
import torch
diagonals = torch.tensor([1, 2, 3]) # Three diagonals
offsets = torch.tensor([-1, 0, 1]) # One below, main, and one above
shape = (5, 5) # Desired output shape
sparse_matrix = torch.sparse.spdiags(diagonals, offsets, shape)
print(sparse_matrix)
This code will create a sparse matrix with the following structure:
0 1 2 3 4
0 1.0 0.0 0.0 0.0 0.0
1 0.0 2.0 0.0 0.0 0.0
2 0.0 0.0 3.0 0.0 0.0
3 0.0 0.0 0.0 1.0 0.0
4 0.0 0.0 0.0 0.0 1.0
As you can see, only the elements on the specified diagonals (-1
, 0
, and 1
) have non-zero values (1
, 2
, and 3
).
- PyTorch doesn't currently have a built-in
torch.sparse.diags
function for constructing sparse diagonal matrices from a single diagonal array. You can achieve this by creating a tensor with the desired diagonal elements and usingspdiags
with an offset of 0. - Offsets cannot be repeated, meaning you cannot have multiple diagonals at the same position.
- The number of rows in
diagonals
must match the length ofoffsets
.
Sparse Diagonal Matrix from a Single Diagonal
import torch
diagonal_values = torch.tensor([2, 4, 6, 8]) # Elements of the diagonal
shape = (4, 4) # Desired output shape
# Create a tensor with the diagonal values and offset of 0
diagonals = torch.diag(diagonal_values)
offsets = torch.zeros(1) # Offset of 0 for the main diagonal
sparse_matrix = torch.sparse.spdiags(diagonals, offsets, shape)
print(sparse_matrix)
This code will create a sparse matrix with non-zero values only on the main diagonal ([2, 4, 6, 8]
).
Creating a Sparse Identity Matrix
You can use torch.sparse.spdiags
to create a sparse identity matrix:
import torch
size = 5 # Size of the identity matrix
identity_diagonals = torch.ones(1) # One diagonal with all ones
offsets = torch.zeros(1) # Offset of 0 for the main diagonal
sparse_identity = torch.sparse.spdiags(identity_diagonals, offsets, (size, size))
print(sparse_identity)
This code will create a sparse identity matrix with shape (5, 5)
.
Sparse Matrix with Multiple Off-Diagonal Elements
import torch
diagonals = torch.tensor([3, 2, 1]) # Three diagonals
offsets = torch.tensor([-2, 0, 1]) # Two below, main, and one above
shape = (5, 5) # Desired output shape
sparse_matrix = torch.sparse.spdiags(diagonals, offsets, shape)
print(sparse_matrix)
This code will create a sparse matrix with non-zero values on diagonals -2
, 0
, and 1
.
Manual Construction with torch.sparse_coo_tensor (Efficient for Small Matrices)
import torch
diagonal_values = torch.tensor([2, 4, 6, 8]) # Elements for the main diagonal
shape = (4, 4) # Desired output shape
# Calculate indices for the diagonal entries
i = torch.arange(shape[0])
j = i
# Create sparse COO tensor with diagonal indices and values
sparse_matrix = torch.sparse_coo_tensor(torch.stack([i, j]), diagonal_values, shape)
print(sparse_matrix)
torch.diag_embed (Convenient for Diagonal Embedding)
If you want to embed a diagonal vector into a larger square matrix, torch.diag_embed
is a suitable choice:
import torch
diagonal_values = torch.tensor([2, 4, 6, 8])
# Embed the diagonal into a larger matrix (5x5 in this example)
sparse_matrix = torch.diag_embed(diagonal_values, dim1=5, dim2=5)
print(sparse_matrix)
torch.sparse.sum with Dense Diagonal (Efficient for Large Matrices)
For larger sparse matrices, a combination of torch.sparse.sum
with a dense diagonal tensor can be memory-efficient:
import torch
dense_diagonal = torch.diag(torch.arange(1, 101)) # Example dense diagonal
sparse_matrix = A # Your existing sparse matrix (replace A with your actual matrix)
# Efficiently multiply sparse and dense matrices
sparse_diagonal = torch.sparse.sum(sparse_matrix * dense_diagonal, dim=-1)
# Combine the sparse diagonal with the original sparse matrix (if needed)
combined_matrix = torch.sparse.block_diag(sparse_matrix, sparse_diagonal.unsqueeze(-1))
print(combined_matrix) # Output will depend on the structure of your sparse matrix A
This approach avoids creating a large sparse diagonal matrix, potentially saving memory.
- Large Sparse Matrices with Sparse-Dense Multiplication
Considertorch.sparse.sum
with a dense diagonal for memory efficiency. - Diagonal Embedding
Usetorch.diag_embed
for its convenience. - Small Matrices
Usetorch.sparse_coo_tensor
for its simplicity.