倒排索引与分词
- 索引
- 索引介绍
- 倒排索引组成
- 分词
- 分词器
- Analyze API
- 预定义的分词器
- 中文分词
- 自定义分词
- 分词使用说明
- 分词使用建议
- 更多分词使用可查看官方文档
索引
索引介绍
- 正排索引 :文档 Id 到文档内容、单词的关联关系
- 倒排索引:单词到文档 Id 的关联关系
倒排索引组成
- 倒排索引是搜索引擎的核心,主要包含两部分:
- 单词词典(Term Dictionary)
单词词典是倒排索引的重要组成部分,记录所有文档的单词,一般都比较大,记录单词到倒排列表的关联信息。
倒排索引查询流程:通过倒排索引获得单词对应的文档Id,再通过正排索引查询完整的内容,返回用户最终结果
单词字典的实现一般都是用B + Tree,保障高效 - 倒排列表(Posting List)
倒排列表记录了单词对应的文档集合,有倒排索引项(Posting)组成
倒排索引项主要包括如下信息:
(1)文档Id,用于获取原始信息
(2)单词频率(TF, Term Frequency),记录该单词在该文档中出现的次数,用于后续相关性算分
(3)位置(Postion),记录单词在文档中的分词位置(多个),用于作词语搜索(Phrase Query)
(4)偏移(Offset),记录单词在文档的开始和结束位置,用于做高亮显示
以“搜索引擎”为例
文档ID | 文档内容 |
1 | elasticsearch是最流行的搜索引擎 |
2 | php是世界上最好的语言 |
3 | 搜索引擎挺好用的 |
Posting List 如下:
DocId | TF | Position | Offset |
1 | 1 | 2 | <18,22> |
3 | 1 | 0 | <0,4> |
es存储的是一个json格式的文档,其中包含多个字段,每个字段会有自己的倒排索引
分词
分词是指将文本转换成一系列单词(term to token)的过程,也可以叫做文本分析,在es 里面成为 Analysis,例如:
文本:【elasticsearch是最流行的搜索引擎 】
分词结果:【elasticsearch】【流行】【搜索引擎】
分词器
分词器是 es 中专门处理分词的组件,英文为Analyer,它的组成如下,分词器的调用顺序也是从1到3:
(1)Character Filters:针对原始文本进行处理,比如去除html特殊标记符,增加、删除或替换字符等
自带的如下:
HTML Strip 去除 html标签和转换html实体
Mapping进行字符替换操作
Pattern Replace 进行正则匹配替换
会影响后续tokenizer解析的postion和offset信息
(2)Tokenizer:将原始文本按照一定规则切分为单词
自带的如下:
standard 按照单词进行分割
letter 按照非字符类进行分割
whitespace 按照空格进行分割
UAX URL Email 按照standard 进行分割,但不会分割邮箱和url
NGam 和 Edge NGam 连词分割
Path Hierarchy 按照文件路径进行分割
(3)Token Filters:针对tokenizer处理的单词进行再加工,比如转小写、删除或新增等处理
自带的如下:
lowercase 将所有term 转换为小写
stop 删除stop words
NGam 和 Edge NGam 连词分割
Synonym添加近义词的term
Analyze API
es提供了一个测试分词的api接口,方便验证分词效果,endpoint 是 _analyze。
- 它可以直接指定analyzer进行测试
- 可以直接指定索引中的字段进行测试
- 可以自定义分词器进行测试
预定义的分词器
- Standard Analyzer 默认分词器,特征为:按词切分,支持多语言,有小写处理
完整返回:
{
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "<NUM>",
"position": 1
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "<ALPHANUM>",
"position": 4
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "<ALPHANUM>",
"position": 5
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "<ALPHANUM>",
"position": 6
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "<ALPHANUM>",
"position": 7
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "<ALPHANUM>",
"position": 8
},
{
"token": "dog's",
"start_offset": 45,
"end_offset": 50,
"type": "<ALPHANUM>",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "<ALPHANUM>",
"position": 10
}
]
}
- Simple Analyzer 特征为:按照非字母切分,小写处理
完整返回:
"tokens": [
{
"token": "the",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "quick",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 1
},
{
"token": "brown",
"start_offset": 12,
"end_offset": 17,
"type": "word",
"position": 2
},
{
"token": "foxes",
"start_offset": 18,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "word",
"position": 6
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog",
"start_offset": 45,
"end_offset": 48,
"type": "word",
"position": 8
},
{
"token": "s",
"start_offset": 49,
"end_offset": 50,
"type": "word",
"position": 9
},
{
"token": "bone",
"start_offset": 51,
"end_offset": 55,
"type": "word",
"position": 10
}
]
}
- Whitespace Analyzer 特征为:按空格切分
完整返回:
{
"tokens": [
{
"token": "The",
"start_offset": 0,
"end_offset": 3,
"type": "word",
"position": 0
},
{
"token": "2",
"start_offset": 4,
"end_offset": 5,
"type": "word",
"position": 1
},
{
"token": "QUICK",
"start_offset": 6,
"end_offset": 11,
"type": "word",
"position": 2
},
{
"token": "Brown-Foxes",
"start_offset": 12,
"end_offset": 23,
"type": "word",
"position": 3
},
{
"token": "jumped",
"start_offset": 24,
"end_offset": 30,
"type": "word",
"position": 4
},
{
"token": "over",
"start_offset": 31,
"end_offset": 35,
"type": "word",
"position": 5
},
{
"token": "the",
"start_offset": 36,
"end_offset": 39,
"type": "word",
"position": 6
},
{
"token": "lazy",
"start_offset": 40,
"end_offset": 44,
"type": "word",
"position": 7
},
{
"token": "dog's",
"start_offset": 45,
"end_offset": 50,
"type": "word",
"position": 8
},
{
"token": "bone.",
"start_offset": 51,
"end_offset": 56,
"type": "word",
"position": 9
}
]
}
- Stop Analyzer 去除Stop Word 指语气助词等修饰性词语,比如 the、an、的、这等。特征为:相比Simple Analyzer 多了Stop Word处理
- Keyword Analyzer 特征为:不分词,直接将输入作为一个单词输出
- Pattern Analyzer 特征为:通过正则表法式自定义分隔符;默认值是 \w+,即非字词的符号作为分隔符
- Language Analyzer 提供了30+ 常见语言的分词器
中文分词
- 难点
中文分词指的是将一个汉字序列切分成一个个单独的词,在英文中,单词之间是以空格为自然分界符,汉语中词没有一个形式上的分界符。
上下文不同,分词结果迥异,比如交叉歧义问题,如下:
【乒乓球拍/卖/完了】 【乒乓球/拍卖/完了】 - 常用分词系统
(1)IK:实现中英文单词的切分,支持ik_smart、ik_maxword等模式;可自定义词库,支持热更新分词词典
https://github.com/medcl/elasticsearch-analysis-ik (2)jieba:python中最流行的分词系统,支持分词和词性标注;支持繁体分词、自定义词典、并行分词等
https://github.com/singlee/elasticsearch-jieba-plugin - 基于自然语言处理的分词系统
(1)Hanlp:有一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用
https://github.com/hankcs/HanLP (2)THULAC:THU Lexical Analyzer for Chinese,由清华大学自然语言处理与社会人文计算实验室研制推出的一套中文词法分析工具包,具有中文分词和词性标注功能
https://github.com/microbun/elasticsearch-thulac-plugin
自定义分词
- Character Filters 的使用
- Filter 的使用
- 自定义分词的API
自定义分词需要在索引的配置中设置,格式如下:
PUT test_index
{
"settings": {
"analysis": {
"char_filter": {},
"tokenizer": {},
"filter": {},
"analyzer": {}
}
}
}
示例:新建索引+自定义分词
PUT test_index_1
{
"settings": {
"analysis": {
"analyzer": {
"my_analyzer":{
"type":"custom",
"char_filter": [
"html_strip"
],
"tokenizer": "standard",
"filter": [
"lowercase",
"asciifolding"]
}
}
}
}
}
分词使用说明
- 分词会在如下两个时机使用:
(1)创建或更新文档时(Index Time),会对相应的文档进行分词处理
索引时分词 是通过配置 Index Mapping 中每个字段的analyzer 属性实现的,不指定分词时,默认standard
(2)查询时(Search Time),会对查询语句进行分词
查询时通过analyzer指定分词器;index mapping 设置 search_analyzer实现
PUT test_index
{
"mappings": {
"doc": {
"properties": {
"title":{
"type": "text",
"analyzer": "whitespace",
"search_analyzer": "standard"
}
}
}
}
}
分词使用建议
- 明确字段是否需要分词,不需要分词的字段就将type设置为keyword,可以节省空间和提高读写性能
- 善用_analyze API,查看文档的具体分词结果