You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

86 lines
11 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 47 | 搜索引擎(上):如何通过倒排索引和向量空间模型,打造一个简单的搜索引擎?
你好,我是黄申。
上一节,我们充分利用了哈希表时间复杂度低的特点,设计了一个简单的缓存系统。在实际项目中,哈希表或者类似的哈希数据结构,有着更为广泛的运用。比如,搜索引擎中的倒排索引,也是基于哈希表结构来设计的。这种倒排索引可以大大提升数据对象的检索效率。
除了搜索的效率,搜索引擎另一个需要考虑的问题是相关性,也就是说,我们需要保证检索出来的信息是满足用户需求的。最简单的基于倒排索引的实现,属于一种布尔排序模型,它只考虑了单词是不是出现在文档之中,如果出现了就返回相应的文档,否则就不返回,对应于布尔模型中的真假值。在这种实现中,只要出现了相关搜索词的文档都会被检索出来,因此相关性比较差。对于这点,我们可以利用向量空间模型,来衡量文档和用户查询之间的相似程度,确保两者是相关的。不过,向量空间模型需要涉及两两之间的比较,时间复杂度比较高。
考虑到上述两点,今天,我们就以文档检索为例,参照倒排索引加向量空间模型的设计思路,设计一个简单的搜索引擎。
## 搜索引擎的设计框架
之前在讲解向量空间模型的时候,我们介绍了信息检索的基础知识,而我们平时经常使用的搜索引擎,就是一种典型的信息检索系统。在讲解如何结合倒排索引和向量空间模型之前,我们先来看,常见的文本搜索引擎都由哪些模块组成。
文本搜索系统的框架通常包括2个重要模块**离线的预处理**和**在线的查询**。离线预处理也就是我们通常所说的“索引”阶段,包括数据获取、文本预处理、词典和倒排索引的构建、相关性模型的数据统计等。数据的获取和相关性模型的数据统计这两步,根据不同的应用场景,必要性和处理方式有所不同。可是,文本预处理和倒排索引构建这两个步骤,无论在何种应用场景之中都是必不可少的,所以它们是离线阶段的核心。之前我们讲过,常规的文本预处理是指针对文本进行分词、移除停用词、取词干、归一化、扩充同义词和近义词等操作。
在第17讲里我讲解了如何使用倒排索引把文档集转换为从关键词到文档的这种查找关系。有了这种“倒排”的关系我们可以很高效地根据给定的单词找到出现过这个单词的文档集合。
倒排索引是典型的牺牲空间来换取时间的方法。我们假设文章总数是k每篇文章的单词数量是m查询中平均的关键词数量是l那么倒排索引可以把时间复杂度从O(k×logm)降到O(l)。但是,如果使用倒排索引,就意味着除了原始数据,我们还需要额外的存储空间来放置倒排索引。因此,如果我们的字典里,不同的词条总数为$n\_1$,每个单词所对应的文章平均数为$n\_2$那么空间复杂度就是O($n\_1$×$n\_2$)。
在文本的离线处理完毕后,我们来看在线的文本查询。这个过程相对简单。
查询一般都会使用和离线模块一样的预处理,词典也是沿用离线处理的结果。当然,也可能会出现离线处理中未曾出现过的新词,我们一般会忽略或给予非常小的权重。在此基础上,系统根据用户输入的查询条件,在倒排索引中快速检出文档,并进行相关性的计算。
不同的相关性模型有不同的计算方式。最简单的布尔模型只需要计算若干匹配条件的交集向量空间模型VSM则需要计算查询向量和待查文档向量的余弦夹角而语言模型需要计算匹配条件的贝叶斯概率等等。
综合上述的介绍,我使用下面这张图来展示搜索引擎的框架设计。
![](https://static001.geekbang.org/resource/image/5f/71/5fd4e7baf56b4ee938aa586289965571.png?wh=1470*1042)
## 倒排索引的设计
我们之前已经把倒排索引的概念讲清楚了。不过到具体设计的时候,除了从关键词到文档这种“倒排”的关系,还有其它两个要点值得考虑:第一个是倒排索引里具体存储什么内容,第二个就是多个关键词的查询结果如何取交集。我们下面一个个来看。
首先我们来聊聊倒排索引里具体存放的内容。
从倒排索引的概念我们很容易就想到使用哈希表、尤其是基于链式地址法的哈希表来实现倒排索引。哈希的键key就是文档词典的某一个词条value就是一个链表链表是出现这个词条的所有文档之集合而链表的每一个结点就表示出现过这个词条的某一篇文档。这种最简单的设计能够帮助我们判断哪些文档出现过给定的词条因此它可以用于布尔模型。但是如果我们要实现向量空间模型或者是基于概率的检索模型就需要很多额外的信息比如词频tf、词频-逆文档频率tf-idf、词条出现的条件概率等等。
另外有些搜索引擎需要返回匹配到的信息摘要nippet因此还需要记住词条出现的位置。这个时候最简单的倒排索引就无法满足我们的需求了。我们要在倒排索引中加入更多的信息。每个文档列表中存储的不仅仅是文档的ID还有其他额外的信息。我使用下面这张图展示了一个示例帮助你理解这种新的设计。
![](https://static001.geekbang.org/resource/image/b4/2d/b4efd06d8eb915d8f68a0ebc4fb6852d.png?wh=1742*484)
其中ID字段表示文档的IDtf字段表示词频tfidf字段表示词频-逆文档频率而prob表示这个词条在这篇文档中出现的条件概率。
好了,下面我们来看,如何确定出现所有多个关键词的文档。
由于倒排索引本身的特性,我们可以很快知道某一个词条对应的文档,也就是说查找出现某一个词条的所有文档是很容易的。可是,如果用户的查询包含多个关键词,那么该如何利用倒排索引,查找出现多个词条的所有文档呢?
还记得我讲解分治法时所提到的归并排序吗在这里我们可以借鉴其中的合并步骤。假设有两个词条a和ba对应的文档列表是Ab对应的文档列表是B而A和B这两个列表中的每一个元素都包含了文档的ID。
首先我们根据文档的ID分别对这两个列表进行从小到大的排序然后依次比较两个列表的文档ID如果当前的两个ID相等就表示这个ID所对应的文档同时包含了a和b两个关键词所以是符合要求的进行保留然后两个列表都拿出下一个ID进行之后的对比。如果列表A的当前ID小于列表B的当前ID那么表明A中的这个ID一定不符合要求跳过它然后拿出A中的下一个ID和B进行比较。同样如果是列表B的第一个ID更小那么就跳过B中的这个ID拿出B中的下一个ID和A进行比较。依次类推直到遍历完所有A和B中的ID。
我画了张图来进一步解释这个过程。
![](https://static001.geekbang.org/resource/image/ba/89/ba281eeeb565405d95bedcc5a908ae89.png?wh=1252*708)
基于这种两两比较的过程,我们可以推广到比较任意多的列表。此外,在构建倒排索引的时候,我们可以事先对每个词条的文档列表进行排序,从而避免了查询时候的排序过程,达到提升搜索效率的目的。
## 向量空间和倒排索引的结合
有了倒排索引的高效查询,向量空间的实现就不难了。还记得之前我们讲解的向量空间模型吗?这个模型假设所有的对象都可以转化为向量,然后使用向量间的距离(通常是欧氏距离)或者是向量间的夹角余弦来表示两个对象之间的相似程度。
在文本搜索引擎中我们使用向量来表示每个文档以及用户的查询而向量的每个分量由每个词条的tf-idf构成最终用户查询和文档之间的相似度或者说相关性由文档向量和查询向量的夹角余弦确定。如果能获取这个查询和所有文档之间的相关性得分那么我们就能对文档进行排序返回最相关的那些。不过当文档集合很大的时候这个操作的复杂度会很高。你可以观察一下这个夹角余弦的公式。
![](https://static001.geekbang.org/resource/image/47/58/47a3eb754bc98caab86dc554b4cf7558.png?wh=488*234)
如果文档中词条的平均数量是n查询中词条的平均数量是m那么计算某个查询和某个文档之间的夹角余弦时间复杂度是O(n×m)。如果整个被索引的文档集合有k个文档那么计算某个查询和所有文档之间的夹角余弦时间复杂度就变为O(n×m×k)。
实际上很多文档并没有出现查询中的关键词条所以计算出来的夹角余弦都是0而这些计算都是可以完全避免的解决方案就是倒排索引。通过倒排索引我们挑选出那些出现过查询关键词的文档并仅仅针对这些文档进行夹角余弦的计算那么计算量就会大大减少。
此外我们之前设计的倒排索引也已经保存了tf-idf这种信息因此可以直接利用从倒排索引中取出的tf-idf值计算夹角余弦公式的分子部分。至于分母部分它包含了用户查询的向量和文档向量的L2范数。通常查询向量所包含的非0分量很少L2范数计算是很快的。而每篇文档的L2范数在文档没有更新的情况下是不变的因此我们可以在索引阶段就计算好并保持在额外的数据结构之中。
## 小结
目前,以搜索引擎为代表的信息检索技术已经相当成熟,无论是大型的互联网系统,还是小型的手机操作系统,都支持高效率的搜索。而搜索引擎最重要的核心就是及时性和相关性。及时性确保用户可以快速找到信息,而相关性确保所找到的信息是用户真正需要的。
在文本搜索中,倒排索引通过一种称为“索引”的过程,把文档到词条的关系,转化为词条到文档的逆关系,这样对于任何给定的关键词,我们可以很快地找到哪些文档包含这个关键词条。所以,倒排索引是搜索引擎提升及时性中非常关键的一步。倒排索引非常适合使用哈希表,特别是链地址型的哈希表来实现。
向量空间模型可以作为文本搜索的相关性模型。但是,它的计算需要把查询和所有的文档进行比较,时间复杂度太高,影响了及时性。这个时候,我们可以利用倒排索引,过滤掉绝大部分不包含查询关键词的文档。
## 思考题
请根据今天所讲解的设计思想,使用你熟悉的编程语言,来实现一个基于倒排索引和向量空间模型的文本搜索引擎。
欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。