SciPy 中不同稀疏矩阵存储方式介绍
稀疏矩阵
稀疏矩阵
- 具有少量非零项的矩阵 - Number of Non-Zero (NNZ) < 0.5
- (在矩阵中,若数值0的元素数目远多于非0元素的数目,并且非0元素分布没有规律)
矩阵的稠密度
- 非零元素的总数比上矩阵所有元素的总数为矩阵的稠密度。
Scipy 矩阵存储
存储矩阵的一般方法是采用二维数组,其优点是可以随机地访问每一个元素,因而能够容易实现矩阵的各种运算。
对于稀疏矩阵,它通常具有很大的维度,有时甚大到整个矩阵(零元素)占用了绝大部分内存
采用二维数组的存储方法既浪费大量的存储单元来存放零元素,又要在运算中浪费大量的时间来进行零元素的无效运算。因此必须考虑对稀疏矩阵进行压缩存储(只存储非零元素)。
1 | from scipy import sparse |
矩阵属性
1 | from scipy.sparse import csr_matrix |
通用方法
1 | import scipy.sparse as sp |
稀疏矩阵分类
COO - coo_matrix
Coordinate Matrix 对角存储矩阵
- 采用三元组
(row, col, data)
(或称为ijv format)的形式来存储矩阵中非零元素的信息 - 三个数组
row
、col
和data
分别保存非零元素的行下标、列下标与值(一般长度相同) - 故
coo[row[k]][col[k]] = data[k]
,即矩阵的第row[k]
行、第col[k]
列的值为data[k]
- 当
row[0] = 1
,column[0] = 1
时,data[0] = 2
,故coo[1][1] = 2
- 当
row[3] = 0
,column[3] = 2
时,data[3] = 9
,故coo[0][3] = 9
适用场景
- 主要用来创建矩阵,因为coo_matrix无法对矩阵的元素进行增删改等操作
- 一旦创建之后,除了将之转换成其它格式的矩阵,几乎无法对其做任何操作和矩阵运算
优缺点
优点
- 转换成其它存储格式很快捷简便(
tobsr()
、tocsr()
、to_csc()
、to_dia()
、to_dok()
、to_lil()
) - 能与CSR / CSC格式的快速转换
- 允许重复的索引(例如在1行1列处存了值2.0,又在1行1列处存了值3.0,则转换成其它矩阵时就是2.0+3.0=5.0)
缺点
- 不支持切片和算术运算操作
- 如果稀疏矩阵仅包含非0元素的对角线,则对角存储格式(DIA)可以减少非0元素定位的信息量
- 这种存储格式对有限元素或者有限差分离散化的矩阵尤其有效
实例化方法
coo_matrix(D)
:D代表密集矩阵;coo_matrix(S)
:S代表其他类型稀疏矩阵coo_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
,coo_matrix((data, (i, j)), [shape=(M, N)]))
:三元组初始化i[:]
: 行索引j[:]
: 列索引A[i[k], j[k]]=data[k]
特殊属性
data
:稀疏矩阵存储的值,是一个一维数组row
:与data
同等长度的一维数组,表征data
中每个元素的行号col
:与data
同等长度的一维数组,表征data
中每个元素的列号
代码示例
1 | # 数据 |
CSR - csr_matrix
Compressed Sparse Row Matrix 压缩稀疏行格式
csr_matrix是按行对矩阵进行压缩的
通过
indices
,indptr
,data
来确定矩阵。data
表示矩阵中的非零数据对于第
i
行而言,该行中非零元素的列索引为indices[indptr[i]:indptr[i+1]]
可以将
indptr
理解成利用其自身索引i
来指向第i
行元素的列索引根据
[indptr[i]:indptr[i+1]]
,我就得到了该行中的非零元素个数,如- 若
index[i] = 3
且index[i+1] = 3
,则第i
行的没有非零元素 - 若
index[j] = 6
且index[j+1] = 7
,则第j
行的非零元素的列索引为indices[6:7]
- 若
得到了行索引、列索引,相应的数据存放在:
data[indptr[i]:indptr[i+1]]
对于矩阵第
0
行,我们需要先得到其非零元素列索引由
indptr[0] = 0
和indptr[1] = 2
可知,第0
行有两个非零元素。- 它们的列索引为
indices[0:2] = [0, 2]
,且存放的数据为data[0] = 8
,data[1] = 2
- 因此矩阵第
0
行的非零元素csr[0][0] = 8
和csr[0][2] = 2
- 它们的列索引为
对于矩阵第
4
行,同样我们需要先计算其非零元素列索引由
indptr[4] = 3
和indptr[5] = 6
可知,第4
行有3个非零元素。- 它们的列索引为
indices[3:6] = [2, 3,4]
,且存放的数据为data[3] = 7
,data[4] = 1
,data[5] = 2
- 因此矩阵第
4
行的非零元素csr[4][2] = 7
,csr[4][3] = 1
和csr[4][4] = 2
- 它们的列索引为
适用场景
- 常用于读入数据后进行稀疏矩阵计算,运算高效
优缺点
优点
- 高效的稀疏矩阵算术运算
- 高效的行切片
- 快速地矩阵矢量积运算
缺点
- 较慢地列切片操作(可以考虑CSC)
- 转换到稀疏结构代价较高(可以考虑LIL,DOK)
实例化
csr_matrix(D)
:D代表密集矩阵;csr_matrix(S)
:S代表其他类型稀疏矩阵csr_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
,csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)]))
三者关系:
a[row_ind[k], col_ind[k]] = data[k]
csr_matrix((data, indices, indptr), [shape=(M, N)])
第i行的列索引存储在其中
indices[indptr[i]:indptr[i+1]]
- 其对应值存储在中
data[indptr[i]:indptr[i+1]]
- 其对应值存储在中
特殊属性
data
:稀疏矩阵存储的值,一维数组indices
:存储矩阵有有非零值的列索引indptr
:类似指向列索引的指针数组has_sorted_indices
:索引indices
是否排序
1 | # 生成数据 |
CSC - csc_matrix
Compressed Sparse Column Matrix 压缩稀疏列矩阵
csc_matrix是按列对矩阵进行压缩的
通过
indices
,indptr
,data
来确定矩阵,可以对比CSRdata
表示矩阵中的非零数据对于第
i
列而言,该行中非零元素的行索引为indices[indptr[i]:indptr[i+1]]
可以将
indptr
理解成利用其自身索引i
来指向第i
列元素的列索引根据
[indptr[i]:indptr[i+1]]
,我就得到了该行中的非零元素个数,如- 若
index[i] = 1
且index[i+1] = 1
,则第i
列的没有非零元素 - 若
index[j] = 4
且index[j+1] = 6
,则第j
列的非零元素的行索引为indices[4:6]
- 若
得到了列索引、行索引,相应的数据存放在: data[indptr[i]:indptr[i+1]]
对于矩阵第
0
列,我们需要先得到其非零元素行索引- 由
indptr[0] = 0
和indptr[1] = 1
可知,第0
列行有1个非零元素。 - 它们的行索引为
indices[0:1] = [0]
,且存放的数据为data[0] = 8
- 因此矩阵第
0
行的非零元素csc[0][0] = 8
- 由
对于矩阵第
3
列,同样我们需要先计算其非零元素行索引- 由
indptr[3] = 4
和indptr[4] = 6
可知,第4
行有2个非零元素。 - 它们的行索引为
indices[4:6] = [4, 6]
,且存放的数据为data[4] = 1
,data[5] = 9
- 因此矩阵第
i
行的非零元素csr[4][3] = 1
,csr[6][3] = 9
- 由
实例化
csc_matrix(D)
:D代表密集矩阵;csc_matrix(S)
:S代表其他类型稀疏矩阵csc_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
,csc_matrix((data, (row_ind, col_ind)), [shape=(M, N)]))
- 三者关系:
a[row_ind[k], col_ind[k]] = data[k]
- 三者关系:
csc_matrix((data, indices, indptr), [shape=(M, N)])
- 第i列的列索引存储在其中
indices[indptr[i]:indptr[i+1]]
- 其对应值存储在中
data[indptr[i]:indptr[i+1]]
- 第i列的列索引存储在其中
特殊属性
data
:稀疏矩阵存储的值,一维数组indices
:存储矩阵有有非零值的行索引indptr
:类似指向列索引的指针数组has_sorted_indices
:索引是否排序
1 | # 生成数据 |
BSR - bsr_matrix
Block Sparse Row Matrix 分块压缩稀疏行格式
基于行的块压缩,与csr类似,都是通过
data
,indices
,indptr
来确定矩阵与csr相比,只是data中的元数据由0维的数变为了一个矩阵(块),其余完全相同
块大小
blocksize
- 块大小
(R, C)
必须均匀划分矩阵(M, N)
的形状。 - R和C必须满足关系:
M % R = 0
和N % C = 0
- 适用场景及优点参考csr
- 块大小
实例化
bsr_matrix(D)
:D代表密集矩阵;bsr_matrix(S)
:S代表其他类型稀疏矩阵bsr_matrix((M, N), [blocksize =(R,C), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
,(data, ij), [blocksize=(R,C), shape=(M, N)]
- 两者关系:
a[ij[0,k], ij[1,k]] = data[k]]
bsr_matrix((data, indices, indptr), [shape=(M, N)])
- 第i行的块索引存储在其中
indices[indptr[i]:indptr[i+1]]
- 其相应块值存储在中
data[indptr[i]:indptr[i+1]]
特殊属性
data
:稀疏矩阵存储的值,一维数组indices
:存储矩阵有有非零值的列索引indptr
:类似指向列索引的指针数组blocksize
:矩阵的块大小has_sorted_indices
:索引indices
是否排序
代码示例
1 | # 生成数据 |
优缺点
优点
- 与csr很类似
- 更适合于适用于具有密集子矩阵的稀疏矩阵
- 在某些情况下比csr和csc计算更高效。
DOK-dok_matrix
Dictionary of Keys Matrix 按键字典矩阵
- 采用字典来记录矩阵中不为0的元素
- 字典的
key
存的是记录元素的位置信息的元组,value
是记录元素的具体值
适用场景
- 逐渐添加矩阵的元素
实例化方法
dok_matrix(D)
:D代表密集矩阵;dok_matrix(S)
:S代表其他类型稀疏矩阵dok_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
优缺点
优点
- 对于递增的构建稀疏矩阵很高效,比如定义该矩阵后,想进行每行每列更新值,可用该矩阵。
- 可以高效访问单个元素,只需要O(1)
缺点
- 不允许重复索引(coo中适用),但可以很高效的转换成coo后进行重复索引
代码示例
1 | dok = sparse.dok_matrix((5, 5), dtype=np.float32) |
LIL-lil_matrix
Linked List Matrix 链表矩阵
- 使用两个列表存储非0元素data
- rows保存非零元素所在的列
- 可以使用列表赋值来添加元素,如
lil[(0, 0)] = 8
lil[(0, -1)] = 4
:第0行的最后一列元素为4lil[(4, 2)] = 5
:第4行第2列的元素为5
适用场景
- 适用的场景是逐渐添加矩阵的元素(且能快速获取行相关的数据)
- 需要注意的是,该方法插入一个元素最坏情况下可能导致线性时间的代价,所以要确保对每个元素的索引进行预排序
优缺点
优点
- 适合递增的构建成矩阵
- 转换成其它存储方式很高效
- 支持灵活的切片
缺点
- 当矩阵很大时,考虑用coo
- 算术操作,列切片,矩阵向量内积操作慢
实例化方法
lil_matrix(D)
:D代表密集矩阵;lil_matrix(S)
:S代表其他类型稀疏矩阵lil_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
特殊属性
data
:存储矩阵中的非零数据rows
:存储每个非零元素所在的列(行信息为列表中索引所表示)
代码示例
1 | # 创建矩阵 |
DIA - dia_matrix
Diagonal Matrix 对角存储格式
- 最适合对角矩阵的存储方式
- dia_matrix通过两个数组确定:
data
和offsets
data
:对角线元素的值offsets
:第i
个offsets
是当前第i
个对角线和主对角线的距离data[k:]
存储了offsets[k]
对应的对角线的全部元素
- 当
offsets[0] = 0
时,表示该对角线即是主对角线,相应的值为[1, 2, 3, 4, 5]
- 当
offsets[2] = 2
时,表示该对角线为主对角线向上偏移2个单位,相应的值为[11, 12, 13, 14, 15]
- 但该对角线上元素仅有三个 ,于是采用先出现的元素无效的原则
- 即前两个元素对构造矩阵无效,故该对角线上的元素为
[13, 14, 15]
实例化方法
dia_matrix(D)
:D代表密集矩阵;dia_matrix(S)
:S代表其他类型稀疏矩阵dia_matrix((M, N), [dtype])
:构建一个shape为M*N的空矩阵,默认数据类型是d
dia_matrix((data, offsets)), [shape=(M, N)]))
:data[k,:]
存储着对角偏移量为offset[k]
的对角值
特殊属性
data
:存储DIA对角值的数组offsets
:存储DIA对角偏移量的数组
代码示例
1 | # 生成数据 |
读取 - load_npz
1 | # 从npz文件中读取 |
存储大小比较
1 | a = np.arange(100000).reshape(1000,100) |
对于存储到npz文件中的CSR格式的稀疏矩阵,内容为:
1 | data.npy |