ES中文分詞器之精確短語匹配(自定義分詞器)

為什麼要自己寫分詞器?

樓主想要一種分詞器,分詞器完全按照詞典分詞,只要是詞典有的詞語,分詞器就一定要分出來。測試了兩個分詞器比如說IK,MMseg,都不能按照樓主的要求分詞。

MMSeg有考慮到詞頻,即使使用mmseg_max_word,也不能完全按照詞典分詞。

IK理論上是按照詞典分詞的,但是經測試,還是發現了些問題。比如說“一群穆斯林聚在一起”,單獨用這句話測試,“穆斯林”可以分出,而這句話放入一篇文章中,卻無法分出“穆斯林”。

樓主是用ik和standard對比命中量發現不一致,導出不一致數據後,才發現的這個問題(ik和mmseg都修改了源碼,過濾掉中文之間的特殊符號,因此不存在詞語中間有特殊符號standard可以分出,ik分不出而導致的不一致情況)。

沒辦法了,自己寫一個吧。

ES自定義分詞器

由於ES是採用juice依賴注入的方式,所以要實現一個工廠類和Provider類。

public class TestAnalyzerProvider extends AbstractIndexAnalyzerProvider<infosecanalyzer> {

public TestAnalyzerProvider(IndexSettings indexSettings, Environment env, String name, Settings settings) {
super(indexSettings, name, settings);
}

public static AnalyzerProvider extends Analyzer> getMaxWord(IndexSettings indexSettings, Environment environment, String s, Settings settings) {
return new TestAnalyzerProvider(indexSettings,environment,s,settings);
}

@Override public InfosecAnalyzer get() {
return new InfosecAnalyzer();
}

}

public class TestTokenizerFactory extends AbstractTokenizerFactory {

public TestTokenizerFactory(IndexSettings indexSettings, Environment env, String name, Settings settings) {
super(indexSettings, name, settings);
}

public static TokenizerFactory getMaxWord(IndexSettings indexSettings, Environment environment, String name, Settings settings) {
return new TestTokenizerFactory(indexSettings,environment,name,settings);
}

@Override
public Tokenizer create() {
return new TestTokenizor();
}
}
/<infosecanalyzer>

接下來寫自己的插件配置類:

public class AnalysisTestPlugin extends Plugin implements AnalysisPlugin {

public static String PLUGIN_NAME = "analysis-test;

@Override
public Map<string>> getTokenizers() {
Map<string>> extra = new HashMap<>();

extra.put("test_max_word", TestTokenizerFactory::getMaxWord);

return extra;
}

@Override
public Map<string>>> getAnalyzers() {
Map<string>>> extra = new HashMap<>();

extra.put("test_max_word", TestAnalyzerProvider::getMaxWord);

return extra;
}
}
/<string>/<string>/<string>/<string>

因為我們只需要按照詞典分詞,所以這邊只有一種最大分詞模式,test_max_word。接下來就是Analyzer 和Tokenizor。

public class TestAnalyzer extends Analyzer {

public TestAnalyzer(){
super();
}
@Override
protected TokenStreamComponents createComponents(String fieldName) {
Tokenizer _TestTokenizer = new TestTokenizor();
return new TokenStreamComponents(_TestTokenizer);
}
}


public class TestTokenizor extends Tokenizer {
//詞元文本屬性
private final CharTermAttribute termAtt;
//詞元位移屬性
private final OffsetAttribute offsetAtt;
//詞元分類屬性(該屬性分類參考org.wltea.analyzer.core.Lexeme中的分類常量)
private final TypeAttribute typeAtt;
//記錄最後一個詞元的結束位置
private int endPosition;

private TestSegmenter test =null;

public InfosecTokenizor(){
super();
offsetAtt = addAttribute(OffsetAttribute.class);
termAtt = addAttribute(CharTermAttribute.class);
typeAtt = addAttribute(TypeAttribute.class);

test = new TestSegmenter(input);
}

@Override
public boolean incrementToken() throws IOException {
clearAttributes();
Word word = test.getNext();
if(word != null) {
termAtt.copyBuffer(word.getSen(), word.getWordOffset(), word.getLength());
offsetAtt.setOffset(word.getStartOffset(), word.getEndOffset());
typeAtt.setType(word.getType());
return true;
} else {
end();

return false;
}
}

public void reset() throws IOException {
super.reset();
//setReader 自動被調用, input 自動被設置。
test.reset(input);
}
}

