- 正排索引:文档id到单词的关联关系
- 倒排索引:单词到文档id的关联关系
示例:
对以下三个文档去除停用词后构造倒排索引
倒排索引-查询过程
查询包含“搜索引擎”的文档
- 通过倒排索引获得“搜索引擎”对应的文档id列表,有1,3
- 通过正排索引查询1和3的完整内容
- 返回最终结果
倒排索引-组成
- 单词词典(Term Dictionary)
- 倒排列表(Posting List)
单词词典(Term Dictionary)
单词词典的实现一般用B+树,B+树构造的可视化过程网址: B+ Tree Visualization
关于B树和B+树
- 维基百科-B树
- 维基百科-B+树
- B树和B+树的插入、删除图文详解
倒排列表(Posting List)
- 倒排列表记录了单词对应的文档集合,有倒排索引项(Posting)组成
- 倒排索引项主要包含如下信息:
- 文档id用于获取原始信息
- 单词频率(TF,Term Frequency),记录该单词在该文档中出现的次数,用于后续相关性算分
- 位置(Posting),记录单词在文档中的分词位置(多个),用于做词语搜索(Phrase Query)
- 偏移(Offset),记录单词在文档的开始和结束位置,用于高亮显示
B+树内部结点存索引,叶子结点存数据,这里的 单词词典就是B+树索引,倒排列表就是数据,整合在一起后如下所示
note:
B+树索引中文和英文怎么比较大小呢?unicode比较还是拼音呢?
ES存储的是一个JSON格式的文档,其中包含多个字段,每个字段会有自己的倒排索引
分词是将文本转换成一系列单词(Term or Token)的过程,也可以叫文本分析,在ES里面称为Analysis
分词器是ES中专门处理分词的组件,英文为Analyzer,它的组成如下:
- Character Filters:针对原始文本进行处理,比如去除html标签
- Tokenizer:将原始文本按照一定规则切分为单词
- Token Filters:针对Tokenizer处理的单词进行再加工,比如转小写、删除或增新等处理
分词器调用顺序
ES提供了一个可以测试分词的API接口,方便验证分词效果,endpoint是_analyze
- 可以直接指定analyzer进行测试
- 可以直接指定索引中的字段进行测试
- 可以自定义分词器进行测试
- Standard Analyzer
- 默认分词器
- 按词切分,支持多语言
- Simple Analyzer
- 按照非字母切分
- Whitespace Analyzer
- 空白字符作为分隔符
- Stop Analyzer
- 相比Simple Analyzer多了去除请用词处理
- 停用词指语气助词等修饰性词语,如the, an, 的, 这等
- Keyword Analyzer
- 不分词,直接将输入作为一个单词输出
- Pattern Analyzer
- 通过正则表达式自定义分隔符
- 默认是W+,即非字词的符号作为分隔符
- Language Analyzer
- 提供了30+种常见语言的分词器
示例:停用词分词器
结果
- 难点
- 中文分词指的是将一个汉字序列切分为一个一个的单独的词。在英文中,单词之间以空格作为自然分界词,汉语中词没有一个形式上的分界符
- 上下文不同,分词结果迥异,比如交叉歧义问题
- 常见分词系统
- IK:实现中英文单词的切分,可自定义词库,支持热更新分词词典
- jieba:支持分词和词性标注,支持繁体分词,自定义词典,并行分词等
- Hanlp:由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用
- THUAC:中文分词和词性标注
安装ik中文分词插件
- ik测试 - ik_smart
- ik测试 - ik_max_word
- ik两种分词模式ik_max_word 和 ik_smart 什么区别?
-
ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合;
-
ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。
当自带的分词无法满足需求时,可以自定义分词,通过定义Character Filters、Tokenizer和Token Filters实现
Character Filters
- 在Tokenizer之前对原始文本进行处理,比如增加、删除或替换字符等
- 自带的如下:
- HTML Strip Character Filter:去除HTML标签和转换HTML实体
- Mapping Character Filter:进行字符替换操作
- Pattern Replace Character Filter:进行正则匹配替换
- 会影响后续tokenizer解析的position和offset信息
Character Filters测试
Tokenizers
- 将原始文本按照一定规则切分为单词(term or token)
- standard 按照单词进行分割
- letter 按照非字符类进行分割
- whitespace 按照空格进行分割
- UAX URL Email 按照standard进行分割,但不会分割邮箱和URL
- Path Hierarchy 按照文件路径进行分割
- 对于tokenizer输出的单词(term)进行增加、删除、修改等操作
- lowercase 将所有term转为小写
- stop 删除停用词
- Synonym 添加近义词的term
- 分词器名称:my_custom
- 过滤器将token转为大写
- 创建或更新文档时(Index Time),会对相应的文档进行分词处理
- 查询时(Search Time),会对查询语句进行分词
- 查询时通过analyzer指定分词器
- 通过index mapping设置search_analyzer实现
- 一般不需要特别指定查询时分词器,直接使用索引分词器即可,否则会出现无法匹配的情况
分词使用建议
- 明确字段是否需要分词,不需要分词的字段就将type设置为keyword,可以节省空间和提高写性能
- 善用_analyze API,查看文档的分词结果
- 首先,将一块文本分成适合于倒排索引的独立的 词条 ,
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall
分析 包含下面的过程:
分析器执行上面的工作。 分析器 实际上是将三个功能封装到了一个包里:
字符过滤器
首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 转化成 `and`。
其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。
Token 过滤器
最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 ),删除词条(例如, 像 等无用词),或者增加词条(例如,像 和 这种同义词)。
Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。我们会在 自定义分析器 章节详细讨论。
但是, Elasticsearch还附带了可以直接使用的预包装的分析器。 接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:
"Set the shape to semi-transparent by calling set_trans(5)"
标准分析器
标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。它会产生
set, the, shape, to, semi, transparent, by, calling, set_trans, 5
简单分析器
简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生
set, the, shape, to, semi, transparent, by, calling, set, trans
空格分析器
空格分析器在空格的地方划分文本。它会产生
Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
语言分析器
特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 分析器附带了一组英语无用词(常用单词,例如 或者 ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的 词干 。
分词器会产生下面的词条:
set, shape, semi, transpar, call, set_tran, 5
注意看 和 已经变为词根格式。
当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域 搜索 的时候,我们需要将查询字符串通过 相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。
全文查询,理解每个域是如何定义的,因此它们可以做 正确的事:
- 当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
- 当你查询一个 精确值 域时,不会分析查询字符串, 而是搜索你指定的精确值。
现在你可以理解在 开始章节 的查询为什么返回那样的结果:
- 域包含一个精确值:单独的词条 `2014-09-15`。
- 域是一个全文域,所以分词进程将日期转化为三个词条: `2014`, `09`, 和 `15`。
当我们在 域查询 :
GET /_search?q=2014 # 12 results
当我们在 域查询 中 任意 词条的查询。这也会匹配所有12条推文,因为它们都含有 :
GET /_search?q=2014-09-15 # 12 results !
当我们在 域查询 `2014-09-15`,它寻找 精确 日期,只找到一个推文:
GET /_search?q=date:2014-09-15 # 1 result
当我们在 域查询 `2014`,它找不到任何文档,因为没有文档含有这个精确日志:
GET /_search?q=date:2014 # 0 results !
有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是你刚接触 Elasticsearch。为了理解发生了什么,你可以使用 API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:
GET /_analyze "analyzer": "standard", "text": "Text to analyze"
结果中每个元素代表一个单独的词条:
{ "tokens": [ "token": "text", "start_offset": 0, "end_offset": 4, "position": 1 "token": "to", "start_offset": 5, "end_offset": 7, "position": 2 "token": "analyze", "start_offset": 8, "end_offset": 15, "position": 3 ] }
是实际存储到索引中的词条。 指明词条在原始文本中出现的位置。 和 指明字符在原始字符串中的位置。
每个分析器的 值都不一样,可以忽略它们。它们在Elasticsearch中的唯一作用在于 token 过滤器。
API 是一个有用的工具,它有助于我们理解Elasticsearch索引内部发生了什么,随着深入,我们会进一步讨论它。
当Elasticsearch在你的文档中检测到一个新的字符串域 ,它会自动设置其为一个全文 域,使用 分析器对它进行分析。
你不希望总是这样。可能你想使用一个不同的分析器,适用于你的数据使用的语言。有时候你想要一个字符串域就是一个字符串域--不使用分析,直接索引你传入的精确值,例如用户ID或者一个内部的状态域或标签。
要做到这一点,我们必须手动指定这些域的映射。
Tokenizers 测试
Token Filters
Token Filters测试
自定义分词
自定义分词需要在索引配置中设定 char_filter、tokenizer、filter、analyzer等
自定义分词示例:
自定义分词器测试
分词使用说明
分词会在如下两个时机使用:
-
ES自带的分词器有如下: