压缩即预测

查看原文 HN 讨论

文章摘要

Annie Sexton 这篇约 3700 字、配了大量交互式演示的长文,从压缩的基础一路讲到 LLM,论证一个核心命题:压缩器和大语言模型在最底层解决的是同一个问题。

压缩到底是什么。 文章先用「代码压缩(minification)」做反例:它把人类可读的变量名缩成单个字母、去掉空白和注释,文件确实小了很多,但你几乎不会在数据压缩领域听到有人提它。原因是 minification 只是扔掉机器不需要的语法,而「真正的」压缩依赖冗余。作者用一个由连续重复字符构成的字符串演示了游程编码(run-length encoding):把每一段连续重复替换成「字符 + 重复次数」,28 个字符的 224 位缩到 12 个字符的 96 位,小了 57%。

压缩器的三个「器官」。 现代压缩工具大致由三部分组成:变换(transforms)模型(models)熵编码器(entropy coders)。变换是让数据更容易压缩的预处理步骤,游程编码就是一种变换;值得注意的是变换不一定缩小数据,有时它反而是用来制造更多冗余的,冗余越多后面能压得越狠。模型根据每个符号(字母、数字、token、甚至二进制码)的频率描述数据的形状——现在你可以把它想成一张符号到概率的映射表,但它可以复杂得多。熵编码器几乎总是最后一步,产出最终的压缩产物:一段原始比特流。

文章特别强调中间那个环节:数据模型把一组概率交给熵编码器,概率进去,压缩比特流出来。

用概率压缩数据:算术编码。 作者选算术编码来讲解,因为它最能说明「更好的概率带来更好的压缩」。想法是用一个数字表示整个数据集。以字符串 “ABABAAC” 为例,统计出 A 概率 0.571、B 概率 0.286、C 概率 0.143,把 0 到 1 的区间按概率切成三段。然后对字符串里的每个符号,把当前区间收缩到该符号所占的那一段——注意收缩后仍然用同样的概率去切分这个新的、更小的区间。走完所有符号后得到一个极小的区间 [0.38730, 0.38855),最终数字可以是这个区间里任何一个数,理想情况下应该是所需比特最少的那个:0.3876953125。原字符串按 8 位 ASCII 要 56 位,这个数字只要 10 位。作者特别说明这不是浮点数而是二进制小数——浮点数也是二进制小数,但宽度固定,不管需不需要都得付 32 或 64 位的代价,而这个只需要 10 位。解压时,解压器拿到同样的概率表重建 [0,1) 区间,看这个魔法数字落在哪一段就记下哪个符号,然后把区间收缩到那一段,如此反复。

概率如何影响压缩。 作者换了一个 A 占绝对主导(概率 0.833)的 12 字符字符串对比:原来 7 个符号的串压到平均 1.38 比特/符号,新的 12 个符号的串压到 0.82 比特/符号。结论是:数据分布越偏斜(某些符号概率越高),压缩比越好。

这个「平均比特/符号」就是——不是物理里的熵,而是香农熵,不过作者指出它的数学形式和热力学里的吉布斯熵公式几乎一样。她用一个直观的例子解释:「昨天我在市区散步时看到一只动物,那是一只 ___」。如果答案是常见的鸟,你可能一次就猜中;如果是熊,大概要猜好几次。给定各动物的概率(1/2、1/4、1/8、1/16、1/16),可以用一棵是/否决策树算出平均需要多少次猜测。把动物当成符号、把「是/否」换成 1 和 0,猜测次数就正好等于表示它所需的比特数——常见动物得到更短的码字,罕见的得到更长的。这种给符号分配码字的方式就是另一种熵编码器:哈夫曼编码,gzip 和 Brotli 都在用它;与算术编码把数据编成单个数字不同,哈夫曼是为每个符号造码字。

但哈夫曼有个问题:当概率不是整齐的二分之一时怎么办?树上每条路径都是整数次「猜测」,所以必须取整,而取整意味着为不需要的比特付费。精确答案由公式给出:所需比特数 = −log₂(概率)。把动物的概率代进去,得到的比特数和决策树的猜测次数完全一致。所有符号的 −log₂(概率) 的平均值就是熵。

关于熵,最重要的一点是:它是地板——这是给定数据所能达到的最小平均比特数,再也压不动了。(作者注明这只适用于无损压缩;JPEG、MP3 这类有损压缩通过扔掉不会被察觉的细节可以压得更小,但同样依赖模型和概率。)

那为什么没有一个万能的「上帝压缩器」?因为熵是相对于一组特定概率而言的。如果我们能让概率分布更偏斜,就能压得更狠。

上下文很重要。 到目前为止用的模型只关心符号频率(出现次数除以总符号数)。但上下文能极大改变一个符号的概率:在整个英语里字母 U 的概率约 0.028,但紧跟在 Q 后面时飙升到约 0.999。换算成比特:−log₂(0.028) ≈ 5.158 比特,而 −log₂(0.999) ≈ 0.001 比特。用单个上下文决定符号概率叫做 order-1 模型,回答的是「给定某个上下文,某符号的概率是多少」;可以扩展到 order-2、order-3……也就是看前 N 个符号。代价是模型不再是一张概率表,而是一整组表,每个前导符号一张。作者用 “TO BE OR NOT TO BE” 这个 18 符号字符串做了对比:无上下文时压到约 47 位、平均 2.59 比特/符号;用 order-1 模型压到约 21 位、平均 1.16 比特/符号——砍掉了一半以上。加上下文给了我们更强的概率,换句话说,它帮我们预测下一个符号

「还有什么东西特别擅长预测呢?」

语言建模与压缩。 作者引用了 DeepMind 2023 年那篇论文,主张语言建模和压缩是同一件事的两种视角。LLM 常被形容为「花哨的自动补全」,这基本属实:你提交 prompt,它成为模型用来返回下一个可能词的概率分布的上下文,然后选一个附加到上下文里,如此循环。

关键的转折在这里:熵编码器虽然产出最终比特流,但里面没有任何可调的东西——它们是固定的、确定性的、无损的。想要更好的压缩,你必须改进模型,让每个符号获得更高的概率。换句话说,你需要一个更好的预测器。而说到预测,LLM 基本上就是天花板。

用 LLM 做压缩和用它生成文本几乎一样,只有一处不同:我们不选下一个词。因为我们不是要生成新文本,我们已经知道下一个词是什么了。流程是:基于前面的 token,模型说「这些是我认为接下来的 token 以及它们的概率」;然后看真实的下一个符号是什么;模型给它分配的概率就决定了它的比特代价(同样是 −log₂(概率))。模型训练得好,它认为概率最高的 token 就会是真实的下一个符号。

反过来,模型没训练好就要付出代价:如果上下文是「The rain in」,一个训练不佳的模型可能给「Bermuda」0.82 的概率而给真实答案「Spain」只有 0.02——前者只要 0.29 比特,后者要 5.64 比特。在算术编码里这体现为区间的坍缩:编码低概率符号会让区间变得极小,而最终数字必须落在区间里,区间越小需要的精度越高,精度越高就是更多位数、更多比特。作者用一组对比图展示:好模型的区间在三层之后仍然很宽,最终编码成 9 比特;坏模型的区间收缩成一条细缝,需要 28 比特。

即便是今天看来很烂的老模型,压缩比也相当可观。文中用狄更斯《双城记》开头那段著名文字做测试:order-1 模型压到 434 比特(原始的 24%),GPT-2 压到 176 比特(原始的 10%)

现实世界里的压缩。 那既然 LLM 这么会压,为什么不到处用?因为压缩工具的目标不是「尽可能压小」,而是「在特定资源约束下尽可能压小」。以 HTTP 响应为例:浏览器发 Accept-Encoding: gzip, br 告诉服务器自己能解哪些格式,服务器挑一个压缩响应。浏览器收到后用一个内置的小模型解出 HTML、CSS、JS,开销极小。如果换成 LLM,浏览器和服务器都需要一份可能几个 GB 的模型副本——这是为好压缩率付的高昂代价,而且还没开始跑呢;压缩和解压会消耗大量资源,把页面加载速度拖到无法使用的地步。「想象一下:每一个样式表、每一个脚本、每一个 JSON 载荷都要跑一次 LLM 来压缩和解压。恶心。」对压缩 HTTP 响应这种小任务,LLM 是滑稽的杀鸡用牛刀——把模型体积算进去,你为了省几 KB 要发几个 GB。但即使你压缩的数据集远大于 LLM 本身,所需的天文数字级算力仍然让它不切实际。

一枚硬币的两面。 结论部分把观点收拢:把数据压到它的熵,在今天已经是个已解决的问题——1970 年代末发明的算术编码能落在极限之内几个比特的范围,如今熵编码器竞争的是速度和内存,不是压缩率。真正的开放问题是我们能把熵做到多小。更好的模型、更好的预测器能拉低这个数字。LLM 在这件事上极其出色(先不算开销),但真正有意思的是:它们被训练的目标正是最小化这个「每符号比特数」——在 LLM 里这叫交叉熵(cross-entropy),但底层公式是同一个。所以在压缩里熵衡量的是我们能把东西缩到多小,而在语言建模里它是我们为了让模型更会预测而去降低的数字。归根结底,LLM 和压缩算法都是预测器,是同一套底层数学的两种表达。压缩即预测,而 LLM 就是压缩器。

HN 评论精华

这条帖子拿到 668 分、295 条评论。讨论质量相当高,主线有三条:这个命题的历史渊源(大量指路)、它对「LLM 能否产生新想法」之争的意义,以及一场关于「压缩等价于预测是否隐含了过强假设」的精彩辩论。