<?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/kr/search-labs/blog/category/python-programming</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/python-programming</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/python-programming.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Mon, 28 Sep 2026 20:51:29 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch와 SigLIP-2로 산봉우리에 대한 멀티모달 검색 ]]></title>
    <description><![CDATA[SigLIP-2 임베딩과 Elasticsearch kNN 벡터 검색을 사용해 텍스트 대 이미지 및 이미지 대 이미지 다중 모드 검색을 구현하는 방법을 알아보세요. 프로젝트 초점: 에베레스트 트레킹에서 아마다블람 산 정상 사진 찾기.]]></description>
    <content:encoded><![CDATA[<p>사진 앨범을 의미별로 검색하고 싶었던 적이 있나요? "파란색 재킷을 입고 벤치에 앉아 있는 내 사진 보여줘", "에베레스트산 사진 보여줘", "사케와 초밥" 등의 검색어를 사용해 보세요. 커피 한 잔(또는 좋아하는 음료)을 들고 계속 읽으세요. 이 블로그에서는 멀티모달 하이브리드 검색 애플리케이션을 구축하는 방법을 설명합니다. 멀티모달이란 앱이 단어뿐만 아니라 텍스트, 이미지, 오디오 등 다양한 종류의 입력을 이해하고 검색할 수 있다는 뜻입니다. 하이브리드란 키워드 매칭, kNN 벡터 검색, 지오펜싱과 같은 기술을 결합하여 더 선명한 결과를 제공하는 것을 의미합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa1ec1ccd450e94/6a17da751d1b8308ee93e344/0ec6bbb45013846b59ee00d2bf73ee2182ee7392-1920x1080.gif" alt="에베레스트 산 등반에서 찍은 다양한 산봉우리 사진 라이브러리." /><p>이를 위해 Google의 SigLIP-2를 사용해 이미지와 텍스트 모두에 대한 벡터 임베딩을 생성하고 이를 Elasticsearch 벡터 데이터베이스에 저장합니다. 쿼리 시 텍스트 또는 이미지와 같은 검색 입력을 임베딩으로 변환하고 빠른 kNN 벡터 검색을 실행하여 결과를 검색합니다. 이 설정을 통해 텍스트 대 이미지 및 이미지 대 이미지 검색을 효율적으로 수행할 수 있습니다. 스트림릿 UI는 텍스트 기반 검색을 통해 앨범에서 일치하는 사진을 찾아서 볼 수 있을 뿐만 아니라 업로드된 이미지에서 산봉우리를 식별하고 사진 앨범에서 해당 산의 다른 사진을 볼 수 있는 프론트엔드를 제공함으로써 이 프로젝트에 활기를 불어넣었습니다.
또한 검색 정확도를 개선하기 위해 취한 조치와 실용적인 팁과 요령에 대해서도 설명합니다. 더 자세히 살펴볼 수 있도록 <a href="https://github.com/navneet83/multimodal-mountain-peak-search">GitHub 리포지토리와</a> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/notebooks/multimodal_mountain_peak_search.ipynb">Colab 노트북을</a> 제공합니다.</p><h2>시작 방법</h2><p>이 블로그 게시물은 에베레스트 베이스캠프 트레킹에서 찍은 아마 다블람 산의 모든 사진을 보여 달라는 10살짜리 아이의 요청에 영감을 받아 작성했습니다. 사진첩을 훑어보면서 이름을 알 수 없는 다른 산봉우리도 몇 개 더 찾아달라는 요청을 받았습니다.</p><p>이를 통해 재미있는 컴퓨터 비전 프로젝트가 될 수 있겠다는 생각이 들었습니다. 우리가 달성하고자 했던 목표:</p><ul><li><p>이름으로 산봉우리 사진 찾기</p></li><li><p>이미지에서 산봉우리 이름을 맞추고 사진 앨범에서 비슷한 봉우리를 찾습니다.</p></li><li><p>컨셉 쿼리가 작동하도록 하기<em>(사람</em>, <em>강</em>, <em>기도 깃발</em> <em>등)</em></p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf82df9d7005fc3fe/6a17da78abe0f2e77bdfe8b9/e9d0d720a9b565d5b749bdc915068852d4f157ad-1200x1600.png" alt="아마 다블람 산 " /><h2>드림팀 구성: SigLIP-2, Elasticsearch &amp; Streamlit</h2><p>이 작업을 수행하려면 텍스트('Ama Dablam')와 이미지(내 앨범의 사진)를 모두 의미 있게 비교할 수 있는 벡터, 즉 동일한 벡터 공간으로 변환해야 한다는 것이 금방 분명해졌습니다. 이렇게 하면 검색은 "가장 가까운 이웃을 찾는 것"에 불과합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f80b69a9d5bd28a/6a17da7a4b055ddd1243209e/20e6f8b7d4fa48414f407ec200adbe00ee28d517-1536x1024.png" alt="SigLIP-2, Elasticsearch &amp; Streamlit- 드림팀." /><p>이미지 임베딩을 생성하기 위해 다국어<a href="https://huggingface.co/blog/vlms-2025"> 비전 언어 인코더를</a> 사용하므로 산 사진과 "Ama Dablam"과 같은 문구가 동일한 벡터 공간에 배치됩니다.</p><p>최근 Google에서 출시한 <a href="https://huggingface.co/blog/siglip2"><strong>SigLIP-2가</strong></a> 여기에 잘 맞습니다. 작업별 교육( <strong>제로 샷</strong> 설정) 없이 임베딩을 생성할 수 있으며, 라벨이 없는 사진과 이름과 언어가 다른 봉우리라는 사용 사례에 적합하게 작동합니다. 텍스트 ↔ 이미지 매칭을 위해 학습되었기 때문에 쿼리 언어나 철자가 다르더라도 트레킹에서 찍은 산 사진과 짧은 텍스트 프롬프트가 임베딩으로 비슷하게 표시됩니다.</p><p>SigLIP-2는 강력한 속도 대비 품질 균형을 제공하고, 다양한 입력 해상도를 지원하며, CPU와 GPU 모두에서 실행됩니다. SigLIP-2는 기존 CLIP과 같은 이전 모델에 비해 야외 촬영에 더욱 견고하게 설계되었습니다. 테스트하는 동안 SigLIP-2는 일관되게 신뢰할 수 있는 결과를 생성했습니다. 또한 지원도 매우 잘 되어 있어 이 프로젝트의 확실한 선택이 될 것입니다.</p><p>다음으로 임베딩과 파워 검색을 저장할 벡터 데이터베이스가 필요합니다. 이미지 임베딩에 대한 코사인 kNN 검색을 지원할 뿐만 아니라 단일 쿼리에서 지오펜스 및 텍스트 필터를 적용할 수 있어야 합니다. Elasticsearch는 벡터(dense_vector 필드의 HNSW kNN)를 매우 잘 처리하고 텍스트, 벡터, 위치 기반 쿼리를 결합하는 하이브리드 검색을 지원하며 필터링과 정렬 기능을 기본으로 제공합니다. 또한 수평으로 확장할 수 있어 몇 장의 사진에서 수천 장으로 쉽게 늘릴 수 있습니다. 공식 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">Elasticsearch Python 클라이언트는</a> 배관을 단순하게 유지하며 프로젝트와 깔끔하게 통합됩니다. 마지막으로 검색 쿼리를 입력하고 결과를 볼 수 있는 경량 프론트엔드가 필요합니다. 파이썬 기반의 빠른 데모를 원한다면 Streamlit이 적합합니다. 파일 업로드, 반응형 이미지 그리드, 정렬 및 지오펜싱을 위한 드롭다운 메뉴 등 우리에게 필요한 기본 요소를 제공합니다. 로컬에서 쉽게 복제하고 실행할 수 있으며 Colab 노트북에서도 작동합니다.</p><h2>구현</h2><h3>Elasticsearch 인덱싱 설계 및 인덱싱 전략</h3><p>이 프로젝트에는 <code>peaks_catalog</code> 와 <code>photos</code> 의 두 가지 인덱스를 사용할 것입니다.</p><h4>Peaks_catalog 인덱스</h4><p>이 색인은 에베레스트 베이스캠프 트레킹 중에 볼 수 있는 주요 산봉우리를 간결하게 정리한 카탈로그 역할을 합니다. 이 색인에 포함된 각 문서는 에베레스트 산과 같은 하나의 산봉우리에 해당합니다. 각 산봉우리 문서에는 이름/별칭, 위도-경도 좌표(선택 사항), SigLIP-2 텍스트 프롬프트(+ 참조 이미지 옵션)를 혼합하여 구축한 단일 프로토타입 벡터가 저장됩니다.</p><p><strong>인덱스 매핑:</strong></p><p>필드</p><p>유형</p><p>예</p><p>목적/참고 사항</p><p>벡터/인덱싱</p><p>id</p><p>키워드</p><p>아마다블람</p><p>안정적인 슬러그/ID</p><p>-</p><p>이름</p><p>텍스트 + 키워드 하위 필드</p><p>["아마다블람","아마다블람"]</p><p>별칭/다국어 이름; 정확한 필터를 위한 names.raw</p><p>-</p><p>latlon</p><p>geo_point</p><p>{"lat":27.8617,"lon":86.8614}</p><p>위도/경도 조합의 피크 GPS 좌표(선택 사항)</p><p>-</p><p>elev_m</p><p>정수</p><p>6812</p><p>고도(선택 사항)</p><p>-</p><p>text_embed</p><p>dense_vector</p><p>768</p><p>이 피크에 대한 혼합 프로토타입(프롬프트 및 선택적으로 1~3개의 참조 이미지)</p><p>index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}</p><p>이 색인은 주로 이미지에서 산봉우리를 식별하는 등 이미지 대 이미지 검색에 사용됩니다. 또한 이 인덱스를 사용하여 텍스트-이미지 검색 결과를 개선합니다.</p><p><code>peaks_catalog</code> 요약하면, "어떤 산입니까?" 라는 질문을 가장 가까운 이웃에 초점을 맞춘 문제로 변환하여 이미지 데이터의 복잡성에서 개념적 이해를 효과적으로 분리하는 것입니다.</p><p><strong>peaks_catalog 인덱스의 인덱싱 전략: </strong>EBC 트레킹 중 가장 눈에 띄는 봉우리 목록을 만드는 것부터 시작합니다. 각 봉우리에 대해 지리적 위치, 이름, 동의어, 고도를 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/data/peaks.yaml">yaml 파일에</a> 저장합니다. 다음 단계는 각 피크에 대한 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L351">임베딩을 생성하여</a> <code>text_embed</code> 필드에 저장하는 것입니다. 강력한 임베딩을 생성하기 위해 다음 기술을 사용합니다:</p><ul><li><p>다음을 사용하여 텍스트 프로토타입을 만듭니다:</p><ul><li><p>봉우리 이름</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L301">프롬프트 앙상블</a> (여러 개의 다른 프롬프트를 사용하여 동일한 질문에 답하기) 등을 예로 들 수 있습니다:</p><ul><li><p>"네팔 히말라야의 산봉우리 {name} 의 자연 사진"</p></li><li><p>"{name} 쿰부 지역의 랜드마크 봉우리, 고산 풍경"</p></li><li><p>"{name} 산 정상, 눈, 바위 능선"</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">안티 콘셉트</a> (SigLIP-2에 일치하지 않을 대상을 알려줌): '그림, 일러스트, 포스터, 지도, 로고'에 대해 작은 벡터를 빼서 실제 사진에 편향되도록 합니다.</p></li></ul></li><li><p>피크의 참조 이미지가 제공된 경우 선택적으로 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L388C13-L388C29">이미지 프로토타입을 생성합니다</a>.</p></li></ul><p>그런 다음 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L392">텍스트와 이미지 프로토타입을 혼합하여</a> 최종 임베딩을 생성합니다. 마지막으로 모든 필수 필드가 포함된 문서가 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L396">색인됩니다</a>:</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><code>peaks_catalog</code> 색인의 샘플 문서입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1219f5d0e39b512c/6a17da7c57726263161bcace/bc05fbd0c4f8d721d5170c28a3884a9eda80bb7d-1210x1132.png" alt="Elasticsearch의 peaks_catalog 인덱스에 있는 샘플 문서입니다." /><h4>사진 색인</h4><p>이 기본 색인은 앨범의 모든 사진에 대한 자세한 정보를 저장합니다. 각 문서는 다음 정보를 포함하는 단일 사진을 나타냅니다:</p><ul><li><p>사진 앨범에서 사진의 상대 경로입니다. 검색 UI에서 일치하는 이미지를 보거나 이미지를 로드하는 데 사용할 수 있습니다.</p></li><li><p>사진의 GPS 및 시간 정보.</p></li><li><p>SigLIP-2에서 생성된 이미지 인코딩을 위한 고밀도 벡터입니다.</p></li><li><p><code>predicted_peaks</code> 를 사용하면 피크 이름을 기준으로 필터링할 수 있습니다.

<strong>인덱스 매핑</strong></p></li></ul><p>필드</p><p>유형</p><p>예</p><p>목적/참고 사항</p><p>벡터 / 인덱싱</p><p>경로</p><p>키워드</p><p>데이터/이미지/IMG_1234.HEIC</p><p>UI에서 썸네일/전체 이미지를 여는 방법</p><p>-</p><p>clip_image</p><p>dense_vector</p><p>768</p><p>SigLIP-2 이미지 임베딩</p><p>index:true, 유사성:"코사인", index_옵션:{type:"hnsw", m:16, ef_construction:128}</p><p>예측된 피크</p><p>키워드</p><p>["아마다블람","푸모리"]</p><p>인덱스 시점의 Top-K 추측(저렴한 UX 필터/패싯)</p><p>-</p><p>gps</p><p>geo_point</p><p>{"lat":27.96,"lon":86.83}</p><p>지리적 필터 사용</p><p>-</p><p>shot_time</p><p>date</p><p>2023-10-18T09:41:00Z</p><p>캡처 시간: 정렬/필터링</p><p>-</p><p><strong>사진 색인 색인 전략: </strong>앨범의 각 사진에 대해 다음을 수행합니다:
<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L526">이미지 메타데이터에서 이미지</a> <code>shot_time</code> 및 <code>gps</code> 정보를 추출합니다.</p><ul><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L511">SigLIP-2 이미지 임베딩</a>: 이미지를 모델에 전달하고 벡터를 L2 정규화합니다. <code>clip_image</code> 필드에 임베딩을 저장합니다.</p></li><li><p><a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L519">피크를 예측하여</a> <code>predicted_peaks</code> 필드에 저장합니다. 이를 위해 먼저 이전 단계에서 생성된 사진의 이미지 벡터를 가져온 다음 <code>peaks_catalog</code> 인덱스의 text_embed 필드에 대해 빠른 kNN 검색을 실행합니다. 상위 3~4개 봉우리는 유지하고 나머지는 무시합니다.</p></li><li><p>이미지 이름과 경로에 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L509">해시를</a> 수행하여 <code>_id</code> 필드를 계산합니다. 이렇게 하면 여러 번 실행한 후에도 중복이 발생하지 않습니다.</p></li></ul><p>사진의 모든 필드를 결정한 후에는 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/embed_and_index_photos.py#L530">일괄</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>사진 색인의 샘플 문서입니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt744b7e6326937cfc/6a17da7e6df731d3040a0da8/1dc1406ac2a97440b6804838795b3c2205c4c6b2-1080x1234.png" alt="Elasticsearch의 사진 색인에서 가져온 샘플 문서입니다." /><p>요약하자면, 사진 인덱스는 앨범에 있는 모든 사진을 빠르고 필터링이 가능하며 kNN으로 저장할 수 있는 저장소입니다. 매핑은 일부러 최소화하여 빠르게 검색하고, 깔끔하게 표시하고, 공간과 시간별로 결과를 분류할 수 있는 구조로만 구성했습니다. 이 인덱스는 두 가지 검색 사용 사례를 모두 지원합니다. 두 인덱스를 생성하는 Python 스크립트는 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/create_indices.py">여기에서</a> 찾을 수 있습니다.</p><p>아래의 Kibana 지도 시각화에서는 사진 앨범의 문서를 녹색 점으로, <code>peaks_catalog</code> 인덱스의 산봉우리를 빨간색 삼각형으로 표시하며, 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5bf016e8d9c3e84/6a17da80be608681f10045e6/1c75d0ed0ce53d28a94bf2f47a354e25581d2baf-1600x1402.png" alt="사진 앨범의 문서를 녹색 점으로, peaks_catalog 인덱스의 산봉우리를 빨간색 삼각형으로 표시하는 Kibana 지도 시각화에서 녹색 점이 에베레스트 베이스캠프 트레킹 코스와 잘 정렬되어 있습니다." /><h2>검색 사용 사례</h2><p><strong>이름으로 검색(텍스트-이미지):</strong> 이 기능을 사용하면 텍스트 검색을 통해 산봉우리 사진(및 '기도 깃발'과 같은 추상적인 개념까지)을 찾을 수 있습니다. 이를 위해 텍스트 입력은 SigLIP-2를 사용하여 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L87C5-L87C20">텍스트 벡터로 변환됩니다</a>. 강력한 텍스트 벡터 생성을 위해 인덱스에서 텍스트 임베딩을 생성할 때와 동일한 전략을 <code>peaks_catalog</code> 사용합니다. <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104"></a> 즉, 텍스트 입력을 작은<a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L103"> 프롬프트</a> <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L100"></a>앙상블과 결합하고, 작은 반개념 벡터를 뺀 다음 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L104">L2 정규화를</a> 적용하여 최종 쿼리 벡터를 생성하는 것입니다. 그런 다음 <code>photos.clip_image</code> 필드에서 kNN <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L140">쿼리를</a> 실행하여 가장 가까운 이미지를 찾기 위해 코사인 유사성을 기반으로 가장 일치하는 상위 피크를 검색합니다. 선택적으로 지역 및 날짜 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/query_by_peak_name.py#L152">필터를</a> 적용하거나 <code>photos.predicted_peaks</code> 용어 필터를 쿼리의 일부로 적용하여 검색 결과의 연관성을 높일 수 있습니다(아래 쿼리 예시 참조). 이렇게 하면 트레킹에서 실제로 보이지 않는 유사 봉우리를 제외하는 데 도움이 됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9bb9abf5ce64fcbb/6a17da81e8fbce20db3a17da/b5fac28ffdbedb820505365ca07df125cd01b939-946x370.png" alt="Elasticsearch에서 다중 모드, 이름별 검색(텍스트-이미지 변환)이 작동하는 방식." /><p><strong>지리적 필터를 사용한 Elasticsearch 쿼리:</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>이미지로 검색(이미지 대 이미지):</strong> 이 기능을 사용하면 사진에서 산을 식별하고 사진 앨범 내에서 같은 산의 다른 이미지를 찾을 수 있습니다. 이미지가 업로드되면 SigLIP-2 이미지 인코더가 이미지를 처리하여 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L228">이미지 벡터를</a> 생성합니다. 그런 다음 <code>peaks_catalog.text_embed</code> 필드에서 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L234">kNN 검색을</a> 수행하여 가장 일치하는 피크 이름을 식별합니다. 그 후, 일치하는 피크 이름에서 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L257">텍스트 벡터를 생성하고</a> 사진 인덱스에서 또 다른 <a href="https://github.com/navneet83/multimodal-mountain-peak-search/blob/main/scripts/identify_from_picture_find_similar_peaks.py#L263">kNN 검색을</a> 수행하여 해당 사진을 찾습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab9d16333e2a9e69/6a17da827f6f155448c099cc/3a3d5635bee7a222b95529dd7f9fbee016381610-1226x550.png" alt="Elasticsearch에서 이미지별(이미지 대 이미지) 멀티모달 검색이 작동하는 방식." /><p><strong>Elasticsearch 쿼리:</strong></p><p>1단계: 일치하는 피크 이름 찾기</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>2단계: <code>photos</code> 색인에서 검색을 수행하여 일치하는 사진을 찾습니다(텍스트-이미지 검색 사용 사례에 표시된 것과 동일한 쿼리):</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>스트림라이트 UI</h2><p>모든 것을 하나로 모으기 위해 두 가지 검색 사용 사례를 모두 수행할 수 있는 간단한 Streamlit UI를 만들었습니다. 왼쪽 레일에는 스크롤 가능한 피크 목록( <code>photos.predicted_peaks</code> 에서 집계됨)이 체크박스와 미니맵/지리 필터와 함께 표시됩니다. 상단에는 <strong>이름으로 검색하기</strong> 상자와 <strong>사진 업로드에서 식별하기</strong> 버튼이 있습니다. 가운데 창에는 반응형 썸네일 그리드가 있어 kNN 점수, 예상 피크 배지, 캡처 시간을 보여줍니다. 각 이미지에는 전체 해상도 미리 보기를 위한 <strong>이미지 보기</strong> 버튼이 포함되어 있습니다.</p><p><strong>이미지를 업로드하여 검색합니다:</strong> 피크를 예측하고 사진 앨범에서 일치하는 피크를 찾습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd1fb2b0304a310d2/6a17da8425daab7cda08a0fa/dca540cbf5279e6d6102c5a0c0351ddd4ac91cda-1600x1112.png" alt="아마다블람 산의 봉우리를 텍스트에서 이미지로, 이미지에서 이미지로 멀티모드 검색할 수 있는 간결한 UI를 제공합니다." /><p><strong>텍스트로 검색</strong>: 텍스트에서 앨범에서 일치하는 피크 찾기</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt496c1ae8f7886320/6a17da86abe0f2da48dfe8bd/b1e8618db746cd49ea4962d3dc73031387b975dd-1600x1166.png" alt="산봉우리 라이브러리에서 텍스트로 검색하여 에베레스트산 봉우리를 검색하는 방법을 알아보세요." /><h2>결론</h2><p><em><strong>아마 </strong></em>다블람<em> 사진만</em> <em>볼 수 있나요?</em> 를 작고 작동하는 <strong>멀티모달 검색</strong> 시스템으로 전환했습니다. 우리는 원시 트레킹 사진을 찍어 <strong>SigLIP-2 임베딩으로</strong> 변환하고, <strong>Elasticsearch를</strong> 사용해 벡터를 통한 빠른 <strong>kNN과</strong> 간단한 지리적/시간 필터를 통해 <em>의미별로</em> 적합한 이미지를 표시했습니다. 그 과정에서 저희는 혼합된 프로토타입의 작은 <code>peaks_catalog</code> 인덱스(식별용)와 이미지 벡터 및 EXIF의 확장 가능한 <code>photos</code> 인덱스(검색용) 등 두 가지 인덱스를 사용하여 문제를 분리했습니다. 실용적이고 재현 가능하며 쉽게 확장할 수 있습니다.</p><p>튜닝을 원한다면 몇 가지 설정으로 조정할 수 있습니다:</p><ul><li><p><strong>쿼리 시간 설정:</strong> <code>k</code> (반환할 이웃 수) 및 <code>num_candidates</code> (최종 점수 산출 전 검색 범위). 이러한 설정은 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-knn-and-num-candidates-strategies">여기</a> 블로그에서 설명합니다.</p></li><li><p><strong>인덱스 시간 설정:</strong> <code>m</code> (그래프 연결성) 및 <code>ef_construction</code> (빌드 시간 정확도 대 메모리). 쿼리의 경우 <code>ef_search</code> 을 너무 높게 설정하면 일반적으로 약간의 지연 시간 절충을 통해 더 나은 리콜을 얻을 수 있습니다. 이러한 설정에 대한 자세한 내용은 <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">이 블로그를</a> 참조하세요.</p></li></ul><p>앞으로 <strong>멀티모달</strong> 및 <strong>다국어</strong> 검색을 위한 기본 모델/랭커가 곧 Elastic 생태계에 출시될 예정이므로 이미지/텍스트 검색과 하이브리드 랭킹이 더욱 강력해질 것입니다.<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>직접 체험해보고 싶으신가요?</p><ul><li><p><strong>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>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>이것으로 우리의 여정은 끝났고 이제 돌아올 시간입니다. 도움이 되었기를 바라며, 이 기능을 중단(또는 개선)하신다면 어떤 점이 달라졌는지 알려주시기 바랍니다.</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[벡터 데이터베이스]]></category>
    <category><![CDATA[하이브리드 검색]]></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[사용자 행동 데이터에 기반한 판단 목록으로 Elasticsearch에서 LTR 모델 학습하기]]></title>
    <description><![CDATA[UBI 데이터를 사용해 판단 목록을 생성하여 Elasticsearch에서 학습 순위 지정(LTR) 모델의 학습을 자동화하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><em><strong>학습형 랭킹</strong></em></a> 모델을 사용할 때 가장 큰 과제는 모델을 학습시킬 고품질의 <a href="https://www.elastic.co/search-labs/blog/judgment-lists"><em><strong>판단 목록을</strong></em></a> 만드는 것입니다. 전통적으로 이 프로세스에는 쿼리-문서 관련성을 <em><strong>수동으로</strong></em> 평가하여 각 문서에 등급을 부여하는 작업이 포함됩니다. 이는 확장성이 떨어지고 유지 관리가 어려운 느린 프로세스입니다(수백 개의 항목이 있는 목록을 수작업으로 업데이트해야 한다고 상상해 보세요).</p><p>이제 이 학습 데이터를 생성하기 위해 검색 애플리케이션과의 실제 사용자 상호 작용을 사용할 수 있다면 어떨까요? <a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em><strong>UBI</strong></em></a> 데이터를 사용하면 바로 그렇게 할 수 있습니다. 검색, 클릭 및 기타 상호 작용을 캡처하고 사용하여 판단 목록을 생성할 수 있는 자동 시스템을 구축합니다. 이 프로세스는 수동 상호 작용보다 훨씬 쉽게 확장하고 반복할 수 있으며 더 나은 결과를 얻을 수 있습니다. 이 블로그에서는 Elasticsearch에 저장된 UBI 데이터를 쿼리하여 의미 있는 신호를 계산하여 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><em><strong>LTR</strong></em></a> 모델을 위한 학습 데이터 세트를 생성하는 방법을 살펴보겠습니다.</p><p><em><strong>전체 실험은 </strong></em><a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"><em><strong>여기에서</strong></em></a>확인할 수 있습니다<em><strong>.</strong></em></p><h2>UBI 데이터가 LTR 모델 학습에 유용한 이유</h2><p>UBI 데이터는 수동 주석에 비해 몇 가지 장점이 있습니다:</p><ul><li><p><strong>볼륨:</strong> UBI 데이터는 실제 상호 작용에서 생성되므로 수동으로 생성하는 것보다 훨씬 더 많은 데이터를 수집할 수 있습니다. 물론 이 데이터를 생성할 수 있는 충분한 트래픽이 있다는 전제입니다.</p></li><li><p><strong>실제 사용자 의도:</strong> 일반적으로 수동 판단 목록은 사용 가능한 데이터에 대한 전문가의 평가에서 비롯됩니다. 반면에 UBI 데이터는 실제 사용자 행동을 반영합니다. 즉, 관련성이 있어야 한다는 이론적 가정이 아니라 사용자가 실제로 콘텐츠와 상호작용하고 가치를 찾는 방식에 기반하기 때문에 검색 시스템의 정확도를 향상시킬 수 있는 더 나은 학습 데이터를 생성할 수 있습니다.</p></li><li><p><strong>지속적인 업데이트:</strong> 판단 목록은 시간이 지남에 따라 새로 고쳐야 합니다. UBI 데이터로 생성하면 최신 데이터로 업데이트된 판정 목록을 얻을 수 있습니다.</p></li><li><p><strong>비용 효율성:</strong> 판단 목록을 수동으로 작성하는 번거로움 없이 프로세스를 몇 번이고 효율적으로 반복할 수 있습니다.</p></li><li><p><strong>자연스러운 쿼리 분포</strong>: UBI 데이터는 실제 사용자 쿼리를 나타내므로 더 심층적인 변화를 이끌어낼 수 있습니다. 예를 들어, 사용자가 시스템에서 자연어를 사용하여 검색하나요? 그렇다면 시맨틱 검색 또는 하이브리드 검색 방식을 구현하는 것이 좋습니다.</p></li></ul><p>하지만 몇 가지 경고가 함께 제공됩니다:</p><ul><li><p><strong>편향성 증폭: </strong>인기 있는 콘텐츠는 더 많이 노출된다는 이유만으로 클릭을 받을 가능성이 더 높습니다. 따라서 인기 있는 항목이 증폭되어 더 나은 옵션이 사라질 수 있습니다.</p></li><li><p><strong>불완전한 커버리지: </strong>새 콘텐츠는 상호 작용이 부족하므로 결과에서 높은 순위를 차지하기 어려울 수 있습니다. 희귀 쿼리는 의미 있는 학습 데이터를 생성하기에 충분한 데이터 포인트가 부족할 수도 있습니다.</p></li><li><p><strong>계절적 변화:</strong> 시간이 지남에 따라 사용자 행동이 급격하게 변할 것으로 예상되는 경우, 과거 데이터로는 무엇이 좋은 결과인지 알 수 없을 수 있습니다.</p></li><li><p><strong>작업 모호성:</strong> 클릭 한 번으로 사용자가 원하는 것을 찾았다고 항상 보장할 수 있는 것은 아닙니다.</p></li></ul><h2>성적 계산</h2><h3>LTR 교육 성적</h3><p>LTR 모델을 학습시키려면 문서가 쿼리와 얼마나 연관성이 있는지를 수치로 표현해야 합니다. 저희 구현에서 이 숫자는 0.0에서 5.0+까지 연속 점수로 표시되며, 점수가 높을수록 관련성이 높다는 것을 의미합니다.</p><p>이 채점 시스템이 어떻게 작동하는지 보여드리기 위해 수동으로 만든 이 예시를 살펴보겠습니다:</p><p>쿼리</p><p>문서 콘텐츠</p><p>등급</p><p>설명</p><p>"최고의 피자 레시피"</p><p>"단계별 사진이 포함된 정통 이탈리아 피자 도우 레시피"</p><p>4.0</p><p>연관성이 높고, 사용자가 찾고 있는 정확한 내용</p><p>"최고의 피자 레시피"</p><p>"이탈리아 피자의 역사"</p><p>1.0</p><p>주제는 피자에 관한 것이지만 레시피는 아닙니다.</p><p>"최고의 피자 레시피"</p><p>"초보자를 위한 빠른 15분 피자 레시피"</p><p>3.0</p><p>관련성이 있고 좋은 결과이지만 '최고의' 레시피라는 점에서는 다소 부족할 수 있습니다.</p><p>"최고의 피자 레시피"</p><p>"자동차 관리 가이드"</p><p>0.0</p><p>전혀 관련이 없으며 쿼리와 전혀 관련이 없습니다.</p><p>여기에서 볼 수 있듯이 등급은 문서가 '최고의 피자 레시피'라는 샘플 쿼리와 얼마나 관련성이 있는지를 수치로 나타낸 것입니다. 이러한 점수를 통해 LTR 모델은 결과에서 어떤 문서가 더 높은 순위에 표시되어야 하는지 학습할 수 있습니다.</p><p>성적을 계산하는 방법은 교육 데이터 세트의 핵심입니다. 이를 위한 <a href="https://www.elastic.co/search-labs/blog/judgment-lists">여러 가지 접근 방식이</a> 있으며, 각 방식에는 고유한 장단점이 있습니다. 예를 들어, 관련성이 있으면 1점, 관련성이 없으면 0점이라는 이진 점수를 할당하거나 각 쿼리에 대해 결과 문서의 클릭 수를 계산할 수 있습니다.</p><p>이 블로그 게시물에서는 <em><strong>사용자 행동을 입력으로 고려하고 성적 번호를 출력으로 계산하는</strong></em> 다른 접근 방식을 사용할 것입니다. 또한 문서의 관련성에 관계없이 높은 결과가 더 많이 클릭되는 경향으로 인해 발생할 수 있는 편향도 수정할 예정입니다.</p><h2>성적 계산 - COEC 알고리즘</h2><p>COEC<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">(예상 클릭 수 대비 클릭 수</a>) 알고리즘은 사용자 클릭 수로 평가 등급을 계산하는 방법론입니다.
앞서 언급했듯이 사용자는 문서가 검색어와 가장 관련성이 높지 않더라도 높은 위치에 있는 결과를 클릭하는 경향이 있는데, 이를 <a href="https://eugeneyan.com/writing/position-bias/">위치 편향이라고</a> 합니다. COEC 알고리즘을 사용하는 핵심 아이디어는 모든 클릭이 똑같이 중요한 것은 아니며, 10번 위치에 있는 문서를 클릭하면 1번 위치에 있는 문서를 클릭하는 것보다 해당 문서가 쿼리와 훨씬 더 관련성이 높다는 것을 나타냅니다. COEC 알고리즘에 대한 연구 논문(위 링크)을 인용합니다:</p><p><em>"검색 결과나 광고의 클릭률(CTR)은 결과의 위치에 따라 크게 감소한다는 것은 잘 알려진 사실입니다."</em></p><p>포지션 편향에 대한 자세한 내용은 <a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">여기에서</a> 확인할 수 있습니다.</p><p>COEC 알고리즘으로 이 문제를 해결하려면 다음 단계를 따르세요:</p><p><strong>1. 게재 순위 기준선을 설정합니다:</strong> 각 검색 위치의 클릭률(CTR)을 1부터 10까지 계산합니다. 즉, 일반적으로 위치 1, 위치 2 등을 클릭하는 사용자의 비율을 파악합니다. 이 단계는 사용자의 자연스러운 위치 편향을 포착합니다.

다음을 사용하여 CTR을 계산합니다:</p><p>Where:</p><p>p = 위치. 1에서 10까지
Cp = 모든 쿼리에서 p 위치에 있는 총 클릭 수(모든 문서)
Ip = 총 노출 수입니다: 모든 쿼리에서 모든 문서가 위치 p에 노출된 횟수</p><p>여기서는 순위가 높을수록 더 많은 클릭을 얻을 것으로 예상합니다.</p><p><strong>2.</strong> <strong>예상 클릭 수(EC)를 계산합니다</strong>:</p><p>이 측정지표는 문서가 표시된 위치와 해당 위치에 대한 CTR을 기준으로 문서가 '받아야 하는' 클릭 수를 설정하며, 이를 통해 EC를 계산합니다:</p><p>Where:</p><p>Qd = 문서 d가 나타난 모든 쿼리
pos(d,q)= 쿼리 q 결과에서 문서 d의 위치</p><p>3. <strong>실제 클릭 수 계산: </strong>문서가 표시된 모든 쿼리에서 문서가 받은 실제 총 클릭 수를 계산합니다(이하 <strong>A(d)</strong>라고 함).</p><p>4. <strong>COEC 점수를 계산합니다:</strong> 예상 클릭 수(EC(d))에 대한 실제 클릭 수(A(d))의 비율입니다:</p><p>이 메트릭은 이와 같은 위치 편향에 대해 정규화합니다:</p><ul><li><p>1.0점은 문서가 표시된 위치에서 예상한 대로 정확하게 수행되었음을 의미합니다.</p></li><li><p>1.0점 이상은 문서의 위치를 살펴봤을 때 예상보다 우수한 성능을 보였다는 의미입니다. 따라서 이 문서가 쿼리와 더 관련이 있습니다.</p></li><li><p>1.0 미만의 점수는 문서의 위치를 살펴볼 때 예상보다 실적이 좋지 않음을 의미합니다. 따라서 이 문서는 쿼리와 관련성이 낮습니다.</p></li></ul><p><em><strong>최종 결과는 검색 시스템과의 실제 상호 작용에서 추출한 위치 기반 기대치를 고려하여 사용자가 찾고 있는 것을 포착하는 등급 번호입니다.</strong></em></p><h2>기술적 구현</h2><p>LTR 모델을 학습시키기 위해 판단 목록을 만드는 스크립트를 만들 것입니다.</p><p>이 스크립트의 입력은 Elastic에서 색인된 UBI 데이터(쿼리 및 이벤트)입니다.</p><p>출력은 COEC 알고리즘을 사용하여 이러한 UBI 문서에서 생성된 CSV 파일의 판정 목록입니다. 이 판단 목록은 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">이랜드와</a> 함께 관련 특징을 추출하고 LTR 모델을 훈련하는 데 사용할 수 있습니다.</p><h3>빠른 시작</h3><p>이 블로그의 샘플 데이터에서 판단 목록을 생성하려면 다음 단계를 따르세요:</p><p>1. 리포지토리를 복제합니다:</p>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog<p>2. 필요한 라이브러리 설치</p><p>이 스크립트에는 다음 라이브러리가 필요합니다:</p><ul><li><p><em>판다: 판정</em> 목록 저장하기</p></li><li><p><em>엘라스틱서치</em>: Elastic 배포에서 UBI 데이터를 가져오려면 다음과 같이 하세요.</p></li></ul><p>파이썬 3.11도 필요합니다.</p>pip install -r requirements.txt<p>3. <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">.env 파일에서</a>Elastic 배포의 환경 변수를 업데이트합니다.</p><ul><li><p>ES_HOST</p></li><li><p>API_KEY</p></li></ul><p>환경 변수를 추가하려면 다음을 사용합니다:</p>source .env<p>4. ubi_queries, ubi_events 인덱스를 생성하고 샘플 데이터를 업로드합니다. setup.py 파일을 실행합니다:</p>python setup.py<p>5. 파이썬 스크립트를 실행합니다:</p>python judgement_list-generator.py<p>이 단계를 수행하면 다음과 같은 판단 목록이라는 새 파일이 표시됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94317eda8f7af194/6a170aa46f7f04542f914821/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" alt="" /><p>이 스크립트는 아래에 표시된 <strong>계산_관련성_등급()</strong> 함수를 사용하여 앞에서 설명한 COEC 알고리즘을 적용하여 성적을 계산합니다.</p><h2>데이터 아키텍처</h2><h3>Ubi 쿼리</h3><p>UBI 쿼리 색인에는 검색 시스템에서 실행된 쿼리에 대한 정보가 있습니다. 이 문서는 샘플 문서입니다:</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>여기에서 사용자(client_id), 쿼리 결과(query_response_object_ids), 쿼리 자체(timestamp, user_query)의 데이터를 볼 수 있습니다.</p><h3>Ubi 클릭 이벤트</h3><p>ubi_events 인덱스에는 사용자가 결과에서 문서를 클릭할 때마다 발생하는 데이터가 있습니다. 이 문서는 샘플 문서입니다:</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>판결 목록 생성 스크립트</h2><h3>일반 스크립트 개요</h3><p>이 스크립트는 Elasticsearch에 저장된 쿼리 및 클릭 이벤트의 UBI 데이터를 사용하여 판단 목록 생성을 자동화합니다. 이러한 작업을 실행합니다:</p><ul><li><p>Elasticsearch에서 UBI 데이터를 가져와 처리합니다.</p></li><li><p>UBI 이벤트와 쿼리의 상관관계를 파악합니다.</p></li><li><p>각 포지션에 대한 CTR을 계산합니다.</p></li><li><p>각 문서에 대한 예상 클릭 수(EC)를 계산합니다.</p></li><li><p>각 문서에 대한 실제 클릭 수를 계산합니다.</p></li><li><p>각 쿼리-문서 쌍에 대한 COEC 점수를 계산합니다.</p></li><li><p>판정 목록을 생성하여 CSV 파일에 기록합니다.</p></li></ul><p>각 기능을 살펴보겠습니다:</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>이 함수는 호스트와 API 키를 사용하여 Elasticsearch 클라이언트 객체를 반환합니다.</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>이 함수는 데이터 추출 계층으로, Elasticsearch와 연결하여 match_all 쿼리를 사용해 UBI 쿼리를 가져오고 'CLICK_THROUGH' 이벤트만 가져오도록 UBI 이벤트를 필터링합니다.</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>이 함수는 판단 목록 생성을 처리합니다. UBI 이벤트와 쿼리를 연결하여 UBI 데이터 처리를 시작합니다. 그런 다음 각 문서-쿼리 쌍에 대해 계산_관련성_등급() 함수를 호출하여 판정 목록의 항목을 가져옵니다. 마지막으로 결과 목록을 판다 데이터 프레임으로 반환합니다.</p><h3>계산_관련성_등급()</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>COEC 알고리즘을 구현하는 함수입니다. 각 위치에 대한 CTR을 계산한 다음 문서-쿼리 쌍에 대한 실제 클릭 수를 비교하고 마지막으로 각 위치에 대한 실제 COEC 점수를 계산합니다.</p><h3>GENERATE_JUDGEMENT_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>심사 목록에서 총 쿼리 수, 총 고유 문서 수 또는 등급 분포와 같은 유용한 통계를 생성합니다. 이는 순전히 정보 제공용이며 결과 판정 목록은 변경되지 않습니다.</p><h2>결과 및 영향</h2><p>빠른 시작 섹션의 지침을 따르면 320개의 항목이 포함된 판정 목록이 포함된 결과 CSV 파일이 표시됩니다(리포지토리에서 <a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">샘플 출력을</a> 확인할 수 있습니다). 이러한 필드를 사용합니다:</p><ul><li><p>qid: 쿼리의 고유 ID</p></li><li><p>docid: 결과 문서의 고유 식별자</p></li><li><p>성적: 쿼리-문서 쌍에 대해 계산된 성적입니다.</p></li><li><p>쿼리: 사용자 쿼리</p></li></ul><p> "이탈리아 요리법" 쿼리에 대한 결과를 살펴보겠습니다:</p><p>qid</p><p>docid</p><p>학년</p><p>쿼리</p><p>Q1-이탈리아어 레시피</p><p>레시피_파스타_베이직</p><p>0.0</p><p>이탈리안 레시피</p><p>Q1-이탈리아어 레시피</p><p>레시피_피자_마르게리타</p><p>3.333333</p><p>이탈리안 레시피</p><p>Q1-이탈리아어 레시피</p><p>레시피_리조또_가이드</p><p>10.0</p><p>이탈리안 레시피</p><p>Q1-이탈리아어 레시피</p><p>레시피_프랑스_크로아상</p><p>0.0</p><p>이탈리안 레시피</p><p>Q1-이탈리아어 레시피</p><p>레시피_스페인_파에야</p><p>0.0</p><p>이탈리안 레시피</p><p>Q1-이탈리아어 레시피</p><p>레시피_그리스_무사카</p><p>1.875</p><p>이탈리안 레시피</p><p>결과에서 '이탈리아 요리법'이라는 검색어를 확인할 수 있습니다:</p><ul><li><p>리조또 레시피는 예상보다 10배 더 많은 클릭을 받아 검색어에 가장 적합한 결과입니다.</p></li><li><p>피자 마르게리타도 훌륭한 결과물입니다.</p></li><li><p>(놀랍게도) 그리스 무사카도 좋은 결과를 얻었고, 결과에서 보이는 위치보다 더 좋은 성능을 보였습니다. 즉, 이탈리아 레시피를 찾는 일부 사용자가 대신 이 레시피에 관심을 갖게 된 것입니다. 이러한 사용자는 일반적으로 지중해 요리에 관심이 있을 수 있습니다. 결국, 이것이 우리에게 알려주는 것은 위에서 설명한 다른 두 가지 '더 나은' 경기에서도 좋은 결과를 보여줄 수 있다는 것입니다.</p></li></ul><h2>결론</h2><p>UBI 데이터를 사용하면 LTR 모델의 학습을 자동화하여 자체 사용자로부터 고품질의 판단 목록을 만들 수 있습니다. UBI 데이터는 검색 시스템이 어떻게 사용되고 있는지를 반영하는 빅 데이터 세트를 제공하며, COEC 알고리즘을 사용하여 성적을 생성함으로써 내재된 편향을 설명하는 동시에 사용자가 더 나은 결과라고 생각하는 것을 반영합니다. 여기에 설명된 방법을 실제 사용 사례에 적용하여 실제 사용 트렌드에 따라 진화하는 더 나은 검색 환경을 제공할 수 있습니다.</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[ES|QL을 사용한 Elasticsearch 위치 기반 정보 검색]]></title>
    <description><![CDATA[Elasticsearch 쿼리 언어(ES|QL)로 위치 기반 정보 검색. Elasticsearch는 강력한 위치 기반 정보 검색 기능을 갖추고 있으며, 이제 사용 편의성과 OGC 친숙도를 획기적으로 개선하기 위해 ES|QL에 제공됩니다.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch는 수년 동안 강력한 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/geospatial-analysis.html">위치 기반 정보 검색 및 분석 기능을</a> 제공해 왔지만, API는 일반적인 GIS 사용자들에게 익숙한 것과는 상당히 달랐습니다. 작년에 저희는 SQL만큼 쉽거나 그보다 더 쉬운 파이프라인 쿼리 언어인 <a href="https://www.elastic.co/search-labs/blog/esql-piped-query-language-goes-ga">ES|QL 쿼리 언어를 추가했습니다</a>. 특히 Elastic이 탁월한 검색, 보안 및 통합 가시성 사용 사례에 적합합니다. 또한 ES|QL 내에서 지리공간 검색 및 분석에 대한 지원을 추가하여 특히 SQL 또는 <a href="https://en.wikipedia.org/wiki/Geographic_information_system">GIS</a> 커뮤니티에서 온 사용자들이 훨씬 쉽게 사용할 수 있도록 하고 있습니다.</p><p>Elasticsearch 8.12와 8.13은 ES|QL에 위치 기반 정보 유형에 대한 기본 지원을 도입했습니다. 이 기능은 8.14에서 지리공간 검색 기능이 추가되면서 크게 향상되었습니다. 무엇보다도 이 지원은 PostGIS와 같은 다른 공간 데이터베이스에서 사용하는 <a href="https://en.wikipedia.org/wiki/Simple_Features"></a> <a href="https://en.wikipedia.org/wiki/Open_Geospatial_Consortium">오픈 지리공간 컨소시엄(OGC) 의 간단한 기능 액세스 표준을 밀접하게 준수하도록 설계되어 이러한</a> 표준에 익숙한 GIS 전문가가 훨씬 쉽게 사용할 수 있습니다.</p><p>이 블로그에서는 ES|QL을 사용하여 위치 기반 정보 검색을 수행하는 방법과 SQL 및 Query DSL과 비교하는 방법을 보여드리겠습니다. 또한 ES|QL을 사용하여 공간 조인을 수행하는 방법과 Kibana Maps에서 결과를 시각화하는 방법도 보여드리겠습니다. 여기에 설명된 모든 기능은 "기술 미리보기" 에서 확인할 수 있으며, 개선 방법에 대한 피드백을 보내주시면 감사하겠습니다.</p><h2>지리공간 데이터 검색</h2><p>쿼리 예제부터 시작하겠습니다:</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>이것은 싼야 피닉스 국제공항(SYX) 주변의 직사각형 검색 다각형과 교차하는 모든 도시 경계 다각형을 검색합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3897df6bed5d6061/6a17d7c2abe0f29eccdfe861/e48bac8f246c8842f2ea97ddd54910045262aeb1-1440x808.png" alt="ESQL 지리공간 검색" /><p>공항, 도시 및 도시 경계로 구성된 샘플 데이터 세트에서 이 검색은 교차하는 다각형을 찾아 일치하는 문서에서 원하는 필드를 반환합니다:</p><p>abbrev</p><p>공항</p><p>지역</p><p>도시</p><p>도시_위치</p><p>SYX</p><p>산야 피닉스 인터내셔널</p><p>天涯区</p><p>Sanya</p><p>point(109.5036 18.2533)</p><p>쉬웠습니다! 이제 동일한 쿼리에 대한 기존 Elasticsearch 쿼리 DSL과 비교해 보세요:</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>두 쿼리 모두 의도가 상당히 명확하지만 ES|QL 쿼리는 SQL과 매우 유사합니다. PostGIS에서 동일한 쿼리는 다음과 같습니다:</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>ES|QL 예제를 다시 살펴보세요. 비슷하지 않나요?</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>Elasticsearch API의 기존 사용자들은 ES|QL이 훨씬 더 사용하기 쉽다는 것을 알게 되었습니다. 이제 기존 SQL 사용자, 특히 공간 SQL 사용자는 ES|QL이 익숙한 것과 매우 유사하게 느껴질 것으로 예상합니다.</p><h4>SQL은 왜 안 될까요?</h4><p>Elasticsearch SQL은 어떻습니까? 오래 전부터 사용되어 왔으며 몇 가지 지리 공간적 특징을 가지고 있습니다. 그러나 Elasticsearch SQL은 원래 쿼리 API 위에 래퍼로 작성되었기 때문에 원래 API로 변환할 수 있는 쿼리만 지원되었습니다. ES|QL에는 이러한 제한이 없습니다. 완전히 새로운 스택이기 때문에 SQL에서는 불가능했던 많은 최적화가 가능합니다. 벤치마크 결과 ES|QL은 특히 집계에서 <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/esql/nightly/default/6M">쿼리 API보다 매우 빠른 경우가 많습니다</a>!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bda964c8b24e36/6a17d7c3e3179155d22d568a/b8b6c2b2e45850d832805ed1e71e522f4955f53c-1440x813.png" alt="다각형-교차-벤치마크" /><h2>SQL과의 차이점</h2><p>이전 예제에서 보듯이 ES|QL은 SQL과 다소 유사하지만 몇 가지 중요한 차이점이 있습니다. 예를 들어 ES|QL은 파이프 쿼리 언어로, FROM과 같은 소스 명령으로 시작한 다음 모든 후속 명령을 파이프 | 문자로 연결합니다. 이렇게 하면 각 명령이 데이터 테이블을 수신하고 해당 테이블에서 필터링( <code>WHERE</code>), 열 추가( <code>EVAL</code>), 집계 수행( <code>STATS</code>) 등의 작업을 수행하는 방식을 매우 쉽게 이해할 수 있습니다. <code>SELECT</code> 로 시작하여 최종 출력 열을 정의하는 대신 하나 이상의 <code>KEEP</code> 명령이 있을 수 있으며, 마지막 명령은 최종 출력 결과를 지정합니다. 이 구조는 쿼리에 대한 추론을 단순화합니다.</p><p>위의 예제에서 <code>WHERE</code> 명령에 초점을 맞추면 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>문자열 따옴표 문자의 차이 외에도 가장 큰 차이점은 문자열을 공간 유형으로 타입 변환하는 방식에 있습니다. PostGIS에서는 <code>::geometry</code> 접미사를 사용하고, ES|QL에서는 <code>::geo_shape</code> 접미사를 사용합니다. 이는 ES|QL이 Elasticsearch 내에서 실행되고 유형 캐스팅 연산자 <code>::</code> 를 사용하여 문자열을 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-limitations.html#_supported_types">지원되는 ES|QL 유형</a>(이 경우 <code>geo_shape</code>)으로 변환할 수 있기 때문입니다. 또한 Elasticsearch의 <code>geo_shape</code> 및 <code>geo_point</code> 유형은 WGS84로 알려진 공간 좌표계를 의미하며, 더 일반적으로 SRID 번호 4326을 사용하여 참조합니다. PostGIS에서는 명시적이어야 하므로 WKT 문자열에 <code>SRID=4326;</code> 접두사를 사용해야 합니다. 이 접두사를 제거하면 SRID는 0으로 설정되며, 이는 특정 좌표계에 연결되지 않는 Elasticsearch 유형 <code>cartesian_point</code> 및 <code>cartesian_shape</code> 과 유사합니다.</p><p>ES|QL과 PostGIS 모두 유형 변환 함수 구문도 제공합니다:</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>OGC 기능</h2><p>Elasticsearch 8.14에는 다음과 같은 네 가지 OGC 공간 검색 기능이 도입되었습니다:</p><p>ES|QL</p><p>PostGIS</p><p>설명</p><p>ST_INTERSECTS</p><p>ST_Intersects</p><p>두 지오메트리가 교차하면 참을 반환하고 그렇지 않으면 거짓을 반환합니다.</p><p>ST_DISJOINT</p><p>ST_Disjoint</p><p>두 지오메트리가 교차하지 않으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. ST_INTERSECTS의 역수입니다.</p><p>ST_CONTAINS</p><p>ST_Contains</p><p>하나의 지오메트리에 다른 지오메트리가 포함되어 있으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다.</p><p>ST_WITHIN</p><p>ST_Within</p><p>하나의 지오메트리가 다른 지오메트리 안에 있으면 참을 반환하고, 그렇지 않으면 거짓을 반환합니다. ST_CONTAINS의 역수입니다.</p><p>이러한 함수는 PostGIS와 유사하게 작동하며 동일한 방식으로 사용됩니다. 예를 들어 <code>ST_INTERSECTS</code> 은 두 지오메트리가 교차하면 참을 반환하고 그렇지 않으면 거짓을 반환합니다. 위 표의 문서 링크를 따라가 보면 모든 ES|QL 예제는 <code>FROM</code> 절 뒤에 <code>WHERE</code> 절 안에 있는 반면, 모든 PostGIS 예제는 리터럴 지오메트리를 사용하고 있음을 알 수 있습니다. 실제로 두 플랫폼 모두 쿼리의 모든 부분에서 해당 함수를 사용할 수 있도록 지원합니다.</p><p><code>ST_INTERSECTS</code> 에 대한 PostGIS 설명서의 첫 번째 예는 다음과 같습니다:</p>SELECT ST_Intersects(
    'POINT(0 0)'::geometry,
    'LINESTRING ( 2 0, 0 2 )'::geometry
);
<p>이에 해당하는 ES|QL은 다음과 같습니다:</p>ROW ST_INTERSECTS(
    "POINT(0 0)"::geo_point,
    "LINESTRING ( 2 0, 0 2 )"::geo_shape
)
<p>PostGIS 예제에서 SRID를 지정하지 않은 점에 유의하세요. 이는 PostGIS에서 <code>geometry</code> 유형을 사용할 때 모든 계산이 평면 좌표계에서 수행되므로 두 도형의 SRID가 같으면 SRID가 무엇이든 상관없기 때문입니다. Elasticsearch에서는 대부분의 함수에서도 마찬가지이지만, 다음 블로그에서 공간 거리 검색에 대해 살펴보겠지만 <code>geo_shape</code> 및 <code>geo_point</code> 에서 구형 계산을 사용하는 예외가 있습니다.</p><h2>ES|QL 다용도성</h2><p>위에서 <code>WHERE</code> 절과 <code>ROW</code> 명령에서 공간 함수를 사용하는 예제를 살펴봤습니다. 그 외에는 어디에 의미가 있을까요? 매우 유용한 곳 중 하나는 <code>EVAL</code> 명령어입니다. 이 명령을 사용하면 표현식을 평가하고 결과를 반환할 수 있습니다. 예를 들어 국가 이름으로 그룹화된 모든 공항의 중심이 국가를 나타내는 경계선 내에 있는지 확인해 보겠습니다:</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>결과는 예상대로 영국 공항의 중심이 영국 경계 내에 있고 아이슬란드 경계 내에 있지 않으며 그 반대의 경우도 마찬가지입니다:</p><p>중심</p><p>개수</p><p>in_uk</p><p>in_iceland</p><p>within_uk</p><p>within_iceland</p><p>포인트 (-21.946634463965893 64.13187285885215)</p><p>1</p><p>false</p><p>true</p><p>false</p><p>true</p><p>포인트 (-2.597342072712148 54.33551226578214)</p><p>17</p><p>true</p><p>false</p><p>true</p><p>false</p><p>POINT (0.04453958108176276 23.74658354606057)</p><p>873</p><p>false</p><p>false</p><p>false</p><p>false</p><p>실제로 이러한 함수는 서명이 의미가 있는 쿼리의 모든 부분에서 사용할 수 있습니다. 이 함수는 모두 리터럴 공간 객체 또는 공간 유형의 필드인 두 개의 인수를 받으며, 모두 부울 값을 반환합니다. 한 가지 중요한 고려 사항은 지오메트리의 좌표 참조 시스템(CRS)이 일치해야 하며, 그렇지 않으면 오류가 반환된다는 것입니다. 즉, 동일한 함수 호출에서 <code>geo_shape</code> 유형과 <code>cartesian_shape</code> 유형을 혼합할 수 없습니다. 그러나 <code>geo_point</code> 유형은 <code>geo_shape</code> 유형의 특수한 경우이며 둘 다 동일한 좌표 참조 시스템을 공유하므로 <code>geo_point</code> 유형과 <code>geo_shape</code> 유형을 혼합하여 사용할 수 있습니다. 위에 정의된 각 함수에 대한 문서에는 지원되는 유형 조합이 나열되어 있습니다.</p><p>또한 인수는 공간 리터럴 또는 필드 중 어느 것이든 순서와 상관없이 사용할 수 있습니다. 두 개의 필드, 두 개의 리터럴, 필드와 리터럴 또는 리터럴과 필드를 지정할 수도 있습니다. 유일한 요구 사항은 유형이 호환되어야 한다는 것입니다. 예를 들어, 이 쿼리는 동일한 인덱스에 있는 두 필드를 비교합니다:</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>이 쿼리는 기본적으로 도시 위치가 도시 경계 내에 있는지 묻는데, 일반적으로는 사실이어야 하지만 항상 예외가 있습니다:</p><p>카디널리티</p><p>개수</p><p>in_city</p><p>few</p><p>29</p><p>false</p><p>많은</p><p>740</p><p>true</p><p>훨씬 더 흥미로운 질문은 공항의 위치가 해당 공항이 서비스를 제공하는 도시의 경계 내에 있는지 여부입니다. 그러나 공항 위치는 도시 경계를 포함하는 인덱스와 다른 인덱스에 있습니다. 이를 위해서는 이 두 개의 개별 인덱스에서 데이터를 효과적으로 쿼리하고 상호 연관시킬 수 있는 방법이 필요합니다.</p><h2>공간 조인</h2><p>ES|QL은 <code>JOIN</code> 명령을 지원하지 않지만 SQL의 '왼쪽 조인'과 유사하게 작동하는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"><code>ENRICH</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql-commands.html#esql-enrich"> 명령을</a> 사용하여 특수한 경우의 조인을 수행할 수 있습니다. 이 명령은 SQL의 'Left 조인'과 유사하게 작동하며, 두 데이터 집합 간의 공간적 관계를 기반으로 한 인덱스의 결과를 다른 인덱스의 데이터로 보강할 수 있습니다.</p><p>예를 들어 공항 위치가 포함된 도시 경계를 찾아서 공항이 취항하는 도시에 대한 추가 정보로 공항 테이블의 결과를 보강한 다음 결과에 대한 몇 가지 통계를 수행해 보겠습니다:</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>그러면 공항이 가장 많은 상위 5개 지역과 해당 지역과 일치하는 모든 공항의 중심점, 해당 지역 내 도시 경계를 나타내는 WKT의 길이 범위가 반환됩니다:</p><p>중심</p><p>개수</p><p>min_wkt</p><p>max_wkt</p><p>지역</p><p>POINT (-32.56093470960719 32.598117914802714)</p><p>90</p><p>207</p><p>207</p><p>null</p><p>포인트 (-73.94515332765877 40.70366442203522)</p><p>9</p><p>438</p><p>438</p><p>뉴욕시</p><p>포인트 (-83.10398317873478 42.300230911932886)</p><p>9</p><p>473</p><p>473</p><p>디트로이트</p><p>POINT (-156.3020245861262 20.176383580081165)</p><p>5</p><p>307</p><p>803</p><p>하와이</p><p>포인트 (-73.88902732171118 45.57078813901171)</p><p>4</p><p>837</p><p>837</p><p>몬트리올</p><p>그렇다면 여기서 실제로 무슨 일이 일어났을까요? <code>JOIN</code> 은 어디에서 발생했나요? 쿼리의 핵심은 <code>ENRICH</code> 명령어에 있습니다:</p>FROM airports
| ENRICH city_boundaries ON city_location WITH airport, region, city_boundary
<p>이 명령은 <code>airports</code> 인덱스에서 검색된 결과를 보강하고 원본 인덱스의 <code>city_location</code> 필드와 앞서 몇 가지 예제에서 사용한 <code>airport_city_boundaries</code> 인덱스의 <code>city_boundary</code> 필드 간에 <code>intersects</code> 조인을 수행하도록 Elasticsearch에 지시합니다. 그러나 이 정보 중 일부는 이 쿼리에서 명확하게 표시되지 않습니다. 우리가 볼 수 있는 것은 인라이크 정책의 이름 <code>city_boundaries</code> 이며, 누락된 정보는 해당 정책 정의에 캡슐화되어 있습니다.</p>{
  "geo_match": {
    "indices": "airport_city_boundaries",
    "match_field": "city_boundary",
    "enrich_fields": ["city", "airport", "region", "city_boundary"]
  }
}
<p>여기에서 <code>geo_match</code> 쿼리를 수행하고(<code>intersects</code> 이 기본값), 일치시킬 필드는 <code>city_boundary</code>, <code>enrich_fields</code> 은 원본 문서에 추가하려는 필드임을 알 수 있습니다. 이러한 필드 중 하나인 <code>region</code> 은 실제로 <code>STATS</code> 명령의 그룹화 키로 사용되었는데, 이 '왼쪽 조인' 기능이 없었다면 불가능했을 것입니다. 인라이크 정책에 대한 자세한 내용은 인라이크 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-setup.html">문서를</a> 참조하세요. 이러한 문서를 읽다 보면, 수집 파이프라인을 구성하여 인덱스 시점에 데이터를 보강하기 위해 인덱스 보강을 사용하는 방법을 설명하는 것을 볼 수 있습니다. <code>ENRICH</code> 명령은 쿼리 시점에 작동하므로 ES|QL에는 필요하지 않습니다. 필요한 데이터와 보강 정책으로 보강 인덱스를 준비한 다음 ES|QL 쿼리에서 <code>ENRICH</code> 명령을 사용하면 충분합니다.</p><p>또한 가장 많이 발견된 지역은 <code>null</code> 입니다. 이것은 무엇을 의미할까요? 이 명령을 SQL의 '왼쪽 조인'에 비유했는데, 이는 공항에 대해 일치하는 도시 경계를 찾을 수 없는 경우 공항이 여전히 반환되지만 <code>airport_city_boundaries</code> 인덱스의 필드에 대한 <code>null</code> 값이 포함된다는 의미입니다. 일치하는 항목이 없는 공항이 89개( <code>city_boundary</code>), 일치하는 항목이 있는 공항이 1개( <code>region</code> 필드 <code>null</code>)인 것으로 나타났습니다. 그 결과 결과에서 <code>region</code> 이 없는 공항이 90곳으로 집계되었습니다. 또 다른 흥미로운 세부 사항은 <code>MV_EXPAND</code> 명령이 필요하다는 것입니다. 이는 <code>ENRICH</code> 명령이 각 입력 행에 대해 여러 개의 결과를 반환할 수 있고 <code>MV_EXPAND</code> 을 사용하면 이러한 결과를 각 결과마다 하나씩 여러 행으로 분리할 수 있기 때문에 필요합니다. 이는 또한 "하와이" 과 <code>min_wkt</code> 및 <code>max_wkt</code> 결과가 다르게 표시되는 이유를 설명합니다. 이름은 같지만 경계가 다른 여러 지역이 존재하기 때문입니다.</p><h2>Kibana 지도</h2><p>Kibana는 지도 애플리케이션에서 공간 ES|QL에 대한 지원을 추가했습니다. 즉, 이제 ES|QL을 사용해 Elasticsearch에서 위치 기반 정보 데이터를 검색하고 그 결과를 지도에서 시각화할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91922eb4ef43d290/6a16f7161949f737bfe7a7b5/bd78470bd8a4bc60f0db7006bd804b8fe87e2fea-1440x683.png" alt="Kibana 레이어 ES|QL" /><p>레이어 추가 메뉴에 "ES|QL" 이라는 새로운 레이어 옵션이 있습니다. 지금까지 설명한 모든 지리공간 기능과 마찬가지로 "기술 미리보기" 에서 확인할 수 있습니다. 이 옵션을 선택하면 ES|QL 쿼리 결과를 기반으로 맵에 레이어를 추가할 수 있습니다. 예를 들어 전 세계의 모든 공항을 표시하는 레이어를 지도에 추가할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5185ef7461d7e81d/6a16f718839dfa1559dcfcb1/1dd28d3d0509f92d26b0bb5320a2925f7a54c5d9-1440x736.png" alt="Kibana ES|QL - 공항" /><p>또는 <code>airport_city_boundaries</code> 인덱스의 다각형을 표시하는 레이어를 추가하거나, 각 지역에 몇 개의 공항이 있는지 통계를 생성하는 위의 복잡한 <code>ENRICH</code> 쿼리를 추가하는 것이 더 좋을 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt51ee3543bb811d98/6a16f71a8b73cbbe63189df1/679a0a401faa613c7cedddd07c64f614ac2b7144-1440x727.png" alt="Kibana ES|QL - 지역 통계" /><h2>그 다음은 무엇일까요?</h2><p>위의 두 가지 예제에서 또 다른 공간 함수 <code>ST_CENTROID_AGG</code> 를 압축한 것을 보셨을 것입니다. 이것은 <code>STATS</code> 명령에 사용되는 집계 함수이며, ES|QL에 추가할 예정인 많은 공간 분석 기능 중 첫 번째 기능입니다. 더 많은 것을 보여드릴 수 있게 되면 블로그에 포스팅하겠습니다!</p><p>그 전에 우리가 작업한 특히 흥미로운 기능에 대해 자세히 알려드리고자 합니다. 바로 Elasticsearch에서 가장 많이 사용되는 공간 검색 기능 중 하나인 공간 거리 검색을 수행하는 기능입니다. 거리 검색의 구문이 어떤 모습일지 상상할 수 있나요? OGC 기능과 비슷하지 않을까요? 이 시리즈의 다음 블로그에서 자세히 알아보세요!</p><p>스포일러 경고: Elasticsearch 8.15가 방금 출시되었으며, ES|QL을 사용한 공간 거리 검색이 포함되어 있습니다!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/esql-geospatial-search-part-one</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[ES|QL]]></category>
    <dc:creator><![CDATA[Craig Taverner]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd05627be20e89dfb/6a17d7c6414c640256944fdb/de886289dcb56494920875303b622b030b9b810f-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 12 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[검색 정확도 평가 1부 - BEIR 벤치마크]]></title>
    <description><![CDATA[BEIR 벤치마크에 대한 더 나은 이해를 바탕으로 검색 시스템을 평가하는 방법을 배우고, 검색 평가 프로세스를 개선하는 데 도움이 되는 팁과 기법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 글은 BEIR 벤치마크를 더 잘 이해하기 위한 맥락에서, 스스로의 검색 시스템을 어떻게 평가해야 하는지 논의하는 블로그 글 시리즈 중 첫 번째입니다. 본 글에서는 BEIR에 대한 더 나은 이해를 바탕으로 검색 평가 프로세스를 개선할 수 있는 구체적인 팁과 기법을 소개합니다. 또한 평가의 신뢰성을 떨어뜨리는 흔한 함정들도 함께 다룹니다. 마지막으로, LLM이 검색 엔지니어의 도구 상자에 강력한 새로운 도구를 제공한다는 점을 짚고, 실제 예시를 통해 이를 검색 평가에 어떻게 활용할 수 있는지 보여드립니다.</p><h2>검색 관련성 평가에서 BEIR 벤치마크 이해하기</h2><p>어떤 시스템이든, 개선하려면 현재 성능을 얼마나 잘 내고 있는지를 측정할 수 있어야 합니다. 검색의 맥락에서 <a href="https://arxiv.org/abs/2104.08663">BEIR</a>(또는 동등하게 <a href="https://huggingface.co/spaces/mteb/leaderboard">MTEB</a> 리더보드의 검색 섹션)은 정보 검색 커뮤니티에서 "성배"로 여겨지며, 이는 전혀 놀라운 일이 아닙니다. 서로 다른 작업 전반에 걸쳐 다양한 데이터 세트를 포함한, 매우 잘 구조화된 벤치마크입니다. 더 구체적으로 말하자면, 다음과 같은 영역을 다룹니다.</p><ul><li><p>논증 검색(ArguAna, Touche2020)</p></li><li><p>오픈 도메인 QA(HotpotQA, Natural Questions, FiQA)</p></li><li><p>구절 검색(MSMARCO)</p></li><li><p>중복 질문 검색(Quora, CQADupstack)</p></li><li><p>사실 확인(FEVER, Climate-FEVER, Scifact)</p></li><li><p>생의학 정보 검색(TREC-COVID, NFCorpus, BioASQ)</p></li><li><p>엔티티 검색(DBPedia)</p></li><li><p>인용 예측(SCIDOCS)</p></li></ul><p>단일 통계치인 nDCG@10을 제공하며, 이는 각 작업 예시에 대해 시스템이 반환한 상위 결과에서 가장 관련성 높은 문서들을 얼마나 잘 매칭하는지를 나타냅니다. 사용자가 상위 결과의 정확도와 직접 상호작용하는 검색 시스템에서는 이러한 지표가 매우 중요합니다. 하지만 검색을 평가할 때는 단일 요약 통계로는 포착하기 어려운 미묘한 차이가 많습니다.</p><h2>BEIR 데이터 세트의 구조</h2><p>각 벤치마크는 세 가지 구성 요소로 이루어져 있습니다.</p><ul><li><p>검색 대상이 될 코퍼스 또는 문서</p></li><li><p>쿼리</p></li><li><p>쿼리에 대한 관련성 판단 값(일명 <code>qrels</code>)</p></li></ul><p>정확도 판단 값은 0점 이상의 점수로 제공됩니다. 점수가 0이 아니라면 문서가 쿼리와 어느 정도 관련이 있음을 나타냅니다.</p><p>데이터 세트</p><p>코퍼스 크기</p><p>테스트 세트의 쿼리 수</p><p>#qrels에 긍정적으로 레이블이 지정됨</p><p>정확도 판단 값이 0인 항목 수</p><p>코퍼스 내 중복 항목 수</p><p>Arguana</p><p>8,674</p><p>1,406</p><p>1,406</p><p>0</p><p>96</p><p>Climate-FEVER</p><p>5,416,593</p><p>1,535</p><p>4,681</p><p>0</p><p>0</p><p>DBPedia</p><p>4,635,922</p><p>400</p><p>15,286</p><p>28,229</p><p>0</p><p>FEVER</p><p>5,416,568</p><p>6,666</p><p>7,937</p><p>0</p><p>0</p><p>FiQA-2018</p><p>57,638</p><p>648</p><p>1,706</p><p>0</p><p>0</p><p>HotpotQA</p><p>5,233,329</p><p>7,405</p><p>14,810</p><p>0</p><p>0</p><p>Natural Questions</p><p>2,681,468</p><p>3,452</p><p>4,021</p><p>0</p><p>16,781</p><p>NFCorpus</p><p>3,633</p><p>323</p><p>12,334</p><p>0</p><p>80</p><p>Quora</p><p>522,931</p><p>10,000</p><p>15,675</p><p>0</p><p>1,092</p><p>SCIDOCS</p><p>25,657</p><p>1,000</p><p>4,928</p><p>25,000</p><p>2</p><p>Scifact</p><p>5,183</p><p>300</p><p>339</p><p>0</p><p>0</p><p>Touche2020</p><p>382,545</p><p>49</p><p>932</p><p>1,982</p><p>5,357</p><p>TREC-COVID</p><p>171,332</p><p>50</p><p>24,763</p><p>41,663</p><p>0</p><p>MSMARCO</p><p>8,841,823</p><p>6,980</p><p>7,437</p><p>0</p><p>324</p><p>CQADupstack(합계)</p><p>457,199</p><p>13,145</p><p>23,703</p><p>0</p><p>0</p><p><strong>표 1</strong>: 데이터 세트 통계 수치는 각 데이터 세트의 구간을 기준으로 산출되었습니다(<code>dev</code> 대상: <code>MSMARCO</code>).</p><p><strong>표 1</strong>은 코퍼스의 문서 수, 테스트 데이터 세트의 쿼리 수, <code>qrels</code> 파일의 긍정/부정(쿼리, 문서) 쌍의 수와 같은, <code>BEIR</code> 벤치마크를 구성하는 데이터 세트에 대한 몇 가지 통계를 제시합니다. 데이터를 간단히 살펴보면 다음과 같은 사실을 즉시 추론할 수 있습니다.</p><ul><li><p>대부분의 데이터 세트는 <code>qrels</code> 파일에 부정 관계가 포함되어 있지 않습니다. 즉, 주어진 쿼리와 무관함을 명시적으로 나타내는 0점 항목이 없다는 의미입니다.</p></li><li><p>쿼리당 문서 관계의 평균 수(<code>#qrels</code> / <code>#queries</code>)는 <code>ArguAna</code>의 경우 1.0에서 <code>TREC-COVID</code>의 경우 493.5까지 다양하며, 대부분의 경우 <code>&lt;</code>5 미만의 값을 가집니다.</p></li><li><p>일부 데이터 세트는 코퍼스 내에 중복 문서가 포함되어 있어, 경우에 따라 잘못된 평가로 이어질 수 있습니다. 즉 하나의 문서가 쿼리에 대해 관련성이 있다고 판단되지만, 동일한 중복 문서는 그렇지 않게 처리되는 경우입니다. 예를 들어 <code>ArguAna</code>의 경우, 한 쿼리에 대해 문서 쌍 중 한 문서만 관련성이 있는 것으로 표시된 중복 문서 쌍을 96건 확인했습니다. 초기 qrels 목록을 중복 항목까지 포함하도록 '확장'했을 경우 평균적으로 <code>nDCG@10</code> 점수가 약 1% 상대적으로 증가하는 것을 확인했습니다.</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>ArguAna에서 중복된 쌍의 예시입니다. qrels 파일에서는 첫 번째 항목만이 쿼리(“test-economy-epiasghbf-pro02a”)에 대해 (반론으로서) 관련성이 있는 것으로 표시되어 있습니다.</strong></p><p>MTEB 리더보드에서 모델을 비교할 때는 평균 검색 품질에 집중하고 싶은 유혹이 생기기 쉽습니다. 이는 모델의 전반적인 품질을 나타내는 좋은 지표이지만, 실제로 여러분의 사용 사례에서 어떻게 성능을 낼지는 반드시 알려주지는 않습니다. 결과는 데이터 세트별로 보고되므로, 서로 다른 데이터 세트가 검색 작업과 얼마나 밀접하게 관련되어 있는지 파악하고 가장 관련성이 높은 데이터 세트만 사용하여 모델의 점수를 재조정하는 것이 좋습니다. 더 자세히 살펴보고 싶다면, 다양한 데이터 세트 코퍼스 간의 주제 중복 여부도 추가로 확인해볼 수 있습니다. 품질 측정값을 주제 기준으로 나누어 분석하면, 특정 강점과 약점에 대한 훨씬 더 세밀한 평가가 가능합니다.</p><p>여기서 중요한 점은 문서가 <code>qrels</code> 파일에 표시되지 않으면 기본적으로 쿼리와 무관하다고 간주된다는 것입니다. 이 부분을 조금 더 깊이 파고들어, 다음 질문을 보다 명확히 하기 위한 몇 가지 근거를 수집합니다. “평가자가 기준 정답 정보가 없는 (쿼리, 문서) 쌍을 접하는 경우는 얼마나 자주 발생하는가?” 이 점이 중요한 이유는, 얕은 마크업만 존재하는 경우(즉, 모든 관련 문서가 라벨링되어 있지 않은 경우) 정보 검색 시스템이 단지 서로 다른 관련 문서(하지만 표시되지 않은 문서)를 노출한다는 이유만으로 다른 시스템보다 성능이 낮게 평가될 수 있기 때문입니다. 이는 특히 대규모 데이터 세트의 경우, 고품질 평가 세트를 만들 때 흔히 발생하는 문제점입니다. 실행 가능한 수동 라벨링은 일반적으로 현재 시스템에서 반환된 상위 결과에 초점을 맞추므로, 그 시스템의 사각지대에 있는 관련 문서들을 놓칠 가능성이 큽니다. 따라서 광범위하지만 얕은 마크업을 적용하기보다는, 더 적은 수의 쿼리에 대해 보다 충실한 마크업에 리소스를 집중하는 편이 일반적으로 더 바람직합니다.</p><h2>검색 정확도 평가에 BEIR 벤치마크 활용하기</h2><p>분석을 시작하기 위해 다음의 시나리오를 구현합니다(<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">노트북</a> 참조).</p><ol><li><p>먼저, 각 데이터 세트의 코퍼스를 Elasticsearch 인덱스에 로드합니다.</p></li><li><p>테스트 세트의 각 쿼리에 대해 BM25를 사용해 상위 100개의 문서를 검색합니다.</p></li><li><p>검색된 문서들은 최신 성능의 다양한 순위 재지정 모델을 사용해 다시 정렬합니다.</p></li><li><p>마지막으로, 2단계(검색 후)와 3단계(재순위화 후)에서 도출된 상위 10개 문서에 대한 '심사 비율'을 보고합니다. 즉, <code>qrels</code> 파일에서 점수가 부여된 상위 10개 문서의 평균 비율을 계산합니다.</p></li></ol><p>이번에 사용한 모델 순위 재지정 목록은 다음과 같습니다.</p><ul><li><p><a href="https://docs.cohere.com/reference/rerank">Cohere의</a> <code>rerank-english-v2.0</code> 및 <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>검색</p><p>순위 재지정</p><p></p><p></p><p></p><p></p><p>데이터 세트</p><p>BM25 (%)</p><p>Cohere Rerank v2 (%)</p><p>Cohere Rerank v3 (%)</p><p>BGE-base (%)</p><p>mxbai-rerank-xsmall-v1 (%)</p><p>MiniLM-L-6-v2 (%)</p><p>Arguana</p><p>7.54</p><p>4.87</p><p>7.87</p><p>4.52</p><p>4.53</p><p>6.84</p><p>Climate-FEVER</p><p>5.75</p><p>6.24</p><p>8.15</p><p>9.36</p><p>7.79</p><p>7.58</p><p>DBPedia</p><p>61.18</p><p>60.78</p><p>64.15</p><p>63.9</p><p>63.5</p><p>67.62</p><p>FEVER</p><p>8.89</p><p>9.97</p><p>10.08</p><p>10.19</p><p>9.88</p><p>9.88</p><p>FiQA-2018</p><p>7.02</p><p>11.02</p><p>10.77</p><p>8.43</p><p>9.1</p><p>9.44</p><p>HotpotQA</p><p>12.59</p><p>14.5</p><p>14.76</p><p>15.1</p><p>14.02</p><p>14.42</p><p>Natural Questions</p><p>5.94</p><p>8.84</p><p>8.71</p><p>8.37</p><p>8.14</p><p>8.34</p><p>NFCorpus</p><p>31.67</p><p>32.9</p><p>33.91</p><p>30.63</p><p>32.77</p><p>32.45</p><p>Quora</p><p>12.2</p><p>10.46</p><p>13.04</p><p>11.26</p><p>12.58</p><p>12.78</p><p>SCIDOCS</p><p>8.62</p><p>9.41</p><p>9.71</p><p>8.04</p><p>8.79</p><p>8.52</p><p>Scifact</p><p>9.07</p><p>9.57</p><p>9.77</p><p>9.3</p><p>9.1</p><p>9.17</p><p>Touche2020</p><p>38.78</p><p>30.41</p><p>32.24</p><p>33.06</p><p>37.96</p><p>33.67</p><p>TREC-COVID</p><p>92.4</p><p>98.4</p><p>98.2</p><p>93.8</p><p>99.6</p><p>97.4</p><p>MSMARCO</p><p>3.97</p><p>6.00</p><p>6.03</p><p>6.07</p><p>5.47</p><p>6.11</p><p>CQADupstack (avg.)</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>표 2</strong>: 검색·재순위화된 상위 10개 문서를 기준으로 (데이터 세트, 재순위화 모델) 조합별 심사 비율 계산</p><p><strong>표 2</strong>를 보면 <code>TREC-COVID</code>(90% 이상 커버리지), <code>DBPedia</code>(~65%), <code>Touche2020</code>, <code>nfcorpus</code>(~35%)를 제외한 대부분의 데이터 세트는 검색 또는 재순위화 이후의 라벨링 비율이 5%에서 10%를 조금 넘는 수준에 불과함을 알 수 있습니다. 이는 표시되지 않은 문서들이 모두 관련 문서라는 뜻은 아니지만, 그중 일부는 특히 상위 순위에 위치한 경우 긍정적인, 즉 관련성 있는 문서일 가능성이 있다는 점을 시사합니다.</p><p>범용 목적의 지시 튜닝 언어 모델의 등장으로, 관련성 판단을 자동화할 잠재력을 지닌 새롭고 강력한 도구를 갖게 되었습니다. 이러한 방법들은 일반적으로 계산 비용이 너무 커서 실제 온라인 검색에 사용하기는 어렵지만, 여기서는 오프라인 평가에 초점을 맞추고 있습니다. 다음에서는 이를 활용해 일부 BEIR 데이터 세트가 얕은 마크업 문제를 겪고 있다는 증거를 살펴봅니다.</p><p>이 가설을 더 자세히 검증하기 위해 MSMARCO에 초점을 맞추고, 현재 관련 문서로 표시되지 문서 중 Cohere v2로 재순위화된 상위 5개 문서를 포함해 100개의 쿼리 하위 집합을 선택했습니다. 평가는 두 가지 서로 다른 경로로 진행했습니다. 첫째, 신중하게 조정된 프롬프트(자세한 내용은 나중에 다룰 예정)를 사용해 최근 공개된 <a href="https://huggingface.co/microsoft/Phi-3-mini-4k-instruct">Phi-3-mini-4k</a> 모델이 특정 문서가 해당 쿼리와 관련이 있는지(또는 없는지)를 예측하도록 했습니다. 이와 동시에, LLM 출력과 사람의 판단 간의 일치율을 평가하기 위해 이러한 사례들에 대해 수동으로 라벨링을 하는 작업도 진행했습니다. 전반적으로, 다음과 같은 두 가지 결론을 도출할 수 있습니다.</p><ul><li><p>LLM 응답과 인간의 판단 간의 일치율은 약 80%였으며, 이는 해당 방향으로 나아가기 위한 출발점으로 보기에 충분히 좋은 수치입니다.</p></li><li><p>인간 판단을 기준으로 했을 때, 전체 사례의 57.6%에서 반환된 문서들이 실제로 쿼리와 관련이 있는 것으로 확인되었습니다. 이를 다른 방식으로 표현하면, 100개의 쿼리에 대해 관련 문서로 판단된 문서는 107개였지만, 실제로는 최소 0.576 × 5 × 100 = 288개의 추가 문서가 쿼리와 관련이 있다는 의미입니다!</p></li></ul><p>다음은 <code>MSMARCO</code>/<code>dev</code> 데이터 세트에서 가져온 몇 가지 예시입니다. 여기에는 쿼리, 주석이 달린 긍정 문서(<code>qrels</code>에 포함된 문서), 그리고 마크업이 불완전해 발생한 거짓 음성 문서가 함께 포함되어 있습니다.</p><p>예 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>예 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>이처럼 특정 쿼리를 수동으로 평가하는 방식은 nDCG@10과 같은 정량적 지표를 보완하면서 검색 품질을 이해하는 데 전반적으로 유용한 기법입니다. 검색 변경 시 항상 실행하는 대표적인 쿼리 집합이 있다면, 통계에서는 보이지 않는 성능 변화에 대한 중요한 정성적 정보를 얻을 수 있습니다. 예를 들어, 검색 결과에 포함된 잘못된 결과들에 대해 훨씬 더 많은 인사이트를 제공합니다. 검색 시스템이 반환한 결과 중 명백한 오류를 찾아내거나, 도메인 특화 용어를 잘못 해석하는 등 서로 연관된 오류 유형을 파악하는 데 도움이 됩니다.</p><p>이 결과는 <code>MSMARCO</code> 평가에 관한 관련 연구와 일치합니다. 예를 들어, <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh 외</a> 연구진은 크라우드소싱 작업자를 활용해 선호도 판단을 수행하는 유사한 절차를 따르며, 여러 결과 중에서도 재순위화 모듈이 반환한 문서가 MSMARCO <code>qrels</code> 파일에 포함된 문서보다 많은 경우 선호된다는 점을 보여줍니다. 또 다른 근거는 <a href="https://arxiv.org/pdf/2010.08191">RocketQA</a> 재순위화 모델의 저자들이 제시한 결과에서 확인할 수 있는데, 수작업 검토 후 재순위화된 문서의 70% 이상이 관련 문서로 판명되었다고 보고하고 있습니다.</p><p> 업데이트 - 9월 9일: 데이터 세트를 면밀히 재평가한 결과, 관련 문서가 15건 더 확인되어 총 273건에서 288건으로 증가했습니다.</p><h2>주요 요점 및 향후 계획</h2><ul><li><p>더 나은 기준 데이터를 위한 추구는 끝이 없습니다. 이는 벤치마킹과 모델 비교에 매우 중요하기 때문입니다. LLM은 주의 깊게 사용하고 적절한 지시로 튜닝한다면 일부 평가 영역에서 도움을 줄 수 있습니다.</p></li><li><p>더 일반적으로 말하면, 벤치마크가 완벽할 수는 없기 때문에 단순한 점수 비교에서 벗어나, 통계적으로 유의미한 차이를 포착할 수 있는 보다 견고한 기법으로 전환하는 편이 바람직할 수 있습니다. <a href="https://arxiv.org/pdf/2109.00062">Arabzadeh 외</a> 연구진의 작업은 이러한 접근의 좋은 예를 제공하는데, 이들은 연구 결과를 바탕으로 여러 실험 실행 간의 차이가 유의미한지 여부를 보여주는 95% 신뢰구간을 구축했습니다. 함께 제공된 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/evaluating-search-relevance-part-1/retrieve-and-rerank.ipynb">노트북</a>에서는 <a href="https://en.wikipedia.org/wiki/Bootstrapping_(statistics)">부트스트래핑</a>을 사용해 신뢰구간을 계산하는 구현 예시도 제시합니다.</p></li><li><p>최종 사용자 관점에서는, 벤치마크 결과를 해석할 때 작업 정합성을 함께 고려하는 것이 유용합니다. 예를 들어, RAG 파이프라인을 구축하는 AI 엔지니어라면 일반적인 사용 사례가 서로 다른 출처의 여러 정보를 조합하는 것임을 알고 있을 것입니다. 이런 경우에는 BEIR 전체 벤치마크의 전역 평균 성능을 보는 것보다, HotpotQA와 같은 멀티홉 QA 데이터 세트에서 검색 모델의 성능을 평가하는 편이 훨씬 더 의미가 있습니다.</p></li></ul><p><a href="https://www.elastic.co/search-labs/blog/evaluating-search-relevance-part-2">다음 블로그 게시물에서는</a> Phi-3를 LLM 판정자로 사용하는 방법과, 관련성을 예측하도록 이를 튜닝해 나간 과정을 보다 깊이 있게 다룹니다.</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[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[AI 표절: Elasticsearch를 통한 표절 탐지]]></title>
    <description><![CDATA[NLP 모델과 벡터 검색을 사용한 사용 사례를 중심으로 Elasticsearch를 사용해 AI 표절을 확인하는 방법을 알려드립니다.]]></description>
    <content:encoded><![CDATA[<p>표절은 콘텐츠의 일부 또는 전체를 복사하는 <strong>직접적</strong> 표절과 일부 단어나 문구를 변경하여 저자의 저작물을 다시 표현하는 <strong>의역적</strong> 표절로 나눌 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6e139760e56ec98/6a171147dc55de0ad2e00edf/5d7073187fda829438aeec8d3a1194a5bea2ba57-1440x347.png" alt="" /><p>영감과 의역에는 차이가 있습니다. 콘텐츠를 읽고 영감을 얻은 다음 비슷한 결론에 도달하더라도 자신의 말로 아이디어를 탐구할 수 있습니다.</p><p>표절은 오랫동안 논의의 대상이 되어 왔지만, 콘텐츠의 제작과 게시가 가속화되면서 표절 문제는 계속 제기되고 있으며 지속적인 과제가 되고 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2af1678cb04506b/6a171149cf4f251c2ab2d257/a0b9a98d729db09dae0a79315c001e6763c12704-1400x1016.png" alt="" /><p>이 문제는 표절 검사가 자주 이루어지는 서적, 학술 연구 또는 사법 문서에만 국한되지 않습니다. 또한 신문과 소셜 미디어까지 확장할 수 있습니다.</p><p>정보가 풍부하고 퍼블리싱에 쉽게 접근할 수 있는 상황에서 어떻게 하면 확장 가능한 수준에서 표절을 효과적으로 검사할 수 있을까요?</p><p>대학, 정부 기관 및 기업에서는 다양한 도구를 사용하지만, 간단한 <a href="https://www.elastic.co/search-labs/lexical-and-semantic-search-with-elasticsearch">어휘 검색을</a> 통해 직접적인 표절을 효과적으로 감지할 수 있지만, 가장 큰 문제는 <strong>의역된 콘텐츠를</strong>식별하는 데 있습니다.</p><h2>생성적 AI를 통한 표절 탐지</h2><p>제너레이티브 AI로 새로운 도전이 시작됩니다. AI가 생성한 콘텐츠를 복사할 경우 표절로 간주되나요?</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a1ed1b6fa3f1a56/6a17114b4a531bc40736aa69/9345b28d6d27c37469bc38e823c41780b4eabfe5-1440x875.png" alt="" /><p>예를 들어 <a href="https://openai.com/">OpenAI</a> <a href="https://openai.com/policies/terms-of-use">이용약관에는</a> OpenAI가 사용자를 위해 API로 생성한 콘텐츠에 대한 저작권을 주장하지 않는다고 명시되어 있습니다. 이 경우 생성 AI를 사용하는 개인은 생성된 콘텐츠를 인용 없이 원하는 대로 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc2e08ab5b808e38/6a17114dab7f086cbadb9f93/e1f415f69a247666f02ddc81468944920c874cd7-968x814.png" alt="" /><p>그러나 효율성을 개선하기 위해 제너레이티브 AI를 사용하는 것에 대한 수용 여부는 여전히 논의의 여지가 있습니다.</p><p>표절 탐지에 기여하기 위해 OpenAI는 <a href="https://huggingface.co/roberta-base-openai-detector">탐지 모델을</a> 개발했지만 나중에 그 정확도가 충분히 높지 않다는 사실을 인정했습니다.</p><p><em>"이는 단독으로 탐지하기에는 정확도가 충분하지 않으며, 메타데이터 기반 접근 방식, 사람의 판단, 대중 교육과 함께 사용해야 더 효과적이라고 생각합니다."</em></p><p>하지만 더 많은 도구가 제공되면서 의역 및 인공지능 콘텐츠의 경우에도 표절을 감지할 수 있는 옵션이 늘어났습니다.</p><h2>Elasticsearch로 표절 탐지하기</h2><p>이 블로그에서는 메타데이터 검색을 넘어 자연어 처리(NLP) 모델과 벡터 검색의 사용 사례인 표절 탐지에 대해 한 가지 더 살펴보고자 합니다.</p><p>이는 NLP 관련 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/plagiarism-detection-with-elasticsearch/plagiarism_detection_es.ipynb">기사가</a> 포함된 <a href="https://www.sbert.net/">SentenceTransformers의</a> <a href="https://sbert.net/datasets/emnlp2016-2018.json">데이터 세트를</a> 활용하는 Python 예제를 통해 설명합니다. 이전에 Elasticsearch로 가져온 텍스트 <a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2">임베딩 모델로</a> 생성된 '초록' 임베딩을 고려하여 '의미론적 텍스트 유사성'을 수행하여 초록의 표절 여부를 확인합니다. 또한, AI가 생성한 콘텐츠인 AI 표절을 식별하기 위해 OpenAI에서 개발한 자연어 처리( <a href="https://huggingface.co/roberta-base-openai-detector">NLP) 모델도</a> Elasticsearch로 가져왔습니다.</p><p>다음 이미지에는 데이터 흐름이 나와 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0464f88d3ed12070/6a17114fab7f084905db9f97/1ad89c98a2f42a497548ca3947749bad54ec1172-1440x880.png" alt="" /><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ingest.html">추론 프로세서가</a> 있는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-processor.html">수집</a> 파이프라인에서 'abstract' 단락은 768차원 벡터인 'abstract_vector.predicted_value'에 매핑됩니다.</p><p>매핑:</p>"abstract_vector.predicted_value": { # Inference results field
"type": "dense_vector", 
"dims": 768, # model embedding_size
"index": "true", 
"similarity": "dot_product" # When indexing vectors for approximate kNN search, you need to specify the similarity function for comparing the vectors.
<p>벡터 표현 간의 유사성은 '유사성' <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html#dense-vector-params">매개변수를</a> 사용하여 정의된 벡터 유사성 메트릭을 사용하여 측정합니다.</p><p><a href="https://en.wikipedia.org/wiki/Cosine_similarity">코사인은</a> 기본 유사성 지표로, '(1 + 코사인(쿼리, 벡터)) / 2'로 계산됩니다. / 2'. 원본 벡터를 보존해야 하고 미리 정규화할 수 없는 경우가 아니라면, 코사인 유사성을 수행하는 가장 효율적인 방법은 모든 벡터를 단위 길이로 정규화하는 것입니다. 이렇게 하면 검색 중에 추가 벡터 길이 계산을 수행하지 않고 대신 'dot_product'를 사용할 수 있습니다.</p><p>동일한 파이프라인에서 <a href="https://huggingface.co/roberta-base-openai-detector">텍스트 분류 모델을</a> 포함하는 또 다른 추론 프로세서는 콘텐츠가 사람이 작성한 '진짜' 콘텐츠인지, 아니면 AI가 작성한 '가짜' 콘텐츠인지 감지하여 각 문서에 'openai-detector.predicted_value'를 추가합니다.</p><p>수집 파이프라인:</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>쿼리 시, 동일한 텍스트 임베딩 모델이 'query_vector_builder' 객체에서 쿼리 'model_text'의 벡터 표현을 생성하는 데도 사용됩니다.</p><p>k-근접 이웃(kNN) 검색은 유사성 메트릭으로 측정한 쿼리 벡터에 가장 가까운 k개의 벡터를 찾습니다.</p><p>각 문서의 _점수는 유사성에서 파생되며, 점수가 높을수록 순위가 높아집니다. 이는 문서가 의미론적으로 더 유사하다는 것을 의미합니다. &gt; 0.9점일 경우 '높은 유사성', &lt; 0.7점일 경우 '낮은 유사성', 그 외에는 '보통 유사성'으로 간주합니다. 사용 사례에 따라 표절로 인정되는 _점수 수준을 결정하기 위해 다양한 임계값을 유연하게 설정할 수 있습니다.</p><p>또한 텍스트 분류를 수행하여 텍스트 쿼리에서 AI가 생성한 요소도 확인합니다.</p><p>쿼리:</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>출력:</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>이 예에서는 데이터 세트의 '추상' 값 중 하나를 텍스트 쿼리 'model_text'로 사용한 후 표절이 식별되었습니다. 유사도 점수는 1.0으로 높은 수준의 유사성, 즉 <strong>직접적인 표절을</strong> 나타냅니다. 벡터화된 쿼리와 문서는 예상대로 AI가 생성한 콘텐츠로 인식되지 않았습니다.</p><p>쿼리:</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>출력:</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>텍스트 쿼리 'model_text'를 유사한 단어의 반복을 최소화하면서 동일한 메시지를 전달하는 AI 생성 텍스트로 업데이트한 결과, 감지된 유사도는 여전히 높았지만 1.0이 아닌 0.9302529로 <strong>표절로</strong> 판정되었습니다. AI가 생성한 이 쿼리도 감지될 것으로 예상했습니다.</p><p>마지막으로, 이 문서 중 하나의 초록이 아닌 Elasticsearch에 대한 텍스트 쿼리 'model_text'를 고려한 결과, 탐지된 유사도는 0.68991005 으로 고려 임계값에 따라 유사도가 낮은 것으로 나타났습니다.</p><p>쿼리:</p>#different text - not a plagiarism

model_text = 'Elasticsearch provides near real-time search and analytics for all types of data.'
<p>출력:</p>Low similarity detected. This might not be plagiarism.
<p>AI가 생성한 텍스트 쿼리에서 표절이 정확하게 식별되었지만, 의역과 직접 복사한 콘텐츠의 경우 표절 탐지를 위해서는 다양한 측면을 고려해야 합니다.</p><p>AI가 생성한 콘텐츠 감지의 맥락에서 가치 있는 기여를 하는 모델을 살펴봤습니다. 그러나 독립형 탐지에는 내재된 한계가 있으므로 정확도를 높이기 위해 다른 방법을 통합해야 한다는 점을 인식하는 것이 중요합니다.</p><p>텍스트 임베딩 모델 선택에 따른 가변성도 고려해야 할 사항입니다. 각기 다른 데이터 세트로 학습된 모델에 따라 유사성 수준이 달라지며, 이는 생성된 텍스트 임베딩의 중요성을 강조합니다.</p><p>마지막으로, 이 예제에서는 문서의 초록을 사용했습니다. 그러나 표절 탐지는 대용량 문서와 관련된 경우가 많기 때문에 텍스트 길이 문제를 해결하는 것이 필수적입니다. 텍스트가 모델의 토큰 한도를 초과하는 경우가 많으므로 임베딩을 구축하기 전에 청크로 분할해야 하는 경우가 많습니다. 이를 처리하는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.11/knn-search.html#nested-knn-search">실용적인 접근 방식은</a> dense_vector와 함께 중첩된 구조를 활용하는 것입니다.</p><h2>결론</h2><p>이 블로그에서는 특히 의역 및 AI 생성 콘텐츠에서 표절을 탐지하는 데 따르는 어려움과 이를 위해 시맨틱 텍스트 유사성 및 텍스트 분류를 사용하는 방법에 대해 설명했습니다.</p><p>이러한 방법을 결합하여 AI가 생성한 콘텐츠, 직접 표절 및 의역 표절을 성공적으로 식별한 표절 탐지 사례를 제공했습니다.</p><p>주요 목표는 탐지를 간소화하는 필터링 시스템을 구축하는 것이었지만, 검증을 위해서는 여전히 사람의 평가가 필수적이었습니다.</p><p>의미론적 텍스트 유사도 및 NLP에 대해 자세히 알아보려면 다음 링크도 확인해 보세요:</p><ul><li><p><a href="https://www.elastic.co/what-is/semantic-search">시맨틱 검색이란 무엇인가요?</a></p></li><li><p><a href="https://www.elastic.co/what-is/natural-language-processing">자연어 처리(NLP)란 무엇인가요?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/lexical-and-semantic-search-with-elasticsearch">Elasticsearch를 사용한 어휘 및 시맨틱 검색</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chunking-via-ingest-pipelines">수집 파이프라인과 중첩된 벡터를 통해 대용량 문서를 청크 처리하면 구절 검색이 쉬워집니다.</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[벡터 데이터베이스]]></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[Elasticsearch를 사용한 어휘 및 시맨틱 검색]]></title>
    <description><![CDATA[이 블로그에서는 어휘 및 시맨틱 검색을 중심으로 Elasticsearch를 사용해 정보를 검색하는 다양한 접근 방식을 살펴보겠습니다.]]></description>
    <content:encoded><![CDATA[<p>검색은 검색어 또는 복합 검색어를 기반으로 가장 관련성이 높은 정보를 찾는 과정이며, 관련 검색 결과는 이러한 검색어와 가장 잘 일치하는 문서입니다. 검색과 관련된 여러 가지 과제와 방법이 있지만, <strong>질문에 대한 최상의 답변을 찾는다는</strong> 궁극적인 목표는 동일합니다.</p><p>이 목표를 고려하여 이 블로그 게시물에서는 텍스트 검색에 특히 초점을 맞춘 <strong>어휘 검색과 의미론적 검색을</strong>중심으로 Elasticsearch를 사용하여 정보를 검색하는 다양한 접근 방식을 살펴보겠습니다.</p><h2>필수 구성 요소</h2><p>이를 위해 이커머스 상품 정보를 시뮬레이션하기 위해 생성된 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/products-ecommerce.json">데이터 세트에</a> 대한 다양한 검색 시나리오를 보여주는 Python 예제를 제공합니다.</p><p>이 데이터 세트에는 각각 설명이 포함된 2,500개 이상의 제품이 포함되어 있습니다. 이러한 제품은 아래와 같이 76개의 개별 제품 카테고리로 분류되며, 각 카테고리에는 다양한 수의 제품이 포함되어 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34415151c00ced2b/6a17d8710b0bed6c9bdd342c/4104466050f3024b6bcaf382da2a702650f62227-1440x708.png" alt="" /><p><em>트리맵 시각화 - category.keyword(제품 카테고리)의 상위 22개 값</em></p><p>설정에는 다음이 필요합니다:</p><ul><li><p>Python 3.6 이상</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/python-api/current/index.html">Elastic Python 클라이언트</a></p></li><li><p>Elastic 8.8 이상 배포, 8GB 메모리 머신 러닝 노드 사용</p></li><li><p>Elastic에 사전 로드되어 배포에 설치 및 시작되는 <a href="https://www.elastic.co/guide/en/machine-learning/8.9/ml-nlp-elser.html">Elastic 학습된 Sparse EncodeR</a> 모델</p></li></ul><p>저희는 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">무료 체험판을 사용할 수</a> 있습니다.</p><p>이 블로그 게시물에 제공된 검색어 외에도 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 노트북에서</a> 다음 프로세스를 안내해 드립니다:</p><ul><li><p>Python 클라이언트를 사용하여 Elastic 배포에 연결 설정하기</p></li><li><p>텍스트 임베딩 모델을 Elasticsearch 클러스터에 로드합니다.</p></li><li><p>피처 벡터와 고밀도 벡터를 인덱싱하기 위한 매핑으로 인덱스를 생성합니다.</p></li><li><p>텍스트 삽입 및 텍스트 확장을 위한 추론 프로세서가 포함된 수집 파이프라인 만들기</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e95406ece8f08d9/6a17d8731d1b83d32e93e2f4/54a9a490a3b0cf1b9c2228bee8eddd3f566bd435-1418x1102.png" alt="" /><h2>어휘 검색 - 희소 검색</h2><p>텍스트 쿼리를 기반으로 Elasticsearch에서 문서의 관련성 순위를 매기는 고전적인 방식은 <strong>어휘 검색을 위한</strong> 희소 모델 <a href="https://en.wikipedia.org/wiki/Okapi_BM25">인 BM25 모델의</a> Lucene 구현을 사용합니다. 이 방법은 텍스트 검색의 전통적인 접근 방식을 따르며, 정확한 용어 일치 항목을 찾습니다.</p><p>이 검색을 가능하게 하기 위해 Elasticsearch는 텍스트 분석을 수행하여 <strong>텍스트 필드</strong> 데이터를 검색 가능한 형식으로 변환합니다.</p><p><strong>텍스트 분석</strong> 은 검색을 위해 관련 토큰을 추출하는 프로세스를 관리하는 일련의 규칙인<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analyzer-anatomy.html"> 분석기에</a> 의해 수행됩니다. 분석기에는<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html"> 토큰라이저가</a> 정확히 하나만 있어야 합니다. 토큰화 도구는 아래 예시와 같이 문자 스트림을 수신하여 개별 토큰(일반적으로 개별 단어)으로 분할합니다:</p><h3>어휘 검색을 위한 문자열 토큰화</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>출력</p>Analyzed Tokens: ['comfortable', 'furniture', 'for', 'a', 'large', 'balcony']
<p>이 예에서는 영어 문법 기반 토큰화를 제공하기 때문에 대부분의 사용 사례에서 잘 작동하는 기본 분석기인 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-standard-analyzer.html">표준</a> 분석기를 사용하고 있습니다. 토큰화를 통해 개별 조건에 따른 매칭이 가능하지만, 각 토큰은 여전히 문자 그대로 매칭됩니다.</p><p>검색 환경을 맞춤 설정하고 싶다면 다른<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-analyzers.html"> 기본 제공 분석기를</a> 선택할 수 있습니다. 예를 들어, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-stop-analyzer.html">중지 분석기를</a> 사용하도록 코드를 업데이트하면 중지 단어 제거를 지원하여 문자가 아닌 모든 문자에서 텍스트를 토큰으로 분리할 수 있습니다.</p>...
# Define the analyze request
request_body = {
  "analyzer": "stop",
  "text": text
}
...
<p>출력</p>Analyzed Tokens: ['comfortable', 'furniture', 'large', 'balcony']
<p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-custom-analyzer.html">기본</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-charfilters.html">제공</a> 분석기가 사용자의 요구 사항을 충족하지 못하는 경우, 0개 이상의 문자 필터,<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenizers.html">토큰화</a> 도구 및 0개 이상의 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-tokenfilters.html">토큰 필터를 적절히 조합하여 사용자 지정</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>토큰화기와 토큰 필터를 결합한 위의 예에서 텍스트는 <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.">동의어 토큰</a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-lowercase-tokenfilter.html">필터에</a> 의해 처리되기 전에 소문자 필터에 의해 소문자로 처리됩니다.</p><h2>어휘 일치</h2><p><a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables">BM25는</a> 용어의 빈도와 중요도에 따라 주어진 검색 쿼리에 대한 문서의 관련성을 측정합니다.</p><p>아래 코드는 <em>"전자상거래 검색"</em> 인덱스의 "설명" 필드 값과 검색 쿼리를 고려하여 최대 2개의 문서를 <em>검색하는</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html">일치</a> 쿼리를 수행합니다.<strong>"</strong><em><strong>넓은 발코니를 위한 편안한 가구 ".</strong></em></p><p>이 쿼리와 일치하는 문서로 간주되는 기준을 세분화하면 정확도를 높일 수 있습니다. 그러나 보다 구체적인 결과를 얻으려면 변형에 대한 허용 오차가 낮아지는 대가가 따릅니다.</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>출력</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>결과물을 분석한 결과, 가장 연관성이 높은 결과는 "<em>장난감 " 카테고리의</em>" 바비 드림하우스 " 제품이며,<em>설명에 "</em><em>가구</em>", " 대형" 및 <em>"발코니</em>" 라는 용어가 포함되어 있어 연관성이 높으며, 이 제품은 설명에 검색어와 일치하는 3개의 용어가 포함된 유일한 제품이며, 설명에 <em>"발코니"</em> 라는 용어가 포함된 유일한 제품이기도 합니다.</p><p>두 번째로 관련성이 높은 제품은 "<em>실내 가구</em>" 로 분류된<em>" 편안한 흔들</em>의자 " 이며 설명에 "<em>편안한</em>" 및 "<em>가구 " 라는</em>용어가 포함되어 있습니다. 데이터 세트에서 이 검색 쿼리의 2개 이상의 용어와 일치하는 제품은 3개뿐이며, 이 제품은 그 중 하나입니다.</p><p><em>"105개 제품의 설명에는 '</em> 편안함" '이, 4개 카테고리의 4개 제품 설명에는 ' <em>"가구"</em> '가 표시됩니다: <em>장난감</em>, <em>실내 가구, 실외 가구 및 '개 및 고양이 용품 &amp; 장난감'.</em></p><p>보시다시피, 검색어와 가장 연관성이 높은 제품은 장난감이고 두 번째로 연관성이 높은 제품은 실내 가구입니다. 이러한 문서가 일치하는 이유를 알 수 있도록 점수 계산에 대한 자세한 정보를 원한다면 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-explain.html"><em>explain</em></a> __query 매개 변수를 true로 설정하면 됩니다.</p><p>두 결과 모두 가장 관련성이 높은 결과이지만, 이 데이터 세트의 문서 수와 용어 발생을 모두 고려할 때 "<em>넓은 발코니에 어울리는 편안한 가구</em>" 쿼리의 의도는 장난감과 실내 가구를 제외한 실제 넓은 발코니에 어울리는 가구를 검색하는 것입니다.</p><p>어휘 검색은 비교적 <strong>간단하고 빠르지만</strong>, 사용자의 의도와 쿼리를 알지 못하면 가능한 모든 용어와 동의어를 알 수 없기 때문에 한계가 있습니다. 자연어 사용에서 흔히 볼 수 있는 현상은 <strong>어휘 불일치입니다</strong>. <a href="https://dl.acm.org/doi/abs/10.1145/32206.32212">연구에</a> 따르면, 같은 분야의 전문가들이 같은 사물의 이름을 다르게 지을 확률은 평균적으로 <strong>80% %에</strong> 달한다고 합니다.</p><p>이러한 한계로 인해 의미론적 지식을 통합하는 다른 채점 모델을 찾게 되었습니다. 자연어처럼 순차적인 입력 토큰을 처리하는 데 탁월한 트랜스포머 기반 모델은 문서와 쿼리의 수학적 표현을 모두 고려하여 검색의 기본 의미를 포착합니다. 이를 통해 문맥을 인식하는 조밀한 텍스트 벡터 표현이 가능해져 관련 콘텐츠를 찾는 정교한 방법인 <strong>시맨틱 검색을</strong> 강화할 수 있습니다.</p><h2>시맨틱 검색 - 고밀도 검색</h2><p>이러한 맥락에서 데이터를 의미 있는 벡터 값으로 변환한 후, 데이터 집합에서 쿼리 벡터와 가장 유사한 벡터 표현을 찾기 위해 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">k-최근접 이웃(kNN)</a> 검색 알고리즘을 사용합니다. Elasticsearch는 kNN 검색을 위해 두 가지 방법, 즉 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#exact-knn">정확한 무차별 kNN과</a> ANN이라고도 하는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#approximate-knn">대략적인 kNN을</a> 지원합니다.</p><p>무차별 대입 방식은 정확한 결과를 보장하지만 대규모 데이터 세트에는 잘 확장되지 않습니다. 근사 kNN은 성능 향상을 위해 정확도를 일부 희생하여 가장 가까운 이웃을 효율적으로 찾습니다.</p><p>kNN 검색과 고밀도 벡터 인덱스에 대한 Lucene의 지원을 통해 Elasticsearch는 다양한 <a href="http://ann-benchmarks.com/">앤 벤치마크 데이터 세트에서</a> 강력한 검색 성능을 보여주는 계층적 탐색 가능한 작은 세계(HNSW) 알고리즘을 활용할 수 있습니다. 아래 예제 코드를 사용하여 Python에서 대략적인 kNN 검색을 수행할 수 있습니다.</p><h3>대략적인 kNN을 사용한 시맨틱 검색</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>이 코드 블록은 제품 데이터 세트의 " description" 필드가 포함된 것을 고려하여<em>Elasticsearch의 kNN을</em><em>사용하여 " 큰 발코니를 위한 편안한 가구 "</em> 의 벡터화된 쿼리(query_vector_build)와 유사한 설명을 가진 최대 두 개의 제품을 반환합니다.</p><p>제품 임베딩은 이전에 추론 프로세서가 포함된 수집 파이프라인에서 생성되었습니다. <em>"</em><a href="https://huggingface.co/sentence-transformers/all-mpnet-base-v2"><em>all-mpnet-base-v2</em></a><em>"</em> 텍스트 임베딩 모델을 포함하는 추론 프로세서를 사용하여 파이프라인에서 수집되는 데이터에 대해 추론하는 방식으로 생성되었습니다.</p><p>이 모델은 다음을 사용하여 사전 학습된 모델의 평가를 기반으로 선택되었습니다. <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> 훈련 중 모델을 평가하는 데 다양한 클래스가 사용됩니다. "all-mpnet-base-v2" 모델은 <a href="https://www.sbert.net/docs/pretrained_models.html">문장-변환 순위에서</a> 가장 우수한 평균 성능을 보였으며, <a href="https://huggingface.co/spaces/mteb/leaderboard">대용량 텍스트 임베딩 벤치마크(MTEB)</a> 리더보드에서도 유리한 위치를 확보했습니다. 이 모델은<a href="https://huggingface.co/microsoft/mpnet-base"> Microsoft/MPnet-Base</a> 모델을 사전 학습하고 1B 문장 쌍 데이터 세트에서 미세 조정하여 문장을 768차원의 고밀도 벡터 공간에 매핑합니다.</p><p>또는 도메인별 데이터에 맞게 세밀하게 조정된 다른 모델을 사용할 수도 있습니다.</p><p>출력</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>출력은 선택한 모델,</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example"><em>필터</em></a> <em>및</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#tune-approximate-knn-for-speed-accuracy"><em>대략적인 kNN 튜닝에</em></a>따라 달라질 수 있습니다<em>.</em></p><p><em>검색어에 " outdoor</em>" 라는 단어가 명시적으로 언급되지 않았음에도 불구하고 kNN 검색 결과는 모두 "<em>아웃도어</em>가구 " 카테고리에 속하며, 이는 문맥에서 의미 이해의 중요성을 강조합니다.</p><p>고밀도 벡터 검색은 몇 가지 장점이 있습니다:</p><ul><li><p>시맨틱 검색 활성화</p></li><li><p>대규모 데이터 세트를 처리할 수 있는 확장성</p></li><li><p>다양한 데이터 유형을 처리할 수 있는 유연성</p></li></ul><p>하지만 <strong>밀도 높은 벡터 검색에는 고유한 문제도</strong> 있습니다:</p><ul><li><p>사용 사례에 적합한 임베딩 모델 선택하기</p></li><li><p>모델을 선택한 후에는 도메인별 데이터 세트에서 성능을 최적화하기 위해 모델을 미세 조정해야 할 수 있으며, 이 과정에는 도메인 전문가의 참여가 필요합니다.</p></li><li><p>또한 고차원 벡터를 인덱싱하는 데는 계산 비용이 많이 들 수 있습니다.</p></li></ul><h2>시맨틱 검색 - 학습된 희소 검색</h2><p>시맨틱 검색을 수행하는 또 다른 방법인 학습된 희소 검색에 대해 알아보겠습니다.</p><p>스파스 모델로서, 수십 년에 걸친 최적화의 혜택을 누리고 있는 Elasticsearch의 Lucene 기반 반전 인덱스를 활용합니다. 그러나 이 접근 방식은 단순히 BM25와 같은 어휘 채점 기능을 사용하여 동의어를 추가하는 것 이상의 의미를 갖습니다. 대신, 더 심층적인 언어 규모 지식을 사용하여 학습된 연관성을 통합하여 관련성을 최적화합니다.</p><p>검색 쿼리를 확장하여 원래 쿼리에 없는 관련 용어를 포함시킴으로써, 아래 예에서 볼 수 있듯이 <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic 학습형 스파스 인코더는</a> <strong>스파스 벡터 임베딩을 개선합니다</strong>.</p><h3>Elastic 학습형 스파스 인코더를 사용한 스파스 벡터 검색</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>출력</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>이 경우 결과에는 "<em>야외 가구</em>" 와 매우 유사한 제품을 제공하는 "<em>정원 가구 " 카테고리가 포함됩니다.</em></p><p>분석하여 "ml.tokens", 학습된 희소 검색이 생성한 토큰이 포함된 "rank_features" 필드를 보면, 생성된 다양한 토큰 중 검색 쿼리의 일부가 아니지만 "<em>relax</em>" (편안한), "<em>sofa</em>" (가구), "<em>outdoor</em>" (발코니)와 같이 의미상 여전히 연관성이 있는 용어가 있다는 것을 알 수 있습니다.</p><p>아래 이미지는 용어 확장이 있는 경우와 없는 경우 모두 쿼리와 함께 이러한 용어 중 일부를 강조 표시합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7e869985347b19a7/6a17d875e31791dc2c2d56a1/dd86607fce6137d843a3ec002390eaa988b432f9-1440x502.png" alt="" /><p>이 모델은 문맥 인식 검색을 제공하고 어휘 불일치 문제를 완화하는 동시에 해석 가능한 결과를 제공하는 데 도움이 됩니다. 도메인별 재학습이 적용되지 않은 경우에도 고밀도 벡터 모델을 능가하는 성능을 발휘할 수 있습니다.</p><h2>하이브리드 검색: 어휘 검색과 시맨틱 검색을 결합하여 관련성 높은 결과 제공</h2><p>검색에 관한 한 만능 솔루션은 존재하지 않습니다. 이러한 검색 방법에는 각각 장점도 있지만 문제점도 있습니다. 사용 사례에 따라 가장 적합한 옵션이 변경될 수 있습니다. 종종 검색 방법 간에 상호 보완적으로 최상의 결과를 얻을 수 있습니다. 따라서 관련성을 높이기 위해 각 방법의 강점을 결합하는 방법을 살펴볼 것입니다.</p><p><strong>하이브리드 검색을</strong> 구현하는 방법에는 선형 조합, 각 점수에 가중치를 부여하는 방법, 가중치를 지정할 필요가 없는 상호 순위 융합(RRF) 등 여러 가지가 있습니다.</p><h3>Elasticsearch: 어휘 검색과 시맨틱 검색의 두 가지 장점을 모두 갖춘 최고의 솔루션</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>이 코드에서는 "<em>큰 발코니를 위한 식탁과 편안한 의자</em>" 라는 값을 갖는 두 개의 쿼리를 사용하여 하이브리드 검색을 수행했습니다. "<em>가구</em>" 을 검색어로 사용하는 대신 찾고 있는 내용을 지정하고 있으며, 두 검색 모두 동일한 필드 값인 "설명" 을 고려하고 있습니다. 순위는 BM25와 ELSER 점수에 동일한 가중치를 부여한 선형 조합으로 결정됩니다.</p><p>출력</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>아래 코드에서는 쿼리에 동일한 값을 사용하되, 상호 순위 융합 방법을 사용하여 BM25(쿼리 파라미터)와 kNN(knn 파라미터)의 점수를 결합하여 문서를 결합하고 순위를 매깁니다.</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>RRF 기능은 기술 미리보기 중입니다. 구문은 GA 이전에 변경될 가능성이 높습니다.</em></p><p>출력</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>여기에서도 다양한 필드와 값을 사용할 수 있으며, 이러한 예제 중 일부는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 노트북에서</a> 사용할 수 있습니다.</p><p>보시다시피, Elasticsearch를 사용하면 기존의 어휘 검색과 벡터 검색(희소 또는 고밀도)의 두 가지 장점을 모두 활용하여 목표에 <strong>도달하고 질문에 대한 최상의 답을 찾을</strong>수 있습니다 .</p><p>여기에 언급된 접근 방식에 대해 계속 알아보고 싶다면 다음 블로그가 유용할 수 있습니다:</p><ul><li><p><a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-hybrid">Elastic Stack에서 정보 검색 개선: 하이브리드 검색</a></p></li><li><p><a href="https://www.elastic.co/blog/vector-search-elasticsearch-rationale">Elasticsearch의 벡터 검색: 설계의 근거</a></p></li><li><p><a href="https://www.elastic.co/blog/lexical-ai-powered-search-elastic-vector-database">Elastic의 벡터 데이터베이스로 어휘 및 AI 기반 검색을 최대한 활용하는 방법</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-sparse-encoder-ai-model">Elastic 학습형 스파스 인코더를 소개합니다: 시맨틱 검색을 위한 Elastic의 AI 모델</a></p></li><li><p><a href="https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model">Elastic Stack에서 정보 검색 개선: 새로운 검색 모델인 Elastic 학습형 스파스 인코더를 소개합니다.</a></p></li></ul><p>Elasticsearch는 벡터 검색을 구축하는 데 필요한 모든 도구와 함께 벡터 데이터베이스를 제공합니다:</p><ul><li><p>Elasticsearch <a href="https://www.elastic.co/elasticsearch/vector-database">벡터 데이터베이스</a></p></li><li><p>Elastic의 <a href="https://www.elastic.co/enterprise-search/vector-search">벡터 검색</a> 사용 사례</p></li></ul><h2>결론</h2><p>이 블로그 게시물에서는 특히 텍스트, 어휘 및 의미 검색에 초점을 맞춰 Elasticsearch를 사용해 정보를 검색하는 다양한 접근 방식을 살펴봤습니다. 이를 보여드리기 위해 이커머스 제품 정보가 포함된 데이터 세트를 사용하여 다양한 검색 시나리오를 보여주는 Python 예제를 제공했습니다.</p><p>BM25로 기존 어휘 검색을 검토하고 어휘 불일치 등의 장점과 문제점에 대해 논의했습니다. 우리는 이 문제를 극복하기 위해 시맨틱 지식을 통합하는 것이 중요하다고 강조했습니다. 또한 시맨틱 검색을 가능하게 하는 고밀도 벡터 검색에 대해 논의하고, 고차원 벡터를 색인화할 때의 계산 비용 등 이 검색 방법과 관련된 문제점에 대해서도 다뤘습니다.</p><p>반면에 스파스 벡터는 압축률이 매우 높다고 언급했습니다. 따라서 원래 쿼리에 없는 관련 용어를 포함하도록 검색 쿼리를 확장하는 Elastic의 학습된 스파스 인코더에 대해 설명했습니다.</p><p>검색에 있어 만능 솔루션은 존재하지 않습니다. 각 검색 방법에는 장단점이 있습니다. 따라서 하이브리드 검색의 개념에 대해서도 논의했습니다.</p><p>보시다시피, Elasticsearch를 사용하면 기존의 어휘 검색과 벡터 검색이라는 두 가지 장점을 모두 누릴 수 있습니다!</p><p>시작할 준비가 되셨나요? 사용 가능한 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/lexical-and-semantic-search-with-elasticsearch/ecommerce_dense_sparse_project.ipynb">Python 노트북을</a> 확인하고 <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">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[벡터 데이터베이스]]></category>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[쿼리 언어]]></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>