如何有效地存储具有高冗余值的矩阵

Dus*_*ell 17 algorithm sparse-matrix matrix-multiplication data-structures

我有一个非常大的矩阵(100M行乘100M列),它有很多重复的值,彼此相邻.例如:

8 8 8 8 8 8 8 8 8 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 4 8 8 1 1 1 1 1 8 8 8 8
8 8 8 8 8 8 8 8 8 8 8 8 8
8 8 3 3 3 3 3 3 3 3 3 3 3
Run Code Online (Sandbox Code Playgroud)

我想要一个数据结构/算法来尽可能紧凑地存储像这样的基质.例如,上面的矩阵应该只占用O(1)空间(即使矩阵被任意拉伸),因为只有恒定数量的矩形区域,其中每个区域只有一个值.

重复发生在行和下行中,因此逐行压缩矩阵的简单方法不够好.(这将需要至少O(num_rows)空间来存储任何矩阵.)

矩阵的表示也需要逐行访问,以便我可以对列向量进行矩阵乘法.

Ira*_*ter 13

您可以将矩阵存储为四叉树,其中叶子包含单个值.将此视为价值观的二维"运行".

  • 您递归地将任何正方形数组细分为4个子数组,当(子)数组在任何地方包含相同的值时停止.一个四分区可能有两个具有不同常数但不进一步细分的子单元,以及两个进一步细分的子单元.这是有效的,因为在最坏的情况下,您可以将数组细分为单个单元格,并且它们显然形成仅包含单个值的1x1矩阵!通过递归遍历四叉树来访问任何数组元素是O(log(n)),如果矩阵稀疏,平均访问时间将远远小于最坏情况. (3认同)

And*_*tus 10

现在我的首选方法.

好吧,正如我在之前的答案行中提到的那样,矩阵A中每列中的相同条目将在矩阵AB中相乘得到相同的结果.如果我们能够维持这种关系,那么我们理论上可以大大加快计算速度(探查器是你的朋友).

在这个方法中,我们维护矩阵的row*列结构.

每行都用任何方法压缩,可以足够快地解压缩,不会过多地影响乘法速度.RLE可能就足够了.

我们现在有一个压缩行列表.

我们使用熵编码方法(如Shannon-Fano,Huffman或算术编码),但我们不用此压缩行中的数据,我们用它来压缩行集.我们用它来编码行的相对频率.即我们对待行的方式与标准熵编码处理字符/字节的方式相同.

在这个例子中RLE压缩排,和Huffman压缩整个所述的行.

因此,例如,给定以下矩阵(前缀为行号,霍夫曼用于解释)

0 | 8 8 8 8 8 8 8 8 8 8 8 8 8 |
1 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
2 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
3 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
4 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
5 | 8 4 8 8 1 1 1 1 1 8 8 8 8 |
6 | 8 8 8 8 8 8 8 8 8 8 8 8 8 |
7 | 8 8 3 3 3 3 3 3 3 3 3 3 3 |
Run Code Online (Sandbox Code Playgroud)

运行长度编码

0 | 8{13}                    |
1 | 8{1} 4{1} 8{2} 1{5} 8{4} |
2 | 8{1} 4{1} 8{2} 1{5} 8{4} |
3 | 8{1} 4{1} 8{2} 1{5} 8{4} |
4 | 8{1} 4{1} 8{2} 1{5} 8{4} |
5 | 8{1} 4{1} 8{2} 1{5} 8{4} |
6 | 8{13}                    |
7 | 8{2} 3{11}               |
Run Code Online (Sandbox Code Playgroud)

因此,0和6出现两次,1 - 5出现5次.7只一次.

频率表

A: 5 (1-5) | 8{1} 4{1} 8{2} 1{5} 8{4} |
B: 2 (0,6) | 8{13}                    |
C: 1    7  | 8{2} 3{11}               |
Run Code Online (Sandbox Code Playgroud)

哈夫曼树

    0|1
   /   \
  A    0|1
      /   \
     B     C
Run Code Online (Sandbox Code Playgroud)

因此,在这种情况下,需要一位(对于每一行)对行1-5进行编码,并且对于对行0,6和7进行编码需要2位.

