Elasticsearch:使用同義詞 synonyms 來提高搜尋效率

哈哈哈hh發表於2021-11-03

阿里雲官方映象站:

https://developer.aliyun.com/mirror/?&utm_content=g_1000304576


在我們的很多情況下,我們希望在搜尋時,有時能夠使用一個詞的同義詞來進行搜尋,這樣我們能搜尋出來更多相關的內容。我們可以透過 text analysis 來幫助我們形成同義詞。文字分析通常應用於你建立索引時的所有文件以及傳送給 Elasticsearch 的所有查詢。在進行同義詞搜尋時,我們有如下的幾種方案:


  • 在建立索引時 (indexing),透過 analyzer 建立 synonyms 的反向索引 (inverted index)

  • 在 query 時,透過 search analyzer 對查詢的詞建立 synonyms

  • 在 indexing 及 query 時,同時建立反向索引中的 synonym 及在 query 時為查詢的詞建立 synonyms

那麼在實際的使用中,我們到底是用上述的哪種方案呢?在下面的例子中,你將看到在 query 時使用 synonym 會更加靈活,並且更容易讓我們更新同義詞的名單已經更好地支援 multi-word synonyms。

在今天的文章中,我們將分別論述。


在 query 時對詞進行同義詞解析

首先,我們來建立一個具有如下 anaylzer 及 mapping 的一個索引:


PUT myindex
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonyms": {
          "type": "synonym_graph",
          "synonyms": [
            "China, chn, PRC, People's Republic of China"
          ]
        }
      },
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter":[
            "lowercase",
            "my_synonyms"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "standard", 
        "search_analyzer": "my_analyzer"
      }
    }
  }
}


在上面,我們使用 synonym_graph 過濾器對 quey 時的詞進行過濾。在這個過濾器中,我們把如下的一個詞都視為同義詞:


China, chn, PRC, People’s Republic of China


在 mapping 中,我們定義了 search_analyzer 為 my_analyzer,也就是說在 query 時,它會對所有的詞進行分詞。但凡有任何一個詞是 China, chn, PRC, People’s Republic of China 其中的一個,它都將被視為同義詞。


我們首先來建立一個文件:


PUT myindex/_doc/1
{
  "content": "I like People's Republic of China"
}


執行上面的指令,我們將建立一個 content 為 I like People’s Republic of China 的文件。


接下來,我們做如下的查詢:


GET myindex/_search
{
  "query": {
    "match": {
      "content": "China"
    }
  }
}


那麼顯示的結果是:


{
  "took" : 256,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.4384104,
    "hits" : [
      {
        "_index" : "myindex",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.4384104,
        "_source" : {
          "content" : "I like People's Republic of China"
        }
      }
    ]
  }
}


可能有人說了,這是因為上面的 content 裡本身就含有 China, 所以上面的結果證明不了什麼。接下來,我們進行如下的搜尋:


GET myindex/_search
{
  "query": {
    "match": {
      "content": "prc"
    }
  }
}


結果,我們可以發現,我們同樣顯示上面的搜尋的結果。這個說明了這個同義詞的搜尋是成功的。


接下來,我們想搜尋 silk road 也能搜尋出中國來,那麼我怎麼做呢?


我們來執行如下的命令:


POST myindex/_close
PUT myindex/_settings
{
  "analysis": {
    "filter": {
      "my_synonyms": {
        "type": "synonym_graph",
        "synonyms": [
          "china, silk road, chn, PRC, People's Republic of China"
        ]
      }
    },
    "analyzer": {
      "my_analyzer": {
        "type": "custom",
        "tokenizer": "standard",
        "filter": [
          "lowercase",
          "my_synonyms"
        ]
      }
    }
  }
}
POST myindex/_open


我們可以透過更新 setting 來實現這個。在上面請注意:當我們更新一個索引的 index 時,我們必須先把它關掉,等設定好後,在重新開啟。否則會有錯誤。那麼經過上面的修改後,我們重新執行如下的搜尋:


GET myindex/_search
{
  "query": {
    "match": {
      "content": "silk road"
    }
  }
}


那麼上面的搜尋結果將會顯示我們之前顯示的結果。在這裡 silk road 也就是和之前的其它詞都是同義詞。


有人可能覺得上面在 settings 裡配置太多的同義詞很麻煩(如果同義詞很多的話)。按照 Elastic 的官方文件,我們可以把所有的同義詞放到一個文件中。首先,我們在 Elasticsearch 的 config 目錄中,建立一個叫做 analysis 的子目錄,然後建立一個叫做 synonyms.txt 的文件,而它的內容如下:


$ pwd
/Users/liuxg/elastic/elasticsearch-7.8.0/config/analysis
liuxg:analysis liuxg$ cat synonyms.txt 
"china, silk road, chn, PRC, People's Republic of China",
"elk, elastic stack"


在這裡,我們多新增了一個 elk, elastic stack 的同義詞。我們來建立一個新的索引:


