一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第4天,点击查看活动详情。
1. 概述
全文检索是指计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并将查找的结果反馈给用户的检索方式。
这个过程类似于通过字典中的检索字表查字的过程。
2. 特点
- 做了相关度排序
- 对文本中的关键字做了高亮显示
- 摘要截取
- 只关注文本,不考虑语义
- 搜索效果更加精确——基于单词搜索,比如搜索Java的时候找不到JavaScript,因为它们是不同的两个单词
3. 使用场景
- 替换数据库的模糊查询,提高查询速度,降低数据库压力,增强了查询效率
- 数据库模糊查询缺点:查询速度慢,左模糊和全模糊会使索引失效,没有相关度排序,没有对文本中关键字
- 做高亮显示,搜索效果不好
- 全文检索是搜索引擎的基础
- 只对“指定领域”的网站进行索引和搜索,即垂直搜索
- 可以在word、pdf等各种各样的数据格式中检索内容
- 其他场合,比如输入法等
4. 倒排索引
既然有倒排索引,那么一定就有正向索引。
4.1 什么是正向索引?
假设这样一个场景,某个文件夹下有多个文档,我们要从这些文档中查找某个关键词,按照常规的思维,我们是不是要把这几个文档全部都搜索一遍呢?
正向索引的结构如下:
“文档1”的ID > 单词1:出现次数,出现位置列表;单词2:出现次数,出现位置列表;…………。
“文档2”的ID > 此文档出现的关键词列表。
这就是正向索引的思路了。这样做的结果就是资源消耗高,查询效率低下。
4.2 什么是倒排索引?
倒排索引用来存储再全文检索下某个单词在一个文档或多个文档中的存储位置的映射。
搜索引擎会将正向索引重新构建为倒排索引,即把文件ID对应到关键词的映射转换为关键词到文件ID的映 射,每个关键词都对应着一系列的文件,这些文件中都出现这个关键词。
倒排索引的结构如下:
“关键词1”:“文档1”的ID,“文档2”的ID,…………。
“关键词2”:带有此关键词的文档ID列表。
4.3 举个栗子
现在你有3篇文档,
文档ID | 文档内容 |
---|---|
1 | 稀土掘金 |
2 | 稀土资源 |
3 | 掘金篮球 |
经过分词器分词后得到每篇文档的词袋表示,即每篇文档所包含的关键词
文档ID | 关键词 |
---|---|
1 | 稀土,掘金 |
2 | 稀土,资源 |
3 | 掘金,篮球 |
然后再构建从词汇到文档ID的映射
关键词 | 文档ID |
---|---|
稀土 | 1,2 |
掘金 | 1,3 |
资源 | 2 |
篮球 | 3 |
现在再对比一下表格1与表格3,是不是实现了倒排。
5. 创建索引
全文检索的索引创建过程一般有以下几步:
5.1 分词器分词
将原文档传给分词器(Tokenizer)
,分词器会从进行分词操作(Tokenize):
- 将文档分成一个个单独的单词;
- 去除标点符号;
- 去除停词(Stop Word)。
所谓停词(Stop word)
:就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
比如:“是”,“这个”,“那个”,“一行”等。
对于每一种语言的分词组件(Tokenizer),都有一个停词(Stop Word)集合。
经过分词(Tokenizer) 后得到的结果称为词元(Token)。 比如上面的例子:“cars”、“driving”、“students”、“allowed”就是词元。
5.2 将词元传给语言处理组件
语言处理组件(Linguistic Processor)主要是对得到的词元(Token)做一些同语言相关的处理。
以英文词元处理为例,一般会做以下操作:
- 变为小写(Lowercase) 。
- 将单词缩减为词根形式,如“cars ”到“car ”等,称为:Stemming 。
- 将单词转变为词根形式,如“drove ”到“drive ”等,称为:Lemmatization 。
语言处理组件(Linguistic Processor)处理的结果称为词(Term) 。
经过语言处理后,得到的词(Term)就变成了这样:“car”、“drive”、“student”、“allow”。
5.3 将词传给索引组件
索引组件(Indexer)
主要做以下几件事情:
- 利用得到的词(Term)创建一个字典。
Term | Document ID |
---|---|
car | 1 |
drive | 1 |
student | 2 |
allow | 2 |
- 对字典按字母顺序进行排序。
Term | Document ID |
---|---|
allow | 2 |
car | 1 |
drive | 1 |
student | 2 |
- 合并相同的词(Term)成为文档倒排(Posting List)链表。
以此图为例,有些词在文档1存在,有些词在文档2存在,有些词在文档1和文档2中都存在。 在此图中,有几个定义:
Document Frequency
即文档频次,表示总共有多少文件包含此词(Term)。Frequency
即词频率,表示此文件中包含了几个此词(Term)。
所以对词(Term) “allow”
来讲,总共有两篇文档包含此词(Term),从而词(Term)后面的文档链表总共有两项,第一项表示包含“allow”的第一篇文档,即文档1,此文档中,“allow”出现了2次,第二项表示包含“allow”的第二个文
档,是文档2,此文档中,“allow”出现了1次。
6. 搜索索引
搜索主要分为以下几步:
6.1 用户输入查询语句
查询语句同我们普通的语言一样,也是有一定语法的。
不同的查询语句有不同的语法,如SQL语句就有一定的语法。
查询语句的语法根据全文检索系统的实现而不同。最基本的有比如:AND, OR, NOT等。
举个例子,用户输入语句:lucene AND learned NOT Hadoop。
说明用户想找一个包含lucene和learned然而不包括hadoop的文档。
6.2 对查询语句进行词法分析,语法分析,及语言处理
由于查询语句有语法,因而也要进行语法分析,语法分析及语言处理。
- 词法分析主要用来识别单词和关键字。
如上述例子中,经过词法分析,得到单词有lucene,learned,hadoop, 关键字有AND, NOT。
如果在词法分析中发现不合法的关键字,则会出现错误。如lucene AMD learned,其中由于AND拼错,导致AMD 作为一个普通的单词参与查询。
- 语法分析主要是根据查询语句的语法规则来形成一棵语法树。
如果发现查询语句不满足语法规则,则会报错。如lucene NOT AND learned,则会出错。
如上述例子,lucene AND learned NOT hadoop形成的语法树如下:
- 语言处理同索引过程中的语言处理几乎相同。
如learned变成learn等。
经过第二步,我们得到一棵经过语言处理的语法树。
6.3 搜索索引,得到符合语法树的文档
此步骤有分几小步:
- 首先,在反向索引表中,分别找出包含lucene,learn,hadoop的文档链表。
- 其次,对包含lucene,learn的链表进行合并操作,得到既包含lucene又包含learn的文档链表。
- 然后,将此链表与hadoop的文档链表进行差操作,去除包含hadoop的文档,从而得到既包含lucene又包含learn而且不包含hadoop的文档链表。
- 此文档链表就是我们要找的文档。
6.4 根据得到的文档和查询语句的相关性,对结果进行排序
此时,需要更具文档之间的关系,找出词(Term) 对文档的重要性,这个过程称为计算词的权重(Term weight) 的过程。
计算词的权重(term weight)有两个参数,第一个是词(Term),第二个是文档(Document)。
词的权重(Term weight)表示此词(Term)在此文档中的重要程度,越重要的词(Term)有越大的权重(Term weight), 因而在计算文档之间的相关性中将发挥更大的作用。
7. 本文总结
最后,我们结合Java的全文检索库Lucene的索引流程图来总结一下索引的全过程。
7.1 索引过程
- 有一系列被索引文件
- 被索引文件经过语法分析和语言处理形成一系列词(Term) 。
- 经过索引创建形成词典和反向索引表。
- 通过索引存储将索引写入硬盘。
7.2 搜索过程
- 用户输入查询语句。
- 对查询语句经过语法分析和语言分析得到一系列词(Term) 。
- 通过语法分析得到一个查询树。
- 通过索引存储将索引读入到内存。
- 利用查询树搜索索引,从而得到每个词(Term) 的文档链表,对文档链表进行交,差,并得到结果文档。
- 将搜索到的结果文档对查询的相关性进行排序。
- 返回查询结果给用户。