概念
信息熵
信息熵是用来衡量事物的信息量的,度量公式为:
H=
∑
i
=
1
n
p
i
l
o
g
1
p
i
\sum_{i=1}^{n}{p_i}log{\frac{1}{p_i}}
∑i=1npilogpi1
n表示该事物有n中可能的情况,
p
i
p_i
pi表示每种情况发生的概率。
比如:某个人怀孕后,生的小孩是什么性别,这件事的信息熵是1.
因为生男孩,还是女孩的概率都为0.5
H = 0.5log2 + 0.5log2 = 1.
如果一件事情发生的可能性越多,每种可能的概率越分散,则信息熵越大,即信息量越多。
比如中国有14亿人,这个事件的概率是1,大叫都知道的事情,所以没啥信息量。
在等概率情况下,简单的理解就是,但需要使用多少bit来存储某个数据。
熵编码
熵编码就是根据信息论的原理,对原始数据进行编码。它可以达到数据压缩的效果。广泛应用在压缩算法中。
熵编码分三种:哈夫曼编码,区间编码(算数编码),ANS。
区间编码
区间编码就是利用区间的变化,来承载数据。
基础的区间编码过程
编码
为了简化计算过程,假设只有4个符号,分别为A,B,C,D且每个符号的概率相等,都为0.25
初始区间为[0,512],这样编码依次编码AABB,过程如下:
1.编码的时候,按照符号的概率划分区间大小。然后根据符号的顺序,得到对应的区间。
比如,约定A,B,C,D分别对应第1,2,3,4区间。但编码A后,就得到第一个区间,区间的大小等于原始区间*A的概率。
2.编码结束后,从区间取任意一个数都可以作为编码结果。比如,编码完后,区间变成[10,12],取11可以作为编码结果。
解码
编码 | 区间 | 解码出的符号 | 新区间 |
---|---|---|---|
11 | [0,512] | A,因为11在第一个区间[0,128],所以解码出A | [0,128] |
11 | [0,128] | A,因为11在第一个区间中[0,32] | [0,32] |
11 | [0,32] | B,因为11在第二个区间中[8,16] | [8,16] |
11 | [8,16] | B,因为11在第二个区间中[10,12] | 结束 |
区间编码的证明
原始区间 | 编码后的区间 |
---|---|
range | range*p |
一个区间可以容纳range个值,所以区间能够容纳的信息为log(range)
编码前后信息差量为log(range)-log(range*p)=-log(p),正好等于符号的信息量。
基础的区间编码面临的问题
-
区间在不断的缩小,到后面无法编码新的符号。
区间容纳的信息有限,所以区间大小会趋于0,无法继续编码。
解决方案:如果把数字转成二进制,可以看到,编码后,区间上下沿的部分高位相同,而且后续编码中,该部分不会再变化。因此可以输出出来,来对区间进行扩展。
编码:
解码
-
当区间上下沿,高位不同,但区间却比较小,比如
[0111 1111,1000 0000],这是因为区间中存在延时数字,无法输出,导致区间无法扩展。这个也需要特殊的处理,可以将上下沿同时减去一个数后,在进行区间扩展。
3.区间共享
在zstd,lzma这些算法,是先通过lz77获取三元组序列,在对三元组序列进行熵编码,三元组中的各个数据在概率上来说并没有关联性,所以分开统计概率在进行熵编码能够得到更高的压缩率。
如果各自创建一个区间,并根据概率去编码就会产生麻烦,因为区间编码每个符号时,输出的bit并不是固定的,这样各个数据通过熵编码后的输出混合在一起后,解码的时候没法区分
解决方法:
通过观察可以知道,解码时候的跟区间没关系,跟概率表有关系。不管用什么区间去编码,只要使用的是同一套概率表,解码时,就能够解码出正确的符号。所以可以各个数据共用一个区间,然后应用各自的概率表对区间进行编码并输出。
4.固定概率 or 预测概率
- 固定概率:
可以先统计出三元组中各个符号的概率,然后应用熵编码进行编码。
缺点:需要提前扫描全部的三元组,增加计算量,同时输出的数据中需要保存概率表 - 预测概率
可以预设一个概率,然后编码的过程中,不断的调整概率表。如果不算固定概率方式中概率表的存储空间,这种方式的压缩率会稍微低一点,因为概率不准确。
备注:区间编码的原理比较简单,但是真正能够在压缩中使用时,就得解决上述问题,上述问题对应的解决方法,不容易理解,也不好表达,而且区间编码资料本身就很少,作者自己也是花了很大功夫,突然顿悟才想到解决方案。大家如果从事区间编码研究的,可以参考一下我的代码,看能得到灵感不。
https://github.com/sunny-shu/rangecode。
另外第三个问题的解决办法,在这个项目中,https://github.com/sunny-shu/lzrc