PUT myindex1
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonyms": {
          "type": "synonym_graph",
          "synonyms_path": "analysis/synonyms.txt"
        }
      },
      "analyzer": {
        "my_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter":[
            "lowercase",
            "my_synonyms"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "standard", 
        "search_analyzer": "my_analyzer"
      }
    }
  }
}


執行完上的指令後,我們來建立一個文件:


PUT myindex1/_doc/1
{
  "content": "I love elastic stack"
}


然後我們做如下的搜尋:


GET myindex1/_search
{
  "query": {
    "match": {
      "content": "elk"
    }
  }
}


上面的搜尋結果顯示:


{
  "took" : 451,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.5753642,
    "hits" : [
      {
        "_index" : "myindex1",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.5753642,
        "_source" : {
          "content" : "I love elastic stack"
        }
      }
    ]
  }
}


顯然,我可以看到搜尋 elk,我們就可以搜尋到含有 elastic stack 的文件。


在實際的使用中,如果我們更新 synonyms.txt 檔案,那麼,我們可以使用如下的 API 來進行更新:


POST myindex1/_reload_search_analyzers


在建立索引時建立同義詞

針對這種情況,我們可以在建立索引的時候,就把同義詞建立好。這樣,我們可以在 query 時,不使用同義詞解析。在這種情況下,我們可以使用 synonym 過濾器,而不是 synonym_graph 過濾器。


我們接下來使用如下的命令來建立一個新的索引:


PUT myindex2
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonyms": {
          "type": "synonym",
          "synonyms": [
            "china, silk road, chn, PRC, People's Republic of China",
            "elk, elastic stack"
          ]
        }
      },
      "analyzer": {
        "my_analyzer": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_synonyms"
          ]
        }
      }
    },
    "number_of_shards": 1
  },
  "mappings": {
    "properties": {
      "content": {
        "type": "text",
        "analyzer": "my_analyzer"
      }
    }
  }
}


在上面,我們使用了 my_analyzer 作為 myindex2 在索引時使用的分詞器。它將使用 synonym 過濾器,並把如下的詞視為同義詞:


"china, silk road, chn, PRC, People's Republic of China",
"elk, elastic stack"


我們可以使用如下的方法來測試這個 analyzer:


POST myindex2/_analyze
{
  "text": "I like elk a lot",
  "analyzer": "my_analyzer"
}


上面的命令顯示的結果是:


{
  "tokens" : [
    {
      "token" : "i",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "like",
      "start_offset" : 2,
      "end_offset" : 6,
      "type" : "<ALPHANUM>",
      "position" : 1
    },
    {
      "token" : "elk",
      "start_offset" : 7,
      "end_offset" : 10,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "elastic",
      "start_offset" : 7,
      "end_offset" : 10,
      "type" : "SYNONYM",
      "position" : 2
    },
    {
      "token" : "a",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "stack",
      "start_offset" : 11,
      "end_offset" : 12,
      "type" : "SYNONYM",
      "position" : 3
    },
    {
      "token" : "lot",
      "start_offset" : 13,
      "end_offset" : 16,
      "type" : "<ALPHANUM>",
      "position" : 4
    }
  ]
}


你可以看到,儘管在測試的 text 沒有 elastic stack,只有 elk,但是顯示的結果了含有 elastic 及 stack 這兩個 token。


我們接下來使用如下的命令來建立一個文件:


PUT myindex2/_doc/1
{
  "content": "I like elk a lot"
}


我們使用如下的查詢:


GET myindex2/_validate/query?rewrite=true
{
  "query": {
    "match": {
      "content": "elastic stack"
    }
  }
}


上面顯示的結果是:


{
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "failed" : 0
  },
  "valid" : true,
  "explanations" : [
    {
      "index" : "myindex2",
      "valid" : true,
      "explanation" : """content:"elastic stack" content:elk"""
    }
  ]
}


從上面的顯示的結果來看,當我們搜尋 elastic stack 時,它同時匹配 content: “elastic stack” 以及 content: elk。也就是說,如果文件裡含有 elk,那麼這個文件也將被搜尋到。我們做如下的搜尋:


GET myindex2/_search
{
  "query": {
    "match": {
      "content": "elastic stack"
    }
  }
}


那麼上面的命令顯示的結果是:


{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 0.977273,
    "hits" : [
      {
        "_index" : "myindex2",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 0.977273,
        "_source" : {
          "content" : "I like elk a lot"
        }
      }
    ]
  }
}

顯然它已經把我們的想要的結果搜尋出來了。


總結

在上面,我們展示了兩種方法進行同義詞的查詢。在實際的使用中,你可以根據自己的情況適當進行選擇。當然,我們有可以把上面的兩種方法進行同時並用。透過這兩種方法,也有可能會造成搜尋的精確度的問題。這個是你必須要想清楚的。這個就像我們撒網打魚一樣,把網撒大了,撈上來的也有可能不是我們想要的。


原文連結:https://elasticstack.blog.csdn.net/article/details/108003222




來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70003733/viewspace-2840424/,如需轉載,請註明出處,否則將追究法律責任。

相關文章