28 pontos por xguru 2024-08-26 | 2 comentários | Compartilhar no WhatsApp
  • É possível construir dentro do Postgres um mecanismo de busca híbrido com busca semântica, full-text e fuzzy
  • A busca é uma parte importante de muitos apps, mas não é fácil implementá-la corretamente. Especialmente em pipelines de RAG, a qualidade da busca pode determinar o sucesso ou fracasso de todo o processo
  • A busca semântica está em alta, mas a busca tradicional baseada em termos ainda é a espinha dorsal da busca
  • Técnicas semânticas podem melhorar os resultados, mas funcionam melhor sobre uma base sólida de busca textual

Implementando um mecanismo de busca com Postgres

  • Combina três técnicas:
    • busca full-text com tsvector
    • busca semântica com pgvector
    • correspondência fuzzy com pg_trgm
  • Essa abordagem pode não ser a melhor absoluta em todos os cenários, mas é uma excelente alternativa a construir um serviço de busca separado
  • Um ponto de partida robusto que pode ser implementado e escalado dentro do banco de dados Postgres já existente
  • Por que usar Postgres para tudo: Apenas use Postgres para tudo, PostgreSQL é suficiente, Apenas use Postgres

Implementando FTS e busca semântica

  • O Supabase tem uma excelente documentação sobre implementação de busca híbrida, então ela será usada como ponto de partida
  • Seguindo o guia, a FTS é implementada com índices GIN, e a busca semântica com pgvector (também chamada de bi-encoder dense retrieval)
  • Pela experiência pessoal, escolher embeddings de 1536 dimensões pode gerar resultados muito melhores
  • As funções do Supabase são substituídas por CTEs e consultas, e $ é adicionado antes dos parâmetros
  • Aqui, os resultados são mesclados usando RRF (Reciprocal Ranked Fusion)
  • Esse método garante que itens bem posicionados em várias listas recebam alta colocação na lista final
  • Também evita que itens que estão muito bem em uma lista, mas mal em outras, acabem recebendo uma posição alta na lista final
  • Calcular a pontuação colocando a posição no denominador pode penalizar registros com ranking mais baixo
  • Pontos importantes
    • $rrf_k: para evitar que a pontuação do item na primeira posição fique extrema demais (já que se divide pela posição), costuma-se adicionar uma constante k ao denominador para suavizar a pontuação
    • $ _weight: é possível atribuir pesos a cada método. Isso é muito útil ao ajustar os resultados

Implementando busca fuzzy

  • Os métodos anteriores já resolvem bastante coisa, mas podem surgir problemas imediatos quando há erros de digitação em entidades nomeadas
  • A busca semântica capta similaridade e elimina parte desses problemas, mas ainda tem dificuldade com nomes, siglas e outros textos que não são semanticamente parecidos
  • Para mitigar isso, introduz-se a extensão pg_trgm para permitir busca fuzzy
    • Ela funciona com trigramas. Trigramas são úteis para busca fuzzy porque decompõem palavras em sequências de 3 caracteres
    • Isso permite corresponder palavras semelhantes mesmo quando há erros de digitação ou pequenas variações
    • Por exemplo, "hello" e "helo" compartilham muitos trigramas, então podem ser correspondidos mais facilmente na busca fuzzy
  • Cria-se um novo índice para a coluna desejada e depois ele é adicionado à consulta geral de busca
  • A extensão pg_trgm expõe o operador %, que filtra textos cuja similaridade é maior que pg_trgm.similarity_threshold (o padrão é 0.3)
  • Há também vários outros operadores úteis

Ajustando a busca full-text

  • Ajustando os pesos do tsvector: documentos reais incluem não só o título, mas também o conteúdo
  • Mesmo com várias colunas, mantém-se apenas uma coluna de embedding
  • Pessoalmente, foi observado que manter title e body no mesmo embedding não traz grande diferença de desempenho em relação a manter vários embeddings
  • No fim, o title deve ser uma representação resumida do corpo. Vale a pena experimentar isso conforme a necessidade
  • Espera-se que o título seja curto e rico em palavras-chave, enquanto o corpo tende a ser mais longo e conter mais detalhes
  • Portanto, é preciso ajustar como as colunas da busca full-text recebem pesos entre si
  • É possível atribuir prioridade conforme a posição da palavra no documento ou sua importância
    • A-weight: mais importante (ex.: título, cabeçalhos). Padrão 1.0
    • B-weight: importante (ex.: começo do documento, resumo). Padrão 0.4
    • C-weight: importância padrão (ex.: texto do corpo). Padrão 0.2
    • D-weight: menos importante (ex.: notas de rodapé, comentários). Padrão 0.1
  • A relevância pode ser refinada ajustando os pesos de acordo com a estrutura do documento e os requisitos da aplicação
  • Por que dar mais peso ao título
    • Porque o título normalmente expressa de forma concisa o tema principal do documento
    • Os usuários tendem a olhar primeiro os títulos ao pesquisar, então a correspondência de palavras-chave no título geralmente se relaciona mais com a intenção do usuário do que a correspondência no texto do corpo

