zoukankan      html  css  js  c++  java
  • 如何在Elasticsearch中安装中文分词器(IK+pinyin)

    如果直接使用Elasticsearch的朋友在处理中文内容的搜索时,肯定会遇到很尴尬的问题——中文词语被分成了一个一个的汉字,当用Kibana作图的时候,按照term来分组,结果一个汉字被分成了一组。

    这是因为使用了Elasticsearch中默认的标准分词器,这个分词器在处理中文的时候会把中文单词切分成一个一个的汉字,因此引入中文的分词器就能解决这个问题。

    本篇文章按照下面的内容进行描述:

    • 分词器的作用
    • 安装IK
    • 简单的测试
    • 模拟测试
    • 安装elasticsearch-analysis-pinyin
    • 简单的测试
    • 模拟测试

    分词器的作用

    分词顾名思义,就是把一句话分成一个一个的词。这个概念在搜索中很重要,比如 This is a banana. 如果按照普通的空格来分词,分成this,is,a,banana,的出来的a其实对我们并没有什么用处。因此需要注意下面的问题:

    • 1 区分停顿词(a,or,and这种都属于停顿词)
    • 2 大小写转换(Bananabanana)
    • 3 时态的转换....

    具体的算法可以参考http://tartarus.org/~martin/PorterStemmer/,对照的词语可以参考这里http://snowball.tartarus.org/algorithms/porter/diffs.txt

    相比中文,就复杂的度了。因为中文不能单纯的依靠空格,标点这种进行分词。就比如中华人民共和国国民,不能简单的分成一个词,也不能粗暴的分成中华人民共和国国民人民中华这些也都算一个词!

    因此常见的分词算法就是拿一个标准的词典,关键词都在这个词典里面。然后按照几种规则去查找有没有关键词,比如:

    • 正向最大匹配(从左到右)
    • 逆向最大匹配(从右到左)
    • 最少切分
    • 双向匹配(从左扫描一次,从右扫描一次)

    IK,elasticsearch-analysis-ik提供了两种方式,ik_smart就是最少切分,ik_max_word则为细粒度的切分(可能是双向,没看过源码)

    了解了分词器的背景后,就可以看一下如何在Elasticsearch重安装分词器了。

    安装IK

    github中下载相应的代码,比如我的最新版本2.4.0就没有对应的ik版本,不用担心,只需要修改pom.xml就可以了:

    <properties>
            <!-- 这里的版本号,修改成你对应的版本就行了。
            不过最好不要跨度太大,相近的版本可能没有问题,但是跨度太大的版本,这样做就不保证好使了-->
            <elasticsearch.version>2.4.0</elasticsearch.version>
            <maven.compiler.target>1.7</maven.compiler.target>
            <elasticsearch.assembly.descriptor>${project.basedir}/src/main/assemblies/plugin.xml</elasticsearch.assembly.descriptor>
            <elasticsearch.plugin.name>analysis-ik</elasticsearch.plugin.name>
            <elasticsearch.plugin.classname>org.elasticsearch.plugin.analysis.ik.AnalysisIkPlugin</elasticsearch.plugin.classname>
            <elasticsearch.plugin.jvm>true</elasticsearch.plugin.jvm>
            <tests.rest.load_packaged>false</tests.rest.load_packaged>
            <skip.unit.tests>true</skip.unit.tests>
            <gpg.keyname>4E899B30</gpg.keyname>
            <gpg.useagent>true</gpg.useagent> 
        </properties>
    

    下载后,执行mvn package,进行打包:

    ├─config
    ├─src
    └─target
        ├─archive-tmp
        ├─classes
        ├─generated-sources
        ├─maven-archiver
        ├─maven-status
        ├─releases
        │  └─elasticsearch-analysis-ik-1.9.5.zip
        └─surefire
    

    编译完成后,可以在target/releases目录下找到对应的zip包。

    解压zip包,复制到elasticsearch-root-path/plugins/ik下即可。

    [root@hadoop-master ik]# ll
    total 1428
    -rw-r--r-- 1 root root 263965 Sep 26 15:03 commons-codec-1.9.jar
    -rw-r--r-- 1 root root  61829 Sep 26 15:03 commons-logging-1.2.jar
    drwxr-xr-x 3 root root   4096 Sep 26 16:11 config
    -rw-r--r-- 1 root root  56023 Sep 26 15:03 elasticsearch-analysis-ik-1.9.5.jar
    -rw-r--r-- 1 root root 736658 Sep 26 15:03 httpclient-4.5.2.jar
    -rw-r--r-- 1 root root 326724 Sep 26 15:03 httpcore-4.4.4.jar
    -rw-r--r-- 1 root root   2666 Sep 26 15:03 plugin-descriptor.properties
    [root@hadoop-master ik]# pwd
    /usr/elk/elasticsearch-2.4.0/plugins/ik
    

    拷贝后,重启elasticsearch就可以使用分词器了。

    最简单的测试

    这里使用_analyze api对中文段落进行分词,测试一下:

    GET _analyze
    {
      "analyzer":"ik_max_word",
      "text":"中华人民共和国国歌"
    }
    

    可以看到ik尽可能多的切分的单词:

    {
      "tokens": [
        {
          "token": "中华人民共和国",
          "start_offset": 0,
          "end_offset": 7,
          "type": "CN_WORD",
          "position": 0
        },
        {
          "token": "中华人民",
          "start_offset": 0,
          "end_offset": 4,
          "type": "CN_WORD",
          "position": 1
        },
        {
          "token": "中华",
          "start_offset": 0,
          "end_offset": 2,
          "type": "CN_WORD",
          "position": 2
        },
        {
          "token": "华人",
          "start_offset": 1,
          "end_offset": 3,
          "type": "CN_WORD",
          "position": 3
        },
        {
          "token": "人民共和国",
          "start_offset": 2,
          "end_offset": 7,
          "type": "CN_WORD",
          "position": 4
        },
        {
          "token": "人民",
          "start_offset": 2,
          "end_offset": 4,
          "type": "CN_WORD",
          "position": 5
        },
        {
          "token": "共和国",
          "start_offset": 4,
          "end_offset": 7,
          "type": "CN_WORD",
          "position": 6
        },
        {
          "token": "共和",
          "start_offset": 4,
          "end_offset": 6,
          "type": "CN_WORD",
          "position": 7
        },
        {
          "token": "国",
          "start_offset": 6,
          "end_offset": 7,
          "type": "CN_CHAR",
          "position": 8
        },
        {
          "token": "国歌",
          "start_offset": 7,
          "end_offset": 9,
          "type": "CN_WORD",
          "position": 9
        }
      ]
    }
    

    如果使用ik_smart,则会尽可能少的返回词语:

    {
      "tokens": [
        {
          "token": "中华人民共和国",
          "start_offset": 0,
          "end_offset": 7,
          "type": "CN_WORD",
          "position": 0
        },
        {
          "token": "国歌",
          "start_offset": 7,
          "end_offset": 9,
          "type": "CN_WORD",
          "position": 1
        }
      ]
    }
    

    模拟测试

    我这里直接在elastic Sense中进行测试的(强烈推荐这个插件,非常好用,不过输入中文的时候,有点BUG)

    第一步,创建一个空的索引

    PUT test 
    {
      
    }
    

    如果你用的是curl,可以执行curl -XPUT localhost:9200/test

    第二步,设置映射类型

    POST test/test/_mapping
    {
        "test": {
            "_all": {
                "analyzer": "ik_max_word",
                "search_analyzer": "ik_max_word",
                "term_vector": "no",
                "store": "false"
            },
            "properties": {
                "content": {
                    "type": "string",
                    "store": "no",
                    "term_vector": "with_positions_offsets",
                    "analyzer": "ik_max_word",
                    "search_analyzer": "ik_max_word",
                    "include_in_all": "true",
                    "boost": 8
                }
            }
        }
    }
    

    上面的命令,是定义test索引下test类型的映射。其中定义了_all字段的分析方法,以及content属性的分析方法。

    这里介绍下什么是_all字段,其实_all字段是为了在不知道搜索哪个字段时,使用的。es会把所有的字段(除非你手动设置成false),都放在_all中,然后通过分词器去解析。当你使用query_string的时候,默认就在这个_all字段上去做查询,而不需要挨个字段遍历,节省了时间。

    properties中定义了特定字段的分析方式。在上面的例子中,仅仅设置了content的分析方法。

    • type,字段的类型为string,只有string类型才涉及到分词,像是数字之类的是不需要分词的。
    • store,定义字段的存储方式,no代表不单独存储,查询的时候会从_source中解析。当你频繁的针对某个字段查询时,可以考虑设置成true。
    • term_vector,定义了词的存储方式,with_position_offsets,意思是存储词语的偏移位置,在结果高亮的时候有用。
    • analyzer,定义了索引时的分词方法
    • search_analyzer,定义了搜索时的分词方法
    • include_in_all,定义了是否包含在_all字段中
    • boost,是跟计算分值相关的。

    设置完成后,添加一个文档

    POST test/test/1
    {
      "test":"美国留给伊拉克的是个烂摊子吗"
    }
    
    POST test/test/2
    {
      "content":"公安部:各地校车将享最高路权吗"
    }
    
    POST test/test/3
    {
      "content":"中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
    }
    
    POST test/test/4
    {
      "content":"中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
    }
    
    

    最后,执行查询进行测试

    GET test/_search
    {
      "query" : { "term" : { "content" : "中国" }},
      "highlight" : {
            "pre_tags" : ["<tag1>", "<tag2>"],
            "post_tags" : ["</tag1>", "</tag2>"],
            "fields" : {
                "content" : {}
            }
        }
    }
    

    得到返回结果:

    {
      "took": 4,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 2,
        "max_score": 1.5,
        "hits": [
          {
            "_index": "test",
            "_type": "test",
            "_id": "4",
            "_score": 1.5,
            "_source": {
              "content": "中国驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
            },
            "highlight": {
              "content": [
                "<tag1>中国</tag1>驻洛杉矶领事馆遭亚裔男子枪击 嫌犯已自首"
              ]
            }
          },
          {
            "_index": "test",
            "_type": "test",
            "_id": "3",
            "_score": 0.53699243,
            "_source": {
              "content": "中韩渔警冲突调查:韩警平均每天扣1艘中国渔船"
            },
            "highlight": {
              "content": [
                "中韩渔警冲突调查:韩警平均每天扣1艘<tag1>中国</tag1>渔船"
              ]
            }
          }
        ]
      }
    }
    

    安装elasticsearch-analysis-pinyin分词器

    pinyin分词器可以让用户输入拼音,就能查找到相关的关键词。比如在某个商城搜索中,输入shuihu,就能匹配到水壶。这样的体验还是非常好的。

    pinyin分词器的安装与IK是一样的,这里就省略掉了。下载的地址参考github.

    这个分词器在1.8版本中,提供了两种分词规则:

    • pinyin,就是普通的把汉字转换成拼音;
    • pinyin_first_letter,提取汉字的拼音首字母

    简单的测试

    首先创建索引,并创建分词器:

    PUT medcl
    {
      "index" : {
            "analysis" : {
                "analyzer" : {
                    "pinyin_analyzer" : {
                        "tokenizer" : "my_pinyin",
                        "filter" : "word_delimiter"
                        }
                },
                "tokenizer" : {
                    "my_pinyin" : {
                        "type" : "pinyin",
                        "first_letter" : "none",
                        "padding_char" : " "
                    }
                }
            }
        }
    }
    

    然后使用analyze api,进行测试

    GET medcl/_analyze
    {
      "text":"刘德华",
      "analyzer":"pinyin_analyzer"
    }
    

    可以得到结果:

    {
      "tokens": [
        {
          "token": "liu",
          "start_offset": 0,
          "end_offset": 3,
          "type": "word",
          "position": 0
        },
        {
          "token": "de",
          "start_offset": 0,
          "end_offset": 3,
          "type": "word",
          "position": 1
        },
        {
          "token": "hua",
          "start_offset": 0,
          "end_offset": 3,
          "type": "word",
          "position": 2
        }
      ]
    }
    

    如果分词器设置为pinyin_first_letter,则分析的结果为:

    {
      "tokens": [
        {
          "token": "ldh",
          "start_offset": 0,
          "end_offset": 3,
          "type": "word",
          "position": 0
        }
      ]
    }
    

    模拟测试

    如果索引已经存在,需要先关闭索引

    POST medcl/_close
    {
      
    }
    

    然后设置分词器配置

    PUT medcl/_settings 
    {
      "index" : {
            "analysis" : {
                "analyzer" : {
                    "pinyin_analyzer" : {
                        "tokenizer" : "my_pinyin",
                        "filter" : ["word_delimiter","nGram"]
                    }
                },
                "tokenizer" : {
                    "my_pinyin" : {
                        "type" : "pinyin",
                        "first_letter" : "prefix",
                        "padding_char" : " "
                    }
                }
            }
        }
    }
    

    打开索引

    POST medcl/_open
    {
      
    }
    

    定义映射类型

    POST medcl/folks/_mapping
    {
      "folks": {
            "properties": {
                "name": {
                    "type": "multi_field",
                    "fields": {
                        "name": {
                            "type": "string",
                            "store": "no",
                            "term_vector": "with_positions_offsets",
                            "analyzer": "pinyin_analyzer",
                            "boost": 10
                        },
                        "primitive": {
                            "type": "string",
                            "store": "yes",
                            "analyzer": "keyword"
                        }
                    }
                }
            }
        }
    }
    

    提交样例数据

    POST medcl/folks/1
    {
      "name":"刘德华"
    }
    

    执行查询

    GET medcl/folks/_search
    {
     "query": {"match": {
       "name": "l d hua"
     }}
    }
    

    这里搜liu de hua,ldh,l de hua都能匹配到,还是很强大滴。

    得到结果

    {
      "took": 7,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
      },
      "hits": {
        "total": 1,
        "max_score": 7.408082,
        "hits": [
          {
            "_index": "medcl",
            "_type": "folks",
            "_id": "1",
            "_score": 7.408082,
            "_source": {
              "name": "刘德华"
            }
          }
        ]
      }
    }
    

    参考

  • 相关阅读:
    ubuntu安装jdk的两种方法
    LeetCode 606. Construct String from Binary Tree (建立一个二叉树的string)
    LeetCode 617. Merge Two Binary Tree (合并两个二叉树)
    LeetCode 476. Number Complement (数的补数)
    LeetCode 575. Distribute Candies (发糖果)
    LeetCode 461. Hamming Distance (汉明距离)
    LeetCode 405. Convert a Number to Hexadecimal (把一个数转化为16进制)
    LeetCode 594. Longest Harmonious Subsequence (最长的协调子序列)
    LeetCode 371. Sum of Two Integers (两数之和)
    LeetCode 342. Power of Four (4的次方)
  • 原文地址:https://www.cnblogs.com/xing901022/p/5910139.html
Copyright © 2011-2022 走看看