1、問題引出
微信羣裏的線上實戰問題:
諸位大哥,es中:
keyword類型的字段進行高亮查詢,值爲 123asd456,查詢 sd4,高亮結果是 em 123asd456 em
有沒有辦法只對我查詢的sd4高亮?
明明查詢id的一部分,卻高亮結果是整個id串,怎麼辦?
死磕Elasticsearch技術微信羣
2、一個Demo描述清楚問題
注:本文示例DSL在7.2版本運行ok,6.X之前早期版本可能需要微調。
PUT findex
{
"mappings": {
"properties": {
"aname":{
"type":"text"
},
"acode":{
"type":"keyword"
}
}
}
}
POST findex/_bulk
{"index":{"_id":1}}
{"acode":"160213.OF","aname":"X泰納斯達克100"}
{"index":{"_id":2}}
{"acode":"160218.OF","aname":"X泰國證房地產"}
POST findex/_search
{
"highlight": {
"fields": {
"acode": {}
}
},
"query": {
"bool": {
"should": [
{
"wildcard": {
"acode": "*1602*"
}
}
]
}
}
}
高亮檢索結果,
"highlight" : {
"acode" : [
"<em>160213.OF</em>"
]
}
也就是說整個串都被高亮了,沒有達到預期。
實際需求:搜索1602,相關數據:160213.O、160218.OF都能召回,且僅高亮搜索字段1602。
3、問題拆解
檢索選型wildcard是爲了解決子串能匹配的問題,wildcard的實現類似mysql的“like”模糊匹配。
傳統的text標準分詞器,包括中文分詞器ik、英文分詞器english、standard等都不能解決上述子串匹配問題。
而實際業務需求:
一方面:要求輸入子串召回全串;
另一方面:要求高亮檢索的子串。
只能更換一種分詞Ngram來實現了!
4、什麼是Ngram?
4.1 Ngram定義
Ngram是一種基於統計語言模型的算法。
Ngram基本思想:是將文本里面的內容按照字節進行大小爲N的滑動窗口操作,形成了長度是N的字節片段序列。每一個字節片段稱爲gram,對所有gram的出現頻度進行統計,並且按照事先設定好的閾值進行過濾,形成關鍵gram列表,也就是這個文本的向量特徵空間,列表中的每一種gram就是一個特徵向量維度。
該模型基於這樣一種假設,第N個詞的出現只與前面N-1個詞相關,而與其它任何詞都不相關,整句的概率就是各個詞出現概率的乘積。
這些概率可以通過直接從語料中統計N個詞同時出現的次數得到。常用的是二元的Bi-Gram(二元語法)和三元的Tri-Gram(三元語法)。
4.2 Ngram舉例
中文句子:“你今天吃飯了嗎”,它的Bi-Gram(二元語法)分詞結果爲:
你今
今天
天吃
吃飯
飯了
了嗎
4.3 Ngram 應用場景
場景1:文本壓縮、檢查拼寫錯誤、加速字符串查找、文獻語種識別。
場景2:自然語言處理自動化領域得到新的應用,如自動分類、自動索引、超鏈的自動生成、文獻檢索、
無分隔符語言文本的切分
等。場景3:自然語言的自動分類功能。對應到Elasticsearch檢索,應用場景就更加明確:無分隔符語言文本的切分分詞,提高檢索效率(相比:wildcard 查詢和正則查詢)。
5、實踐一把
PUT findex_ext
{
"settings": {
"index.max_ngram_diff": 10,
"analysis": {
"analyzer": {
"my_analyzer": {
"tokenizer": "my_tokenizer"
}
},
"tokenizer": {
"my_tokenizer": {
"type": "ngram",
"min_gram": 4,
"max_gram": 10,
"token_chars": [
"letter",
"digit"
]
}
}
}
},
"mappings": {
"properties": {
"aname": {
"type": "text"
},
"acode": {
"type": "text",
"analyzer": "my_analyzer",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
POST findex_ext/_bulk
{"index":{"_id":1}}
{"acode":"160213.OF","aname":"X泰納斯達克100"}
{"index":{"_id":2}}
{"acode":"160218.OF","aname":"X泰國證房地產"}
# 查看分詞結果
POST findex_ext/_analyze
{
"analyzer": "my_analyzer",
"text":"160213.OF"
}
POST findex_ext/_search
{
"highlight": {
"fields": {
"acode": {}
}
},
"query": {
"bool": {
"should": [
{
"match_phrase": {
"acode": {
"query": "1602"
}
}
}
]
}
}
}
注意:三個核心參數
min_gram:最小字符長度(切分),默認爲1
max_gram:最大字符長度(切分),默認爲2
token_chars:生成的分詞結果中包含的字符類型,默認是全部類型。如上的示例中代表:保留數字、字母。若上述示例中,只指定 "letter",則數字就會被過濾掉,分詞結果只剩下串中的字符如:"OF"。
返回結果截取片段如下:
"highlight" : {
"acode" : [
"<em>1602</em>13.OF"
]
}
已經能滿足檢索和高亮的雙重需求。
5、選型注意
Ngram的本質:用空間換時間。其能匹配的前提是寫入的時候已經按照:min_gram、max_gram切詞。
數據量非常少且不要求子串高亮,可以考慮keyword。
數據量大且要求子串高亮,推薦使用:Ngram分詞結合match或者match_phrase檢索實現。
數據量大,切記不要使用wildcard前綴匹配!
原因:帶有通配符的pattern構造出來的DFA(Deterministic Finite Automaton)可能會很複雜,開銷很大!甚至可能導致線上環境宕機。
Wood大叔也 多次強調:wildcard query應杜絕使用通配符打頭,實在不得已要這麼做,就一定需要限制用戶輸入的字符串長度。
6、小結
爲討論解決線上問題,引申出Ngram的原理和使用邏輯,並指出了wildcard和Ngram的適用業務場景。希望對實戰中的你有所啓發和幫助!
你在業務中遇到子串匹配和高亮的情況嗎?你是如何分詞和檢索的?歡迎留言討論。
參考:
1、https://zhuanlan.zhihu.com/p/32829048
2、http://blog.sciencenet.cn/blog-713101-797384.html
3、https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-ngram-tokenizer.html
4、https://elasticsearch.cn/article/171
更多推薦:
1、Elasticsearch實戰|如何從數千萬手機號中識別出情侶號?
3、95後運維小哥20天+通過Elastic認證考試經驗分享
中國通過Elastic認證工程師考試人數最多的圈子!
更短時間更快習得更多幹貨!