IK分词器优化

1,597 阅读2分钟

背景

电商搜索系统中部分用户会输入商品型号进行搜索,商品型号一般是英文字母、阿拉伯数字、特殊符号(如-)的组合。如:LED-220-T8-19,NBS9502V。

搜索索引设计的时候为了平衡召回和精确率,一般建立索引时分词采用ik_max_word,搜索时分词采用ik_smart。ik_max_word会对文本做最细颗粒度的拆分,ik_smart则是对文本进行粗颗粒度拆分。两者最大的区别是:ik_max_word直接输出按照词典分词的结果,ik_smart对这些词进行歧义处理后才作为结果输出。

对商品型号进行分词的时候,两种分词方式会有不同的输出结果。例如:NBS9502V,ik_max_word 输出为:NBS9502V,NBS,9502,V。整体首先作为一个词,然后再找出字符串中的英文(ENGLISH)和数字(ARABIC)作为单独的词。ik_smart则直接将整体作为一个词输出:NBS9502V。从分词上看,当用户输入完整的型号词时(NBS9502V),可以召回该产品,当用户只输入英文(NBS)或者数字(9502)时也可以召回该产品,但是当用户输入为英文+数字的组合(NBS9502)时,由于两种分词器分词不一致,导致召回失败。

优化方案

当对型号进行ik_max_word分词时在原来分词的结果上,增加相邻英文和数字的组合。例如:NBS9502V,经过优化后分词结果为:NBS9502V,NBS,9502,V,NBS9502,9502V。

源码修改:

outputToResult()方法最后添加

//ik_max_word分词对英文+数字,数字+英文进行合并
        if (!this.cfg.isUseSmart()) {
            LinkedList<Lexeme> finalResults = new LinkedList<>();
            for (int i = 0; i < this.results.size() - 1; i++) {
                Lexeme result = this.results.get(i);
                for (int j = i + 1; j < this.results.size(); j++) {
                    Lexeme nextLexeme = this.results.get(j);
                    if (Lexeme.TYPE_ENGLISH == result.getLexemeType() && Lexeme.TYPE_ARABIC == nextLexeme.getLexemeType()) {
                        if (nextLexeme != null && result.getEndPosition() == nextLexeme.getBeginPosition()) {
                            Lexeme lexeme = new Lexeme(result.getOffset(), result.getBegin(),
                                    result.getLength() + nextLexeme.getLength(), Lexeme.TYPE_LETTER);
                            result = new Lexeme(result.getOffset(), result.getBegin(),
                                    result.getLength() + nextLexeme.getLength(), Lexeme.TYPE_ARABIC);
                            if (!this.results.contains(lexeme)) {
                                finalResults.add(lexeme);
                            }
                        }
                    }
                    if (Lexeme.TYPE_ARABIC == result.getLexemeType() && Lexeme.TYPE_ENGLISH == nextLexeme.getLexemeType()) {
                        if (nextLexeme != null && result.getEndPosition() == nextLexeme.getBeginPosition()) {
                            Lexeme lexeme = new Lexeme(result.getOffset(), result.getBegin(),
                                    result.getLength() + nextLexeme.getLength(), Lexeme.TYPE_LETTER);
                            result = new Lexeme(result.getOffset(), result.getBegin(),
                                    result.getLength() + nextLexeme.getLength(), Lexeme.TYPE_ENGLISH);
                            if (!this.results.contains(lexeme)) {
                                finalResults.add(lexeme);
                            }
                        }
                    }
                }
            }
            finalResults.addAll(results);
            results = finalResults;
            Collections.sort(results)
       }

总结

此优化可以部分解决当型号输入不全时召回问题。当输入型号为相邻的部分英文+数字时(如:NBS95)依然存在无法召回的问题,此时可以考虑ngram召回方案。ngram方案中,当n越大的时候,分词的结果会比较多,召回过程也会明显慢很多,一般n不大于3是可以满足召回要求的。