Information Retrieval(信息检索)笔记04:Compression

在前面几节,我们已经学习了信息检索系统 (IR System) 中最重要的两个数据结构:词典 (Dictionary) 以及倒排索引 (Inverted Index),并且了解了如何去构建索引 (Index Construction)。这一节,我们将目光放在如何使信息检索变得更高效,这就涉及到一项非常重要的技术:压缩技术 (Compression)。

压缩带来的好处是极其直观的:

  1. 首先,它能够大大节省磁盘 (Disk) 的空间
  2. 其次,它可以增加告诉缓存 (Caching) 的利用率。我们在进行检索时,词典中总会有一部分词项 (Term) 相比其他词项使用得更频繁,如果能把这些词项和它们对应的倒排记录表 (Posting) 存储在高速缓存 (Caching) 中,可以避免多次的磁盘读写,同时尽可能将对这些词项查询的计算在内存中完成,压缩技术能够帮助我们在高速缓存中存放更多的信息,从而降低系统的运作时间。
  3. 最后,压缩技术能够加快数据从磁盘 (Disk) 到内存 (Memory) 的传输速度。将压缩后的数据传输到内存,再进行解压操作的时间成本远低于直接传输未压缩的数据。

在具体了解压缩技术之前,我们需要先对待压缩对象(比如词项和倒排记录表)的一些统计特性。

注意,在本节的介绍中,为了便于理解,我们都以最简单的无位置信息倒排索引为例子,也就是说倒排记录表中的每一个元素都是当前词项 (Term) 所在的文档 ID

信息检索中词项的统计特性

和前一节的索引构建 (Index Construction) 一样,这里我们仍然使用 RCV1 语料库作为例子。 这里给出对该语料库进行处理得到的一个统计信息表:

在这里插入图片描述
该表中,“△%” 表示和前一行相比数目的减少比率,“T%” 表示从未过滤条目开始的数目累积减少比率。根据这张表,我们能够得到以下结论,预处理 (Preprocessing) 对于词典 (Dictionary) 和倒排记录表 (Posting List) 的数量影响很大。词干还原和大小写转换分别会将不同词项的数目降低 17%,而将倒排记录的个数分别降低 4% 和 3%。同时,对于高频词的处理效果也是立竿见影。

对于压缩技术,它主要有两类。一种是无损压缩 (Lossless Compression) 技术,即压缩之后,所有的原始信息都会得以保留。与之相对的就是有损压缩 (Lossy Compression) 技术会造成一些信息的丢失。事实上,我们刚刚看到的预处理 (Preprocessing) ,比如大小写转换 (Case Folding)、词干还原 (Stemming) 以及去除停用词 (Stop Word) 都可以看作是有损压缩。当损失的信息不会被检索系统所用时,有损压缩是很有意义的。而在本节中,我们将要介绍的压缩方法,都属于无损压缩。

Heaps’ Law (Heaps 定律)

我们可以大致估计一下文档集中不同的词项的总数 M。有人或许会以牛津英语词典(Oxford English Dictionary)作为一个标准的尺度,它里面的单词数目前超过了600,000,但是对于大部分大规模的文档集来说,其词汇量会远远大于这个数目,因为词典不会包含人名之类的特殊词汇。

因此,一般我们会采用 Heaps 定律 (Heaps’ Law) 来估计词项数目 M。该定律将词项数目估计为一个与文档集 (Collection) 大小相关的函数

在这里插入图片描述
这里,T 是文档集中的词条 (Token) 总数。参数 kb 的典型取值为:30 ≤ k ≤ 100,b ≈ 0.5。

Heaps 定律的核心思想在于,它认为文档集 (Collection) 大小和词汇量 (Vocabulary) 之间最简单的关系就是它们在对数空间 (log-log Space) 中存在线性关系。再简单一点说,在对数空间中,词汇量 M 和文档集尺寸 (词条数量) T 组成一条直线,斜率 (slope) 约为 1/2。 下面我们给出以 RCV1 文档集为对象绘制的文档集大小 (Collection Size = Number of Tokens) 与词汇量 (Vocabulary) 之间的关系图:

在这里插入图片描述
在这幅图中,虚线表示的是 log10M = 0.49 × log10T + 1.64,它是其基于最小二乘法的拟合结果。可以清楚地看到,当 T > 105 时,曲线和真实情况极为吻合,这个时候,b = 0.49,k = 101.64 = 44。因此,对于前 1,000,020 个词条,Heaps 定律会估计得到大约 44 × 1,000,0200.49 = 38,323 个词项,而实际值为 38,365 ,可以看到,非常接近。

