聊聊全文检索的实现——简析Lucene
Lucene 作为Apache 的一个顶级项目很早就进入人们的视野了,Lucene 作为一个NLP的教科书级实践可以说属于必读必会的项目。Lucene 到底是什么呢?根据官方的定义:
Lucene Core is a Java library providing powerful indexing and search features, as well as spellchecking, hit highlighting and advanced analysis/tokenization capabilities. The PyLucene sub project provides Python bindings for Lucene Core.
Lucene 是一个“库”提供了强大的索引和检索特性。同时具备了诸如拼写检查,命中高亮和先进的词法、句法分析能力。在《Lucene in action》一书中用下图来形象地描述了Lucene的结构:
图片摘自:https://livebook.manning.com/book/lucene-in-action-second-edition/chapter-1/50
图中,从最中间的“Index”模块横向分为上下两个核心部分,上半部分为用户检索,下半部分为原始文档处理,两部分通过“索引”联系到了一起。将图进一步展开:
图片摘自:https://blog.csdn.net/forfuture1978/article/details/4711308
所以我们只需要弄清楚三个基本问题就可以了解Lucene具体实现了什么,并且知道一般的文档全文检索的普适步骤是什么样子的。这三个问题分别是:
索引(Index)是什么?
索引是怎么创建的(Index Documents)?
如何对索引进行检索(Search Index)?
下面将对1和2进行浅析,因为检索有必要单独抽出一篇聊聊。
索引
做全文检索可以简单抽象化为由“字符串”找到“文档”的过程。为了方便查询,我们一般会将字符串和对应的文档存为键值对的形式。而这个形式也是Lucene使用的方式,这个方式就是经常听到的“倒排索引”,倒排索引并不是什么复杂的概念,一张图足以说明:
图片摘自:https://leanjavaengineering.files.wordpress.com/2012/02/figure4.png
图中左侧即为存储结构示意,如词语 “consign” 对应的包含当前词的文档列表是 1,4和7。因为这个结构我们可以做很多简单的逻辑运算。比如当我们需要检索同时包含 “consign” 和 “pack” 的文档,那么我们只需要将二者对应的文档链表检索出来取并集。
如何创建索引
创建索引在我之前的笔记中已经基本提过了。这里做一个汇总归纳。步骤如下:
首先需要对文本库内的文档进行分词和去停词操作(参考:)这一步主要是为了将文档切为一个一个的词素。然后将词素传输到语法分析模块,将里面的单词首先进行形式上的“归一化”,归一化通常是指将单词大小写统一、将动词和名词回归到词根。比如:去除名词复数“s”,将动词的进行时”ing“回归到动词原形,这一步相对复杂因为要涉及到语言学。最后一步就是将一些列的词汇(Term)传输到下一个模块了,这个模块就是索引构建的模块。这个模块的工作原理我在之前的文章(参考:)也已经说过了,大致可以理解为TF-IDF的简单应用,数据组织方式如下:
图片摘自:https://blog.csdn.net/forfuture1978/article/details/4711308
每一个词汇对应了其“文档频次”和文档链表。比如单词“drink”,包含其的文档在文本库中有2篇,其中id为1的文档包含了其1次,id为2的文档包含了其1次。
看到这里,对Lucene的索引模块有大致了解了吗?关注我,继续了解Lucene的搜索实现方式。