同樣,卻又不一樣:藉助同義詞讓 Elasticsearch 更增強大

同樣,卻又不一樣:藉助同義詞讓 Elasticsearch 更增強大
做者[Christoph Büscher]node

毫無疑問,使用同義詞是搜索工程師工具箱中最重要的技巧之一。儘管新手有時會低估同義詞的重要性,但幾乎全部搜索系統都離不開它。與此同時,人們有時仍會低估與使用同義詞相關的一些複雜狀況和微妙情形,甚至高級用戶也不例外。同義詞篩選器是將輸入文本轉化爲可搜索字詞這一分析過程當中的一部分;雖然這一工具入門相對簡單,但其使用方法十分多樣,首先須要深刻了解一些概念,而後才能在實際情境中成功應用。算法

最近咱們在 Elasticsearch 中進行了一些分析方面的改進。最重大的一個功能可能就是容許從新加載搜索時使用的分析器,這樣用戶即可更改並從新加載搜索時使用的同義詞。除了向你們演示這一新的 API 以外,本篇博文還會回答一些有關同義詞使用方法的常見問題,並指出一些常常須要注意的相關事項。app

爲什麼使用同義詞?

爲幫助你們理解同義詞的巨大做用和靈活性,咱們來快速看一下當今大多數搜索引擎的內在工做原理。搜索引擎會對文檔和查詢進行分析並將其拆解爲最小的單元(一般稱爲詞元,實際上就是抽象的符號)。搜索時,匹配過程會使用簡單字串類似度,因此若是查詢中有一些十分微小的拼寫錯誤(例如「hous」,只比「house」少一個字母 e)或者使用名詞的複數形式(「houses」),即便文檔中包含名詞的單數形式(「house」),搜索引擎也不會匹配到這份文檔。詞幹提取器或模糊查詢等工具雖然能夠解決一些最多見的此類問題,可是它們並不能消除相關聯的概念或想法之間的差別,也不能將文檔或查詢中稍有不一樣的單詞用法視爲等同。框架

這時同義詞就派上了大用場。同義詞的英文 synonym 來自於希臘語,分別是前綴 _σύν(syn,表示「一塊兒」)_和 _ὄνομα(ónoma,表示「名稱」)_。從它的詞源能夠看出,同義詞表示的是在同一語言或領域中具備徹底或基本相贊成思的不一樣詞語。實際上,同義詞的範圍很是普遍,包括通常同義詞(「疲勞」和「睏倦」)、縮寫(英鎊的兩種寫法「lb.」和「pound」)、電商搜索中產品的不一樣拼寫(「iPod」和「i-Pod」)、細微的語言差別(例如均表示電梯的英式英語「lift」和美式英語「elevator」)、專業用詞和普通用詞(例如「犬」和「狗」),甚至單純表示同一律唸的兩種方式(「宇宙」和「太空」)。經過提供恰當的同義詞規則,搜索工程師可以就哪些詞在各自領域內具備類似意思並應該採起類似處理方法提供相關信息。工具

對搜索引擎而言,至爲重要的是知道文檔中的哪一個字詞與查詢內容相匹配,即便它們可能看起來並不同。因爲這涉及到十分具體的領域知識,因此用戶須要提供恰當的規則。同義詞篩選器可在定製分析器中使用,其可以基於用戶定義的規則替換或添加其餘詞元,既可在索引時進行以便在索引後的文檔中同時存儲這些內容(例如詞語的兩種變體),也可在索引時進行以擴展搜索詞並匹配到更多相關文檔。咱們稍後會討論這兩種方法的優缺點。性能

使用同義詞時須要注意的幾種情形

同義詞分析器是一款十分靈活的工具,但可能會致使人們在特定情形中過分使用。例如,有時人們會強行用其來替代詞幹提取器,這樣便會致使同義詞文件很大,由於其中包含動詞和名詞的各類語法變形(針對英語)。儘管這種方法可能奏效,但相較於使用真正的詞幹提取器或詞形還原工具,一般性能較差並且維護起來也較爲困難。用其來糾正拼寫錯誤的話,後果也是這樣。若是僅有幾個特別常見的拼寫錯誤,例如針對電商平臺,嘗試經過使用同義詞來糾正有時還算可取。但若是問題更加普遍,那麼使用模糊查詢或字符級別的 ngram方法可能更具備持續性。在分析鏈中還可考慮使用同義詞擴展法的替代方案。有時,相對於在限制性更強的分析過程當中使用同義詞,在採集管道或某些其餘客戶端過程當中改善文檔反倒更加靈活和易於管理。例如,您可使用命名實體識別 (NER) 框架對您文檔中的命名實體進行檢測,而後在您的預處理管道中或者在採集時以您特有的標識符對其進行編碼。若是您隨後對用戶的查詢應用一樣的過程,而後再將它們發送到 Elasticsearch,您能夠實現一樣的效果,但一般還能擁有更多控制權。測試

