Tensor数据转换为稀疏矩阵

一、稀疏矩阵

原文链接

常用的稀疏矩阵存储格式有COO,CSR/CSC,LIL

1.COO

COO(Coordinate format )是最为简单的格式,以三元组的形式存储稀疏矩阵。记录矩阵中非零元素的数值和所在的行序号和列序号。形式为(行号,列号,数值)。这种存储方式的主要优点是灵活、简单。但是缺点是不可以直接进行矩阵的相关运算

image-20230909002327571

2.CSR/CSC

CSR(Compressed Sparse Row)格式实现了用于存储二维张量的 CSR 格式。尽管不支持 N 维张量,但与 COO 格式相比的主要优势是更好地利用存储和更快的计算操作。目前尚不存在 CUDA 支持

image-20230909002457546

3.LIL

LIL (List-of-List) 每行存储一个列表,每个条目包含列索引和值。通常,这些条目按列索引进行排序,以便更快地查找

4.稀疏矩阵的处理

Pytorch中,处理稀疏矩阵的有效工具torch.sparse。Torch 支持 COO(rdinate) 格式的稀疏张量,可以有效地存储和处理大多数元素为零的张量

二、Tensor数据转换为稀疏矩阵

1.torch.spares_coo_tenso

1
torch.spares_coo_tensor(indices, values, siez=None,*, dtype=None, requires_grad=False)->Tensor

参数:

  • indices: 一个2D张量,其中每一列都代表一个非零元素的坐标。
  • values: 一个1D张量,其中每个值都是与indices对应坐标中的非零元素对应的值。
  • size: (可选) 一个表示稀疏张量大小的元组

假设一个2D的tensor数据

1
2
3
0 0 0 5
0 0 3 0
0 2 0 0

非零元素的坐标(indices)和对应的值(values)为:

1
2
3
4
5
indices = [[0, 3],
[1, 2],
[2, 1]]

values = [5, 3, 2]

可以使用torch.sparse_coo_tensor来创建这个稀疏张量:

1
2
3
4
5
6
7
8
import torch

indices = torch.tensor([[0, 1, 2],
[3, 2, 1]])
values = torch.tensor([5, 3, 2])
size = (3, 4)

sparse_tensor = torch.sparse_coo_tensor(indices, values, size)

2.将一个2D的Tensor数据变为COO稀疏张量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import torch

# 示例张量
tensor = torch.tensor([[0, 2, 1], [0, 0, 3], [4, 0, 0]])

# 寻找非零元素的索引
non_zero_indices = torch.nonzero(tensor).t()
print(non_zero_indices[0]) # tensor([0, 0, 1, 2])
print(non_zero_indices[1]) # tensor([1, 2, 2, 0])

print(tensor.dim()) # 2

# 获取非零元素的值
values = tensor[tuple(non_zero_indices[i] for i in range(tensor.dim()))]

print(values) # tensor([2, 1, 3, 4])

print(tensor.size()) # torch.Size([3, 3])

# 创建稀疏张量
sparse_tensor = torch.sparse_coo_tensor(non_zero_indices, values, tensor.size())

print(sparse_tensor)

最后得到的稀疏矩阵

1
2
3
4
tensor(indices=tensor([[0, 0, 1, 2],
[1, 2, 2, 0]]),
values=tensor([2, 1, 3, 4]),
size=(3, 3), nnz=4, layout=torch.sparse_coo)

3.将一个3Dtensor数据转换为COO稀疏张量

仍然可以使用torch.sparse_coo_tensor函数将其转换为稀疏表示。与2D张量相似,你需要确定非零元素的位置和它们的值。对于3D张量,每个非零元素的坐标将由三个值表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import torch

# 示例3D张量
tensor = torch.tensor([
[[0, 2, 0], [0, 0, 3], [4, 0, 0]],
[[0, 0, 0], [0, 5, 0], [0, 0, 6]]
])