需要格外注意的是,对不同的文档集,k 的取值会有很大的差异,这是因为词汇量的大小直接和文档集的尺寸以及对文档进行的预处理决定。不论在某个特定的文档集中的参数取值如何,Heaps 定律提出了如下两点假设:

  1. 随着文档数目的增加,词汇量会持续增长而不会稳定到一个最大值
  2. 大规模文档集的词汇量也会非常大

Zipf’s Law (Zipf 定律)

Heaps 定律为我们提供了计算词汇量 (词项数量) 的方法,现在,我们来看一看词项 (Term) 的相对频率 (Relative Frequency) 或者说在文档中的分布情况。Zipf 定律一言以蔽之就是 “第 i 个最常见词项的频率与 1/i 呈正比”。

详细地来说,如果用词项 t1 表示文档集中出现次数最多的词项,用词项 t2 表示出现次数第二多的词项,以此类推,那么排名第 i 的词项 ti 的文档集频率 (Collection Frequency) cfi ∝ 1/i = K/i。所谓的文档集频率 (Collection Frequency) ,就是词项在文档集中出现的次数。因此,如果出现最多的词项的文档集频率是cf1的话,出现第二多的词项的文档集频率就是cf1 的一半,出现第三多的词项出现次数会是cf1的1/3,其余均可依此类推。换言之,随着词项出现次数的排名下降,就表示它出现的次数越少。

我们可以将上面的式子同样转换为 log 形式: log cfi = log K - log i。因此 Zipf 定律实际上是一个幂定律 (Power Law)。我们同样给出 RCV1 文档集在对数空间下,词项的文档集频率和其出现次数名次之间的关系图:

在这里插入图片描述
可以看到,实际数据和 log cfi = log K - log i 并不是十分贴合,但是对于我们将要介绍的此项分布模型已经足够了。

接下来,我们将正式开始对压缩技术的介绍,这里根据索引的结构,我们会分为两个部分,即对词典 (Dictionary) 的压缩以及对倒排记录表 (Posting List) 的压缩。

词典压缩(Dcitionary Compression)

首先我们需要明确,为什么要对词典 (Dictionary) 进行压缩?这是因为在 IR 系统中进行的检索,总是以词典为开始,因此,我们希望能够将词典,或者说大部分的词典存放在内存 (Memory) 中,这样可以减少对于磁盘 (Disk) 的访问,大大节省系统的响应时间。

根据前几节的学习,我们不难想象,最简单的词典存储方式就是用长数组来按照词项的字典顺序来存储整个词典 (Dictionary)。该数组中,每个对象的宽度相同,如下图所示:

在这里插入图片描述
这里,我们对每个词项 (Term) 采用20Bytes 的固定长度(因为英文中很少有长度大于20B 的词)存储,文档频率 (Document Frequency, DF) 采用 4Bytes 存储,词项到倒排记录表的地址指针也采用 4Bytes 存储。那么,对于我们使用的 RCV1 文档集来说,存储词典 (Dictionary) 需要的空间为 M ✖ (20 + 4 + 4) = 400,000 ✖ 28 = 11.2 MB。

但很显然,这样的存储方式会浪费不少空间。因为这里我们按照英文单词的最大长度为每个词项分配空间,但在实际情况中,一个单词的平均长度仅有 8 Bytes,这样对每个词项,平均会浪费 12 Bytes 的空间。然而我们也无法轻易减少空间分配,这样可能会有很多词项无法存储,这就形成了一个两难的局面。

将词典看作单一字符串进行压缩(Dictionary-­as-­a-­String)

为了避免上述的问题,我们可以选择一种全新的存储方式。我们可以将所有的词项看作一个长字符串。这种方法大约能节省 60% 的空间。

这一方法实际上很好理解。我们用一个具体的例子来看:

在这里插入图片描述
在这个例子中,我们将整个词典中的词项连接为一个长字符串。此时,为每一个词项 (Term) 分配一个词项指针 (Term Pointer) ,这个指针在指向下一个词项指针的同时也标明当前词项的结束,这样一来就在这个长字符串中分割出了所有的词项。上图中的第一个词项就是 “systile”, 第二个是 “syzygetic”…

