<?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/es/search-labs/blog/category/python-programming</link>
    </image>
    <link>https://www.elastic.co/es/search-labs/blog/category/python-programming</link>
    <atom:link href="https://www.elastic.co/es/search-labs/rss/category/python-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[es]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 16:08:14 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Búsqueda multimodal de picos montañosos con Elasticsearch y SigLIP-2 ]]></title>
    <description><![CDATA[Aprende a implementar búsqueda multimodal texto a imagen e imagen a imagen usando incrustaciones SigLIP-2 y búsqueda vectorial kNN en Elasticsearch. Enfoque del proyecto: encontrar fotos del pico del Monte Ama Dablam durante una travesía por el Everest.]]></description>
    <content:encoded><![CDATA[<p>¿Alguna vez quisiste buscar en tu álbum de fotos por significado? Prueba con preguntas como "muéstrame mis fotos donde llevo una chaqueta azul y estoy sentado en un banco", "muéstrame fotos del Monte Everest" o "sake y sushi". Toma una taza de café (o tu bebida favorita) y sigue leyendo. En este blog, te mostramos cómo construir una aplicación de búsqueda híbrida multimodal. Multimodal significa que la app puede entender y buscar entre diferentes tipos de entradas—texto, imágenes y audio—no solo palabras. Híbrido significa que combina técnicas como la coincidencia de palabras clave, la búsqueda vectorial kNN y el geofencing para ofrecer resultados más precisos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="Recopilatorio de diferentes fotos de las cumbres de montaña de la ruta al Everest." /><p>Para lograrlo, empleamos SigLIP-2 de Google para generar incrustaciones vectoriales tanto para imágenes como para texto, y las almacenamos en la base de datos vectorial Elasticsearch. En el momento de la consulta, convertimos la entrada de búsqueda, texto o imagen, en incrustaciones y realizamos búsquedas rápidas con vectores kNN para obtener resultados. Esta configuración permite una búsqueda eficiente de texto a imagen y de imagen a imagen. Una interfaz Streamlit da vida a este proyecto proporcionándonos una interfaz no solo para hacer búsquedas por texto para encontrar y ver las fotos coincidentes del álbum, sino también para identificar la cima de la montaña a partir de la imagen subida y ver otras fotos de esa montaña en el álbum.
También cubrimos los pasos que seguimos para mejorar la precisión de las búsquedas, junto con consejos y trucos prácticos. Para una exploración más profunda, proporcionamos un <a href="https://github.com/navneet83/multimodal-mountain-peak-search">repositorio de GitHub</a> y un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">cuaderno de Colab</a>.</p><h2>Cómo empezó todo</h2><p>Esta entrada del blog fue inspirada por un niño de 10 años que me pidió que les mostrara todas las fotos del Monte Ama Dablam de mi travesía al campamento base del Everest. Mientras revisábamos el álbum de fotos, también me pidieron que identificara varias otras cumbres montañosas, algunas de las cuales no podía nombrar.</p><p>Eso me dio la idea de que esto puede ser un proyecto divertido de visión por computadora. Lo que queríamos conseguir:</p><ul><li><p>Encuentra fotos de un pico montañoso por nombre</p></li><li><p>Adivina el nombre de la cima de la montaña a partir de una imagen y también encuentra picos similares en el álbum de fotos</p></li><li><p>Haz que las consultas conceptuales funcionen (<em>persona</em>, <em>río</em>, <em>banderas de oración</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>Formando el equipo soñado: SigLIP-2, Elasticsearch y Streamlit</h2><p>Pronto quedó claro que, para que esto funcionara, tendríamos que convertir tanto el texto ("Ama Dablam") como las imágenes (fotos de mi álbum) en vectores que puedan comparar de forma significativa, es decir, en el mismo espacio vectorial. Una vez que hacemos eso, la búsqueda es simplemente "encontrar a los vecinos más cercanos".</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch y Streamlit: equipo de ensueño." /><p>Para generar incrustaciones de imágenes, usamos un<a href="https://huggingface.co/blog/vlms-2025"> codificador multilingüe visión-lenguaje</a>, de modo que una foto de una montaña y una frase como "Ama Dablam" caen en el mismo espacio vectorial.</p><p><a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2</strong></a>, lanzado recientemente por Google, encaja bien aquí. Puede generar incrustaciones sin entrenamiento específico de tarea (un ajuste <strong>de cero disparos</strong> ) y funciona bien para nuestro caso: fotos sin etiqueta y picos con diferentes nombres e idiomas. Como está capacitado para la coincidencia de imágenes de texto ↔, una foto de montaña de la travesía y un breve prompt de texto acaban siendo similares a incrustaciones, incluso cuando el idioma de consulta o la ortografía varían.</p><p>SigLIP-2 ofrece un fuerte equilibrio calidad-velocidad, soporta múltiples resoluciones de entrada y funciona tanto en CPU como en GPU. El SigLIP-2 está diseñado para ser más robusto para fotos exteriores en comparación con modelos anteriores como el CLIP original. Durante nuestras pruebas, SigLIP-2 generó resultados fiables de forma constante. Además, está muy bien apoyado, lo que lo convierte en la opción obvia para este proyecto.</p><p>A continuación, necesitamos una base de datos vectorial para almacenar los embebidos y la búsqueda de potencia. Debe soportar no solo búsqueda kNN coseno sobre incrustaciones de imágenes, sino también aplicar filtros de geocerca y texto en una sola consulta. Elasticsearch encaja bien aquí: maneja vectores (HNSW kNN en campos dense_vector), soporta búsqueda híbrida que combina texto, vectores y consultas geográficas, y ofrece filtrado y ordenación desde el principio. Además, escala horizontalmente, lo que facilita crecer de unas pocas fotos a miles. El cliente oficial <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">de Python de Elasticsearch</a> mantiene la fontanería sencilla y se integra perfectamente con el proyecto. Por último, necesitamos un frontend ligero donde podamos introducir consultas de búsqueda y ver resultados. Para una demostración rápida basada en Python, Streamlit es una opción ideal. Proporciona las primitivas que necesitamos: carga de archivos, una cuadrícula de imágenes responsiva y menús desplegables para ordenar y geovaller. Es fácil de clonar y ejecutar localmente, y también funciona en un cuaderno de Colab.</p><h2>Implementación</h2><h3>Diseño y estrategia de indexación de Elasticsearch</h3><p>Emplearemos dos índices para este proyecto: <code>peaks_catalog</code> y <code>photos</code>.</p><h4>Peaks_catalog índice</h4><p>Este índice sirve como un catálogo compacto de picos montañosos prominentes visibles durante la travesía al Campamento Base del Everest. Cada documento de este índice corresponde a una sola cima montañosa, como el Monte Everest. Para cada documento de pico de montaña, almacenamos nombres/alias, coordenadas opcionales de latitud-longitud y un único vector prototipo construido mediante la mezcla de prompts de texto SigLIP-2 (+ imágenes de referencia opcionales).</p><p><strong>Mapeo indexado:</strong></p><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector/Indexación</p><p>identificación</p><p>palabra clave</p><p>ama-dablam</p><p>Slug/id estable</p><p>—</p><p>Nombres</p><p>Subcampo texto + palabra clave</p><p>["Ama Dablam","Amadablam"]</p><p>Alias / nombres multilingües; names.raw para filtros exactos</p><p>—</p><p>Latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>Coordenadas GPS de pico como combinación de latitud/longitud (opcional)</p><p>—</p><p>elev_m</p><p>entero</p><p>6812</p><p>Elevación (opcional)</p><p>—</p><p>text_embed</p><p>dense_vector</p><p>768</p><p>Prototipo mezclado (prompts y, opcionalmente, 1–3 imágenes de referencia) para este pico</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>Este índice se emplea principalmente para búsquedas imagen a imagen, como identificar picos montañosos a partir de imágenes. También empleamos este índice para mejorar los resultados de búsqueda de texto a imagen.</p><p>En resumen, el <code>peaks_catalog</code> transforma la pregunta "¿Qué montaña es esta?" en un problema enfocado del vecino más cercano, separando efectivamente la comprensión conceptual de las complejidades de los datos de imagen.</p><p><strong>Estrategia de indexación para el índice peaks_catalog: </strong>Comenzamos creando una lista de los picos más destacados visibles durante la travesía por el EBC. Para cada pico, almacenamos su ubicación geográfica, nombre, sinónimos y elevación en un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">archivo yaml</a>. El siguiente paso es <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">generar la incrustación</a> de cada pico y almacenarla en <code>text_embed</code> campo. Para generar incrustaciones robustas, empleamos la siguiente técnica:</p><ul><li><p>Crea un prototipo de texto usando:</p><ul><li><p>Nombres de los 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 varios prompts diferentes para intentar responder a la misma pregunta), por ejemplo:</p><ul><li><p>"una foto natural de la cima de la montaña {name} en el Himalaya, Nepal"</p></li><li><p>"{name} pico emblemático en la región del Khumbu, paisaje alpino"</p></li><li><p>"{name} cima de montaña, nieve, cresta rocosa"</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">anticoncepto</a> opcional (indicar a SigLIP-2 en qué no debe coincidir): resta un pequeño vector para "pintura, ilustración, afiche, mapa, logo" para inclinarnos hacia fotos reales.</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">, crea un prototipo de imagen</a> si se proporcionan imágenes de referencia del pico.</p></li></ul><p>Luego <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">mezclamos el prototipo de texto e imagen</a> para generar la incrustación final. Finalmente, el documento está <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">indexado</a> con todos los campos requeridos:</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 ejemplo de <code>peaks_catalog</code> índice:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Un documento de ejemplo del índice de peaks_catalog en Elasticsearch." /><h4>Índice de fotos</h4><p>Este índice principal almacena información detallada sobre todas las fotos del álbum. Cada documento representa una sola foto, que contiene la siguiente información:</p><ul><li><p>Camino relativo a la foto del álbum. Esto puede usar para ver la imagen correspondiente o cargarla en la interfaz de búsqueda.</p></li><li><p>GPS e información horaria de la imagen.</p></li><li><p>Vector denso para codificación de imágenes generado por SigLIP-2.</p></li><li><p><code>predicted_peaks</code> Eso nos permite filtrar por nombre de pico.

<strong>Mapeo de índices</strong></p></li></ul><p>Campo</p><p>Tipo</p><p>Ejemplo</p><p>Propósito/Notas</p><p>Vector / Indexación</p><p>camino</p><p>palabra clave</p><p>datos/imágenes/IMG_1234.HEIC</p><p>Cómo se abre la interfaz en miniatura/imagen completa</p><p>—</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>Incrustación de imágenes SigLIP-2</p><p>Index:True, Similitud:"Coseno", index_options:{type:"hnsw", m:16, ef_construction:128}</p><p>predicted_peaks</p><p>palabra clave</p><p>["ama-dablam", "pumori"]</p><p>Top-K suposiciones en el tiempo del índice (filtro UX barato / facet)</p><p>—</p><p>GPS</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>Activa los filtros geográficos</p><p>—</p><p>shot_time</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>Tiempo de captura: ordenar/filtrar</p><p>—</p><p><strong>Estrategia de indexación para el índice de fotos: </strong>Para cada foto del álbum, hacemos lo siguiente:
Extrae <code>shot_time</code> de imagen y <code>gps</code> información <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">de los metadatos de las imágenes</a>.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">Embedding de imagen SigLIP-2</a>: pasar la imagen por el modelo y normalizar el vector en modo L2. Almacena el embedding en <code>clip_image</code> campo.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">Predecir los picos</a> y almacenarlos en el campo <code>predicted_peaks</code> . Para ello, primero tomamos el vector de imagen de la foto generado en el paso anterior y luego ejecutamos una búsqueda rápida kNN en el campo text_embed en el índice de <code>peaks_catalog</code> . Mantenemos los 3-4 primeros picos e ignoramos el resto.</p></li><li><p>Calculamos el campo <code>_id</code> haciendo un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">hash</a> en el nombre y el camino de la imagen. Esto cerciora que no acabemos con duplicados tras varias partidas.</p></li></ul><p>Una vez que determinamos todos los campos para la foto, los documentos fotográficos se indexan en lotes usando <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">indexación masiva</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>Documento de ejemplo del índice de fotos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Un documento de ejemplo del índice de fotos en Elasticsearch." /><p>En resumen, el índice de las fotos es el almacén rápido, filtrable y listo para kNN de todas las fotos del álbum. Su mapeo es mínimo a propósito: la estructura justa para recuperar rápidamente, mostrar limpiamente y recortar los resultados por espacio y tiempo. Este índice sirve tanto para casos de búsqueda como para el uso. Aquí se <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">puede encontrar un</a> script en Python para crear ambos índices.</p><p>La visualización de mapas de Kibana que aparece a continuación muestra documentos del álbum de fotos como puntos verdes y picos montañosos del índice de <code>peaks_catalog</code> como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta del campamento base del Everest.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="Una visualización de Kibana muestra documentos del álbum de fotos como puntos verdes y las cumbres de montaña del índice de peaks_catalog como triángulos rojos, con los puntos verdes alinear bien con el sendero de la ruta de trekking al Campamento Base del Everest." /><h2>Casos de uso de búsqueda</h2><p><strong>Buscar por nombre (texto a imagen):</strong> Esta función permite a los usuarios localizar fotos de picos montañosos (e incluso conceptos abstractos como "banderas de oración") mediante consultas de texto. Para lograrlo, la entrada de texto <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">se convierte en un vector de texto</a> usando SigLIP-2. Para una generación robusta de vectores de texto, empleamos la misma estrategia que se usa para crear incrustaciones de texto en el índice <code>peaks_catalog</code> : <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">combinar</a> la entrada de texto con un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100">pequeño conjunto de prompts</a>, restar un<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> pequeño vector anti-concepto</a> y aplicar la <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">normalización L2</a> para producir el vector de consulta final. A continuación, se ejecuta una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">consulta</a> kNN en el campo <code>photos.clip_image</code> para recuperar los picos que coinciden con la parte superior, basar en la similitud coseno para encontrar las imágenes más cercanas. Opcionalmente, los resultados de búsqueda pueden ser más 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 y de fecha, y/o un filtro de <code>photos.predicted_peaks</code> términos como parte de la consulta (ver ejemplos de consultas más abajo). Esto ayuda a excluir picos que se parecen y que en realidad no se ven durante la travesía.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Qué tan multimodal funciona, la búsqueda por nombre (texto a imagen) en Elasticsearch." /><p><strong>Consulta de Elasticsearch con 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>Buscar por imagen (imagen a imagen):</strong> Esta función nos permite identificar una montaña en una imagen y encontrar otras imágenes de esa misma montaña dentro del álbum. Cuando se sube una imagen, el codificador de imagen SigLIP-2 la procesa para generar un <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">vector de imagen</a>. A continuación, se realiza una <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">búsqueda kNN</a> en el campo <code>peaks_catalog.text_embed</code> para identificar los nombres de picos que mejor coinciden. Posteriormente, <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">se genera un vector de texto</a> a partir de estos nombres de picos coincidentes, y se realiza otra <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">búsqueda kNN</a> en el índice de fotos para localizar las imágenes correspondientes.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Cómo funciona la búsqueda multimodal por imagen (imagen a imagen) en Elasticsearch." /><p><strong>Consulta Elasticsearch:</strong></p><p>Paso 1: Encontrar los nombres de picos que coincidan</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>Paso 2: Realiza una búsqueda en el índice de <code>photos</code> para encontrar las imágenes coincidentes (misma consulta que se muestra en el caso de búsqueda text-to-image):</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>Interfaz Streamlit</h2><p>Para unir todo, creamos una interfaz sencilla de Streamlit que nos permite realizar ambos casos de uso de búsqueda. El riel izquierdo muestra una lista desplazable de picos (agregados a partir de <code>photos.predicted_peaks</code>) con casillas de verificación y un minimapa/filtro geográfico. En la parte superior hay una caja <strong>de búsqueda por nombre</strong> y un botón <strong>de identificación por subida de fotos</strong> . El panel central presenta una cuadrícula en miniatura sensible que muestra puntajes kNN, insignias de picos predichos y tiempos de captura. Cada imagen incluye un botón <strong>para ver imagen</strong> para vistas previas en resolución completa.</p><p><strong>Busca subiendo una imagen:</strong> Predecimos el pico y encontramos picos coincidentes del álbum de fotos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="Una interfaz sencilla de iluminación streamlit que permite buscar tanto texto a imagen como imagen a imagen multimodal los picos del Monte Ama Dablam." /><p><strong>Buscar por texto</strong>: Encuentra los picos coincidentes en el álbum a partir del texto</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="Cómo buscar un pico del Monte Everest buscando por texto en la biblioteca de picos de montaña." /><h2>Conclusión</h2><p>¿Qué empezó como <em>, ¿podemos simplemente ver las </em>imágenes<em><strong>de Ama Dablam</strong></em><em>?</em> se convirtió en un pequeño sistema de <strong>búsqueda multimodal</strong> funcional. Tomamos fotos en bruto de trekking, las convertimos en <strong>incrustaciones SigLIP-2</strong> y usamos <strong>Elasticsearch</strong> para hacer <strong>kNN</strong> rápido sobre vectores, además de filtros geo/temporales simples para mostrar las imágenes <em>correctas por significado</em>. En el camino, separamos las preocupaciones con dos índices: un pequeño <code>peaks_catalog</code> de prototipos combinados (para identificación) y un índice escalable de <code>photos</code> de vectores de imagen y EXIF (para recuperación). Es práctica, reproducible y fácil de ampliar.</p><p>Si quieres afinarla, hay algunos ajustes con los que puedes experimentar:</p><ul><li><p><strong>Ajustes de tiempo de consulta:</strong> <code>k</code> (cuántos vecinos quieres que devuelvan) y <code>num_candidates</code> (qué ancho buscar antes del puntaje final). Estos ajustes se discuten en el <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">blog aquí</a>.</p></li><li><p><strong>Ajustes de tiempo de índice:</strong> <code>m</code> (conectividad de grafos) y <code>ef_construction</code> (precisión en tiempo de construcción frente a memoria). Para consultas, experimenta también con <code>ef_search</code> : más alto suele significar mejor recordación con cierto compromiso de latencia. Consulta <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">este blog</a> para más detalles sobre estos entornos.</p></li></ul><p>De cara al futuro, los modelos nativos/reclasificadores para búsqueda <strong>multimodal</strong> y <strong>multilingüe</strong> pronto llegarán al ecosistema Elastic, lo que debería mejorar aún más la recuperación de imágenes/texto y el ranking híbrido desde el primer momento.<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>Si quieres probar esto tú mismo:</p><ul><li><p><strong>Repositorio de 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>Inicio rápido de 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>Con esto, nuestro viaje llegó a su fin y es hora de volar de regreso. Espero que esto te fue útil y si lo rompes (o lo mejoras), me encantaría saber qué cambiaste.</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[Base de datos vectorial]]></category>
    <category><![CDATA[Búsqueda 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[Capacitar modelos LTR en Elasticsearch con listas de juicio basadas en datos de comportamiento del usuario]]></title>
    <description><![CDATA[Aprende a usar datos de RBU para crear listas de juicios que automatizen el entrenamiento de tus modelos Learning to Rank (LTR) en Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>Un gran reto al usar <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><em><strong>modelos Learning-to-rank</strong></em></a> es crear una <a href="https://www.elastic.co/search-labs/blog/judgment-lists"><em><strong>lista de juicios</strong></em></a> de alta calidad para capacitar el modelo. Tradicionalmente, este proceso implica una evaluación <em><strong>manual</strong></em> de la relevancia de los documentos de consulta para asignar una calificación a cada uno. Es un proceso lento que no escala bien y es difícil de mantener (imagina tener que actualizar una lista con cientos de entradas a mano).</p><p>Ahora, ¿y si pudiéramos usar interacciones reales de usuario con nuestra aplicación de búsqueda para crear estos datos de entrenamiento? Emplear datos <a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em><strong>de RBU</strong></em></a> nos permite hacer precisamente eso. Crear un sistema automático que pueda capturar y usar nuestras búsquedas, clics y otras interacciones para generar una lista de juicios. Este proceso puede escalar y repetir mucho más fácilmente que una interacción manual y tendería a dar mejores resultados. En este blog, exploraremos cómo podemos consultar datos de RBU almacenados en Elasticsearch para calcular señales significativas que generen un conjunto de datos de entrenamiento para un 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>Puedes encontrar el experimento completo </strong></em><a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"><em><strong>aquí</strong></em></a><em><strong>.</strong></em></p><h2>Por qué los datos de RBU pueden ser útiles para capacitar tu modelo de LTR</h2><p>Los datos de la RBU ofrecen varios beneficios sobre una anotación manual:</p><ul><li><p><strong>Volumen:</strong> Dado que los datos de RBU provienen de interacciones reales, podemos recopilar muchos más datos de los que generamos manualmente. Esto suponiendo que tengamos suficiente tráfico para generar estos datos, por supuesto.</p></li><li><p><strong>Intención real del usuario:</strong> Tradicionalmente, una lista de juicios manual proviene de una evaluación experta de los datos disponibles. Por otro lado, los datos de RBU reflejan el comportamiento real de los usuarios. Esto significa que podemos generar mejores datos de entrenamiento que mejorarán la precisión de nuestro sistema de búsqueda, porque se basan en cómo los usuarios interactúan y encuentran valor en tu contenido, más que en suposiciones teóricas sobre lo que debería ser relevante.</p></li><li><p><strong>Actualizaciones continuas:</strong> Las listas de juicios necesitan actualizar con el tiempo. Si los creamos a partir de datos de la RBU, podemos tener datos actuales que resulten en listas de juicios actualizadas.</p></li><li><p><strong>Rentabilidad:</strong> Sin la carga de crear manualmente una lista de juicios, el proceso puede repetir eficientemente cualquier número de veces.</p></li><li><p><strong>Distribución natural de consultas</strong>: Los datos de RBU representan consultas reales de usuario, lo que puede impulsar cambios más profundos. Por ejemplo, ¿nuestros usuarios usan lenguaje natural para buscar en nuestro sistema? Si es así, podríamos querer implementar un enfoque de búsqueda semántica o de búsqueda híbrida.</p></li></ul><p>Sin embargo, viene con algunas advertencias:</p><ul><li><p><strong>Amplificación de polarización: </strong>El contenido popular tiene más probabilidades de recibir clics, simplemente porque tiene más visibilidad. Así que esto podría acabar amplificando los productos populares y posiblemente ahogando opciones mejores.</p></li><li><p><strong>Cobertura incompleta: </strong>El contenido nuevo carece de interacciones, por lo que puede ser difícil que los resultados sean altos. Las consultas raras también pueden carecer de suficientes puntos de datos para crear datos de entrenamiento significativos.</p></li><li><p><strong>Variaciones estacionales:</strong> Si esperas que el comportamiento del usuario cambie significativamente con el tiempo, los datos históricos pueden no decirte mucho sobre qué es un buen resultado.</p></li><li><p><strong>Ambigüedad de la tarea:</strong> Un clic no siempre garantiza que el usuario encontró lo que buscaba.</p></li></ul><h2>Cálculo de calificaciones</h2><h3>Calificaciones para el entrenamiento a largo plazo</h3><p>Para capacitar modelos LTR, necesitamos proporcionar alguna representación numérica de cuán relevante es un documento para una consulta. En nuestra implementación, este número es un puntaje continuo que va de 0,0 a 5,0+, donde puntajes más altos indican mayor relevancia.</p><p>Para mostrar cómo funciona este sistema de calificación, consideremos este ejemplo creado manualmente:</p><p>Búsqueda</p><p>Contenido del documento</p><p>Grado</p><p>Explicación</p><p>"La mejor receta de pizza"</p><p>"Receta auténtica de masa de pizza italiana con fotos paso a paso"</p><p>4.0</p><p>Muy relevante, exactamente lo que el usuario busca</p><p>"La mejor receta de pizza"</p><p>"Historia de la pizza en Italia"</p><p>1.0</p><p>Algo en el tema, trata sobre pizza pero no es una receta</p><p>"La mejor receta de pizza"</p><p>"Receta rápida de pizza de 15 minutos para principiantes"</p><p>3.0</p><p>Relevante, un buen resultado pero quizá no cumpla con la "mejor" receta.</p><p>"La mejor receta de pizza"</p><p>"Guía de mantenimiento de autos"</p><p>0.0</p><p>No tiene nada que ver, completamente ajeno a la consulta</p><p>Como podemos ver aquí, la calificación es una representación numérica de cuán relevante es un documento para nuestra consulta de ejemplo de "mejor receta de pizza". Con estos puntajes, nuestro modelo de LTR puede aprender qué documentos deben presentar mejor en los resultados.</p><p>Cómo calcular las notas es el núcleo de nuestro conjunto de datos de entrenamiento. Existen <a href="https://www.elastic.co/search-labs/blog/judgment-lists">múltiples enfoques</a> para hacerlo, cada uno con sus propias fortalezas y debilidades. Por ejemplo, podríamos asignar un puntaje binario de 1 para el 0 relevante para no relevante o simplemente contar el número de clics en un documento resultante para cada consulta.</p><p>En esta entrada del blog, emplearemos un enfoque diferente, <em><strong>teniendo en cuenta el comportamiento del usuario como nuestra entrada y calculando un número de calificación como resultado</strong></em>. También corregiremos el sesgo que podría surgir por el hecho de que los resultados más altos tienden a ser más clicados, independientemente de la relevancia del documento.</p><h2>Cálculo de las calificaciones - algoritmo COEC</h2><p>El algoritmo COEC (<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">Clics over Expected Clics</a>) es una metodología para calcular las calificaciones de juicio a partir de clics de los usuarios.
Como mencionamos antes, los usuarios tienden a hacer clic en resultados de mejor posición incluso si el documento no es el más relevante para la consulta; esto se <a href="https://eugeneyan.com/writing/position-bias/">llama sesgo de posición</a>. La idea central para usar el algoritmo COEC es que no todos los clics son igual de significativos; Un clic en un documento en la posición 10 indica que el documento es mucho más relevante para la consulta que un clic en un documento en la posición 1. Para citar el artículo de investigación sobre el algoritmo COEC (enlazado arriba):</p><p><em>"Es bien sabido que la tasa de clics (CTR) de los resultados de búsqueda o anuncios disminuye significativamente dependiendo de la posición de los resultados."</em></p><p>Puedes leer más sobre el sesgo de <a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">posición aquí</a>.</p><p>Para abordar esto con el algoritmo COEC, seguimos estos pasos:</p><p><strong>1. Establecer líneas base de posición:</strong> Calculamos la tasa de clics (CTR) para cada posición de búsqueda del 1 al 10. Esto significa que determinamos qué porcentaje de usuarios suelen hacer clic en la posición 1, posición 2, y así sucesivamente. Este paso captura el sesgo natural de posición de los usuarios.

Calculamos el CTR usando:</p><p>Dónde:</p><p>p = Posición. Del 1 al 10
Cp = Total de clics (en cualquier documento) en la posición p en todas las consultas
Ip = Total de impresiones: Cuántas veces apareció cualquier documento en la posición p en todas las consultas</p><p>Aquí, esperamos que los puestos más altos consigan más clics.</p><p><strong>2.</strong> <strong>Calcular los clics esperados (EC):</strong></p><p>Esta métrica establece cuántos clics "debería" recibir un documento en función de las posiciones en las que apareció y el CTR para esas posiciones. Calculamos la EC usando:</p><p>Dónde:</p><p>Qd = Todas las consultas donde apareció el documento d
pos(d,q)= Posición del documento d en los resultados de la consulta q</p><p>3. <strong>Contar clics reales: </strong>Contamos el total real de clics que un documento recibió en todas las consultas donde apareció, en adelante llamado <strong>A(d).</strong></p><p>4. <strong>Calcular el puntaje del COEC:</strong> Esta es la proporción de clics reales (A(d)) sobre los clics esperados (EC(d)):</p><p>Esta métrica normaliza para el sesgo de posición como este:</p><ul><li><p>Un puntaje de 1,0 significa que el documento funcionó exactamente como se espera dadas las posiciones en las que apareció.</p></li><li><p>Un puntaje superior a 1,0 significa que el documento tuvo un mejor rendimiento de lo esperado al observar sus posiciones. Así que este documento es más relevante para la consulta.</p></li><li><p>Un puntaje inferior a 1,0 significa que el documento tuvo un rendimiento peor de lo esperado al observar sus posiciones. Así que este documento es menos relevante para la consulta.</p></li></ul><p><em><strong>El resultado final es un número de calificación que captura lo que los usuarios buscan, teniendo en cuenta expectativas basadas en la posición extraídas de interacciones reales con nuestro sistema de búsqueda.</strong></em></p><h2>Implementación técnica</h2><p>Crearemos un script para crear una lista de juicios y capacitar un modelo de LTR.</p><p>La entrada para este script es los datos de la RBU indexados en Elastic (consultas y eventos).</p><p>La salida es una lista de juicios en un archivo CSV generada a partir de estos documentos de RBU empleando el algoritmo COEC. Esta lista de juicios puede usar con <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a> para extraer características relevantes y capacitar un modelo LTR.</p><h3>Inicio rápido</h3><p>Para generar una lista de juicios a partir de los datos de muestra de este blog, puedes seguir estos pasos:</p><p>1. Clonar el repositorio:</p>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog<p>2. Instalar las librerías necesarias</p><p>Para este guion, necesitamos las siguientes librerías:</p><ul><li><p><em>Pandas</em>: Para salvar la lista de juicios</p></li><li><p><em>elasticsearch</em>: Para obtener los datos de RBU de nuestro despliegue de Elastic</p></li></ul><p>También necesitamos Python 3.11</p>pip install -r requirements.txt<p>3. Actualizar las variables de entorno para tu despliegue de Elastic en un <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">archivo .env</a></p><ul><li><p>ES_HOST</p></li><li><p>API_KEY</p></li></ul><p>Para agregar las variables de entorno, emplea:</p>source .env<p>4. Crear el ubi_queries, ubi_events índices y subir los datos de muestra. Ejecuta el archivo setup.py:</p>python setup.py<p>5. Ejecutar el script en Python:</p>python judgement_list-generator.py<p>Si sigues estos pasos, deberías ver un archivo nuevo llamado judgment_list.csv que se ve así:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94317eda8f7af194/6a170aa46f7f04542f914821/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" alt="" /><p>Este script calcula las calificaciones aplicando el algoritmo COEC discutido antes de usar la función <strong>calculate_relevance_grade()</strong> que se muestra a continuación.</p><h2>Arquitectura de datos</h2><h3>Consultas Ubi</h3><p>Nuestro índice de consultas de RBU contiene información sobre las consultas ejecutadas en nuestro sistema de búsqueda. Este es un documento de ejemplo:</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>Aquí podemos ver datos del usuario (client_id), de los resultados de la consulta (query_response_object_ids) y de la propia consulta (marca de tiempo, user_query)</p><h3>Eventos de Ubi Clic</h3><p>Nuestro índice de ubi_events contiene datos de cada vez que un usuario hizo clic en un documento en los resultados. Este es un documento de ejemplo:</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 generación de lista de sentencias</h2><h3>Resumen general de la escritura</h3><p>Este script automatiza la generación de la lista de juicios empleando datos de RBU de los eventos de Consultas y Clics almacenados en Elasticsearch. Ejecuta estas tareas:</p><ul><li><p>Recupera y procesa los datos de la RBU en Elasticsearch.</p></li><li><p>Correlaciona los eventos de RBU con sus consultas.</p></li><li><p>Calcula el CTR para cada posición.</p></li><li><p>Calcula los clics esperados (EC) para cada documento.</p></li><li><p>Cuenta los clics reales de cada documento.</p></li><li><p>Calcula el puntaje del COEC para cada par consulta-documento.</p></li><li><p>Genera una lista de juicios y la escribe en un archivo CSV.</p></li></ul><p>Repasemos cada función:</p><h3>connect_to_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>Esta función devuelve un objeto cliente de Elasticsearch usando la clave host y API.</p><h3>fetch_ubi_data()</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 función es la capa de extracción de datos; se conecta con Elasticsearch para obtener consultas de RBU usando una consulta match_all y filtra los eventos de RBU para obtener solo eventos 'CLICK_THROUGH'.</p><h3>process_ubi_data()</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 función se encarga de la generación de la lista de juicios. Comienza a procesar los datos de RBU asociando eventos y consultas de RBU. Luego llama a la función calculate_relevance_grade() para cada par documento-consulta para obtener las entradas de la lista de juicios. Finalmente, devuelve la lista resultante como un dataframe pandas.</p><h3>calculate_relevance_grade()</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 es la función que implementa el algoritmo COEC. Calcula el CTR para cada posición, luego compara los clics reales de un par documento-consulta, y finalmente calcula el puntaje real del COEC para cada una.</p><h3>generate_judgment_statistics()</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>Genera estadísticas útiles a partir de la lista de valores, como el total de consultas, el total de documentos únicos o la distribución de calificaciones. Esto es puramente informativo y no cambia la lista de sentencias resultante.</p><h2>Resultados e impacto</h2><p>Si sigues las instrucciones de la sección de Inicio rápido, deberías ver un archivo CSV resultante que contiene una lista de juicios con 320 entradas (puedes ver una <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">salida de ejemplo</a> en el repositorio). Con estos campos:</p><ul><li><p>qid: ID único de la consulta</p></li><li><p>DocID: Identificador único para un documento resultante</p></li><li><p>Grado: la calificación calculada para el par consulta-documento</p></li><li><p>consulta: La consulta de usuario</p></li></ul><p> Veamos los resultados de la consulta "recetas italianas":</p><p>Qid</p><p>docid</p><p>grado</p><p>Búsqueda</p><p>Q1-recetas-italianas</p><p>recipe_pasta_basics</p><p>0.0</p><p>Recetas italianas</p><p>Q1-recetas-italianas</p><p>recipe_pizza_margherita</p><p>3.333333</p><p>Recetas italianas</p><p>Q1-recetas-italianas</p><p>recipe_risotto_guide</p><p>10.0</p><p>Recetas italianas</p><p>Q1-recetas-italianas</p><p>recipe_french_croissant</p><p>0.0</p><p>Recetas italianas</p><p>Q1-recetas-italianas</p><p>recipe_spanish_paella</p><p>0.0</p><p>Recetas italianas</p><p>Q1-recetas-italianas</p><p>recipe_greek_moussaka</p><p>1.875</p><p>Recetas italianas</p><p>Podemos ver en los resultados que para la consulta "recetas italianas":</p><ul><li><p>La receta de risotto es sin duda el mejor resultado para la consulta, recibiendo 10 veces más clics de lo esperado</p></li><li><p>Pizza Margherita también es un gran resultado.</p></li><li><p>La mousaka griega (sorprendentemente) también es un buen resultado y rinde mejor de lo que su posición en los resultados sugeriría. Esto significa que algunos usuarios que buscaban recetas italianas se interesaron por esta receta. Quizá estos usuarios estén interesados en platos mediterráneos en general. Al final, esto nos dice que podría ser un buen resultado para mostrar bajo los otros dos partidos 'mejores' que mencionamos antes.</p></li></ul><h2>Conclusión</h2><p>Emplear datos de RBU nos permite automatizar el entrenamiento de modelos de LTR, creando listas de juicios de alta calidad de nuestros propios usuarios. Los datos de RBU proporcionan un gran conjunto de datos que refleja cómo se está empleando nuestro sistema de búsqueda. Al emplear el algoritmo de la COEC para generar las calificaciones, tenemos en cuenta el sesgo inherente y, al mismo tiempo, refleja lo que el usuario considera un mejor resultado. El método descrito aquí puede aplicar a casos de uso reales para ofrecer una mejor experiencia de búsqueda que evoluciona con las tendencias reales 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[Búsqueda geoespacial de Elasticsearch con ES|QL]]></title>
    <description><![CDATA[Búsqueda geoespacial en el lenguaje de consultas Elasticsearch (ES|QL). Elasticsearch cuenta con poderosas características de búsqueda geoespacial, que ahora están llegando a ES|QL por una facilidad de uso mucho mejorada y familiaridad con el OGC.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch tuvo poderosos <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geospatial-analysis.html">capacidades de búsqueda y análisis geoespacial</a> durante muchos años, pero la API era bastante diferente de lo que los usuarios típicos de SIG estaban acostumbrados. En el último año hemos <a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">agregado el ES|Lenguaje de consulta QL</a>, un lenguaje de consulta por tubes tan fácil, o incluso más sencillo, que SQL. Es especialmente adecuado para los casos de búsqueda y observabilidad en los que Elastic destaca. También estamos agregando soporte para búsqueda y análisis geoespacial dentro de ES|QL, lo que lo hace mucho más fácil de usar, especialmente para usuarios que vienen de comunidades SQL o <a href="https://en.wikipedia.org/wiki/Geographic_information_system">GIS</a> .</p><p>Elasticsearch 8.12 y 8.13 llevaron soporte básico para tipos geoespaciales a ES|QL. Esto se mejoró significativamente con la incorporación de capacidades de búsqueda geoespacial en la versión 8.14. Más importante aún, este soporte fue diseñado para ajustar estrechamente al estándar <a href="https://en.wikipedia.org/wiki/Simple_Features">Simple Feature</a> Access del <a href="https://en.wikipedia.org/wiki/Open_Geospatial_Consortium">Open Geospatial Consortium (OGC)</a> empleado por otras bases de datos espaciales como PostGIS, facilitando mucho su uso para expertos en SIG familiarizados con estos estándares.</p><p>En este blog, te mostraremos cómo usar ES|QL para realizar búsquedas geoespaciales y cómo se compara con los equivalentes SQL y Query DSL. También te mostraremos cómo usar ES|QL para realizar uniones espaciales y cómo visualizar los resultados en Kibana Maps. Ten en cuenta que todas las funciones descritas aquí están en "vista previa técnica", y nos encantaría conocer vuestros comentarios sobre cómo podemos mejorarlas.</p><h2>Búsqueda de datos geoespaciales</h2><p>Empecemos con una consulta de ejemplo:</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>Esto realiza una búsqueda de cualquier polígono de límite de ciudad que intersecte con un polígono rectangular alrededor del Aeropuerto Internacional Sanya Phoenix (SYX).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3897df6bed5d6061/6a17d7c2abe0f29eccdfe861/e48bac8f246c8842f2ea97ddd54910045262aeb1-1440x808.png" alt="Búsqueda Geoespacial ESQL" /><p>En un conjunto de datos de ejemplo de aeropuertos, ciudades y límites urbanos, esta búsqueda encuentra el polígono que se cruza y devuelve los campos deseados del documento correspondiente:</p><p>Abbrev</p><p>aeropuerto</p><p>región</p><p>ciudad</p><p>city_location</p><p>SYX</p><p>Sanya Phoenix Internacional</p><p>天涯区</p><p>Sanya</p><p>POINT(109.5036 18.2533)</p><p>¡Eso fue fácil! Ahora compáralo con el tradicional DSL de Elasticsearch Query para la misma 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 consultas son bastante claras en su intención, pero el ES|La consulta QL se parece mucho a SQL. La misma consulta en PostGIS es la siguiente:</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>Mira atrás en el ES|Ejemplo de QL. Muy parecido, ¿verdad?</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>Comprobamos que los usuarios actuales de la API de Elasticsearch encuentran ES|QL mucho más fácil de usar. Ahora esperamos que los usuarios existentes de SQL, especialmente los de SQL Espacial, descubran que ES|QL se siente muy familiar con lo que están acostumbrados a ver.</p><h4>¿Por qué no SQL?</h4><p>¿Y qué pasa con Elasticsearch SQL? Lleva un tiempo existiendo y tiene algunas características geoespaciales. Sin embargo, Elasticsearch SQL se escribía como un envoltorio sobre la API original de Consultas, lo que significaba que solo se soportaban consultas que podían transpilarse a la API original. ES|QL no tiene esta limitación. Ser una pila completamente nueva permite muchas optimizaciones que no eran posibles en SQL. Nuestros benchmarks muestran ES|QL <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/esql/nightly/default/6M">suele ser muy rápido que la API de Consultas</a>, ¡especialmente con agregaciones!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bda964c8b24e36/6a17d7c3e3179155d22d568a/b8b6c2b2e45850d832805ed1e71e522f4955f53c-1440x813.png" alt="polígono-intersección-benchmark" /><h2>Diferencias con SQL</h2><p>Claramente, por el ejemplo anterior, ES|QL es algo similar a SQL, pero hay algunas diferencias importantes. Por ejemplo, ES|QL es un lenguaje de consulta por pipes, que comienza con un comando fuente como FROM y luego encadena todos los comandos posteriores junto con el pipe | carácter. Esto facilita mucho entender cómo cada comando recibe una tabla de datos y realiza alguna acción sobre esa tabla, como filtrar con <code>WHERE</code>, agregar columnas con <code>EVAL</code>, o realizar agregaciones con <code>STATS</code>. En lugar de comenzar con <code>SELECT</code> para definir las columnas de salida finales, puede haber uno o más comandos <code>KEEP</code> , siendo el último el que especifica los resultados finales. Esta estructura simplifica el razonamiento sobre la consulta.</p><p>Centrándonos en el comando <code>WHERE</code> del ejemplo anterior, podemos ver que se parece bastante al ejemplo de 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>Aparte de la diferencia en los caracteres de comillas de cadena, la mayor diferencia está en cómo tipificamos la cadena a un tipo espacial. En PostGIS, usamos el sufijo <code>::geometry</code> , mientras que en ES|QL, usamos el sufijo <code>::geo_shape</code> . Esto se debe a que ES|QL se ejecuta dentro de Elasticsearch, y el operador de typecasting <code>::</code> puede usar para convertir una cadena a cualquiera de los <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#_supported_types">ES| soportadosQL tipia</a>, en este caso, un <code>geo_shape</code>. Además, los tipos <code>geo_shape</code> y <code>geo_point</code> en Elasticsearch implican el sistema de coordenadas espaciales conocido como WGS84, más comúnmente conocido con el número SRID 4326. En PostGIS, esto debe ser explícito, de ahí el uso del prefijo <code>SRID=4326;</code> a la cadena WKT. Si se elimina ese prefijo, el SRID se pondrá en 0, que es más parecido a los tipos de Elasticsearch <code>cartesian_point</code> y <code>cartesian_shape</code>, que no están vinculados a ningún sistema de coordenadas específico.</p><p>Ambos ES|QL y PostGIS también proporcionan sintaxis de funciones de conversión de tipos:</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>Funciones OGC</h2><p>Elasticsearch 8.14 introduce las siguientes cuatro funciones de búsqueda espacial OGC:</p><p>ES|QL</p><p>PostGIS</p><p>Descripción</p><p>ST_INTERSECTS</p><p>ST_Intersects</p><p>Devuelve verdadero si dos geometrías se cruzan, y falso en caso contrario.</p><p>ST_DISJOINT</p><p>ST_Disjoint</p><p>Devuelve verdadero si dos geometrías no se intersectan, y falso en caso contrario. El inverso de ST_INTERSECTS.</p><p>ST_CONTAINS</p><p>ST_Contains</p><p>Devuelve verdadero si una geometría contiene otra, y falso en caso contrario.</p><p>ST_WITHIN</p><p>ST_Within</p><p>Devuelve verdadero si una geometría está dentro de otra, y falso en caso contrario. El inverso de ST_CONTAINS.</p><p>Estas funciones se comportan de forma similar a sus contrapartes PostGIS y se emplean de la misma manera. Por ejemplo, <code>ST_INTERSECTS</code> devuelve verdadero si dos geometrías se intersectan y falso en caso contrario. Si sigues los enlaces de documentación de la tabla anterior, puede que notes que todos los ES|Los ejemplos de QL están dentro de una cláusula de <code>WHERE</code> luego de una cláusula de <code>FROM</code> , mientras que todos los ejemplos de PostGIS usan geometrías literales. De hecho, ambas plataformas soportan el uso de las funciones en cualquier parte de la consulta donde tengan sentido.</p><p>El primer ejemplo en la documentación de PostGIS para <code>ST_INTERSECTS</code> es:</p>SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);
<p>El ES|El equivalente en QL de esto sería:</p>ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)
<p>Fíjate en que no especificamos el SRID en el ejemplo de PostGIS. Esto se debe a que en PostGIS, al usar el tipo <code>geometry</code> , todos los cálculos se realizan en un sistema de coordenadas planas, por lo que si ambas geometrías tienen el mismo SRID, no importa cuál sea el SRID. En Elasticsearch, esto también es cierto para la mayoría de las funciones, sin embargo, hay excepciones donde <code>geo_shape</code> y <code>geo_point</code> emplean cálculos esféricos, como veremos en el próximo blog sobre búsqueda por distancia espacial.</p><h2>ES|Versatilidad en QL</h2><p>Vimos ejemplos arriba de usar funciones espaciales en cláusulas <code>WHERE</code> y en comandos <code>ROW</code> . ¿Dónde más tendrían sentido? Un lugar muy útil es en el comando <code>EVAL</code> . Este comando te permite evaluar una expresión y devolver el resultado. Por ejemplo, determinemos si los centroides de todos los aeropuertos agrupados por el nombre de sus países están dentro de un límite que delimita el 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>Se esperan los resultados: el centroide de los aeropuertos del Reino Unido está dentro del límite del Reino Unido, y no dentro del límite de Islandia, y viceversa:</p><p>centroide</p><p>Recuento</p><p>in_uk</p><p>in_iceland</p><p>within_uk</p><p>within_iceland</p><p>POINT (-21.946634463965893 64.13187285885215)</p><p>1</p><p>falso</p><p>true</p><p>falso</p><p>true</p><p>POINT (-2.597342072712148 54.33551226578214)</p><p>17</p><p>true</p><p>falso</p><p>true</p><p>falso</p><p>POINT (0.04453958108176276 23.74658354606057)</p><p>873</p><p>falso</p><p>falso</p><p>falso</p><p>falso</p><p>De hecho, estas funciones pueden usar en cualquier parte de la consulta donde su firma tenga sentido. Todos toman dos argumentos, que son o bien un objeto espacial literal o un campo de un tipo espacial, y todos devuelven un valor booleano. Una consideración importante es que el sistema de referencia de coordenadas (CRS) de las geometrías debe coincidir, o se devolverá un error. Esto significa que no puedes mezclar <code>geo_shape</code> y <code>cartesian_shape</code> tipos en la misma llamada de función. Sin embargo, puedes mezclar <code>geo_point</code> y <code>geo_shape</code> tipos, ya que el tipo <code>geo_point</code> es un caso especial del tipo <code>geo_shape</code> , y ambos comparten el mismo sistema de referencia de coordenadas. La documentación de cada una de las funciones definidas anteriormente lista las combinaciones de tipos soportadas.</p><p>Además, cualquiera de los dos argumentos puede ser un literal espacial o un campo, en cualquiera de los dos ordenes. Incluso puedes especificar dos campos, dos literales, un campo y un literal, o un literal y un campo. El único requisito es que los tipos sean compatibles. Por ejemplo, esta consulta compara dos campos en el mismo í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>La consulta básicamente pregunta si la ubicación de la ciudad está dentro del límite de la ciudad, lo cual generalmente debería ser cierto, pero siempre hay excepciones:</p><p>cardinalidad</p><p>Recuento</p><p>in_city</p><p>poco</p><p>29</p><p>falso</p><p>mucho</p><p>740</p><p>true</p><p>Una pregunta mucho más interesante sería si la ubicación del aeropuerto está dentro de los límites de la ciudad a la que sirve el aeropuerto. Sin embargo, la ubicación del aeropuerto se encuentra en un índice diferente al que contiene los límites de la ciudad. Esto requiere un método para consultar y correlacionar eficazmente los datos de estos dos índices separados.</p><h2>Unirías espaciales</h2><p>ES|QL no soporta comandos <code>JOIN</code>, pero puedes lograr un caso especial de unión usando el<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 similar a una 'unión izquierda' en SQL. Este comando funciona de forma similar a una 'unión por la izquierda' en SQL, permitiéndote enriquecer resultados de un índice con datos de otro índice basar en una relación espacial entre los dos conjuntos de datos.</p><p>Por ejemplo, enriquezcamos los resultados de una tabla de aeropuertos con información adicional sobre la ciudad a la que sirven encontrando el límite de la ciudad que contiene la ubicación del aeropuerto, y luego realicemos algunas estadísticas sobre los 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>Esto devuelve las 5 regiones con más aeropuertos, junto con el centroide de todos los aeropuertos que tienen regiones coincidentes, y el rango en longitud de la representación WKT de los límites de la ciudad dentro de esas regiones:</p><p>centroide</p><p>Recuento</p><p>min_wkt</p><p>max_wkt</p><p>región</p><p>PUNTO (-32.56093470960719 32.598117914802714)</p><p>90</p><p>207</p><p>207</p><p>nulo</p><p>POINT (-73.94515332765877 40.70366442203522)</p><p>9</p><p>438</p><p>438</p><p>Ciudad de Nueva York</p><p>POINT (-83.10398317873478 42.300230911932886)</p><p>9</p><p>473</p><p>473</p><p>Detroit</p><p>POINT (-156.3020245861262 20.176383580081165)</p><p>5</p><p>307</p><p>803</p><p>Hawai</p><p>POINT (-73.88902732171118 45.57078813901171)</p><p>4</p><p>837</p><p>837</p><p>Montréal</p><p>Entonces, ¿qué pasó realmente aquí? ¿Dónde ocurrió la supuesta <code>JOIN</code> ? El núcleo de la consulta reside en el comando <code>ENRICH</code> :</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
<p>Este comando instruye a Elasticsearch para enriquecer los resultados recuperados del índice de <code>airports</code> y realizar una unión <code>intersects</code> entre el campo <code>city_location</code> del índice original y el campo <code>city_boundary</code> del índice de <code>airport_city_boundaries</code> , que usamos en algunos ejemplos anteriores. Pero parte de esta información no es claramente visible en esta consulta. Lo que sí vemos es el nombre de una política de enriquecimiento <code>city_boundaries</code>, y la información que falta está encapsulada dentro de esa definición de política.</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}
<p>Aquí podemos ver que realizará una consulta <code>geo_match</code> (<code>intersects</code> es el valor predeterminado), el campo a emparejar es <code>city_boundary</code>, y los <code>enrich_fields</code> son los campos que queremos agregar al documento original. Uno de esos campos, el <code>region</code> , se usó como clave de agrupación para el comando <code>STATS</code> , algo que no podríamos hacer sin esta capacidad de 'unión izquierda'. Para más información sobre las pólizas de enriquecimiento, consulte la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">documentación de enriquecimiento</a>. Al leer esos documentos, notarás que describen el uso de los índices enrich para enriquecer datos en tiempo de indexación, configurando pipelines de ingest. Esto no es necesario para ES|QL, ya que el comando <code>ENRICH</code> funciona en el momento de la consulta. Basta con preparar el índice de enriquecimiento con los datos y la política de enriquecimiento necesarios, y luego usar el comando <code>ENRICH</code> en tu ES|Consultas QL.</p><p>También puede que notes que la región más común era <code>null</code>. ¿Qué podría implicar esto? Recuerda que comparé este comando con un 'left join' en SQL, lo que significa que si no se encuentra ningún límite de ciudad coincidente para un aeropuerto, el aeropuerto sigue devolver pero con valores <code>null</code> para los campos del índice de <code>airport_city_boundaries</code> . Resulta que había 89 aeropuertos que no encontraron <code>city_boundary</code>coincidentes, y un aeropuerto con un <code>region</code> campo de <code>null</code>. Esto llevó a un recuento de 90 aeropuertos sin <code>region</code> en los resultados. Otro detalle interesante es la necesidad del comando <code>MV_EXPAND</code> . Esto es necesario porque el comando <code>ENRICH</code> puede devolver múltiples resultados por cada fila de entrada, y <code>MV_EXPAND</code> ayuda a separar estos resultados en varias filas, una para cada resultado. Esto también aclara por qué "Hawái" muestra resultados <code>min_wkt</code> y <code>max_wkt</code> diferentes: había varias regiones con el mismo nombre pero diferentes límites.</p><h2>Kibana Maps</h2><p>Kibana agregó soporte para Spatial ES|QL en la aplicación de Mapas. Esto significa que ahora puedes usar ES|QL para buscar datos geoespaciales en Elasticsearch y visualizar los resultados en un mapa.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Kibana Layers ES|QL" /><p>Hay una nueva opción de capa en el menú de agregar capas, llamada "ES|QL". Como todas las características geoespaciales descritas hasta ahora, esto está en "vista previa técnica". Seleccionar esta opción te permite agregar una capa al mapa basada en los resultados de un ES|Consulta QL. Por ejemplo, podrías agregar una capa al mapa que muestre todos los aeropuertos del mundo.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="Kibana ES|QL - Aeropuertos" /><p>O podrías agregar una capa que muestre los polígonos del índice de <code>airport_city_boundaries</code> , o mejor aún, ¿qué tal esa compleja consulta de <code>ENRICH</code> arriba que genera estadísticas sobre cuántos aeropuertos hay en cada región?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - Estadísticas de la región" /><h2>¿Qué sigue ahora?</h2><p>Quizá notaste que en dos de los ejemplos anteriores incluimos otra función espacial <code>ST_CENTROID_AGG</code>. Esta es una función agregadora empleada en el comando <code>STATS</code> , y la primera de muchas funciones de análisis espacial que planeamos agregar a ES|QL. ¡Escribiremos sobre ello en el blog cuando tengamos más que mostrar!</p><p>Antes de eso, queremos contarte más sobre una función especialmente emocionante en la que trabajamos: la capacidad de realizar búsquedas espaciales, una de las funciones de búsqueda espacial más empleadas en Elasticsearch. ¿Te imaginas cómo sería la sintaxis de las búsquedas a distancia? ¿Quizá similar a una función OGC? ¡Permanece atento al próximo blog de este serial para descubrirlo!</p><p>Alerta de spoilers: Elasticsearch 8.15 acaba de ser lanzado, y la búsqueda por distancia espacial con ES|¡QL está incluido!</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[Evaluación de la relevancia en búsquedas, parte 1: el índice de referencia BEIR]]></title>
    <description><![CDATA[Aprende a evaluar tu sistema de búsqueda en el contexto de una mejor comprensión del índice de referencia BEIR, con consejos y técnicas para mejorar tus procesos de evaluación de búsquedas.]]></description>
    <content:encoded><![CDATA[<p>Esta es la primera de una serie de publicaciones del blog que analizan cómo evaluar tus propios sistemas de búsqueda en el contexto de una mejor comprensión del índice de referencia BEIR. Presentaremos consejos y técnicas específicas para mejorar tus procesos de evaluación de búsqueda en el contexto de una mejor comprensión de BEIR. También presentaremos errores comunes que hacen que la evaluación sea menos confiable. Finalmente, notamos que los LLM ofrecen una poderosa nueva herramienta en el repositorio de los ingenieros de búsqueda por lo que mostraremos, con un ejemplo, cómo se pueden usar para ayudar a evaluar la búsqueda.</p><h2>Comprender el índice de referencia BEIR en la evaluación de la relevancia de búsqueda</h2><p>Para mejorar cualquier sistema, debes poder medir su eficacia. En el contexto de una búsqueda, <a href="https://arxiv.org/abs/2104.08663">BEIR</a> (o, de manera equivalente, la sección de recuperación de la tabla de clasificación de <a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB</a>) se considera el “santo grial” para la comunidad de recuperación de información, y no es de extrañar. Es un índice de referencia muy bien estructurado con sets de datos variados para diferentes tareas. Más específicamente, se cubren las siguientes áreas:</p><ul><li><p>Recuperación de argumentos (ArguAna, Touche2020)</p></li><li><p>QA de dominio abierto (HotpotQA, Natural Questions, FiQA)</p></li><li><p>Recuperación de pasajes (MSMARCO)</p></li><li><p>Recuperación de preguntas duplicadas (Quora, CQADupstack)</p></li><li><p>Verificación de hechos (FEVER, Climate-FEVER, Scifact)</p></li><li><p>Recuperación de información biomédica (TREC-COVID, NFCorpus, BioASQ)</p></li><li><p>Recuperación de entidades (DBPedia)</p></li><li><p>Predicción de citas (SCIDOCS)</p></li></ul><p>Ofrece una única estadística, nDCG@10, relacionada con la capacidad de un sistema para encontrar los documentos más relevantes para cada ejemplo de tarea en los primeros resultados que devuelve. Para un sistema de búsqueda con el que interactúa un ser humano, la relevancia de los resultados principales es fundamental. Sin embargo, hay muchos matices a la hora de evaluar las búsquedas que una sola estadística resumida no refleja.</p><h2>Estructura de un set de datos BEIR</h2><p>Cada benchmark tiene tres artefactos:</p><ul><li><p>el corpus o los documentos que se van a recuperar</p></li><li><p>las búsquedas</p></li><li><p>los juicios de relevancia para las búsquedas (también conocidos como <code>qrels</code>).</p></li></ul><p>Las evaluaciones de relevancia se ofrecen como una puntuación que es igual a cero o mayor. Las puntuaciones no nulas indican que el documento está algo relacionado con la búsqueda.</p><p>Set de datos</p><p>Tamaño del corpus</p><p>#Búsquedas en el conjunto de pruebas</p><p>#qrels etiquetados positivamente</p><p>#qrels igual a cero</p><p>#duplicados en el 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>Preguntas naturales</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>1000</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 (suma)</p><p>457,199</p><p>13 145</p><p>23,703</p><p>0</p><p>0</p><p><strong>Tabla 1</strong>: Estadísticas de los sets de datos. Los números se calcularon en la parte de prueba de los sets de datos (<code>dev</code> para <code>MSMARCO</code>).</p><p>La <strong>Tabla 1</strong> presenta algunas estadísticas para los sets de datos que comprenden el índice de referencia <code>BEIR</code>, como la cantidad de documentos en el corpus, la cantidad de búsquedas en el set de datos de prueba y la cantidad de pares positivos/negativos (búsqueda, documento) en el archivo <code>qrels</code>. Con una rápida mirada a los datos podemos inferir inmediatamente lo siguiente:</p><ul><li><p>La mayoría de los sets de datos no contienen relaciones negativas en el archivo <code>qrels</code>, es decir, puntuaciones cero, lo que indicaría explícitamente que los documentos son irrelevantes para la búsqueda dada.</p></li><li><p>La cantidad promedio de relaciones de documentos por búsqueda (<code>#qrels</code> / <code>#queries</code>) varía desde 1.0 en el caso de <code>ArguAna</code> hasta 493.5 (<code>TREC-COVID</code>), pero con un valor de <code>&lt;</code>5 para la mayoría de los casos.</p></li><li><p>Algunos sets de datos tienen documentos duplicados en el corpus, lo que en algunos casos puede conducir a una evaluación incorrecta, es decir, cuando un documento se considera relevante para una búsqueda, pero su duplicado no lo es. Por ejemplo, en <code>ArguAna</code> hemos identificado 96 casos de pares de documentos duplicados con solo un documento por par marcado como relevante para una búsqueda. Al “expandir” la lista inicial de qrels para incluir también los duplicados, hemos observado un aumento relativo de ~1 % en la puntuación <code>nDCG@10</code> en promedio.</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>Ejemplo de pares duplicados en ArguAna. En el archivo qrels solo el primero parece ser relevante (como contra-argumento) para la búsqueda (“test-economy-epiasghbf-pro02a”)</strong></p><p>Al comparar modelos en la tabla de líderes de MTEB, es tentador centrarse en la calidad promedio de recuperación. Es un buen indicador de la calidad general del modelo, pero no necesariamente te dice cómo se aplicará en tu caso. Dado que los resultados se reportan por conjunto de datos, vale la pena entender hasta qué punto los diferentes conjuntos de datos se relacionan con tu tarea de búsqueda y volver a calificar modelos usando solo los más relevantes. Si quieres profundizar más, también puedes verificar si hay superposición de temas con los diversos conjuntos de datos. Estratificar las medidas de calidad por tema ofrece una evaluación mucho más detallada de sus fortalezas y debilidades específicas.</p><p>Una nota importante aquí es que cuando un documento no está marcado en el archivo <code>qrels</code>, por defecto, se considera irrelevante para la búsqueda. Nos adentramos un poco más en esta área y recopilamos algunas pruebas para aclarar la siguiente pregunta: “¿Con qué frecuencia se presentan pares al evaluador (búsqueda, documento) para los cuales no hay información fidedigna?”. La razón por la que esto es importante es que cuando solo se dispone de marcado superficial (y, por lo tanto, no todos los documentos relevantes están etiquetados como tales), un sistema de recuperación de información puede ser juzgado como peor que otro simplemente porque "elige" mostrar diferentes documentos relevantes (pero no marcados). Este es un problema habitual a la hora de crear conjuntos de evaluación de alta calidad, especialmente en el caso de sets de datos de gran tamaño. Para ser factible, el etiquetado manual generalmente se enfoca en los principales resultados devueltos por el sistema actual, por lo que potencialmente se pierden documentos relevantes en sus puntos ciegos. Por lo tanto, generalmente es preferible enfocar más recursos en un marcado más completo de menos búsquedas que en un marcado amplio y superficial.</p><h2>Aprovechar el índice de referencia BEIR para la evaluación de relevancia en búsquedas</h2><p>Para iniciar nuestro análisis implementamos el siguiente escenario (ver el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">cuaderno</a>):</p><ol><li><p>En primer lugar, cargamos el corpus de cada set de datos en un índice de Elasticsearch.</p></li><li><p>Para cada búsqueda en el conjunto de pruebas, recuperamos los 100 documentos principales con BM25.</p></li><li><p>Volvemos a clasificar los documentos recuperados utilizando una variedad de modelos de reclasificación SOTA.</p></li><li><p>Finalmente, presentamos el reporte de la “tasa de evaluación” para los 10 documentos principales que provienen de los pasos 2 (después de la recuperación) y 3 (después de la reclasificación). En otras palabras, calculamos el porcentaje promedio de los 10 documentos principales que tienen una puntuación en el archivo <code>qrels</code>.</p></li></ol><p>La lista de reclasificación de modelos que utilizamos es la siguiente:</p><ul><li><p><code>rerank-english-v2.0</code> de <a href="https://docs.cohere.com/reference/rerank">Cohere</a> y <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>Recuperación</p><p>Reclasificación</p><p></p><p></p><p></p><p></p><p>Set de datos</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>Preguntas naturales</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 (promedio)</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>Tabla 2</strong>: Tasa de evaluación por pares (set de datos, reclasificador) calculada en los 10 primeros documentos recuperados/reclasificados</p><p>Desde <strong>Tabla 2</strong>, con la excepción de <code>TREC-COVID</code> (&gt;90 % de cobertura), <code>DBPedia</code> (~65 %), <code>Touche2020</code> y <code>nfcorpus</code> (~35 %), vemos que la mayoría de los sets de datos tienen una tasa de etiquetado entre el 5 % y un poco más del 10 % después de la recuperación o reclasificación. Esto no significa que todos estos documentos no marcados sean relevantes, pero podría haber un subconjunto de ellos, especialmente aquellos ubicados en las posiciones superiores, que podrían ser positivos.</p><p>Con la llegada de los modelos de lenguaje ajustados mediante instrucciones de propósito general, tenemos una nueva herramienta poderosa que potencialmente puede automatizar la evaluación de la relevancia. Estos métodos suelen ser demasiado costosos en cuanto a la complejidad de los procesos para ser utilizados en línea para la búsqueda, pero aquí nos preocupa la evaluación fuera de línea. A continuación, los usamos para explorar la evidencia de que algunos de los sets de datos BEIR tienen un marcado superficial.</p><p>Para investigar más a fondo esta hipótesis, decidimos centrarnos en MSMARCO y seleccionar un subconjunto de 100 búsquedas junto con los 5 principales documentos reclasificados (con Cohere v2) que actualmente no están marcados como relevantes. Seguimos dos caminos diferentes de evaluación: Primero, usamos un indicador cuidadosamente ajustado (veremos más sobre esto en una publicación posterior) para preparar el modelo <a href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct">Phi-3-mini-4k</a> recientemente lanzado para predecir la relevancia (o no) de un documento para la búsqueda. Paralelamente, estos casos también se etiquetaron manualmente para evaluar la tasa de concordancia entre el resultado del LLM y el juicio humano. En general, podemos extraer estas dos conclusiones:</p><ul><li><p>La tasa de acuerdo entre las respuestas de los LLM y los juicios humanos fue cercana al 80 %, lo que parece suficiente como punto de partida en esa dirección.</p></li><li><p>En el 57.6 % de los casos (basado en el juicio humano) se descubrió que los documentos devueltos eran realmente relevantes para la búsqueda. Para decirlo de otra manera: Para 100 búsquedas tenemos 107 documentos considerados relevantes, ¡pero al menos 0.576 x 5 x 100 = 288 documentos adicionales que son realmente relevantes!</p></li></ul><p>Aquí, algunos ejemplos extraídos del set de datos <code>MSMARCO</code>/<code>dev</code> que contienen la búsqueda, el documento positivo anotado (de <code>qrels</code>) y un documento falso negativo debido a un marcado incompleto:</p><p>Ejemplo 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>Ejemplo 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>La evaluación manual de búsquedas específicas como esta es una técnica generalmente útil para comprender la calidad de la búsqueda que complementa las medidas cuantitativas como nDCG@10. Si tienes un conjunto representativo de búsquedas que siempre ejecutas al hacer cambios en la búsqueda, te proporciona información cualitativa importante sobre cómo cambia el rendimiento, lo cual es invisible en las estadísticas. Por ejemplo, te da mucha más información sobre los resultados falsos que devuelve la búsqueda: puede ayudarte a detectar errores evidentes en los resultados recuperados, clases de errores relacionados, como malinterpretar terminología específica de un dominio, y así sucesivamente.</p><p>Nuestro resultado concuerda con la investigación relevante sobre la evaluación de <code>MSMARCO</code>. Por ejemplo, <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh et al.</a> siguen un procedimiento similar en el que emplean trabajadores de crowdsourcing para hacer juicios de preferencia: entre otros puntos, muestran que en muchos casos se prefieren los documentos devueltos por los módulos de reclasificación en comparación con los documentos del archivo MSMARCO <code>qrels</code>. Otra evidencia proviene de los autores del reclasificador <a href="https://arxiv.org/pdf/2010.08191">RocketQA</a> quienes en su reporte indican que más del 70 % de los documentos reclasificados se consideraron relevantes luego de la inspección manual.</p><p> Actualización - 9 de septiembre: Tras una cuidadosa reevaluación del set de datos, identificamos 15 casos más de documentos relevantes, lo que aumenta el total de 273 a 288</p><h2>Principales puntos clave y próximos pasos</h2><ul><li><p>La búsqueda de mejores datos fidedignos es interminable, ya que es de vital importancia para la evaluación comparativa y la comparación de modelos. Los LLM pueden ayudar en algunas áreas de evaluación si se usan con precaución y se ajustan con las instrucciones adecuadas.</p></li><li><p>De forma más general, dado que los índices de referencia nunca serán perfectos, podría ser preferible pasar de una comparación pura de puntaje a técnicas más sólidas que capturen diferencias estadísticamente significativas. El trabajo de <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh et al.</a> ofrece un buen ejemplo de esto, donde, basándose en sus hallazgos, crean intervalos de confianza del 95 % que indican diferencias significativas (o no) entre las distintas secuencias. En el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">cuaderno</a> adjunto se encuentra una implementación de intervalos de confianza usando <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">bootstrapping</a>.</p></li><li><p>Desde la perspectiva del usuario final, es útil pensar en la alineación de tareas al leer los resultados de los índices de referencia. Por ejemplo, para un ingeniero de IA que crea una pipeline RAG y sabe que el caso de uso más típico implica ensamblar múltiple información de diferentes fuentes, sería más significativo evaluar el rendimiento de su modelo de recuperación en sets de datos de QA multisaltos, como HotpotQA, en lugar de la media global de todo el índice de referencia BEIR</p></li></ul><p>En la <a href="https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-2">próxima publicación del blog</a> profundizaremos en el uso de Phi-3 como juez de LLM y en el proceso de ajustarlo para predecir su relevancia.</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[Investigación en ML]]></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[Plagio por IA: detección de plagio con Elasticsearch]]></title>
    <description><![CDATA[Aquí tienes cómo comprobar el plagio de IA usando Elasticsearch, centrándote en casos de uso con modelos de PLN y Búsqueda Vectorial.]]></description>
    <content:encoded><![CDATA[<p>El plagio puede ser <strong>directo</strong>, implicando la copia de partes o de todo el contenido, o <strong>parafraseado</strong>, donde la obra del autor se reformula cambiando algunas palabras o frases.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>Hay una distinción entre inspiración y parafraseo. Es posible leer un contenido, inspirar y luego explorar la idea con tus propias palabras, incluso si llegas a una conclusión similar.</p><p>Aunque el plagio fue un tema de debate durante mucho tiempo, el aceleramiento de la producción y publicación de contenido lo mantuvo relevante y supuso un desafío constante.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>Este desafío no se limita a libros, investigaciones académicas o documentos judiciales, donde con frecuencia se realizan comprobaciones de plagio. También puede extender a los periódicos e incluso a las redes sociales.</p><p>Con la abundancia de información y el fácil acceso a la publicación, ¿cómo se puede controlar el plagio de forma eficaz a un nivel escalable?</p><p>Las universidades, entidades gubernamentales y compañías emplean herramientas diversas, pero aunque una <a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">búsqueda léxica</a> sencilla puede detectar el plagio directo, el principal desafío radica en identificar <strong>contenido parafraseado.</strong></p><h2>Detección de plagio con IA generativa</h2><p>Surge un nuevo reto con la IA generativa. ¿Se considera plagio el contenido generado por IA cuando se copia?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>Los <a href="https://openai.com/policies/terms-of-use">términos de uso</a> de <a href="https://openai.com/">OpenAI, por ejemplo, especifican que OpenAI no reclamará derechos de autor sobre el contenido generado por la API para los usuarios.</a> En este caso, las personas que usan su IA generativa pueden emplear el contenido generado como prefieran sin necesidad de citas.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>Sin embargo, la aceptación del uso de IA generativa para mejorar la eficiencia sigue siendo un tema de debate.</p><p>En un esfuerzo por contribuir a la detección de plagio, OpenAI desarrolló un <a href="https://huggingface.co/roberta-base-openai-detector">modelo de detección</a> , pero más tarde reconoció que su precisión no es suficientemente alta.</p><p><em>"Creemos que esto no es lo suficientemente preciso para una detección independiente y debe combinar con enfoques basados en metadatos, juicio humano y educación pública para ser más efectivo."</em></p><p>El desafío persiste; sin embargo, con la disponibilidad de más herramientas, ahora hay más opciones para detectar plagio, incluso en casos de contenido parafraseado y de IA.</p><h2>Detección de plagio con Elasticsearch</h2><p>Reconociendo esto, en este blog exploramos un caso de uso más con los modelos de Procesamiento del Lenguaje Natural (PLN) y la búsqueda vectorial, la detección de plagio, más allá de las búsquedas de metadatos.</p><p>Esto se demuestra con <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb">ejemplos en Python</a>, donde empleamos un conjunto de <a href="https://sbert.net/datasets/emnlp2016-2018.json">datos</a> de <a href="https://www.sbert.net/">SentenceTransformers</a> que contiene artículos relacionados con el PLN. Comprobamos los resúmenes en busca de plagio realizando una 'similitud textual semántica' considerando incrustaciones 'abstractas' generadas con un <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">modelo de incrustación de texto</a> previamente importado a Elasticsearch. Además, para identificar contenido generado por IA — plagio por IA, también se importó un <a href="https://huggingface.co/roberta-base-openai-detector">modelo de PLN</a> desarrollado por OpenAI a Elasticsearch.</p><p>La siguiente imagen ilustra el flujo de datos:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p>Durante la <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">tubería de ingesta</a> con un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">procesador de inferencia</a>, el párrafo 'abstracto' se mapea a un vector de 768 dimensiones, el 'valor_predicho abstract_vector.predicho'.</p><p>Cartografía:</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>La similitud entre representaciones vectoriales se mide mediante una métrica de similitud vectorial, definida mediante el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">parámetro</a> de 'similitud'.</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">El coseno</a> es la métrica de similitud por defecto, calculada como '(1 + coseno(consulta, vector)) / 2'. A menos que necesites preservar los vectores originales y no puedas normalizarlos de antemano, la forma más eficiente de realizar similitud coseno es normalizar todos los vectores a longitud unitaria. Esto ayuda a evitar realizar cálculos adicionales de longitud vectorial durante la búsqueda, y en su lugar emplea 'dot_product'.</p><p>En esta misma canalización, otro procesador de inferencia que contiene el <a href="https://huggingface.co/roberta-base-openai-detector">modelo de clasificación de texto</a> detecta si el contenido es 'real' probablemente escrito por humanos, o 'falso' probablemente escrito por IA, agregando el 'openai-detector.predicted_value' a cada documento.</p><p>Pipeline de ingestión:</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>En el momento de la consulta, también se emplea el mismo modelo de incrustación de texto para generar la representación vectorial de la consulta 'model_text' en un objeto 'query_vector_builder'.</p><p>Una búsqueda de k vecinos más cercanos (kNN) encuentra el k vector más cercano al vector de consulta medido por la métrica de similitud.</p><p>El _score de cada documento se deriva de la similitud, cerciorando que un puntaje mayor corresponda a una clasificación más alta. Esto significa que el documento es más similar semánticamente. Como resultado, imprimimos tres posibilidades: si el puntaje &gt; 0,9, consideramos 'alta similitud'; si &lt; 0,7, 'baja similitud', de lo contrario, 'similitud moderada'. Tienes la flexibilidad de establecer diferentes valores umbral para determinar qué nivel de _score califica como plagio o no, según tu caso de uso.</p><p>Además, se realiza la clasificación de texto para comprobar también la presencia de elementos generados por IA en la consulta de texto.</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>Salida:</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>En este ejemplo, tras emplear uno de los valores 'abstractos' de nuestro conjunto de datos como consulta de texto 'model_text', se identificó plagio. El puntaje de similitud es 1,0, lo que indica un alto nivel de similitud: <strong>plagio directo</strong>. La consulta vectorizada y el documento no fueron reconocidos como contenido generado por IA, lo cual era de esperar.</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>Salida:</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>Al actualizar la consulta de texto 'model_text' con un texto generado por IA que transmite el mismo mensaje minimizando la repetición de palabras similares, la similitud detectada seguía siendo alta, pero el puntaje era de 0,9302529 en lugar de 1,0 — <strong>plagio de paráfrasis</strong>. También se esperaba que esta consulta, generada por IA, fuera detectada.</p><p>Por último, considerando la consulta de texto 'model_text' como texto sobre Elasticsearch, que no es un resumen de ninguno de estos documentos, la similitud detectada fue 0,68991005, lo que indica baja similitud según los valores umbral 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>Salida:</p>Low similarity detected. This might not be plagiarism.
<p>Aunque el plagio se identificó correctamente en la consulta de texto generada por IA, así como en casos de paráfrasis y contenido copiado directamente, navegar por el panorama de la detección de plagio implica reconocer diversos aspectos.</p><p>En el contexto de la detección de contenido generado por IA, exploramos un modelo que aporta una contribución valiosa. Sin embargo, es fundamental reconocer las limitaciones inherentes a la detección independiente, lo que requiere incorporar otros métodos para mejorar la precisión.</p><p>La variabilidad introducida por la elección de modelos de incrustación de texto es otra consideración. Diferentes modelos, capacitados con conjuntos de datos distintos, resultan en distintos niveles de similitud, lo que destaca la importancia de las incrustaciones de texto generadas.</p><p>Por último, en estos ejemplos, usamos el resumen del documento. Sin embargo, la detección de plagio suele implicar documentos grandes, por lo que es esencial abordar el reto de la longitud del texto. Es común que el texto supere el límite de tokens de un modelo, requiriendo segmentación en fragmentos antes de construir incrustaciones. Un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">enfoque práctico</a> para manejar esto implica emplear estructuras anidadas con dense_vector.</p><h2>Conclusión</h2><p>En este blog, hablamos sobre los desafíos de detectar plagio, especialmente en contenido parafraseado y generado por IA, y cómo la similitud textual semántica y la clasificación de texto pueden emplear para este propósito.</p><p>Combinando estos métodos, proporcionamos un ejemplo de detección de plagio donde identificamos con éxito contenido generado por IA, plagio directo y parafraseado.</p><p>El objetivo principal era establecer un sistema de filtrado que simplificara la detección, pero la evaluación humana sigue siendo esencial para la validación.</p><p>Si te interesa aprender más sobre similitud textual semántica y PNL, te animamos a que también consultes estos enlaces:</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">¿Qué es la búsqueda semántica?</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">¿Qué es el procesamiento del lenguaje natural (PLN)?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Búsqueda léxica y semántica con Elasticsearch</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">Fragmentar documentos grandes mediante pipelines de ingesta más vectores anidados equivale a una búsqueda de pasajes fácil</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[Base de datos vectorial]]></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[Búsqueda léxica y semántica con Elasticsearch]]></title>
    <description><![CDATA[En este blog, exploraremos diversos enfoques para recuperar información empleando Elasticsearch, centrándonos en la búsqueda léxica y semántica.]]></description>
    <content:encoded><![CDATA[<p>La búsqueda es el proceso de localizar la información más relevante basar en tu consulta de búsqueda o en la combinación de consultas y resultados de búsqueda relevantes son documentos que mejor se ajustan a estas consultas. Aunque existen varios desafíos y métodos asociados a la búsqueda, el objetivo final sigue siendo el mismo: <strong>encontrar la mejor respuesta posible a tu pregunta</strong>.</p><p>Teniendo en cuenta este objetivo, en esta entrada de blog exploraremos diferentes enfoques para recuperar información usando Elasticsearch, con un enfoque específico en la búsqueda de texto: <strong>búsqueda léxica y semántica.</strong></p><h2>Prerrequisitos</h2><p>Para lograrlo, proporcionaremos ejemplos en Python que demuestren diversos escenarios de búsqueda en un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">conjunto de datos</a> generado para simular información de productos de comercio electrónico.</p><p>Este conjunto de datos contiene más de 2.500 productos, cada uno con una descripción. Estos productos se clasifican en 76 categorías distintas, cada una conteniendo un número variable de productos, como se muestra a continuación:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>Visualización de treemap: 22 valores principales de category.keyword (categorías de productos)</em></p><p>Para la configuración necesitarás:</p><ul><li><p>Python 3.6 o posterior</p></li><li><p>El <a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">cliente Python Elástico</a></p></li><li><p>Despliegue Elastic 8.8 o posterior, con nodo de aprendizaje automático de 8GB de memoria</p></li><li><p>El modelo <a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> , que viene preinstalado en Elastic, se instaló y comenzó en tu despliegue</p></li></ul><p>Usaremos Elastic Cloud, <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">hay una prueba gratis disponible</a>.</p><p>Además de las consultas de búsqueda que se proporcionan en esta entrada del blog, un <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno de Python</a> te guiará a través de los siguientes procesos:</p><ul><li><p>Establece una conexión con nuestro despliegue de Elastic usando el cliente de Python</p></li><li><p>Carga un modelo de incrustación de texto en el clúster de Elasticsearch</p></li><li><p>Crea un índice con mapeos para indexar vectores de características y vectores densos.</p></li><li><p>Crear una tubería de ingesta con procesadores de inferencia para incrustación y expansión de texto</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>Búsqueda léxica - recuperación dispersa</h2><p>La forma tradicional en que los documentos se clasifican para relevancia según Elasticsearch basar en una consulta de texto emplea la implementación Lucene del modelo <a href="https://en.wikipedia.org/wiki/Okapi_BM25">BM25</a> , un <strong>modelo disperso para la búsqueda léxica</strong>. Este método sigue el enfoque tradicional de búsqueda de texto, buscando coincidencias exactas de términos.</p><p>Para hacer posible esta búsqueda, Elasticsearch convierte los datos <strong>de los campos de texto</strong> en un formato buscable mediante análisis de texto.</p><p><strong>El análisis de texto</strong> se realiza mediante un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> analizador</a>, un conjunto de reglas que regulan el proceso de extracción de tokens relevantes para la búsqueda. Un analizador debe tener exactamente un<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> tokenizador</a>. El tokenizador recibe una secuencia de caracteres y la divide en fichas individuales (normalmente palabras individuales), como en el ejemplo siguiente:</p><h3>Tokenización de cadenas para búsqueda léxica</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>Salida</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>En este ejemplo estamos usando el analizador por defecto, el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">analizador estándar</a> , que funciona bien para la mayoría de los casos de uso ya que proporciona tokenización basada en gramática inglesa. La tokenización permite la coincidencia en términos individuales, pero cada token sigue siendo emparejado literalmente.</p><p>Si quieres personalizar tu experiencia de búsqueda, puedes elegir otro<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> analizador integrado</a> diferente. Por ejemplo, actualizando el código para usar el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">analizador de atajada</a> , se descompone el texto en tokens en cualquier carácter que no sea letra, con soporte para eliminar palabras de atajada.</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>Salida</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p>Cuando los analizadores integrados no satisfacen tus necesidades, puedes crear un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html">analizador personalizado</a>, que emplee la combinación adecuada de filtros de cero o <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">más caracteres</a>, un <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">tokenizador</a> y filtros de cero o más <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">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>En el ejemplo anterior, que combina un tokenizador y filtros de token, el texto se pondrá en minúsculas con el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html">filtro</a> minúsculo antes de ser procesado por el <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 sinónimos.</a></p><h2>Emparejamiento léxico</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25</a> medirá la relevancia de los documentos para una consulta de búsqueda determinada en función de la frecuencia de los términos y su importancia.</p><p>El código siguiente realiza una consulta <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">de coincidencia</a>, buscando hasta dos documentos que consideren los valores del campo <em>"descripción"</em> del índice <em>"ecommerce-search"</em> y la consulta <strong>de búsqueda "</strong><em><strong>Muebles cómodos para un balcón grande</strong></em><strong>".</strong></p><p>Refinar los criterios para que un documento se considere compatible con esta consulta puede mejorar la precisión. Sin embargo, los resultados más específicos tienen el costo de una menor tolerancia a las variaciones.</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>Salida</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>Analizando el resultado, el resultado más relevante es el producto "<em>Barbie Dreamhouse</em>", en la categoría "<em>Juguetes</em>", y su descripción es muy relevante ya que incluye los términos "<em>muebles</em>", "<em>grande"</em> y <em>"balcón";</em>este es el único producto con 3 términos en la descripción que coinciden con la consulta de búsqueda,  El producto es también el único que tiene el <em>término "balcón"</em> en la descripción.</p><p>El segundo producto más relevante es una "<em>Mecedora Cómoda</em>" categorizada como "<em>Mobiliario de Interior</em>" y su descripción incluye los términos "<em>cómodo</em>" y "<em>mueble".</em> Solo 3 productos en el conjunto de datos coinciden con al menos 2 términos de esta consulta de búsqueda, este producto es uno de ellos.</p><p><em>"Cómodo"</em> aparece en la descripción de 105 productos y <em>"muebles"</em> en la descripción de 4 productos con 4 categorías diferentes: <em>Juguetes</em>, <em>Muebles de interior, Muebles de exterior y 'Suministros y Juguetes para perros y gatos'.</em></p><p>Como puedes ver, el producto más relevante para la pregunta es un juguete y el segundo producto más relevante son los muebles de interior. Si quieres información detallada sobre el cálculo de puntaje para saber por qué estos documentos coinciden, puedes poner el <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>parámetro de explicación</em></a> __query en verdadero.</p><p>A pesar de que ambos resultados son los más relevantes, considerando tanto el número de documentos como la presencia de términos en este conjunto de datos, la intención detrás de la consulta "<em>Cómodos muebles para un balcón grande</em>" es buscar muebles para un balcón grande real, excluyendo, entre otros, juguetes y muebles de interior.</p><p>La búsqueda léxica es relativamente <strong>sencilla y rápida</strong>, pero tiene limitaciones ya que no siempre es posible conocer todos los términos y sinónimos posibles sin necesariamente conocer la intención y las consultas del usuario. Un fenómeno común en el uso del lenguaje natural es la <strong>desadaptación del vocabulario</strong>. <a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">Las investigaciones</a> muestran que, de media, el <strong>80% de las veces</strong> diferentes personas (expertos en el mismo campo) nombran el mismo nombre de forma distinta.</p><p>Estas limitaciones nos motivan a buscar otros modelos de puntaje que incorporen conocimientos semánticos. Los modelos basados en transformadores, que destacan en el procesamiento de tokens de entrada secuenciales como el lenguaje natural, capturan el significado subyacente de tu búsqueda considerando representaciones matemáticas tanto de documentos como de consultas. Esto permite una representación vectorial densa y consciente del contexto del texto, impulsando <strong>la Búsqueda Semántica</strong>, una forma refinada de encontrar contenido relevante.</p><h2>Búsqueda semántica - recuperación densa</h2><p>En este contexto, tras convertir tus datos en valores vectoriales significativos, se emplea el algoritmo de búsqueda <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">de k-vecinos más cercanos (kNN)</a> para encontrar representaciones vectoriales en un conjunto de datos que sean más similares a un vector de consulta. Elasticsearch soporta dos métodos para la búsqueda en kNN: <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">kNN exacto de fuerza bruta</a> y <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">kNN aproximado</a>, también conocido como ANN.</p><p>La kNN de fuerza bruta garantiza resultados precisos pero no escala bien con grandes conjuntos de datos. El kNN aproximado encuentra eficientemente a los vecinos más cercanos sacrificando cierta precisión para mejorar el rendimiento.</p><p>Con el soporte de Lucene para la búsqueda kNN y los índices vectoriales densos, Elasticsearch aprovecha el algoritmo Hierarchical Navigable Small World (HNSW), que demuestra un rendimiento estable en búsquedas en una variedad de <a href="http://ann-benchmarks.com/">conjuntos de datos ann-benchmark</a>. Se puede realizar una búsqueda aproximada de kNN en Python usando el código de ejemplo siguiente.</p><h3>Búsqueda semántica con 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 bloque de código emplea kNN de Elasticsearch para devolver hasta dos productos con una descripción similar a la consulta vectorizada (query_vector_build) de "<em>Cómodo mobiliario para un balcón grande</em>" considerando las incrustaciones del campo "<em>descripción</em>" en el conjunto de datos de productos.</p><p>Las incrustaciones de productos se generaban previamente en una tubería de ingesta con un procesador de inferencia que contenía el modelo de incrustación <em>de texto "</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em> para inferir contra los datos que se estaban ingeriendo en la canalización.</p><p>Este modelo se eligió basar en la evaluación de modelos preentrenados 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> donde se emplean diferentes clases para evaluar un modelo durante el entrenamiento. El modelo "all-mpnet-base-v2" mostró el mejor rendimiento medio según la <a href="https://www.sbert.net/docs/pretrained_models.html">clasificación de Sentence-Transformers</a> y también cercioró una posición favorable en la tabla de clasificación <a href="https://huggingface.co/spaces/mteb/leaderboard">del Massive Text Embedding Benchmark (MTEB</a> ). El modelo preentrenado<a href="https://huggingface.co/microsoft/mpnet-base"> es Microsoft y mpnet-base</a> y ajustado finamente en un conjunto de datos de pares de oraciones de 1B, que mapea oraciones a un espacio vectorial denso de 768 dimensiones.</p><p>Alternativamente, existen muchos otros modelos disponibles que se pueden emplear, especialmente aquellos ajustados para los datos específicos de tu dominio.</p><p>Salida</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>La salida puede variar según el modelo elegido,</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>los filtros</em></a> <em>y</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>la afinación aproximada de kNN</em></a><em>.</em></p><p>Los resultados de búsqueda de kNN están ambos en la categoría de "<em>Muebles de exterior</em>", aunque la palabra "<em>exterior</em>" no se mencionó explícitamente en la consulta, lo que resalta la importancia de la comprensión semántica en este contexto.</p><p>La búsqueda vectorial densa ofrece varios beneficios:</p><ul><li><p>Activación de la búsqueda semántica</p></li><li><p>Escalabilidad para manejar conjuntos de datos muy grandes</p></li><li><p>Flexibilidad para manejar una amplia variedad de tipos de datos</p></li></ul><p>Sin embargo, la <strong>búsqueda vectorial densa también conlleva sus propios desafíos</strong>:</p><ul><li><p>Seleccionar el modelo de incrustación adecuado para tu caso de uso</p></li><li><p>Una vez elegido un modelo, puede ser necesario ajustarlo para optimizar el rendimiento en un conjunto de datos específico de un dominio, un proceso que requiere la participación de expertos en el dominio</p></li><li><p>Además, indexar vectores de alta dimensión puede ser computacionalmente costoso</p></li></ul><h2>Búsqueda semántica - recuperación escasa aprendida</h2><p>Exploremos un enfoque alternativo: la recuperación esparsa aprendida, otra forma de realizar búsqueda semántica.</p><p>Como modelo escaso, emplea el índice invertido basado en Lucene de Elasticsearch, que se beneficia de décadas de optimizaciones. Sin embargo, este enfoque va más allá de simplemente agregar sinónimos con funciones de puntaje léxico como BM25. En su lugar, incorpora asociaciones aprendidas empleando un conocimiento más profundo a escala del lenguaje para optimizar su relevancia.</p><p>Al ampliar las consultas de búsqueda para incluir términos relevantes que no están presentes en la consulta original, el <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Codificador Escaso Aprendido</a> <strong>Elástico mejora las incrustaciones vectoriales dispersas</strong>, como puedes ver en el ejemplo siguiente.</p><h3>Búsqueda vectorial dispersa con codificador elástico aprendido de dispersión</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>Salida</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>Los resultados en este caso incluyen la categoría "<em>Muebles de jardín</em>", que ofrece productos bastante similares a los "<em>Muebles de Exterior</em>".</p><p>Analizando "ml.tokens", el campo "rank_features" que contiene tokens generados por Recuperación Escasa Aprendida, se hace evidente que entre los diversos tokens generados hay términos que, aunque no forman parte de la consulta de búsqueda, siguen siendo relevantes en su significado, como "<em>relax</em>" (cómodo), "<em>sofá</em>" (muebles) y "<em>exterior</em>" (balcón).</p><p>La imagen de abajo destaca algunos de estos términos junto con la consulta, tanto con como sin ampliación de términos.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>Como se observó, este modelo proporciona una búsqueda consciente del contexto y ayuda a mitigar el problema de la descoordinación de vocabulario, a la vez que proporciona resultados más interpretables. Incluso puede superar a los modelos vectoriales densos cuando no se aplica un reentrenamiento específico de dominio.</p><h2>Búsqueda híbrida: resultados relevantes combinando la búsqueda léxica y semántica</h2><p>Cuando se trata de búsqueda, no hay una solución universal. Cada uno de estos métodos de recuperación tiene sus fortalezas pero también sus desafíos. Dependiendo del caso de uso, la mejor opción puede cambiar. A menudo, los mejores resultados entre los métodos de recuperación pueden ser complementarios. Por tanto, para mejorar la relevancia, veremos la combinación de las fortalezas de cada método.</p><p>Existen múltiples formas de implementar una <strong>búsqueda híbrida</strong>, incluyendo la combinación lineal, que da un peso a cada puntaje y la fusión recíproca de rangos (RRF), donde no es necesario especificar un peso.</p><h3>Elasticsearch: lo mejor de ambos mundos con búsqueda léxica y 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>En este código, realizamos una búsqueda híbrida con dos consultas que tenían el valor "<em>Una mesa de comedor y sillas cómodas para un balcón grande</em>". En lugar de usar "<em>muebles</em>" como término de búsqueda, especificamos lo que buscamos, y ambas búsquedas consideran los mismos valores de campo, "descripción". La clasificación se determina mediante una combinación lineal con el mismo peso para los puntajes BM25 y ELSER.</p><p>Salida</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>En el código siguiente, usaremos el mismo valor para la consulta, pero combinaremos los puntajes de BM25 (parámetro de consulta) y kNN (parámetro knn) usando el método de fusión de rangos recíprocos para combinar y clasificar los 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>La funcionalidad de RRF está en vista previa técnica. La sintaxis probablemente cambiará antes de GA.</em></p><p>Salida</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>Aquí también podríamos usar diferentes campos y valores; algunos de estos ejemplos están disponibles en el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno de Python</a>.</p><p>Como puedes ver, con Elasticsearch tienes lo mejor de ambos mundos: la búsqueda léxica tradicional y la búsqueda vectorial, ya sea escasa o densa, para alcanzar tu objetivo <strong>y encontrar la mejor respuesta posible a tu pregunta.</strong></p><p>Si quieres seguir aprendiendo sobre los enfoques mencionados aquí, estos blogs pueden ser útiles:</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Mejorar la recuperación de información en el Elastic Stack: Recuperación híbrida</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Búsqueda vectorial en Elasticsearch: La razón detrás del diseño</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Cómo obtener lo mejor de la búsqueda léxica y basada en IA con la base de datos vectorial de Elastic</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Presentamos Elastic Learned Sparse Encoder: el modelo de IA de Elastic para búsqueda semántica</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Mejorando la recuperación de información en el Elastic Stack: Presentamos Elastic Learned Sparse Encoder, nuestro nuevo modelo de recuperación</a></p></li></ul><p>Elasticsearch proporciona una base de datos vectorial, junto con todas las herramientas que necesitas para construir la búsqueda vectorial:</p><ul><li><p><a href="https://www.elastic.co/elasticsearch/vector-database">Base de datos de vectoresElasticsearch</a></p></li><li><p>Casos de uso <a href="https://www.elastic.co/enterprise-search/vector-search">de búsqueda vectorial</a> con Elastic</p></li></ul><h2>Conclusión</h2><p>En esta entrada del blog, exploramos varios enfoques para recuperar información usando Elasticsearch, centrándonos específicamente en la búsqueda textual, léxica y semántica. Para demostrarlo, proporcionamos ejemplos en Python que muestran diferentes escenarios de búsqueda empleando un conjunto de datos que contiene información de productos de comercio electrónico.</p><p>Revisamos la búsqueda léxica tradicional con BM25 y discutimos sus beneficios y desafíos, como la descoordinación del vocabulario. Destacamos la importancia de incorporar el conocimiento semántico para superar este problema. Además, hablamos sobre la búsqueda vectorial densa, que permite la búsqueda semántica, y abordamos los retos asociados a este método de recuperación, incluyendo el costo computacional al indexar vectores de alta dimensión.</p><p>Por otro lado, mencionamos que los vectores dispersos comprimen excepcionalmente bien. Por ello, hablamos del Learned Sparse Encoder de Elastic, que amplía las consultas de búsqueda para incluir términos relevantes que no estaban presentes en la consulta original.</p><p>No existe una solución única para todos en cuanto a búsqueda. Cada método de recuperación tiene sus fortalezas y desafíos. Por ello, también discutimos el concepto de búsqueda híbrida.</p><p>Como puedes ver, con Elasticsearch puedes tener lo mejor de ambos mundos: ¡búsqueda léxica tradicional y búsqueda vectorial!</p><p>¿Listo para empezar? Consulta el <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">cuaderno Python</a> disponible y comienza una <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">prueba gratis de 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[Base de datos vectorial]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Lenguajes de búsqueda]]></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>