此外,您可能還會傾向於使用同義詞來處理其餘「相同」概念,例如將特定動物物種分組到一個經常使用字詞下,甚至針對您的領域構建分類學支持內容。這時事情就會變得特別有趣,也有不少問題須要探索,但要記住同義詞有時並不是最佳選擇,如使用不慎可能會致使您的系統出現異常行爲。搜索引擎

索引時使用同義詞和搜索時使用同義詞的對比

同義詞在分析器中使用,其既可在索引時使用,也可在搜索時使用。關於在 Elasticsearch 中如何使用同義詞篩選器,最多見的問題之一就是:「我應該索引時使用,仍是搜索時使用,仍是同時都用?」 咱們首先看一下在_索引時_應用同義詞篩選。這意味着會在索引後的文檔中對字詞進行一次性替換或擴展,結果將一直保存在搜索索引中。編碼

索引時使用同義詞有幾個劣勢:spa

  • 因爲必須對全部同義詞進行索引,因此索引規模會變大。
  • 搜索得分(依賴於字詞統計數據)可能會受影響,由於同義詞也會計算在內,因此不常見單詞的統計數據會存在誤差。
  • 除非進行從新索引,不然沒法針對既有文檔更改同義詞規則。

最後兩條尤爲是巨大劣勢。索引時應用同義詞的惟一潛在好處是性能好,由於您在前期已費心完成了擴展過程,因此無需再在每次查詢時完成一遍擴展過程,這有可能導致須要與更多的字詞進行匹配。然而這一點在實踐中一般並不是真正的問題。

相反,在搜索時所用的分析工具中使用同義詞則能夠避免不少上述問題:

  • 索引規模不受影響。
  • 語料庫中的字詞統計數據保持不變。
  • 如需變動同義詞規則,無需對文檔進行從新索引。

這些優點一般要高出惟一的劣勢,即每次查詢時都必須執行同義詞擴展操做,這有可能致使須要匹配更多字詞。不只如此,搜索時擴展同義詞還可以容許使用更加複雜的 synonym_graph詞元篩選器,這一工具可以正確處理多單詞同義詞,而且僅可在搜索分析器中使用。

通常而言,搜索時使用同義詞的優點一般要高於索引時使用同義詞可能實現的微小性能改進。

然而,若是在搜索時使用同義詞,過去還須要注意另一個問題。儘管更改同義詞規則不須要對文檔進行從新索引,可是如要更改的話,您必須暫時關閉再從新打開索引。這一點頗有必要,由於分析器在下列時候纔會建立實例:建立索引時,重啓節點時,以及從新打開已關閉的索引時。爲了讓對同義詞規則文件所作的變動對索引可見,用戶必須首先在全部節點上更新文件,而後再關閉並從新打開索引。可是這個問題已經得以解決。

同義詞,從新加載成功

從 Elasticsearch 7.3 開始,無需從新打開索引便能看到同義詞文件中的變動。咱們新增了一個端點,讓用戶可以按需觸發分析器資源的從新加載操做。調用這個新端點將會從新加載索引中的全部分析器,前提是這些索引中的組件已被標記爲可更新。相應地,這會使這些組件只能在搜索時使用。

對同義詞篩選器而言,將其標記爲可更新並調用「從新加載 API」會使每一個節點上的同義詞配置文件對分析過程可見。雖然仍不能(經過同義詞參數)更新篩選器定義中的同義詞規則,但這些同義詞規則應該主要用於偶爾的測試目的。不管何種狀況,用配置文件來配置同義詞有幾個優點:

  • 管理更簡單!在生產系統中,可能會有不少同義詞規則,並且因爲這些同義詞規則會顯著影響搜索相關度,因此應該將其視爲配置中不可或缺的一部分,且對於任何更新,都須要進行版本控制和測試。
  • 同義詞一般來自其餘來源,或由您的數據上所運行的算法建立。從文件讀取的話,便無需將這些同義詞加入到篩選器配置中。
  • 同一份同義詞文件可在不一樣篩選器中使用。
  • 較大的同義詞規則集會佔用 Elasticsearch 集羣狀態(用於存儲索引設置的相關元信息)中的大量內存。爲避免無謂地增長集羣規模,咱們建議將較大的同義詞規則集存儲在配置文件中。

爲演示起見,咱們假設您將包含下列單條規則的初始 my_synonyms.txt 文件添加到 Elasticsearch 節點的 config 目錄中。咱們假設此文件最初僅包含下列一條規則:

universe, cosmos

接下來,咱們須要定義一個分析器,並讓其在同義詞篩選器中引用此文件:

PUT /synonym_test
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "synonym_analyzer": {
            "tokenizer": "whitespace",
            "filter": ["my_synonyms"]
          }
        },
        "filter": {
          "my_synonyms": {
            "type": "synonym",
            "synonyms_path": "my_synonyms.txt",
            "updateable": true
          }
        }
      }
    }
  }
}

請注意咱們將同義詞篩選器標記爲了 updateable(可更新)。這一點很重要,由於當咱們調用新的從新加載端點時,只會從新加載可更新的篩選器;但這樣作也有缺點,由於在索引時不容許使用包含可更新篩選器的分析器。可是咱們首先來檢查一下同義詞是否已正確應用,能夠經過 _analyze 端點運行一個簡短的測試:

GET /synonym_test/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "cosmos"
}

此操做應該會返回兩個詞元,正如咱們所料,其中一個是「universe」。咱們接下來經過添加第二行向 synonyms.txt 文件添加另外一條規則:

lift, elevator

若是使用以前版本,您此時必須關閉並從新打開索引,以便顯示這些變動。如今您能夠簡單地調用新端點:

POST /synonym_test/_reload_search_analyzers

雖然此請求並不要求有正文,但可能會限制爲一個或多個使用典型索引通配符模式的索引。響應中包括的信息有已從新加載了哪些分析器,以及哪些節點受到了影響:

{
  [...],
  "reload_details": [{
    "index": "synonym_test",
    "reloaded_analyzers": ["synonym_analyzer"],
    "reloaded_node_ids": ["FXbmbgG_SsOrNRssrYcPow"]
  }]
}

如今對字詞「lift」運行上面的 _analyze 請求,也會返回「elevator」(做爲第二個同義詞詞元)。

然而,還有一些注意事項。如上面提到的,搜索時應該使用已標記爲 updateable(可更新)的篩選器,因此在字段層面使用上面所定義的同義詞分析器的正確方式以下:

POST /synonym_test/_mapping
{
  "properties": {
    "text_field": {
      "type": "text",
      "analyzer": "standard",
      "search_analyzer": "synonym_analyzer"
    }
  }
}

一樣,從新加載僅適用於從文件中加載的同義詞,亦即不支持更改經過篩選器中的設置所定義的同義詞。最後,您在實踐中須要確保對集羣中的全部節點均應用對同義詞文件進行的更新。若是某些節點上的分析器看到了文件的不一樣版本,則您可能會收到不一樣的搜索結果,具體取決於搜索中使用的是哪一個節點。若是發生與同義詞相關的此種狀況,首先須要檢查每一個節點上的同義詞文件是否同樣,而後從新觸發「從新加載」操做。

總結一下,新的 _reload_search_analyzer 端點可以讓您快速修訂和更改查詢時應用的同義詞,而無需從新打開索引。例如,經過檢查查詢日誌,您能夠肯定用戶查詢時所用的字詞與已索引文檔中的既有字詞是否不一樣,而後隨時進行添加。可是添加同義詞會對相關度得分產生意料以外的不良影響,因此咱們建議首先進行某些形式的測試(A/B 測試,或者排名評估API 等均可以),而後再直接在生產環境中應用這些變動。

做爲分析鏈中的一部分

另一個有關同義詞篩選器的常見問題是其在更復雜分析鏈中的行爲。在大多數狀況下,您會在同義詞篩選器以前加入一些常見的字符或詞元篩選器,例如 lowercase(小寫)篩選器。這意味着流經分析鏈的全部詞元都會變爲小寫,而後纔會應用同義詞篩選器。這是否意味着同義詞規則中的輸入同義詞也須要變爲小寫才能匹配呢?咱們經過這個簡單的示例看一下:

