天天看點

[Elasticsearch] 多字段搜尋 (三) - multi_match查詢和多數字段

multi_match查詢

multi_match

查詢提供了一個簡便的方法用來對多個字段執行相同的查詢。

NOTE

存在幾種類型的

multi_match

查詢,其中的3種正好和在"了解你的資料"一節中提到的幾種類型相同:

best_fields

most_fields

以及

cross_fields

預設情況下,該查詢以

best_fields

類型執行,它會為每個字段生成一個

match

查詢,然後将這些查詢包含在一個

dis_max

查詢中。下面的

dis_max

查詢:

{
  "dis_max": {
    "queries":  [
      {
        "match": {
          "title": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
      {
        "match": {
          "body": {
            "query": "Quick brown fox",
            "minimum_should_match": "30%"
          }
        }
      },
    ],
    "tie_breaker": 0.3
  }
}      

可以通過

multi_match

簡單地重寫如下:

{
    "multi_match": {
        "query":                "Quick brown fox",
        "type":                 "best_fields", 
        "fields":               [ "title", "body" ],
        "tie_breaker":          0.3,
        "minimum_should_match": "30%" 
    }
}      

注意到以上的

type

屬性為

best_fields

。 

minimum_should_match

operator

參數會被傳入到生成的

match

查詢中。

在字段名中使用通配符

字段名可以通過通配符指定:任何比對了通配符的字段都會被包含在搜尋中。你可以通過下面的查詢來比對

book_title

chapter_title

以及

section_title

字段:

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": "*_title"
    }
}      

提升個别字段

個别字段可以通過caret文法(

^

)進行提升:僅需要在字段名後添加

^boost

,其中的

boost

是一個浮點數:

{
    "multi_match": {
        "query":  "Quick brown fox",
        "fields": [ "*_title", "chapter_title^2" ] 
    }
}      

chapter_title

字段的

boost

值為

2

,而

book_title

section_title

字段的

boost

值為預設的

1

多數字段(Most Fields)

全文搜尋是一場召回率(Recall) - 傳回所有相關的文檔,以及準确率(Precision) - 不傳回無關文檔,之間的戰鬥。目标是在結果的第一頁給使用者呈現最相關的文檔。

為了提高召回率,我們會廣撒網 - 不僅包括精确比對了使用者搜尋詞條的文檔,還包括了那些我們認為和查詢相關的文檔。如果一個使用者搜尋了"quick brown fox",一份含有fast foxes的文檔也可以作為一個合理的傳回結果。

如果我們擁有的相關文檔僅僅是含有fast foxes的文檔,那麼它會出現在結果清單的頂部。但是如果我們有100份含有quick brown fox的文檔,那麼含有fast foxes的文檔的相關性就會變低,我們希望它出現在結果清單的後面。在包含了許多可能的比對後,我們需要確定相關度高的文檔出現在頂部。

一個用來調優全文搜尋相關性的常用技術是将同樣的文本以多種方式索引,每一種索引方式都提供了不同相關度的信号(Signal)。主要字段(Main field)中含有的詞條的形式是最寬泛的(Broadest-matching),用來盡可能多的比對文檔。比如,我們可以這樣做:

  • 使用一個詞幹提取器來将jumps,jumping和jumped索引成它們的詞根:jump。然後當使用者搜尋的是jumped時,我們仍然能夠比對含有jumping的文檔。
  • 包含同義詞,比如jump,leap和hop。
  • 移除變音符号或者聲調符号:比如,ésta,está和esta都會以esta被索引。

但是,如果我們有兩份文檔,其中之一含有jumped,而另一份含有jumping,那麼使用者會希望第一份文檔的排序會靠前,因為它含有使用者輸入的精确值。

我們可以通過将相同的文本索引到其它字段來提供更加精确的比對。一個字段可以包含未被提取詞幹的版本,另一個則是含有變音符号的原始單詞,然後第三個使用了shingles,用來提供和單詞鄰近度相關的資訊。這些其它字段扮演的角色就是信号(Signals),它們用來增加每個比對文檔的相關度分值。能夠比對的字段越多,相關度就越高。

如果一份文檔能夠比對具有最寬泛形式的主要字段(Main field),那麼它就會被包含到結果清單中。如果它同時也比對了信号字段,它會得到一些額外的分值用來将它移動到結果清單的前面。

我們會在本書的後面讨論同義詞,單詞鄰近度,部分比對以及其他可能的信号,但是我們會使用提取了詞幹和未提取詞幹的字段的簡單例子來解釋這個技術。

多字段映射(Multifield Mapping)

第一件事就是将我們的字段索引兩次:一次是提取了詞幹的形式,一次是未提取詞幹的形式。為了實作它,我們會使用多字段(Multifields),在字元串排序和多字段中我們介紹過:

DELETE /my_index

PUT /my_index
{
    "settings": { "number_of_shards": 1 }, 
    "mappings": {
        "my_type": {
            "properties": {
                "title": { 
                    "type":     "string",
                    "analyzer": "english",
                    "fields": {
                        "std":   { 
                            "type":     "string",
                            "analyzer": "standard"
                        }
                    }
                }
            }
        }
    }
}      

title字段使用了english解析器進行詞幹提取。 title.std字段則使用的是standard解析器,是以它沒有進行詞幹提取。

下一步,我們會索引一些文檔:

PUT /my_index/my_type/1
{ "title": "My rabbit jumps" }

PUT /my_index/my_type/2
{ "title": "Jumping jack rabbits" }      

以下是一個簡單的針對title字段的match查詢,它查詢jumping rabbits:

GET /my_index/_search
{
   "query": {
        "match": {
            "title": "jumping rabbits"
        }
    }
}      

它會變成一個針對兩個提幹後的詞條jump和rabbit的查詢,這要得益于english解析器。兩份文檔的title字段都包含了以上兩個詞條,是以兩份文檔的分值是相同的:

{
  "hits": [
     {
        "_id": "1",
        "_score": 0.42039964,
        "_source": {
           "title": "My rabbit jumps"
        }
     },
     {
        "_id": "2",
        "_score": 0.42039964,
        "_source": {
           "title": "Jumping jack rabbits"
        }
     }
  ]
}      

如果我們隻查詢title.std字段,那麼隻有文檔2會比對。但是,當我們查詢兩個字段并将它們的分值通過bool查詢進行合并的話,兩份文檔都能夠比對(title字段也比對了),而文檔2的分值會更高一些(比對了title.std字段):

GET /my_index/_search
{
   "query": {
        "multi_match": {
            "query":  "jumping rabbits",
            "type":   "most_fields", 
            "fields": [ "title", "title.std" ]
        }
    }
}      

在上述查詢中,由于我們想合并所有比對字段的分值,是以使用的類型為most_fields。這會讓multi_match查詢将針對兩個字段的查詢子句包含在一個bool查詢中,而不是包含在一個dis_max查詢中。

{
  "hits": [
     {
        "_id": "2",
        "_score": 0.8226396, 
        "_source": {
           "title": "Jumping jack rabbits"
        }
     },
     {
        "_id": "1",
        "_score": 0.10741998, 
        "_source": {
           "title": "My rabbit jumps"
        }
     }
  ]
}      

此時,文檔2的分值比文檔1的高許多。

我們使用了擁有寬泛形式的title字段來比對盡可能多的文檔 - 來增加召回率(Recall),同時也使用了title.std字段作為信号來讓最相關的文檔能夠擁有更靠前的排序(譯注:增加了準确率(Precision))。

每個字段對最終分值的貢獻可以通過指定boost值進行控制。比如,我們可以提升title字段來讓該字段更加重要,這也減小了其它信号字段的影響:

GET /my_index/_search
{
   "query": {
        "multi_match": {
            "query":       "jumping rabbits",
            "type":        "most_fields",
            "fields":      [ "title^10", "title.std" ] 
        }
    }
}      

繼續閱讀