【问题标题】:File format optimized for sparse matrix exchange针对稀疏矩阵交换优化的文件格式
【发布时间】:2018-07-24 01:39:40
【问题描述】:

我想将一个稀疏的数字矩阵(整数,但它可以是浮点数)保存到一个文件中以进行数据交换。对于稀疏矩阵,我的意思是一个高百分比的值(通常为 90%)等于 0 的矩阵。在这种情况下,稀疏与文件格式无关,而是与矩阵的实际内容有关。

矩阵的格式如下:

        col1   col2   ....
row1  int1_1 int1_2   ....
row2  int2_1   ....   ....
....  ....     ....   .... 

通过使用文本文件(制表符分隔),文件大小为 4.2G。我可以使用哪种文件格式(最好是普遍存在的 .txt 文件)轻松加载和保存这个稀疏数据矩阵?我们通常使用 Python/R/Matlab,因此首选它们支持的格式。

【问题讨论】:

  • 你是如何存储这些稀疏数据的?注意:如果您使用 .txt 或任何其他非压缩数据,那么显然大小不会改变。
  • 是的,txt 显然是大小的上限,但它可以很容易地在不同框架之间交换。我正在寻找一种受科学库支持的类似压缩的格式(例如 python 中的 pandas)。
  • 如果我没记错的话,稀疏数组存储为非零元素的 3 个值(row_in_array、col_in_array、val)的行。所以基本上你可以用这种格式表达你的数组,使用可以包含每一位信息的最小数据类型(某种uint用于行/列,可能double用于值),然后将其导出为任何二进制你想要的格式,然后在目标软件中导入,用它在本地创建一个稀疏数组。
  • @gc5 你可能误解了我的意思。稀疏矩阵的全部意义在于您仅在非零元素上存储(并执行操作)。存储元素的方式是每个元素使用 3 个值。您的任务是以节省空间的方式将此信息编码到文件中。如果您使用uint 而不是double,您也许可以节省一些空间。如果您选择将其导出为 MATLAB .mat 文件(v7 或更高版本),它将被压缩,从而节省更多空间。您不必导出源软件对sparse 数组的任何表示,并希望以后可以阅读它。
  • @gc5 是的,您可以将稀疏的 Matlab 矩阵保存在 mat 文件中,就像您可以保存任何变量一样。但是,快速测试表明文件大小可能并不总是更小。使用标准 saveeye(100)sparse(eye(100)) 分别为 257 和 364 字节。虽然一个 1 的 1000*1000 零矩阵是 3.63kB 标准,而稀疏时是 215B。优势取决于您的矩阵的稀疏程度。注意有different compression options

标签: python r matlab format sparse-matrix


【解决方案1】:

我找到了Feather 格式(目前不支持 Matlab,afaik)。

this section 提供了 Pandas 中读写和内存性能的一些比较。

它还提供对 Julia 语言的支持。

编辑:

我发现在我的例子中,这种格式比.txt 使用更多的磁盘空间,可能是为了提高 I/O 性能。使用 zip 压缩可以缓解问题,但在写入 seems to not be supported yet 时会进行压缩。

【讨论】:

  • 更大的文件大小如何提高 IO 性能?
【解决方案2】:

您有多种解决方案,但通常您需要输出非零元素的索引以及值。假设您要导出到单个文本文件。

生成数组

让我们首先生成一个 10000 x 5000 的稀疏数组,填充约 10%(由于复制索引,它会少一些):

N = 10000; 
M = 5000; 
rho = .1; 
rN = ceil(sqrt(rho)*N);
rM = ceil(sqrt(rho)*M);
S = sparse(N, M); 
S(randi(N, [rN 1]), randi(M, [rM 1])) = randi(255, rN, rM);

如果您的数组没有存储为稀疏数组,您可以简单地使用(其中 M 是完整数组)来创建它:

S = sparse(M);

另存为文本文件

现在我们将矩阵保存为以下格式 row_indx col_indx 值 row_indx col_indx 值 row_indx col_indx 值

