读数据压缩入门笔记04_统计编码

Posted 躺柒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读数据压缩入门笔记04_统计编码相关的知识,希望对你有一定的参考价值。

一种新的精确熵编码方法,所得到的结果可以和最优熵任意接近,它的压缩率与算术编码接近,而性能则与哈夫曼编码相当

1. 统计编码(statistical encoders)的算法

1.1. 每种编码方法都对每个符号的概率分布做了不同的假定

1.2. 需要处理的数据集中符号的概率分布与现有的VLC方法都不能完全匹配

1.3. 统计编码算法通过数据集中符号出现的概率来进行编码使结果尽可能与熵接近

1.4. 给定任何输入数据,我们都能为其构造出一套自定义的码字集,而无须去匹配现有的VLC方法

1.5. 该算法以数据流中符号的频率为依据,为该数据流中的各个符号分配长度可变的码字,从而使最终的输出压缩得更小

2. 国际电信联盟H.82建议书(ITU-T,1993)将熵编码定义为“任意无损的压缩或解压数据的方法”

3. 熵编码的技术用途

3.1. 也可以称为“香农–范诺编码”或“哈夫曼编码”

3.2. 给定二进制位率时增加信噪比

3.3. 给定信噪比时减少二进制位率

4. 香农–范诺编码

4.1. 由香农于1948年提出,随后他将它告诉了罗伯特•范诺(Robert Fano),范诺后来将它作为技术报告正式发表了

4.2. 最早通过符号及其出现概率来生成VLC的方法之一

4.3. 它没有达到最短的码字长度预期,但它已经很接近了

4.4. PKZIP/IMPLODE格式使用了两到三棵香农–范诺树(Shannon-Fano tree)

5. 哈夫曼编码

5.1. 计算机科学和数据通信领域内人员一直都在使用的基本思想之一

5.2. 对于给定的数据集,为了产生小的、自定义的VLC,你需要一个输入是概率列表、输出是码字的算法

5.3. 哈夫曼编码可能是生成自定义VLC最直接、最有名的方法

5.3.1. 给定一组符号及其出现频率,该方法能生成一组符号平均长度最短的VLC

5.3.2. 如果使用二叉树,就能利用符号表中的概率与二叉树的分支来创建优化的二进制代码

5.4. 工作原理

5.4.1. 将数据排序后建立决策树(decision tree),然后从“树干”一直往下直到“树叶”为止,并记录下所做的是/否选择

5.4.2. 为了获得给定符号(叶子节点)的码字,需要从根节点“沿着树枝”往下走,并将所得的1和0按从MSB到LSB排列起来,也就是从左排到右

5.5. 由于创建哈夫曼树(需使用计算资源)要比传输符号码字对应表(会增加数据流大小)困难得多,因此总是应该将码字对应表加在数据流的前面,而不是在解码时再重新创建一次

5.6. 简单、高效,也能为单个的数据符号生成最佳的码字

5.7. 对于给定的符号集来说,它并非总是生成最有效的码字

5.8. 能生成理想VLC(即码字的平均长度等于符号集的熵)的唯一情形是,各个符号的出现概率等于2的负整数次幂(即是1/2、1/4或1/8这样的值)

6. 算术编码

6.1. 早在20世纪60年代初,Peter Elias就首先提出了算术压缩背后的概念(即算术编码)

6.2. 20世纪70年代,才由IBM公司的Jorma Rissane针对其实现发表了第一个有效的研究,随之而来的还有相应的专利

6.3. 会将整个输入流转换为一个长度很长的数值,而它的lb表示则与整个输入流真正的熵值很接近

6.4. 它将转换应用到整个源数据上以生成一个输出值,而表示这个输出值所需要的二进制位数比源数据本身少

6.5. 现代主流的文件、音频和视频的压缩格式(如LZMA和BZIP这样的文件格式,JPEG、WebP、WebM和H.264这样的音视频格式),在统计编码步骤上都会使用算术编码压缩方法

6.6. 工作原理

6.6.1. 将字符串转换为一个数,与字符串相比,表示这个数需要的二进制位数要少一些

6.7. 目前的主流算法

6.7.1. 应用在大多数的多媒体编码器中,甚至有了有效的硬件实现

7. 区间编码

7.1. Range Coding

7.2. 1979年

7.3. 所做的事情与算术编码基本相同,却不受算术编码相关专利的约束

8. ANS

8.1. 2007年

8.1.1. 在数据压缩领域里出现的时间还不长

8.1.2. 已开始取代过去20多年里占据主流地位的哈夫曼编码和算术编码

8.1.2.1. ZHuff、LZTurbo、LZA、Oodle和LZNA这些压缩工具已开始使用ANS

8.2. 2013年

8.2.1. 又出现了一个被称为有限状态熵(Finite State Entropy,FSE)的更注重性能的版本

8.2.1.1. 它只使用加法、掩码和移位运算,使ANS对开发人员更具吸引力

8.3. 2015年

8.3.1. 推出了一款名为LZFSE的GZIP变种,作为苹果下一代iOS版本的核心API

8.4. Jarek Duda引入了一种新的与数据压缩有直接关联的信息论概念:非对称数字系统(asymmetric numeral systems,ANS)

8.5. 一种新的精确熵编码方法,所得到的结果可以和最优熵任意接近,它的压缩率与算术编码接近,而性能则与哈夫曼编码相当

8.6. 工作原理

8.6.1. 根据符号的出现频率对数值区间进行细分

8.6.2. 创建一张表,将子区间与离散的整数值关联起来

8.6.3. 每个符号都是通过读取和响应表中的数值来处理的

8.6.4. 向输出流中写入可变的二进制位位数

8.7. tANS是ANS的一种变体,它是围绕着一张表格工作的

8.7.1. 创建备查表

8.7.1.1. 它使得从符号转换为数值再从数值转换为符号成为可能

8.7.1.2. 表中的每个值都是唯一的(即不存在重复)

8.7.1.3. 每列都按照值从小到大排序

8.7.1.4. 每行的值都要比该行的行号大

8.7.2. 想要tANS变成真正的熵编码器

8.7.2.1. 在确定每一列值的个数时,需满足该值乘以maxVal后,等于该列符号的出现概率

8.7.2.2. 在确定每一行的值时,需确保该行列选择的值与该列符号的出现概率一致,这样当用该值除以行号,所得商就会(近似)等于该列符号的出现概率

8.7.3. 压缩来自于逐位输出(bit-wise output)

8.7.3.1. 出现可能性越小的符号其列高越低,有效的行号值离最可能出现的符号也就越远(二进制位距离意义上的远)

8.7.3.2. 为了得到更小的行号,就需要进行更多次的右移操作,这也意味着每次循环会有更多的二进制位输出到数据流

8.7.3.3. 出现可能性越小的符号,就会输出更多的二进制位到最终的数据流中

读数据压缩入门笔记03_VLC

一种表示整数的方法,它用一个或多个字节来表示一个整数,数值越小用的字节数越少,数值越大用的字节数越多

1. 概率、熵与码字长度

1.1. 数据压缩的目的

1.1.1. 给定一个数据集中的符号,将最短的编码分配给最可能出现的符号

1.2

1.2.1. 当P(A)=P(B),也就是两个符号等可能出现时,数据集对应的熵取最大值LOG2(符号的个数),此时数据集很难压缩

1.2.2. 其中一个符号出现的可能越大,数据集的熵值就越小,此时数据集也越容易压缩

1.2.3. 对只包含两个符号的数据集来说,两个符号互换概率不影响其熵值

1.3. 启示

1.3.1. 随着数据集的冗余度下降,它的熵在变大,其最大值为数据集中不同符号个数的LOG2值

1.3.2. 数据集中一个符号出现的概率越大,整个数据集的熵就越小,数据集也就越容易压缩

1.3.3. 码字的长度与符号的出现概率密切相关,而与符号本身没有太大关系

2. VLC算法

2.1. 在过去的40多年中,人们创造了数百种VLC算法

2.2. 在为数据集选择一种VLC编码方法的考虑因素

2.2.1. 数据集的整体大小

2.2.2. 数据范围

2.2.3. 计算各个符号的出现概率

2.2.4. 如果不这样做,得到的结果可能就是,数据集的大小不但没有压缩,有可能反而比原来的数据集还大

2.3. 存在的主要问题

2.3.1. 它们不按字节 / 字 / 整型对齐

2.3.2. 对于大的数值N,为了方便解码,其码字长度的增长速度一般会超过lb(N)个二进制位

2.3.3. 解码的速度很慢(每次只能读取一个二进制位)

2.3.4. 只能用于表示压缩数据流,没有其他应用

3. 设计VLC集的码字原则

3.1. 越频繁出现的符号,其对应的码字越短

3.2. 码字需满足前缀性质

4. 前缀性质

4.1. 如果一个码字是另一个码字的前缀,那么用VLC解码二进制流就会很难

4.2. 某个码字被分配给一个符号之后,其他的码字就不能用该码字作为前缀

4.2.1. 每个符号都能通过其码字前缀唯一地确定

4.3. 前缀性质是VLC能正常工作所必须具有的性质

4.3.1. 与二进制表示相比,VLC要更长一些

5. 唯一可译码

5.1. uniquely decodable codes

6. 非奇异码

6.1. nonsingular codes

7. 每一种前缀编码都是唯一可译的和非奇异的

8. VLC编码步骤

8.1. 遍历数据集中的所有符号并计算每个符号的出现概率

8.1.1. 画出数据集中所有符号的直方图

8.2. 根据概率为每个符号分配码字,一个符号出现的概率越大,所分配的码字就越短

8.2.1. 根据出现的频数对直方图进行排序

8.2.2. 给每个符号分配一个VLC,从VLC集中码字最短的开始

8.3. 再次遍历数据集,对每一个符号进行编码,并将对应的码字输出到压缩后的数据流中

9. VLC解码步骤

9.1. 由于码字的长度并非是固定的,因此解码过程还是稍微有些复杂

9.2. 解码的时候,我们会一二进制位一二进制位地读取数据,直到读取的二进制位流与其中的某个码字相匹配

9.3. 一旦匹配,就会输出相应的符号,并继续读取下一个码字

10. 摩尔斯码

10.1. 1836年

10.1.1. 画家Samuel F. B. Morse

10.1.2. 物理学家Joseph Henry

10.1.3. 机械师Alfred Vail

10.1.4. 发明了第一套电报系统

10.2. 克劳德•香农

10.2.1. 摩尔斯码方面的专家

10.3. 最简单的编码文本信息的方法

10.3.1. 用数字126来编码AZ的英文字母

10.4. 发送一次信息所需要的人工操作次数太多

10.4.1. 物理硬件(发报机设备)和人工硬件(也就是操作人员的手腕)的磨损比预期的要快,解决方法则是使用统计来减少工作量

10.5. 对符号分配变长编码(variable-length codes,VLC)的最初实现之一

10.6. 为英语字母表中的每一个字符都分配了或长或短的脉冲,一个字母用得越频繁,其编码也就越短、越简单

10.6.1. 目的则在于减少传输信息过程中所需要的总工作量

11. 通用编码

11.1. universal codes

11.2. 一种将整数转换为VLC的独特方法

11.3. 一类特殊的前缀编码

11.4. 为正整数赋上一个长度可变的二进制码字

11.5. 数值越小,其对应的码字也越短

11.5.1. 因为假定小整数比大整数出现得更频繁

12. 二进制编码

12.1. 不满足前缀性质

12.2. 用B(n)来表示整数n的标准二进制表示

12.3. beta编码或二进制编码

12.4. 给定0~N的任意整数,都能用1+floor(lb(n))个二进制位来表示

12.4.1. 只要提前知道N的值,就能通过固定长度表示法来避开前缀问题

12.4.2. 如果不能提前知道数据集中有多少个不同的整数,就不能用固定长度表示法

13. 一元码

13.1. 满足前缀性质

13.2. 任意正整数N,它的一元码表示都是N-1个1后面跟着1个0

13.2.1. 4的一元码表示为1110

13.3. 整数N的一元码长度也是N个二进制位

13.4. 将一元码应用在那些前一个符号的出现概率是后一个符号2倍的数据集上,效果最佳

13.5. 如果每个整数N的出现概率P(N)服从指数分布2^(-N),即1/2、1/4、1/8、1/16、1/32,其他以此类推,就可以使用一元码进行编码

14. Peter Elias

14.1. 1923年11月23日生

14.2. 1955年,他就引入了卷积码(convolutional codes),作为分组码(block codes)的一种替代方法

14.3. 建立了二进制删除信道(binary erasure channel),并提出了用纠错码的列表译码(list decoding of error-correcting codes)来代替唯一可译码(unique decoding)

14.4. Elias gamma编码

14.4.1. 用于事先无法确定其上界的整数的编码

14.4.1.1. 不知道最大的整数会是多大

14.4.2. 对整数n的出现概率P(n)=1/(2n*n)的情形比较适用

14.4.3. 最主要的思想是不再对整数直接编码,而是用其数量级作为前缀

14.4.3.1. 相应的码字就由两部分组成,即与此整数相当的2的次幂再加上余数

14.4.4. 工作原理

14.4.4.1. 找出最大的整数N,使其满足2N<n<2(N+1),并且将n表示为n=2^N+L这样的形式

14.4.4.1.1. L=n-2^N
14.4.4.1.2. n=12,23=8,24=16,23<n<24,N=3
14.4.4.1.3. L=12-2^3=4

14.4.4.2. 用一元码表示N

14.4.4.2.1. N=3,一元码110

14.4.4.3. 将L表示为长为N的二进制编码,并加在步骤(2)中得出的一元码之后

14.4.4.3.1. 有了这样的对称性,后面才能顺利解码
14.4.4.3.2. L=4,其对应的长度为3的二进制码为100
14.4.4.3.3. 将前两个步骤得出的编码连接,就得到了最终的输出110100

14.5. Elias delta编码

14.5.1. 对整数N的出现概率P(N)等于1/[2n(lb(2n)*lb(2n))的数据集来说是理想的选择

14.5.2. 工作原理

14.5.2.1. 将要编码的数N用二进制表示

14.5.2.1.1. 将N=12表示为二进制1100

14.5.2.2. 数一下N的二进制位数,并将这个位数转化为二进制,作为C

14.5.2.2.1. 12的二进制表示共有4位,将4表示为二进制,即C = 100

14.5.2.3. 去掉N的二进制表示的最左边一位,这个值肯定是1

14.5.2.3.1. 去掉N=12的二进制表示的最左一位,得到100

14.5.2.4. 将C的二进制表示加在去掉最左边一位后的N的二进制表示之前

14.5.2.4.1. 将C = 100加到上一步骤所得的结果之前,得到100100

14.5.2.5. 在上一步骤所得的结果前加上C的二进制位数减1个0作为最终的编码

14.5.2.5.1. 将C的二进制位数减1,即3-1 = 2,在上一步骤所得的结果前加上2个0,由此得到12的最终编码为00100100

15. 谷歌的Varint算法

15.1. 最基本的概念早在1972年就提出

15.2. 2010年作为“避免压缩整数”(escaping for compressed integers)而被重新引入

15.3. 是一种表示整数的方法,它用一个或多个字节来表示一个整数,数值越小用的字节数越少,数值越大用的字节数越多

15.3.1. 结合了VLC的灵活性和现代计算机体系结构的高效率,是一种很好的混合方法

15.3.2. 既允许我们表示可变范围内的整数,同时还对自身的数据进行了对齐以提高解码的效率,达到了双赢

15.4. 方法

15.4.1. 将几个字节连接起来表示整数

15.4.2. 并用每个字节的MSB作为布尔标志,来判断当前的字节是否为该整数的最后一个字节

15.4.3. 每个字节的低7位则用来存储该数的二进制补码(two\'s complement representation)

15.4.4. 整数1可以用一个字节表示,因此它的MSB不需要设置,可表示为00000001

15.5. 示例

15.5.1. 10101100 00000010

15.5.1.1. 10101100 00000010 → 0101100 0000010

15.5.1.1.1. 删掉每个字节的MSB
15.5.1.1.1.1. 它的作用只是判断当前字节是否是最后一个字节
15.5.1.1.1.2. 第一个字节的MSB已经设置为1,因为用Varint方法来表示,该数需要多个字节

15.5.1.2. 0101100 0000010

15.5.1.2.1. 将剩下的两个7二进制位的数据顺序颠倒一下
15.5.1.2.1.1. 用Varint方法表示时,低位的字节在前

15.5.1.3. 0000010 0101100

15.5.1.3.1. 将二进制表示转换为十进制数,就得到了最终的数值300

以上是关于读数据压缩入门笔记04_统计编码的主要内容,如果未能解决你的问题,请参考以下文章

读数据压缩入门笔记02_二进制和熵

读改变未来的九大算法笔记01_数据压缩

读Java性能权威指南(第2版)笔记02_ Java SE API技巧上

读Java性能权威指南(第2版)笔记03_ Java SE API技巧中

读Java实战(第二版)笔记04_用流收集数据

读C#代码整洁之道笔记01_C#的编码标准和原则