
一种方法如何能在没有任何先验信息的情况下,高效地压缩像文学经典、基因序列和金融市场数据这样迥然不同的数据?这是通用信源编码所要解决的核心问题,这类强大的算法构成了现代数据压缩的支柱。与需要预先分析统计数据的静态方法不同,通用编码通过动态学习来解决压缩未知信源的问题。本文将深入探讨这个引人入胜的主题,不仅解释这些算法的工作原理,还将阐明它们为何具有根本性的重要意义。在接下来的章节中,我们将首先揭示 Lempel-Ziv 等关键算法背后的自适应“原理与机制”,探索它们如何动态地构建数据模型。随后,我们将遍览其多样化的“应用与跨学科联系”,探索一个用于缩小文件的工具如何成为从人工智能到分子生物学等领域不可或缺的仪器。
一个算法如何能如此智能,在事先对莎士比亚的十四行诗、细菌的遗传密码以及金融数据流一无所知的情况下,对它们进行有效压缩?其秘诀不在于单一的静态码本,而在于自适应这一优雅的原则。通用信源编码算法不仅仅是编码器,它们还是学习者。它们以最少的假设开始,在处理过程中动态构建数据结构的模型,并调整策略以利用其发现的任何模式。
让我们踏上一段旅程,从最简单的技巧到信息论中一些最深刻的思想,来理解这种学习是如何发生的。
想象一下你的办公桌。如果你正在处理一个项目,你可能会拿出一个特定的文件夹。当你用完后,你可以把它放回文件柜中按字母顺序排列的位置,或者你也可以就把它放在桌上一堆文件的最上面。哪种策略更好?如果你很可能马上又要用到同一个文件夹,那么把它放在最上面可以省去你之后寻找它的麻烦。
这就是移至前端 (Move-to-Front, MTF) 算法背后优美而简单的思想。该算法维护一个包含字母表中所有符号的有序列表(例如 'A', 'B', 'C', ...)。当需要编码一个符号时,它传输的不是符号本身,而是该符号在列表中的当前位置——即其索引。然后,它会做一个关键操作:将该符号移动到列表的最前端。
考虑一个初始字母表列表 (A, B, C) 和消息 ACABBC。
(A, B, C)。(C, A, B)。(A, C, B)。通过继续这个过程,序列 ACABBC 被转换为索引序列 1, 3, 2, 3, 1, 3。注意,一件奇妙的事情发生了。如果一个符号频繁出现或以突发方式出现(这种特性称为时间局部性),它将倾向于停留在列表的前端。这意味着它将被编码为较小的整数(1, 2, 3...)。一个由小整数主导的序列比原始字符序列具有低得多的熵——即更少的“意外”——因此更容易被后续的压缩阶段处理。MTF 是一个预处理步骤,它将重复模式转化为小数值模式。
“移至前端”算法很巧妙,但它只学习了单个字符的近期使用情况。那么整个单词或短语呢?英语不仅仅是重复使用字母 'e',它还重复使用单词 'the'。压缩的真正威力来自于识别并替换这些更长的重复序列。
这正是 Lempel-Ziv (LZ) 族算法的天才之处,它们构成了像 GIF、PNG 和无处不在的 ZIP 文件等格式的核心。想象两个人,一个编码器和一个解码器,他们想要通信。他们从一个只包含单个字母(例如,A=0, B=1)的微型共享字典开始。
当编码器读取输入字符串,比如 BBAABABB 时,它会寻找已在字典中的最长字符串。
通过继续这个过程,输入 BBAABABB 被编码为序列 1, 1, 0, 0, 3, 2。神奇之处在于:解码器看到这个码流后,可以完美地重建出与编码器完全相同的字典,而这个字典从未被传输过!当解码器看到码 1 时,它知道这是 'B'。当它看到下一个码 1 时,它知道这是另一个 'B'。而且它也知道编码器肯定刚刚创建了一个新的字典条目:先前解码的字符串('B')加上当前字符串的第一个字符('B')。所以解码器也将 'BB' 添加为条目 2。两个字典以完美的同步方式增长。
这些算法学习信源的“语言”,为像 AB、BA、AC 等常见短语创建新词。更长、更重复的序列被一个单一的短码所取代,从而实现巨大的压缩。
自适应方案功能强大,但也必须稳健。当出现一个前所未见的字符时会发生什么?系统不能就此崩溃。像自适应 Huffman 编码 (Adaptive Huffman Coding) 这样的方案对此有相应的协议。除了已知符号(如 'A', 'B', 'C')的码之外,编码树还包含一个特殊的尚未传输 (Not-Yet-Transmitted, NYT) 或 ESCAPE 符号。
如果编码器需要发送一个新符号,比如 'Q',它首先传输 ESCAPE 的码。这告诉解码器:“注意,接下来的是新东西。”然后编码器发送一个预先约定好的、固定长度的 'Q' 的码。解码器接收到 ESCAPE 信号,读取定长码以识别 'Q',然后双方都将 'Q' 添加到它们的动态 Huffman 树中,为下一次出现做好准备。
然而,这种自适应性带来了一个关键的脆弱性。因为编码器和解码器是独立更新其内部状态(它们的字典或编码树)的,它们必须保持完美的同步。一个单一的错误就可能是灾难性的。
想象一下编码器想发送一个 'B',其码为 10。如果信道噪声翻转了第一位,解码器会收到 00...。如果 'A' 的码恰好是 0,解码器会将其解释为 'A'。然后它会根据看到了一个 'A' 来更新它的树,增加 'A' 的频率计数。而编码器则根据发送了一个 'B' 正确地更新了它的树。从这一点开始,它们的模型就出现了分歧。共享的语言已经破裂,后续的通信很可能会被解码成乱码。这凸显了一个根本性的权衡:动态自适应提供了惊人的压缩性能,但要求一个近乎完美的通信信道来维持同步。
我们已经看到了这些算法是如何工作的,但为什么它们对几乎任何类型的数据都如此有效?为什么我们称它们为“通用的”?答案在于一个更深层次的信息概念:Kolmogorov 复杂度。
Shannon 的信息论告诉我们如何压缩来自已知概率信源的数据。熵 设定了极限。但如果我们只有一个长的数据字符串呢?它的内在信息内容是什么?一个字符串的 Kolmogorov 复杂度是能够生成该字符串的最短计算机程序的长度。如果一个字符串有一个简短的描述,那么它是简单的;如果它的最短描述就是打印字符串本身,那么它就是复杂或随机的。
考虑两个各含十亿比特的字符串:
print "01101001..."。它的 Kolmogorov 复杂度很高,大约为十亿比特。像 Lempel-Ziv 这样的通用压缩算法,本质上就是在寻找那个短程序。当它看到 ABACABADABACABA... 时,它并不知道自己正在观察一个模式。但通过构建字典,它发现像 ABA 和 ABACA 这样的短语很常见。它在不自觉中发现了生成数据的简单底层规则。对于抛硬币生成的字符串,LZ 算法找不到任何超出偶然预期的重复模式,其压缩效果会很差。对于 的数字,它会迅速建立一个能捕捉字符串隐藏结构的字典,其压缩效果将非常出色。这就是通用性的承诺:将任何字符串压缩到接近其真实算法复杂度的大小。
通用编码的效果惊人地好,但它们不可能无所不知。一个预先知道信源确切统计特性的理想压缩器总能达到 Shannon 熵极限 。而一个必须学习这些特性的通用编码,则必须为其最初的无知付出一点小小的代价。这个代价被称为冗余 (redundancy),即与理想熵相比,每个符号多使用的比特数。
设计通用编码的目标是找到一个单一的编码 ,对于一系列潜在信源中最坏情况的信源,这个编码能最小化冗余。这就是极小化极大冗余 (minimax redundancy),。这个值量化了通用性不可避免的代价。
值得注意的是,这个代价是可以计算的。对于一族信源,它通常与一些深奥的理论概念相关,比如某个信道的容量,其中“输入”是未知的信源参数,“输出”是我们观察到的数据。例如,对于一个长度为 的二进制符号块,精确的极小化极大冗余可以计算为 比特。
最优美的结果是,对于许多通用编码方案,包括 Lempel-Ziv 家族,当数据长度 变得很大时,这种冗余会消失。每个符号的压缩长度会趋近于信源的真实熵。该算法为了学习数据结构支付了一个小的、固定的成本,一旦结构被学会,其性能就与一个从一开始就知道所有情况的理想编码几乎无法区分。无知的代价是真实存在的,但只要有足够的数据,这个代价我们只需支付一次。这是通用信源编码的最终胜利和深邃之美。
在上一章中,我们揭示了通用信源编码背后那个优美、近乎神奇的原理:在没有任何关于数据统计结构的先验知识的情况下,高效压缩数据的能力。我们看到,像 LZW 这样的算法通过动态学习,在遇到模式时建立模式字典来实现这一点。这是一个强大的技巧,但其真正的意义不仅在于其巧妙,更在于其广泛且常常令人惊讶的应用范围。
要了解一个事物的本质,我们必须看它做什么。因此,在本章中,我们将踏上一段旅程,去看看这些通用算法在何处发挥作用。我们将从它们作为我们数字世界的主力这一最熟悉的角色开始,然后前往科学的前沿,在那里它们成为解码自然界隐藏模式的强大探针。我们将发现,一个为缩小文件这样实际目的而设计的工具,可以为统计物理学、分子生物学,甚至为一个系统拥有“记忆”意味着什么的定义本身,提供深刻的见解。
当然,通用编码最直接、最广泛的应用是数据压缩。每当你保存一个 GIF 图像、创建一个 ZIP 压缩包或发送一个 PDF 文件时,你很可能就在使用 Lempel-Ziv 家族中的一种算法。这些方法的天才之处在于其优雅的简洁性和适应性。
想象一下像 LZW 这样的算法在读取一段文本。当它看到一个常用短语,比如“the theory of relativity”,它不会逐个字母地编码。在见过一次之后,它会说:“啊哈!我要为这个整个短语创建一个新的短代码。”下次这个短语再出现时,算法就会发送那个单一的短代码,而不是 23 个单独的字符。这是基于字典的压缩的基本思想。但真正了不起的部分是字典的管理方式。编码器和解码器在完全同步的情况下构建它们的字典,而无需传输字典本身!解码器看到代码序列后,可以完美地推断出编码器在此过程中必须创建了哪些新条目,从而重建出完全相同的字典,并由此重建原始文本。
这种自适应策略并不仅限于某一个特定算法。其核心原则是不断更新数据的内部模型。一些方案可能会通过调整用于表示常见模式的比特数来自适应,随着它们学习到数据源的“节奏”而变得更加高效。另一些方案则可能在检测到数据统计特性发生变化时,动态地重构整个编码字典,删除不再常见的模式的表示,并为新出现的模式创建新的表示。这种适应局部统计特性的能力,正是使这些算法成为“通用的”并对我们硬盘中各种数据类型如此有效的原因。
如果我们看得更仔细一些,就会发现通用压缩器所做的事情远比仅仅压缩数据要深刻得多。它本质上是一台学习机器。要压缩一个序列,它必须首先学习其底层结构。它含蓄地构建了数据源的统计模型。
这种联系在像 Krichevsky-Trofimov (KT) 估计器这样的方法中变得十分明确。想象一下,你试图预测一个二进制序列中的下一个比特。如果你已经看到了十个比特——比如说,七个 1 和三个 0——一个简单的猜测是下一个比特为 1 的概率可能是 0.7。但如果你一个比特都没看到呢?或者只看到了一个?KT 估计器为顺序地进行这些预测提供了一种稳健的方法。它的工作方式是为每个符号赋予一个小的“伪计数”(比如在开始前假设你已经看到了半个 '0' 和半个 '1'),然后在观察到更多数据时更新概率。这是贝叶斯统计中的一种经典技术,相当于从一个“先验信念”开始,并用证据来更新它。
因此,这样一个编码器为下一个符号分配的增量比特成本,是衡量其在给定过去信息的情况下的“惊奇程度”的指标。一个可预测的符号编码成本很低;一个令人意外的符号则成本高昂。这个视角将通用压缩重新定义为一种在线预测或机器学习。
我们甚至可以使用强大的数学工具来分析这些学习系统的长期行为。例如,一些数据流可能包含特殊的“重置”符号,迫使压缩器重新初始化其统计模型。利用再生过程理论,我们可以精确计算系统的长期平均属性,比如观察到某个特定符号的总概率。这表明,这些算法的自适应行为不仅仅是一种启发式技巧;它植根于严谨的数学理论,将信息论与随机过程的研究联系在一起。
也许通用编码最激动人心的应用是将其用作科学发现的工具。通过测量一段数据的可压缩性,我们可以了解创造它的过程的基本属性。压缩率不再仅仅是一个技术指标,而变成了一种科学测量。
考虑一个物理系统的模拟,比如 Ising 磁性模型,其中无数微小的原子自旋可以指向上或下。在非常高的温度下,自旋处于混乱状态;每个自旋都独立于其邻居随机翻转。由此产生的数据流是无序和不可预测的,很像一系列随机抛硬币的结果。一个通用压缩器找不到任何模式或重复,几乎无法压缩这些数据。压缩后的长度将几乎等于原始长度。
现在,让我们把系统冷却下来。当它接近一个临界温度时,自旋开始相互影响,形成大的、有序的区域,其中所有自旋都指向同一个方向。系统变得更加有序和可预测。模拟生成的数据流现在是高度重复的。通用压缩器在这里会表现出色,识别出这些大的均匀块,并用非常短的代码来表示它们。数据变得高度可压缩。
通过这种方式,模拟输出的可压缩性就像一个“计算温度计”或“有序度计”。仅仅通过压缩数据,我们就可以区分系统的高温(无序)和低温(有序)相。通用压缩器所隐含测量的、信息论中的熵概念,成为了物理系统热力学熵的直接代表。
同样的原理也可以应用于我们这个时代最伟大的科学挑战之一:理解基因组。DNA 序列不是 A、C、G 和 T 的随机字符串。它是用生命语言写成的文本,具有复杂的“语法”和“词汇”结构。一些称为外显子 (exons) 的区域包含了构建蛋白质的关键指令。这些序列承受着巨大的进化压力,从而形成了复杂且信息丰富的结构。
其他区域,称为内含子 (introns) 和基因间 DNA,通常包含长的、重复的序列,有时被称为“垃圾 DNA”。虽然我们现在知道这些区域中有许多具有功能,但它们在统计上通常比外显子简单得多,也更具重复性。
通用压缩器能分辨出这种差异吗?绝对可以。当我们把 DNA 序列输入到像 LZ78 这样的算法中时,它会给我们一个衡量该序列复杂度的指标。一个高度复杂、不可压缩的序列很可能是一个信息丰富的外显子。一个简单、高度可压缩的序列则更可能是一个重复的内含子或基因间区域。这将数据压缩从一个计算机科学工具转变为一个强大的计算基因发现特征,帮助生物学家在浩如烟海的数据中定位基因组中最重要的部分。
最后,对通用编码的思考可以迫使我们重新审视科学和工程中的一些最基本概念。以“无记忆”系统的概念为例。在信号处理中,如果一个系统的当前输出仅依赖于其当前输入,那么该系统就是无记忆的。一个简单地将输入信号乘以一个常数的放大器就是无记忆的。
现在,考虑这样一个系统,其对于给定的输入流的输出是该流经通用算法压缩后的表示长度。这个系统是无记忆的吗?乍一看,人们可能倾向于回答是,但深入思考后会发现恰恰相反。在时间 的压缩输出长度取决于直到时间 的整个输入历史,因为算法正是用这段历史来构建其字典的。适应和学习的行为本身就需要记忆。事实上,可以证明,对于任何非平凡的输入序列,这样的系统都不可能表现出无记忆性。因为随着我们添加更多符号,压缩后的大小必须总是增加,所以时间 的输出永远不可能等于更早时间 的输出,即使输入符号相同。这给了我们一个深刻的洞见:任何真正从经验中学习的系统,根据定义,都必须是一个有记忆的系统。
从压缩你电脑上的文件到测量模拟宇宙的熵,从在 DNA 链中寻找基因到重新定义记忆的含义,通用信源编码远不止是一个巧妙的算法。它是一个触及学习、预测以及信息本质的基本原则。它向我们展示了,寻找和描述模式的追求是一条统一的线索,贯穿于人类各种令人惊叹的努力之中。