这是通过提取行和列索引以及数据值,然后循环保存到文本文件来完成的:

[n, m, s] = find(S);
fid = fopen('Sparse.txt', 'wt');
arrayfun(@(n, m, s) fprintf(fid, '%d\t%d\t%d\n', n, m, s), n, m, s);
fclose(fid);

如果基础数据不是整数,那么您可以在最后一个输出上使用 %f 标志,例如(保留小数点后 15 位)

arrayfun(@(n, m, s) fprintf(fid, '%d\t%d\t%.15f\n', n, m, s), n, m, s);

将此与完整数组进行比较:

fid = fopen('Full.txt', 'wt'); 
arrayfun(@(n) fprintf(fid, '%s\n', num2str(S(n, :))), (1:N).'); 
fclose(fid);

在这种情况下,稀疏文件约为 50MB,完整文件约为 170MB,代表效率的 3 倍。这是意料之中的,因为我需要为数组的每个非零元素保存 3 个数字,并且填充了约 10% 的数组,与整个数组相比,需要保存的数字多约 30%。

对于浮点格式,由于索引的大小与浮点值相比要小得多,因此节省的空间更大。

在 Matlab 中,一种快速提取数据的方法是保存以下给出的字符串:

mat2str(S)

这本质上是相同的,但将其包装在 sparse 命令中以便在 Matlab 中轻松加载 - 需要用其他语言对其进行解析才能读取它。该命令告诉您如何重新创建数组,暗示您可能还需要将矩阵的大小存储在文件中(我建议在第一行中进行,因为您可以在解析文件的其余部分之前读入并创建稀疏矩阵。

另存为二进制文件

一种更有效的方法是保存为二进制文件。假设数据和索引可以存储为无符号 16 位整数,您可以执行以下操作:

[n, m, s] = find(S);
fid = fopen('Sparse.dat', 'w');
fwrite(fid, size(S), 'uint16');
fwrite(fid, [n m s], 'uint16');
fclose(fid);

然后读取数据:

fid = fopen('Sparse.dat', 'r');
sz = fread(fid, 2, 'uint16');
s = reshape(fread(fid, 'uint16'), [], 3);
s = sparse(s(:, 1), s(:, 2), s(:, 3), sz(1), sz(2));
fclose(fid);

现在我们可以检查它们是否相等:

isequal(S, s)

保存完整的数组:

fid = fopen('Full.dat', 'w');
fwrite(fid, full(S), 'uint16');
fclose(fid);

比较稀疏和完整文件大小,我得到 21MB 和 95MB。

几点说明:

  1. 使用单个写入/读取命令比循环快很多(很多),所以最后一种方法是迄今为止最快的,也是最节省空间的。
  2. 可以保存为二进制整数的最大索引/数据值大小为 2^n - 1,其中 n 是位深度。在我的 16 位 (uint16) 示例中,它对应于 0..65,535 的范围。听起来,您可能需要使用 32 位甚至 64 位来存储索引。
  3. 将索引保存为一种数据类型(例如 uint32)并将实际值保存为另一种数据类型(例如 uint8)可以获得更高的效率。但是,这会增加保存和读取的复杂性。
  4. 您仍然希望首先存储矩阵大小,正如我在二进制示例中所示。
  5. 如果需要,您可以将值存储为双精度值,但索引应始终为整数。同样,额外的复杂性,但可行。

【讨论】:

  • 请注意,您也可以在 Matlab 中压缩(和解压缩)文件,这将很好地压缩文本文件。您可能还会发现全文文件压缩到类似的数量。
  • 这是一个很好的答案,谢谢。我仍然需要了解它是否可以轻松地与 Python 和 R 代码一起使用。
  • 所有这些方法都兼容任何语言,尤其是R和python。文本文件就是这样,纯 ASCII 文本,因此可以被任何语言读取。所有语言都处理二进制文件——你只需要告诉它数据是如何打包的。注意不同的语言对数据的编码方式不同(例如大端或小端),但您应该能够指定/转换。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-17
  • 1970-01-01
  • 2017-03-20
相关资源
最近更新 更多