什麼是建議器
目前爲止,瀏覽器都已經具備Suggest as you type
功能,即在我們輸入搜索的過程中,進行自動的補全或者糾錯功能,協助用戶輸入更精確的關鍵詞,提高搜索階段的文檔匹配程度。例如我們在百度或谷歌瀏覽器輸入搜索關鍵詞時,雖然我們輸入的有誤,但是瀏覽器依然能夠提示出我們想要的正確結果。
在elasticsearch中,建議功能通過使用建議器基於提供的文本建議類似的詞
目前_suggest
已經棄用,我們可以通過_search
來做建議器的查詢。
在5.0版本中,_search
經過優化,變得非常的方便。
建議器如何使用
query與suggest結合
PUT s1
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
}
}
}
}
}
PUT s1/doc/1
{
"title": "Lucene is cool"
}
PUT s1/doc/2
{
"title":"Elasticsearch builds on top of lucene"
}
GET s1/doc/_search
{
"query": {
"match": {
"title": "Lucene"
}
},
"suggest": {
"my_suggest": {
"text": "Elasticsear lucen",
"term": {
"field": "title"
}
}
}
}
上例是一個包含建議的查詢請求,查詢query
我們已經瞭然。
讓我們注意suggest
,每個建議器都有自己名稱my-suggestion
,es根據text字段返回建議結果。建議類型是term
。從field
字段生成建議。
直接使用 suggest
如果我們僅需要建議而不需要查詢功能,我們可以忽略query
而直接使用suggest
對象返回建議。
GET s1/doc/_search
{
"suggest": {
"my_sugget": {
"text": "Elasticsear lucen",
"term": {
"field": "title"
}
}
}
}
可以根據需要指定幾組建議器,每組建議器都有自己的名稱。如下例的my_suggest1
和my_suggest2
。
GET s1/doc/_search
{
"suggest": {
"my_sugget1": {
"text": "Elasticsear",
"term": {
"field": "title"
}
},
"my_suggest2": {
"text": "lucen",
"term": {
"field": "title"
}
}
}
}
在多個建議器中,如果輸入的text
字段值一致,可以單獨寫出來,以適用於my_suggest1
和my_suggest2
兩個建議器。
GET s1/doc/_search
{
"suggest": {
"text": "Elasticsear lucen",
"my_sugget1": {
"term": {
"field": "title"
}
},
"my_suggest2": {
"term": {
"field": "title"
}
}
}
}
建議器種類
根據需求不同elasticsearch
設計了4種suggester
,分別是:
- 詞條建議器(
term suggester
):主要做糾正。對於給定文本的每個詞條,該鍵議器從索引中抽取要建議的關鍵詞,這對於短字段(如分類標籤)很有效。 - 詞組建議器(
phrase suggester
):主要做糾正。我們可以認爲它是詞條建議器的擴展,爲整個文本(而不是單個詞條)提供了替代方案,它考慮了各詞條彼此臨近出現的頻率,使得該建議器更適合較長的字段,比如商品的描述。 - 完成建議器(
completion suggester
):該建議器根據詞條的前綴,提供自動完成的功能(智能提示,有點最左前綴查詢的意思),爲了實現這種實時的建議功能,它得到了優化,工作在內存中。所以,速度要比之前說的match_phrase_prefix
快的多! - 上下文建議器(
context suggester
):它是完成建議器的擴展,允許我們根據詞條或分類亦或是地理位置對結果進行過濾。
詞條建議器(term suggester
)
詞條建議器接收輸入的文本,對其進行分析並且分爲詞條,然後爲每個詞條提供一系列的建議。
測試數據:創建索引,創建文檔。
PUT s2
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
}
}
}
}
}
PUT s2/doc/1
{
"title": "Lucene is cool"
}
PUT s2/doc/2
{
"title": "Elasticsearch builds on top of lucene"
}
PUT s2/doc/3
{
"title": "Elasticsearch rocks"
}
PUT s2/doc/4
{
"title": "Elastic is the company behind ELK stack"
}
PUT s2/doc/5
{
"title": "elk rocks"
}
PUT s2/doc/6
{
"title": "elasticsearch is rock solid"
}
查詢:
GET s2/doc/_search
{
"suggest": {
"my_suggest": {
"text": "luenc",
"term": {
"field": "title"
}
}
}
}
返回結果:
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : 0.0,
"hits" : [ ]
},
"suggest" : {
"my_suggest" : [
{
"text" : "luenc",
"offset" : 0,
"length" : 5,
"options" : [
{
"text" : "lucene",
"score" : 0.6,
"freq" : 2
}
]
}
]
}
}
上例中,在options
字段中,建議結果是lucene
。我們來看看,在建議器中,都有哪些字段。
text
:建議文本,建議文本是必需的選項,可以通過全局(多個建議器中查詢相同的內容)或者按照單個建議器的格式來。
field
:從field
字段中獲取候選建議的字段。這是一個必需的選項,需要全局設置或根據建議設置。
analyzer
:用於分析建議文本的分析器。默認爲建議字段的搜索分析器。
size
:個建議文本標記返回的最大條目。
sort
:定義如何根據建議文本術語對建議進行排序。它有兩個可能的值。
score
,先按分數排序,然後按文檔頻率排序,再按術語本身排序。
frequency
,首先按文檔頻率排序,然後按相似性分數排序,然後按術語本身排序。也可以理解爲按照流行度排序。
suggest_mode
:控制建議的模式,有3個模式可選擇。
missing
,僅爲不在索引中的建議文本術語提供建議。這是默認值。popular
,僅建議在比原始建議文本術語更多的文檔中出現的建議。也就是說提供比原有輸入詞頻更高的詞條always
,根據建議文本中的條款建議任何匹配的建議。說白了就是無論如何都會提供建議。
lowercase_terms
:在文本分析之後降低建議文本術語的大小寫。
min_word_length
:建議文本術語必須具有的最小長度才能包含在內。默認爲4.(舊名稱min_word_len
已棄用)。
shard_size
:設置從每個單獨分片中檢索的最大建議數。在減少階段,僅根據size
選項返回前N個建議。默認爲該 size
選項。將此值設置爲高於該值的值size
可能非常有用,以便以性能爲代價獲得更準確的拼寫更正文檔頻率。由於術語在分片之間被劃分,因此拼寫校正頻率的分片級文檔可能不準確。增加這些將使這些文檔頻率更精確。
max_inspections
:用於乘以的因子, shards_size
以便在碎片級別上檢查更多候選拼寫更正。可以以性能爲代價提高準確性。默認爲5。
string_distance
:用於比較類似建議術語的字符串距離實現。
internal
,默認值基於damerau_levenshtein
,但高度優化用於比較索引中術語的字符串距離。
damerau_levenshtein
,基於Damerau-Levenshtein
算法的字符串距離算法。
levenshtein
,基於Levenshtein
編輯距離算法的字符串距離算法。
jaro_winkler
,基於Jaro-Winkler
算法的字符串距離算法。
ngram
,基於字符n-gram
的字符串距離算法。
瞭解了各字段的大致含義,我們來探討一下,詞條建議器是如何運作的。以便理解如何確定哪些建議將成爲第一名。
選擇哪些詞條被建議
詞條建議器使用了Lucene
的錯拼檢查器模塊,該模塊會根據給定詞條的編輯距離(es使用了叫做Levenstein edit distance的算法,其核心思想就是一個詞改動多少字符就可以和另外一個詞一致),從索引中返回最大編輯距離不超過某個值的那些詞條。比如說爲了從mik
得到mick
,需要加入一個字母(也就是說需要至少要改動一次),所以這兩個詞的編輯距離就是1。我們可以通過配置一系列的選項,來均衡靈活和性能:
- max_edits:最大編輯距離候選建議可以具有以便被視爲建議。只能是介於1和2之間的值。任何其他值都會導致拋出錯誤的請求錯誤。默認爲2。
- prefix_length:必須匹配的最小前綴字符的數量纔是候選建議。默認爲1.增加此數字可提高拼寫檢查性能。通常拼寫錯誤不會出現在術語的開頭。(舊名
prefix_len
已棄用)。 - min_doc_freq:建議應出現的文檔數量的最小閾值。可以指定爲絕對數字或文檔數量的相對百分比。這可以僅通過建議高頻項來提高質量。默認爲0f且未啓用。如果指定的值大於1,則該數字不能是小數。分片級文檔頻率用於此選項。
- max_term_freq:建議文本令牌可以存在的文檔數量的最大閾值,以便包括在內。可以是表示文檔頻率的相對百分比數(例如0.4)或絕對數。如果指定的值大於1,則不能指定小數。默認爲0.01f。這可用於排除高頻術語的拼寫檢查。高頻術語通常拼寫正確,這也提高了拼寫檢查的性能。分片級文檔頻率用於此選項。
小結,term suggester
首先將輸入文本經過分析器(所以,分析結果由於採用的分析器不同而有所不同)分析,處理爲單個詞條,然後根據單個詞條去提供建議,並不會考慮多個詞條之間的關係。然後將每個詞條的建議結果(有或沒有)封裝到options
列表中。最後由建議器統一返回。
詞組建議器(phrase suggester
)
詞組建議器和詞條建議器一樣,不過它不再爲單個詞條提供建議,而是爲整個文本提供建議。
測試數據:創建索引,創建文檔。
PUT s4
{
"mappings": {
"doc": {
"properties": {
"title": {
"type": "text",
"analyzer": "standard"
}
}
}
}
}
PUT s4/doc/1
{
"title": "Lucene is cool"
}
PUT s4/doc/2
{
"title": "Elasticsearch builds on top of lucene"
}
PUT s4/doc/3
{
"title": "Elasticsearch rocks"
}
PUT s4/doc/4
{
"title": "Elastic is the company behind ELK stack"
}
PUT s4/doc/5
{
"title": "elk rocks"
}
PUT s4/doc/6
{
"title": "elasticsearch is rock solid"
}
查詢
GET s4/doc/_search
{
"suggest": {
"my_s4": {
"text": "lucne and elasticsear rock",
"phrase": {
"field": "title"
}
}
}
}
text
是輸入帶有拼錯的文本。而建議類型則換成了phrase
。來看查詢結果:
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : 0.0,
"hits" : [ ]
},
"suggest" : {
"my_s4" : [
{
"text" : "lucne and elasticsear rock",
"offset" : 0,
"length" : 26,
"options" : [
{
"text" : "lucne and elasticsearch rocks",
"score" : 0.12709484
},
{
"text" : "lucne and elasticsearch rock",
"score" : 0.10422645
},
{
"text" : "lucne and elasticsear rocks",
"score" : 0.10036137
}
]
}
]
}
}
可以看到options
直接返回了相關短語列表。雖然lucene
建議的並不好。但elasticserch
和rock
很不錯。除此之外,我們還可以使用高亮來向用戶展示哪些原有的詞條被糾正了。
GET s4/doc/_search
{
"suggest": {
"my_s4": {
"text": "lucne and elasticsear rock",
"phrase": {
"field": "title",
"highlight":{
"pre_tag":"<em>",
"post_tag":"</em>"
}
}
}
}
}
除了默認的,還可以自定義高亮顯示:
GET s4/doc/_search
{
"suggest": {
"my_s4": {
"text": "lucne and elasticsear rock",
"phrase": {
"field": "title",
"highlight":{
"pre_tag":"<b id='d1' class='t1' style='color:red;font-size:18px;'>",
"post_tag":"</b>"
}
}
}
}
}
需要注意的是,建議器結果的高亮顯示和查詢結果高亮顯示有些許區別,比如說,這裏的自定義標籤是pre_tag
和post_tag
而不是之前如這樣的:
GET s4/doc/_search
{
"query": {
"match": {
"title": "rock"
}
},
"highlight": {
"pre_tags": "<b style='color:red'>",
"post_tags": "</b>",
"fields": {
"title": {}
}
}
}
phrase suggester
在term suggester
的基礎上,會考慮多個term
之間的關係,比如是否同時出現索引的原文中,臨近程度,詞頻等。
完成建議器(completion suggester
)
前言
我們來看一下自動完成的建議器——是一個導航功能,提供自動完成、搜索功能,可以在用戶輸入時引導用戶查看相關結果,從而提高搜索精度。
但並不適用於拼接檢查或者像term
和phrase
建議那樣的功能。
如果說在2000年左右,自動完成還是很炫酷的功能,那麼現在它是必備的了——任何沒有自動完成功能的搜索引擎都是很古老的。用戶期望一個良好的自動完成來幫助用戶實現更快的(特別是移動端)以及更好的(比如輸入e
,搜索引擎就應該知道用戶想要查找的是elasticsearch
)搜索。
一個優秀的自動完成將降低搜索引擎的負載,特別是在用戶有一些快速搜索可用時,也就是直接跳轉到主流的搜索結果而無須執行完整的搜索。
除此之外,一個優秀的自動完成必須是和快速的、相關的:
- 快速是因爲它必須在用戶不斷輸入的時候產生建議。
- 相關則是用戶並不希望建議一個沒有搜索結果或者沒有用處的結果。
那我們依靠之前學過的match_phrase_prefix
最左前綴查詢來完成該功能,但是這樣的查詢可能不夠快,因爲理想的情況下,搜索引擎需要在用戶輸入下一個字符前返回建議結果。
完成建議器和後面的上下文建議器可以幫助用戶構建更快的自動完成,它們是基於Lucene
的suggest
建議模塊而構建的,將數據保存在內存中的有限狀態轉移機中(FST)。FST實際上是一種圖。它可以將詞條以壓縮和易於檢索的方式來存儲。
上圖展示了詞條index
、search
、suggest
是如何存儲的。當然實際中的實現更加複雜,比如它允許我們添加權重。
FST(Finite StateTransducers),通常中文譯作有限狀態傳感器,FST目前在語音識別和自然語言搜索、處理等方向被廣泛應用。
FST的功能更類似於字典,Lucene4.0在查找Term時使用了FST算法,用來快速定位Term的位置。FST的數據結構可以理解成(key:value
)的形式。
在同義詞過濾器(SynonymFilter)的實現中甚至可以用HashMap
代替,不過相比較於HashMap
,它的優點是:- 以O(1)的時間複雜度找到key對應的value。
- 以字節的方式來存儲所有的Term,重複利用Term Index的前綴和後綴,使Term - Index小到可以放進內存,減少存儲空間,不過相對的也會佔用更多的cpu資源。
- FST還可以用來快速確定term是否在系統中。
測試數據:創建索引,創建文檔。
爲了告訴elasticsearch我們準備將建議存儲在自動完成的FST中,需要在映射中定義一個字段並將其type類型設置爲completion:
PUT s5
{
"mappings":{
"doc":{
"properties": {
"title": {
"type": "completion",
"analyzer": "standard"
}
}
}
}
}
PUT s5/doc/1
{
"title":"Lucene is cool"
}
PUT s5/doc/2
{
"title":"Elasticsearch builds on top of lucene"
}
PUT s5/doc/3
{
"title":"Elasticsearch rocks"
}
PUT s5/doc/4
{
"title":"Elastic is the company behind ELK stack"
}
PUT s5/doc/5
{
"title":"the elk stack rocks"
}
PUT s5/doc/6
{
"title":"elasticsearch is rock solid"
}
GET s5/doc/_search
{
"suggest": {
"my_s5": {
"text": "elas",
"completion": {
"field": "title"
}
}
}
}
建議結果不展示了!
上例的特殊映射中,支持以下參數:
- analyzer,要使用的索引分析器,默認爲simple。
- search_analyzer,要使用的搜索分析器,默認值爲analyzer。
- preserve_separators,保留分隔符,默認爲true。 如果您禁用,您可以找到以Foo Fighters開頭的字段,如果您建議使用foof。
- preserve_position_increments,啓用位置增量,默認爲true。如果禁用並使用停用詞分析器The Beatles,如果您建議,可以從一個字段開始b。注意:您還可以通過索引兩個輸入來實現此目的,Beatles並且 The Beatles,如果您能夠豐富數據,則無需更改簡單的分析器。
- max_input_length,限制單個輸入的長度,默認爲50UTF-16代碼點。此限制僅在索引時使用,以減少每個輸入字符串的字符總數,以防止大量輸入膨脹基礎數據結構。大多數用例不受默認值的影響,因爲前綴完成很少超過前綴長於少數幾個字符。
除此之外,該建議映射還可以定義在已存在索引字段的多字段:
PUT s6
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text",
"fields": {
"suggest": {
"type": "completion"
}
}
}
}
}
}
}
PUT s6/doc/1
{
"name":"KFC"
}
PUT s6/doc/2
{
"name":"kfc"
}
GET s6/doc/_search
{
"suggest": {
"my_s6": {
"text": "K",
"completion": {
"field": "name.suggest"
}
}
}
}
如上示例中,我們需要索引餐廳這樣的地點,而且每個地點的name
名稱字段添加suggest
子字段。
上例的查詢將肯德基(KFC)和開封菜(kfc)都返回。
在索引階段提升相關性
在進行普通的索引時,輸入的文本在索引和搜索階段都會被分析,這就是爲什麼上面的示例會將KFC
和kfc
都返回了。我們也可以通過analyzer
和search_analyzer
選項來進一步控制分析過程。如上例我們可以只匹配KFC
而不匹配kfc
:
PUT s7
{
"mappings": {
"doc": {
"properties": {
"name": {
"type": "text",
"fields": {
"suggest": {
"type": "completion",
"analyzer":"keyword",
"search_analyzer":"keyword"
}
}
}
}
}
}
}
PUT s7/doc/1
{
"name":"KFC"
}
PUT s7/doc/2
{
"name":"kfc"
}
GET s7/doc/_search
{
"suggest": {
"my_s7": {
"text": "K",
"completion": {
"field": "name.suggest"
}
}
}
}
建議結果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : 0.0,
"hits" : [ ]
},
"suggest" : {
"my_s7" : [
{
"text" : "K",
"offset" : 0,
"length" : 1,
"options" : [
{
"text" : "KFC",
"_index" : "s7",
"_type" : "doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "KFC"
}
}
]
}
]
}
}
上述的建議結果中,只有KFC
被返回。更多的細節控制可以搭配不同的分析器來完成。
多數的情況下,我們將在單獨的字段中、單獨的索引中甚至是單獨的集羣中保存建議。這對於主搜索引擎的性能提升和擴展建議器都是非常有利的。
除此之外,還可以使用input
和可選的weight
屬性,input
是建議查詢匹配的預期文本,weight
是建議評分方式(也就是權重)。例如:
PUT s8
{
"mappings": {
"doc":{
"properties":{
"title":{
"type": "completion"
}
}
}
}
}
添加數據的幾種形式:
PUT s8/doc/1
{
"title":{
"input":"blow",
"weight": 2
}
}
PUT s8/doc/2
{
"title":{
"input":"block",
"weight": 3
}
}
上例分別添加兩個建議並設置各自的權重值。
PUT s8/doc/3
{
"title": [
{
"input":"appel",
"weight": 2
},
{
"input":"apple",
"weight": 3
}
]
}
上例以列表的形式添加建議,設置不同的權重。
PUT s8/doc/4
{
"title": ["apple", "appel", "block", "blow"],
"weght": 32
}
上例是爲多個建議設置相同的權重。
查詢的結果由權重決定:
GET s8/doc/_search
{
"suggest": {
"my_s8": {
"text": "app",
"completion": {
"field": "title"
}
}
}
}
比如,我們在設置建議的時候,將apple
建議的權重weight
設置的更高,那麼在如上例的查詢中,apple
將會排在建議的首位。
在搜索階段提升相關性
當在運行建議的請求時,可以決定出現哪些建議,就像其他建議器一樣,size
參數控制返回多少項建議(默認爲5項);還可以通過fuzzy
參數設置模糊建議,以對拼寫進行容錯。當開啓模糊建議之後,可以設置下列參數來完成建議:
- fuzziness,可以指定所允許的最大編輯距離。
- min_length,指定什麼長度的輸入文本可以開啓模糊查詢。
- prefix_length,假設若干開始的字符是正確的(比如block,如果輸入blaw,該字段也認爲之前輸入的是對的),這樣可以通過犧牲靈活性提升性能。
這些參數都是在建議的completion
對象的下面:
GET s8/doc/_search
{
"suggest": {
"my_s9": {
"text": "blaw",
"completion": {
"field": "title",
"size": 2,
"fuzzy": {
"fuzziness": 2,
"min_length": 3,
"prefix_length": 2
}
}
}
}
}
結果如下:
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : 0.0,
"hits" : [ ]
},
"suggest" : {
"my_s9" : [
{
"text" : "blow",
"offset" : 0,
"length" : 4,
"options" : [
{
"text" : "block",
"_index" : "s8",
"_type" : "doc",
"_id" : "3",
"_score" : 6.0,
"_source" : {
"title" : {
"input" : "block",
"weight" : 3
}
}
},
{
"text" : "blow",
"_index" : "s8",
"_type" : "doc",
"_id" : "2",
"_score" : 4.0,
"_source" : {
"title" : {
"input" : "blow",
"weight" : 2
}
}
}
]
}
]
}
}
其他
_source
爲了減少不必要的響應,我們可以對建議結果做一些過濾,比如加上_source
:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"text": "appl",
"completion": {
"field": "title"
}
}
},
"_source": "title"
}
好吧,雖然我們只有一個字段!
size
除了_source
,我們還可以指定size
參數:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"prefix": "app",
"completion": {
"field": "title",
"size": 1
}
}
},
"_source": "title"
}
size
參數指定返回建議數(默認爲5),需要注意的是,size must be positive
,也就是說size
參數必須是積極的——非0非負數!
skip_duplicates
我們的建議可能是來自不同的文檔,這其中就會有一些重複建議項,我們可以通過設置skip_duplicates:true
來修改此行爲,如果爲true
則會過濾掉結果中的重複建議文檔:
GET s8/doc/_search
{
"suggest": {
"completion_suggest": {
"prefix": "app",
"completion": {
"field": "title",
"size": 5,
"skip_duplicates":true
}
}
},
"_source": "title"
}
但需要注意的是,該參數設置爲true
的話,可能會降低搜索速度,因爲需要訪問更多的建議結果項,才能過濾出來前N個。
最後,完成建議器還支持正則表達式查詢,這意味着我們可以將前綴表示爲正則表達式:
GET s5/doc/_search
{
"suggest": {
"completion_suggest": {
"regex": "e[l|e]a",
"completion": {
"field": "title"
}
}
}
}