分詞器選擇
調研了幾種分詞器,例如IK分詞器,ansj分詞器,mmseg分詞器,發現IK的分詞效果最好。舉個例子:
詞:<<是的>><span>哈<\span>撒多撒?????? ????王者榮耀sdsd@4342啊啊啊
Standard: 是,的,span,哈,span,撒,多,撒,??????,????,王,者,榮,耀,sdsd,4342,啊,啊,啊,啊
mmseg_maxword:是,的,span,哈,span,撒,多,撒,?,?,?,?,?,?,?,?,?,?,王者,榮耀,sdsd,4342,啊,啊,啊,啊
IK_max_word:是的,span,哈,span,撒,多,撒,王者,王,者,榮耀,榮,耀,sdsd,4342,sdsd@4342,啊啊啊,啊啊,啊,啊啊,啊
Ansj: <,<,是,的,>,>,<,span,>,哈,<,\,span,>,撒,多,撒,?,?,?,?,?,?,?,?,?,?,王者,榮耀,sdsd,@,4342,啊,啊,啊,啊
在上述例子中,IK和Mmsg 用的同一套詞典。Ansj和IK,Mmsg使用的不是一套詞典,也沒有配置停詞。
本文講的中文分詞器就是IK分詞器。
分詞器需要達到的效果
1)短語可以精確匹配
2)查找時間要比standard少
3)如果查找的詞語不在詞典中,也必須要查到
4)如果數據在原文中出現,就一定要查全
IK分詞器短語精確匹配的問題
樓主意淫著將所有的單字放入詞典中,這樣用ik_max_word 對數據建索引時既可以把詞分出來建索引,又可以把字分出來建索引。然后用 ik_smart 將查找短語,因為ik_smart分出的數據是 ik_max_word 的一個子集,如果要查找的短語在原文中有出現,那么一定可以查到。后來發現用ik_smart分詞器查找句子(match_phrase)時一個都沒有查到,exo???為什么會查不到呢?明明是一個子集。對此官方網站對match_phrase的解釋如下:
Like the match query, the match_phrase query first analyzes the query string to produce a list of terms. It then searches for all the terms, but keeps only documents that contain all of the search terms, in the same positions relative to each other.
意思就是說用match_phrase查找時,查找分詞器分出的詞的位置和要建索引時分出的詞的位置一樣。舉個例子:
原文:快樂感恩
ik_max_word:快樂 1 快 2 樂感 3 樂 4 感恩 5 感 6 恩 7
ik_smart:快樂 1 感恩 2
從上面可以看出,查找時ik_smart將語句分為了快樂和感恩兩個詞,位置分別為1和2,而ik_max_word建索引時,快樂和感恩的位置分別是1和4,在match_phrase看來,這種是不匹配的,所以用ik_smart分詞短語時無法查到或者查全數據。
好吧,既然ik_smart無法查到,我用ik_max_word查找總行了吧。用上述的例子,查找”快樂“時,你會發現你用ik_max_word查找到的結果沒有standard分詞器建索引查找獲取到的結果多。原因和上述講的一樣:
查找詞語:快樂
ik_max_word:快樂 1 快 2 樂 3
在構建索引的時候,快樂,快和樂的位置分別是1,2,4,而查找時分詞的順序是1,2,3,然后match_phrase認為其不匹配,因此查詢不到這種結果。
網上答案
遇到問題了,在網上尋求解決方案??戳藥灼┛?,都指出了match_phrase的這個匹配問題,解決方案有以下兩種:
1) standard分詞器
2) NGram分詞器
standard分詞器大家都比較熟,針對于漢字就是一個一個分,這種肯定是可以查全的。但一個一個字分的話,每個字對應的文檔集合非常多,如果數據量達到了百億,在求交集,計算距離時,效果非常差。
Ngram分詞器類似于standard分詞器,他可以指定分詞的長度,然后用standard的方法切割。比如說“節日快樂”,我們指定切割的長度為2,NGram會切成“節日”,“日快”,“快樂”。雖然查找時可以減少每個token對應的文檔數,但是存儲量會增大很多,而且不在支持模糊的match匹配。很土。
求解過程
ik_max_word構建索引,ik_smart無法查找,原因是ik_max_word分出了所有的詞,ik_smart只分出了一種詞,由于match_phrase本身的限制導致ik_smart查找不到。那我構建的時候采用ik_smart,查找的時候也用ik_smart,這樣只要原文中有數據,構建和查找用同一種分詞方法,就應該可以查找得到。測試后發現,這種也有很大的問題,即像“潛行者”這樣的詞,只分為了“潛行”和“者”兩個token,但是“行者”也是一個詞,在查找“行者”時無法查全數據。
ik_smart無法查全的原因是只分出了一種詞的可能性,導致有些詞查詢不全。ik_max_word能解決這個問題。。但是ik_max_word的問題是如果查找的最后一個字能和原文中這個字的下一個字組成詞語,那么就會出現無法查全的問題。我們能不能讓ik_max_word將詞和字分開?
當然可以,對一個屬性指定兩種分詞方法:
curl -XPUT 'xxx/my_test_both?pretty' -H 'Content-Type: application/json' -d'
{"mappings": {
"my_type": {
"properties": {
"ulluin": {
"type": "text",
"fields": {
"ik":{"type":"text","analyzer": "ik_max_word"}
}
}
}
}
}
}'
這樣ulluin屬性采用standard分詞,即單字分詞,ulluin.ik采用ik_max_word即按詞分詞,ik_max_word的詞典中去除所有的單字。
查詢時先將查詢短語分詞,如果第一個token和最后一個token中存在一個字,那么這個字可能與原文中的下一個字或者上一個字組成詞,導致ik_max_word無法查到,這時我們用standard分詞器在ulluin中查詢,如果第一個token和最后一個token都是詞,那么說明可以在ik_max_word中查詢。來吧,測試一下:
原文:節日快樂龍哥
ik_max_word:節日 1 快樂龍 2 快樂 3 龍哥 4
查詢短語:節日快樂
ik_max_word:節日 1 快樂 2
為什么還是有問題?????ik_max_word查出的數據量比standard的少???還是因為match_phrase的限制,索引中“節日”和“快樂”的位置是1和3,而查找時“節日”和“快樂”的位置是1和2。這個問題很好解決,用match_phrase_prefix查詢即可,即:
curl -XGET 'xxx/my_test_both/_search?pretty' -H 'Content-Type: application/json' -d'
{
"query": {
"match_phrase_prefix" : {
"ulluin" : {
"query" : "節日快樂"
}
}
},
"size":1
}'
上面還提到ik_max_word有一個問題是分出的詞語比standard的多,我們過濾了單字分詞后,這個效果就會有很大的提升。假設我們的詞典沒有四字分詞,只有二三字。比如說
原詞:中華人民共和國
修改前:中華,中,華人,華,人民,人,民,共和國,共和,和,國
修改后:中華,華人,人民,共和國,共和
可以看出,修改后的效果要比standard的效果好的多,不但token數變少了,而且每個token對應的文檔數也大大的降低,減少了求交集的數據量和計算距離的數據量。
至此總算解決了ES中文分詞切精確匹配的問題。
源碼修改:
* 修改IK不支持小語種的問題
* 修改中文之間特殊字符不能過濾的問題。即原文“節 日 快 樂”不能匹配“節日快樂”的問題。
* 修改量詞,數詞單獨分出的問題。修改前“一天天”分為“一天天,一天,一,天天,天”,修改后分為“一天天,一天,天天”。