一、正排索引与倒排索引
正排索引:文档 Id 到文档内容、单词的关联关系
倒排索引:单词到文档 Id 的关联关系
根据以上数据,假设我们现在要查询包含 “搜索引擎” 的文档,具体的查询流程如下:
- 通过倒排索引获得 “搜索引擎” 对应的文档 Id 有 1 和 3
- 通过正排索引查询 1 和 3 的完整内容
- 返回用户最终结果
二、倒排索引
倒排索引是搜索引擎的核心,主要包含两部分:
1、单词词典(Term Dictionary):是倒排索引的重要组成,记录所有文档的单词,一般都比较大,记录单词到倒排列表的关联信息。
单词词典一般用 B+Trees 来实现:
2、倒排列表(Posting List):记录了单词对应的文档集合,由倒排索引项组成。
倒排索引项主要包含以下信息:
1)文档 ID,用来获取原始信息
2)单词频率(TF:Term Frequency),记录该单词在该文档中的出现次数,用于后续相关性算分
3)位置(Position),记录单词在文档中的分词位置(多个),用于做词语搜索
4)偏移(Offset),记录单词在文档的开始位置和结束位置,用于做高亮显示
以【搜索引擎】为例,我们来看一下倒排列表:
最后,单词字段和倒排列表整合在一起的结构如下(只列出了 docId):
另外,es 存储的是一个 json 格式的文档,其中包含多个字段,每个字段都会有自己的倒排索引:
{
"username":"seina gao",
"refuse_reason":"I can't agree"
}
三、分词
分词是指将文本转换成一系列单词的过程,也可以叫做文本分析,在 es 里面成为 Analysis,比如文本是【elasticsearch 是最流行的搜索引擎】,分词结果是【elasticsearch、流行、搜索引擎】
1 分词器
分词器是 es 中专门处理分词的组建,英文为 Analyzer,它的组成如下:
1) Character Filters:针对原始文本进行处理,比如去除 html 特殊标记符,自带的如下:
- HTML Strip 去除 html 标签和转换 html 实体
- Mapping 进行字符替换操作
- Pattern Replace 进行正则匹配替换
示例:
POST _analyze
{
"tokenizer":"keyword",
"char_filter":["html_strip"],
"text":"<p>I&appos;m so <b>happy</b>!</p>"
}
// 得到 I‘m so happy!
2)Tokenizer:将原始文本按照一定规则切分为单词,自带的如下:
- standard 按照单词进行分割
- letter 按照非字符类进行分割
- whitespace 按照空格进行分割
- UAX URL Email 按照 standard 分割,但不会分割邮箱和 url
- NGram 和 Edge NGram 连词分割
- Path Hierachy 按照文件路径进行分割
示例:
POST _analyze
{
"tokenizer":"path_hierarchy",
"text":"/one/two/three"
}
// 得到 /one、 /one/two、 /one/two/three
3) Token Filters:针对 tokenizer 处理的单词进行再加工,比如转小写、删除或新增等处理,自带的如下:
- lowercase 将所有 term 转换为小写
- stop 删除 stop words
- NGram 和 Edge NGram 连词分割
- Synonym 添加近义词的 term
// filter 可以有多个
POST _analyze
{
"text":"a Hello world!",
"tokenizer":"standard",
"filter":[
"stop", // 把 a 去掉了
"lowercase",// 小写
{
"type":"ngram",
"min_gram":"4",
"max_gram":"4"
}
]
}
// 得到 hell、ello、worl、orld
2 分词器的调用顺序
3 Analyze API
es 提供了一个测试分词的 api 接口,方便验证分词效果,endpoint 是 _analyze:
- 可以直接指定 analyzer 进行测试
POST _analyze
{
"analyzer":"standard",
"text":"hello world"
}
- 可以直接指定索引中的字段进行测试
POST test_index_name/_analyze
{
"filed":"username",
"text":"hello world"
}
- 可以自定义分词器进行测试
POST _analyze
{
"tokenizer":"standard",
"filter":["lowercase"],
"text":"hello world"
}
4 预定义的分词器
es 自带的分词器包括:
1)Standard Analyzer:默认分词器,特性是按词切分,支持多语言,小写处理
2)Simple Analyzer:特性是按照非字母切分,小写处理
3)Whitespace Analyzer:特性是按照空格切分
4)Stop Analyzer:Stop Word 指语气助词等修饰性词语,比如 the、an、的、这等等,特性是相比 Simple Analyzer 多 Stop Word 处理
5)keyword Analyzer:特性为不分词,直接将输入作为一个单词输出
6)Pattern Analyzer:特性为通过正则表达式自定义分隔符,默认 \W+,即非字词的符号为分隔符
7)Language Analyzer:提供了 30+ 常见语言的分词器
5 中文分词(难点)
中文分词指的是将一个汉字序列切分成一个个单独的词。在英文中,单词之间是以空格作为自然分界符,汉语中词没有一个形式上的分界符。除此之外,上下文不同,分词结果也很迥异,比如交叉歧义问题,下面两种分词都很合理
- 乒乓球拍/卖/完了
- 乒乓球/拍卖/完了
常见的分词系统
- IK:实现中英文单词的切分,可自定义词库,支持热更新分词词典
- jieba:python 中最流行饿分词系统,支持分词和词性标注,支持繁体分词,自定义词典,并行分词
- Hanlp:由一系列模型与算法组成的 java 工具包,支持索引分词、繁体分词、简单匹配分词(极速模式)、基于 CRF 模型的分词、N- 最短路径分词等,实现了不少经典分词方法。目标是普及自然语言处理在生产环境中的应用
更多的中文分词了解请点击一篇文章总结语言处理中的分词问题
6 自定义分词器
当自带的分词无法满足需求时,可以自定义分词器,通过定义 Character Filters、Tokenizer、Token Filter 实现。自定义的分词需要在索引的配置中设定,示例如下所示:
// 自定义分词器
PUT test_index_name
{
"settings":{
"analysis":{
"analyzer":{
"my_customer_analyzer":{
"type":"custome",
"tokenizer":"standard",
"char_filter":["html_strip"],
"filter":["lowercase", "asciifolding"]
}
}
}
}
}
// 测试自定义分词器效果:
POST test_index/_analyze
{
"analyzer":"my_custom_analyzer",
"text":"Is this <b>a box</b>?"
}
// 得到 is、this、a、box
7 分词使用说明
分词会在如下两个时机使用:
- 创建或者更新文档时,会对相应的文档进行分词处理
- 查询时,会对查询语句进行分词
明确字段是否需要分词,不需要分词的字段就将 type 设置为 keyword,可以节省空间和提高写性能