或者我们可以用另一种方式来理解它,我们可以把这个所有词项连接而成的长字符串看做一个数组 (Array) A,每个字母就是一个元素,那么,我们只需要知道每个词项的 Head 的 Offset,就能知道所有词项,我们的词项指针 (Term Pointer) 和这里的数组中的 Offset 实质上是一样的。

此时我们再来计算一下 RCV1 文档集用该方法存储词典所需要的空间: M ✖ (4 + 4 + 3 + 8) = 400,000 ✖ 19 = 7.6 MB。可以看到,相比之前定长数组存储的方法,节省了不少空间。

按块存储(Blocking)

将词典看作单一长字符串的缺点在于我们需要为每一个词项 (Term) 维护一个指针 (Term Pointer)。这是必须的,因为我们把所有的词项连接在了一起,没有任何分割,缺少了这个词项指针,我们将无法得到对应的词项。

现在我们可以更进一步。将长字符串中的词项 (Term) 进行分组,每 k 个词项分为一组,这样的一个分组,就称为一个块 (Block)。 然后,对这个块中的 k 个词项,我们只保留第一个词项的指针 (Term Pointer),同时,用一个额外的字节将每个词项的长度标注在各词项的头部。

在这里插入图片描述
上图所示的例子中,共有两个块 (Blocks)。第一个块包含 {“systile”, “syzygetic”, “syzygial”, “syzygy”},它们的长度分别为 7 Bytes, 9 Bytes, 8 Bytes 和 6 Bytes,都已标注在各词项的头部。从表中我们可以看到,后 3 个词项的指针都可以被省略,如此一来就节省了 3 ✖ 3 Bytes = 9 Bytes 的空间。我们可以得到结论,对于由 k 个词项组成的块 (Block),可以节省 k - 1 个词项指针 (Term Pointer),但是需要额外的 k Bytes 来表示各词项的长度。

当 k = 4 时,我们可以节省 3 ✖ 3 Bytes = 9 Bytes 的空间,但是需要额外的 4 Bytes 来存储长度信息,因此实际节省了 9 Bytes - 4 Bytes = 5 Bytes,即每 k = 4 个词项就会节省 5 Bytes 的空间,我们用对 RCV1 文档集进行计算可知,采用 k = 4 的块存储,可以节省 400,000 ✖ 1/4 ✖ 5 Bytes = 0.5 Bytes 空间,这样,原来使用单一字符串方法得到的 7.6MB 存储空间可以进一步降低到 7.1MB。

很显然,如果我们增加 k 的值,可以进一步提高压缩率。但是,我们在压缩率和词项查找速度之间,一定要保持某种平衡。当前使用块存储 (Blocking) 的方法确实能够节省大量的空间,但是,这并不代表它会节省在词典 (Dictionary) 中查找词项 (Term) 的时间。 我们同样可以用一个例子来理解:

在这里插入图片描述
假设我们的词典中只有以上 8 个词项 (Term)。

  • 首先我们来看在未压缩的词典中进行查找,因为我们知道词典 (Dictionary) 中的词项已经按照字典顺序进行过排序,因此,可以使用二分法进行搜索,如上图 (a) 所示。假定 (a) 中后续词项的出现概率都相同,那么,在未压缩的词典中进行查找的平均时长为 (0 + 1 + 1 + 2 + 2 + 2 + 2 + 3) / 8 ≈ 1.6 Steps。假设我们要查找的目标词项为 “aid”,需要 3 Steps;若是要查找 “box”,则需要 2 Steps
  • 接下来看使用块存储的词典,这时要先使用二分法找到目标词项所在的块 (Block),然后在这个块 (Block) 中进行一一比对,也就是线性查找,此时需要的平均时长为 (0 + 1 + 2 + 3 + 4 + 1 + 2 + 3) / 8 = 2 Steps,与之前相比增加了近 25% 的时间。假如我们要查找的目标词项是 “den”,那么就需要 1 Step 的二分法搜索以及 2 Steps 的块内搜索。

因此,当我们增加 k 值时,固然会增大压缩率,但是此时每个块 (Block) 内的词项数量会激增,这样会大大增加块内搜索时间。 而实际上,块内搜索有大部分的时间都被消耗在了 “无效的对比” 上,我们用之前的一个例子来做说明:

