elasticsearch筆記_多字段搜索(六)

多字符串對應多字段

多字符串是最簡單的一種,例如,搜索標題是 War and Peace ,作者是Leo Tolstoy ,直接用bool連接兩個match查詢即可.(這裏面War and Peace和Leo Tolstoy就是多字符串,title和author是多字段)

GET /_search
{
  "query": {
    "bool": {
      "should": [
        { "match": { "title":  "War and Peace" }},
        { "match": { "author": "Leo Tolstoy"   }}
      ]
    }
  }
}

單符串(多關鍵詞)對應多字段

當輸入單個字符串搜索時候,通常有下面三種需求:

1.最佳字段(best_fields) : 字符串整體的匹配度最高,而不是將字符創拆分成分詞以後分詞的匹配量最高 .(例如: “brown fox” ,詞組比各自獨立的單詞更有意義。文檔在 相同字段 中包含的詞越多越好,評分也來自於 最匹配字段 。)

2.多數字段(most_fields) : 字符串在被拆分成分詞以後,與每一個字段的匹配數量的和最大時被認爲是匹配度最高.(這種情況多用於相關度微調技術 : 將相同的數據索引到不同的字段,它們各自具有獨立的分析鏈.比如,一個字段被索引(動詞)的是原詞,另一個字段被索引(動詞)的是這個詞的變形詞,口音,去掉過去式或複數形式等)

3.混合字段(cross_fields)
對多個字段的組合進行匹配.例如:
(1) Person: first_name 和 last_name (人:名和姓)
(2) Book: title 、 author 和 description (書:標題、作者、描述)
(3) Address: street 、 city 、 country 和 postcode (地址:街道、市、國家和郵政編碼)

最佳字段(best_fields)

1 . 第一種搜索情況 : 輸入”Brown fox”對title和body進行搜索.(注意:doc1裏面title和body都只含有brown ; doc2裏面的title不含任何關鍵字 , body裏面含有Brown fox整體.)

//以下面兩個文檔爲例:

{
    "title": "Quick brown rabbits",
    "body":  "Brown rabbits are commonly seen."
}

{
    "title": "Keeping pets healthy",
    "body":  "My quick brown fox eats rabbits on a regular basis."
}

//輸入"Brown fox"對title和body進行搜索.

{
    "query": {
        "bool": {
            "should": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

分析 : 根據肉眼觀察,”Brown fox”明顯和文檔2更加匹配 , 但是搜索結果卻是文檔1的評分更高 . 這與bool的評分方式有關 . bool的評分方式是:(1)將每一個should的查詢結果評分進行相加. (2)乘以匹配語句的總數 . (3)除以全部語句的總數.

很明顯,上面的評分結果並不是我們想要的 . 最大化查詢就是爲了解決上面的搜索情況的.

分離最大化查詢(Disjunction Max Query)

分離最大化查詢(dis_max):將任何與任一查詢匹配的文檔作爲結果返回,但只將最佳匹配的評分作爲查詢的評分結果返回 .

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Brown fox" }},
                { "match": { "body":  "Brown fox" }}
            ]
        }
    }
}

2.第二種搜索情況 : 輸入”Quick pets”對title和body進行搜索 . (注意 : 兩個文檔都包含詞 quick ,但是隻有文檔 2 包含詞 pets)

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ]
        }
    }
}

此時會發現兩個文檔的用dis_max查詢的評分是相同的 . 也是明顯不符合我們想要的結果,這種情況就要用到tie_breaker參數了.

tie_breaker參數

{
    "query": {
        "dis_max": {
            "queries": [
                { "match": { "title": "Quick pets" }},
                { "match": { "body":  "Quick pets" }}
            ],
            "tie_breaker": 0.3
        }
    }
}

分析過程 :
1 . 獲得最佳匹配語句的評分 _score 。
2 . 將其他匹配語句的評分結果與 tie_breaker 相乘。
3 . 對以上評分求和並規範化。

注意 :tie_breaker 可以是 0 到 1 之間的浮點數,其中 0 代表使用 dis_max 最佳匹配語句的普通邏輯, 1 表示所有匹配語句同等重要。最佳的精確值需要根據數據與查詢調試得出,但是合理值應該與零接近(處於 0.1 - 0.4 之間),這樣就不會顛覆 dis_max 最佳匹配性質的根本。

multi_match查詢

multi_match是爲了簡化多個match的情況 . 例如 :默認情況下,查詢的類型是 best_fields(最佳字段) , 而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",     //best_fields 類型是默認值,可以不指定。
        "fields":               [ "title", "body" ],
        "tie_breaker":          0.3,
        "minimum_should_match": "30%" 
    }
}

multi_match還支持字段模糊查詢 .

//匹配 book_title 、 chapter_title 和 section_title (書名、章名、節名)這三個字段:

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

提升單個字段的權重:

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

多數字段(most_fields)

提高全文相關性精度的常用方式是爲同一文本建立多種方式的索引, 每種方式都提供了一個不同的相關度信號 signal 。主字段會包括最廣匹配(broadest-matching)形式的詞去儘可能的匹配更多的文檔。舉個例子,我們可以進行以下操作:

1.使用詞幹提取來索引 jumps 、 jumping 和 jumped 樣的詞,將 jump 作爲它們的詞根形式。這樣即使用戶搜索 jumped ,也還是能找到包含 jumping 的匹配的文檔。
將同義詞包括其中,如 jump 、 leap 和 hop 。

2.移除變音或口音詞:如 ésta 、 está 和 esta 都會以無變音形式 esta 來索引。
儘管如此,如果我們有兩個文檔,其中一個包含詞 jumped ,另一個包含詞 jumping ,用戶很可能期望前者能排的更高,因爲它正好與輸入的搜索條件一致。

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"
                        }
                    }
                }
            }
        }
    }
}

