<?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[Navneet Kumar - 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[Navneet Kumar - 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/author/navneet-kumar</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/author/navneet-kumar</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/author/navneet-kumar.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 10:28:08 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>
  </channel>
</rss>