在这里插入图片描述
这里我们有 2 个块 (Blocks),假设我们的目标词项为 “syzygy”,首先使用二分法我们已经确定它在第一个块中,此时开始块内搜索。首先,我们拿它和 systile 进行匹配,我们发现只有前两个字母匹配成功,因此在完成前两个字母的比对之后,直接跳转到下一个词项(根据长度跳转),比对下一个词项,前 5 个字母匹配成功,但是仍未完全匹配,接着再往后跳转到下一个词项,直到找到完全匹配的词项 syzygy。这个过程中有太多无效的比对,主要的原因在于词项在此之前已经进行过排序了,这就会使得很多有着相同前缀 (Prefix) 的词项挨在一起,这里的共有前缀就是 “sy”。那么,我们是否也能反过来利用这个特点呢?

前端编码(Front Encoding)

注意,这里我们不采用 Christopher D. Manning 所著的《信息检索导论》中对于前端编码的介绍,因为它采用了一些非常规的符号,这里我们介绍另外的一种方法

按照字典顺序排序的连续词项之间常常会有相同的前缀 (Prefix),因此,我们可以使用一种叫前端编码 (Front Encoding) 的技术。我们先来看一看完全前端编码 (Complete Front Encoding) 的格式,采用完全前端编码会将每个词项变为以下格式:

(Prefix Length, Suffix Length, Suffix), 即 (前缀长度,后缀长度,后缀)

完全前端编码用于未分块 (Non-blocking) 的长字符串。而对于 k = 4 的分块,我们采用部分 3 合 4 前端编码 (Partial 3-in-4 Front Encoding)。其实它们的概念非常简单,我们用一个例子就能很轻易地理解:

在这里插入图片描述
这里表中的第一列就是连续的 4 个词项 (Terms)。第二列就是进行完全前端编码后的结果,以 (8, automata) → (4, 4, mata) 为例,表明词项 “automata” 相较于它之前的一个词项,它们有着长为 4 的前缀 “auto” 相同,剩下的后缀 “mata” 长度为 4。这里我们假设 “automata” 的前一个词项是 “auto”,但实际上根据字典顺序,可能是任何前缀为 “auto”,第 5 个字母小于 “m” 的词项。第三列就是在块内进行部分 3 合 4 前端编码 (Partial 3-in-4 Front Encoding) 的结果,可以看到,第一个词项 ( , 8, automata) 的前缀长度 (Prefix Length) 为空,这是因为它值该块 (Block) 的第一个词项 (Term),因此它之前没有其他词项存在,因此我们不需要对它进行前端编码,只需要对剩下的 3 个词项进行编码即可。

在进行了前端编码的块中,也支持进行二分法搜索,这样就大大减少了进行块内搜索的时间。

到现在为止,我们已经一步步完成了对词典 (Dictionary) 的压缩,现在我们就来汇总一下上述的各个技术对词典压缩的具体效果(仍以 RCV1 文档集为例):

在这里插入图片描述
可以看到,相比最朴素的定长数组存储,现在词典 (Dictionary) 整体的空间占用减少了近一半。

倒排记录表压缩(Posting List Compression)

在完成了对词典的压缩之后,现在我们来看一看对倒排记录表 (Posting List) 的压缩。根据之前的学习,我们很清楚地知道,倒排记录表 (Posting List) 占用的空间远远大于词典 (Dictionary)。在本例子,我们之前已经说过,只考虑最简单的无位置信息倒排索引,即倒排记录表 (Posting) 中只保存文档的 ID。现在我们考虑 RCV1 文档集的具体情况,在这个文档集中,我们总共有 800,000 个文档 (Document),因此,如果要表示这些文档的 ID,则每个 ID 需要 log2 800,000 ≈ 20 Bits。现在我们想要一个更高效的方法来替代使用文档 ID 表示每个文档,也就是说,我们希望使用少于 20 Bits 的空间来表示每个文档。

一种非常好的方法是记录文档 ID 之间的差值/间距 (Gap) ,而不去直接保存文档 ID。这个方法在大多数的情况下非常好用,因为文档 ID 之间的差值一定是比文档 ID 本身小的,同时,因为倒排记录表 (Posting List) 中的文档 ID 一定是升序排列的,因此只需要保存倒排记录表中的第一个文档 ID,剩下的所有文档 ID 都可以用差值来进行替换存储。比如:

在这里插入图片描述
可以看到,原本数字非常大的文档 ID 在替换成文档 ID 差值/间距之后,都会变成比较小的数字,这样就可以使用更少的 Bits 来表示这些数。但是这里我们也需要注意,像 “the” 这类的高频词,它们普遍存在于几乎所有的文档中,因此可以想象,会有相当一部分的差值为 1 或者 2,因此可以用极少的 Bits 来表示这些数。而像 “arachnocentric” 这样的生僻词项,它可能只在一两个文档中出现,那么它的 ID 差值虽然少,但可能会非常大,甚至可能还需要 20 Bits 来进行表示。这就形成了两个极端,因此我们希望能有一种可变的编码方式来解决这类问题。

可变字节码(Variable Byte (VB) codes)

之前已经提到,我们希望对于文档 ID 的差值,能够使用可能少的 Bits 去进行表示,即 log2(Gap), 这样有利于空间的压缩。这里提出可变字节码 (VB Code) 的概念:

用整数个字节 (Bytes) 来对文档 ID 差值进行编码。每个字节的第 1 位被称为延续位 (Continuation bit) c,如果 c=1,表示该字节 (Byte) 是某个文档 ID 差值的可变字节码的最后一个部分,而在它之前的所有字节,其延续位 c 都为0。至于剩下的 7 位则是有效编码区。我们可以用一个例子来进行理解:

在这里插入图片描述
这里共有 3 个文档 ID:{824, 829, 215406},首先我们将它们根据文档 ID 差值进行转换,得到的结果为 {824, 5, 215577}。现在,对这三个数进行可变字节编码,824 的二进制数为 1100111000,一共有 10 bits,因此我们需要 2 个字节来表示它(因为 1 Byte = 8 Bits)。我们从右向左看(这也符合二进制从低位到高位的顺序),前 7 位为 0111000,因为我们知道这是该数的最后一个部分,因此延续位为 1,所以此时有 10111000;此时 824 的二进制数只剩下 3 位:110,因为我们使用整数个字节 (Bytes) 进行编码,所以进行补 0,注意此时补 4 个 0,因为还有一个延续位,此时这部分不是组成 824 的最后一部分,延续位为 0,因此,本字节为 00000110。综上所述,824 的可变字节码 (VB Code) 为 (00000110 10111000).

值得注意的是对 5 进行的编码,因为我们知道,5 < 127 = 27 - 1,所以用 7 位表示绰绰有余,所以直接将延续位置为 1。

如此一来,给定一个长序列,我们就能根据延续位 c 来还原出所有的数:

在这里插入图片描述
在对 RCV1 文档集使用 VB 编码后,能达到接近 50% 的压缩率,可见是非常成功的。同时,我们需要注意,VB 编码的单位是可以调整的,这里我们采用的是 8 Bits 为单位,也就是一个字节的长度,但实际上也可以设置成 16 Bits,32 Bits 等。单位越长,位 (Bit) 操作次数越少,但是压缩率会被降低。

对于绝大多数的信息检索系统来说,VB 编码能够取得非常好的效果,但是如果磁盘 (Disk) 空间极度稀缺,我们可以更进一步采用基于位 (Bit) 的编码以得到更高的压缩率,这里我们介绍其中几种。

Simple9

Simple9 的概念很简单,我们在一个 DWROD (32 Bits) 中尽可能多地对文档 ID 差值 (Gap) 进行编码。在这 32 Bits 中,前 4 Bits 被称为 Selector,我们可以把它看作是一个特别的 flag,用来表示剩下的 28 Bits 是怎么使用的。因此一个 DWORD = 4 bit Selector + 28 data bits。之所以称为 Simple9,是因为有 9 种可能的操作:

在这里插入图片描述
上表中的第一列就是所有可能的 Selector,我们以第一行为例,来看具体的含义:当 Selector 为 0000 时,剩下的 28 Bits 用来编码 28 个文档 ID 差值 (Gap),每个差值 (Gap) 用 1 Bit 表示,因为所有位 (Bit) 都被使用,没有位 (0 Bit) 被浪费。很显然,这很适合对像 “is”, “the” 这样的高频词进行编码。现在我们再看看第五行,Selector 为 0100,此时用剩下的 28 Bits 对 5 个 Gap 进行编码,每个 Gap 用 5 Bits 表示,因此有 28 Bits - 5 ✖ 5 Bits = 3 Bits 被浪费。

因此,每个 Selector 实质上对应一种编码模式,我们在进行编码的时候,只需要根据当前词项 (Terms) 的统计特性,选择最合适个模式即可。

Elias-­γ Code

