elasticsearch之completion suggester


前言

我們來看一下自動完成的建議器——是一個導航功能,提供自動完成、搜索功能,可以在用戶輸入時引導用戶查看相關結果,從而提高搜索精度。但並不適用於拼接檢查或者像termphrase建議那樣的功能。
如果說在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是否在系統中。

完成建議器:completion suggester

為了告訴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)都返回。

在索引階段提升相關性

在進行普通的索引時,輸入的文本在索引和搜索階段都會被分析,這就是為什么上面的示例會將KFCkfc都返回了。我們也可以通過analyzersearch_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"
      }
    }
  }
}

see also:completion suggester | Weighted Finite-State Transducer Algorithms
An Overview
| FST(Finite-State Transducer) 原理
歡迎斧正,that's all


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM