• <sup id="mk476"></sup>
    <dl id="mk476"></dl>
  • <progress id="mk476"><tr id="mk476"></tr></progress>
    <div id="mk476"><tr id="mk476"></tr></div>
    <sup id="mk476"><ins id="mk476"></ins></sup>
  • <progress id="mk476"></progress>
    <div id="mk476"></div>
    <div id="mk476"><tr id="mk476"></tr></div>
  • <div id="mk476"></div>
    <dl id="mk476"><s id="mk476"></s></dl><dl id="mk476"></dl><div id="mk476"></div>
  • <div id="mk476"></div>
    <dl id="mk476"><ins id="mk476"></ins></dl>

    es 修改拼音分词器源码实现汉字/拼音/简拼混合搜索时同音字不匹配

    [版权声明]:本文章由danvid发布于http://danvid.cnblogs.com/,如需转载或部分使用请注明出处  

     

      在业务中经常会用到拼音匹配查询,大家都会用到拼音分词器,但是拼音分词器匹配的时候有个问题,就是会出现同音字匹配,有时候这种情况是业务不希望出现的。

      业务场景:我输入"纯生pi酒"进行搜索,文档中有以下数据:

    doc[1]:{"name":"纯生啤酒"}

    doc[2]:{"name":"春生啤酒"}

    doc[3]:{"name":"纯生劈酒"}

    以上业务点是我输入"纯生pi酒"理论上业务希望只返回doc[1]:{"name":"纯生啤酒"}和doc[3]:{"name":"纯生劈酒"}其他的不是我要的数据,因为从业务角度?#32431;矗?#25105;已经输入"纯生"了,理论?#29616;?#38656;要返回有"纯生"的数据(当然也有很多情况,会希望把"春生"也返回来),正常使用拼音分词器,会把doc[2]?#19981;?#36820;回,原因是拼音分词器会把doc[2]变成:

    {
      "tokens": [
        {
          "token": "c",
          "start_offset": 0,
          "end_offset": 1,
          "type": "word",
          "position": 0
        },
        {
          "token": "chun",
          "start_offset": 0,
          "end_offset": 1,
          "type": "word",
          "position": 0
        },
        {
          "token": "s",
          "start_offset": 1,
          "end_offset": 2,
          "type": "word",
          "position": 1
        },
        {
          "token": "sheng",
          "start_offset": 1,
          "end_offset": 2,
          "type": "word",
          "position": 1
        },
        {
          "token": "p",
          "start_offset": 2,
          "end_offset": 3,
          "type": "word",
          "position": 2
        },
        {
          "token": "pi",
          "start_offset": 2,
          "end_offset": 3,
          "type": "word",
          "position": 2
        },
        {
          "token": "j",
          "start_offset": 3,
          "end_offset": 4,
          "type": "word",
          "position": 3
        },
        {
          "token": "jiu",
          "start_offset": 3,
          "end_offset": 4,
          "type": "word",
          "position": 3
        }
      ]
    }

    由于"纯生"和"春生"是同音字,分词结果doc[1]和doc[2]是一样的,所?#22253;裠oc[2]匹配上就是理所当然了,那么如何解决?

      其实我们的需求是就?#31508;?#20837;搜索文本时(搜索文本中可能同时存在中文/拼音),搜索文本中有[中文] 则按[中文]匹配,有[拼音]则按[拼音]匹配?#32431;桑?#36825;样就屏蔽掉了输入中文时匹配到同音字的问题。那么我们可以这样思考,我们索引的时候同时存在 全拼/简拼/中文 三种分词,搜索的时候 输入中有中文则按中文一个个分开,有英文则按拼音进行分词?#32431;?例如:

    索引时"纯生啤酒"分词为:

    索引分词:
    {
    "tokens": [ { "token": "c", "start_offset": 0, "end_offset": 1, "type": "word", "position": 0 }, { "token": "chun", "start_offset": 0, "end_offset": 1, "type": "word", "position": 0 }, { "token": "", "start_offset": 0, "end_offset": 1, "type": "word", "position": 0 }, { "token": "s", "start_offset": 1, "end_offset": 2, "type": "word", "position": 1 }, { "token": "sheng", "start_offset": 1, "end_offset": 2, "type": "word", "position": 1 }, { "token": "", "start_offset": 1, "end_offset": 2, "type": "word", "position": 1 }, { "token": "p", "start_offset": 2, "end_offset": 3, "type": "word", "position": 2 }, { "token": "pi", "start_offset": 2, "end_offset": 3, "type": "word", "position": 2 }, { "token": "", "start_offset": 2, "end_offset": 3, "type": "word", "position": 2 }, { "token": "j", "start_offset": 3, "end_offset": 4, "type": "word", "position": 3 }, { "token": "jiu", "start_offset": 3, "end_offset": 4, "type": "word", "position": 3 }, { "token": "", "start_offset": 3, "end_offset": 4, "type": "word", "position": 3 } ] }

    搜索"纯生pi酒",分词为:

    搜索分词:
    {
    "tokens": [ { "token": "", "start_offset": 0, "end_offset": 1, "type": "word", "position": 0 }, { "token": "", "start_offset": 1, "end_offset": 2, "type": "word", "position": 1 }, { "token": "pi", "start_offset": 2, "end_offset": 4, "type": "word", "position": 2 }, { "token": "", "start_offset": 4, "end_offset": 5, "type": "word", "position": 3 } ] }

    这样就可以只匹配出有"纯"|"生"|"酒"这几个字的数据了,而不是把"春"字的doc也匹配出来,既然解决思路有了,就找方案了。

      由于目前的es的拼音分词器是没有分离中文并保留中文的功能,所以就需要修改其源码增加这个功能(使用的拼音分词器: https://github.com/medcl/elasticsearch-analysis-pinyin)

      源码的话在上面地址上可以下在,源码的原理大概讲一下,就是他调用一个nlp工具包( https://github.com/NLPchina)先对输入文本解析成拼音 即"纯生pi酒"会解析成["chun","sheng",null,null,"酒"]数组(这里再提一句这个nlp工具包会对词组进行解析,而不是单个字进行解析例如"厦/门"会解析成"xia/men"而不是"sha/men"这个确实有用很多,当然他还有很多工具,例如简繁体转化等等,大家可以学习使用一哈),然后再单独对英文数字放到buff里面进行二次匹配,采用"正向最大匹配"和"逆向最大匹配"取出最优解(这些都是常用的分词?#22336;?匹配出拼音字符,源代码如下:

    // 分别正向、逆向最大匹配,选出最短的作为最优结果
    List<String> forward = positiveMaxMatch(pinyinText, PINYIN_MAX_LENGTH);
    if (forward.size() == 1) { // 前向只切出1个的话,没有必要再做逆向分词
    pinyinList.addAll(forward);
    } else {
    // 分别正向、逆向最大匹配,选出最短的作为最优结果
    List<String> backward = reverseMaxMatch(pinyinText, PINYIN_MAX_LENGTH);
    if (forward.size() <= backward.size()) {
    pinyinList.addAll(forward);
    } else {
    pinyinList.addAll(backward);
    }
    }

    至于拼音字典匹配结构由于拼音的数量不多,拼音源码采用了HashSet的结构而不是我们ik里面的字典树。("正向最大匹配"和"逆向最大匹配"百度一大把就不在这说了)

      原理大概讲完了根据需求我们是不需要管英文数字这一块的匹配逻辑的,只需要修改中文转拼音这附近的逻辑?#32431;傘?/p>

      首先我们先写一个中文分割的工具类或者方法如下:

    public class ChineseUtil {
        /**
         * 汉字始
         */
        public static char CJK_UNIFIED_IDEOGRAPHS_START = '\u4E00';
        /**
         * 汉字止
         */
        public static char CJK_UNIFIED_IDEOGRAPHS_END = '\u9FA5';
    
        public static List<String> segmentChinese(String str){
            if (StringUtil.isBlank(str)) {
                return Collections.emptyList();
            }
            
            List<String> lists = str.length()<=32767?new ArrayList<>(str.length()):new LinkedList<>();
            for (int i=0;i<str.length();i++){
                char c = str.charAt(i);
                if(c>=CJK_UNIFIED_IDEOGRAPHS_START&&c<=CJK_UNIFIED_IDEOGRAPHS_END){
                    lists.add(String.valueOf(c));
                }
                else{
                    lists.add(null);
                }
    
            }
            return lists;
        }
    }

    汉字始或者汉字止这个查一下nlp工具的源码(PinyinUtil)就可以?#19994;劍?#25110;者百度。然后在拼音源码中的PinyinConfig类中添加一项中文分割的配置:

    默认false就可以了,然后我们需要修改两个类(PinyinTokenFilter/PinyinTokenizer),这两个类是最要的分词类,对应es的analysis的filter和tokenizer

      由于这两个类修改地方是一样的我就随便讲一个,首先需要修改构造器的校验,添加刚刚增加的配置:

    然后修改该类的readTerm()方法,如下:

     

    两个类都修改完就完成源码修改了,现在需要对源码重新进行打包,mvn install以下就可以了,你就会拿到elasticsearch-analysis-pinyin-5.6.4.jar(你下载源码的时候要下载release的版本进行修改,版本也要对应你的es哦),同时在源码的lib拿到nlp-lang-1.7.jar包 ,再?#30001;蟫esource中的plugin-descriptor.properties(这个需要定义插件版本,启动类等东西,这个去拼音release版本中找个可用的插件解压一下跟着配置就可以了),最后变?#19978;?#38754;这个样子:

    放在一个文件夹里面,这个就是打包好的插件了,名字自?#22909;?#21517;?#32431;桑?#28982;后放到es的plugin目录里面就完成修改了。

      剩下就是修改index的setting和mapping,修改思想就是按照开头说的那样search_analyzer和analyzer分开?#32431;桑?#22914;下:

    PUT /test_index
    {
      "settings": {
        "analysis": {
          "analyzer": {
            "pinyin_chinese_analyzer": {
              "tokenizer": "pinyin_tokenizer"
            },
            "pinyin_analyzer": {
              "tokenizer": "pinyin_chinese_tokenizer"
            }
          }, 
          "tokenizer": {
            "pinyin_chinese_tokenizer": {
              "type": "pinyin",
              "keep_first_letter": false,
              "keep_separate_first_letter": false,
              "keep_full_pinyin":false,
              "keep_original":false,
              "limit_first_letter_length":50,
              "keep_separate_chinese": true,
              "lowercase":true
              
            },
            "pinyin_tokenizer": {
              "type": "pinyin",
              "keep_first_letter": false,
              "keep_separate_first_letter": true,
              "keep_full_pinyin":true,
              "keep_original":false,
              "limit_first_letter_length":50,
              "keep_separate_chinese": true,
              "lowercase":true
            }
          }
        }
      }
      , "mappings": {
        "indexType":{
          "properties": {
            "name":{
              "type": "text",
              "search_analyzer": "pinyin_chinese_analyzer",
              "analyzer": "pinyin_analyzer"
            }
          }
        }
      }
    }

    查询使用match_pharse?#32431;?使用原理可以参考?#19994;?#25991;章

      总结:其实解决思路并不复杂,不过其实在修改源码之前?#37096;?#34385;过其他方案,例如通过修改tokenizer为standard或者ik+fliter为pinyin进行分词等,但是总是存在各种问题不尽人意,用standard的时候由于已经拆分成了字,所以会出现"厦门"这种多音字被转化为"shamen"而不是"xiamen",而ik分词则在使用match_phrase时可控性较差~?#30001;?#21463;词库的影响,最后才决定使用修改源码增加功能的方式~如果大家有更好的方式可以推荐一下

     

     

    [说明]:elasticsearch版本5.6.4

    江苏11选5软件 吉林11选5软件手机版 梭哈游戏哪里可以下载 足彩胜负 平特一肖精准图片 江苏十一选五开奖号码走势图 最准特马资料 红球中五个有奖吗 新疆11选5出号走势图 福彩3d跨度走势图带连线 河北11选5软件手机版下载 广西11选50 17年亿元大奖 体彩任选9场 足彩14场胜负 买彩票什么软件可靠