Elasticsearch 6.x 倒排索引与分词

倒排索引

  • 正排索引:文档id到单词的关联关系
  • 倒排索引:单词到文档id的关联关系

示例:
对以下三个文档去除停用词后构造倒排索引


image

倒排索引-查询过程

查询包含“搜索引擎”的文档

  1. 通过倒排索引获得“搜索引擎”对应的文档id列表,有1,3
  2. 通过正排索引查询1和3的完整内容
  3. 返回最终结果

倒排索引-组成

  • 单词词典(Term Dictionary)
  • 倒排列表(Posting List)

单词词典(Term Dictionary)

单词词典的实现一般用B+树,B+树构造的可视化过程网址: B+ Tree Visualization

关于B树和B+树

  1. 维基百科-B树
  2. 维基百科-B+树
  3. B树和B+树的插入、删除图文详解
image

倒排列表(Posting List)

  • 倒排列表记录了单词对应的文档集合,有倒排索引项(Posting)组成
  • 倒排索引项主要包含如下信息:
    1. 文档id用于获取原始信息
    2. 单词频率(TF,Term Frequency),记录该单词在该文档中出现的次数,用于后续相关性算分
    3. 位置(Posting),记录单词在文档中的分词位置(多个),用于做词语搜索(Phrase Query)
    4. 偏移(Offset),记录单词在文档的开始和结束位置,用于高亮显示
image

B+树内部结点存索引,叶子结点存数据,这里的 单词词典就是B+树索引,倒排列表就是数据,整合在一起后如下所示

note:
B+树索引中文和英文怎么比较大小呢?unicode比较还是拼音呢?

image

ES存储的是一个JSON格式的文档,其中包含多个字段,每个字段会有自己的倒排索引

分词

分词是将文本转换成一系列单词(Term or Token)的过程,也可以叫文本分析,在ES里面称为Analysis

image

分词器

分词器是ES中专门处理分词的组件,英文为Analyzer,它的组成如下:

  • Character Filters:针对原始文本进行处理,比如去除html标签
  • Tokenizer:将原始文本按照一定规则切分为单词
  • Token Filters:针对Tokenizer处理的单词进行再加工,比如转小写、删除或增新等处理

分词器调用顺序


image

Analyze API

ES提供了一个可以测试分词的API接口,方便验证分词效果,endpoint是_analyze

  • 可以直接指定analyzer进行测试
image
  • 可以直接指定索引中的字段进行测试
POST test_index/doc
{
  "username": "whirly",
  "age":22
}

POST test_index/_analyze
{
  "field": "username",
  "text": ["hello world"]
}
  • 可以自定义分词器进行测试
POST _analyze
{
  "tokenizer": "standard",
  "filter": ["lowercase"],
  "text": ["Hello World"]
}

预定义的分词器

ES自带的分词器有如下:

  • Standard Analyzer
    • 默认分词器
    • 按词切分,支持多语言
    • 小写处理
  • Simple Analyzer
    • 按照非字母切分
    • 小写处理
  • Whitespace Analyzer
    • 空白字符作为分隔符
  • Stop Analyzer
    • 相比Simple Analyzer多了去除请用词处理
    • 停用词指语气助词等修饰性词语,如the, an, 的, 这等
  • Keyword Analyzer
    • 不分词,直接将输入作为一个单词输出
  • Pattern Analyzer
    • 通过正则表达式自定义分隔符
    • 默认是\W+,即非字词的符号作为分隔符
  • Language Analyzer
    • 提供了30+种常见语言的分词器

示例:停用词分词器

POST _analyze
{
  "analyzer": "stop",
  "text": ["The 2 QUICK Brown Foxes jumped over the lazy dog's bone."]
}

结果

{
  "tokens": [
    {
      "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": "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
    }
  ]
}

中文分词

  • 难点
    • 中文分词指的是将一个汉字序列切分为一个一个的单独的词。在英文中,单词之间以空格作为自然分界词,汉语中词没有一个形式上的分界符
    • 上下文不同,分词结果迥异,比如交叉歧义问题
  • 常见分词系统
    • IK:实现中英文单词的切分,可自定义词库,支持热更新分词词典
    • jieba:支持分词和词性标注,支持繁体分词,自定义词典,并行分词等
    • Hanlp:由一系列模型与算法组成的Java工具包,目标是普及自然语言处理在生产环境中的应用
    • THUAC:中文分词和词性标注

安装ik中文分词插件

# 在Elasticsearch安装目录下执行命令,然后重启es
bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

# 如果由于网络慢,安装失败,可以先下载好zip压缩包,将下面命令改为实际的路径,执行,然后重启es
bin/elasticsearch-plugin install file:///path/to/elasticsearch-analysis-ik-6.3.0.zip
  • ik测试 - ik_smart
POST _analyze
{
  "analyzer": "ik_smart",
  "text": ["公安部:各地校车将享最高路权"]
}

# 结果
{
  "tokens": [
    {
      "token": "公安部",
      "start_offset": 0,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "各地",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "校车",
      "start_offset": 6,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 2
    },
    {
      "token": "将",
      "start_offset": 8,
      "end_offset": 9,
      "type": "CN_CHAR",
      "position": 3
    },
    {
      "token": "享",
      "start_offset": 9,
      "end_offset": 10,
      "type": "CN_CHAR",
      "position": 4
    },
    {
      "token": "最高",
      "start_offset": 10,
      "end_offset": 12,
      "type": "CN_WORD",
      "position": 5
    },
    {
      "token": "路",
      "start_offset": 12,
      "end_offset": 13,
      "type": "CN_CHAR",
      "position": 6
    },
    {
      "token": "权",
      "start_offset": 13,
      "end_offset": 14,
      "type": "CN_CHAR",
      "position": 7
    }
  ]
}
  • ik测试 - ik_max_word
POST _analyze
{
  "analyzer": "ik_max_word",
  "text": ["公安部:各地校车将享最高路权"]
}

# 结果
{
  "tokens": [
    {
      "token": "公安部",
      "start_offset": 0,
      "end_offset": 3,
      "type": "CN_WORD",
      "position": 0
    },
    {
      "token": "公安",
      "start_offset": 0,
      "end_offset": 2,
      "type": "CN_WORD",
      "position": 1
    },
    {
      "token": "部",
      "start_offset": 2,
      "end_offset": 3,
      "type": "CN_CHAR",
      "position": 2
    },
    {
      "token": "各地",
      "start_offset": 4,
      "end_offset": 6,
      "type": "CN_WORD",
      "position": 3
    },
    {
      "token": "校车",
      "start_offset": 6,
      "end_offset": 8,
      "type": "CN_WORD",
      "position": 4
    },
    {
      "token": "将",
      "start_offset": 8,
      "end_offset": 9,
      "type": "CN_CHAR",
      "position": 5
    },
    {
      "token": "享",
      "start_offset": 9,
      "end_offset": 10,
      "type": "CN_CHAR",
      "position": 6
    },
    {
      "token": "最高",
      "start_offset": 10,
      "end_offset": 12,
      "type": "CN_WORD",
      "position": 7
    },
    {
      "token": "路",
      "start_offset": 12,
      "end_offset": 13,
      "type": "CN_CHAR",
      "position": 8
    },
    {
      "token": "权",
      "start_offset": 13,
      "end_offset": 14,
      "type": "CN_CHAR",
      "position": 9
    }
  ]
}
  • 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测试

POST _analyze
{
  "tokenizer": "keyword",
  "char_filter": ["html_strip"],
  "text": ["<p>I&apos;m so <b>happy</b>!</p>"]
}

# 结果
{
  "tokens": [
    {
      "token": """

I'm so happy!

""",
      "start_offset": 0,
      "end_offset": 32,
      "type": "word",
      "position": 0
    }
  ]
}

Tokenizers

  • 将原始文本按照一定规则切分为单词(term or token)
  • 自带的如下:
    • standard 按照单词进行分割
    • letter 按照非字符类进行分割
    • whitespace 按照空格进行分割
    • UAX URL Email 按照standard进行分割,但不会分割邮箱和URL
    • Ngram 和 Edge NGram 连词分割
    • Path Hierarchy 按照文件路径进行分割

Tokenizers 测试

POST _analyze
{
  "tokenizer": "path_hierarchy",
  "text": ["/path/to/file"]
}

