zoukankan      html  css  js  c++  java
  • ES倒排索引与分词详解

    倒排索引

    • 正排索引:文档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,查看文档的分词结果

    分析与分析器

    分析 包含下面的过程:

    • 首先,将一块文本分成适合于倒排索引的独立的 词条 ,
    • 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall

    分析器执行上面的工作。 分析器 实际上是将三个功能封装到了一个包里:

    字符过滤器

    首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成 `and`。

    分词器

    其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。

    Token 过滤器

    最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a`, `and`, `the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

    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)

    语言分析器

    特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 英语 分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的 词干 。

    英语 分词器会产生下面的词条:

    set, shape, semi, transpar, call, set_tran, 5

    注意看 transparent`、 `calling 和 set_trans 已经变为词根格式。

    什么时候使用分析器

    当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域 搜索 的时候,我们需要将查询字符串通过 相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。

    全文查询,理解每个域是如何定义的,因此它们可以做 正确的事:

    • 当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
    • 当你查询一个 精确值 域时,不会分析查询字符串, 而是搜索你指定的精确值。

    现在你可以理解在 开始章节 的查询为什么返回那样的结果:

    • date 域包含一个精确值:单独的词条 `2014-09-15`。
    • _all 域是一个全文域,所以分词进程将日期转化为三个词条: `2014`, `09`, 和 `15`。

    当我们在 _all 域查询 2014`,它匹配所有的12条推文,因为它们都含有 `2014 :

    GET /_search?q=2014              # 12 results
    

    当我们在 _all 域查询 2014-09-15`,它首先分析查询字符串,产生匹配 `2014`, `09`, 或 `15 中 任意 词条的查询。这也会匹配所有12条推文,因为它们都含有 2014 :

    GET /_search?q=2014-09-15        # 12 results !
    

    当我们在 date 域查询 `2014-09-15`,它寻找 精确 日期,只找到一个推文:

    GET /_search?q=date:2014-09-15   # 1  result
    

    当我们在 date 域查询 `2014`,它找不到任何文档,因为没有文档含有这个精确日志:

    GET /_search?q=date:2014         # 0  results !
    

    测试分析器

    有些时候很难理解分词的过程和实际被存储到索引中的词条,特别是你刚接触 Elasticsearch。为了理解发生了什么,你可以使用 analyze API 来看文本是如何被分析的。在消息体里,指定分析器和要分析的文本:

    GET /_analyze
    {
      "analyzer": "standard",
      "text": "Text to analyze"
    }
    

    结果中每个元素代表一个单独的词条:

    {
       "tokens": [
          {
             "token":        "text",
             "start_offset": 0,
             "end_offset":   4,
             "type":         "<ALPHANUM>",
             "position":     1
          },
          {
             "token":        "to",
             "start_offset": 5,
             "end_offset":   7,
             "type":         "<ALPHANUM>",
             "position":     2
          },
          {
             "token":        "analyze",
             "start_offset": 8,
             "end_offset":   15,
             "type":         "<ALPHANUM>",
             "position":     3
          }
       ]
    }

    token 是实际存储到索引中的词条。 position 指明词条在原始文本中出现的位置。 start_offset 和 end_offset 指明字符在原始字符串中的位置。

    每个分析器的 type 值都不一样,可以忽略它们。它们在Elasticsearch中的唯一作用在于keep_types token 过滤器

    analyze API 是一个有用的工具,它有助于我们理解Elasticsearch索引内部发生了什么,随着深入,我们会进一步讨论它。

    指定分析器

    当Elasticsearch在你的文档中检测到一个新的字符串域 ,它会自动设置其为一个全文 字符串 域,使用 标准 分析器对它进行分析。

    你不希望总是这样。可能你想使用一个不同的分析器,适用于你的数据使用的语言。有时候你想要一个字符串域就是一个字符串域--不使用分析,直接索引你传入的精确值,例如用户ID或者一个内部的状态域或标签。

    要做到这一点,我们必须手动指定这些域的映射。

    更多内容请访问网站: http://laijianfeng.org

    转自:https://www.jianshu.com/p/3abaa0083bac 

    正因为当初对未来做了太多的憧憬,所以对现在的自己尤其失望。生命中曾经有过的所有灿烂,终究都需要用寂寞来偿还。
  • 相关阅读:
    Spark Streaming 的容错
    Master 接受其它组件的注册
    Spark Context 概述
    Python 使用random模块生成随机数
    Python 中print 和return 的区别
    Python 访问字典(dictionary)中元素
    PIL:处理图像的好模块
    2.线性回归
    3.梯度下降法
    4.pca与梯度上升法
  • 原文地址:https://www.cnblogs.com/candlia/p/11920028.html
Copyright © 2011-2022 走看看