(如果运行时间超过几个字节,那么就在你进行RLE时建立的哈希上对freq进行计数).

存储Huffman树,唯一字符串和行编码位流.

关于Huffman的好处是它具有唯一的前缀属性,所以你总能知道你何时完成.因此,给定位串,10000001011您可以从存储的唯一字符串和树重建矩阵A. 编码的比特流告诉您行出现的顺序.

您可能希望研究自适应霍夫曼编码或其算术对应.

看到具有相同列条目的A中的行与AB相对于矢量B的相同结果相乘,您可以缓存结果并使用它而不是再次计算它(如果可以的话,最好避免100M*100M乘法).

链接到更多信息:

算术编码+统计建模=数据压缩

优先级队列和STL

算术编码

霍夫曼编码

一个对比

未压缩

    0   1   2   3   4   5   6   7
  =================================
0 | 3   3   3   3   3   3   3   3 |
  |-------+               +-------|
1 | 4   4 | 3   3   3   3 | 4   4 |
  |       +-----------+---+       |
2 | 4   4 | 5   5   5 | 1 | 4   4 |
  |       |           |   |       |
3 | 4   4 | 5   5   5 | 1 | 4   4 |
  |---+---|           |   |       |
4 | 5 | 0 | 5   5   5 | 1 | 4   4 |
  |   |   +---+-------+---+-------|
5 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       |                   |
6 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       +-------------------|
7 | 5 | 0   0   0   0   0   0   0 |
  =================================
Run Code Online (Sandbox Code Playgroud)

= 64个字节

四叉树

    0   1   2   3   4   5   6   7
  =================================
0 | 3 | 3 |       |       | 3 | 3 |
  |---+---|   3   |   3   |---+---|
1 | 4 | 4 |       |       | 4 | 4 |
  |-------+-------|-------+-------|
2 |       |       | 5 | 1 |       |
  |   4   |   5   |---+---|   4   |
3 |       |       | 5 | 1 |       |
  |---------------+---------------|
4 | 5 | 0 | 5 | 5 | 5 | 1 | 4 | 4 |
  |---+---|---+---|---+---|---+---|
5 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 |
  |-------+-------|-------+-------|
6 | 5 | 0 | 0 | 2 | 2 | 2 | 2 | 2 |
  |---+---+---+---|---+---+---+---|
7 | 5 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
  =================================

0 +- 0 +- 0 -> 3
  |    +- 1 -> 3
  |    +- 2 -> 4
  |    +- 3 -> 4
  +- 1      -> 3
  +- 2      -> 4
  +- 3      -> 5
1 +- 0      -> 3
  +- 1 +- 0 -> 3
  |    +- 1 -> 3
  |    +- 2 -> 4
  |    +- 3 -> 4
  +- 2 +- 0 -> 5
  |    +- 1 -> 1
  |    +- 2 -> 5
  |    +- 3 -> 1
  +- 3      -> 4
2 +- 0 +- 0 -> 5
  |    +- 1 -> 0
  |    +- 2 -> 5
  |    +- 3 -> 0
  +- 1 +- 0 -> 5
  |    +- 1 -> 5
  |    +- 2 -> 0
  |    +- 3 -> 2
  +- 2 +- 0 -> 5
  |    +- 1 -> 0
  |    +- 2 -> 5
  |    +- 3 -> 0
  +- 3 +- 0 -> 0
       +- 1 -> 2
       +- 2 -> 0
       +- 3 -> 0
3 +- 0 +- 0 -> 5
  |    +- 1 -> 1
  |    +- 2 -> 2
  |    +- 3 -> 2
  +- 1 +- 0 -> 4
  |    +- 1 -> 4
  |    +- 2 -> 2
  |    +- 3 -> 2
  +- 2 +- 0 -> 2
  |    +- 1 -> 2
  |    +- 2 -> 0
  |    +- 3 -> 0
  +- 3 +- 0 -> 2
       +- 1 -> 2
       +- 2 -> 0
       +- 3 -> 0

((1*4) + 3) + ((2*4) + 2) + (4 * 8) = 49 leaf nodes 
49 * (2 + 1) = 147 (2 * 8 bit indexer, 1 byte data)
+ 14 inner nodes -> 2 * 14 bytes (2 * 8 bit indexers)
= 175 Bytes
Run Code Online (Sandbox Code Playgroud)

地区哈希

    0   1   2   3   4   5   6   7
  =================================
0 | 3   3   3   3   3   3   3   3 |
  |-------+---------------+-------|
1 | 4   4 | 3   3   3   3 | 4   4 |
  |       +-----------+---+       |
2 | 4   4 | 5   5   5 | 1 | 4   4 |
  |       |           |   |       |
3 | 4   4 | 5   5   5 | 1 | 4   4 |
  |---+---|           |   |       |
4 | 5 | 0 | 5   5   5 | 1 | 4   4 |
  |   + - +---+-------+---+-------|
5 | 5 | 0   0 | 2   2   2   2   2 |
  |   |       |                   |
6 | 5 | 0   0 | 2   2   2   2   2 |
  |   +-------+-------------------|
7 | 5 | 0   0   0   0   0   0   0 |
  =================================

0: (4,1; 4,1), (5,1; 6,2), (7,1; 7,7)         | 3
1: (2,5; 4,5)                                 | 1
2: (5,3; 6,7)                                 | 1
3: (0,0; 0,7), (1,2; 1,5)                     | 2
4: (1,0; 3,1), (1,6; 4,7)                     | 2
5: (2,2; 4,4), (4,0; 7,0)                     | 2
Run Code Online (Sandbox Code Playgroud)

区域:(3 + 1 + 1 + 2 + 2 + 2)*5 = 55字节{4字节矩形,1字节数据)

{查找表是一个排序数组,因此它不需要额外的存储空间}.

霍夫曼编码RLE

0   | 3 {8}                                 | 1
1   | 4 {2} | 3 {4} | 4 {2}                 | 2
2,3 | 4 {2} | 5 {3} | 1 {1} | 4 {2}         | 4
4   | 5 {1} | 0 {1} | 5 {3} | 1 {1} | 4 {2} | 5
5,6 | 5 {1} | 0 {2} | 2 {5}                 | 3
7   | 5 {1} | 0 {7}                         | 2


RLE Data:    (1 + 3+ 4 + 5 + 3 + 2) * 2 = 36
Bit Stream:   20 bits packed into 3 bytes = 3
Huffman Tree: 10 nodes * 3 = 30
= 69 Bytes
Run Code Online (Sandbox Code Playgroud)

一个巨大的RLE流

3{8};4{2};3{4};4{4};5{3};1{1};4{4};5{3};1{1};4{2};5{1};0{1};
5{3};1{1};4{2};5{1};0{2};2{5};5{1};0{2};2{5};5{1};0{7}

= 2 * 23 = 46 Bytes
Run Code Online (Sandbox Code Playgroud)

一个巨型RLE流使用公共前缀折叠编码

3{8};
4{2};3{4};
4{4};5{3};1{1};
4{4};5{3};
1{1};4{2};5{1};0{1};5{3};
1{1};4{2};5{1};0{2};2{5};
5{1};0{2};2{5};
5{1};0{7}

0 + 0 -> 3{8};4{2};3{4};
  + 1 -> 4{4};5{3};1{1};

1 + 0 -> 4{2};5{1} + 0 -> 0{1};5{3};1{1};
  |                + 1 -> 0{2}
  |
  + 1 -> 2{5};5{1} + 0 -> 0{2};
                   + 1 -> 0{7}

3{8};4{2};3{4}           | 00
4{4};5{3};1{1}           | 01
4{4};5{3};1{1}           | 01
4{2};5{1};0{1};5{3};1{1} | 100
4{2};5{1};0{2}           | 101
2{5};5{1};0{2}           | 110
2{5};5{1};0{7}           | 111

Bit stream: 000101100101110111
RLE Data:  16 * 2 = 32
Tree:   : 5 * 2 = 10 
Bit stream: 18 bits in 3 bytes = 3
= 45 bytes
Run Code Online (Sandbox Code Playgroud)