在正式开始对 γ 编码 (γ Encode) 的介绍之前,我们先来看以种非常常见的编码方式。一元码 (Unary Code),用对应数量的 1 来表示一个数,以 0 结尾。比如 3 的一元码就是 1110:

在这里插入图片描述
一元码看似是一种十分无用的编码方式,但实则不然,比如我们想要表示 0 ~ 5,用二进制 (Binary) 的话,每个数字都需要 3bits,但是这时候,对于 0 和 1 采用一元码实际上更节省空间(分别需要 1 bit 和 2 bits)。这只是最简单的例子,对于更大的数有相似的特性。尤其是在对每个数有着不同的概率分布时,这样的差异会更显著,因此,对于这样一个看似冗余的编码方式,我们也不能说它完全无用。

在回顾了一元码的概念之后,我们来看 γ 编码 (γ Encode)。根据我们所学习的数学可以知道,任意的自然数可以被写为

在这里插入图片描述

kd 的范围为 (0, log(kMAX)),kr 的范围为 [0, 2kd]。

在这里插入图片描述

我们在前面已经了解到,一元码 (Unary Code) 对于小数字的编码有优势,而二元码/二进制 (Binary Code) 对于比较大的数字编码会有优势,因此,现在我们可以将这两种方式结合起来。因为 kd 表示指数,因此一般来说不会很大,可以用一元码进行编码,而 kr 可以用二元码进行编码,这样就将数字 k 编码为 kd kr

这样的操作是有意义的,对于一个均匀分布,假设 k 是从 1 ~ 1023 的数字序列,我们能够知道,kd 的范围是 [1, 9],即使不是均匀的概率分布,大多数情况下,位于 [29, 210-1] 的数字依然不会很多,因此我们不必担心会有过多的空间占用。现在我们来看具体的例子:

在这里插入图片描述

我们从解码 (Decode) 的角度来理解这个方法,这里以 2 和 6 为例进行理解,我们的已编码信息为 “10011010”。因为我们知道一元码 (Unary) 总在最前面,所以只要找到第一个 0,就是当前一元码的结束,因此可以得到 kd 为 10。因为该一元码表示 1,因此,我们只需要再往后看 1 bit 就是我们要的 kr ,这里 kr 就是 0,因此第一个 γ 编码 (γ code) 就是 10 0,所以第一个数字就是 21 + 0 = 2


接下来的操作相同,现在未解码的信息只剩 “11010”,先找到第一个 0,一元码就是 110,表示 2,我们再往下看 2 位,二元码就是 10,表示 2,因此第二个数字就是 22 + 2 = 6

因为 γ 编码总是需要使用 2 * floor(log k) + 1 bits 进行编码,因此,γ 编码的位数一定是奇数。

Elias-­δ Code

有人或许会说,“就算 kd 用来表示指数,但它未必会很小啊?” 针对这样的情况,就有了 δ 编码 (δ Encode),它实质上就是对 kd 的再分解,所以,实际上 δ 编码 (δ Encode) 是对 kd 使用的。我们将 kd + 1 进行 δ 编码 (δ Encode),将 kd + 1 拆分成 kdd 和 kdr:

在这里插入图片描述
所以此时,原数字 k 就表示为:

在这里插入图片描述
还是用一个具体的例子来看:

这里是引用
可以看到,对于 1023 这个数,kr 部分我们并没有做改变,只是进一步分解了 kd ,因为 kd + 1 = 10,所以 2kdd + kdr = 10,即 kdd = 3,kdr = 2。


反向的解码步骤实际上也是一样,对于 “1110 010 111111111”,我们先找第一个 0,得到 1110,所以 kdd = 3,再往后看 3 位,得到 010 表示 2,所以 kdr = 2,可得到 kd = 23 + 2 - 1 = 9。之后的 9 位为 111111111,得到 kr = 511,所以原数字 k = 29 + 511 = 1023。

现在我们来看为何是对 kd + 1 进行分解,而不是直接对 kd 进行分解。这是因为一个很特殊的数字 0,如果我们对 k = 1 进行分解,此时 kd= 0,kr = 0,因为 20 + 0 = 1。但是,此时若直接对 kd = 0 进行分解,则需要 20 - 1 = 0,但是无论是一元码还是二元码,都根本不可能去表示一个负数,因此,我们不能直接对 kd 进行分解,而是对 kd + 1 去进行分解。

在对 RCV1 文档集使用了上述的压缩方法后,可以得到如下的结果:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值