自定義分詞器主要操作的是incrementToken方法,每次從TestSegmenter中取出一個詞,如果改詞存在,設置改詞的token屬性,返回true,即還有下一個token。如果改詞不存在,返回false,標誌著沒有數據了,結束分詞。

自定義分詞的詳細內容

由於代碼太多了,這裡就不一一貼出,只介紹下算法思想。

匹配類型

1)不匹配
2)前綴
3)匹配
4)匹配且是前綴

算法思想

先將數據分類組裝成句子,然後經過句子處理器將句子分為多個word,存入queue中,再由increateToken()方法依次取出。

組裝句子

依次掃描,將同類的數據組裝成句子。比如說“你好哈233節日,快樂!233dad”,掃描第一個字符發現是中文,則繼續向下掃描,一直掃描到‘2’,發現‘2’不是中文,則將“你好哈”組成句子交給句子處理器處理,將處理結果放入queue中。繼續掃描,遍歷到‘節’,發現‘節’不是數組,則將“233”組成一個word,放入queue。繼續掃描,將“節”,“日”依次放入句子中,掃描到“,”,因為要和standard 對比效果,所以我在代碼中過濾了中文間所有的符號,忽略“,”繼續掃描,依次將“快”“樂”存入句子。後面類似處理即可。

句子分詞

依次掃描句子,如果相鄰的數據可以組裝成一個詞,則將詞放入queue中,繼續遍歷下一個。例如“節日快樂”,分詞時首先掃描“節”,在詞典中查詢“節”,發現“節”是一個前綴,則繼續掃描“日”,發現“節日”是一個詞匹配,且是一個前綴,則將“節日”存入queue中,繼續掃描“節日快”,發現“節日快”是一個前綴,繼續掃描“節日快樂”,發現“節日快樂”僅是一個詞匹配,則將“節日快樂”存入queue中,結束從“節”開始的掃描。接下來按照上述方法從“日”字開始掃描。依次處理完整個句子。

詞典

詞典採用樹的結構,比如說“節日愉快”,“節日快樂”和“萬事如意”這三個詞,在詞典中如下表示:

ES中文分詞器之精確短語匹配(自定義分詞器)

詞典結構

查找時,記錄上一次前綴匹配的DicSegment,在前綴的DicSegment中,直接查找當前掃描字符,可以加快匹配速度。

比如說已經匹配到了”節日快“這個前綴,在匹配”節日快樂“時,直接在”快“對應的DicSegment中查找,這樣就不用再次匹配”節日“兩個字符。

問題

測試的過程中同樣的發現了一些問題,比如說:

原文:長白山脈
test分詞:長白 1 長白山 2 長白山脈 3 白山4 山脈5
查找詞語:長白山
test分詞:長白 1 長白山 2 白山 3

通過分詞可以看出在“長白山脈”中查詢不到“長白山”的。問題在於match_phrase的限制,長白山的分詞順序在原文構建索引時的位置不一樣,中間多出了一個“長白山脈”。

解決方案:

不能匹配的原因是,查找詞語在原文中和後面的字組成了詞語。用最小粒度分詞即可解決。也就是說只用長度為2和3的詞語。不存在長度為4的詞語,所以一個詞長度為3時,在原文中不會和後面的數據組成詞。當詞的長度為2時,和後面的一個字匹配,可以組成一個長度為3的詞,按照我們分詞的規則,是先分出兩個字的詞,再分出三個字的詞,所以,兩個字的詞是可以匹配的到的。

ES中文分詞器之精確短語匹配(自定義分詞器)



分享到:


相關文章: