Clustering não supervisionado de documentos com Elasticsearch + embeddings Jina

Uma abordagem prática e reproduzível para clustering não supervisionado de documentos com Elasticsearch e embeddings Jina.

A busca vetorial começa com uma consulta, mas e se você não tiver o que consultar?

As organizações acumulam grandes coleções de documentos, como chamados de suporte, processos judiciais, notícias, artigos de pesquisa, e precisam entender o que eles contêm antes de poderem fazer as perguntas certas. Sem rótulos nem dados de treinamento, revisar manualmente milhares de documentos é impraticável. A busca tradicional não ajuda quando você não sabe o que procurar.

Esta publicação tem uma abordagem nativa do Elasticsearch para clustering de documentos não supervisionados e rastreamento de histórias temporais que lida com esse problema de descoberta. Ao final, você poderá acompanhar arcos narrativos como este ao longo de vários dias:

O que você vai descobrir:

  • Por que embeddings de clustering (e não embeddings de recuperação) são importantes quando se deseja descobrir tópicos sem uma consulta?
  • Como a classificação de centroides sondada por densidade agrupa documentos por tópico usando Elasticsearch k-nearest neighbor (kNN) e processamento em lote msearch.
  • Como significant_text pode automaticamente rotular clusters para que os temas sejam legíveis sem precisar treinar um modelo?
  • Como as cadeias temporais de histórias conectam clusters diários para mostrar como os temas evoluem dia após dia.

Esta publicação foi gerada a partir de um Jupyter Notebook executável. As saídas inline que você vê ao longo deste pipeline são resultados reais. Clone o notebook complementar para que você mesmo possa executá-lo.

O pipeline utiliza ~8.500 artigos de fevereiro de 2025 da BBC News e do The Guardian como um corpus de teste. As notícias são convenientes porque apresentam um comportamento temporal claro, mas esse padrão se aplica a qualquer situação em que a descoberta de documentos seja importante: revisão jurídica, monitoramento de conformidade, síntese de pesquisas, triagem de suporte ao cliente.

Stack:

  • Jina v5 clustering embeddings: adaptadores LoRA (Low-Rank Adaptation) específicos para tarefas no agrupamento de tópicos. Jina ingressou na Elastic e os modelos estão disponíveis nativamente por meio do Elastic Inference Service (EIS).
  • Elasticsearch: kNN escalável, rotulagem significant_text e armazenamento de vetores.
  • DiskBBQ: um formato de índice vetorial baseado em disco que combina quantização binária aprimorada (BBQ) com particionamento hierárquico k-means para aceleração aproximada de vizinhos mais próximos (ANN). Essa partição de índice é interna à busca vetorial e separada do algoritmo de clustering baseado em densidade usado nesta postagem. bbq_disk armazena vetores quantizados em disco e mantém apenas metadados de partição no heap, reduzindo os requisitos de recursos, em comparação com bbq_hnsw, mantendo alta recuperação.
  • Clustering global + vinculação temporal diária: descoberta e evolução da narrativa.

O que você precisará:

  • Uma implementação do Elasticsearch (Elastic Cloud, Elasticsearch Serverless ou Elastic Self-Managed 8.18+/9.0+): bbq_disk requer a versão 8.18 ou posterior. A seção opcional do diversificador retriever exige 9.3+ ou serverless.
  • Uma chave de API Jina: o nível gratuito inclui 10 milhões de tokens, o que cobre o pipeline principal de clusterização (aproximadamente 4,25 milhões de tokens). A comparação opcional entre recuperação e clustering usa uma segunda passagem de incorporação.
  • Uma chave de API do Guardian (gratuita).

Configuração

Instale os pacotes necessários:

Opcional (somente se você executar ferramentas de scraping deste repositório):

Depois, configure chaves de API em um arquivo .env na raiz do projeto:

Este notebook chama load_dotenv(override=True), portanto os valores locais .env têm precedência.

Parte 1: clustering de descoberta – Por que fazer clustering de embeddings?

A maioria das buscas vetoriais utiliza embeddings de recuperação treinados para associar uma consulta a documentos relevantes. Isso é perfeito para buscas, mas não para descobertas. Quando você quer descobrir quais tópicos existem em um corpus sem qualquer consulta, precisa de embeddings que agrupem documentos semelhantes.

O Jina v5 resolve isso com adaptadores Low-Rank Adaptation (LoRA) específicos para cada tarefa. O LoRa adiciona pequenas atualizações de baixa classificação às camadas internas específicas, mantendo a maioria dos pesos do modelo base congelados, de modo que o comportamento do modelo se adapta a uma tarefa específica sem a necessidade de um novo treinamento completo. O mesmo modelo base produz embeddings diferentes dependendo do parâmetro task:

TarefaPreparado paraCaso de uso
retrieval.passageCorrespondência entre consulta e documentoBusca, retrieval augmented generation (RAG)
clusteringAgrupamento de tópicos (otimizado para clusters compactos)Descoberta, categorização

O adaptador de clustering é treinado para aproximar documentos sobre o mesmo tópico no espaço de incorporação e distanciar documentos sobre tópicos diferentes. A comparação visual abaixo torna a diferença concreta.

Recuperação vs. clustering: uma comparação visual

Para ver a diferença, uma amostra de documentos recebe embedding de ambos os tipos de tarefa. O clustering é realizado no espaço de incorporação original de 1024 dimensões; a aproximação e projeção uniforme de variedades (UMAP) é usada apenas para projetar essas incorporações em 2D para visualização. A UMAP preserva a estrutura local de vizinhança, tornando-a útil para comparar a separação de clusters.

Abaixo, o mesmo exemplo de 480 documentos é incorporado com ambos os tipos de tarefas e projetado para 2D com UMAP. Procure grupos de cores mais fechados e separados no painel de clustering.

Os embeddings de recuperação (à esquerda) espalham amplamente os tópicos; os embeddings de clustering (à direita) produzem grupos mais coesos e separados a partir dos mesmos documentos.

Os embeddings de clustering produzem grupos mais compactos e visualmente distintos. Os embeddings de recuperação distribuem os tópicos de maneira mais uniforme, ideais para busca (similaridade refinada); mas, para descoberta, o que importa são os clusters temáticos compactos.

É por isso que o task="clustering" é usado no restante deste guia.

Carregando o conjunto de dados

O corpus combina duas fontes de notícias para fevereiro de 2025:

Ter múltiplas fontes ajuda a validar se o clustering encontra tópicos em vez de estilos específicos de cada fonte.

Embedding com a tarefa de clustering

A API Jina v5 é chamada com task="clustering" para todos os documentos. Os embeddings são armazenados em cache no disco, portanto, as execuções subsequentes ignoram a API completamente.

A chamada da API é direta. O parâmetro task é a principal diferença em relação ao uso típico de embeddings:

O tempo abaixo reflete uma taxa de acerto do cache. A primeira execução contra a API demora mais, dependendo do tamanho do corpus.

Indexação em um único índice do Elasticsearch

Para clustering de descoberta, o mês inteiro é dedicado a um índice (docs-clustering-all). A partição diária vem depois para a ligação temporal da história.

O mapeamento do índice usa bbq_disk para o campo vetorial:

Um vetor float32 de dimensão 1024 tem 4 KB. bbq_disk utiliza k-means hierárquicos para particionar vetores em pequenos clusters, quantificá-los de forma binária e armazenar os vetores de precisão total no disco para repontuação. Apenas os metadados de partição permanecem no heap, então os requisitos de memória permanecem baixos mesmo para corpora grandes. Para cargas de trabalho que podem suportar mais heap, bbq_hnsw constrói um gráfico Hierarchical Navigable Small World (HNSW) para consultas mais rápidas com mais custo de recursos.

O tipo de campo dense_vector suporta múltiplas estratégias de quantização: bbq_disk e bbq_hnsw são os melhores ajustes para embeddings de alta dimensão como os vetores de dimensão 1024 usados aqui.

Clusteringo: classificação de centroides baseada em densidade

Algoritmos de clustering tradicionais, como o HDBSCAN, pressupõem que você possa manter a matriz vetorial completa de N×d na memória e executar atualizações de passagem completa repetidas. Para 8.495 documentos em 1024 dimensões, isso é administrável (aproximadamente 35 MB), mas a abordagem não é escalável para milhões de documentos sem infraestrutura adicional.

Este algoritmo é conceitualmente semelhante à inicialização do KMeans++ com atribuição de Voronoi e um nível de ruído, mas utiliza a busca kNN do Elasticsearch como primitiva de computação, mantendo quase todo o trabalho no lado do servidor:

  1. Amostra de 5% de documentos como sondas de densidade (amostra aleatória, mínimo de 50).
  2. Densidade da sonda por meio de lote msearch kNN. Cada sonda dispara uma consulta kNN e registra a semelhança média dos vizinhos. Alta similaridade média = região densa do espaço de embedding. msearch envia várias solicitações de pesquisa em uma única chamada HTTP, o que é fundamental aqui: a sondagem de densidade gera centenas de consultas kNN e processá-las em lote evita a sobrecarga por solicitação.
  3. Selecione sementes de alta densidade com diversificação: os candidatos acima da densidade média são classificados por densidade decrescente e aceitos avidamente somente quando a semelhança de cosseno com cada semente existente estiver abaixo de um limite de separação. Este é o único processamento do lado do cliente (~0,01s para 8k documentos).
  4. Classificar todos os documentos em relação aos centroides via msearch kNN: cada semente atua como um centroide; uma pesquisa kNN recupera documentos próximos acima de um limite de similaridade. Cada documento é atribuído ao centroide que o retornou com a maior pontuação. Pequenos clusters são dissolvidos em ruído.

O Elasticsearch cuida do trabalho pesado: msearch para sondas de densidade, msearch para classificação e significant_text para rotulagem. Para esse corpus (8.495 documentos), a amostra de sonda de densidade de 5% executa consultas de sonda de 425 kNN, que msearch agrupam lotes em nove chamadas HTTP (no tamanho de lote 50), evitando a sobrecarga de uma solicitação por sonda. Combinado com bbq_disk busca ANN, isso mantém a etapa de clustering rápida e escalável. As consultas kNN usam um valor mínimo de num_candidates para velocidade durante a passagem de clustering; consultas de busca em produção devem usar valores de num_candidates mais altos para melhorar a recordação, mas isso custa latência.

Clusters têm tamanhos naturais determinados pela densidade do espaço de embedding ao redor de cada centroide, não por um limite de k rígido. Regiões temáticas densas produzem clusters maiores; tópicos de nicho produzem agrupamentos menores.

Por que escolher KMeans ou HDBSCAN?

O algoritmo KMeans pressupõe clusters esféricos e requer a matriz completa N×d na memória. Para corpora que cabem na memória, HDBSCAN é uma excelente alternativa. Ele lida com formatos de cluster arbitrários e possui semântica de densidade bem compreendida.

A abordagem de centroide sondado por densidade mira em um nicho diferente: corpora onde você quer armazenamento, recuperação e clustering em um único sistema, ou onde a escala torna as operações matriciais do lado do cliente impraticáveis. Ele usa o Elasticsearch kNN como primitiva de computação, lida com tamanhos arbitrários de cluster e mantém quase toda a computação no lado do servidor.

Entendendo a taxa de ruído

A taxa de ruído de ~28% é intencional, não uma falha. Documentos que não cabem em nenhum cluster denso na similarity_threshold configurada ficam sem atribuição, em vez de serem forçados a uma correspondência ruim. Isso funciona como um filtro de qualidade: colunas de opinião, artigos curtos e reportagens isoladas naturalmente resistem ao clustering porque falta a densidade temática que define um grupo coerente.

