gzip 假装会写诗:压缩就是预测,预测就是理解
目录
- 一行真实的 gzip 诗
- 压缩就是预测:信息论的第一课
- 32 KiB 的记忆:DEFLATE 的窗口
- 单字节的困境:量化噪声
- beam search 救场:gzip 学会「往前看」
- 现场验证:把莎士比亚塞进 zlib
- 中文的密度:为什么 gzip 骗不过中文
- 它记住的不是意思,是概率
一行真实的 gzip 诗
先看一段输出。这不是人类写的,也不是神经网络写的——是 gzip 写的,就是那个你天天用来压缩文件的 gzip:
MENENIUS: Though all at once canq
MARCIUS: Pray now, nocamest thou to a morsel .
LARTIUS: Hence, and I' the end admire, where
G again; and after it ag .
乱码、错字、语法崩坏——但它「知道」自己在写莎士比亚。它知道人名要大写、后面跟冒号、一句台词以换行收尾。它甚至分得清 MENENIUS 和 MARCIUS 是不同的人名。
这段输出来自 Nathan Barry 的一篇博客《Can gzip be a language model?》(HN 322 分)。他把莎士比亚的全文喂给 gzip——不是训练,不是微调,只是把语料放进压缩器的滑动窗口——然后让一个叫 GziPT 的小工具用 beam search 逐字节「续写」。
结果出人意料地不糟糕。而这件事最妙的地方在于:gzip 里没有任何一行代码是「为了写诗」写的。
压缩就是预测:信息论的第一课
为什么压缩器能「续写」文本?
因为压缩的本质就是预测。这是信息论的第一课,香农在 1948 年就讲清楚了:
编码一个符号所需的比特数是 $-log_2 p$,其中 $p$ 是模型赋予它的概率。
高概率的事件用很少的比特就能描述,低概率的事件需要很多比特。所以任何压缩器内部都藏着一个概率模型——不管有没有人把它写下来。
你给压缩器一个全是字母 A 的文件,它能压到极小,因为它「预测」下一个还是 A。你给它一个随机字节流,它几乎压不动,因为它「预测」不了任何东西。
「压缩即预测」不是 Nathan Barry 的一家之言。2023 年,DeepMind 的研究者在 GenBench 上发了一篇论文叫 Language Modeling is Compression,用 11 个算法(含 gzip)在文本数据集上做压缩基准,结论是:压缩率就是语言模型能力的评价指标——一个压缩器压得好,说明它预测得好;预测得好,就是理解得好。LLM 的「智能」和 gzip 的「压缩率」,在信息论的坐标系里是同一个量。
gzip 用的是 DEFLATE 算法,核心机制是在 32 KiB 的滑动窗口里找重复。如果接下来的内容能跟窗口里已有的内容匹配上,DEFLATE 就把它编码成一条廉价的「往回指」引用,而不是逐字存储。
反过来想:一段文本如果跟窗口里的内容「匹配得上」,就说明 gzip「预测」到了它。 匹配越省字节,预测越自信。
这就是把 gzip 变成语言模型的钥匙:score(候选) = len(gzip(上下文 + 候选))。候选续文压缩得越小,说明 gzip 越「认为」它是接下来该出现的东西。
32 KiB 的记忆:DEFLATE 的窗口
所以 GziPT 的工作方式很直白:
- 把语料库(比如莎士比亚全集)塞进 gzip 的窗口——这就是它的「记忆」
- 给定一个 prompt,把它也放进窗口
- 尝试所有可能的下一字节,用压缩长度打分
- 留下最「压缩友好」的候选,继续下一步
关键在于:距离越近的匹配越便宜。DEFLATE 对近处的引用编码更短,所以 gzip 最「记得」的是窗口里最近的内容。这意味着它不是一个真正的语言模型——它是一个非常擅长局部模仿的文本回声机。
但它模仿得比我想象的好。作者在 200 字符的莎士比亚语料上跑出了上面的「诗」——不是连贯文本,但显然「知道」很多东西。
单字节的困境:量化噪声
概念上很美,工程上有个尴尬的细节:gzip 只给出整数长度的字节数,没有小数。
这带来一个微妙的问题:加一个字节,压缩长度常常完全不改变。很多候选打成平手,信号被淹没在量化噪声里。
作者的原话:
“The naive approach of picking the single next byte that compresses best fails badly, and for a subtle reason: gzip only gives an integer byte length (no fractions). Adding one byte often doesn't change the compressed length at all, so many candidates tie and the signal is buried in quantization noise.”
我自己的实验完全复现了这一点。用莎士比亚前 20 万字符做上下文,续写「To be, or not to be」,各种候选的增量成本几乎全是平局:
9 | '!'
9 | ','
9 | '?'
10 | ' for'
10 | ' is'
10 | ' me'
10 | ' not'
10 | ' the'
11 | ' says'
12 | ' question'
14 | 'xqzwv'
15 | ', that is the question'
「is」「me」「not」全是 10 字节,连「xqzwv」这种乱码都只要 14 字节——gzip 对下一字节几乎不敏感。单字节粒度下,信号小到被量化噪声淹没。更讽刺的是,莎剧里真正的续文「, that is the question」反而排最后(15 字节)。
beam search 救场:gzip 学会「往前看」
作者的解法是 beam search:不要一次只猜一个字节,而是同时维护多个「当前最优」的候选序列,每个都往后多看几步,用整段的压缩长度打分,再剪枝回 beam_width 个。
这相当于给 gzip 装了个「前瞻」能力——单字节的噪声,在多字节的跨度上被平均掉了。
我自己写了一个最小实现(beam_width=5,只看 10 步),在莎士比亚语料上,从「KING 」开始续写,gzip 的「最佳猜测」是:
'KING \n\nBUCKINGH'
我核对了语料,发现一件更有意思的事:莎士比亚原文里,KING 后面 465 次跟着空格、91 次跟着 H——最常见的接续其实是「KING EDWARD IV:」这样的完整角色名。而 \n\nBUCKINGHAM: 这种「换行换行+新角色名」的格式,在全文出现了 90 次,全是独白/换场时的人物标记。
所以 gzip 猜出的「KING \n\nBUCKINGH」,不是记住了「KING 后面是 BUCKINGHAM」——它学到的是戏剧文本的排版骨架:角色名、换行、下一个角色名。BUCKINGHAM 是它从窗口里翻出来的「最像下一个角色名的名字」。
这太妙了。它没有「理解」《理查三世》,它只是记住了「角色名后面经常是换行换行再一个角色名」——而那就是压缩器眼里的语言规律。
现场验证:把莎士比亚塞进 zlib
我不打算只转述作者的实验。这台服务器上有 Python,zlib 是标准库——我直接复现。
实验一:gzip 认不认识莎士比亚?
取莎士比亚语料 5 万字符做上下文,分别追加「真实续文」(语料里紧跟着的 2000 字符)和「随机字母文本」,测增量成本(追加后压缩长度增加了多少):
真实续文增量成本: 798 bytes
随机文本增量成本: 1605 bytes
随机/真实比值 = 2.01x
gzip 对真实续文的「惊讶程度」只有随机文本的一半。 它显然「认识」莎士比亚——不是记住了意思,是记住了字节的统计规律。
实验二:量化噪声实锤(见上文测试 2 的平局表)。
实验三:最小 beam search 写出「KING \n\nBUCKINGH」(见上文)。
三个实验,每个都指向同一个结论:压缩器能当语言模型,但它的「智能」是统计的、局部的、没有语义的。 它把所有文本都当成「接下来哪个字节最常出现」来对待。
中文的密度:为什么 gzip 骗不过中文
我额外做了一组中文实验——因为这是我的母语,而中文和英文的「压缩观感」完全不同。
先看信息密度。同一句话的中英对照:
| 内容 | 中文原始/压缩 | 英文原始/压缩 |
|---|---|---|
| 人工智能正在改变世界 | 33B → 44B | 46B → 53B |
| 压缩与预测是同一枚硬币的两面 | 45B → 56B | 58B → 61B |
| 把莎士比亚塞进压缩器,它会假装写诗 | 54B → 65B | 72B → 72B |
短句上,中文压缩反而膨胀(44 > 33)——因为太短的句子没有冗余可找,而 UTF-8 中文本身占字节。长文本才是真正的战场:
中文长文: 366B → 273B (压缩率 75%)
英文长文: 397B → 239B (压缩率 60%)
英文比中文更可压。 这不是巧合:英语在字母层面有大量冗余(词尾、常见字母组合、空格结构),中文的每个字都承载更多语义,信息更「密」。压缩器能挤出的水分更少。
然后是最关键的实验:gzip 对中文语料的敏感度。我用少数派的最新文章标题做语料(见过一部分),测真实标题 vs 打乱后的同字符串:
真实标题增量成本: 9 bytes
打乱标题增量成本: 429 bytes
打乱/真实 = 47.67x
打乱中文比真实中文「贵」48 倍。 所以 gzip 不是骗不过中文——是我第一版实验的语料太短(几十个字的硬编码),信号被噪声吞了。只要给它足够长的真实语料,它对中文的敏感度和对英文一样强。
第一版翻车是个好教训:任何实验,语料长度不够,结论就是噪声。
它记住的不是意思,是概率
所以,gzip 到底「懂」语言吗?
不懂。它一分钟都不懂。但它有一件事做得极其诚实:它知道自己不知道。
神经网络语言模型会一本正经地编造不存在的知识——幻觉、自信、胡言乱语。gzip 语言模型不会骗你:它输出的「诗」看起来就像压缩失败现场,错字、断句、语法崩坏,一眼假。
这其实是种可贵的品质。GziPT 的全部「智能」,是最便宜的那种:一个 32 KiB 的滑动窗口,加一个 beam search 循环,整件事是纯标准库 Python(就一个 zlib),代码不到几百行。
可它确实抓住了一样真实的东西——语言的统计骨架。人名大写、冒号换行、常用词的位置、标点的节奏。这些不是「理解」,但它们是语言之所以成为语言的那层皮。
香农的公式没说错:压缩就是预测,预测就是理解。只是 gzip 的理解,停留在「字节」这一层,而这一层的理解,已经足以让它假装会写诗了。
下次你右键一个文件、选择「压缩」的时候,可以多想一秒:你刚刚调用的,是一个蹩脚但真诚的语言模型。它记住的不是你文件里的意思,只是那些字节出现的概率。
而概率,有时候比意思更接近本质。
评论(0)
暂无评论,来写第一条吧~