Produto

BM25 na prática — parte 1: como os shards afetam a pontuação de relevância no Elasticsearch

Este é o primeiro post da série Practical BM25 de três partes sobre classificação de similaridade (relevância). O próximo post está linkado na parte inferior.

Contexto

No Elasticsearch 5.0, mudamos para o Okapi BM25 como nosso algoritmo de similaridade padrão, que é o que é usado para pontuar resultados conforme eles se relacionam a uma consulta. Não vou entrar muito em detalhes sobre o BM25 versus medidas alternativas neste blog, mas se você quiser uma introdução à justificativa teórica do BM25, pode assistir à apresentação BM25 Demystified do Elastic{ON} 2016. Em vez disso, vou abordar (e espero que desmistificar) o uso prático do BM25 para você, incluindo a cobertura dos parâmetros que estão disponíveis e o que afeta a pontuação.

Lembre-se de que este blog se destina principalmente àqueles que realizam a pontuação de documentos de texto. Ou seja, ele é realmente focado em ajudar nossos usuários que buscam. Se você está fazendo a indexação de logs ou métricas e retornando resultados classificados por alguma ordem explícita de metadados/numérica, como o timestamp, este blog pode servir apenas para satisfazer sua curiosidade.

Entendendo como os shards afetam a pontuação

Como espero que você acompanhe em casa, uma das primeiras coisas que precisamos esclarecer é entender como ter mais de 1 shard afeta a pontuação, já que o Elasticsearch usa 5 shards principais por índice por padrão. Vamos começar criando um índice chamado “people”. As configurações que estou fornecendo aqui seriam o padrão (e, portanto, desnecessárias de definir), mas vou fazê-lo de qualquer maneira para ser explícito para fins de demonstração. Usarei variantes do meu nome (“Shane Connelly”) aqui, mas fique à vontade para substituí-lo por um nome de sua escolha se você estiver acompanhando em casa.

PUT people
{
  "settings": {
    "number_of_shards": 5,
    "index" : {
        "similarity" : {
          "default" : {
            "type" : "BM25"
          }
        }
    }
  }
}

E agora vamos adicionar um documento e depois buscá-lo. Primeiro, adicionaremos apenas meu primeiro nome:

PUT /people/_doc/1
{
  "title": "Shane"
}
GET /people/_doc/_search
{
    "query": {
        "match": {
             "title": "Shane"
         }
      }
}

Você recebe 1 resultado neste ponto, com uma pontuação de 0,2876821. Vamos mergulhar em como essa pontuação é derivada em breve, mas primeiro vamos ver o que acontece quando adicionamos mais alguns documentos com diferentes variantes do meu nome completo.

PUT /people/_doc/2
{
  "title": "Shane C"
}
PUT /people/_doc/3
{
  "title": "Shane Connelly"
}
PUT /people/_doc/4
{
  "title": "Shane P Connelly"
}

E agora faça a mesma buscar novamente:

GET /people/_doc/_search
{
    "query": {
        "match": {
             "title": "Shane"
         }
      }
}

Neste ponto, você deve ter 4 resultados, mas se olhar para as pontuações, pode ficar confuso. Os documentos 1 e 3 têm uma pontuação de 0,2876821, mas o documento 2 tem uma pontuação de 0,19856805 e o documento 4 tem uma pontuação de 0,16853254. Isso é algo que frequentemente confunde novos usuários. Os documentos 2 e 3 são muito semelhantes — ambos têm 2 termos e ambos correspondem a “shane”, mas o documento 2 tem uma pontuação muito menor. Você pode começar a presumir que há algo diferente na pontuação de “C” em relação à pontuação de “Connelly”, mas, na verdade, isso tem a ver com a forma como os documentos foram distribuídos nos shards.

Como lembrete, o Elasticsearch divide os documentos em shards, e cada shard contém um subconjunto dos dados. Se dermos uma olhada em:

GET /_cat/shards/people?v

Se você executar isso, verá que o shard 2 tem 2 documentos, enquanto os shards 3 e 4 têm apenas 1 documento (os shards 0 e 1 ainda não têm documentos). Isso significa que o total de ocorrências do termo “shane” é diferente nesses shards, e é isso que, em última análise, causa a diferença nas pontuações neste caso. Por padrão, o Elasticsearch calcula as pontuações com base em cada shard.

As pessoas começam a carregar apenas alguns documentos em seu índice e perguntam “por que o documento A tem uma pontuação maior/menor que o documento B” e, às vezes, a resposta é que o usuário tem uma proporção relativamente alta de shards para documentos, de modo que as pontuações ficam distorcidas entre diferentes shards. Existem algumas maneiras de obter pontuações mais consistentes entre os shards:

  1. Quanto mais documentos você carregar no seu índice, mais normalizadas as estatísticas de termo dos seus shards se tornarão. Com documentos suficientes, você pode não notar as pequenas diferenças nas estatísticas de termo e, portanto, na pontuação em cada shard.
  2. Você poderia usar uma contagem de shards menor para reduzir desvios estatísticos nas frequências de termos. Por exemplo, se tivéssemos definido number_of_shards como 1 nas configurações do índice, teríamos pontuações muito diferentes. Veríamos o documento 1 com uma pontuação de 0,13245322, os documentos 2 e 3 com pontuações de 0,105360515 cada, e o documento 4 com uma pontuação de 0,0874691. Existem algumas compensações ao ter vários números de shards principais, o que é discutido em nosso webinar de dimensionamento quantitativo de cluster.
  3. Você pode adicionar ?search_type=dfs_query_then_fetch à solicitação, que primeiro coleta as frequências distribuídas de termos (DFS = Distributed Frequency buscar) e depois calcula as pontuações usando isso. Na verdade, isso retorna a mesma pontuação que ter apenas 1 shard. Veja como os resultados diferem com e sem o parâmetro “search_type”:
    GET /people/_doc/_search?search_type=dfs_query_then_fetch
    {
        "query": {
            "match": {
                 "title": "Shane"
             }
          }
    }
    Isso proporciona o mesmo que ter definido number_of_shards=1. Você pode então perguntar: "bem, se isso produz pontuações mais precisas, por que não está ativado por padrão?" A resposta é que isso adiciona uma viagem de ida e volta extra durante o processamento para coletar todas as estatísticas e, para alguns casos de uso (onde a precisão da pontuação não é tão importante quanto a velocidade), essa viagem de ida e volta é desnecessária. Além disso, com dados suficientes nos shards, as estatísticas podem ficar muito próximas umas das outras, tornando as viagens de ida e volta desnecessárias também. Se você tiver dados suficientes, search_type=dfs_query_then_fetch é necessário principalmente apenas quando os dados entre os shards continuam distribuídos de forma desigual, como é o caso com algum roteamento personalizado.

OK, agora temos uma noção de como o sharding pode afetar nossa pontuação (e como ajustá-la). Em seguida, veremos o algoritmo BM25 e como diferentes variáveis entram em jogo.

Continue esta série com: Parte 2: O Algoritmo BM25 e suas Variáveis