//添加兩個新的文檔.

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

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

查詢title字段,結果是兩個文檔的評分相同,因爲title用的是english分析器.如果只是查詢 title.std 字段,那麼只有文檔 2 是匹配的。

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

如果同時查詢兩個字段,然後使用 bool 查詢將評分結果 合併 ,那麼兩個文檔都是匹配的( title 字段的作用),而且文檔 2 的相關度評分更高( title.std 字段的作用):


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

每個字段對於最終評分的貢獻可以通過自定義值 boost 來控制。比如,使 title 字段更爲重要,這樣同時也降低了其他信號字段的作用:

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

混合字段(cross_fields)

現在想要查詢 “Poland Street W1V” 這個地址 , 地址的索引(名詞)是這樣建立的:

{
    "street":   "5 Poland Street",
    "city":     "London",
    "country":  "United Kingdom",
    "postcode": "W1V 3DG"
}

最簡單的方式是這樣的:

{
  "query": {
    "bool": {
      "should": [
        { "match": { "street":    "Poland Street W1V" }},
        { "match": { "city":      "Poland Street W1V" }},
        { "match": { "country":   "Poland Street W1V" }},
        { "match": { "postcode":  "Poland Street W1V" }}
      ]
    }
  }
}

另一種形式:
{
  "query": {
    "multi_match": {
      "query":       "Poland Street W1V",
      "type":        "most_fields",    //注意,不是cross_fields
      "fields":      [ "street", "city", "country", "postcode" ]
    }
  }
}

注意:most_fields 這種方式搜索也存在某些問題.
問題1. 它是爲多數字段匹配 任意 詞設計的,而不是在 所有字段 中找到最匹配的。
問題2. 它不能使用 operator或and 操作符 或 minimum_should_match 參數來降低次相關結果造成的長尾效應。
問題3. 詞頻對於每個字段是不一樣的,而且它們之間的相互影響會導致不好的排序結果。

通過 validate-query API 查看查詢的解釋 :

GET /_validate/query?explain
{
  "query": {
    "multi_match": {
      "query":   "Poland Street W1V",
      "type":    "most_fields",
      "fields":  [ "street", "city", "country", "postcode" ]
    }
  }
}

生成 explanation 解釋:

(street:poland   street:street   street:w1v)
(city:poland     city:street     city:w1v)
(country:poland  country:street  country:w1v)
(postcode:poland postcode:street postcode:w1v)

注意 : 可以發現, 兩個 字段都與 poland 匹配的文檔要比一個字段同時匹配 poland 與 street 文檔的評分高。(這就是問題1)

{
    "query": {
        "multi_match": {
            "query":       "Poland Street W1V",
            "type":        "most_fields",
            "operator":    "and", 
            "fields":      [ "street", "city", "country", "postcode" ]
        }
    }
}

注意 : 使用 and 操作符要求所有詞都必須存在於 相同字段 ,這顯然是不對的!可能就不存在能與這個查詢匹配的文檔。(這就是問題2)

{
    "query": {
        "multi_match": {
            "query":       "Peter Smith",
            "type":        "most_fields",
            "fields":      [ "*_name" ]
        }
    }
}

注意 : 用字段 first_name 和 last_name 查詢 “Peter Smith” 時, Peter 是個平常的名 Smith 也是平常的姓,這兩者都具有較低的 IDF 值。但當索引中有另外一個人的名字是 “Smith Williams” 時,姓 Smith 就會非常的不平常,以致它有一個較高的 IDF 值!

這個查詢可能會在結果中將 “Smith Williams” 置於 “Peter Smith” 之上,儘管事實上是第二個人比第一個人更爲匹配。(這就是問題3)

解決方案

1.創建一個full_name , 將搜索轉化爲搜索full_name , 缺點就是存儲冗餘數據.

{
    "first_name":  "Peter",
    "last_name":   "Smith",
    "full_name":   "Peter Smith"
}

2.自定義_all字段,利用copy_to來實現,copy_to 設置對multi-field無效。如果嘗試這樣配置映射,Elasticsearch 會拋異常。爲什麼呢?多字段只是以不同方式簡單索引“主”字段;它們沒有自己的數據源。也就是說沒有可供 copy_to 到另一字段的數據源。

PUT /my_index
{
    "mappings": {
        "person": {
            "properties": {
                "first_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "last_name": {
                    "type":     "string",
                    "copy_to":  "full_name" 
                },
                "full_name": {
                    "type":     "string"
                }
            }
        }
    }
}

3.cross_field詞中心匹配 , 爲了讓 cross_fields 查詢以最優方式工作,所有的字段都須使用相同的分析器, 具有相同分析器的字段會被分組在一起作爲混合字段使用。

GET /_validate/query?explain
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields", 
            "operator":    "and",
            "fields":      [ "first_name", "last_name" ]
        }
    }
}

搜索過程 : 它會同時在 first_name 和 last_name 兩個字段中查找 smith 的 IDF ,然後用兩者的最小值作爲兩個字段的 IDF 。結果實際上就是 smith 會被認爲既是個平常的姓,也是平常的名。

爲了讓 cross_fields 查詢以最優方式工作,所有的字段都須使用相同的分析器, 具有相同分析器的字段會被分組在一起作爲混合字段使用。

cross_fields 查詢與 自定義 _all 字段 相比,其中一個優勢就是它可以在搜索時爲單個字段提升權重。

GET /books/_search
{
    "query": {
        "multi_match": {
            "query":       "peter smith",
            "type":        "cross_fields",
            "fields":      [ "title^2", "description" ] 
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章