<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Python - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Python - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/pt/search-labs/blog/category/python-programming</link>
    </image>
    <link>https://www.elastic.co/pt/search-labs/blog/category/python-programming</link>
    <atom:link href="https://www.elastic.co/pt/search-labs/rss/category/python-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[pt]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 08:31:00 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Busca multimodal de picos de montanhas com Elasticsearch e SigLIP-2 ]]></title>
    <description><![CDATA[Aprenda como implementar buscas multimodais de texto para imagem e de imagem para imagem usando embeddings SigLIP-2 e busca vetorial kNN do Elasticsearch. Objetivo do projeto: encontrar fotos do pico do Monte Ama Dablam tiradas durante uma trilha no Everest.]]></description>
    <content:encoded><![CDATA[<p>Você já quis pesquisar seu álbum de fotos por significado? Experimente buscas como "mostre-me fotos minhas onde estou usando uma jaqueta azul e sentado em um banco", "mostre-me fotos do Monte Everest" ou "saquê e sushi". Pegue uma xícara de café (ou sua bebida favorita) e continue lendo. Neste blog, mostraremos como criar um aplicativo de busca híbrido multimodal. Multimodal significa que o aplicativo consegue entender e pesquisar em diferentes tipos de entrada — texto, imagens e áudio — e não apenas palavras. Híbrido significa que combina técnicas como correspondência de palavras-chave, busca vetorial kNN e geofencing para fornecer resultados mais precisos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="Biblioteca de fotos de diferentes picos de montanhas da trilha até o Monte Everest." /><p>Para isso, utilizamos o SigLIP-2 do Google para gerar representações vetoriais tanto para imagens quanto para texto, e as armazenamos no banco de dados vetorial Elasticsearch. No momento da consulta, convertemos a entrada da pesquisa, seja texto ou imagem, em representações vetoriais (embeddings) e executamos buscas vetoriais kNN rápidas para recuperar os resultados. Essa configuração permite uma busca eficiente de texto para imagem e de imagem para imagem. A interface Streamlit UI dá vida a este projeto, fornecendo-nos um frontend que não só permite realizar buscas textuais para encontrar e visualizar as fotos correspondentes no álbum, como também nos permite identificar o pico da montanha na imagem carregada e visualizar outras fotos dessa montanha no álbum.
Abordamos também as medidas que tomamos para melhorar a precisão da pesquisa, juntamente com dicas e truques práticos. Para uma exploração mais aprofundada, disponibilizamos um <a href="https://github.com/navneet83/multimodal-mountain-peak-search">repositório no GitHub</a> e um <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">notebook no Colab</a>.</p><h2>Como tudo começou</h2><p>Este post do blog foi inspirado por uma criança de 10 anos que me pediu para mostrar todas as fotos do Monte Ama Dablam que tirei na minha trilha até o Acampamento Base do Everest. Enquanto examinávamos o álbum de fotos, também me pediram para identificar vários outros picos de montanhas, alguns dos quais eu não sabia o nome.</p><p>Isso me deu a ideia de que este pode ser um projeto divertido de visão computacional. O que queríamos alcançar:</p><ul><li><p>Encontre fotos de um pico de montanha pelo nome.</p></li><li><p>Adivinhe o nome do pico da montanha a partir de uma imagem e encontre picos semelhantes no álbum de fotos.</p></li><li><p>Fazer com que as consultas de conceito funcionem (<em>pessoa</em>, <em>rio</em>, <em>bandeiras de oração</em>, <em>etc.)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="Monte Ama Dablam " /><h2>Montando a equipe dos sonhos: SigLIP-2, Elasticsearch e Streamlit</h2><p>Rapidamente ficou claro que, para isso funcionar, precisaríamos transformar tanto o texto (“Ama Dablam”) quanto as imagens (fotos do meu álbum) em vetores que pudessem ser comparados de forma significativa, ou seja, no mesmo espaço vetorial. Uma vez feito isso, a busca se torna simplesmente "encontrar os vizinhos mais próximos".</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch e Streamlit - uma combinação dos sonhos." /><p>Para gerar representações vetoriais de imagens, usamos um<a href="https://huggingface.co/blog/vlms-2025"> codificador multilíngue de visão e linguagem</a>, de modo que uma foto de uma montanha e uma frase como "Ama Dablam" fiquem no mesmo espaço vetorial.</p><p><a href="https://huggingface.co/blog/siglip2"><strong>O SigLIP-2</strong></a>, lançado recentemente pelo Google, se encaixa bem aqui. Ele consegue gerar embeddings sem treinamento específico para a tarefa (uma configuração <strong>zero-shot</strong> ) e funciona bem para o nosso caso de uso: fotos não rotuladas e picos com nomes e idiomas diferentes. Como foi treinado para correspondência de texto ↔ imagem, uma foto da montanha tirada durante a trilha e um breve texto de exemplo resultam em representações vetoriais muito semelhantes, mesmo quando o idioma ou a ortografia da consulta variam.</p><p>O SigLIP-2 oferece um excelente equilíbrio entre qualidade e velocidade, suporta múltiplas resoluções de entrada e funciona tanto na CPU quanto na GPU. O SigLIP-2 foi projetado para ser mais resistente a fotos tiradas ao ar livre em comparação com modelos anteriores, como o CLIP original. Durante nossos testes, o SigLIP-2 gerou resultados confiáveis de forma consistente. Além disso, conta com amplo suporte, o que a torna a escolha óbvia para este projeto.</p><p>Em seguida, precisamos de um banco de dados vetorial para armazenar os embeddings e realizar buscas avançadas. Deveria suportar não apenas a busca kNN de cosseno em embeddings de imagem, mas também aplicar filtros de geolocalização e texto em uma única consulta. O Elasticsearch se encaixa bem aqui: ele lida muito bem com vetores (HNSW kNN em campos dense_vector), suporta busca híbrida que combina consultas de texto, vetores e geolocalização, e oferece filtragem e classificação prontas para uso. Ele também se adapta à escala horizontal, facilitando a expansão de um punhado de fotos para milhares. O <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">cliente oficial do Elasticsearch para Python</a> mantém a infraestrutura simples e se integra perfeitamente ao projeto. Por fim, precisamos de uma interface leve onde possamos inserir consultas de pesquisa e visualizar os resultados. Para uma demonstração rápida baseada em Python, o Streamlit é uma ótima opção. Ele fornece os recursos básicos de que precisamos: upload de arquivos, uma grade de imagens responsiva e menus suspensos para classificação e geolocalização. É fácil clonar e executar localmente, e também funciona em um notebook do Colab.</p><h2>Implementação</h2><h3>Design e estratégia de indexação do Elasticsearch</h3><p>Usaremos dois índices para este projeto: <code>peaks_catalog</code> e <code>photos</code>.</p><h4>Índice do catálogo de picos</h4><p>Este índice serve como um catálogo compacto dos picos de montanhas mais proeminentes que podem ser vistos durante a trilha até o Acampamento Base do Everest. Cada documento neste índice corresponde a um único pico de montanha, como o Monte Everest. Para cada documento de pico de montanha, armazenamos nomes/apelidos, coordenadas opcionais de latitude e longitude e um único vetor protótipo construído pela combinação de prompts de texto SigLIP-2 (e imagens de referência opcionais).</p><p><strong>Mapeamento do índice:</strong></p><p>Campo</p><p>Tipo</p><p>Exemplo</p><p>Objetivo/Observações</p><p>Vetor/Indexação</p><p>eu ia</p><p>palavra-chave</p><p>ama-dablam</p><p>Slug/ID estável</p><p>—</p><p>nomes</p><p>texto + subcampo de palavra-chave</p><p>["Ama Dablam","Amadablam"]</p><p>Aliases / nomes multilíngues; names.raw para filtros exatos</p><p>—</p><p>latlon</p><p>ponto_geográfico</p><p>{"lat":27.8617,"lon":86.8614}</p><p>Coordenadas GPS do pico como uma combinação de latitude/longitude (opcional)</p><p>—</p><p>elev_m</p><p>inteiro</p><p>6812</p><p>Elevação (opcional)</p><p>—</p><p>texto incorporado</p><p>dense_vector</p><p>768</p><p>Protótipo misto (com instruções e, opcionalmente, 1 a 3 imagens de referência) para este pico.</p><p>índice:true, similaridade:"cosseno", opções_de_índice:{type:"hnsw", m:16, ef_construction:128}</p><p>Este índice é usado principalmente para buscas de imagem para imagem, como identificar picos de montanhas a partir de imagens. Também utilizamos esse índice para aprimorar os resultados de busca de texto para imagem.</p><p>Em resumo, o <code>peaks_catalog</code> transforma a pergunta "Que montanha é esta?" em um problema de vizinho mais próximo focado, separando efetivamente a compreensão conceitual das complexidades dos dados da imagem.</p><p><strong>Estratégia de indexação para o índice peaks_catalog: </strong>Começamos criando uma lista dos picos mais proeminentes visíveis durante a trilha do Campo Base do Everest. Para cada pico, armazenamos sua localização geográfica, nome, sinônimos e altitude em um <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">arquivo YAML</a>. O próximo passo é <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">gerar o embedding</a> para cada pico e armazená-lo no campo <code>text_embed</code> . Para gerar embeddings robustos, utilizamos a seguinte técnica:</p><ul><li><p>Crie um protótipo de texto usando:</p><ul><li><p>nomes dos picos</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">Conjunto de prompts</a> (usando vários prompts diferentes para tentar responder à mesma pergunta), por exemplo:</p><ul><li><p>“uma foto natural do pico da montanha {name} no Himalaia, Nepal”</p></li><li><p>“{name} pico emblemático na região de Khumbu, paisagem alpina”</p></li><li><p>“{name} cume da montanha, neve, crista rochosa”</p></li></ul></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L333">Anti-conceito</a> opcional (indicando ao SigLIP-2 o que não deve ser correspondido): subtrair um pequeno vetor para "pintura, ilustração, pôster, mapa, logotipo" para que haja uma preferência por fotos reais.</p></li></ul></li><li><p>Opcionalmente, <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">crie um protótipo de imagem</a> se forem fornecidas imagens de referência do pico.</p></li></ul><p>Em seguida, <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">combinamos o texto e o protótipo da imagem</a> para gerar a incorporação final. Finalmente, o documento é <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">indexado</a> com todos os campos necessários:</p>def l2norm(v: np.ndarray) -&gt; np.ndarray:
    return v / (np.linalg.norm(v) + 1e-12)
def compute_blended_peak_vec(
        emb: Siglip2,
        names: List[str],
        peak_id: str,
        peaks_images_root: str,
        alpha_text: float = 0.5,
        max_images: int = 3,
) -&gt; Tuple[np.ndarray, int, int, List[str]]:
    """
    Build blended vector for a single peak.

    Returns:
      vec           : np.ndarray (L2-normalized)
      found_count   : number of reference images discovered
      used_count    : number of references used (&lt;= max_images)
      used_filenames: list of filenames used (for logging)
    """
    # 1) TEXT vector
    tv = embed_text_blend(emb, names)

    # 2) IMAGE refs: prefer folder by id; fallback to slug of the primary name
    root = Path(peaks_images_root)
    candidates = [root / peak_id]
    if names:
        candidates.append(root / slugify(names[0]))

    all_refs: List[Path] = []
    for c in candidates:
        if c.exists() and c.is_dir():
            all_refs = list_ref_images(c)
            if all_refs:
                break

    found = len(all_refs)
    used_list = all_refs[:max_images] if (max_images and found &gt; max_images) else all_refs
    used = len(used_list)

    img_v = embed_image_mean(emb, used_list) if used_list else None

    # 3) Blend TEXT and IMAGE vectors, clamp alpha to [0,1]
    a = max(0.0, min(1.0, float(alpha_text)))
    vec = l2norm(tv if img_v is None else (a * tv + (1.0 - a) * img_v)).astype("float32")
    return vec, found, used, [p.name for p in used_list]<p>Documento de exemplo do índice <code>peaks_catalog</code> :</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Um documento de exemplo do índice peaks_catalog no Elasticsearch." /><h4>Índice de fotos</h4><p>Este índice principal armazena informações detalhadas sobre todas as fotos do álbum. Cada documento representa uma única fotografia, contendo as seguintes informações:</p><ul><li><p>Caminho relativo até a foto no álbum de fotos. Isso pode ser usado para visualizar a imagem correspondente ou carregar a imagem na interface de pesquisa.</p></li><li><p>Informações de GPS e horário da imagem.</p></li><li><p>Vetor denso para codificação de imagem gerado por SigLIP-2.</p></li><li><p><code>predicted_peaks</code> Isso nos permite filtrar pelo nome do pico.

<strong>Mapeamento de índice</strong></p></li></ul><p>Campo</p><p>Tipo</p><p>Exemplo</p><p>Objetivo/Observações</p><p>Vetor / Indexação</p><p>caminho</p><p>palavra-chave</p><p>dados/imagens/IMG_1234.HEIC</p><p>Como a interface do usuário abre a miniatura/imagem completa</p><p>—</p><p>imagem_recortada</p><p>dense_vector</p><p>768</p><p>Incorporação de imagem SigLIP-2</p><p>índice:true, similaridade:"cosseno", opções_de_índice:{type:"hnsw", m:16, ef_construction:128}</p><p>picos_previstos</p><p>palavra-chave</p><p>["ama-dablam","pumori"]</p><p>Top-K palpites no momento da indexação (filtro/faceta de UX barato)</p><p>—</p><p>GPS</p><p>ponto_geográfico</p><p>{"lat":27.96,"lon":86.83}</p><p>Ativa filtros geográficos</p><p>—</p><p>tempo_de_tiro</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>Tempo de captura: classificar/filtrar</p><p>—</p><p><strong>Estratégia de indexação para o índice de fotos: </strong>Para cada foto no álbum, fazemos o seguinte:
 Extrair informações das imagens <code>shot_time</code> e <code>gps</code> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">dos metadados da imagem</a>.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Incorporação de imagem SigLIP-2</a>: passe a imagem pelo modelo e normalize o vetor usando a notação L2. Armazene o embedding no campo <code>clip_image</code> .</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">Preveja os picos</a> e armazene-os no campo <code>predicted_peaks</code> . Para fazer isso, primeiro pegamos o vetor de imagem da foto gerado na etapa anterior e, em seguida, executamos uma busca kNN rápida no campo text_embed no índice <code>peaks_catalog</code> . Mantemos os 3 ou 4 picos mais altos e ignoramos o resto.</p></li><li><p>Calculamos o campo <code>_id</code> fazendo um <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">hash</a> no nome e caminho da imagem. Isso garante que não teremos duplicatas após várias execuções.</p></li></ul><p>Após determinarmos todos os campos da foto, os documentos fotográficos são indexados em lotes usando indexação <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">em massa</a> :</p>def bulk_index_photos(
        es: Elasticsearch,
        images_root: str,
        photos_index: str = "photos",
        peaks_index: str = "peaks_catalog",
        topk_predicted: int = 5,
        batch_size: int = 200,
        refresh: str = "false",
) -&gt; None:
    """Walk a folder of images, embed + enrich, and bulk index to Elasticsearch."""
    root = Path(images_root)
    if not root.exists():
        raise SystemExit(f"Images root not found: {images_root}")

    emb = Siglip2()
    batch: List[Dict[str, Any]] = []
    n_indexed = 0

    for p in iter_images(root):
        rel = relpath_within(root, p)
        _id = id_for_path(rel)

        # 1) Image embedding (and reuse it for predicted_peaks)
        try:
            with Image.open(p) as im:
                ivec = emb.image_vec(im.convert("RGB")).astype("float32")
        except (UnidentifiedImageError, OSError) as e:
            print(f"[skip] {rel} — cannot embed: {e}")
            continue

        # 2) Predict top-k peak names
        try:
            top_names = predict_peaks(es, ivec.tolist(), peaks_index=peaks_index, k=topk_predicted)
        except Exception as e:
            print(f"[warn] predict_peaks failed for {rel}: {e}")
            top_names = []

        # 3) EXIF enrichment (safe)
        gps = get_gps_decimal(str(p))
        shot = get_shot_time(str(p))

        # 4) Build doc and stage for bulk
        doc = {"path": rel, "clip_image": ivec.tolist(), "predicted_peaks": top_names}
        if gps:
            doc["gps"] = gps
        if shot:
            doc["shot_time"] = shot

        batch.append(
            {"_op_type": "index", "_index": photos_index, "_id": _id, "_source": doc}
        )

        # 5) Periodic flush
        if len(batch) &gt;= batch_size:
            helpers.bulk(es, batch, refresh=refresh)
            n_indexed += len(batch)
            print(f"[photos] indexed {n_indexed} (last: {rel})")
            batch.clear()

    # Final flush
    if batch:
        helpers.bulk(es, batch, refresh=refresh)
        n_indexed += len(batch)
        print(f"[photos] indexed {n_indexed} total.")

    print("[done] photos indexing")<p>Exemplo de documento do índice de fotos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Um documento de exemplo do índice de fotos no Elasticsearch." /><p>Em resumo, o índice de fotos é um armazenamento rápido, filtrável e compatível com kNN de todas as fotos do álbum. Seu mapeamento é propositalmente minimalista — apenas a estrutura necessária para recuperar rapidamente, exibir de forma clara e segmentar os resultados por espaço e tempo. Este índice serve para ambos os casos de uso de pesquisa. O script em Python para criar ambos os índices pode ser encontrado <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">aqui</a>.</p><p>A visualização do mapa Kibana abaixo exibe documentos do álbum de fotos como pontos verdes e picos de montanhas do índice <code>peaks_catalog</code> como triângulos vermelhos, com os pontos verdes alinhando-se bem com a trilha da caminhada até o Acampamento Base do Everest.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Uma visualização do mapa Kibana exibindo documentos do álbum de fotos como pontos verdes e picos de montanhas do índice peaks_catalog como triângulos vermelhos, com os pontos verdes alinhando-se bem com a trilha da caminhada até o Acampamento Base do Everest." /><h2>Pesquisar casos de uso</h2><p><strong>Busca por nome (texto para imagem):</strong> Este recurso permite que os usuários localizem fotos de picos de montanhas (e até mesmo conceitos abstratos como "bandeiras de oração") usando consultas de texto. Para isso, o texto de entrada é <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">convertido em um vetor de texto</a> usando o SigLIP-2. Para geração robusta de vetores de texto, empregamos a mesma estratégia usada para criar embeddings de texto no índice <code>peaks_catalog</code> : <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">combinando</a> a entrada de texto com um pequeno <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100">conjunto de prompts</a>, subtraindo um<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> vetor de anti-conceito</a> menor e aplicando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">a normalização L2</a> para produzir o vetor de consulta final. Uma <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">consulta</a> kNN é então executada no campo <code>photos.clip_image</code> para recuperar os picos correspondentes principais, com base na similaridade de cosseno para encontrar as imagens mais próximas. Opcionalmente, os resultados da pesquisa podem ser tornados mais relevantes aplicando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">filtros</a> geográficos e de data e/ou um filtro de termo <code>photos.predicted_peaks</code> como parte da consulta (veja exemplos de consulta abaixo). Isso ajuda a excluir picos semelhantes que, na verdade, não estão visíveis durante a trilha.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Como funciona a busca multimodal por nome (texto para imagem) no Elasticsearch." /><p><strong>Consulta Elasticsearch com filtro geográfico:</strong></p>POST photos/_search
{
  "knn": {
    "field": "clip_image",
    "query_vector": [ ... ],
    "k": 60,
    "num_candidates": 2000
  },
  "query": {
    "bool": {
      "filter": [
        { "geo_bounding_box": { "gps": { "top_left": "...", "bottom_right": "..." } } }
      ]
    }
  },
  "_source": ["path","predicted_peaks","gps","shot_time"]
}

Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<p><strong>Busca por imagem (imagem para imagem):</strong> Este recurso permite identificar uma montanha em uma imagem e encontrar outras imagens dessa mesma montanha no álbum de fotos. Quando uma imagem é carregada, ela é processada pelo codificador de imagens SigLIP-2 para gerar um <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">vetor de imagem</a>. Uma <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">busca kNN</a> é então realizada no campo <code>peaks_catalog.text_embed</code> para identificar os nomes de picos que melhor correspondem. Em seguida, um <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">vetor de texto é gerado</a> a partir desses nomes de picos correspondentes, e outra <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">busca kNN</a> é realizada no índice de fotos para localizar as imagens correspondentes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Como funciona a busca multimodal por imagem (imagem para imagem) no Elasticsearch." /><p><strong>Consulta do Elasticsearch:</strong></p><p>Passo 1: Encontre os nomes de pico correspondentes.</p>GET peaks_catalog/_search
{
 "knn": {
   "field": "text_embed",
   "query_vector": [...image-vector... ],
   "k": 3,
   "num_candidates": 500
 },
 "_source": [
   "id",
   "names",
   "latlon",
   "text_embed"
 ]
}


Response (first two documents):
{
 "took": 2,
 "timed_out": false,
 "_shards": {
   "total": 1,
   "successful": 1,
   "skipped": 0,
   "failed": 0
 },
 "hits": {
   "total": {
     "value": 3,
     "relation": "eq"
   },
   "max_score": 0.58039916,
   "hits": [
     {
       "_index": "peaks_catalog",
       "_id": "pumori",
       "_score": 0.58039916,
       "_source": {
         "id": "pumori",
         "names": [
           "Pumori",
           "Pumo Ri"
         ],
         "latlon": {
           "lat": 28.01472,
           "lon": 86.82806
         },
         "text_embed": [
                  ... embeddings...
         ]
       }
     },
     {
       "_index": "peaks_catalog",
       "_id": "kyajo-ri",
       "_score": 0.57942784,
       "_source": {
         "id": "kyajo-ri",
         "names": [
           "Kyajo Ri",
           "Kyazo Ri"
         ],
         "latlon": {
           "lat": 27.909167,
           "lon": 86.673611
         },
         "text_embed": [
           ... embeddings...
         ]
       }
     }
   ]
 }
}<p>Etapa 2: Realize uma busca no índice <code>photos</code> para encontrar as imagens correspondentes (mesma consulta mostrada no caso de uso de busca de texto para imagem):</p>POST photos/_search
{
 "knn": {
   "field": "clip_image",
   "query_vector": [ ...image-vector... ],
   "k": 30,
   "num_candidates": 2000
 },
 "_source": [
   "path",
   "gps",
   "shot_time",
   "predicted_peaks",
   "clip_image"
 ],
 "query": {
   "bool": {
     "filter": [
       {
         "term": {
           "predicted_peaks": "Pumori"
         }
       }
     ]
   }
 }
}


Response (first two documents):
{
 "hits": {
   "total": {
     "value": 56,
     "relation": "eq"
   },
   "max_score": 0.5779596,
   "hits": [
     {
       "_index": "photos",
       "_id": "d01da3a1141981486c3493f6053c79e92a788463",
       "_score": 0.5779596,
       "_source": {
         "path": "IMG_2738.HEIC",
         "predicted_peaks": [
           "Pumori",
           "Kyajo Ri",
           "Khumbila",
           "Nangkartshang",
           "Kongde Ri"
         ],
         "gps": {
           "lat": 27.97116388888889,
           "lon": 86.82331111111111
         },
         "shot_time": "2023-11-03T08:07:13"
       }
     },
     {
       "_index": "photos",
       "_id": "c79d251f07adc5efaedc53561110a7fd78e23914",
       "_score": 0.5766071,
       "_source": {
         "path": "IMG_2761.HEIC",
         "predicted_peaks": [
           "Kyajo Ri",
           "Makalu",
           "Baruntse",
           "Cho Oyu",
           "Khumbila"
         ],
         "gps": {
           "lat": 27.975558333333332,
           "lon": 86.82515
         },
         "shot_time": "2023-11-03T08:51:08"
       }
     }
}<h2>Interface de usuário Streamlit</h2><p>Para integrar tudo, criamos uma interface de usuário Streamlit simples que nos permite executar ambos os casos de uso de pesquisa. A barra lateral esquerda exibe uma lista rolável de picos (agregados de <code>photos.predicted_peaks</code>) com caixas de seleção e um minimapa/filtro geográfico. Na parte superior, há uma caixa <strong>de pesquisa por nome</strong> e um botão <strong>para identificar o usuário a partir de uma foto</strong> enviada. O painel central apresenta uma grade de miniaturas interativa que exibe as pontuações kNN, os indicadores de pico previsto e os horários de captura. Cada imagem inclui um botão <strong>"Ver imagem"</strong> para pré-visualizações em resolução total.</p><p><strong>Pesquisa por upload de imagem:</strong> Prevemos o pico e encontramos picos correspondentes no álbum de fotos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="Uma interface de usuário simples e intuitiva que permite a busca multimodal por texto para imagem e por imagem para os picos do Monte Ama Dablam." /><p><strong>Pesquisa por texto</strong>: Encontre os picos correspondentes no álbum a partir do texto.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="Como pesquisar um pico do Monte Everest usando a busca por texto na biblioteca de picos de montanhas." /><h2>Conclusão</h2><p>Tudo começou com <em>uma pergunta: "Podemos ver as </em><em> fotos</em><em><strong>do Ama Dablam ?"</strong></em> transformou-se em um pequeno sistema <strong>de busca multimodal</strong> funcional. Capturamos fotos brutas da trilha, transformamos em <strong>embeddings SigLIP-2</strong> e usamos <strong>o Elasticsearch</strong> para realizar uma rápida <strong>análise kNN</strong> sobre vetores, além de filtros geográficos/temporais simples para exibir as imagens <em>relevantes</em>. Ao longo do processo, separamos as preocupações com dois índices: um pequeno <code>peaks_catalog</code> de protótipos combinados (para identificação) e um índice escalável <code>photos</code> de vetores de imagem e EXIF (para recuperação). É prático, reproduzível e fácil de expandir.</p><p>Se você quiser ajustá-lo, existem algumas configurações que você pode modificar:</p><ul><li><p><strong>Configurações de tempo de consulta:</strong> <code>k</code> (quantos vizinhos você deseja retornar) e <code>num_candidates</code> (quão ampla a pesquisa antes da pontuação final). Essas configurações são discutidas no blog <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">aqui</a>.</p></li><li><p><strong>Configurações de tempo de indexação:</strong> <code>m</code> (conectividade do grafo) e <code>ef_construction</code> (precisão do tempo de construção vs. memória). Para consultas, experimente também com <code>ef_search</code> — um valor maior geralmente significa melhor recuperação com alguma compensação de latência. Consulte <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">este blog</a> para obter mais detalhes sobre essas configurações.</p></li></ul><p>Olhando para o futuro, modelos/reclassificadores nativos para busca <strong>multimodal</strong> e <strong>multilíngue</strong> chegarão em breve ao ecossistema Elastic, o que deverá tornar a recuperação de imagens/texto e a classificação híbrida ainda mais robustas e prontas para uso.<a href="https://ir.elastic.co/news/news-details/2025/Elastic-Completes-Acquisition-of-Jina-AI-a-Leader-in-Frontier-Models-for-Multimodal-and-Multilingual-Search/default.aspx?utm_source=chatgpt.com"> ir.elastic.co+1</a></p><p>Se você quiser experimentar você mesmo:</p><ul><li><p><strong>Repositório do GitHub:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search"><em>https://github.com/navneet83/multimodal-mountain-peak-search</em></a></p></li><li><p><strong>Guia rápido do Colab:</strong> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb</a></p></li></ul><p>Com isso, nossa jornada chegou ao fim e é hora de voltar para casa. Espero que isso tenha sido útil e, se você quebrar alguma coisa (ou melhorar alguma coisa), adoraria saber o que você mudou.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdce2fff1569d2a8b/6a17da894b055dd1f24320a2/d324d1e1472f1bfbd8f25747f57bdeeb9c7f16b2-1600x1200.png" alt="" />]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multimodal-search-siglip-2-elasticsearch</guid>
    <category><![CDATA[Banco de dados vetorial]]></category>
    <category><![CDATA[Busca híbrida]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Navneet Kumar]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltccb66279debb05f9/6a17da8b63baffe228741b15/ffcf93358a7c5dadcea82faf3de460bf060d003c-1600x1200.png" length="0" type="image/png"/>
    <pubDate>Tue, 04 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Treinamento de modelos LTR no Elasticsearch com listas de julgamento baseadas em dados de comportamento do usuário.]]></title>
    <description><![CDATA[Aprenda como usar dados de UBI (Urban Business Information - Informação Baseada no Usuário) para criar listas de julgamento e automatizar o treinamento de seus modelos de Learning to Rank (LTR) no Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Um grande desafio ao usar modelos <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><em><strong>de aprendizado para classificação</strong></em></a> é criar uma <a href="https://www.elastic.co/search-labs/blog/judgment-lists"><em><strong>lista de julgamentos</strong></em></a> de alta qualidade para treinar o modelo. Tradicionalmente, esse processo envolve uma avaliação <em><strong>manual</strong></em> da relevância do documento de consulta para atribuir uma nota a cada um. Este é um processo lento, que não é escalável e é difícil de manter (imagine ter que atualizar manualmente uma lista com centenas de entradas).</p><p>E se pudéssemos usar interações reais de usuários com nosso aplicativo de busca para criar esses dados de treinamento? Utilizar dados de <a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em><strong>Renda Básica Universal (RBU)</strong></em></a> nos permite fazer exatamente isso. Criar um sistema automático capaz de capturar e usar nossas buscas, cliques e outras interações para gerar uma lista de julgamentos. Esse processo pode ser dimensionado e repetido com muito mais facilidade do que uma interação manual e tende a produzir melhores resultados. Neste blog, exploraremos como podemos consultar dados de UBI armazenados no Elasticsearch para calcular sinais relevantes e gerar um conjunto de dados de treinamento para um modelo <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><em><strong>LTR</strong></em></a> .</p><p><em><strong>Você pode encontrar o experimento completo </strong></em><a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"><em><strong>aqui</strong></em></a><em><strong>.</strong></em></p><h2>Por que os dados de UBI podem ser úteis para treinar seu modelo LTR?</h2><p>Os dados UBI oferecem diversas vantagens em relação à anotação manual:</p><ul><li><p><strong>Volume:</strong> Dado que os dados da Renda Básica Universal (RBU) provêm de interações reais, podemos coletar muito mais dados do que poderíamos gerar manualmente. Isso pressupõe que tenhamos tráfego suficiente para gerar esses dados, é claro.</p></li><li><p><strong>Intenção real do usuário:</strong> Tradicionalmente, uma lista de julgamento manual provém de uma avaliação especializada dos dados disponíveis. Por outro lado, os dados da UBI refletem o comportamento real do usuário. Isso significa que podemos gerar melhores dados de treinamento que aprimorarão a precisão do nosso sistema de busca, pois ele se baseia em como os usuários realmente interagem com o seu conteúdo e encontram valor nele, em vez de suposições teóricas sobre o que deveria ser relevante.</p></li><li><p><strong>Atualizações contínuas:</strong> As listas de julgamentos precisam ser atualizadas periodicamente. Se criarmos essas listas a partir de dados da Renda Básica Universal (RBU), poderemos obter dados atuais que resultarão em listas de julgamento atualizadas.</p></li><li><p><strong>Relação custo-benefício:</strong> Sem o trabalho extra de criar manualmente uma lista de julgamento, o processo pode ser repetido de forma eficiente quantas vezes forem necessárias.</p></li><li><p><strong>Distribuição natural de consultas</strong>: os dados UBI representam consultas reais de usuários, o que pode gerar mudanças mais profundas. Por exemplo, nossos usuários utilizam linguagem natural para pesquisar em nosso sistema? Nesse caso, talvez devêssemos implementar uma abordagem de busca semântica ou de busca híbrida.</p></li></ul><p>No entanto, isso vem com alguns avisos:</p><ul><li><p><strong>Amplificação de viés: </strong>conteúdo popular tem maior probabilidade de receber cliques, simplesmente por ter mais visibilidade. Isso pode acabar amplificando os itens populares e possivelmente ofuscando opções melhores.</p></li><li><p><strong>Cobertura incompleta: </strong>O conteúdo novo não possui interações, portanto, pode ser difícil para ele aparecer em posições elevadas nos resultados. Consultas raras também podem não ter pontos de dados suficientes para criar dados de treinamento significativos.</p></li><li><p><strong>Variações sazonais:</strong> Se você espera que o comportamento do usuário mude drasticamente ao longo do tempo, os dados históricos podem não lhe dizer muito sobre o que é um bom resultado.</p></li><li><p><strong>Ambiguidade da tarefa:</strong> um clique nem sempre garante que o usuário encontrou o que procurava.</p></li></ul><h2>Cálculo das notas</h2><h3>Notas para treinamento LTR</h3><p>Para treinar modelos LTR, precisamos fornecer alguma representação numérica da relevância de um documento para uma consulta. Em nossa implementação, esse número é uma pontuação contínua que varia de 0,0 a 5,0+, onde pontuações mais altas indicam maior relevância.</p><p>Para demonstrar como funciona esse sistema de avaliação, considere este exemplo criado manualmente:</p><p>Consulta</p><p>Conteúdo do documento</p><p>Nota</p><p>Explicação</p><p>"Melhor receita de pizza"</p><p>"Receita autêntica de massa de pizza italiana com fotos passo a passo"</p><p>4.0</p><p>Altamente relevante, exatamente o que o usuário está procurando.</p><p>"Melhor receita de pizza"</p><p>"História da Pizza na Itália"</p><p>1.0</p><p>Ainda que relacionado ao assunto, trata-se de pizza, mas não é uma receita.</p><p>"Melhor receita de pizza"</p><p>"Receita rápida de pizza em 15 minutos para iniciantes"</p><p>3.0</p><p>Relevante, um bom resultado, mas talvez não chegue a ser a "melhor" receita.</p><p>"Melhor receita de pizza"</p><p>"Guia de Manutenção Automotiva"</p><p>0,0</p><p>Completamente irrelevante, sem qualquer relação com a pergunta.</p><p>Como podemos ver aqui, a nota é uma representação numérica da relevância de um documento para nossa consulta de exemplo: "melhor receita de pizza". Com essas pontuações, nosso modelo LTR pode aprender quais documentos devem ser apresentados em posições mais altas nos resultados.</p><p>A forma de calcular as notas é o ponto central do nosso conjunto de dados de treinamento. Existem <a href="https://www.elastic.co/search-labs/blog/judgment-lists">várias abordagens</a> para fazer isso, cada uma com seus pontos fortes e fracos. Por exemplo, poderíamos atribuir uma pontuação binária de 1 para relevante e 0 para irrelevante, ou poderíamos simplesmente contar o número de cliques em um documento resultante para cada consulta.</p><p>Neste post do blog, usaremos uma abordagem diferente, <em><strong>considerando o comportamento do usuário como entrada e calculando uma nota como saída</strong></em>. Também corrigiremos o viés que pode ocorrer devido ao fato de que resultados mais altos tendem a receber mais cliques, independentemente da relevância do documento.</p><h2>Cálculo das notas - Algoritmo COEC</h2><p>O algoritmo COEC (<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">Clicks over Expected Clicks</a>) é uma metodologia para calcular notas de avaliação a partir dos cliques do usuário.
Como já mencionamos, os usuários tendem a clicar nos resultados posicionados mais acima, mesmo que o documento não seja o mais relevante para a consulta; isso é chamado de <a href="https://eugeneyan.com/writing/position-bias/">Viés de Posição</a>. A ideia central por trás do uso do algoritmo COEC é que nem todos os cliques têm a mesma importância; um clique em um documento na posição 10 indica que o documento é muito mais relevante para a consulta do que um clique em um documento na posição 1. Citando o artigo de pesquisa sobre o algoritmo COEC (link acima):</p><p><em>“É sabido que a taxa de cliques (CTR) dos resultados de pesquisa ou anúncios diminui significativamente dependendo da posição dos resultados.”</em></p><p>Você pode ler mais sobre viés de posição <a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">aqui</a>.</p><p>Para resolver isso com o algoritmo COEC, seguimos estes passos:</p><p><strong>1. Estabelecer linhas de base de posicionamento:</strong> Calculamos a taxa de cliques (CTR) para cada posição de pesquisa de 1 a 10. Isso significa que determinamos qual a porcentagem de usuários que normalmente clicam na posição 1, na posição 2 e assim por diante. Esta etapa captura a tendência natural de posicionamento dos usuários.

Calculamos a CTR usando:</p><p>Onde:</p><p>p = Posição. De 1 a 10
 Cp = Total de cliques (em qualquer documento) na posição p em todas as consultas
 Ip = Impressões totais: Quantas vezes um documento apareceu na posição p em todas as consultas.</p><p>Aqui, esperamos que posições mais altas recebam mais cliques.</p><p><strong>2.</strong> <strong>Calcular os cliques esperados (CE)</strong>:</p><p>Essa métrica estabelece quantos cliques um documento "deveria" ter recebido com base nas posições em que apareceu e na taxa de cliques (CTR) dessas posições. Calculamos o EC usando:</p><p>Onde:</p><p>Qd = Todas as consultas em que o documento d apareceu
 pos(d,q) = Posição do documento d nos resultados da consulta q</p><p>3. <strong>Contagem de cliques reais: </strong>Contamos o total real de cliques que um documento recebeu em todas as consultas em que apareceu, daqui em diante denominado <strong>A(d).</strong></p><p>4. <strong>Calcule a pontuação COEC:</strong> Esta é a razão entre os cliques reais (A(d)) e os cliques esperados (EC(d)):</p><p>Essa métrica normaliza o viés de posição da seguinte forma:</p><ul><li><p>Uma pontuação de 1,0 significa que o documento teve o desempenho exatamente como esperado, considerando as posições em que foi apresentado.</p></li><li><p>Uma pontuação acima de 1,0 significa que o documento teve um desempenho melhor do que o esperado, considerando suas posições. Portanto, este documento é mais relevante para a consulta.</p></li><li><p>Uma pontuação inferior a 1,0 significa que o documento teve um desempenho pior do que o esperado, considerando suas posições. Portanto, este documento é menos relevante para a consulta.</p></li></ul><p><em><strong>O resultado final é uma nota que reflete o que os usuários procuram, levando em consideração as expectativas baseadas na posição, extraídas de interações reais com nosso sistema de busca.</strong></em></p><h2>Implementação técnica</h2><p>Criaremos um script para gerar uma lista de julgamentos para treinar um modelo LTR.</p><p>A entrada para este script são os dados UBI indexados no Elastic (consultas e eventos).</p><p>O resultado é uma lista de julgamentos em um arquivo CSV gerado a partir desses documentos de Renda Básica Universal (RBU) usando o algoritmo COEC. Essa lista de julgamentos pode ser usada com <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">o Eland</a> para extrair características relevantes e treinar um modelo LTR.</p><h3>Início rápido</h3><p>Para gerar uma lista de julgamentos a partir dos dados de exemplo deste blog, você pode seguir estes passos:</p><p>1. Clone o repositório:</p>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog<p>2. Instale as bibliotecas necessárias</p><p>Para este script, precisamos das seguintes bibliotecas:</p><ul><li><p><em>pandas</em>: para salvar a lista de julgamentos</p></li><li><p><em>elasticsearch</em>: Para obter os dados UBI da nossa implementação do Elasticsearch.</p></li></ul><p>Também precisamos do Python 3.11.</p>pip install -r requirements.txt<p>3. Atualize as variáveis de ambiente para sua implantação do Elasticsearch em um <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">arquivo .env.</a></p><ul><li><p>ES_HOST</p></li><li><p>API_KEY</p></li></ul><p>Para adicionar as variáveis de ambiente, utilize:</p>source .env<p>4. Crie os índices ubi_queries e ubi_events e carregue os dados de exemplo. Execute o arquivo setup.py:</p>python setup.py<p>5. Execute o script Python:</p>python judgement_list-generator.py<p>Seguindo esses passos, você deverá ver um novo arquivo chamado judgment_list.csv com a seguinte aparência:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94317eda8f7af194/6a170aa46f7f04542f914821/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" alt="" /><p>Este script calcula as notas aplicando o algoritmo COEC discutido anteriormente, utilizando a função <strong>calculate_relevance_grade()</strong> mostrada abaixo.</p><h2>Arquitetura de dados</h2><h3>Consultas Ubi</h3><p>Nosso índice de consultas UBI contém informações sobre as consultas executadas em nosso sistema de busca. Este é um documento de exemplo:</p>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }<p>Aqui podemos ver dados do usuário (client_id), dos resultados da consulta (query_response_object_ids) e da própria consulta (timestamp, user_query).</p><h3>Eventos de clique Ubi</h3><p>Nosso índice ubi_events contém dados de cada vez que um usuário clicou em um documento nos resultados. Este é um documento de exemplo:</p>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }<h2>Script de geração de lista de julgamentos</h2><h3>Visão geral do roteiro</h3><p>Este script automatiza a geração da lista de julgamento usando dados UBI de consultas e eventos de clique armazenados no Elasticsearch. Ele executa estas tarefas:</p><ul><li><p>Busca e processa os dados UBI no Elasticsearch.</p></li><li><p>Correlaciona eventos de UBI com suas consultas.</p></li><li><p>Calcula o CTR para cada posição.</p></li><li><p>Calcula os cliques esperados (CE) para cada documento.</p></li><li><p>Contabiliza os cliques reais em cada documento.</p></li><li><p>Calcula a pontuação COEC para cada par consulta-documento.</p></li><li><p>Gera uma lista de julgamentos e a grava em um arquivo CSV.</p></li></ul><p>Vamos analisar cada função:</p><h3>conectar_ao_elasticsearch()</h3>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None<p>Essa função retorna um objeto cliente Elasticsearch usando o host e a chave da API.</p><h3>buscar_dados_ubi()</h3>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -&gt; Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data<p>Esta função é a camada de extração de dados; ela se conecta ao Elasticsearch para buscar consultas UBI usando uma consulta match_all e filtra os eventos UBI para obter apenas os eventos 'CLICK_THROUGH'.</p><h3>processar_dados_ubi()</h3>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -&gt; pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df<p>Esta função é responsável pela geração da lista de julgamentos. O processamento dos dados UBI começa por associar eventos e consultas UBI. Em seguida, chama a função calculate_relevance_grade() para cada par documento-consulta, a fim de obter as entradas para a lista de julgamento. Por fim, retorna a lista resultante como um dataframe do pandas.</p><h3>calcular_nota_de_relevância()</h3>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -&gt; float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position &gt; 3:
                return 4.0
            elif position &gt;= 1 and position &lt;= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position &lt;= 5:
                return 2.0
            elif position &gt;= 6 and position &lt;= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position &lt;= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] &gt; 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position &lt;= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks &gt; 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score<p>Esta é a função que implementa o algoritmo COEC. Ele calcula a CTR para cada posição, depois compara os cliques reais para um par documento-consulta e, finalmente, calcula a pontuação COEC real para cada um.</p><h3>gerar_estatísticas_de_julgamento()</h3>def generate_judgment_statistics(df: pd.DataFrame) -&gt; Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() &gt; 0 else 0,
        'queries_with_clicks': len(df[df['grade'] &gt; 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] &gt; 1]) / len(df) if len(df) &gt; 0 else 0
    }
    return stats<p>Ele gera estatísticas úteis a partir da lista de julgamentos, como o total de consultas, o total de documentos únicos ou a distribuição de notas. Esta informação é meramente informativa e não altera a lista de julgamentos resultante.</p><h2>Resultados e impacto</h2><p>Seguindo as instruções da seção Início rápido, você deverá obter um arquivo CSV contendo uma lista de julgamentos com 320 entradas (você pode ver um <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">exemplo de saída</a> no repositório). Com estes campos:</p><ul><li><p>qid: ID único da consulta</p></li><li><p>docid: identificador único para um documento resultante</p></li><li><p>nota: a nota calculada para o par consulta-documento.</p></li><li><p>consulta: A consulta do usuário</p></li></ul><p> Vejamos os resultados da pesquisa “receitas italianas”:</p><p>qid</p><p>docid</p><p>nota</p><p>Consulta</p><p>q1-receitas-italianas</p><p>receita_básica_de_massa</p><p>0,0</p><p>receitas italianas</p><p>q1-receitas-italianas</p><p>receita_pizza_margherita</p><p>3,333333</p><p>receitas italianas</p><p>q1-receitas-italianas</p><p>guia_de_receitas_de_risoto</p><p>10.0</p><p>receitas italianas</p><p>q1-receitas-italianas</p><p>receita_croissant_francês</p><p>0,0</p><p>receitas italianas</p><p>q1-receitas-italianas</p><p>receita_paella_espanhola</p><p>0,0</p><p>receitas italianas</p><p>q1-receitas-italianas</p><p>receita_moussaka_grega</p><p>1,875</p><p>receitas italianas</p><p>Podemos ver pelos resultados que para a consulta “receitas italianas”:</p><ul><li><p>A receita de risoto é definitivamente o melhor resultado para a pesquisa, recebendo 10 vezes mais cliques do que o esperado.</p></li><li><p>A pizza Margherita também é um ótimo resultado.</p></li><li><p>A mousaka grega (surpreendentemente) também obteve um bom resultado e teve um desempenho melhor do que sua posição nos resultados sugeriria. Isso significa que alguns usuários que procuravam receitas italianas se interessaram por esta receita em vez desta. Talvez esses usuários estejam interessados em pratos mediterrâneos em geral. Em suma, isso nos indica que esse poderia ser um bom resultado para ser apresentado entre as outras duas partidas "melhores" que discutimos anteriormente.</p></li></ul><h2>Conclusão</h2><p>Utilizar dados UBI nos permite automatizar o treinamento de modelos LTR, criando listas de julgamento de alta qualidade a partir de nossos próprios usuários. Os dados do UBI fornecem um grande conjunto de dados que reflete como nosso sistema de busca está sendo usado. Ao usar o algoritmo COEC para gerar as notas, levamos em consideração o viés inerente e, ao mesmo tempo, refletimos o que um usuário considera um resultado melhor. O método descrito aqui pode ser aplicado a casos de uso reais para proporcionar uma melhor experiência de busca que evolua com as tendências reais de uso.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Elastic Cloud Hosted]]></category>
    <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt037eb2f4d380fe65/6a170aa67d8d67397170e6e6/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Pesquisa geoespacial do Elasticsearch com ES|QL]]></title>
    <description><![CDATA[Pesquisa geoespacial na linguagem de consulta Elasticsearch (ES|QL). O Elasticsearch possui recursos poderosos de busca geoespacial, que agora estão chegando ao ES|QL para uma facilidade de uso drasticamente aprimorada e familiaridade com o OGC.]]></description>
    <content:encoded><![CDATA[<p>O Elasticsearch possui <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geospatial-analysis.html">recursos poderosos de busca e análise geoespacial</a> há muitos anos, mas a API era bastante diferente daquilo a que os usuários típicos de SIG estavam acostumados. No último ano <a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">, adicionamos a linguagem de consulta ES|QL</a>, uma linguagem de consulta encadeada tão fácil, ou até mais fácil, que o SQL. É particularmente adequado para os casos de uso de busca, segurança e observabilidade nos quais o Elastic se destaca. Também estamos adicionando suporte para pesquisa e análise geoespacial no ES|QL, tornando-o muito mais fácil de usar, especialmente para usuários vindos das comunidades SQL ou <a href="https://en.wikipedia.org/wiki/Geographic_information_system">GIS</a> .</p><p>O Elasticsearch 8.12 e 8.13 trouxeram suporte básico para tipos geoespaciais ao ES|QL. Isso foi significativamente aprimorado com a adição de recursos de busca geoespacial na versão 8.14. Mais importante ainda, esse suporte foi projetado para estar em estrita conformidade com o padrão <a href="https://en.wikipedia.org/wiki/Simple_Features">Simple Feature Access</a> do <a href="https://en.wikipedia.org/wiki/Open_Geospatial_Consortium">Open Geospatial Consortium (OGC),</a> usado por outros bancos de dados espaciais como o PostGIS, tornando-o muito mais fácil de usar para especialistas em SIG familiarizados com esses padrões.</p><p>Neste blog, mostraremos como usar o ES|QL para realizar buscas geoespaciais e como ele se compara aos seus equivalentes em SQL e Query DSL. Também mostraremos como usar o ES|QL para realizar junções espaciais e como visualizar os resultados no Kibana Maps. Note que todos os recursos descritos aqui estão em "prévia técnica" e gostaríamos muito de receber seu feedback sobre como podemos melhorá-los.</p><h2>Pesquisa de dados geoespaciais</h2><p>Vamos começar com um exemplo de consulta:</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>Esta função realiza uma busca por quaisquer polígonos de limites urbanos que se intersectem com um polígono de busca retangular ao redor do Aeroporto Internacional de Sanya Phoenix (SYX).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3897df6bed5d6061/6a17d7c2abe0f29eccdfe861/e48bac8f246c8842f2ea97ddd54910045262aeb1-1440x808.png" alt="Pesquisa Geoespacial ESQL" /><p>Em um conjunto de dados de exemplo contendo aeroportos, cidades e limites urbanos, esta busca encontra o polígono de interseção e retorna os campos desejados do documento correspondente:</p><p>abreviar</p><p>aeroporto</p><p>região</p><p>cidade</p><p>localização da cidade</p><p>SYX</p><p>Sanya Phoenix Int'l</p><p>天涯区</p><p>Sanya</p><p>PONTO(109,5036 18,2533)</p><p>Isso foi fácil! Agora compare isso com a DSL de consulta clássica do Elasticsearch para a mesma consulta:</p>GET /airport_city_boundaries/_search
{
  "_source": ["abbrev", "airport", "region", "city", "city_location"],
  "query": {
    "geo_shape": {
      "city_boundary": {
        "shape": {
          "type": "polygon",
          "coordinates" : [[
            [109.4, 18.1],
            [109.6, 18.1],
            [109.6, 18.3],
            [109.4, 18.3],
            [109.4, 18.1]
          ]]
        }
      }
    }
  }
}
<p>Ambas as consultas são razoavelmente claras em sua intenção, mas a consulta ES|QL se assemelha bastante ao SQL. A mesma consulta no PostGIS se parece com isto:</p>SELECT abbrev, airport, region, city, city_location
FROM airport_city_boundaries
WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
);
<p>Relembre o exemplo em ES|QL. São muito parecidos, não é?</p>FROM airport_city_boundaries
| WHERE ST_INTERSECTS(
      city_boundary,
      "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
  )
| KEEP abbrev, airport, region, city, city_location
<p>Constatamos que os usuários existentes da API Elasticsearch consideram o ES|QL muito mais fácil de usar. Agora, esperamos que os usuários atuais de SQL, especialmente os usuários de SQL Espacial, achem o ES|QL muito familiar ao que já estão acostumados a ver.</p><h4>Por que não usar SQL?</h4><p>E quanto ao Elasticsearch SQL? Já existe há algum tempo e possui algumas funcionalidades geoespaciais. No entanto, o Elasticsearch SQL foi escrito como um wrapper sobre a API de consulta original, o que significa que apenas as consultas que podiam ser transpiladas para a API original eram suportadas. ES|QL não possui essa limitação. Por ser uma pilha de tecnologias completamente nova, permite muitas otimizações que não eram possíveis em SQL. Nossos testes de desempenho mostram que o ES|QL é <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/esql/nightly/default/6M">frequentemente mais rápido que a API de consulta</a>, principalmente em operações de agregação!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bda964c8b24e36/6a17d7c3e3179155d22d568a/b8b6c2b2e45850d832805ed1e71e522f4955f53c-1440x813.png" alt="referência de interseção de polígonos" /><h2>Diferenças em relação ao SQL</h2><p>Claramente, pelo exemplo anterior, o ES|QL é de certa forma semelhante ao SQL, mas existem algumas diferenças importantes. Por exemplo, ES|QL é uma linguagem de consulta encadeada, começando com um comando de origem como FROM e, em seguida, encadeando todos os comandos subsequentes com o caractere pipe |. Isso torna muito fácil entender como cada comando recebe uma tabela de dados e realiza alguma ação nessa tabela, como filtrar com <code>WHERE</code>, adicionar colunas com <code>EVAL</code> ou realizar agregações com <code>STATS</code>. Em vez de começar com <code>SELECT</code> para definir as colunas de saída finais, pode haver um ou mais comandos <code>KEEP</code> , com o último especificando os resultados de saída finais. Essa estrutura simplifica o raciocínio sobre a consulta.</p><p>Analisando o comando <code>WHERE</code> no exemplo acima, podemos ver que ele é bastante semelhante ao exemplo do PostGIS:</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    "POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))"::geo_shape
)
<p><em>PostGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    'SRID=4326;POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'::geometry
)
<p>Além da diferença nos caracteres de aspas da string, a maior diferença está em como convertemos a string para um tipo espacial. No PostGIS, usamos o sufixo <code>::geometry</code> , enquanto no ES|QL, usamos o sufixo <code>::geo_shape</code> . Isso ocorre porque o ES|QL é executado dentro do Elasticsearch e o operador de conversão de tipo <code>::</code> pode ser usado para converter uma string em qualquer um dos <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#_supported_types">tipos ES|QL suportados</a>, neste caso, um <code>geo_shape</code>. Além disso, os tipos <code>geo_shape</code> e <code>geo_point</code> no Elasticsearch implicam o sistema de coordenadas espaciais conhecido como WGS84, mais comumente referido usando o número SRID 4326. No PostGIS, isso precisa ser explícito, daí o uso do prefixo <code>SRID=4326;</code> na string WKT. Se esse prefixo for removido, o SRID será definido como 0, que é mais parecido com os tipos do Elasticsearch <code>cartesian_point</code> e <code>cartesian_shape</code>, que não estão vinculados a nenhum sistema de coordenadas específico.</p><p>Tanto o ES|QL quanto o PostGIS também fornecem sintaxe para funções de conversão de tipo:</p><p><em>ES|QL</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    TO_GEOSHAPE("POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))")
)
<p><em>PostGIS</em></p>WHERE ST_INTERSECTS(
    city_boundary,
    ST_SetSRID(
      ST_GeomFromText('POLYGON((109.4 18.1, 109.6 18.1, 109.6 18.3, 109.4 18.3, 109.4 18.1))'),
      4326
    )
)
<h2>Funções OGC</h2><p>O Elasticsearch 8.14 introduz as seguintes quatro funções de pesquisa espacial OGC:</p><p>ES|QL</p><p>PostGIS</p><p>Descrição</p><p>ST_INTERSETOS</p><p>ST_Interseções</p><p>Retorna verdadeiro se duas geometrias se intersectam e falso caso contrário.</p><p>ST_DISJOINT</p><p>ST_Disjunto</p><p>Retorna verdadeiro se as duas geometrias não se intersectarem e falso caso contrário. O inverso de ST_INTERSETOS.</p><p>ST_CONTÉM</p><p>ST_Contém</p><p>Retorna verdadeiro se uma geometria contém outra, e falso caso contrário.</p><p>ST_DENTRO</p><p>ST_Dentro</p><p>Retorna verdadeiro se uma geometria estiver dentro de outra, e falso caso contrário. O inverso de ST_CONTAINS.</p><p>Essas funções se comportam de maneira semelhante às suas contrapartes no PostGIS e são usadas da mesma forma. Por exemplo, <code>ST_INTERSECTS</code> retorna verdadeiro se duas geometrias se intersectam e falso caso contrário. Se você seguir os links de documentação na tabela acima, poderá notar que todos os exemplos ES|QL estão dentro de uma cláusula <code>WHERE</code> após uma cláusula <code>FROM</code> , enquanto todos os exemplos PostGIS estão usando geometrias literais. Na verdade, ambas as plataformas suportam o uso das funções em qualquer parte da consulta onde façam sentido.</p><p>O primeiro exemplo na documentação do PostGIS para <code>ST_INTERSECTS</code> é:</p>SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);
<p>O equivalente em ES|QL seria:</p>ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)
<p>Observe que não especificamos o SRID no exemplo do PostGIS. Isso ocorre porque no PostGIS, ao usar o tipo <code>geometry</code> , todos os cálculos são feitos em um sistema de coordenadas planas e, portanto, se ambas as geometrias tiverem o mesmo SRID, não importa qual seja o SRID. No Elasticsearch, isso também é verdade para a maioria das funções, no entanto, existem exceções onde <code>geo_shape</code> e <code>geo_point</code> usam cálculos esféricos, como veremos no próximo blog sobre pesquisa de distância espacial.</p><h2>Versatilidade ES|QL</h2><p>Então, vimos exemplos acima de uso de funções espaciais em cláusulas <code>WHERE</code> e em comandos <code>ROW</code> . Em que outro lugar fariam sentido? Um local muito útil é no comando <code>EVAL</code> . Este comando permite avaliar uma expressão e retornar o resultado. Por exemplo, vamos determinar se os centroides de todos os aeroportos agrupados por seus nomes de país estão dentro de um limite que delimita o país:</p>FROM airports
| EVAL in_uk = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL in_iceland = ST_INTERSECTS(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| EVAL within_uk = ST_WITHIN(location, TO_GEOSHAPE("POLYGON((1.2305 60.8449, -1.582 61.6899, -10.7227 58.4017, -7.1191 55.3291, -7.9102 54.2139, -5.4492 54.0078, -5.2734 52.3756, -7.8223 49.6676, -5.0977 49.2678, 0.9668 50.5134, 2.5488 52.1065, 2.6367 54.0078, -0.9668 56.4625, 1.2305 60.8449))"))
| EVAL within_iceland = ST_WITHIN(location, TO_GEOSHAPE("POLYGON ((-25.4883 65.5312, -23.4668 66.7746, -18.4131 67.4749, -13.0957 66.2669, -12.3926 64.4159, -20.1270 62.7346, -24.7852 63.3718, -25.4883 65.5312))"))
| STATS centroid = ST_CENTROID_AGG(location), count=COUNT() BY in_uk, in_iceland, within_uk, within_iceland
| SORT count ASC
<p>Os resultados são os esperados: o centroide dos aeroportos do Reino Unido está dentro das fronteiras do Reino Unido, e não dentro das fronteiras da Islândia, e vice-versa.</p><p>centroide</p><p>Contagem</p><p>no Reino Unido</p><p>na Islândia</p><p>dentro do Reino Unido</p><p>dentro da Islândia</p><p>PONTO (-21,946634463965893 64.13187285885215)</p><p>1</p><p>falso</p><p>verdadeiro</p><p>falso</p><p>verdadeiro</p><p>PONTO (-2,597342072712148 54,33551226578214)</p><p>17</p><p>verdadeiro</p><p>falso</p><p>verdadeiro</p><p>falso</p><p>PONTO (0,04453958108176276 23,74658354606057)</p><p>873</p><p>falso</p><p>falso</p><p>falso</p><p>falso</p><p>Na verdade, essas funções podem ser usadas em qualquer parte da consulta onde sua assinatura faça sentido. Todas elas recebem dois argumentos, que podem ser um objeto espacial literal ou um campo de um tipo espacial, e todas retornam um valor booleano. Uma consideração importante é que o sistema de referência de coordenadas (SRC) das geometrias deve coincidir, caso contrário, será retornado um erro. Isso significa que você não pode misturar os tipos <code>geo_shape</code> e <code>cartesian_shape</code> na mesma chamada de função. Você pode, no entanto, misturar os tipos <code>geo_point</code> e <code>geo_shape</code> , já que o tipo <code>geo_point</code> é um caso especial do tipo <code>geo_shape</code> e ambos compartilham o mesmo sistema de referência de coordenadas. A documentação de cada uma das funções definidas acima lista as combinações de tipos suportadas.</p><p>Além disso, qualquer um dos argumentos pode ser um literal espacial ou um campo, em qualquer ordem. Você pode até especificar dois campos, dois literais, um campo e um literal, ou um literal e um campo. O único requisito é que os tipos sejam compatíveis. Por exemplo, esta consulta compara dois campos no mesmo índice:</p>FROM airport_city_boundaries
| EVAL in_city = ST_INTERSECTS(city_location, city_boundary)
| STATS count=COUNT(*) BY in_city
| SORT count ASC
| EVAL cardinality = CASE(count &lt; 10, "very few", count &lt; 100, "few", "many")
| KEEP cardinality, count, in_city
<p>A consulta basicamente pergunta se a localização da cidade está dentro dos limites da cidade, o que geralmente deve ser verdade, mas sempre há exceções:</p><p>cardinalidade</p><p>Contagem</p><p>na cidade</p><p>alguns</p><p>29</p><p>falso</p><p>muitos</p><p>740</p><p>verdadeiro</p><p>Uma questão muito mais interessante seria saber se a localização do aeroporto está dentro dos limites da cidade que ele serve. No entanto, a localização do aeroporto reside em um índice diferente daquele que contém os limites da cidade. Isso requer um método para consultar e correlacionar dados de forma eficaz a partir desses dois índices separados.</p><h2>Junções espaciais</h2><p>ES|QL não suporta comandos <code>JOIN</code> , mas você pode obter um caso especial de junção usando o<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"> comando</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"><code>ENRICH</code></a> , que se comporta de forma semelhante a uma 'junção esquerda' em SQL. Este comando funciona de forma semelhante a uma 'junção à esquerda' em SQL, permitindo enriquecer os resultados de um índice com dados de outro índice com base em uma relação espacial entre os dois conjuntos de dados.</p><p>Por exemplo, vamos enriquecer os resultados de uma tabela de aeroportos com informações adicionais sobre a cidade que eles atendem, encontrando o limite da cidade que contém a localização do aeroporto e, em seguida, realizar algumas análises estatísticas dos resultados:</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
| MV_EXPAND city_boundary
| EVAL boundary_wkt_length = LENGTH(TO_STRING(city_boundary))
| STATS centroid = ST_CENTROID_AGG(location), count = COUNT(city_location), min_wkt = MIN(boundary_wkt_length), max_wkt = MAX(boundary_wkt_length) BY region
| SORT count DESC
| LIMIT 5
<p>Isso retorna as 5 principais regiões com o maior número de aeroportos, juntamente com o centroide de todos os aeroportos que possuem regiões correspondentes e o intervalo de comprimento da representação WKT dos limites das cidades dentro dessas regiões:</p><p>centroide</p><p>Contagem</p><p>min_wkt</p><p>max_wkt</p><p>região</p><p>PONTO (-32,56093470960719 32,598117914802714)</p><p>90</p><p>207</p><p>207</p><p>nulo</p><p>PONTO (-73,94515332765877 40,70366442203522)</p><p>9</p><p>438</p><p>438</p><p>Cidade de Nova York</p><p>PONTO (-83.10398317873478 42.300230911932886)</p><p>9</p><p>473</p><p>473</p><p>Detroit</p><p>PONTO (-156.3020245861262 20,176383580081165)</p><p>5</p><p>307</p><p>803</p><p>Havaí</p><p>PONTO (-73,88902732171118 45.57078813901171)</p><p>4</p><p>837</p><p>837</p><p>Montreal</p><p>Então, o que realmente aconteceu aqui? Onde ocorreu o suposto <code>JOIN</code> ? O ponto crucial da questão reside no comando <code>ENRICH</code> :</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
<p>Este comando instrui o Elasticsearch a enriquecer os resultados recuperados do índice <code>airports</code> e a realizar uma junção <code>intersects</code> entre o campo <code>city_location</code> do índice original e o campo <code>city_boundary</code> do índice <code>airport_city_boundaries</code> , que usamos em alguns exemplos anteriores. Mas algumas dessas informações não estão claramente visíveis nesta consulta. O que vemos é o nome de uma política de enriquecimento <code>city_boundaries</code> e a informação em falta está encapsulada na definição dessa política.</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}
<p>Aqui podemos ver que ele executará uma consulta <code>geo_match</code> (<code>intersects</code> é o padrão), o campo para comparar é <code>city_boundary</code> e os <code>enrich_fields</code> são os campos que queremos adicionar ao documento original. Um desses campos, o <code>region</code> foi na verdade usado como chave de agrupamento para o comando <code>STATS</code> , algo que não poderíamos ter feito sem essa capacidade de 'junção à esquerda'. Para obter mais informações sobre políticas de enriquecimento, consulte a <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">documentação de enriquecimento</a>. Ao ler esses documentos, você notará que eles descrevem o uso de índices de enriquecimento para enriquecer dados no momento da indexação, configurando pipelines de ingestão. Isso não é necessário para ES|QL, pois o comando <code>ENRICH</code> funciona no momento da consulta. Basta preparar o índice de enriquecimento com os dados necessários e a política de enriquecimento e, em seguida, usar o comando <code>ENRICH</code> em suas consultas ES|QL.</p><p>Você também pode notar que a região mais comumente encontrada foi <code>null</code>. O que isso poderia implicar? Lembre-se de que comparei este comando a um 'left join' em SQL, o que significa que se nenhum limite de cidade correspondente for encontrado para um aeroporto, o aeroporto ainda será retornado, mas com valores <code>null</code> para os campos do índice <code>airport_city_boundaries</code> . Descobriu-se que havia 89 aeroportos que não encontraram nenhum <code>city_boundary</code> correspondente e um aeroporto com uma correspondência onde o campo <code>region</code> era <code>null</code>. Isso levou a uma contagem de 90 aeroportos sem nenhum <code>region</code> nos resultados. Outro detalhe interessante é a necessidade do comando <code>MV_EXPAND</code> . Isso é necessário porque o comando <code>ENRICH</code> pode retornar vários resultados para cada linha de entrada e <code>MV_EXPAND</code> ajuda a separar esses resultados em várias linhas, uma para cada resultado. Isso também esclarece por que "Havaí" mostra resultados diferentes <code>min_wkt</code> e <code>max_wkt</code> : havia várias regiões com o mesmo nome, mas limites diferentes.</p><h2>Mapas do Kibana</h2><p>O Kibana adicionou suporte para Spatial ES|QL no aplicativo Maps. Isso significa que agora você pode usar o ES|QL para pesquisar dados geoespaciais no Elasticsearch e visualizar os resultados em um mapa.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Camadas do Kibana ES|QL" /><p>Existe uma nova opção de camada no menu "Adicionar camadas", chamada "ES|QL". Assim como todos os recursos geoespaciais descritos até agora, este está em "prévia técnica". Selecionar esta opção permite adicionar uma camada ao mapa com base nos resultados de uma consulta ES|QL. Por exemplo, você poderia adicionar uma camada ao mapa que mostrasse todos os aeroportos do mundo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="Kibana ES|QL - Aeroportos" /><p>Ou você poderia adicionar uma camada que mostre os polígonos do índice <code>airport_city_boundaries</code> , ou ainda melhor, que tal aquela consulta complexa <code>ENRICH</code> acima que gera estatísticas de quantos aeroportos existem em cada região?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - Estatísticas da região" /><h2>O que vem a seguir?</h2><p>Você deve ter notado que em dois dos exemplos acima incluímos mais uma função espacial <code>ST_CENTROID_AGG</code>. Esta é uma função de agregação usada no comando <code>STATS</code> e a primeira de muitas funcionalidades de análise espacial que planejamos adicionar ao ES|QL. Vamos publicar um artigo sobre isso quando tivermos mais informações para mostrar!</p><p>Antes disso, gostaríamos de falar mais sobre um recurso particularmente interessante no qual trabalhamos: a capacidade de realizar buscas por distância espacial, um dos recursos de busca espacial mais utilizados do Elasticsearch. Você consegue imaginar como seria a sintaxe para buscas por distância? Talvez semelhante a uma função OGC? Fique ligado(a) no próximo post desta série para descobrir!</p><p>Alerta de spoiler: o Elasticsearch 8.15 acaba de ser lançado e inclui pesquisa por distância espacial com ES|QL!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Craig Taverner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd05627be20e89dfb/6a17d7c6414c640256944fdb/de886289dcb56494920875303b622b030b9b810f-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Avaliação da relevância de busca - parte 1: o benchmark BEIR]]></title>
    <description><![CDATA[Aprenda a avaliar seu sistema de busca no contexto de uma melhor compreensão do benchmark BEIR, com dicas e técnicas para aprimorar seus processos de avaliação de busca.]]></description>
    <content:encoded><![CDATA[<p>Este é o primeiro de uma série de posts que discutem como pensar a avaliação dos seus próprios sistemas de busca a partir de uma compreensão mais aprofundada do benchmark BEIR. Vamos apresentar dicas e técnicas específicas para melhorar os processos de avaliação de busca nesse contexto. Também destacaremos armadilhas comuns que tornam a avaliação menos confiável. Por fim, observamos que os LLMs oferecem uma nova e poderosa ferramenta no arsenal de engenheiros de busca e mostraremos, com exemplos, como usá-los para auxiliar na avaliação de busca.</p><h2>Entendendo o benchmark BEIR na avaliação de relevância de busca</h2><p>Para melhorar qualquer sistema, é preciso conseguir medir o quão bem ele está funcionando. No contexto de busca, o <a href="https://arxiv.org/abs/2104.08663">BEIR</a> (ou, de forma equivalente, a seção de recuperação da tabela de classificação do <a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB</a>) é considerado o “santo graal” pela comunidade de recuperação de informação, e isso não é por acaso. Trata-se de um benchmark muito bem estruturado, com conjuntos de dados variados que abrangem diferentes tarefas. Mais especificamente, as seguintes áreas são contempladas:</p><ul><li><p>Recuperação de argumentos (ArguAna, Touche2020)</p></li><li><p>QA de domínio aberto (HotpotQA, Natural Questions, FiQA)</p></li><li><p>Recuperação de passagens (MSMARCO)</p></li><li><p>Recuperação de perguntas duplicadas (Quora, CQADupstack)</p></li><li><p>Verificação de fatos (FEVER, Climate-FEVER, Scifact)</p></li><li><p>Recuperação de informações biomédicas (TREC-COVID, NFCorpus, BioASQ)</p></li><li><p>Recuperação de entidades (DBPedia)</p></li><li><p>Previsão de citações (SCIDOCS)</p></li></ul><p>O benchmark fornece uma única métrica, nDCG@10, relacionada a quão bem um sistema corresponde aos documentos mais relevantes para cada exemplo de tarefa entre os principais resultados retornados. Para um sistema de busca com o qual pessoas interagem, a relevância dos primeiros resultados é fundamental. No entanto, existem muitas nuances na avaliação de busca que uma única métrica resumida não consegue capturar.</p><h2>Estrutura de um conjunto de dados BEIR</h2><p>Cada benchmark é composto por três artefatos:</p><ul><li><p>o corpus, ou seja, os documentos a serem recuperados</p></li><li><p>as consultas</p></li><li><p>Os julgamentos de relevância para as consultas (também conhecidos como <code>qrels</code>).</p></li></ul><p>Os julgamentos de relevância são fornecidos como uma pontuação igual ou maior que zero. Pontuações diferentes de zero indicam que o documento tem alguma relação com a consulta.</p><p>Conjunto de dados</p><p>Tamanho do corpus</p><p>#Consultas no conjunto de testes</p><p>#qrels com rótulo positivo</p><p>#qrels igual a zero</p><p>#duplicatas no corpus</p><p>ArguAna</p><p>8.674</p><p>1.406</p><p>1.406</p><p>0</p><p>96</p><p>Climate-FEVER</p><p>5.416.593</p><p>1.535</p><p>4.681</p><p>0</p><p>0</p><p>DBPedia</p><p>4.635.922</p><p>400</p><p>15.286</p><p>28.229</p><p>0</p><p>FEVER</p><p>5.416.568</p><p>6.666</p><p>7.937</p><p>0</p><p>0</p><p>FiQA-2018</p><p>57.638</p><p>648</p><p>1.706</p><p>0</p><p>0</p><p>HotpotQA</p><p>5.233.329</p><p>7.405</p><p>14.810</p><p>0</p><p>0</p><p>Natural Questions</p><p>2.681.468</p><p>3.452</p><p>4.021</p><p>0</p><p>16.781</p><p>NFCorpus</p><p>3.633</p><p>323</p><p>12.334</p><p>0</p><p>80</p><p>Quora</p><p>522.931</p><p>10.000</p><p>15.675</p><p>0</p><p>1.092</p><p>SCIDOCS</p><p>25.657</p><p>1.000</p><p>4.928</p><p>25.000</p><p>2</p><p>SciFact</p><p>5.183</p><p>300</p><p>339</p><p>0</p><p>0</p><p>Touche2020</p><p>382.545</p><p>49</p><p>932</p><p>1.982</p><p>5.357</p><p>TREC-COVID</p><p>171.332</p><p>50</p><p>24.763</p><p>41.663</p><p>0</p><p>MSMARCO</p><p>8.841.823</p><p>6.980</p><p>7.437</p><p>0</p><p>324</p><p>CQADupstack (soma)</p><p>457.199</p><p>13.145</p><p>23.703</p><p>0</p><p>0</p><p><strong>Tabela 1</strong>: Estatísticas dos conjuntos de dados. Os números foram calculados com base na porção de teste dos conjuntos de dados (<code>dev</code> para <code>MSMARCO</code>).</p><p>A <strong>Tabela 1</strong> apresenta algumas estatísticas dos conjuntos de dados que compõem o benchmark <code>BEIR</code>, como o número de documentos no corpus, o número de consultas no conjunto de teste e o número de pares positivos/negativos (consulta, documento) no arquivo <code>qrels</code>. Com uma análise rápida dos dados, é possível inferir imediatamente o seguinte:</p><ul><li><p>maioria dos conjuntos de dados não contém relações negativas no arquivo <code>qrels</code>, ou seja, pontuações zero que indicariam explicitamente que um documento é irrelevante para determinada consulta.</p></li><li><p>O número médio de relações entre documentos por consulta (<code>#qrels</code> / <code>#queries</code>) varia de 1,0 no caso de <code>ArguAna</code> até 493,5 (<code>TREC-COVID</code>), mas fica em torno de <code>&lt;</code>5 na maioria dos casos.</p></li><li><p>Alguns conjuntos de dados apresentam documentos duplicados no corpus, o que em certos casos pode levar a avaliações incorretas, por exemplo quando um documento é considerado relevante para uma consulta, mas seu duplicado não é. Como exemplo, em <code>ArguAna</code>, identificamos 96 casos de pares de documentos duplicados em que apenas um documento do par foi marcado como relevante para uma consulta. Ao “expandir” a lista inicial de qrels para incluir também os duplicados, observamos um aumento relativo médio de aproximadamente 1% na pontuação <code>nDCG@10</code>.</p></li></ul>{
  "_id": "test-economy-epiasghbf-pro02b",
  "title": "economic policy international africa society gender house believes feminisation",
  "text": "Again employment needs to be contextualised with …",
  "metadata": {}
}
{
  "_id": "test-society-epiasghbf-pro02b",
  "title": "economic policy international africa society gender house believes feminisation",
  "text": "Again employment needs to be contextualised with …",
  "metadata": {}
}
<p><strong>Exemplo de pares duplicados no ArguAna. No arquivo qrels, apenas o primeiro aparece como relevante (como contra-argumento) para a consulta (“test-economy-epiasghbf-pro02a”).</strong></p><p>Ao comparar modelos na tabela de classificação do MTEB, é tentador focar na qualidade média de recuperação. Essa é uma boa aproximação da qualidade geral do modelo, mas não necessariamente indica como ele vai se comportar no seu caso de uso específico. Como os resultados são reportados por conjunto de dados, vale a pena entender o quanto cada conjunto se relaciona com a sua tarefa de busca e reavaliar os modelos usando apenas os mais relevantes. Se quiser se aprofundar ainda mais, também é possível verificar a sobreposição de tópicos entre os diferentes corpora dos conjuntos de dados. Estratificar as métricas de qualidade por tópico fornece uma avaliação muito mais detalhada de pontos fortes e limitações específicas.</p><p>Um ponto importante a destacar é que, quando um documento não está marcado no arquivo <code>qrels</code> , ele é considerado irrelevante para a consulta por padrão. Aprofundamos um pouco mais essa análise e reunimos evidências para esclarecer melhor a seguinte pergunta: “Com que frequência um avaliador se depara com pares (consulta, documento) para os quais não existe informação de comprovação?” Isso é importante porque, quando apenas anotações superficiais estão disponíveis (ou seja, nem todos os documentos relevantes são rotulados como tal), um sistema de recuperação de informação pode ser avaliado como pior do que outro simplesmente porque ele “escolhe” destacar documentos relevantes diferentes, porém não marcados. Essa é uma armadilha comum na criação de conjuntos de avaliação de alta qualidade, especialmente em conjuntos de dados grandes. Para que a rotulagem manual seja viável, ela normalmente se concentra nos principais resultados retornados pelo sistema atual, o que pode deixar de fora documentos relevantes que estão fora do seu campo de visão. Por isso, em geral é preferível concentrar mais recursos em uma anotação mais completa de um número menor de consultas, em vez de realizar uma anotação ampla porém superficial.</p><h2>Usando o benchmark BEIR para avaliação de relevância de busca</h2><p>Para iniciar nossa análise, implementamos o seguinte cenário (ver o <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">notebook</a>):</p><ol><li><p>Primeiro, carregamos o corpus de cada conjunto de dados em um índice do Elasticsearch.</p></li><li><p>Para cada consulta no conjunto de teste, recuperamos os top 100 documentos usando BM25.</p></li><li><p>Em seguida, fazemos a reclassificação dos documentos recuperados usando uma variedade de modelos de reclassificação de última geração.</p></li><li><p>Por fim, reportamos a “taxa de julgamento” para os top 10 documentos provenientes das etapas 2 (após a recuperação) e 3 (após a reclassificação). Em outras palavras, calculamos a porcentagem média dos top 10 documentos que possuem uma pontuação no arquivo <code>qrels</code>.</p></li></ol><p>A lista de modelos de reclassificação que usamos é a seguinte:</p><ul><li><p><a href="https://docs.cohere.com/reference/rerank">Cohere</a> <code>rerank-english-v2.0</code> e <code>rerank-english-v3.0</code></p></li><li><p><a href="https://huggingface.co/BAAI/bge-reranker-base">BGE-base</a></p></li><li><p><a href="https://huggingface.co/mixedbread-ai/mxbai-rerank-xsmall-v1">mxbai-rerank-xsmall-v1</a></p></li><li><p><a href="https://huggingface.co/cross-encoder/ms-marco-MiniLM-L-6-v2">MiniLM-L-6-v2</a></p></li></ul><p></p><p>Recuperação</p><p>Reclassificação</p><p></p><p></p><p></p><p></p><p>Conjunto de dados</p><p>BM25 (%)</p><p>Cohere Rerank v2 (%)</p><p>Cohere Rerank v3 (%)</p><p>BGE-base (%)</p><p>mxbai-rerank-xsmall-v1 (%)</p><p>MiniLM-L-6-v2 (%)</p><p>ArguAna</p><p>7,54</p><p>4,87</p><p>7,87</p><p>4,52</p><p>4,53</p><p>6,84</p><p>Climate-FEVER</p><p>5,75</p><p>6,24</p><p>8,15</p><p>9,36</p><p>7,79</p><p>7,58</p><p>DBPedia</p><p>61,18</p><p>60,78</p><p>64,15</p><p>63,9</p><p>63,5</p><p>67,62</p><p>FEVER</p><p>8,89</p><p>9,97</p><p>10,08</p><p>10,19</p><p>9,88</p><p>9,88</p><p>FiQa-2018</p><p>7,02</p><p>11,02</p><p>10,77</p><p>8,43</p><p>9,1</p><p>9,44</p><p>HotpotQA</p><p>12,59</p><p>14,5</p><p>14,76</p><p>15,1</p><p>14,02</p><p>14,42</p><p>Natural Questions</p><p>5,94</p><p>8,84</p><p>8,71</p><p>8,37</p><p>8,14</p><p>8,34</p><p>NFCorpus</p><p>31,67</p><p>32,9</p><p>33,91</p><p>30,63</p><p>32,77</p><p>32,45</p><p>Quora</p><p>12,2</p><p>10,46</p><p>13,04</p><p>11,26</p><p>12,58</p><p>12,78</p><p>SCIDOCS</p><p>8,62</p><p>9,41</p><p>9,71</p><p>8,04</p><p>8,79</p><p>8,52</p><p>SciFact</p><p>9,07</p><p>9,57</p><p>9,77</p><p>9,3</p><p>9,1</p><p>9,17</p><p>Touche2020</p><p>38,78</p><p>30,41</p><p>32,24</p><p>33,06</p><p>37,96</p><p>33,67</p><p>TREC-COVID</p><p>92,4</p><p>98,4</p><p>98,2</p><p>93,8</p><p>99,6</p><p>97,4</p><p>MSMARCO</p><p>3,97</p><p>6,00</p><p>6,03</p><p>6,07</p><p>5,47</p><p>6,11</p><p>CQADupstack (média)</p><p>5,47</p><p>6,32</p><p>6,87</p><p>5,89</p><p>6,22</p><p>6,16</p><p><strong>Tabela 2</strong>: Taxa de julgamento por pares (conjunto de dados, reclassificador), calculada sobre os top 10 documentos recuperados/reclassificados</p><p>A partir da <strong>Tabela 2</strong>, com exceção de <code>TREC-COVID</code> (cobertura de &gt;90%), <code>DBPedia</code> (~65%), <code>Touche2020</code> e <code>nfcorpus</code> (~35%), observamos que a maioria dos conjuntos de dados apresenta uma taxa de rotulagem entre 5% e pouco mais de 10% após a recuperação ou a reclassificação. Isso não significa que todos esses documentos não marcados sejam relevantes, mas pode haver um subconjunto deles — especialmente aqueles posicionados no topo — que seja positivo.</p><p>Com a chegada de modelos de linguagem de propósito geral ajustados por instruções, passamos a contar com uma nova e poderosa ferramenta que pode, potencialmente, automatizar a avaliação de relevância. Esses métodos normalmente são computacionalmente caros demais para uso online em sistemas de busca, mas aqui estamos tratando de avaliação offline. A seguir, usamos esses modelos para explorar evidências de que alguns conjuntos de dados do BEIR sofrem com anotações superficiais.</p><p>Para investigar melhor essa hipótese, decidimos focar no MSMARCO e selecionar um subconjunto de 100 consultas, juntamente com os top 5 documentos reclassificados (com Cohere v2) que atualmente não estão marcados como relevantes. Primeiro, usamos um prompt cuidadosamente ajustado (falaremos mais sobre isso em um post futuro) para preparar o modelo <a href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct">Phi-3-mini-4k</a>, lançado recentemente, a prever se um documento é ou não relevante para a consulta. Em paralelo, esses mesmos casos também foram rotulados manualmente, de modo a avaliar a taxa de concordância entre a saída do LLM e o julgamento humano. De forma geral, podemos tirar as duas conclusões a seguir:</p><ul><li><p>A taxa de concordância entre as respostas dos LLMs e os julgamentos humanos ficou próxima de 80%, o que parece um ponto de partida razoável nessa direção.</p></li><li><p>Em 57,6% dos casos (com base em julgamento humano), os documentos retornados foram considerados realmente relevantes para a consulta. Colocando isso de outra forma: para 100 consultas, temos 107 documentos julgados como relevantes, mas pelo menos 0,576 × 5 × 100 = 288 documentos adicionais que também são, de fato, relevantes.</p></li></ul><p>Aqui estão alguns exemplos extraídos do conjunto de dados <code>MSMARCO</code>/<code>dev</code> que incluem a consulta, o documento positivo anotado (de <code>qrels</code>) e um documento falso negativo devido a anotações incompletas:</p><p>Exemplo 1:</p>{
  "query":
    {
        "_id": 155234,
        "text": "do bigger tires affect gas mileage"
    },
  "positive_doc":
    {
        "_id": 502713,
        "text": "Tire Width versus Gas Mileage. Tire width is one of the only tire size factors that can influence gas mileage in a positive way. For example, a narrow tire will have less wind resistance, rolling resistance, and weight; thus increasing gas mileage.",
    },
    "negative_doc":
    {
        "_id": 7073658,
        "text": "Tire Size and Width Influences Gas Mileage. There are two things to consider when thinking about tires and their effect on gas mileage; one is wind resistance, and the other is rolling resistance. When a car is driving at higher speeds, it experiences higher wind resistance; this means lower fuel economy."
    }
}
<p>Exemplo 2:</p>{
  "query":
    {
        "_id": 300674,
        "text": "how many years did william bradford serve as governor of plymouth colony?"
    },
  "positive_doc":
    {
        "_id": 7067032,
        "text": "http://en.wikipedia.org/wiki/William_Bradford_(Plymouth_Colony_governor) William Bradford (c.1590 \u00e2\u0080\u0093 1657) was an English Separatist leader in Leiden, Holland and in Plymouth Colony was a signatory to the Mayflower Compact. He served as Plymouth Colony Governor five times covering about thirty years between 1621 and 1657."
    },
    "negative_doc":
    {
        "_id": 2495763,
        "text": "William Bradford was the governor of Plymouth Colony for 30 years. The colony was founded by people called Puritans. They were some of the first people from England to settle in what is now the United States. Bradford helped make Plymouth the first lasting colony in New England."
    }
}
<p>Avaliar manualmente consultas específicas dessa forma é uma técnica geralmente útil para entender a qualidade da busca e complementa métricas quantitativas como o nDCG@10. Se você tiver um conjunto representativo de consultas que sempre executa ao fazer alterações no sistema de busca, isso fornece informações qualitativas importantes sobre como a performance muda, algo que não aparece nas estatísticas. Por exemplo, essa abordagem oferece muito mais visibilidade sobre resultados incorretos retornados pela busca: ajuda a identificar erros evidentes nos documentos recuperados, classes de falhas recorrentes, como interpretações equivocadas de terminologia específica de um domínio, entre outros.</p><p>Nossos resultados estão alinhados com pesquisas relevantes sobre avaliação em <code>MSMARCO</code>. Por exemplo, <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh et al.</a> seguem um procedimento semelhante ao empregar trabalhadores de crowdsourcing para realizar julgamentos de preferência. Entre outros achados, eles mostram que, em muitos casos, os documentos retornados pelos módulos de reclassificação são preferidos em relação aos documentos presentes no arquivo <code>qrels</code> do MSMARCO. Outra evidência vem dos autores do reclassificador <a href="https://arxiv.org/pdf/2010.08191">RocketQA</a>, que relatam que mais de 70% dos documentos reclassificados foram considerados relevantes após inspeção manual.</p><p> Atualização – 9 de setembro: após uma reavaliação cuidadosa do conjunto de dados, identificamos mais 15 casos de documentos relevantes, aumentando o total de 273 para 288.</p><h2>Principais conclusões e próximos passos</h2><ul><li><p>A busca por uma comprovação melhor é contínua e extremamente importante para benchmarking e comparação de modelos. Os LLMs podem auxiliar em algumas áreas da avaliação, desde que usados com cautela e ajustados com instruções adequadas.</p></li><li><p>De forma mais geral, considerando que benchmarks nunca serão perfeitos, pode ser preferível migrar de uma comparação puramente baseada em pontuações para técnicas mais robustas, capazes de capturar diferenças estatisticamente significativas. O trabalho de <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh et al.</a> oferece um bom exemplo disso: com base em seus resultados, os autores constroem intervalos de confiança de 95% para indicar se as diferenças entre diferentes execuções são estatisticamente significativas ou não. No <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">notebook</a> que acompanha este conteúdo, fornecemos uma implementação de intervalos de confiança usando <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">bootstrapping</a>.</p></li><li><p>Do ponto de vista do usuário final, é útil pensar em alinhamento com a tarefa ao interpretar resultados de benchmarks. Por exemplo, para um engenheiro de IA que constrói um pipeline de RAG e sabe que o caso de uso mais comum envolve reunir múltiplas informações de diferentes fontes, faz mais sentido avaliar o desempenho do modelo de recuperação em conjuntos de dados de QA multi-hop, como o HotpotQA, em vez de considerar apenas a média global de todo o benchmark BEIR.</p></li></ul><p>No <a href="https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-2">próximo post do blog,</a> vamos nos aprofundar no uso do Phi-3 como avaliador baseado em LLM e no processo de ajuste do modelo para prever relevância.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-1</guid>
    <category><![CDATA[Pesquisa de aprendizado de máquina]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Thanos Papaoikonomou,Thomas Veasey]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d9912c8d4187096/6a1704f5b0367d30e672bc17/54a6e5197f5721b36fc65f27387d29803ed35589-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Detecção de plágio por IA: usando o Elasticsearch]]></title>
    <description><![CDATA[Veja como verificar plágio em inteligência artificial usando o Elasticsearch, com foco em casos de uso com modelos de PNL (Processamento de Linguagem Natural) e Busca Vetorial.]]></description>
    <content:encoded><![CDATA[<p>O plágio pode ser <strong>direto</strong>, envolvendo a cópia de partes ou do conteúdo inteiro, ou <strong>parafraseado</strong>, onde a obra do autor é reformulada alterando-se algumas palavras ou frases.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>Existe uma distinção entre inspiração e paráfrase. É possível ler um conteúdo, se inspirar e depois explorar a ideia com suas próprias palavras, mesmo que você chegue a uma conclusão semelhante.</p><p>Embora o plágio seja um tema de discussão há muito tempo, a produção e publicação aceleradas de conteúdo o mantiveram relevante e representaram um desafio constante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>Esse desafio não se limita a livros, pesquisas acadêmicas ou documentos judiciais, onde verificações de plágio são realizadas com frequência. Isso também pode se estender a jornais e até mesmo às redes sociais.</p><p>Com a abundância de informações e o fácil acesso à publicação, como o plágio pode ser verificado de forma eficaz e em larga escala?</p><p>Universidades, entidades governamentais e empresas utilizam diversas ferramentas, mas, embora uma simples <a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">busca lexical</a> possa detectar plágio direto com eficácia, o principal desafio reside na identificação <strong>de conteúdo parafraseado.</strong></p><h2>Detecção de plágio com IA generativa</h2><p>Um novo desafio surge com a IA generativa. O conteúdo gerado por IA é considerado plágio quando copiado?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>Os <a href="https://openai.com/policies/terms-of-use">termos de uso</a> da <a href="https://openai.com/">OpenAI , por exemplo, especificam que a OpenAI não reivindicará direitos autorais sobre o conteúdo gerado pela API para os usuários.</a> Nesse caso, os indivíduos que utilizam sua IA generativa podem usar o conteúdo gerado como preferirem, sem necessidade de citação.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>No entanto, a aceitação do uso de IA generativa para melhorar a eficiência ainda é um tema de debate.</p><p>Na tentativa de contribuir para a detecção de plágio, a OpenAI desenvolveu um <a href="https://huggingface.co/roberta-base-openai-detector">modelo de detecção</a> , mas posteriormente reconheceu que sua precisão não é suficientemente alta.</p><p><em>"Acreditamos que essa precisão não é suficiente para uma detecção independente e precisa ser combinada com abordagens baseadas em metadados, julgamento humano e educação pública para ser mais eficaz."</em></p><p>O desafio persiste; no entanto, com a disponibilidade de mais ferramentas, existem agora mais opções para detectar plágio, mesmo em casos de conteúdo parafraseado e gerado por IA.</p><h2>Detecção de plágio com Elasticsearch</h2><p>Reconhecendo isso, neste blog exploraremos mais um caso de uso com modelos de Processamento de Linguagem Natural (PLN) e Busca Vetorial: a detecção de plágio, além das buscas por metadados.</p><p>Isso é demonstrado com <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb">exemplos em Python</a>, onde utilizamos um <a href="https://sbert.net/datasets/emnlp2016-2018.json">conjunto de dados</a> do <a href="https://www.sbert.net/">SentenceTransformers</a> contendo artigos relacionados a PNL (Processamento de Linguagem Natural). Verificamos se os resumos contêm plágio realizando uma 'similaridade textual semântica', considerando representações vetoriais de 'resumos' geradas com um <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">modelo de incorporação de texto</a> previamente importado para o Elasticsearch. Além disso, para identificar conteúdo gerado por IA — plágio por IA —, um <a href="https://huggingface.co/roberta-base-openai-detector">modelo de PNL</a> desenvolvido pela OpenAI também foi importado para o Elasticsearch.</p><p>A imagem a seguir ilustra o fluxo de dados:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p>Durante o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">processo de ingestão</a> com um <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">processador de inferência</a>, o parágrafo 'abstract' é mapeado para um vetor de 768 dimensões, o 'abstract_vector.predicted_value'.</p><p>Mapeamento:</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>A similaridade entre representações vetoriais é medida usando uma métrica de similaridade vetorial, definida pelo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">parâmetro</a> 'similaridade'.</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">O cosseno</a> é a métrica de similaridade padrão, calculada como '(1 + cosseno(consulta, vetor)) / 2'. A menos que seja necessário preservar os vetores originais e não seja possível normalizá-los antecipadamente, a maneira mais eficiente de realizar a similaridade de cosseno é normalizar todos os vetores para comprimento unitário. Isso ajuda a evitar cálculos extras de comprimento de vetor durante a busca; em vez disso, use 'produto escalar'.</p><p>Nesse mesmo fluxo de trabalho, outro processador de inferência contendo o <a href="https://huggingface.co/roberta-base-openai-detector">modelo de classificação de texto</a> detecta se o conteúdo é 'Real', provavelmente escrito por humanos, ou 'Falso', provavelmente escrito por IA, adicionando o 'openai-detector.predicted_value' a cada documento.</p><p>Pipeline de ingestão:</p>client.ingest.put_pipeline( 
    id="plagiarism-checker-pipeline",
    processors = [
    {
      "inference": { #for ml models - to infer against the data that is being ingested in the pipeline
        "model_id": "roberta-base-openai-detector", #text classification model id
        "target_field": "openai-detector", # Target field for the inference results
        "field_map": { #Maps the document field names to the known field names of the model.
        "abstract": "text_field" # Field matching our configured trained model input. 
        }
      }
    },
    {
      "inference": {
        "model_id": "sentence-transformers__all-mpnet-base-v2", #text embedding model id
        "target_field": "abstract_vector", # Target field for the inference results
        "field_map": {
        "abstract": "text_field" # Field matching our configured trained model input. Typically for NLP models, the field name is text_field.
        }
      }
    }
    
  ]
)
<p>No momento da consulta, o mesmo modelo de incorporação de texto também é empregado para gerar a representação vetorial da consulta 'model_text' em um objeto 'query_vector_builder'.</p><p>Uma busca por k-vizinhos mais próximos (kNN) encontra os k vetores mais próximos do vetor de consulta, medidos pela métrica de similaridade.</p><p>A pontuação de cada documento é derivada da similaridade, garantindo que uma pontuação maior corresponda a uma classificação mais alta. Isso significa que o documento é semanticamente mais semelhante. Como resultado, estamos apresentando três possibilidades: se a pontuação for &gt; 0,9, consideramos 'alta similaridade'; se for &lt; 0,7, 'baixa similaridade'; caso contrário, 'similaridade moderada'. Você tem a flexibilidade de definir diferentes valores de limite para determinar qual nível de _score se qualifica como plágio ou não, com base no seu caso de uso.</p><p>Além disso, é realizada uma classificação de texto para verificar também a presença de elementos gerados por IA na consulta textual.</p><p>Consulta:</p>from elasticsearch import Elasticsearch
from elasticsearch.client import MlClient

#duplicated text - direct plagiarism test

model_text = 'Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at http://hucvl.github.io/recipeqa.'

response = client.search(index='plagiarism-checker', size=1,
    knn={
        "field": "abstract_vector.predicted_value",
        "k": 9,
        "num_candidates": 974,
        "query_vector_builder": { #The 'all-mpnet-base-v2' model is also employed to generate the vector representation of the query in a 'query_vector_builder' object.
            "text_embedding": {
                "model_id": "sentence-transformers__all-mpnet-base-v2",
                "model_text": model_text
            }
        }
    }
)

for hit in response['hits']['hits']:
    score = hit['_score']
    title = hit['_source']['title']
    abstract = hit['_source']['abstract']
    openai = hit['_source']['openai-detector']['predicted_value']
    url = hit['_source']['url']

    if score &gt; 0.9:
        print(f"\nHigh similarity detected! This might be plagiarism.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    elif score &lt; 0.7:
        print(f"\nLow similarity detected. This might not be plagiarism.")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

    else:
        print(f"\nModerate similarity detected.")
        print(f"\nMost similar document: '{title}'\n\nAbstract: {abstract}\n\nurl: {url}\n\nScore:{score}\n\n")

        if openai == 'Fake':
            print("This document may have been created by AI.\n")

ml_client = MlClient(client)

model_id = 'roberta-base-openai-detector' #open ai text classification model

document = [
    {
        "text_field": model_text
    }
]

ml_response = ml_client.infer_trained_model(model_id=model_id, docs=document)

predicted_value = ml_response['inference_results'][0]['predicted_value']

if predicted_value == 'Fake':
    print("\nNote: The text query you entered may have been generated by AI.\n")
<p>Saída:</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:1.0
<p>Neste exemplo, após utilizar um dos valores 'abstratos' do nosso conjunto de dados como a consulta de texto 'model_text', foi identificado plágio. A pontuação de similaridade é 1,0, indicando um alto nível de similaridade — <strong>plágio direto</strong>. A consulta vetorizada e o documento não foram reconhecidos como conteúdo gerado por IA, o que era esperado.</p><p>Consulta:</p>#similar text - paraphrase plagiarism test 

model_text = 'Comprehending and deducing information from culinary instructions represents a promising avenue for research aimed at empowering artificial intelligence to decipher step-by-step text. In this study, we present CuisineInquiry, a database for the multifaceted understanding of cooking guidelines. It encompasses a substantial number of informative recipes featuring various elements such as headings, explanations, and a matched assortment of visuals. Utilizing an extensive set of automatically crafted question-answer pairings, we formulate a series of tasks focusing on understanding and logic that necessitate a combined interpretation of visuals and written content. This involves capturing the sequential progression of events and extracting meaning from procedural expertise. Our initial findings suggest that CuisineInquiry is poised to function as a demanding experimental platform.'
<p>Saída:</p>High similarity detected! This might be plagiarism.

Most similar document: 'RecipeQA: A Challenge Dataset for Multimodal Comprehension of Cooking Recipes'

Abstract: Understanding and reasoning about cooking recipes is a fruitful research direction towards enabling machines to interpret procedural text. In this work, we introduce RecipeQA, a dataset for multimodal comprehension of cooking recipes. It comprises of approximately 20K instructional recipes with multiple modalities such as titles, descriptions and aligned set of images. With over 36K automatically generated question-answer pairs, we design a set of comprehension and reasoning tasks that require joint understanding of images and text, capturing the temporal flow of events and making sense of procedural knowledge. Our preliminary results indicate that RecipeQA will serve as a challenging test bed and an ideal benchmark for evaluating machine comprehension systems. The data and leaderboard are available at[ http://hucvl.github.io/recipeqa](http://hucvl.github.io/recipeqa).

url:[http://aclweb.org/anthology/D18-1166](http://aclweb.org/anthology/D18-1166)

Score:0.9302529

Note: The text query you entered may have been generated by AI.
<p>Ao atualizar a consulta de texto 'model_text' com um texto gerado por IA que transmite a mesma mensagem, minimizando a repetição de palavras semelhantes, a similaridade detectada ainda foi alta, mas a pontuação foi de 0,9302529 em vez de 1,0 — <strong>plágio por paráfrase</strong>. Também era esperado que essa consulta, gerada por IA, fosse detectada.</p><p>Por fim, considerando a consulta de texto 'model_text' como um texto sobre Elasticsearch, que não é um resumo de nenhum desses documentos, a similaridade detectada foi de 0,68991005, indicando baixa similaridade de acordo com os valores de limite considerados.</p><p>Consulta:</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>Saída:</p>Low similarity detected. This might not be plagiarism.
<p>Embora o plágio tenha sido identificado com precisão na consulta de texto gerada pela IA, bem como em casos de paráfrase e conteúdo copiado diretamente, navegar pelo cenário da detecção de plágio envolve reconhecer vários aspectos.</p><p>No contexto da detecção de conteúdo gerado por IA, exploramos um modelo que oferece uma contribuição valiosa. No entanto, é crucial reconhecer as limitações inerentes à detecção isolada, o que torna necessária a incorporação de outros métodos para aumentar a precisão.</p><p>A variabilidade introduzida pela escolha dos modelos de incorporação de texto é outra consideração importante. Diferentes modelos, treinados com conjuntos de dados distintos, resultam em níveis variados de similaridade, o que destaca a importância das representações vetoriais de texto geradas.</p><p>Por fim, nesses exemplos, utilizamos o resumo do documento. No entanto, a detecção de plágio geralmente envolve documentos extensos, tornando essencial abordar o desafio do tamanho do texto. É comum que o texto exceda o limite de tokens de um modelo, exigindo segmentação em partes antes da construção dos embeddings. Uma <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">abordagem prática</a> para lidar com isso envolve a utilização de estruturas aninhadas com dense_vector.</p><h2>Conclusão</h2><p>Neste blog, discutimos os desafios da detecção de plágio, particularmente em conteúdo parafraseado e gerado por IA, e como a similaridade textual semântica e a classificação de texto podem ser usadas para esse fim.</p><p>Ao combinar esses métodos, fornecemos um exemplo de detecção de plágio no qual identificamos com sucesso conteúdo gerado por IA, plágio direto e plágio por paráfrase.</p><p>O objetivo principal era estabelecer um sistema de filtragem que simplificasse a detecção, mas a avaliação humana continua sendo essencial para a validação.</p><p>Se você tiver interesse em aprender mais sobre similaridade textual semântica e PNL, recomendamos que você também confira estes links:</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">O que é a busca semântica?</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">O que é processamento de linguagem natural (PLN)?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Busca Léxica e Semântica com Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">Fragmentar documentos grandes por meio de pipelines de ingestão, juntamente com vetores aninhados, resulta em uma busca de trechos facilitada.</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/ai-plagiarism-checker-with-elasticsearch</guid>
    <category><![CDATA[Banco de dados vetorial]]></category>
    <category><![CDATA[Python]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a5bc2434a9b03b/6a1711510e2e49a09641a22a/83e05cd4f81799fbb7b7950ed87600e825ec81e9-1024x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 19 Dec 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Busca lexical e semântica com Elasticsearch]]></title>
    <description><![CDATA[Neste blog, exploraremos várias abordagens para recuperar informações usando o Elasticsearch, com foco em busca lexical e semântica.]]></description>
    <content:encoded><![CDATA[<p>A busca é o processo de localizar as informações mais relevantes com base na sua consulta de pesquisa ou em consultas combinadas, e os resultados de pesquisa relevantes são os documentos que melhor correspondem a essas consultas. Embora existam diversos desafios e métodos associados à busca, o objetivo final permanece o mesmo: <strong>encontrar a melhor resposta possível para sua pergunta</strong>.</p><p>Considerando esse objetivo, neste post do blog, exploraremos diferentes abordagens para recuperar informações usando o Elasticsearch, com foco específico em busca de texto: <strong>busca lexical e busca semântica.</strong></p><h2>Pré-requisitos</h2><p>Para isso, forneceremos exemplos em Python que demonstram vários cenários de pesquisa em um <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">conjunto de dados</a> gerado para simular informações de produtos de comércio eletrônico.</p><p>Este conjunto de dados contém mais de 2.500 produtos, cada um com uma descrição. Esses produtos estão categorizados em 76 categorias distintas, cada uma contendo um número variável de produtos, conforme mostrado abaixo:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>Visualização em mapa de árvore - os 22 principais valores de category.keyword (categorias de produtos)</em></p><p>Para a configuração, você precisará de:</p><ul><li><p>Python 3.6 ou posterior</p></li><li><p>O <a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">cliente Elastic Python</a></p></li><li><p>Implantação do Elasticsearch 8.8 ou posterior, com nó de aprendizado de máquina de 8 GB de memória.</p></li><li><p>O modelo <a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> que vem pré-carregado no Elastic instalado e em execução na sua implementação.</p></li></ul><p>Usaremos o Elastic Cloud, e um <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">período de teste gratuito está disponível</a>.</p><p>Além das consultas de pesquisa fornecidas nesta postagem do blog, um <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">notebook Python</a> irá guiá-lo pelos seguintes processos:</p><ul><li><p>Estabeleça uma conexão com nossa implantação Elastic usando o cliente Python.</p></li><li><p>Carregar um modelo de incorporação de texto no cluster Elasticsearch</p></li><li><p>Crie um índice com mapeamentos para indexar vetores de características e vetores densos.</p></li><li><p>Crie um pipeline de ingestão com processadores de inferência para incorporação e expansão de texto.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>Busca lexical - recuperação esparsa</h2><p>A forma clássica como os documentos são classificados por relevância pelo Elasticsearch com base em uma consulta de texto utiliza a implementação Lucene do modelo <a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a> , um <strong>modelo esparso para busca lexical</strong>. Este método segue a abordagem tradicional para busca de texto, procurando por correspondências exatas dos termos.</p><p>Para tornar essa busca possível, o Elasticsearch converte os dados <strong>dos campos de texto</strong> em um formato pesquisável, realizando uma análise de texto.</p><p><strong>A análise de texto</strong> é realizada por um<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> analisador</a>, um conjunto de regras que regem o processo de extração de tokens relevantes para a pesquisa. Um analisador deve ter exatamente um<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> tokenizador</a>. O analisador léxico recebe um fluxo de caracteres e o divide em tokens individuais (geralmente palavras individuais), como no exemplo abaixo:</p><h3>Tokenização de strings para busca lexical</h3>#Performs text analysis on a string and returns the resulting tokens.

# Define the text to be analyzed
text = "Comfortable furniture for a large balcony"

# Define the analyze request
request_body = {
  "analyzer": "standard",
  "text": text
}

# Perform the analyze request
response = client.indices.analyze(analyzer=request_body["analyzer"], text=request_body["text"])

# Extract and display the analyzed tokens
tokens = [token["token"] for token in response["tokens"]]
print("Analyzed Tokens:", tokens)
<p>Saída</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>Neste exemplo, estamos usando o analisador <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">padrão</a> , que funciona bem para a maioria dos casos de uso, pois fornece tokenização baseada na gramática inglesa. A tokenização permite a correspondência de termos individuais, mas cada token ainda é correspondido literalmente.</p><p>Se você deseja personalizar sua experiência de busca, pode escolher um<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> analisador integrado</a> diferente. Por exemplo, ao atualizar o código para usar o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">analisador de palavras irrelevantes,</a> o texto será dividido em tokens em qualquer caractere que não seja letra, com suporte para a remoção de palavras irrelevantes.</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>Saída</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p>Quando os analisadores integrados não atendem às suas necessidades, você pode criar um <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html">analisador personalizado</a>, que utiliza a combinação apropriada de zero ou mais <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">filtros de caracteres</a>, um <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">analisador léxico</a> e zero ou mais <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">filtros de tokens</a>.</p>"analyzer":  {

  "my_analyzer": {

    "type": "custom", #For custom analyzers, use a type of custom or omit the type parameter.

    "tokenizer": "standard", #Built-in or customized tokenizer

    "filter": ["lowercase", "synonym"] #Built-in or customized token filters
  }
}
<p>No exemplo acima, que combina um analisador léxico e filtros de tokens, o texto será convertido para minúsculas pelo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html">filtro de minúsculas</a> antes de ser processado pelo <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-synonym-tokenfilter.html#:~:text=Elasticsearch%20will%20use%20the%20token,applied%20to%20the%20synonym%20entries.">filtro de tokens de sinônimos</a>.</p><h2>Correspondência lexical</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">O BM25</a> medirá a relevância dos documentos para uma determinada consulta de pesquisa com base na frequência dos termos e na sua importância.</p><p>O código abaixo realiza uma consulta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">de correspondência</a> , buscando até dois documentos considerando os valores do campo <em>"description"</em> do índice <em>"ecommerce-search"</em> e a consulta de pesquisa <strong>"</strong><em><strong>Móveis confortáveis para uma varanda grande</strong></em><strong>"</strong>.</p><p>Refinar os critérios para que um documento seja considerado uma correspondência para esta consulta pode melhorar a precisão. No entanto, resultados mais específicos têm como contrapartida uma menor tolerância a variações.</p># BM25

response = client.search(size=2,
index="ecommerce-search",
query= {
  "match": {
    "description" : {  
      "query": "Comfortable furniture for a large balcony",
      "analyzer": "stop"
    }
  }
}
)

hits = response['hits']['hits']

if not hits:
  print("No matches found")

else:
  for hit in hits:
    score = hit['_score']
    product = hit['_source']['product']
    category = hit['_source']['category']
    description = hit['_source']['description']
    print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Saída</p>Score: 15.607948
Product: Barbie Dreamhouse
Category: Toys
Description: is a classic Barbie playset with multiple rooms, furniture, a large balcony, a pool, and accessories. It allows kids to create their dream Barbie world.

Score: 9.137739
Product: Comfortable Rocking Chair
Category: Indoor Furniture
Description: enjoy relaxing moments with this comfortable rocking chair. Its smooth motion and cushioned seat make it an ideal piece of furniture for unwinding.
<p>Ao analisar os resultados, o mais relevante é o produto "<em>Casa dos Sonhos da Barbie</em>", na categoria "<em>Brinquedos</em>", cuja descrição é altamente relevante por incluir os termos "<em>móveis</em>", "<em>grande"</em> e <em>"varanda</em>". Este é o único produto com três termos na descrição que correspondem à consulta de pesquisa, sendo também o único com o termo <em>"varanda"</em> na descrição.</p><p>O segundo produto mais relevante é uma "<em>Cadeira de Balanço Confortável</em>", categorizada como "<em>Móveis para Interiores</em>", cuja descrição inclui os termos "<em>confortável</em>" e "<em>móveis</em>". Apenas 3 produtos no conjunto de dados correspondem a pelo menos 2 termos desta consulta de pesquisa, e este produto é um deles.</p><p><em>"Confortável"</em> aparece na descrição de 105 produtos e <em>"móveis"</em> na descrição de 4 produtos com 4 categorias diferentes: <em>Brinquedos</em>, <em>Móveis para Interior, Móveis para Exterior e 'Suprimentos e Brinquedos para Cães e Gatos'.</em></p><p>Como você pôde ver, o produto mais relevante considerando a consulta é um brinquedo e o segundo produto mais relevante são móveis para interiores. Se você deseja informações detalhadas sobre o cálculo da pontuação para saber por que esses documentos correspondem, pode definir o parâmetro <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>explain</em></a> __query como verdadeiro.</p><p>Apesar de ambos os resultados serem os mais relevantes, considerando tanto o número de documentos quanto a ocorrência de termos neste conjunto de dados, a intenção por trás da consulta "<em>Móveis confortáveis para uma varanda grande</em>" é buscar móveis para uma varanda grande de fato, excluindo, entre outros, brinquedos e móveis de interior.</p><p>A busca lexical é relativamente <strong>simples e rápida</strong>, mas tem limitações, já que nem sempre é possível conhecer todos os termos e sinônimos possíveis sem necessariamente conhecer a intenção e as consultas do usuário. Um fenômeno comum no uso da linguagem natural é <strong>a incompatibilidade de vocabulário</strong>. <a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">Pesquisas</a> mostram que, em média, <strong>80% das vezes,</strong> pessoas diferentes (especialistas na mesma área) nomeiam a mesma coisa de maneiras diferentes.</p><p>Essas limitações nos motivam a buscar outros modelos de pontuação que incorporem conhecimento semântico. Os modelos baseados em Transformers, que se destacam no processamento de tokens de entrada sequenciais, como a linguagem natural, capturam o significado subjacente da sua pesquisa considerando representações matemáticas tanto dos documentos quanto das consultas. Isso permite uma representação vetorial densa e contextualizada do texto, impulsionando <strong>a Busca Semântica</strong>, uma maneira refinada de encontrar conteúdo relevante.</p><h2>Busca semântica - recuperação densa</h2><p>Nesse contexto, após converter seus dados em valores vetoriais significativos, o algoritmo de busca <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-vizinhos mais próximos (kNN)</a> é utilizado para encontrar representações vetoriais em um conjunto de dados que sejam mais semelhantes a um vetor de consulta. O Elasticsearch suporta dois métodos para busca kNN: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">kNN exato por força bruta</a> e <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">kNN aproximado</a>, também conhecido como ANN.</p><p>O kNN de força bruta garante resultados precisos, mas não escala bem com grandes conjuntos de dados. O algoritmo kNN aproximado encontra de forma eficiente os vizinhos mais próximos aproximados, sacrificando um pouco de precisão em troca de um melhor desempenho.</p><p>Com o suporte do Lucene para busca kNN e índices vetoriais densos, o Elasticsearch aproveita o algoritmo Hierarchical Navigable Small World (HNSW), que demonstra um forte desempenho de busca em diversos conjuntos de <a href="http://ann-benchmarks.com/">dados de benchmark de redes neurais</a>. Uma busca aproximada por kNN pode ser realizada em Python usando o código de exemplo abaixo.</p><h3>Busca semântica com kNN aproximado</h3># KNN - approximate kNN

response = client.search(index='ecommerce-search', size=2,
knn={
  "field": "description_vector.predicted_value",
  "k": 50, # Number of nearest neighbors to return as top hits.
#The optimal value of k is dependent on the data. It can vary in different scenarios.

  "num_candidates": 500, # Number of nearest neighbor candidates to consider per shard.

#Increasing num_candidates tends to improve the accuracy of the final k results.

  "query_vector_builder": { # Object indicating how to build a query_vector. kNN search enables you to perform semantic search by using a previously deployed text embedding model, the steps for this process are demonstrated in the Python notebook.
    "text_embedding": { 
      "model_id": "sentence-transformers__all-mpnet-base-v2", # Text embedding model id
      "model_text": "Comfortable furniture for a large balcony" # Query
    }
  }
}
)

for hit in response['hits']['hits']:
        
  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Este bloco de código usa o kNN do Elasticsearch para retornar até dois produtos com uma descrição semelhante à consulta vetorizada (query_vector_build) de "<em>Móveis confortáveis para uma varanda grande</em>", considerando os embeddings do campo "<em>descrição</em> " no conjunto de dados de produtos.</p><p>Os embeddings dos produtos foram gerados previamente em um pipeline de ingestão com um processador de inferência contendo o modelo de embedding de texto <em>"</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em> para inferir com base nos dados que estavam sendo ingeridos no pipeline.</p><p>Este modelo foi escolhido com base na avaliação de modelos pré-treinados usando <em>"</em><a href="https://github.com/UKPLab/sentence-transformers/blob/master/docs/package_reference/sentence_transformer/evaluation.md"><em>sentence_transformers.evaluation</em></a><em>".</em> onde diferentes classes são usadas para avaliar um modelo durante o treinamento. O modelo "all-mpnet-base-v2" demonstrou o melhor desempenho médio de acordo com o <a href="https://www.sbert.net/docs/pretrained_models.html">ranking do Sentence-Transformers</a> e também garantiu uma posição favorável no ranking do <a href="https://huggingface.co/spaces/mteb/leaderboard">Massive Text Embedding Benchmark (MTEB)</a> . O modelo, pré-treinado a partir do modelo<a href="https://huggingface.co/microsoft/mpnet-base"> microsoft/mpnet-base</a> e ajustado em um conjunto de dados de 1 bilhão de pares de frases, mapeia as frases para um espaço vetorial denso de 768 dimensões.</p><p>Alternativamente, existem muitos outros modelos disponíveis que podem ser utilizados, especialmente aqueles ajustados especificamente para os dados do seu domínio.</p><p>Saída</p>Score: 0.79207325
Product: Patio Sofa Set with Ottoman
Category: Outdoor Furniture
Description: is a versatile and comfortable patio sofa set, including a sofa, ottoman, and coffee table, great for outdoor lounging.

Score: 0.7836937
Product: Patio Sofa Set with Canopy
Category: Outdoor Furniture
Description: is a luxurious and comfortable patio sofa set with a canopy, providing shade and style for outdoor lounging.
<p><em>O resultado pode variar dependendo do modelo escolhido,</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>dos filtros</em></a> <em>e</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>do ajuste aproximado do kNN</em></a><em>.</em></p><p>Os resultados da pesquisa kNN estão ambos na categoria "<em>Móveis para Exterior</em>", embora a palavra "<em>exterior</em>" não tenha sido mencionada explicitamente na consulta, o que destaca a importância da compreensão semântica no contexto.</p><p>A busca vetorial densa oferece diversas vantagens:</p><ul><li><p>Habilitar a pesquisa semântica</p></li><li><p>Escalabilidade para lidar com conjuntos de dados muito grandes</p></li><li><p>Flexibilidade para lidar com uma ampla variedade de tipos de dados.</p></li></ul><p>No entanto, <strong>a busca vetorial densa também apresenta seus próprios desafios</strong>:</p><ul><li><p>Selecionando o modelo de incorporação correto para o seu caso de uso.</p></li><li><p>Uma vez escolhido o modelo, pode ser necessário ajustá-lo para otimizar o desempenho em um conjunto de dados específico do domínio, um processo que exige o envolvimento de especialistas na área.</p></li><li><p>Além disso, a indexação de vetores de alta dimensão pode ser computacionalmente dispendiosa.</p></li></ul><h2>Busca semântica - recuperação esparsa aprendida</h2><p>Vamos explorar uma abordagem alternativa: recuperação esparsa aprendida, outra maneira de realizar buscas semânticas.</p><p>Como um modelo esparso, ele utiliza o índice invertido baseado em Lucene do Elasticsearch, que se beneficia de décadas de otimizações. No entanto, essa abordagem vai além da simples adição de sinônimos com funções de pontuação lexical como o BM25. Em vez disso, incorpora associações aprendidas usando um conhecimento mais profundo em escala linguística para otimizar a relevância.</p><p>Ao expandir as consultas de pesquisa para incluir termos relevantes que não estão presentes na consulta original, o <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse Encoder</a> <strong>aprimora os embeddings de vetores esparsos</strong>, como você pode ver no exemplo abaixo.</p><h3>Busca vetorial esparsa com codificador esparso de aprendizado elástico</h3># Elastic Learned Sparse Encoder

response = client.search(index='ecommerce-search', size=2,
query={
  "text_expansion": {
    "ml.tokens": {
      "model_id":"elser_model",
      "model_text":"Comfortable furniture for a large balcony"                
    }
  }
}
)

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Saída</p>Score: 14.405318
Product: Garden Lounge Set with Side Table
Category: Garden Furniture
Description: is a comfortable and stylish garden lounge set, including a sofa, chairs, and a side table for outdoor relaxation.

Score: 14.281318
Product: Rattan Patio Conversation Set
Category: Outdoor Furniture
Description: is a stylish and comfortable outdoor furniture set, including a sofa, two chairs, and a coffee table, all made of durable rattan material.
<p>Os resultados, neste caso, incluem a categoria "<em>Móveis de Jardim</em>", que oferece produtos bastante semelhantes aos de "<em>Móveis para Exterior</em>".</p><p>Ao analisar "ml.tokens", Ao analisar o campo "rank_features" que contém os tokens gerados pela Recuperação Esparsa Aprendida, torna-se evidente que, entre os vários tokens gerados, existem termos que, embora não façam parte da consulta de pesquisa, ainda são relevantes em significado, como "<em>relaxar</em>" (confortável), "<em>sofá</em>" (móveis) e "<em>exterior</em>" (varanda).</p><p>A imagem abaixo destaca alguns desses termos juntamente com a consulta, tanto com quanto sem expansão de termos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>Conforme observado, este modelo proporciona uma busca sensível ao contexto e ajuda a mitigar o problema de incompatibilidade de vocabulário, ao mesmo tempo que oferece resultados mais interpretáveis. Ele pode até superar modelos vetoriais densos quando nenhum re-treinamento específico do domínio é aplicado.</p><h2>Busca híbrida: resultados relevantes combinando busca lexical e semântica.</h2><p>Quando se trata de pesquisa, não existe uma solução universal. Cada um desses métodos de recuperação tem seus pontos fortes, mas também seus desafios. Dependendo do caso de uso, a melhor opção pode mudar. Muitas vezes, os melhores resultados obtidos com diferentes métodos de recuperação de informação podem ser complementares. Portanto, para melhorar a relevância, vamos analisar a possibilidade de combinar os pontos fortes de cada método.</p><p>Existem várias maneiras de implementar uma <strong>busca híbrida</strong>, incluindo combinação linear, atribuição de um peso a cada pontuação e fusão de classificação recíproca (RRF), onde especificar um peso não é necessário.</p><h3>Elasticsearch: o melhor dos dois mundos com busca lexical e semântica</h3># BM25 + Elastic Learned Sparse Encoder (Linear Combination)

response = client.search(index='ecommerce-search', size=2,

query= {
  "bool": {
    "should": [
    {
      "match": {
        "description" : {  
          "query": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
    },                   
    {
      "text_expansion": {
        "ml.tokens": {
          "model_id": "elser_model",
          "model_text": "A dining table and comfortable chairs for a large balcony",
          "boost": 1
        }
      }
     }
    ]
  }
}
)

# The boost value is 1 for the text expansion and match query. This means that the relevance score of the results of these queries are not boosted. You can specify a boost value to give a weight to each score in the sum. The scores will be calculated as: score = boost value * match_score + boost value * text_expansion_score

for hit in response['hits']['hits']:

  score = hit['_score']
  product = hit['_source']['product']
  category = hit['_source']['category']
  description = hit['_source']['description']
  print(f"\nScore: {score}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p>Neste código, realizamos uma busca híbrida com duas consultas que tinham o valor "<em>Uma mesa de jantar e cadeiras confortáveis para uma varanda grande</em>". Em vez de usar "<em>móveis</em>" como termo de busca, estamos especificando o que procuramos, e ambas as buscas consideram os mesmos valores de campo, "descrição". A classificação é determinada por uma combinação linear com pesos iguais para as pontuações BM25 e ELSER.</p><p>Saída</p>Score: 31.628141
Product: Garden Dining Set with Swivel Rockers
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel rockers for easy movement.

Score: 31.334227
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>No código abaixo, usaremos o mesmo valor para a consulta, mas combinaremos as pontuações de BM25 (parâmetro de consulta) e kNN (parâmetro knn) usando o método de fusão de classificação recíproca para combinar e classificar os documentos.</p># BM25 + KNN (RRF)

response = client.search(index='ecommerce-search', size=2,
query={
  "bool": {
    "should": [
    {
      "match": {
        "description": {
        "query": "A dining table and comfortable chairs for a large balcony"
        }
      }
    }
    ]
  }
},
knn={
  "field": "description_vector.predicted_value",
  "k": 50,
  "num_candidates": 500,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": "sentence-transformers__all-mpnet-base-v2",
      "model_text": "A dining table and comfortable chairs for a large balcony"
    }
  }
},
rank={
  "rrf": { # Reciprocal rank fusion
    "window_size": 50, # This value determines the size of the individual result sets per query.
    "rank_constant": 20 # This value determines how much influence documents in individual result sets per query have over the final ranked result set.
  }
}
)

for hit in response['hits']['hits']:
        
  rank = hit['_rank']
  category = hit['_source']['category']
  product = hit['_source']['product']
  description = hit['_source']['description']
  print(f"\nRank: {rank}\nProduct: {product}\nCategory: {category}\nDescription: {description}\n")
<p><em>A funcionalidade RRF está em fase de pré-visualização técnica. A sintaxe provavelmente mudará antes do lançamento oficial.</em></p><p>Saída</p>Rank: 1
Product: Patio Dining Set with Bench
Category: Outdoor Furniture
Description: is a spacious and functional patio dining set, including a dining table, chairs, and a bench for additional seating.

Rank: 2
Product: Garden Dining Set with Swivel Chairs
Category: Garden Furniture
Description: is a functional and comfortable garden dining set, including a table and chairs with swivel seats for convenience.
<p>Aqui também poderíamos usar campos e valores diferentes; alguns desses exemplos estão disponíveis no <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">notebook Python</a>.</p><p>Como você pode ver, com o Elasticsearch você tem o melhor dos dois mundos: a busca lexical tradicional e a busca vetorial, seja ela esparsa ou densa, para atingir seu objetivo <strong>e encontrar a melhor resposta possível para sua pergunta.</strong></p><p>Se você deseja continuar aprendendo sobre as abordagens mencionadas aqui, estes blogs podem ser úteis:</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Aprimorando a recuperação de informações no Elastic Stack: Recuperação híbrida</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Busca vetorial no Elasticsearch: a lógica por trás do design</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Como obter o melhor da busca lexical e da busca com inteligência artificial usando o banco de dados vetorial da Elastic.</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Apresentando o Elastic Learned Sparse Encoder: o modelo de IA da Elastic para busca semântica.</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Aprimorando a recuperação de informações no Elastic Stack: Apresentando o Elastic Learned Sparse Encoder, nosso novo modelo de recuperação.</a></p></li></ul><p>O Elasticsearch fornece um banco de dados vetorial, juntamente com todas as ferramentas necessárias para criar pesquisas vetoriais:</p><ul><li><p><a href="https://www.elastic.co/elasticsearch/vector-database">banco de dados vetorial</a>Elasticsearch</p></li><li><p>Casos de uso <a href="https://www.elastic.co/enterprise-search/vector-search">de busca vetorial</a> com Elastic</p></li></ul><h2>Conclusão</h2><p>Neste artigo, exploramos diversas abordagens para recuperar informações usando o Elasticsearch, com foco específico em busca textual, lexical e semântica. Para demonstrar isso, fornecemos exemplos em Python que mostram diferentes cenários de pesquisa usando um conjunto de dados contendo informações de produtos de comércio eletrônico.</p><p>Analisamos a busca lexical clássica com BM25 e discutimos seus benefícios e desafios, como a incompatibilidade de vocabulário. Enfatizamos a importância de incorporar conhecimento semântico para superar esse problema. Além disso, discutimos a busca vetorial densa, que possibilita a busca semântica, e abordamos os desafios associados a esse método de recuperação, incluindo o custo computacional na indexação de vetores de alta dimensão.</p><p>Por outro lado, mencionamos que vetores esparsos se comprimem excepcionalmente bem. Assim, discutimos o Learned Sparse Encoder da Elastic, que expande as consultas de pesquisa para incluir termos relevantes não presentes na consulta original.</p><p>Não existe uma solução única que sirva para todos quando se trata de pesquisa. Cada método de recuperação tem seus pontos fortes e desafios. Portanto, também discutimos o conceito de busca híbrida.</p><p>Como você pôde ver, com o Elasticsearch, você pode ter o melhor dos dois mundos: busca lexical tradicional e busca vetorial!</p><p>Pronto para começar? Confira o <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">notebook Python</a> disponível e inicie um <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;cta=cloud-registration&amp;tech=trial&amp;plcmt=article%20content&amp;pg=search-labs">teste gratuito do Elastic Cloud</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch</guid>
    <category><![CDATA[Banco de dados vetorial]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Linguagens de consulta]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd7e961f594005e7/6a17d80f033c8d981f6bb009/d240bfef29e9d432069059b312dd044eb76eec6c-1440x840.png" length="0" type="image/png"/>
    <pubDate>Tue, 03 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>