O limiar é ajustável: reduzir similarity_threshold produz clusters mais abrangentes (mais documentos atribuídos, mas clusters menos coesos), enquanto aumentá-lo torna os clusters mais compactos e aumenta a fração de ruído. Para este corpus de conteúdo de notícias misto, ~30% de ruído é um ponto de operação razoável. Implantações em produção devem ajustar o limiar com base em critérios de qualidade específicos do domínio.

Rótulos automáticos com significant_text

Agora, cada cluster precisa de um rótulo de fácil compreensão. A agregação significant_text do Elasticsearch encontra termos que aparecem com frequência incomum em um conjunto em primeiro plano (o cluster) em comparação com um conjunto em segundo plano (o corpus completo).

Nos bastidores, ele usa uma heurística estatística (pontuação JLH por padrão) que equilibra mudanças de frequência absolutas e relativas, sem machine learning, sem chamadas de grandes modelo de linguagem (LLM). Um cluster sobre política do Reino Unido pode apresentar termos como starmer, labour, downing porque esses termos são desproporcionalmente comuns nesse cluster em comparação ao conjunto geral de notícias.

Para essa passagem global, os rótulos são calculados diretamente contra docs-clustering-all, então tanto o plano de frente quanto o plano de fundo são extraídos do mês inteiro. Na parte 2, a rotulagem utiliza o padrão de indexação diário (docs-clustering-*), um caractere curinga que permite que consultas abranjam todos os índices correspondentes simultaneamente, para dar significant_text um plano de fundo mais amplo e melhor contraste.

Um formato de consulta mínimo tem a seguinte aparência:

significant_text serve também como um filtro de qualidade: clusters que não produzem termos significativos não possuem vocabulário distintivo. São agrupamentos incoerentes que devem ser dissolvidos e reduzidos a ruído, em vez de receberem um rótulo enganoso.

Uma etapa de limpeza determinística e leve remove termos de rótulos irrelevantes (tokens numéricos, palavras genéricas) e recorre a um título representativo quando necessário. Isso mantém os rótulos nativos do Elasticsearch enquanto melhora a legibilidade.

Visualizando os clusters

As visualizações abaixo mostram o que a análise de clustering global descobriu: uma análise por data de documentos agrupados versus documentos de ruído, uma projeção UMAP para o mês inteiro e um gráfico de composição de fontes confirmando que os agrupamentos refletem tópicos em vez de fontes.

Distribuição diária de documentos agrupados versus ruídos ao longo de fevereiro de 2025.

Cada ilha colorida no UMAP representa um cluster: um grupo de artigos sobre o mesmo tema descobertos puramente por similaridade de incorporação. Os pontos de ruído cinza são artigos que não se encaixavam perfeitamente em nenhum cluster (artigos curtos, artigos de opinião ou histórias isoladas).

O gráfico de detalhamento da fonte confirma que os clusters contêm artigos de ambos BBC News e The Guardian. O clustering está encontrando tópicos, não fontes, exatamente o que a descoberta não supervisionada deve produzir.

Explorando a amplitude do cluster com o diversificador

O algoritmo kNN simples retorna os documentos mais semelhantes ao centroide de um cluster (o núcleo denso). Mas clusters reais abrangem subtópicos. O recuperador de diversificação usa a relevância marginal máxima (MMR) para destacar documentos que são relevantes para o centroide, mas também diferentes entre si.

O parâmetro chave é λ (lambda):

  • λ = 1,0 → relevância pura (o mesmo que kNN simples).
  • λ = 0,0 → diversidade pura (resultados de distribuição máxima).
  • λ = 0,5 → equilibrado: relevante para o tópico, mas abordando diferentes perspectivas.

Observação sobre a versão: o recurso de recuperação de diversificação está disponível no Elastic Cloud Serverless e no Elasticsearch autogerenciado versão 9.3 ou superior. Versões anteriores ainda podem seguir as seções de clustering e ligação temporal; apenas essa etapa de exploração exige o recuperador diversificado.

Uma forma mínima de solicitação de recuperador é assim:

Os parâmetros type, field, e query_vector são necessários no nível de diversificação: field informa à MMR qual campo dense_vector usar para similaridade entre resultados, e query_vector fornece o ponto de referência para a pontuação de relevância.