# 结果
{
  "tokens": [
    {
      "token": "/path",
      "start_offset": 0,
      "end_offset": 5,
      "type": "word",
      "position": 0
    },
    {
      "token": "/path/to",
      "start_offset": 0,
      "end_offset": 8,
      "type": "word",
      "position": 0
    },
    {
      "token": "/path/to/file",
      "start_offset": 0,
      "end_offset": 13,
      "type": "word",
      "position": 0
    }
  ]
}

Token Filters

  • 对于tokenizer输出的单词(term)进行增加、删除、修改等操作
  • 自带的如下:
    • lowercase 将所有term转为小写
    • stop 删除停用词
    • Ngram 和 Edge NGram 连词分割
    • Synonym 添加近义词的term

Token Filters测试

POST _analyze
{
  "text": [
    "a Hello World!"
  ],
  "tokenizer": "standard",
  "filter": [
    "stop",
    "lowercase",
    {
      "type": "ngram",
      "min_gram": 4,
      "max_gram": 4
    }
  ]
}

# 结果
{
  "tokens": [
    {
      "token": "hell",
      "start_offset": 2,
      "end_offset": 7,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "ello",
      "start_offset": 2,
      "end_offset": 7,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "worl",
      "start_offset": 8,
      "end_offset": 13,
      "type": "<ALPHANUM>",
      "position": 2
    },
    {
      "token": "orld",
      "start_offset": 8,
      "end_offset": 13,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

自定义分词

自定义分词需要在索引配置中设定 char_filter、tokenizer、filter、analyzer等

自定义分词示例:

  • 分词器名称:my_custom\
  • 过滤器将token转为大写
PUT test_index_1
{
  "settings": {
    "analysis": {
      "analyzer": {
        "my_custom_analyzer": {
          "type":      "custom",
          "tokenizer": "standard",
          "char_filter": [
            "html_strip"
          ],
          "filter": [
            "uppercase",
            "asciifolding"
          ]
        }
      }
    }
  }
}

自定义分词器测试

POST test_index_1/_analyze
{
  "analyzer": "my_custom_analyzer",
  "text": ["<p>I&apos;m so <b>happy</b>!</p>"]
}

# 结果
{
  "tokens": [
    {
      "token": "I'M",
      "start_offset": 3,
      "end_offset": 11,
      "type": "<ALPHANUM>",
      "position": 0
    },
    {
      "token": "SO",
      "start_offset": 12,
      "end_offset": 14,
      "type": "<ALPHANUM>",
      "position": 1
    },
    {
      "token": "HAPPY",
      "start_offset": 18,
      "end_offset": 27,
      "type": "<ALPHANUM>",
      "position": 2
    }
  ]
}

分词使用说明

分词会在如下两个时机使用:

  • 创建或更新文档时(Index Time),会对相应的文档进行分词处理
  • 查询时(Search Time),会对查询语句进行分词
    • 查询时通过analyzer指定分词器
    • 通过index mapping设置search_analyzer实现
    • 一般不需要特别指定查询时分词器,直接使用索引分词器即可,否则会出现无法匹配的情况

分词使用建议

  • 明确字段是否需要分词,不需要分词的字段就将type设置为keyword,可以节省空间和提高写性能
  • 善用_analyze API,查看文档的分词结果

更多内容请访问我的个人网站: http://laijianfeng.org
参考文档:

  1. elasticsearch 官方文档
  2. 慕课网 Elastic Stack从入门到实践
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • Elasticsearch 中文搜索时遇到几个问题: 当搜索关键词如:“人民币”时,如果分词将“人民币”分成“人”...
    sudop阅读 67,318评论 5 42
  • Solr&ElasticSearch原理及应用 一、综述 搜索 http://baike.baidu.com/it...
    楼外楼V阅读 7,254评论 1 17
  • 在上一篇文章中,我们知道client和ES交互的数据格式都是json,也知道了ES中的index和type的关系。...
    11舍的华莱士阅读 3,320评论 0 1
  • 借用一个俗套的句式,2017是最丧的一年,也是最燃的一年。最丧的是,我这辈子从来没这么老过,最燃的是,谢天谢地终于...
    白袍女巫阅读 401评论 0 0
  • 感谢艺术专题小编邀请,这是一篇历史文章应邀参加艺术专题、线上邀请展活动。创作时间: 2017.06.24创作材料及...
    狸大叔阅读 481评论 10 22