Ajuste por comprimento

  • Ao ler a documentação de ts_rank_cd, percebe-se que há parâmetros de normalização
    • Ambas as funções de ranking usam uma opção inteira normalization para especificar se e como o comprimento do documento deve afetar a classificação. Como a opção inteira controla vários comportamentos, ela é uma máscara de bits: use | para especificar um ou mais comportamentos (ex.: 2|4).

  • Com essas várias opções, é possível:
    • ajustar o viés de comprimento do documento
    • equilibrar a relevância em diferentes conjuntos de documentos
    • ajustar os resultados de ranking para uma representação consistente
  • Definir 0 (sem normalização) para o título e 1 (comprimento logarítmico do documento) para o corpo produziu bons resultados
  • Novamente, vale experimentar várias opções para encontrar a que melhor se adapta ao seu caso de uso

Re-ranking com cross-encoder

  • Muitos sistemas de busca são compostos por duas etapas
  • Ou seja, usa-se um bi-encoder para recuperar os N resultados iniciais e depois um cross-encoder para comparar esses resultados com a consulta de busca e reordená-los
    • bi-encoder: por ser rápido, é bom para recuperar um grande número de documentos
    • cross-encoder
      • É mais lento, mas oferece melhor desempenho, sendo ideal para reordenar os resultados recuperados
      • Processa a consulta e o documento em conjunto, permitindo uma compreensão mais sutil da relação entre ambos
      • Isso oferece melhor precisão de ranking à custa de tempo de computação e escalabilidade
  • Existem várias ferramentas para fazer isso
  • Uma das melhores é o Rerank da Cohere
  • Outra abordagem é construir a sua própria usando o GPT da OpenAI
  • Cross-encoders podem melhorar a precisão dos resultados de busca por entenderem melhor a relação entre a consulta e o documento
  • No entanto, têm alto custo computacional, o que impõe limitações de escalabilidade
  • Por isso, uma abordagem em duas etapas é eficaz: usar um bi-encoder para a busca inicial e aplicar o cross-encoder apenas a um pequeno conjunto de documentos recuperados

Quando procurar uma solução alternativa

  • O PostgreSQL é uma boa escolha para muitos cenários de busca, mas não está livre de limitações
  • A ausência de algoritmos avançados como BM25 pode ser sentida ao lidar com documentos de comprimentos muito variados
  • A busca full-text do PostgreSQL depende de TF-IDF, então pode ter dificuldades com documentos muito longos e termos raros em coleções muito grandes
  • Antes de buscar uma solução alternativa, é essencial medir. Talvez não valha a pena

Conclusão

  • Este texto cobre muita coisa, desde busca full-text básica até técnicas avançadas como fuzzy matching, busca semântica e boosting de resultados
  • Aproveitando os recursos poderosos do Postgres, é possível criar um mecanismo de busca forte e flexível adaptado a requisitos específicos
  • Postgres talvez não seja a primeira ferramenta que vem à mente para busca, mas ele pode levar isso muito longe
  • Chaves para uma ótima experiência de busca
    • iteração contínua e ajuste fino
    • não tenha receio de usar as técnicas de depuração discutidas para entender o desempenho da busca e ajustar pesos e parâmetros com base no feedback e no comportamento dos usuários
  • Embora o PostgreSQL possa não ter alguns recursos avançados de busca, na maioria dos casos ele é poderoso o suficiente para construir um mecanismo de busca eficaz
  • Antes de procurar soluções alternativas, vale a pena explorar ao máximo os recursos do Postgres e medir o desempenho; se ainda assim não for suficiente, então pode-se considerar outra solução

2 comentários

 
eajrezz 2024-08-27

Fico curioso para saber se a busca em coreano também funciona bem.

 
xguru 2024-08-26

O tema do Weekly de hoje também era Postgres, e claro, lá vem Postgres de novo. Dá para ver que, proporcionalmente à popularidade, saem muitos textos sobre ele haha.
Sobre BM25, veja abaixo.

pg_bm25 - Extensão de busca full-text para Postgres que oferece qualidade no nível do Elastic
ParadeDB - PostgreSQL for Search