首页/文章/ 详情

稀疏矩阵的两种常见存储格式:COO与CSR及其相互转换

1年前浏览674
在数据科学、机器学习与数值计算的世界里,“矩阵” 是绕不开的核心载体。但当矩阵中绝大多数元素为 0 时(例如推荐系统中的用户 - 物品交互矩阵、图神经网络中的邻接矩阵),传统的稠密矩阵存储方式会造成巨大的内存浪费 —— 想象一下,一个 10000×10000 的矩阵若仅有 1% 的非零元素,却要存储 1 亿个数据,其中 99% 都是无意义的 0。此时,稀疏矩阵存储格式应运而生,成为平衡存储效率与计算性能的关键技术。
在深入技术细节前,我们不妨先直观感受一下稀疏存储的 “性价比”。假设一个稀疏矩阵有N行、M列,非零元素个数为K(通常K ≪ N×M,即稀疏度极高)。
稠密存储:无论元素是否为 0,都需存储所有N×M个值,若每个元素占 4 字节(float32),总内存占用为4×N×M字节。
稀疏存储:仅存储非零元素的 “位置” 与 “值”,内存占用与K成正比,无需为 0 元素浪费空间。
以自然语言处理中的词嵌入矩阵为例:若词汇表大小为 10 万(行),嵌入维度为 512(列),但实际训练中每个句子仅涉及数十个词,矩阵稀疏度超过 99.9%。此时,稠密存储需占用4×100000×512 = 204,800,000字节(约 200MB),而稀疏存储仅需存储 “词 ID - 嵌入维度 - 非零值”,内存占用可降至 KB 级 —— 这种效率提升,正是稀疏存储的核心价值。
COO和CSR是稀疏矩阵最常见的两种存储格式之二
COO 格式:最简单直观的 “坐标记录者”
COO 格式,全称 Coordinate Format(坐标格式),是稀疏矩阵存储中最基础、最直观的方式。它的核心思路是:用三个数组记录所有非零元素的 “行号、列号、值”,相当于给每个非零元素贴一个 “坐标标签”。

1. COO 的结构定义

假设稀疏矩阵A有K个非零元素,COO 格式通过以下三个长度均为K的一维数组存储:
row_indices:长度为K,存储每个非零元素的行索引(从 0 或 1 开始,需统一);
col_indices:长度为K,存储每个非零元素的列索引;
data:长度为K,存储每个非零元素的具体数值。

2. 实例:用 COO 存储一个稀疏矩阵

我们以一个简单的 4×4 稀疏矩阵为例,直观理解 COO 的存储逻辑:
原矩阵A:




1 0 0 20 3 0 04 0 5 00 0 0 6
矩阵A的非零元素共 6 个,分别是:A[0,0]=1、A[0,3]=2、A[1,1]=3、A[2,0]=4、A[2,2]=5、A[3,3]=6。
对应的 COO 数组:
row_indices = [0, 0, 1, 2, 2, 3](按行号顺序排列,也可随机,顺序不影响存储);
col_indices = [0, 3, 1, 0, 2, 3];
data = [1, 2, 3, 4, 5, 6]。

CSR 格式:为 “行访问” 优化的 “压缩大师”
CSR 格式,全称 Compressed Sparse Row Format(压缩稀疏行格式),是数值计算领域应用最广泛的稀疏存储格式之一。它在 COO 的基础上对 “行索引” 进行了压缩,核心目标是优化 “按行访问” 的效率—— 这恰好契合了矩阵乘法、向量乘法等常用运算的需求(如深度学习中的卷积层、全连接层计算)。

1. CSR 的结构定义

CSR 格式通过三个数组存储,其中 “行索引” 被压缩,长度不再是K,而是N+1(N为矩阵行数):
indptr:长度为N+1的整数数组,称为 “行指针数组”。indptr[i]表示第 i 行的第一个非零元素在data和col_indices数组中的起始位置,indptr[N]恒等于非零元素总数K;
col_indices:长度为K,存储每个非零元素的列索引(与 COO 的col_indices功能一致);
data:长度为K,存储每个非零元素的具体数值(与 COO 的data功能一致)。

2. 实例:用 CSR 存储同一稀疏矩阵

仍以之前的 4×4 矩阵A为例,我们推导其 CSR 格式的三个数组:




1 0 0 20 3 0 04 0 5 00 0 0 6
原矩阵的非零元素分布(按行排序):
第 0 行:2 个非零元素(索引 0、3),值为 1、2;
第 1 行:1 个非零元素(索引 1),值为 3;
第 2 行:2 个非零元素(索引 0、2),值为 4、5;
第 3 行:1 个非零元素(索引 3),值为 6。
步骤 1:确定data和col_indices
按 “行优先” 顺序排列非零元素的 “值” 和 “列索引”:
data = [1, 2, 3, 4, 5, 6](与 COO 的data相同,因我们按行排序了 COO 的元素);
col_indices = [0, 3, 1, 0, 2, 3](与 COO 的col_indices相同)。
步骤 2:推导indptr数组
indptr的长度为4+1=5,每个元素的含义的计算如下:
indptr[0] = 0(第 0 行的第一个非零元素在data的起始位置为 0);
indptr[1] = indptr[0] + 第0行非零元素个数 = 0 + 2 = 2(第 1 行的第一个非零元素在data的起始位置为 2);
indptr[2] = indptr[1] + 第1行非零元素个数 = 2 + 1 = 3(第 2 行的第一个非零元素在data的起始位置为 3);
indptr[3] = indptr[2] + 第2行非零元素个数 = 3 + 2 = 5(第 3 行的第一个非零元素在data的起始位置为 5);
indptr[4] = indptr[3] + 第3行非零元素个数 = 5 + 1 = 6(等于非零元素总数K=6,作为结束标志)。
因此,indptr = [0, 2, 3, 5, 6]。

CSR 的核心优势:高效的行访问
CSR 的设计精髓在于indptr数组 —— 通过它,我们可以在O(1)时间内定位某一行的所有非零元素,无需遍历整个数组。
例如,要获取第 2 行的所有非零元素:
从indptr[2] = 3和indptr[3] = 5可知,第 2 行的非零元素在data和col_indices中的索引范围是[3, 5)(即索引 3 和 4);
因此,第 2 行的非零元素为data[3]=4(列索引col_indices[3]=0)和data[4]=5(列索引col_indices[4]=2),与原矩阵完全一致。
这种高效的行访问能力,让 CSR 成为矩阵 - 向量乘法(Ax=b)、行内最大值 / 最小值计算等场景的首选格式 —— 而这些操作,正是机器学习和数值计算的核心环节。

在科学计算的实践中,实际上还有其他存储格式,例如与CSR类似的CSC,以及EIA等。实际中有时候也可能会遇到需要对不同存储格式进行转换的需求。例如,在稀疏矩阵高性能求解器Pardiso中,需要提供的是CSR格式,而在另一常用的稀疏矩阵求解器Mumps中,需要提供的是COO格式,此时就可能需要对两种格式进行转换。以下是一个CSR和COO相互转换的代码:


























































#include <iostream>void csr2coo(int* row, int* col,  double* value, int* ia, int* ja, int rows,int nnz){for (int i = 0; i < rows; i++){for (int j =ia[i] ; j < ia[i+1]; j++){col[j] = ja[j];row[j] = i;}}}void coo2csr(int* ia, int* ja, double* value, int* row, int* col, int rows, int nnz){for (int i = 0; i < rows+1; i++){ia[i] = 0;}for (int i = 0; i < rows; i++){int count = 0;for (int j = 0; j < nnz; j++){if (row[j] == i) count++;}ia[i + 1] = ia[i] + count;}for (int i = 0; i < nnz; i++){ja[i] = col[i];}}int main(int argc,char** argv){/*1 0 0 0 2 34 0 5*/const int nrow = 3,nnz=5;int ia[nrow + 1]{ 0,1,3,5 }, ja[nnz]{ 0,1,2,0,2 };double value[nnz]{ 1.0,2.0,3.0,4.0,5.0 };int row[nnz], col[nnz];csr2coo(row, col, value, ia, ja, nrow, nnz);for (int i = 0; i < nnz; i++){printf("row %d col %d val %le \n", row[i],col[i],value[i]);}coo2csr(ia, ja, value, row, col, nrow, nnz);for (int i = 0; i < nrow+1; i++){printf("ia %d\n", ia[i]);}for (int i = 0; i < nnz; i++){printf("ja %d val %le \n", ja[i], value[i]);}return 0;}

通过以上代码,我们可以方便地将CSR和COO格式进行相互转换,从而满足具体地需要。总体上,CSR格式适合矩阵向量乘等数值运算,COO格式简单,组装方便,但需要的存储空间比CSR大一点点。

来源:有限元术
UM
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2025-09-18
最近编辑:1年前
寒江雪_123
硕士 | cae工程师 签名征集中
获赞 54粉丝 121文章 93课程 9
点赞
收藏
作者推荐

¥30 5.0
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