print(tensor.dim()) # 3

# 寻找非零元素的索引
non_zero_indices = torch.nonzero(tensor).t()
print(non_zero_indices[0]) # tensor([0, 0, 0, 1, 1])
print(non_zero_indices[1]) # tensor([0, 1, 2, 1, 2])
print(non_zero_indices[2]) # tensor([1, 2, 0, 1, 2])


# 获取非零元素的值
values = tensor[tuple(non_zero_indices[i] for i in range(tensor.dim()))]

# 创建稀疏张量
sparse_tensor = torch.sparse_coo_tensor(non_zero_indices, values, tensor.size())

print(sparse_tensor)

4.将一个未知维度的张量数据转换为COO稀疏张量,并且存储到硬盘

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
"""
将tensor 数据转换为COO 稀疏张量函数
"""
def tensor_to_sparse(dense_tensor):
size = dense_tensor.size()
# 寻找非零元素的索引
non_zero_indices = torch.nonzero(dense_tensor).t()
# 获取非零元素的值
values = dense_tensor[tuple(non_zero_indices[i] for i in range(dense_tensor.dim()))]
# 创建稀疏张量
sparse_tensor = torch.sparse_coo_tensor(non_zero_indices, values, size)

return sparse_tensor,size

# 随机产生一个4D张量数据
dense_tensor = torch.randn((2,3,3,3))
print(dense_tensor)
print(dense_tensor.dim()) # 4

sparse_tensor,size = tensor_to_sparse(dense_tensor)
print(sparse_tensor)
print(size) # torch.Size([2, 3, 3, 3])

# 保存稀疏张量到硬盘
torch.save(sparse_tensor, "spare_tensor.npz")

5.读取硬盘上存储的COO稀疏张量,并且转换为原来的tensor数据(dense_tensor)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 从硬盘上加载稀疏张量
loaded_sparse_tensor = torch.load("spare_tensor.npz")

"""
COO 稀疏张量转换为密集张量
"""
def sparse_to_tensor(loaded_sparse_tensor):
# 将稀疏张量复原为原始的密集张量
dense_tensor = loaded_sparse_tensor.to_dense()

return dense_tensor

# 调用函数
sparse_to_tensor(loaded_sparse_tensor)

5.使用scipy包完成上述操作

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import scipy.sparse
import torch
import scipy.sparse



def tensor_to_sparse(dense_tensor):
# 将dense_tensor转化为2D
shape = dense_tensor.shape
tensor_2d = dense_tensor.view(-1, shape[-1])

# 将2D tensor转化为numpy array
array_2d = tensor_2d.numpy()

# 从numpy array创建sparse matrix
sparse_matrix = scipy.sparse.coo_matrix(array_2d)

return sparse_matrix,shape


def sparse_to_tensor(sparse_matrix, original_shape):
# 从稀疏矩阵转换为2D array
array_2d = sparse_matrix.toarray()

# 将2D array转换为original_shape_array
original_shape_array = array_2d.reshape(original_shape)

# 将3D array转换为3D tensor
dense_tensor = torch.from_numpy(original_shape_array)

return dense_tensor

# 随机产生一个dense_tensor
dense_tensor = torch.randn((2,3,3,3))



# 转化为sparse matrix
sparse_matrix,original_shape = tensor_to_sparse(dense_tensor)
print(original_shape)


# 将sparse matrix保存到硬盘上
scipy.sparse.save_npz('sparse_matrix.npz', sparse_matrix)

# 使用scipy.sparse.load_npz从硬盘加载保存的稀疏张量
loaded_sparse_matrix = scipy.sparse.load_npz('sparse_matrix.npz')


# 稀疏张量复原为原来的tensor数据
restored_tensor = sparse_to_tensor(loaded_sparse_matrix, original_shape)


print(restored_tensor)

# 判断restored_tensor与原来的tensor数据是否一致
print(dense_tensor==restored_tensor)