PUT /test_index
{
  "settings": {
    "index": {
      "analysis": {
        "analyzer": {
          "synonym_analyzer": {
            "tokenizer": "whitespace",
            "filter": ["lowercase", "my_synonyms"]
          }
        },
        "filter": {
          "my_synonyms": {
            "type": "synonym",
            "synonyms": ["Eins, Uno, One", "Cosmos => Universe"]
          }
        }
      }
    }
  }
}
GET /test_index/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "one"
}

在上面的示例中,您能夠驗證小寫的輸入文本擴展成了三個詞元,這表示小寫操做也會應用到同義詞篩選器的規則中。一樣,右邊的替換規則(例如「Cosmos => Universe」規則)也會從新編寫,正如您看到的下邊示例的小寫輸出:

GET /test_index/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "cosmos"
}

通常而言,對於提供給前面分析鏈中所用分詞器和篩選器的輸入,同義詞篩選器會對其進行從新編寫。而後,這一規則有一些值得注意的例外狀況:多個會輸出堆疊式詞元的篩選器(例如 common_gramsphonetic篩選器)均不容許在同義詞篩選器以前使用,若是您嘗試這樣作,系統會報錯。對於其餘篩選器,例如複合詞篩選器或同義詞篩選器自身,若是它們在分析鏈中位於另外一個同義詞篩選器以前,則會跳過他們。後面一條規則對於實現同義詞篩選器鏈路十分重要。咱們能夠在下面的示例中看一下實現過程。

若是您連着使用兩個或多個同義詞篩選器,會怎樣呢?前項的輸出會成爲後項的輸入嗎,也就是將同義詞篩選器的連接操做轉變爲部分意義上的傳遞操做?咱們用下面的示例來試一下:

PUT /synonym_chaining
{
  "settings": {
    "index": {
      "analysis": {
        "filter": {
          "first_synonyms": {
            "type": "synonym",
            "synonyms": ["a => b", "e => f"]
          },
          "second_synonyms": {
            "type": "synonym",
            "synonyms": ["b => c", "d => e"]
          }
        },
        "analyzer": {
          "synonym_analyzer": {
            "filter": [
              "first_synonyms",
              "second_synonyms"
            ],
            "tokenizer": "whitespace"
          }
        }
      }
    }
  }
}
GET /synonym_chaining/_analyze
{
  "analyzer": "synonym_analyzer",
  "text": "a"
}

輸出詞元爲「c」,這表示兩個篩選器已按順序得以應用,即第一個篩選器將「a」替換爲「b」,第二個篩選器繼而將這一輸入替換爲「c」。若是您嘗試將輸入改成「d」,會將其替換爲「e」(並未應用第一條規則);但若是您將輸入改成「e」,會根據第一條規則將這個詞元替換爲「f」,致使第二個篩選器根本沒有能夠匹配的內容。

還記得嗎?咱們剛纔講到了基於前面的詞元篩選器進行從新編寫時有一些例外狀況。若是上面的 second_synonyms(第二組同義詞)篩選器對其規則集應用了第一個篩選器的規則,那麼它會將自身的規則 d => e 更改成 d => f(由於會應用前面篩選器的規則 e => f)。在 Elasticsearch 的早期版本中,這一行爲過去經常給人們形成困擾,所以如今處理後面篩選器中的同義詞規則時,會跳過同義詞篩選器。在 6.6 及以後的版本中,它將會按照咱們所描述的那樣運行。

展望將來

在這篇簡短的博文中,咱們只是就同義詞的用途向你們介紹了冰山一角,並嘗試解決了與使用同義詞相關的一些常見問題。同義詞是一項強大工具,可以用來提高您搜索系統的從新調用率,可是還有一些很重要的細節也須要您知道並進行試驗,尤爲是與系統性的相關度測試一塊兒進行試驗。

咱們在 Elasticsearch 7.3 中增長了新 API 以便可以從新加載搜索時應用的分析器,這一 API 可以讓您更加輕鬆地完成此類試驗,由於您無需再像以前那樣關閉再從新打開索引;此外,其還可以讓您更新在搜索時應用的同義詞規則,而無需讓您的索引離線。咱們但願經過一系列改進讓用戶更便利地管理大型集羣中的同義詞,此 API 只是這一過程當中邁出的一小步而已。歡迎告訴咱們您的想法,在Segmentfault發佈的本篇文章中,經過【評論】給咱們提供反饋或提問。祝您分析愉快!

相關文章
相關標籤/搜索