数据压缩:算术编码不清楚

时间:2012-04-13 12:42:28

标签: algorithm encoding compression entropy lossless-compression

任何人都可以用实现细节解释数据压缩的算术编码吗?我已经浏览过互联网并找到了标记纳尔逊的帖子,但是在尝试了好几个小时之后,实现的技术确实不清楚。

Mark nelson对算术编码的解释可以在

找到

http://marknelson.us/1991/02/01/arithmetic-coding-statistical-modeling-data-compression/

3 个答案:

答案 0 :(得分:14)

算术压缩的主要思想是能够使用所需的确切数据长度来编码概率

这些数据是已知的,由Shannon证明,可以使用以下公式简单计算:-log2(p)

例如,如果p = 50%,则需要1位。 如果p = 25%,则需要2位。

这对于2的幂的概率来说足够简单(在这种特殊情况下,霍夫曼编码就足够了)。但如果概率为63%怎么办?然后你需要-log2(0.63)= 0.67位。听起来很棘手......

如果您的概率很高,此属性尤为重要。如果你能够以95%的准确率预测某些东西,那么你只需要0.074位代表一个好的猜测。这意味着你要压缩很多。

现在,怎么做?

嗯,它比听起来更简单。您将根据概率划分范围。例如,如果您有100个可能事件的范围,并且第一个事件的概率为95%,则前95个值将显示“事件1”,最后5个值将显示“事件2”

好的,但在计算机上,我们习惯使用2的幂。例如,对于16位,您有65536个可能值的范围。只需这样做:取第一个95%的范围(即62259)说“事件1”,其余的说“事件2”。你显然有一个“舍入”(精度)的问题,但只要你有足够的值来分配,它就没那么重要了。此外,您不会受到2个事件的限制,您可能会遇到无数事件。重要的是根据每个事件的概率分配值。

好的,但现在我有62259个可能的值来表示“事件1”,而3277表示“事件2”。我应该选择哪一个? 好吧,他们中的任何人都会这样做。如果它是1,30,5500或62256,它仍然意味着“事件1”。

事实上,决定选择哪个值不会取决于当前的猜测,而是取决于下一个。

假设我有“事件1”。所以现在我必须选择介于0和62256之间的任何值。在下一个猜测中,我有相同的分布(95%事件1,5%事件2)。我将简单地分配具有这些概率的分布图。除此之外,它分布在62256个值上。我们继续这样做,每次猜测都会减少值的范围。

事实上,我们正在定义“范围”,每次猜测都会缩小范围。然而,在某些时候,存在准确性问题,因为仍然存在很少的值。

这个想法是简单地“再次”扩大范围。例如,每次范围低于32768(2 ^ 15)时,输出最高位,并将其余乘以2(有效地将值向左移一位)。通过不断地这样做,你逐个输出比特,因为它们是通过一系列的猜测来解决的。

现在与压缩的关系变得明显:当范围迅速缩小(例如:5%)时,输出大量的比特以使范围回到限制之上。另一方面,当概率非常高时,范围变窄非常缓慢。在输出第一位之前,您甚至可以进行大量猜测。这就是如何将事件压缩到“一小部分”。

我故意使用术语“概率”,“猜测”,“事件”来保持本文的通用性。但是对于数据压缩,您只需要用您想要建模数据的方式替换它们。例如,下一个事件可以是下一个字节;在这种情况下,你有256个。

答案 1 :(得分:1)

首先感谢您向我介绍算术压缩的概念!

我可以看到这个方法有以下步骤:

  1. 创建映射:计算每个字母的出现分数,给出每个字母的范围大小。然后对它们进行排序并指定0到1之间的实际范围
  2. 给出一条消息计算范围(相当简单的恕我直言)
  3. 找到最佳代码
  4. 第三部分有点棘手。使用以下算法。

    设b是最佳表示。将其初始化为空字符串('')。设x为最小值,y为最大值。

    1. double x和y:x = 2 * x,y = 2 * y
    2. 如果两者都大于1,则附加1到b。转到第1步。
    3. 如果它们都小于1,则将0添加到b。转到第1步。
    4. 如果x <1,但y> 1,则将1附加到b并停止
    5. b基本上包含您传输的数字的小数部分。例如。如果b = 011,则该分数对应于二进制的0.011。

      您不了解哪部分实施?

答案 2 :(得分:1)

也许这个脚本可以用来构建更好的算术编码器心智模型:gen_map.py。最初它的创建是为了便于调试算术编码器库并简化其单元测试的生成。然而,它创建了很好的ASCII可视化,这也可以用于理解算术编码。

一个小例子。想象一下,我们有一个包含3个符号的字母:012,概率相应地为1/102/107/10。我们想编码序列[1, 2]。脚本将提供以下输出(暂时忽略-b N选项):

$ ./gen_map.py -b 6 -m "1,2,7" -e "1,2"
000000111111|1111|111222222222222222222222222222222222222222222222
------011222|2222|222000011111111122222222222222222222222222222222
---------011|2222|222-------------00011111122222222222222222222222
------------|----|-------------------------00111122222222222222222
------------|----|-------------------------------01111222222222222
------------|----|------------------------------------011222222222
==================================================================
000000000000|0000|000000000000000011111111111111111111111111111111
000000000000|0000|111111111111111100000000000000001111111111111111
000000001111|1111|000000001111111100000000111111110000000011111111
000011110000|1111|000011110000111100001111000011110000111100001111
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
001100110011|0011|001100110011001100110011001100110011001100110011
010101010101|0101|010101010101010101010101010101010101010101010101

前6行(在====行之前)表示从0.0到1.0的范围,其在与符号概率成比例的间隔上递归地细分。注释第一行:

[1/10][     2/10    ][                 7/10                      ]
000000111111|1111|111222222222222222222222222222222222222222222222

然后我们再次细分每个区间:

[ 0.1][     0.2     ][                 0.7                       ]
000000111111|1111|111222222222222222222222222222222222222222222222
         [   0.7    ][.1][   0.2 ][          0.7                 ]
------011222|2222|222000011111111122222222222222222222222222222222
                                  [.1][ .2][   0.7               ]  
---------011|2222|222-------------00011111122222222222222222222222

请注意,某些间隔未细分。当没有足够的空间来表示给定精度内的每个子区间时(由-b选项指定),就会发生这种情况。

每一行对应于输入的符号(在我们的例子中 - 序列[1, 2])。通过遵循每个输入符号的子区间,我们将得到一个最终区间,我们希望用最少的位编码。在我们的例子中,它是第二行的第一个2子区间:

         [ This one ]
------011222|2222|222000011111111122222222222222222222222222222222

以下7行(====之后)表示相同的间隔0.0到1.0,但是根据二进制表示法进行细分。每一行都是一点输出,通过在0和1之间选择,您可以选择左半部分或右半部分。例如,位01对应于第二行上的子区间[0.25, 05)

                  [   This one   ]
000000000000|0000|111111111111111100000000000000001111111111111111

算术编码器的思想是输出比特(0或1),直到相应的间隔完全在输入序列确定的间隔内(或等于)。在我们的例子中,它是0011~~~~行显示我们有足够的位来明确识别我们想要的间隔。

|符号组成的垂直线表示可用于编码输入序列的位序列(行)范围。