Isso permite que você responda: "O que esse cluster cobre de fato?" em vez de apenas "Qual é o ponto central?"

Os resultados simples kNN se agrupam em torno de um ângulo do tema: os documentos mais semelhantes ao centroide e entre si. O recurso de recuperação de diversidade revela diferentes facetas do mesmo cluster: subtópicos, fontes diversas e perspectivas variadas.

A métrica de diversidade confirma isso quantitativamente: a similaridade média entre pares é menor para os resultados do recuperador diversificado, o que significa que os documentos retornados abrangem um espectro mais amplo.

Isso é útil para você:

  • Entender o que um cluster cobre, não apenas o centro, mas também as bordas.
  • Geração de resumos. Documentos representativos diversos oferecem um material melhor para um LLM.
  • Encontrar exemplos representativos para análise humana ou rotulagem posterior.
  • Verificações de qualidade. Se os resultados diversos parecerem incoerentes, o cluster pode precisar ser dividido.

Parte 2: Cadeias de histórias temporais

Acompanhando histórias ao longo dos dias

A parte 1 fez o clustering de todo o mês global para descoberta de tópicos. Para o fluxo temporal, a mesma classificação de centroides sondados por densidade é executada independentemente por dia em índices diários, e depois os clusters são vinculados ao longo de dias consecutivos. Observe que os clusters diários são independentes dos clusters globais da parte 1; cada dia produz as próprias atribuições de agrupamento e rótulos ajustados ao conteúdo daquele dia.

A abordagem de vinculação: amostragem e consulta

Para cada cluster no dia A:

  1. Você pode ver uma amostra de alguns documentos representativos.
  2. Executar kNN contra o índice do dia B.
  3. Conte quantos acessos caem em cada cluster B do dia.
  4. Se a fração de acerto ultrapassar um limite (fração de kNN ≥ 0,4), registre um link.

Isso é rápido (apenas alguns documentos por cluster são consultados, nem todos) e usa o kNN nativo do Elasticsearch, sem necessidade de ferramentas externas.

Uma fração de kNN de 100% significa que todos os documentos mostrados do cluster de origem foram atribuídos ao mesmo cluster de destino, o vínculo mais forte possível entre os dias. A maioria dos links acima está relacionada ao futebol, o que faz sentido: a cobertura da Premier League é feita diariamente com alta consistência de tópicos.

O link score | operator | gedlingleague | striker | season é um exemplo de um cluster de futebol local de nicho (Gedling é um clube fora da liga) sendo absorvido pelo cluster mais amplo da Premier League no dia seguinte, um efeito natural do clustering diário em diferentes granularidades.

Criar cadeias de histórias

Uma cadeia de histórias é uma sequência de clusters ligados ao longo de dias consecutivos.

Ligações pareadas individuais indicam que o cluster "política do Reino Unido" de segunda-feira está conectado ao de terça-feira. As cadeias revelam o arco completo: uma história que começa na segunda-feira, evolui durante a semana e encerra na sexta-feira.

As cadeias são construídas de forma ávida a partir de links com uma fração kNN ≥ 0,4, o que significa que pelo menos 40% dos documentos mostrados do cluster de origem chegaram a um único cluster de destino. A partir do cluster mais antigo, o algoritmo sempre segue o link de saída mais forte.

A rede mais longa acompanha a cobertura Ucrânia–Rússia por 19 dias consecutivos, o que não surpreende dada a intensidade geopolítica em fevereiro de 2025. O segundo mais longo acompanha o futebol da Premier League ao longo de 19 dias do mês. Cadeias mais curtas captam a temporada de premiações (filme/prêmios, seis dias), o rúgbi Six Nations (10 dias) e a cobertura da liderança política do Reino Unido (sete dias). Cada cadeia representa um arco narrativo que o algoritmo descobriu ao incorporar similaridade entre índices diários.

Sankey: Visualizando o fluxo da história

Um diagrama de Sankey é uma visualização de fluxo onde a largura da ligação representa a força da conexão. Aqui, cada faixa vertical representa um dia, cada nó é um cluster diário (dimensionado pela contagem de documentos), e cada caminho colorido traça uma cadeia de histórias ao longo do tempo. A largura do link codifica a força de sobreposição kNN: links mais espessos indicam que mais documentos mostrados caíram no cluster alvo. As cores são consistentes por cadeia, então um único caminho colorido da esquerda para a direita representa o progresso de uma história.

Por exemplo, a cadeia Ucrânia-Rússia (visível como um dos caminhos mais longos) flui continuamente desde o início de fevereiro até a terceira semana, com elos consistentemente espessos indicando forte continuidade temática ao longo dos dias.

Cadeias temporais de histórias que fluem ao longo de fevereiro de 2025. Cada caminho colorido representa uma história que persiste ao longo dos dias; com a largura indicando a força de sobreposição do kNN.

O que essa abordagem oferece

Esta análise abordou um pipeline completo de clustering de documentos não supervisionado construído no Elasticsearch:

  1. Embeddings de clustering: os adaptadores específicos de tarefa do Jina v5 produzem embeddings otimizadas para agrupamento de tópicos, e não apenas para correspondência de consulta-documento.
  2. Clustering de descoberta global: clustering o mês inteiro em um único índice maximiza a descoberta de tópicos ao longo dos dias.
  3. Classificação de centroides com base na densidade: amostra 5%, sondar a densidade via msearch kNN, selecionar sementes diversas de alta densidade, classificar todos os documentos em relação aos centroides. O Elasticsearch cuida do processamento pesado; apenas a seleção de sementes executa do lado do cliente (~0,01s).
  4. significant_text rotulagem: o teste de significância produz rótulos de cluster significativos sem qualquer modelo de ML ou anotação manual. Clusters que não produzem termos significativos são incoerentes e são rebaixados a ruído, uma porta de qualidade integrada.
  5. Vinculação temporal de histórias: índices diários e kNN de índice cruzado entre amostra e consulta rastreiam como as histórias evoluem ao longo do tempo.

Principais conclusões:

  • O tipo de tarefa de incorporação importa: embeddings de clustering produzem grupos tópicos mensuravelmente mais compactos.
  • O Elasticsearch pode atuar tanto como camada de armazenamento quanto como motor de clustering por meio da busca kNN.
  • A classificação de centroides baseada em densidade mantém quase toda a computação no lado do servidor e produz clusters com tamanhos naturais determinados pela densidade do espaço de incorporação.
  • significant_text é rápido, compreensível e eficaz tanto para autorrotulagem quanto para controle de qualidade.

Quando essa abordagem é útil:

  • Você tem texto com carimbo de data e hora e quer descobrir tópicos sem dados de treinamento rotulados.
  • Você precisa de uma plataforma para armazenamento, busca vetorial, rotulagem e ligação temporal.

Extensões para explorar:

  • Clustering por múltiplos períodos (semanal, pacotes mensais).
  • Ingestão em tempo real com atribuição incremental de cluster.
  • Resumos de cluster gerados pelo LLM usando os termos significant_text como sementes.
  • Em escala maior, centroides KMeans mostrados podem servir como sementes de aquecimento para clustering baseado em densidade, reduzindo o custo da fase da sonda.

Experimente você mesmo

Troque seu próprio corpus de documentos com carimbo de data; qualquer coleção de texto com datas funciona com esse pipeline. O notebook completo e o código de suporte estão disponíveis no repositório complementar.

Quão útil foi este conteúdo?

Não útil

Um pouco útil

Muito útil

Conteúdo relacionado

Pronto para criar buscas de última geração?

Uma pesquisa suficientemente avançada não se consegue apenas com o esforço de uma só pessoa. O Elasticsearch é impulsionado por cientistas de dados, especialistas em operações de aprendizado de máquina, engenheiros e muitos outros que são tão apaixonados por buscas quanto você. Vamos nos conectar e trabalhar juntos para construir a experiência de busca mágica que lhe trará os resultados desejados.

Experimente você mesmo(a)