Scoring and boosting in Elasticsearch

A subtle difference between filter and full-text search consists on scoring. It's score who distinguishes result corresponding to filter from how well result matches the query.

To work well with Elastcisearch, understanding of scoring is important. Without that, we can explain with difficulty why some documents are returned in higher position than others. It's why the first part of this article begins with explaination of scoring algorithm. After that, we'll try to explore boosting feature which consists on changing score results computed by Elasticsearch.

Scoring in Elasticsearch

Scoring in Elastcisearch consists on associating relevancy values to documents found in search. It's very useful in multiple words full-text search where document can match as well one searched word (for example: "house") as all of them ("my house"). Scores are represented by field called _score. But according to which criteria they are computed ?

To understand scoring calculation ingredients, we'll base on formula used to compute the score. This formula is based on concepts taken from term frequency/inverse document frequency (TF/IDF) and the vector space model concepts. Let's begin by components of the first one:

Vector space model is used to check how well document is matching multiterm query. Elasticsearch constructs a vector over each index document matching search query. The vector contains weights of all terms defined in the search and present in given document. For example, if we search "and Magnoliidae", document containing only "and" term will have vector looking like [1, 0], where 1 is the weight of "and" and 0 is the weight of "Magnoliidae" term, missing in the document. On the other side, document matching both "and Magnoliidae" terms, wil have a vector like [1, 5]. After, Elaticsearch measures the angle between the query and document vector. If the angle between query and document vector are big, the relevancy is low.

Debuging Elasticsearch score

Each search query allows us to debug scoring and understand score differences between documents. To achieve that we can pass explain parameter in query string, as below http://localhost:9200/waitingforcode/teams/_search?q=name:rc%20roubaix&pretty=true&explain. Elasticsearch will return hits part containing _explanation field:

"_source":{  
  "name":"Excelsior Roubaix"
},
"_explanation":{  
  "value":0.34794572,
  "description":"product of:",
  "details":[  
    {  
      "value":0.69589144,
      "description":"sum of:",
      "details":[  
        {  
          "value":0.69589144,
          "description":"weight(_all:roubaix in 15) [PerFieldSimilarity], result of:",
          "details":[  
            {  
              "value":0.69589144,
              "description":"score(doc=15,freq=1.0), product of:",
              "details":[  
                {  
                  "value":0.31664962,
                  "description":"queryWeight, product of:",
                  "details":[  
                    {  
                      "value":3.5162723,
                      "description":"idf(docFreq=101, maxDocs=1263)"
                    },
                    {  
                      "value":0.09005264,
                      "description":"queryNorm"
                    }
                  ]
                },
                {  
                  "value":2.1976702,
                  "description":"fieldWeight in 15, product of:",
                  "details":[  
                    {  
                      "value":1.0,
                      "description":"tf(freq=1.0), with freq of:",
                      "details":[  
                        {  
                          "value":1.0,
                          "description":"termFreq=1.0"
                        }
                      ]
                    },
                    {  
                      "value":3.5162723,
                      "description":"idf(docFreq=101, maxDocs=1263)"
                    },
                    {  
                      "value":0.625,
                      "description":"fieldNorm(doc=15)"
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {  
      "value":0.5,
      "description":"coord(1/2)"
    }
  ]
}

As you can see, we retrieve there the concepts defined in the first part of this article: tf, idf and fieldNorm. Each of them has associated value, used after to make final score computation. Because our search contains two terms: "rc" and "roubaix", we can find explain parts of both of them.

Formula used to compute final score value is called practical scoring function. It's look like:

score(q,d)  =  queryNorm(q) * coord(q,d) * ∑ (tf(t in d) *  idf(t) *  t.getBoost() * norm(t,d)) (t in q) 

Some new functions appeared:

Boosting in Elastcisearch

The last parameter quoted in previous part was boost. It helps to modify à posteriori the scores computed by Elasticsearch. It can be implemented at index time or at query time. According to Elasticsearch index boost documentation, boosting at query time should be prefered over boosting at index time for several reasons:

To resume, if boost is needed, it's better to use it at query time. But how to do ? If we want to boost a single field, we need to define new attribute in query DSL, boost. Query without this field takes a neutral boost equal to 1. We can also boost one or mutliple indexes. To do that, we need to define indices_boost attribute at query DSL root level.

In our example we'll take the example of newspaper, quoted at the begin of this article. It'll contain 2 fields, title and content. Title field will be boosted while content no. Let's first create new type in index (http://localhost:9200/waitingforcode/_mapping/newspaper):

{"newspaper" : { "properties" : { 
  "title" : {"type" : "string", "store" : true },
  "content" : {"type" : "string", "store" : true }
}}}

And save some data (http://localhost:9200/waitingforcode/_bulk):

{"index": {"_index": "waitingforcode", "_type": "newspaper"}}
{"title": "MyTeam won Champions League", "content": "MyTeam won prestigious Champions League"}
{"index": {"_index": "waitingforcode", "_type": "newspaper"}}
{"title": "New Champions League winner", "content": "Prestigious Champions League was won by MyTeam"}

Now, let's compare results given by query without boost with the query containing negative boosting for title field:

{"query": { "bool": { "should": [
  {"match": {"title": {"query": "myteam"}}},
  {"match": {"content": {"query": "myteam"}}}
]}}}

The result returned two hits, one corresponding to document identified by AU74SjgfXaJ2i7zl3W3a, the second for document with id AU74SjgfXaJ2i7zl3W3b. The score was 4.95146 for the first one ("MyTeam won Champions League" title) and 0.2565833 for the second ("New Champions League winner"). Now, we'll try to decreate drastically the importance of title field by boosting it with negative value:

{"query": { "bool": { "should": [
  {"match": {"title": {"query": "myteam", "boost": -1000000}}},
  {"match": {"content": {"query": "myteam", "boost": 2}}}
]}}}

The influence on returned scores are visible immediately. "MyTeam won Champions League" document passed from 4.95146 to -3.7346187, while "New Champions League winner" gained almost 6 points: 6.460153e-7.

This article shows some basic concepts hidden behind Elasticsearch scoring feature. At the begin we can see which ideas are used to define how well given document matches to search criteria. After that, we learn that thanks to explain parameter we can see how many points are attributed for each term defined in matched document. At the end, we can see query boosting, ie. changing score values at query time with the definition of boost field in query DSL.

If you liked it, you should read: