gzip 也能当语言模型?
前段时间我写过一篇《不靠神经网络的语言建模》,用一个无界的 n-gram 模型生成了莎士比亚风格的文本:没有权重、没有训练,只靠统计计数。后来我碰巧读到论文《Language Modeling is Compression》,里面提到了「压缩即预测」的等价关系:
每个预测模型本质上都是压缩器,而所有压缩算法也都是预测模型。
由此引出一个自然的问题:gzip 能做语言建模吗?1不用神经网络,没有学习参数,什么都没有,就用操作系统自带的压缩器。先用一个语料库喂给它,再给一段普通文本提示,它通过搜索压缩效果最好的字节序列来续写这段提示。下面是在 tiny Shakespeare 上训练后生成的真实、未编辑的输出:
gzipt --corpus data/tinyshakespeare.txt --prompt $'MENENIUS:\n' --length 200MENENIUS:
'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 .结果如何?算是能吧。文本谈不上连贯,但它显然懂一些东西——远超我对 gzip 的预期。2那么一个压缩器是怎么生成这些内容的呢?
压缩即预测#
想想压缩器在做什么:它对「预期中」的数据花更少的字节,对意料之外的数据花更多字节。如果你拿到一个文件,内容是一百万个重复的字母 A,一句话就能描述清楚;而一百万个随机字节没有任何可利用的结构,几乎压不动。
这不是巧合,而是信息论的核心。编码一个符号所需的比特数是 $-\log_2 p$,其中 $p$ 是模型赋给它的概率,概率越高,比特数越少。所以任何压缩器内部都藏着一个概率模型,不管有没有人显式写出它。
gzip 采用 DEFLATE 算法,通过在一个 32 KiB 的滑动窗口中查找匹配项来压缩后续字节。如果延续内容与窗口中已有的文本重复,DEFLATE 会将其编码为廉价的反向引用,而非直接存储原始字节。因此:
如果某个延续内容是 gzip“预料到的”(即回声了窗口中已有的文本),它会被压缩得极小,几乎不占空间。
这为我们提供了一个评分机制。当手头有上下文,想知道某个候选延续的质量时,只需测量其压缩后的长度:
$$\text{score}(\text{candidate}) = \texttt{len(gzip(context + candidate))}$$压缩后的长度越小,说明该候选内容越像是被“预测”到的。为了让模型“预热”,我会将一个语料库放入 gzip 的窗口中。任何看起来像语料库的延续内容都会被压缩得很小,而那些不符合语料库特征的延续则压缩体积较大。
通过束搜索生成文本#
评分是一回事,生成是另一回事。简单地挑选压缩效果最好的下一个字节往往效果很差,原因在于一个细微之处:gzip 输出的压缩长度是整数(没有小数)。添加一个字节常常不会改变压缩后的总长度,导致大量候选项得分相同,有用信号淹没在量化噪声中。
解决方法是在做出承诺之前向前看一整段。gzipt 在字节序列上执行束搜索(Beam Search)。每一步中,当前上下文为:
语料库窗口 + (提示词 + 已生成字节) 的近期尾部然后 gzipt 尝试可能的下一个字节。每个候选延续内容通过将 context + candidate 进行压缩,并检查压缩结果占用的字节数来评分。
循环过程如下:
- 提示词。 以用户提示词作为初始待延续文本。没有起始 token,提示词的字节只是 gzip 所见上下文的一部分。
- 上下文。 向 gzip 展示语料库窗口加上提示词/已生成文本的近期尾部。
- 搜索。 保留
beam_width个可压缩性最强的部分延续内容。用语料库中出现过的每个字节扩展这些部分,根据压缩长度对所有扩展结果评分,并裁剪回最佳的beam_width个。重复此过程,生成horizon个字节。
Can gzip be a language model? 这篇关于将数据压缩应用于自然语言生成的探索,试图用熟悉的 Gzip 来生成新的文本。虽然作者最终放弃了这个想法(因为它并不好使),但他们还是把这段经历写成了一篇有趣且信息丰富的博客文章,并附带了可运行的代码。 这个实验展示了:当你的模型只是压缩器的情况下,语言建模会是什么样。
核心思路如下: 1. 生成。 从候选前缀中选出下一个片段。 给定当前序列,让 Gzip 对许多候选前缀(比如所有可能的连续前缀)进行编码,然后选择被压缩得最好的那个。 这就是所谓的“压缩即似然”(这里,最短的描述即为最优模型)。 2. 重复。 在下一个时间步,使用新的(已更新的)序列再次运行相同的流程。