<?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[벡터 데이터베이스 - 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[벡터 데이터베이스 - 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/vector-database</link>
    </image>
    <link>https://www.elastic.co/kr/search-labs/blog/category/vector-database</link>
    <atom:link href="https://www.elastic.co/kr/search-labs/rss/category/vector-database.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[kr]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 09:33:31 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Elasticsearch 벡터 데이터베이스: 몇 분 만에 출시하고, 경제적인 수천억 개 규모의 확장]]></title>
    <description><![CDATA[최적화된 기본 설정, 서드파티 및 네이티브 Jina AI 모델, 관리형 GPU 추론을 기본 제공하여 하 이브리드 검색의 까다로운 부분을 이미 해결했습니다. 인프라가 아닌 신속하고 확장 가능한 AI 앱을 구축하세요.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch는 전 세계에서 벡터 워크로드용으로 가장 널리 배포된 플랫폼 중 하나로, GitHub, Docusign, Seismic 등 수많은 기업의 시맨틱 검색, 검색 증강 생성(RAG) 및 추천 기능을 지원합니다. 오늘 벡터 기반 애플리케이션에 최적화된 새로운 서버리스 오퍼링인 Elasticsearch Vector Database를 발표합니다. 문서와 쿼리만 제공하면 인프라와 함께 임베딩 및 인덱스 튜닝을 처리해 드립니다. 또한 비용을 저렴하고 확장 가능하게 유지합니다. </p><p>신규 사용자에게 이는 고품질 벡터 검색을 실행할 수 있는 가장 빠른 방법입니다. 이미 Elasticsearch를 사용 중인 경우, 별도의 시스템을 도입하지 않아도 새로운 기능을 통해 데이터가 이미 존재하는 플랫폼에서 바로 벡터 검색을 이용할 수 있습니다. Elasticsearch 벡터 데이터베이스는 대규모 언어 모델(LLM)의 기반 구축부터 AI 에이전트에 검색 및 메모리 기능 제공, 수천억 개의 벡터 처리에 이르기까지 다양한 시나리오를 지원합니다. <a href="https://cloud.elastic.co/registration?onboarding_token=vector">새 프로젝트를 생성</a>하고 몇 분 안에 시작해 보세요.</p><h2>하나의 엔진, 모든 벡터 사용 사례</h2><p>Elasticsearch 벡터 데이터베이스는 벡터를 사용하여 애플리케이션을 구축하는 모든 사용자를 위해 구축되었습니다.</p><ul><li><p><strong>RAG:</strong> 밀집 및 희소 벡터 검색으로 LLM에 적합한 컨텍스트를 검색하거나, 벡터 검색과 어휘 검색을 모두 결합한 하이브리드 검색을 사용하십시오. 생성 품질은 검색 품질에 따라 향상됩니다.</p></li><li><p><strong>AI 에이전트:</strong> 다단계 에이전트 루프에서 요구하는 짧은 지연 시간으로 문서 및 대화 메모리에 대한 빠르고 필터링된 검색 기능을 에이전트에 제공하세요.</p></li><li><p><strong>시맨틱 검색:</strong> 파이프라인 코드 없이 단 하나의 필드 유형으로 키워드가 아닌 의미를 기반으로 매칭합니다.</p></li><li><p><strong>추천 및 유사성:</strong> 제품, 이미지 또는 보유한 모든 콘텐츠 전반에서 대규모로 최근접 이웃을 찾습니다.</p></li></ul><h2>벡터 워크로드에 필요한 모든 것을 기본 최적화 상태로 제공</h2><p>벡터 기반 애플리케이션을 구축하려면 여러 개의 개별 요소를 연결해야 합니다. 즉, 임베딩 모델을 설정하고 호스팅하며, 이를 통해 문서를 색인하고, 벡터를 효율적으로 저장하고, 각 쿼리에 임베딩 모델을 적용하고, 벡터 저장소와 일치시키고, 마지막으로 일치 항목에 해당하는 문서를 검색해야 합니다. Elasticsearch 벡터 데이터베이스는 추가 구성이나 설정 없이 이 모든 작업을 처리합니다.</p><h3>vectordb_document 인덱스 모드를 사용한 벡터 색인</h3><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-vectordb-document-mode">vectordb_document</a> 인덱스 모드는 벡터 우선 워크로드를 위해 특별히 설계된 새로운 인덱스 구성으로, 기본적으로 활성화되어 있으므로 전문가가 선택할 설정을 사용할 수 있습니다. 활성화되는 기능은 다음과 같습니다.</p><ul><li><p><strong>기본적으로 bfloat16 적용:</strong> 벡터는 재현율에 거의 영향을 주지 않으면서 float32의 절반 크기로 저장되므로, 양자화를 적용하기 전부터 디스크 사용 공간을 대략 절반으로 줄여 줍니다.</p></li><li><p><strong>소스 벡터 제외:</strong> Elasticsearch에서는 임베딩이 이미 검색에 사용되는 인덱스 구조에 존재하므로, _source에 두 번째 원시 사본을 보관하는 것은 저장 공간을 늘리고 결과를 가져오는 속도를 늦출 뿐입니다. 중복을 제외하여 응답이 더 빠르게 반환되며 저장하는 양도 줄어듭니다.</p></li><li><p><strong>적절한 파일을 캐시에 미리 로드:</strong> 벡터 쿼리가 가장 먼저 사용하는 데이터 구조를 미리 메모리에 워밍업하므로 첫 번째 쿼리부터 천 번째 쿼리까지도 번개처럼 빠르게 실행됩니다.</p></li><li><p><strong>병렬 병합:</strong> 병합을 통해 세그먼트가 더 체계적인 벡터 구조로 통합되므로 재현율과 지연 시간이 모두 개선되며, 이러한 병합을 멀티스레드로 실행하면 더 빠르게 도달할 수 있습니다.</p></li></ul><h3>벡터 저장 공간, 압축 및 자동 튜닝</h3><ul><li><p>벡터가 자동으로 압축됩니다.<a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch"> 더 나은 이진 양자화(BBQ)</a>는 재현율을 유지하면서 벡터 메모리 공간을 최대 32배까지 줄이고, DiskBBQ는 대규모 워크로드의 메모리 요구 사항을 더욱 줄여줍니다.<a href="https://www.elastic.co/kr/search-labs/blog/vector-quantization-auto-calibration-diskbbq"> </a></p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/vector-quantization-auto-calibration-diskbbq">자동 보정</a>을 선택하면 각 세그먼트의 양자화가 데이터에 맞게 조정되고, 데이터가 변화함에 따라 병합할 때마다 다시 조정됩니다. 18개 데이터 세트에서 테스트한 결과, 초당 쿼리 수(QPS)는 평균 16.7% 향상되었으며 대부분의 데이터 세트에서 재현율도 개선되었습니다.</p></li></ul><h3>관리형 GPU 추론 기반 임베딩</h3><ul><li><p>기본 <a href="https://www.elastic.co/kr/jina-search-models">Jina AI 임베딩 및 순위 재지정 모델</a>로 임베딩을 생성하거나, 모델 서버를 운영할 필요 없이 <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service(EIS)</a>를 통해 관리형 GPU에서 타사 모델을 가져오세요. 자체 모델을 선호하신다면 자체 호스팅도 가능합니다.</p></li><li><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><strong>semantic_text</strong></a> 필드 유형은 청킹 및 임베딩과 함께 쿼리를 자동으로 처리하므로, 시장에서 시맨틱 검색으로 가는 가장 간단한 경로입니다. </p></li></ul><h3>하이브리드 검색 및 필터링된 벡터 검색</h3><ul><li><p><a href="https://www.elastic.co/kr/elasticsearch/hybrid-search">하이브리드 검색</a>이 기본 제공되어 단일 쿼리에서 전체 텍스트 검색과 벡터 검색을 결합할 수 있습니다. 상호 순위 결합(RRF) 또는 원하는 다른 결합 메커니즘을 사용하여 결과를 혼합할 수 있습니다. 벡터 검색은 일반적으로 하이브리드 검색에서 제대로 구성하기 가장 까다로운 부분입니다. Elasticsearch 벡터 데이터베이스를 사용하면 이 문제를 쉽게 해결할 수 있으며 전체 하이브리드 스택이 더욱 향상됩니다. </p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/filtered-hnsw-knn-search">필터링된 벡터 검색</a>을 사용하면 재현율을 저하시키는 사후 처리가 아니라 벡터 검색 자체의 일부로 메타데이터 필터를 적용할 수 있습니다.</p></li></ul><h3>첫날부터 엔터프라이즈</h3><p>또한 역할 기반 액세스 제어(RBAC), 감사 로깅, 순수 벡터 데이터베이스에는 일반적으로 부족한 규정 준수 인증도 이용할 수 있습니다.</p><h2>확장 시에도 경제적이고 예측 가능</h2><p>Elasticsearch 벡터 데이터베이스는 규모가 확장되어도 경제적인 비용을 유지하도록 구축되었습니다. 저장 공간을 선형적으로 유지하고 메모리 사용량을 낮게 유지하는 BBQ 및 DiskBBQ 압축 기술 덕분에 수천억 개의 벡터로 확장하더라도 비용이 급증하지 않습니다. 또한 실제로 지불하는 비용은 여러분이 이미 알고 있는 수치, 즉 저장하는 데이터 양, 인덱싱하는 데이터 양, 필요한 검색 용량을 기반으로 산정됩니다. 문서 수, 벡터 차원 및 쿼리 부하를 예측하면 프로젝트를 생성하기 전에 지불할 비용을 산출할 수 있습니다. 또한 월말에 청구서의 항목을 한 줄 한 줄 명확하게 파악할 수 있습니다. 불투명한 컴퓨팅 단위가 없으며 백그라운드 작업에 예상치 못한 요금도 청구되지 않습니다.</p><h2>Elasticsearch 벡터 데이터베이스 시작하기</h2><h3>서버리스 벡터 데이터베이스 프로젝트 만들기</h3><p><a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud에서 새로운 서버리스 벡터 데이터베이스 프로젝트</a>를 생성하세요. 데이터를 엔드포인트로 지정하면 인덱싱 준비가 완료됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt556cbdfba551f248/6aa10b4332b53038406d321a/image1.png" alt="Elastic Cloud Serverless project types: Elasticsearch, Vector Database, Observability and Security" /><h3>semantic_text를 사용한 인덱스 생성</h3><p>벡터 인덱스 모드는 벡터 구성을 처리합니다. semantic_text를 사용하면 관리형 GPU 추론에서 임베딩 및 청킹 설정과 인덱스 설정이 관리되므로 구축할 임베딩 파이프라인이 필요하지 않습니다.</p>PUT my-vectors
{
"mappings": {
"properties": {
"description": { "type": "semantic_text" }
    }
  }
}<h3>문서 수집</h3><p>텍스트를 색인하면 임베딩이 자동으로 생성됩니다.</p>POST /my-vectors/_doc
{
  "id": "park_rocky-mountain",
  "title": "Rocky Mountain",
  "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra."
}<h3>시맨틱 검색 쿼리 실행</h3><p>방금 생성한 동일한 시맨틱 필드에 대해 쿼리 작업을 수행합니다.</p>GET /my-vectors/_search
{
  "query": {
    "semantic": {
      "field": "description",
      "query": "a mountain range in the middle of north america"
    }
  }
}<p>그리고 결과가 반환됩니다.</p>{
  "took": 80,
  "hits": {
    "max_score": 0.7792325,
    "hits": [
      {
        "_index": "my-vectors",
        "_score": 0.7792325,
        "_source": {
          "id": "park_rocky-mountain",
          "title": "Rocky Mountain",
          "description": "Bisected north to south by the Continental Divide, ..."
        }
      }
    ]
  }
}<p>시맨틱 검색은 시작에 불과합니다. 완전한 텍스트 기반 쿼리를 실행하거나 두 가지를 결합하여 하이브리드 쿼리로 만들 수 있습니다. 완전한 제어를 위해 자체 벡터 쿼리를 작성할 수도 있습니다. 전체 지침은 문서의 <a href="https://www.elastic.co/docs/solutions/vector-database/vector-full-text-search">시맨틱 검색 빠른 시작</a>을 참조하세요.</p><h2>Elasticsearch 벡터 검색의 다음 단계</h2><p>이미 다음 개선 사항을 진행 중입니다.</p><ul><li><p><strong>향상된 멀티테넌트 처리:</strong> 테넌트별로 데이터를 분리하여 유지해야 하는 경우, 더 적은 코드로 더 빠르게 처리할 수 있는 방법을 제공해 드립니다.</p></li><li><p><strong>자동 인덱스 최적화:</strong> '새 인덱스'부터 '완전히 최적화된 인덱스'까지, 가능한 한 적은 조정으로 가능합니다.</p></li><li><p><strong>지속적인 인프라 개선:</strong> 항상 최고의 처리량과 가장 빠른 응답을 얻을 수 있도록 벡터 데이터베이스의 설정 및 인프라를 지속적으로 튜닝합니다.</p></li></ul><h2>Elastic Cloud Serverless에서 Elasticsearch 벡터 데이터베이스 써보기</h2><p>프로덕션급 기본 설정이 튜닝을 대신 처리하여, 빈 프로젝트에서 단 몇 분 만에 하이브리드 필터링 벡터 쿼리를 구축해 보세요. 인프라가 아닌 신속하고 확장 가능한 AI 앱을 구축하세요.</p><p><a href="https://cloud.elastic.co/registration?onboarding_token=vector">Elastic Cloud Serverless</a>에서 시작하거나 <a href="https://www.elastic.co/docs/solutions/vector-database">전체 설명서 </a>및 <a href="https://www.elastic.co/docs/api/doc/elastic-cloud-serverless/group/endpoint-vectordb-projects">API 참조를 살펴보십시오.</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-database-rag-serverless</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Elastic Cloud Serverless]]></category>
    <category><![CDATA[하이브리드 검색]]></category>
    <dc:creator><![CDATA[Dustin Coates]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4def84aae6aff861/6aa10ab1ee57e53d9b05253c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Sep 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch 검색 리콜을 측정하고 개선하는 방법: 하이브리드 검색을 통해 0.43에서 0.75로 향상하기]]></title>
    <description><![CDATA[BM25 어휘 검색과 Jina AI 벡터 임베딩을 결합하여 Elasticsearch에서 검색 회상률을 측정하고 개선하는 방법을 알아보고, rank_eval API를 사용해 실제 숫자로 개선 효과를 검증하세요.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/docs/solutions/search/full-text">어휘 검색</a>은 <a href="https://www.elastic.co/blog/practical-bm25-part-1-how-shards-affect-relevance-scoring-in-elasticsearch">BM25 순위 알고리즘</a>을 사용하여 저렴하고, 빠르며, 다양한 쿼리에 매우 효과적입니다. 하지만 문서와 토큰을 공유하지 않는 쿼리는 사각지대에 빠집니다. 이 글에서는 BM25의 부족한 부분을 정확히 측정해 보겠습니다. Elasticsearch의 <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval">순위 평가 API</a>(<code>rank_eval</code>)를 사용하고, <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service</a>(EIS)를 통해 <a href="https://www.elastic.co/search-labs/es/blog/jina-embeddings-v3-elastic-inference-service">Jina AI 임베딩</a>을 추가하여 격차를 좁힐 것입니다. 리콜 점수가 <code>0.43</code>에서 <code>0.75</code> 로 올라가는 것을 보면 그 이유를 확인하실 수 있습니다.</p><h2>리콜이란 무엇입니까?</h2><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall">리콜</a> 척도는 사용자가 실제로 원하는 문서가 검색 결과 어딘가에 나타나는 정도를 <code>0</code>에서 <code>1</code>까지의 범위로 측정합니다. 만약 쿼리에 3개의 제품이 표시되어야 하는데 검색 결과 상위 10개 제품 중 2개만 표시되는 경우, 해당 쿼리에 대해 <code>recall@10 = 0.67</code>을 입력합니다. 이는 집합 기반 메트릭이므로 <em>k</em> 결과 내에서 관련 문서의 위치는 중요하지 않습니다. 10번 위치에 있는 관련 문서는 1번 위치에 있는 문서와 동일하게 계산됩니다. 리콜률이 높다는 것은 관련성 있는 결과를 놓치지 않고 있다는 뜻입니다.</p><p>
</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ffd147b13705680/6a170a6fe8fbce11a539fc22/b13af2a5d0ca055535d8bfe3dfe4b3d1093ee6da-1457x796.png" alt="모든 관련 문서와 BM25에서 검색한 상위 10개 결과 간의 중복을 보여주어 Recall@10이 어떻게 계산되는지 설명하며, 그 결과 Recall@10 점수가 0.40임을 보여주는 벤 다이어그램." /><p>다이어그램은 모든 관련 문서(왼쪽)와 BM25가 실제로 검색한 문서(상위 10개, 오른쪽)의 두 가지 세트를 보여줍니다. 오직 교차점만이 리콜에 포함되며, <code>prod_1</code>과 <code>prod_2</code>는 발견되었지만, <code>prod_3</code>, <code>prod_4</code>, <code>prod_6</code>은 완전히 누락되었습니다. 결과: <code>Recall@10 = 2/5 = </code><strong><code>0.40</code></strong>.</p><h2>필수 구성 요소</h2><p>리콜이 어떻게 작동하는지에 대해 자세히 알아보겠습니다. 이 데모에서는 Python을 사용합니다. 제공된 노트북(<a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/notebook.ipynb">notebook.ipynb</a>)을 통해 따라 해보실 수 있습니다. 모든 코드 블록은 실행할 준비가 된 셀입니다.</p><p>제공된 코드는 다음을 사용합니다.</p><ul><li><p>Elasticsearch 9.3+</p></li><li><p>Python 3.10+</p></li></ul>pip install elasticsearch pandas plotly python-dotenv<ul><li><p>Elasticsearch 자격 증명이 포함된 <code>.env</code> 파일</p></li></ul>ELASTICSEARCH_URL=https://your-cluster-url
ELASTICSEARCH_API_KEY=your-api-key<h2>데이터 세트</h2><p>신발, 전자제품, 공구 등의 카테고리를 아우르는 1,000개의 제품 카탈로그.</p><p>각 문서에는 네 개의 필드가 있습니다.</p><p>필드</p><p>유형</p><p>`title`</p><p>텍스트</p><p>`설명`</p><p>텍스트</p><p>`브랜드`</p><p>키워드</p><p>'카테고리`</p><p>키워드</p><p>데이터 세트는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/relevance-tuning-improving-recall-adding-vectors/dataset.csv"><code>dataset.csv</code></a>에서 로드되었습니다.</p><h2>어휘 검색의 힘과 한계</h2><p>BM25는 Elasticsearch 및 대부분의 검색 엔진에서 기본 순위 알고리즘으로 사용됩니다. 쿼리 용어가 문서에 나타나는 빈도에 따라 점수를 매기며, 이는 문서 길이와 전체 인덱스에서 이러한 용어의 빈도에 따라 조정됩니다. 소문자 정규화, 어간 제거, 중단어 제거 등의 <a href="https://www.elastic.co/docs/reference/text-analysis/analyzer-reference">분석</a> 기능을 상단에서 사용할 수 있습니다. '러닝화'를 검색하면 '러닝화'와 '실행'이 모두 검색 결과에 나타날 가능성이 높습니다.</p><p>이는 대규모 쿼리 클래스에 적합합니다.</p><ul><li><p>'러닝화'를 입력하면 제목에 토큰이 정확히 일치하는 제품이 즉시 검색됩니다.</p></li><li><p>'블루투스 스피커'는 토큰이 문자 그대로 나타나기 때문에 휴대용 오디오 제품을 표시합니다.</p></li></ul><p>결과는 결정론적이고 설명 가능합니다. 문서가 높은 순위를 차지한 이유는 쿼리 용어가 문서에 포함되어 있기 때문입니다. 디버깅 관련성은 간단합니다.</p><h3>부족한 부분</h3><p>이제 동일한 카탈로그에 대해 다음 쿼리를 실행해 보겠습니다.</p><ul><li><p><strong>'스킨케어 루틴':</strong> '루틴'이라는 단어는 제품 제목에 나타나지 않습니다. BM25는 '스킨케어'와 부분적으로 일치시킬 수 있지만, 페이스 세럼, 바디 오일, 보습제 등은 '비타민 C', '레티놀', 또는 '브라이트닝' 등 검색어와 겹치지 않는 용어를 사용하여 설명됩니다. 완전한 스킨케어 루틴을 구성하는 제품들은 인덱스에 흩어져 있으며, 이를 연결할 공유 토큰이 없습니다.</p></li></ul>ID: B06XX6DS3P, Score: 9.0552, Title: Replenix Retinol Smooth + Tighten Body Lotion - Collagen-Boosting, Regenerating Anti-Aging Body Cream, Reduces Appearance of Stretch Marks, 6.7 oz.

  ID: B08XMPKJ1L, Score: 5.2699, Title: Bio-Oil Skincare Body Oil (Natural) Serum for Scars and Stretchmarks, Face and Body Moisturizer Hydrates Skin, with Organic Jojoba Oil and Vitamin E, For All Skin Types, 6.7 oz

  ID: B01CY764KQ, Score: 5.0057, Title: Nike Up Or Down Men Deodorant - Pack of 2 | Long-Lasting Fragrance, Body Spray Combo for Men | Deodorant for Active Living | Nike Men's Deo Set | Ultimate Odor Protection | Grooming Essentials | Signature Nike Scent | High-Performance Men's Deodorant<ul><li><p><strong>'반려동물 여행용 액세서리':</strong> 이는 제품 카테고리가 아닌 사용 사례 그룹입니다. 반려견 슬링 캐리어, 반려동물 카시트, 여행용 케이지는 모두 관련성이 있지만 '여행용 액세서리'보다는 휴대성, 안전성, 편안함에 대한 설명이 더 많습니다. BM25는 '반려동물'을 광범위하게 매칭하지만, 여행 관련 제품을 나머지 반려동물 카탈로그와 구분하는 신호가 없습니다.</p></li></ul>ID: B0BVV7BKTW, Score: 7.4371, Title: Large Foldable Travel Duffel Bag with Shoes Compartment

ID: B07TNPHYNV, Score: 6.6455, Title: 40 Pieces Christmas Bronze Jingle Bells Craft Small Bells

ID: B08R8FRW53, Score: 6.6335, Title: CUBY Dog and Cat Sling Carrier
ID: B08QMCQYGM, Score: 6.5259, Title: YTFGGY Whiteboard Pinstripe Tape 6 Rolls 1/8"
ID: B0CP3LQSWM, Score: 6.2994, Title: Portable Dog Water Bottle 32 Oz<p>이것은 <strong>리콜 문제</strong>입니다. 관련 문서가 인덱스에 존재합니다. 그러나 사용자의 입력 내용과 문서의 내용이 충분히 일치하지 않기 때문에 BM25가 해당 내용을 찾지 못합니다.</p><p>동의어를 추가하면 알려진 경우에 도움이 됩니다. 하지만 사용자가 의사를 표현하는 모든 방법을 열거할 수는 없습니다. 이것이 벡터가 중요한 이유입니다.</p><h2>리콜을 측정해야 하는 이유</h2><p>문제를 해결하기 전에 그것을 정량화해야 합니다.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-recall"><strong>Recall@k</strong></a>는 사용자가 실제로 원하는 문서가 검색 결과 위치에 관계없이 얼마나 많이 나타나는지를 측정합니다. 공식적으로</p>Recall@k = (relevant documents found in top k) / (total relevant documents)<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/search-rank-eval#k-precision"><strong>Precision@k</strong></a>는 상위 k개의 결과와 실제 연관성이 있는 결과의 수를 측정합니다.</p>Precision@k = (relevant documents in top k) / k<p>정확도가 높다는 것은 반환되는 결과가 좋다는 것을 의미합니다. 전자상거래에서 관련 제품을 놓치는 것(낮은 리콜)은 약간 불완전한 결과(낮은 정확도)를 보여주는 것보다 더 나쁠 수 있습니다. 제품이 드러나지 않으면 판매 손실로 연결되기 때문입니다.</p><p>Elasticsearch의 <code>rank_eval</code> API를 사용하면 두 가지를 체계적으로 측정할 수 있습니다. 사용자가 각각 등급이 매겨진 문서 세트가 포함된 쿼리 목록을 제공하면 Elasticsearch가 모든 쿼리에 대해 메트릭을 계산합니다.</p><h2>평가 설정</h2><p><code>rank_eval</code> API에는 관련성 등급(0 = 관련성 없음, 1 = 관련성 있음, 2 = 매우 관련성 있음)과 함께 각 쿼리에 대한 관련 문서 매핑인 <strong>평가 데이터 세트</strong>가 필요합니다.</p><p>노트북에서 이것은 <a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr#learning-to-rank-judgement-list">심사 목록입니다</a>:</p>judgments = [
    # Query 1: "running shoes" BM25 handles well (tokens appear in product titles) 
    {"query_id": "q1", "doc_id": "B09NQJFRW6", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08JMD4LMM", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B08VRJ6F2Q", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07S8NRRWR", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01HD620I8", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B07DX86321", "grade": 2, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B0968YVLQ8", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B093QJ39ZS", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B096FGSC39", "grade": 1, "query": "running shoes"},
    {"query_id": "q1", "doc_id": "B01GVQWVV2", "grade": 1, "query": "running shoes"},

    # Query 2: "skincare routine" intent-based, "routine" never appears in product titles
    {"query_id": "q2", "doc_id": "B08XMPKJ1L", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BN3WQB92", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B0BT7B7P5T", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00NPA2WEY", "grade": 2, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B06XX6DS3P", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B07PDRD1KT", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B074J7869B", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B08JV31QW4", "grade": 1, "query": "skincare routine"},
    {"query_id": "q2", "doc_id": "B00K3TVJMQ", "grade": 1, "query": "skincare routine"},

    # Query 3: "study desk setup" intent-based, products are desks/stands/organizers
    {"query_id": "q3", "doc_id": "B08CS35J2T", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B09B3LFDXJ", "grade": 2, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B07W58LMND", "grade": 1, "query": "study desk setup"},
    {"query_id": "q3", "doc_id": "B0CHYDX91L", "grade": 1, "query": "study desk setup"},

    # Query 4: "pet travel accessories" use-case grouping, products are carriers/crates/seats
    {"query_id": "q4", "doc_id": "B08R8FRW53", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B01MYUYX33", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B003C5RKE4", "grade": 2, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B09GF8GBF6", "grade": 1, "query": "pet travel accessories"},
    {"query_id": "q4", "doc_id": "B0CP3LQSWM", "grade": 1, "query": "pet travel accessories"},
]<p>이 조합은 의도적으로 구성된 것입니다.<code>q1</code>은 BM25가 잘 처리하는 쿼리(제품 제목의 정확한 토큰 포함)이고, <code>q2</code>, <code>q3</code>, <code>q4</code>는 사용자의 의도를 특정 제품 키워드가 아닌 개념으로 표현하는 의도 기반 쿼리입니다.</p><h2>BM25 기준 리콜 측정</h2><p>먼저, Elasticsearch 클라이언트를 설정하고 원시 텍스트 데이터를 색인합니다.</p>import os
import json
import pandas as pd
import plotly.graph_objects as go
from elasticsearch import Elasticsearch, helpers
from dotenv import load_dotenv

load_dotenv()

es = Elasticsearch(
    os.getenv("ELASTICSEARCH_URL"),
    api_key=os.getenv("ELASTICSEARCH_API_KEY")
)

INDEX_NAME = "ecommerce-products"<p>이제 BM25에 대한 <code>rank_eval</code> 요청을 생성합니다. 목록의 각 요청은 쿼리와 그 등급을 결합합니다.</p>judgments_df = pd.DataFrame(judgments)

bm25_requests = []
for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    bm25_requests.append({
        "id": query_id,
        "request": {
            "query": {
                "multi_match": {
                    "query": query_text,
                    "fields": ["title", "description"]
                }
            }
        },
        "ratings": ratings,
    })

bm25_eval = {
    "requests": bm25_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

bm25_result = es.rank_eval(index=INDEX_NAME, body=bm25_eval)
print("BM25 Recall@10:", bm25_result.body["metric_score"])<p>결과:</p>BM25 Recall@10: 0.43<p><code>0.43</code> 이는 네 개의 쿼리 모두에서 BM25가 찾아야 하는 문서 중 43%만을 찾았다는 의미입니다. 부족한 부분은 의도 기반 쿼리에 집중되어 있습니다. '스킨케어 루틴'은 제품 제목에 '루틴'이 포함되지 않는 페이스 세럼과 바디 오일을 놓치고, '여행용 반려동물 액세서리'는 주제와 다른 반려동물 제품을 검색하는 반면, 휴대성과 안전성 측면을 강조해서 설명하는 캐리어와 케이지는 정작 빠져 있습니다.</p><p>여기가 기준이 됩니다. 이제 넘어야 할 목표가 생겼습니다.</p><h2>Jina 임베딩으로 벡터 검색 추가</h2><p><a href="https://www.elastic.co/docs/solutions/search/vector"><code>Vector search</code></a> 문서와 쿼리를 고차원 벡터로 인코딩합니다. 고차원 벡터는 수백에서 수천 개의 수치 값으로 구성되며, 각 값은 해당 데이터의 특정한 특징을 인코딩합니다. 비슷한 의미를 가진 문서들은 단어가 공유되지 않더라도 벡터 공간에서 서로 가깝게 배치됩니다. '헬스 기구'와 '덤벨 세트'는 개념이 관련되어 있기 때문에 가까이에 배치될 것입니다. 하이브리드 검색을 지원하여 의미론적 이해와 키워드 정확도를 모두 제공하는 Elasticsearch를 벡터 데이터베이스로 선택했습니다.</p><p><a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">EIS</a>는 <a href="https://www.elastic.co/docs/api/doc/elasticsearch/group/endpoint-inference">추론 API</a>를 통해 모델 임베딩을 즉시 지원하는 기능을 포함하고 있습니다.</p><h3>1단계: Jina 임베딩 v5를 추론 엔드포인트로 사용</h3>INFERENCE_ENDPOINT_ID = ".jina-embeddings-v5-text-small"<p>클러스터에 GPU 리소스가 있는 경우(Elastic Cloud 및 Elasticsearch 9.3 이상에서 사용 가능), 임베딩이 GPU에서 생성되어 CPU 추론보다 훨씬 빠르며, 과거에 대규모 벡터 처리를 비용 부담으로 만들었던 성능 트레이드오프 문제를 해소합니다.</p><p>왜 Jina 임베딩을 사용할까요? <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text</a>는 32,000개 이상의 토큰 컨텍스트 창을 가진 다국어 모델(119개 이상의 언어)로, 작업별 <a href="https://arxiv.org/abs/2106.09685">LoRA(Low-Rank Adaptation) 어댑터</a>를 지원합니다. 바로 사용할 수 있는 짧은 제품 설명에 적합합니다. <code>jina-embeddings-v5-text</code> 모델에 대한 자세한 내용은 <a href="https://huggingface.co/jinaai/jina-embeddings-v5-text-small">여기</a>에서 확인하실 수 있습니다.</p><h3>2단계: 시맨틱 필드로 인덱스 생성</h3>index_mappings = {
    "mappings": {
        "properties": {
            "title": {"type": "text", "copy_to": "semantic_field"},
            "description": {"type": "text", "copy_to": "semantic_field"},
            "brand": {"type": "keyword"},
            "category": {"type": "keyword"},
            "semantic_field": {
                "type": "semantic_text",
                "inference_id": INFERENCE_ENDPOINT_ID,
            },
        }
    }
}

if not es.indices.exists(index=INDEX_NAME):
    es.indices.create(index=INDEX_NAME, body=index_mappings)
    print(f"Created index: {INDEX_NAME}")<p>여기서는 <a href="https://www.elastic.co/docs/solutions/search/semantic-search/semantic-search-semantic-text"><code>semantic_text</code></a> 필드 유형이 핵심입니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a>보다 더 높은 수준의 추상화입니다. 추론 엔드포인트를 가리키면 Elasticsearch가 자동으로 임베딩 생성을 처리합니다.</p><p><code>title</code>과 <code>description</code>의 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/copy-to"><code>copy_to</code></a> 속성은 두 필드의 콘텐츠가 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/semantic-text"><code>semantic_field</code></a>로 흘러들어가 임베딩되므로, 단일 벡터가 전체 제품 표현을 캡처합니다.</p><h3>3단계: 제품 인덱스 생성</h3>def bulk_index(products, index_name):
    actions = []
    for product in products:
        doc_id = product.get("_id")
        source = {k: v for k, v in product.items() if k != "_id"}
        action = {"_index": index_name, "_source": source}
        if doc_id:
            action["_id"] = doc_id
        actions.append(action)

    success, failed = helpers.bulk(es, actions, raise_on_error=False)
    if failed:
        for error in failed:
            print(f"Error: {error}")
    else:
        print(f"Successfully indexed {success} documents")

bulk_index(products, INDEX_NAME)<p>색인 시점에 Elasticsearch는 각 문서에 대해 추론 엔드포인트를 호출하고, 결과 임베딩을 <code>semantic_field</code>에 저장합니다. 사용자 측에서 추가 코드를 작성할 필요가 없습니다.</p><h2>하이브리드 검색: BM25 및 벡터와 RRF의 결합</h2><p>벡터를 추가하면 리콜이 향상되지만, 벡터만 사용하면 정확한 일치 쿼리에서 정확도를 잃을 위험이 있습니다. '러닝화'와 같은 용어는 여전히 문자 자체가 일치하는 결과가 우선 순위에 표시되어야 합니다. 하이브리드 검색은 정확성을 보장하기 위해 어휘 기반 검색 요소를 유지합니다.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion">상호 순위 결합</a>(RRF)을 사용한 하이브리드 검색은 두 가지 장점을 모두 유지합니다.</p><ul><li><p>BM25는 정확하거나 거의 정확한 쿼리를 높은 정확도로 처리합니다.</p></li><li><p>의미 검색은 의도 기반 및 다국어 쿼리를 높은 리콜로 처리합니다.</p></li><li><p>RRF는 두 개의 순위 목록을 단일 순위로 결합합니다.</p></li></ul><p>RRF 공식은 각 문서의 결과 목록 순위에 따라 점수를 부여합니다.</p>score = sum(1 / (rank_constant + rank))<p>두 목록 모두에서 높은 순위를 차지한 문서가 더 높은 합산 점수를 받습니다. <code>rank_constant</code>는 하위 등급의 문서가 받는 가중치를 조절합니다.</p>hybrid_requests = []

for query_id, query_text in (
    judgments_df[["query_id", "query"]].drop_duplicates().values
):
    relevant_docs = judgments_df[judgments_df["query_id"] == query_id]
    ratings = [
        {"_index": INDEX_NAME, "_id": row["doc_id"], "rating": row["grade"]}
        for _, row in relevant_docs.iterrows()
    ]

    hybrid_requests.append({
        "id": query_id,
        "request": {
            "retriever": {
                "rrf": {
                    "retrievers": [
                        {
                            "standard": {
                                "query": {
                                    "multi_match": {
                                        "query": query_text,
                                        "fields": ["title", "description"],
                                    }
                                }
                            }
                        },
                        {
                            "standard": {
                                "query": {
                                    "match": {
                                        "semantic_field": {"query": query_text}
                                    }
                                }
                            }
                        },
                    ],
                    "rank_window_size": 50,
                    "rank_constant": 5,
                }
            }
        },
        "ratings": ratings,
    })

hybrid_eval = {
    "requests": hybrid_requests,
    "metric": {"recall": {"k": 10, "relevant_rating_threshold": 1}},
}

hybrid_result = es.rank_eval(index=INDEX_NAME, body=hybrid_eval)
print("Hybrid Recall@10:", hybrid_result.body["metric_score"])<p>결과:</p>Hybrid Recall@10: 0.75<p>하이브리드는 BM25(<code>0.43</code>)에 비해 성능이 크게 향상되었으며, '러닝화'와 같은 정확한 일치 쿼리에 대한 정확확도를 유지합니다.</p><h2>결과: 전후 비교</h2><p>세 가지 접근 방식을 모두 비교한 전체 내용은 다음과 같습니다.</p>methods = {
    "BM25 (Lexical)": bm25_requests,
    "Hybrid (BM25 + Vectors)": hybrid_requests,
}

recall_metric = {"recall": {"k": 10, "relevant_rating_threshold": 1}}

comparison_data = []
for method_name, requests in methods.items():
    result = es.rank_eval(
        index=INDEX_NAME,
        body={"requests": requests, "metric": recall_metric}
    )
    comparison_data.append({
        "method": method_name,
        "recall@10": result.body["metric_score"]
    })

comparison_df = pd.DataFrame(comparison_data)
print(comparison_df.to_string(index=False))<p>결과:</p><p>메서드</p><p>Recall@10</p><p>BM25(어휘 검색)</p><p>0.43</p><p>하이브리드(BM25 + 벡터)</p><p>0.75</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5a1d72b57056fe64/6a170a71c1e8a56c58f882ab/e49f6c10516b0a48a0ad75962c6590ee07311407-700x500.png" alt="BM25 어휘 검색과 BM25와 벡터를 결합한 하이브리드 검색의 Recall@10을 비교한 막대 차트에서 하이브리드 검색이 훨씬 더 높은 리콜률을 달성했음을 확인할 수 있습니다." /><p>쿼리별로 분석해 보겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt871347f754c866d0/6a170a73839dfa40abdcfeb4/40e36dcb7b34cbf4649c512bcb60cef60f1778a6-700x500.png" alt="4개의 제품 쿼리에서 BM25 어휘 검색과 하이브리드 검색의 Recall@10을 비교한 그룹화된 막대 차트는 각 쿼리에서 하이브리드 검색이 어휘 검색보다 일관되게 우수한 성능을 보였음을 증명합니다." /><h2>결론</h2><p>이 포스팅에서는 사용자가 정확한 쿼리를 입력할 때 BM25 어휘 검색이 신뢰할 수 있지만, 키워드보다는 의도에 따라 검색할 때 리콜이 떨어진다는 것을 보았습니다. <code>rank_eval</code>을 사용하여 재현 가능한 기준선을 설정하고 실제 숫자로 그 차이를 측정했습니다. 그 후, Jina 임베딩을 기반으로 하는 <code>semantic_text</code> 필드를 추가하고 평가를 다시 실행했습니다. 결과적으로, 하이브리드 검색은 리콜을 <code>0.43</code>에서 <code>0.75</code>로 높이면서도 정확한 일치 쿼리의 정확도를 유지했지만, 실제 마진은 쿼리 구성에 따라 달라집니다.</p><p>이 패턴은 이 예제 이상으로 확장됩니다. 사용자의 실제 쿼리에서 판단을 수집하고 <code>rank_eval</code>을 기준선으로 실행한 다음 <code>semantic_text</code>를 추가하고 다시 측정하세요. 어떤 부분이 얼마나 개선되었는지 정확히 알 수 있습니다.</p><h2>다음 단계</h2><ul><li><p>리콜과 벡터 검색에 대해 자세히 알아보기: Jeff Vestal의 <a href="https://www.elastic.co/search-labs/blog/recall-vector-search-quantization">리콜 및 벡터 검색 양자화</a></p></li><li><p>상위 결과의 정확도를 높이기 위해 재순위 추가</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">Elasticsearch 하이브리드 검색 문서</a> 살펴보기</p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html"><code>rank_eval</code></a><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-rank-eval.html">API</a>에 대해 자세히 알아보기</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-relevance-tuning-improve-recall</guid>
    <category><![CDATA[하이브리드 검색]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt37c9d2971b5a2db3/6a170a75cf4f254223b2d149/492c9b5432a2b9e40cebb3b60f0df019a8c7bf6d-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[세계에서 가장 빠른 벡터 검색을 위해 Elasticsearch simdvec을 구축한 방법]]></title>
    <description><![CDATA[Elasticsearch의 모든 벡터 검색 쿼리 뒤에 있는 수동 조정 SIMD 커널 라이브러리인 Elasticsearch simdvec을 어떻게 구축했는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch simdvec은 Elasticsearch의 모든 벡터 거리 계산의 기반이 되는 엔진입니다. Elasticsearch가 지원하는 모든 벡터 유형에 대해 수동 조정 AVX-512 및 NEON 커널을 제공합니다. 벌크 스코어링 아키텍처는 x86에서는 명시적 프리페칭을 통해, ARM에서는 인터리브 로딩을 통해 메모리 지연 시간을 숨기며 데이터가 CPU 캐시를 초과할 때 FAISS 및 jvector와 같은 라이브러리보다 최대 4배까지 성능이 향상됩니다. 이 글에서는 simdvec을 구축한 이유와 내부 구성 요소, 그리고 이 라이브러리가 어떻게 Elasticsearch 벡터 검색을 세계에서 가장 빠른 검색으로 만드는지 설명합니다.</p><h2>Elasticsearch simdvec을 어떻게 구축했는지</h2><p>Elasticsearch의 모든 벡터 검색 쿼리는 <a href="https://arxiv.org/abs/1603.09320">Hierarchical Navigable Small World(HNSW)</a> 순회, 역파일(IVF) 스캔, 순위 재지정 단계 등 어떤 방식이든 동일한 문제, 즉 쿼리당 수백만 번씩 벡터 간의 거리를 계산하는 작업으로 귀결됩니다. Elasticsearch는 float32, int8, bfloat16, 바이너리, 그리고 Better Binary Quantization(BBQ) 등 다양한 데이터 유형과 양자화 전략을 지원합니다. 각 전략은 메모리, 처리량, 재현율 측면에서 서로 다른 장단점이 있습니다. 이 모든 것을 가능하게 하는 것은 바로 simdvec이라는 단일 엔진입니다.</p><p>하드웨어 성능을 최대한 활용하여 모든 거리 계산을 빠르게 수행하기 위해 simdvec을 개발했습니다. 이 글에서는 simdvec을 구축한 이유, 내부 구성 요소, 그리고 가장 큰 효과를 발휘하는 부분에 대해 설명합니다.</p><h3>레이싱카처럼 구축</h3><p>Formula 1 애호가들로서, 그리고 이중 한 명이 이전에 Ferrari Formula 1 팀에서 근무했던 경험을 바탕으로, 저희는 분명한 공통ㄴ점을 발견했습니다. Formula 1 자동차는 단 하나의 목적, 즉 최고의 랩 타임을 달성하는 것을 위해 설계됩니다. 엔진 출력, 공기역학, 섀시 설계는 오직 그 결과에 기여하는 한에서만 중요합니다. 벡터 데이터베이스도 마찬가지로, 인덱싱 처리량, 쿼리 지연 시간, 그리고 재현율이 성공을 좌우합니다.</p><p>최종 결과가 중요하지만, 최고 수준의 성능을 달성하려면 각 구성 요소가 최상의 성능을 발휘해야 합니다. 단순히 <em>충분히 좋은</em> 정도가 아니라, 해당 범주에서 <em>최고</em>여야 합니다. simdvec은 이러한 사고방식을 바탕으로 시스템의 핵심 요소인 엔진에 집중하여 개발되었습니다. <a href="https://en.wikipedia.org/wiki/Single_instruction,_multiple_data">단일 명령어 다중 데이터</a>(SIMD)에 최적화된 특수 목적 커널 라이브러리로, Java에서 <a href="https://openjdk.org/projects/panama/">Panama</a> F외부 함수 인터페이스(FFI)를 통해 호출되는 수동 조정 네이티브 C++ 거리 함수를 제공합니다. 벌크 스코어링, 캐시 라인 프리페칭, 그리고 Elasticsearch에서 사용되는 모든 벡터 유형 및 레이아웃을 지원합니다.</p><p>모든 쿼리 뒤에 있는 엔진입니다.</p><h3>왜 저희가 직접 구축했는지</h3><p>2023년에 Apache Lucene의 Panama Vector API로 시작했습니다. float32 내적에는 잘 작동했지만, Elasticsearch의 요구 사항은 빠르게 충족할 수 있는 범위를 넘어섰습니다. Elasticsearch는 int8, int4, bfloat16, 단일 비트, 비대칭 BBQ 등 다양한 양자화 벡터 유형을 지원합니다. 각 유형은 서로 다른 SIMD 전략, 패킹 레이아웃, 누산기 요구 사항을 가지고 있습니다. 유형 지원 외에도 Elasticsearch의 스코어링 경로는 단일 쌍 처리량 이상의 것을 요구합니다. HNSW는 한 번의 단계로 여러 그래프 이웃을 스코어링해야 하고, IVF는 프리페칭을 통해 수천 개의 후보를 벌크 스코어링해야 하며, 디스크 기반 스코어링은 복사 없이 mmap된 메모리에서 직접 작동해야 합니다. 사용 가능한 기존 라이브러리를 살펴보았지만, 모든 요구 사항을 충족하는 것은 없었습니다.</p><p>그래서 simdvec을 구축했습니다. simdvec은 FFI를 통해 Java에서 호출되는 수동 조정 네이티브 C++ 커널로, 벌크 스코어링, 프리페칭, 그리고 Elasticsearch에서 사용하는 모든 벡터 유형을 지원합니다. 라이브러리를 직접 소유함으로써 전체 스택을 제어할 수 있습니다. BBQ와 같은 새로운 양자화 유형을 추가하면 시스템 전체에 걸쳐 최적화된 SIMD 커널이 연결됩니다. 상위 라이브러리의 지원을 기다리지 않으며, 어떤 유형에 대해서도 성능이 저하되지 않습니다. Elasticsearch의 모든 벡터 쿼리(HNSW, IVF, 순위 재지정 또는 하이브리드)는 실제로 사용하는 연산 및 유형을 중심으로 구축된 이 엔진에서 실행됩니다.</p><p>simdvec은 x86 및 ARM용으로 별도의 네이티브 라이브러리를 제공하며, 각 라이브러리는 시작 시 여러 명령어 세트 아키텍처(ISA) 계층을 선택할 수 있습니다. FFI를 통한 Java의 호출 오버헤드는 <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#ffm-downcall-overhead-measurements">한 자릿수 나노초</a>로 매우 낮습니다.</p><h3>환경</h3><p>SIMD 최적화 벡터 거리 커널을 개발하는 곳은 저희뿐만이 아닙니다. 생태계는 매우 풍부하며, 저희는 simdvec의 성능을 이해하고 싶었습니다. 프로젝트 순위를 매기려는 것이 아니라 컨텍스트를 제공하고 Elasticsearch 엔진의 위치를 설명하기 위해서입니다. 각기 다른 접근 방식을 대표하는 세 가지 프로젝트를 참조 대상으로 선정했습니다.</p><ul><li><p><strong>jvector:</strong> x86에서 네이티브 C 가속 옵션과 함께 벡터화된 거리 계산을 위해 Panama Vector API를 사용하는 Java 근사 최근접 이웃(ANN) 라이브러리입니다.</p></li><li><p><strong>FAISS:</strong> 널리 배포된 오픈 소스 벡터 검색 프레임워크로, 수동 조정 AVX2/AVX-512 커널을 사용합니다.</p></li><li><p><strong>NumKong</strong> (이전 SimSIMD): 거리 함수, 행렬 연산 및 지리 공간 계산을 아우르는 2,000개 이상의 수동 조정 SIMD 커널로 구성된 종합적인 제품군입니다.</p></li></ul><p>각 프로젝트는 서로 다른 목적을 가지고 있으며 각기 다른 절충점을 고려합니다. Elasticsearch에서 필요한 특정 연산에 대한 simdvec의 성능을 이해하는 데 도움이 되도록 각 프로젝트의 참조 번호를 제공합니다.</p><h3>측정 방법</h3><p>simdvec 및 <a href="https://github.com/ChrisHegarty/jvector-kernel-benchmarks">jvector 벤치마크</a>는 표준 JVM 마이크로벤치마크 도구인 JMH를 사용하여 Java로 작성되었으며, FFI 오버헤드가 포함되어 있습니다. <a href="https://github.com/ldematte/simsimd-benchmarks">NumKong 벤치마크</a>와 <a href="https://github.com/ChrisHegarty/faiss-kernel-benchmarks">FAISS 벤치마크</a>의 경우, 표준 C++ 마이크로벤치마크 프레임워크인 Google Benchmark를 사용하여 소규모 C/C++ 하네스를 작성했습니다. 두 프레임워크 모두 웜업 및 반복 보정을 거쳐 연산당 나노초 단위의 시간을 보고합니다. 하드웨어 성능 카운터를 통해 모든 라이브러리가 두 플랫폼 모두에서 SIMD를 사용하고 있음을 확인했습니다. 모든 벤치마크 코드는 링크된 GitHub 리포지토리(그리고 simdvec의 경우 <a href="https://github.com/elastic/elasticsearch">elasticsearch</a> 리포지토리)에서 공개적으로 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt90a7d66553be3c9d/6a1701d166c4f942caf8bea5/aee116772df161cf86b7668f575ac34c733a23c5-1580x238.png" alt="두 가지 플랫폼을 나열한 표: x86(AMD EPYC Turin(Zen 5), AVX2 및 AVX-512, AWS c8a.4xlarge) 및 ARM(Graviton 4(Neoverse V2), NEON 및 SVE2, AWS c8g.4xlarge)." /><p><strong>소프트웨어:</strong> JDK 25.0.2, JMH 1.37, GCC 14, Google Benchmark(최신).</p><h2>한 번에 하나의 벡터</h2><p>벡터 검색에서 가장 기본적인 연산은 두 벡터 사이의 거리를 계산하는 것입니다. 모든 HNSW 이웃 평가, 모든 IVF 후보 점수, 모든 순위 재지정 비교는 이 내부 루프로 축소됩니다.</p><p>두 플랫폼 모두에서 1024 차원의 단일 쌍 처리량을 측정했으며, 기준 유형이자 생태계 경쟁이 가장 치열한 float32부터 시작했습니다. simdvec을 FAISS 및 jvector와 비교했습니다. NumKong은 float32에 float64 누산기를 사용하기 때문에 (플랫폼에 따라) 처리량보다 수치 정밀도를 우선시하여 3.2배에서 5.3배 느리므로 비교 대상에서 제외했습니다. 공정한 비교를 위해 NumKong은 simdvec과 동일한 누산기 전략을 사용하는 int8에서 벤치마킹했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3bc2ecaf3ad2b61/6a1701d2ab7f08039ddb9d0f/352cfa0fc18f123140f746d843404f80127fb1b7-1500x675.png" alt="'float32 Dot Product — AMD Turin'이라는 제목의 가로 막대 차트는 FAISS AVX‑512(23.2ns/op), ES simdvec AVX‑512(28.3ns/op), FAISS AVX2(36.4ns/op), ES simdvec AVX2(38.9ns/op), jvector(43.9ns/op)의 다섯 가지 구현을 비교합니다." /><p>x86에서 FAISS AVX-512는 23ns로 가장 빠른 단일 쌍 커널입니다. simdvec AVX-512는 28ns로 그 뒤를 잇는데, 이 차이는 FFI 호출 오버헤드를 반영합니다. 두 구현 모두 멀티 누산기 언롤링을 사용하는 512비트 FMA를 사용합니다. AVX2 수준에서는 두 구현의 속도가 각각 36ns와 39ns로 훨씬 더 근접하며, 두 구현 모두 256비트 레지스터 및 메모리 로드 폭의 제약을 받습니다. jvector는 Java Panama Vector API를 사용하여 44ns의 성능을 보였습니다. Panama는 우수한 SIMD 코드를 생성하지만, 수동 조정 C++ 내장 함수가 여전히 우위를 점하고 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbbbe442631a575d/6a1701d42b835f81bcf4b083/95e44d9767f21ec4bccaed835e3c99aa180431ee-1500x495.png" alt="'float32 Dot Produc — Graviton 4(ARM)'이라는 제목의 가로 막대 그래프는 ES simdvec이 70.2ns/op, jvector가 110.0ns/op, FAISS가 155.6ns/op임을 보여줍니다." /><p>ARM에서는 simdvec은 70ns로 jvector(110ns)와 FAISS(156ns)를 크게 앞서고 있습니다. simdvec은 aarch64용으로 수동 조정 NEON 커널을 사용합니다. jvector는 네이티브 ARM 코드가 없으며 Panama에 의존합니다. FAISS는 명시적인 NEON 내장 함수 대신 컴파일러의 자동 벡터화에 의존하기 때문에 성능 차이가 더 큽니다. 이는 커널 라이브러리를 소유함으로써 얻을 수 있는 실질적인 이점을 반영합니다. Elasticsearch가 Graviton으로 확장되었을 때, NEON 전용 커널을 추가했습니다. jvector나 FAISS는 ARM 네이티브 코드에 이처럼 우선순위를 두지 않았습니다.</p><p>하지만 Elasticsearch는 float32만 지원하는 것이 아닙니다. <strong>Int8</strong> 양자화는 메모리 사용량을 4배, bfloat16은 2배, BBQ는 32배 줄여줍니다. 각 유형에는 고유한 SIMD 전략이 필요하며, simdvec은 모든 유형에 대해 수동 조정 네이티브 커널을 제공합니다.</p><p>비교한 라이브러리 중 int8에 대해 유사한 커널을 제공하는 것은 NumKong뿐입니다. 1024 차원에서 int8 내적, 제곱 유클리드, 코사인을 측정했습니다.</p><p><strong>Int8 단일 쌍 스코어링(1,024차원, ns/vec op ― 낮을수록 좋음)</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ac255d158e93504/6a1701d5a292997e48d00eb1/a0b852fd5f51d57bd2488886600472ee65ab64da-1594x378.png" alt="내적, 제곱 유클리드, 코사인 연산에 대한 x86과 ARM 성능을 비교한 표로, 두 아키텍처에서 각 연산에 대한 ES, NumKong, 차이값이 나열되어 있습니다." /><p>두 아키텍처 모두에서 NumKong은 중소 차원까지는 동일하거나 더 빠르며, 그 차이는 주로 호출 오버헤드(직접 C 호출 vs. Java FFI)가 더 낮기 때문입니다. 더 큰 크기에서는 simdvec이 따라잡는데, 이는 더 효율적인 커널 구현(캐스케이드 언롤링 사용)이 호출 비용을 분산시키기 때문입니다. 차원이 증가함에 따라 <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md#single-pair-i8-nsop-2">이 격차는 줄어들고 결국 역전됩니다</a>. 교차점은 함수와 아키텍처에 따라 768에서 1536 사이의 차원에서 발생합니다.</p><p>Java FFI의 오버헤드가 약간 더 높지만, simdvec은 고도로 최적화된 C/C++ 라이브러리와 동등한 수준입니다. float32 <em>및</em> int8 모두에 대해 최적화된 커널을 제공하는 유일한 라이브러리일 뿐만 아니라, ARM에서는 가장 우수한 성능을 보이며 x86에서는 FAISS에 비해 약간 뒤처지고(float32의 경우), int8의 경우 두 아키텍처 모두에서 NumKong과 매우 유사한 성능을 보입니다. bfloat16, int4, 바이너리, BBQ의 경우에도 대안이 존재하지만, simdvec은 각 유형의 데이터 레이아웃에 맞춰 수동 조정 SIMD를 통해 차별화된 성능을 제공합니다.</p><p>하지만 프로덕션 검색 엔진은 한 번에 하나의 벡터를 점수화하는 것이 아니라 쿼리당 수천 개의 벡터를 점수화합니다. 이제 관건은 이러한 규모에서 실제로 어떤 일이 벌어지는가 하는 점입니다.</p><h3>한 번에 수천 개씩</h3><p>단일 쌍 성능은 전체 그림의 일부일 뿐입니다. 실제로 중요한 것은 시스템이 부하 상태에서 어떻게 작동하는지입니다. 단일 HNSW 쿼리는 수백 개의 그래프 이웃에 점수를 매길 수 있습니다. IVF 스캔은 수천 개의 게시 목록 항목에 점수를 매길 수 있습니다. 순위 재지정 단계는 수만 개의 후보에 점수를 매길 수 있습니다. 단일 쌍 처리량도 중요하지만, 더 중요한 것은 많은 벡터를 얼마나 빨리 점수화할 수 있는지, 그리고 작업 세트가 CPU 캐시 범위를 벗어날 때 성능이 얼마나 완만하게 떨어지는지입니다.</p><p>simdvec은 모든 데이터 유형에 대해 벌크 스코어링을 제공합니다. 이는 단순히 단일 쌍 커널에 대한 루프가 아니라 차원 스트라이드당 한 번씩 쿼리 벡터를 로드하고 여러 문서 벡터에 걸쳐 공유하는 다중 누적기 내부 루프를 사용하며, 다음 배치에 대한 명시적인 캐시 라인 프리페칭을 제공합니다. jvector와 FAISS는 (작성 시점 기준으로) 이와 동등한 기능을 제공하지 않습니다. jvector는 벌크 API가 없으므로 호출자는 루프에서 한 번에 한 쌍씩 점수를 매깁니다. FAISS는 <code>fvec_inner_products_ny</code>을(를) 노출하는데, 작성 시점 기준으로 이는 쿼리 상각이나 프리페칭 없이 단일 쌍 거리 함수에 대한 루프로 구현되어 있습니다.</p><p><strong>Float32.</strong> 커널 수준에서의 영향을 측정하기 위해, HNSW와 유사한 산포 그래프 이웃 조회를 시뮬레이션하는 무작위 접근 패턴을 사용하여 1024 차원 float32 문서 벡터의 개수를 늘려가며 단일 쿼리에 대한 점수를 매겼습니다. 세 가지 데이터 세트 크기(32, 625, 32,500개 벡터)는 작업 세트가 각각 L1, L2, L3 캐시를 초과합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df64ec77a1921ae/6a1701d714b270393ae3c4de/1d90267be1c63ac82b8ba588617ebedb0be0d1b6-1334x558.png" alt="AMD Turin(x86, AVX‑512) 및 Graviton 4(ARM, NEON)에서 세 가지 크기(32개, 625개, 32,500개 벡터)에 대해 Elasticsearch simdvec, FAISS 및 jvector의 벌크 Float32 내적 스코어링 시간을 비교하는 두 개의 막대 그래프입니다." /><p>데이터가 캐시에 모두 수용될 때, simdvec이 두 플랫폼 모두에서 가장 빠르지만, 커널 산술이 우세하기 때문에 차이는 크지 않습니다. 실제 성능 차이는 작업 세트가 L3를 넘어 커질수록 나타납니다. x86에서 simdvec은 벡터당 95ns를 기록하는 반면, FAISS는 165ns, jvector는 412ns가 필요합니다. ARM에서도 패턴은 동일합니다. simdvec은 162ns를 유지하는 반면, FAISS는 347ns, jvector는 476ns까지 증가합니다. simdvec의 프리페칭 및 쿼리 상각은 단일 쌍 커널에 대한 단순 루프로는 따라잡을 수 없는 방식으로 메모리 지연 시간을 숨겨주며, 이러한 이점은 실제 검색 워크로드가 작동하는 메인 메모리 깊숙한 곳에서 더욱 두드러집니다.</p><p><strong>Int8.</strong> 양자화된 유형에서도 동일한 패턴이 나타납니다. 동일한 L1, L2 및 L3 캐시 경계를 초과하도록 선택된 데이터 세트 크기로 1024 차원에서 int8 내적 벌크 스코어링을 측정하여 루프 내에서 simdvec의 벌크 스코어링과 NumKong의 단일 쌍 스코어링을 비교했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcda85e10007188d0/6a1701d9cf4f25d722b2cff7/9ee97d98b40d13b19370b76b33e67ea66bfb3250-1580x338.png" alt=" 'x86 — 벌크 스코어링, int8 내적(ns/op, 값이 낮을수록 좋음)'이라는 제목의 표는 세 가지 벡터 크기(128, 2,500, 130,000)에 걸쳐 ES simdvec과 NumKong을 비교하고, 해당 ns/op 값과 속도 향상 비율을 보여줍니다." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt972c311b7d08ba73/6a1701dadc55dec02ee00c87/601f50a03fa3e6263cbac9700281dfe3e511de60-1580x338.png" alt="'ARM — 벌크 스코어링, int8 내적(ns/op, 값이 낮을수록 좋음)'이라는 제목의 표는 세 가지 벡터 크기(128, 2,500, 130,000)에 걸쳐 ES simdvec과 NumKong을 비교하고, 해당 ns/op 값과 속도 향상 비율을 보여줍니다." /><p>x86에서는 명시적 프리페칭과 배치 처리의 조합으로 simdvec이 1.2배~1.9배 더 빠릅니다. ARM에서도 simdvec은 모든 데이터 세트 크기에서 1.7~1.9배 더 빠른 성능을 보입니다. 이러한 이점은 한 번에 네 개의 벡터를 배치 처리하여 인터리브 액세스 패턴을 통해 메모리 수준의 병렬 처리를 제공하는 데서 비롯됩니다. 두 경우 모두 가장 두드러진 결과는 가장 중요하고 가장 데이터 세트 크기에서 나타납니다.</p><p>제곱 거리 및 코사인에 대한 결과도 유사한 패턴을 보이며, ARM의 경우 1.4~1.8배, x86의 경우 1.3~3.0배의 속도 향상을 보입니다(자세한 내용은 <a href="https://github.com/ldematte/simsimd-benchmarks/blob/main/COMPARISON.md">여기</a>를 참조).</p><h3>메모리가 중요한 경우</h3><p>프로덕션 벡터 인덱스는 일반적으로 CPU 캐시에 적합하지 않습니다. 1024 차원의 10M 벡터 int8 인덱스는 10GB입니다. 후보를 스코어링하려면 DRAM에서 데이터를 스트리밍해야 하는데, 바로 이 부분에서 벌크 스코어링 아키텍처가 중요한 역할을 합니다.</p><p>일괄 스코어링 중 CPU 내부에서 발생하는 일을 측정하기 위해 하드웨어 성능 카운터를 사용했으며, 메모리 지연 시간을 숨기려면 아키텍처별로 하나씩, 근본적으로 다른 두 가지 전략이 필요하다는 것을 발견했습니다.</p><p><strong>x86에서는 명시적 프리페칭을 통해 캐시 미스를 방지할 수 있습니다. </strong>벌크 커널은 벡터를 순차적으로 처리하며, 다음 배치에 대한 프리페칭 명령어를 발행하면서 이전 배치가 완전히 계산된 후에 다음 배치를 처리합니다. 따라서 CPU가 데이터를 필요로 하기 전에 미래에 쓰일 데이터를 미리 L1 캐시로 끌어옵니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt368f29b5143d0f5a/6a1701dcc1e8a51780f8817b/a39548f8060c2a4a5154521a4047dd92d8cd77be-1580x309.png" alt="x86(AMD Turin) — int8 연산당 하드웨어 카운터'라는 제목의 표는 L1 캐시 미스, IPC, dTLB 미스에 대한 단일 모드와 벌크 모드를 비교하고, 해당 개선 요소를 보여줍니다." /><p>ARM에서는 프리페칭을 사용하더라도 동일한 순차적 접근 방식의 성능이 저조했습니다. 대신 <strong>벌크 커널</strong>은 매 스트라이드 위치마다 4개의 벡터에서 로드를 인터리브하여 순서에 상관없이 실행되는 엔진에 4개의 독립적인 메모리 스트림을 제공합니다. CPU는 데이터를 더 빠르게 가져오는 것이 아니라 메모리 요청이 처리되는 동안 항상 다른 계산 작업을 수행할 수 있으므로 대기 시간을 줄입니다. 자세한 분석은 <a href="https://github.com/elastic/elasticsearch/issues/145412">이 GitHub 이슈</a>에서 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8751ac5b20b75508/6a1701deab7f08db83db9d13/832de3bc0d556a493b7bf3f250196018acfc1585-1580x238.png" alt="'ARM(Graviton 4) — int8 연산당 하드웨어 카운터'라는 제목의 표는 L1 캐시 미스 및 백엔드 스톨에 대한 단일 모드와 벌크 모드를 비교하고 해당 개선 사항을 설명합니다." /><p>수치는 두 가지 다른 이야기를 들려줍니다.</p><ol><li><p>x86에서는 프리페칭을 통해 139,000개의 캐시 미스가 19,000개로 줄어들고, 사이클당 명령어 수(IPC)는 두 배 이상 증가합니다. 프리페칭은 점점 더 비용이 많이 드는 DRAM 왕복을 숨겨주기 때문에 데이터 세트 크기가 커질수록 이러한 이점은 더욱 커져 L2 캐시에서는 1.2배, L3 이상에서는 2.8배에 이릅니다.</p></li><li><p>ARM에서는 캐시 미스는 거의 변하지 않습니다. 달라지는 것은 활용률입니다. 인터리브 액세스 패턴 덕분에 파이프라인에 데이터가 지속적으로 공급되어 백엔드 스톨이 40% 감소합니다. 데이터가 캐시에서 오든 DRAM에서 오든 메모리 수준 병렬 처리가 적용되므로 이러한 이점은 데이터 세트 크기와 관계없이 1.8배로 일관되게 유지됩니다.</p></li></ol><p>두 가지 아키텍처, 두 가지 전략, 하나의 결과: 프로덕션 규모에서 simdvec은 벡터가 메인 메모리에 분산되어 있더라도 CPU 파이프라인을 계속 바쁘게 유지합니다.</p><h2>Elasticsearch 사용자에게 의미하는 바</h2><p>이러한 커널 수준 기능은 복합적으로 작용합니다. 단일 벡터 검색 쿼리는 수백만 개의 거리 연산(HNSW 그래프 탐색, 후보 스코어링, 순위 재지정)을 수행할 수 있습니다. 수천 개의 동시 쿼리에서 연산당 나노초 단위의 차이는 쿼리 지연 시간과 클러스터 처리량에 직접적인 영향을 미칩니다. float32, int8, bfloat16 또는 BBQ를 사용하든, 인덱스가 메모리에 있든 디스크에 있든, simdvec은 그 기반 엔진이며, 이러한 모든 연산은 마지막 나노초까지 최적화된 동일한 엔진을 통해 실행됩니다.</p><p>핵심은 프로덕션 규모에서 벡터 검색 성능이 주로 원시 SIMD 처리량에 의해 결정되는 것이 아니라는 점입니다. 시스템이 얼마나 효율적으로 메모리 지연 시간을 숨기면서 수백만 개의 소규모 연산에 걸쳐 컴퓨팅을 유지하느냐에 따라 결정됩니다.</p><p>simdvec 커널은 거의 모든 Elasticsearch 릴리즈에서 개선됩니다. 새로운 양자화 유형과 하드웨어 플랫폼이 등장하면 첫날부터 최적화된 커널이 제공됩니다. 또한 기존 유형은 이미 출시된 구현을 개선하면서 계속해서 빨라집니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-vector-search-simdvec-engine</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Lorenzo Dematte,Simon Cooper]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta18088409621369b/6a1701dfdc55de7297e00c8b/df9646091bafbbf0a6dfd212ff8a6bd1e8589708-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 23 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch와 Jina 임베딩을 활용한 비지도형 문서 클러스터링]]></title>
    <description><![CDATA[Elasticsearch와 Jina 임베딩을 활용한 비지도형 문서 클러스터링을 위한 실용적이고 재현 가능한 접근 방식.]]></description>
    <content:encoded><![CDATA[<p>벡터 검색은 보통 쿼리에서 시작됩니다. 하지만 쿼리가 없다면 어떻게 해야 할까요?</p><p>기업은 고객 지원 티켓, 법률 문서, 뉴스 피드, 연구 논문 등 방대한 양의 문서를 축적하며, 올바른 질문을 던지기 위해서는 먼저 그 문서에 어떤 내용이 담겨 있는지 정확히 파악해야 합니다 레이블이나 학습 데이터가 없는 상태에서 수천 건의 문서를 수동으로 검토하는 것은 현실적으로 불가능합니다. 무엇을 검색해야 할지 모를 때는 기존 검색 방식이 도움이 되지 않습니다.</p><p>이 게시물에서는 이러한 탐색 문제를 해결하는 Elasticsearch 기반의 비지도형 문서 클러스터링 및 시간적 스토리 추적 방식을 안내합니다. 이 과정을 마치고 나면, 여러 날에 걸쳐 전개되는 스토리의 흐름을 다음과 같이 추적할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="2025년 2월 한 달간 이어진 시간적 스토리 체인 — 각 색상별 경로는 며칠 동안 지속되는 스토리를 나타내며, 링크 폭은 k-NN 중첩 강도를 보여 줍니다." /><p><strong>다룰 내용:</strong></p><ul><li><p>쿼리 없이 주제를 탐색할 때, (검색용 임베딩이 아닌) <strong>클러스터링 임베딩</strong>을 사용하는 것이 왜 중요한가요?</p></li><li><p>Elasticsearch의 k-최근접 이웃(kNN)과 배치(Batched) <code>msearch</code>을(를) 활용하여, 밀도 조사 중심 분류 방식이 문서를 주제별로 그룹화하는 방법.</p></li><li><p>모델 학습 없이도 주제를 파악할 수 있도록, <a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><code>significant_text</code></a>이(가) 클러스터에 자동 레이블 지정을 수행하는 방법.</p></li><li><p>시간적 스토리 체인이 일별 클러스터를 연결하여, 주제가 매일 어떻게 진화하는지를 보여 줍니다.</p></li></ul><p>이 파이프라인은 BBC 뉴스 및 The Guardian에서 제공하는 2025년 2월 기사 약 8,500개를 테스트 코퍼스로 사용합니다. 뉴스는 시간적 흐름이 명확하기 때문에 활용하기 편리하지만, 이러한 패턴은 법률 검토, 규정 준수 모니터링, 연구 종합, 고객 지원 분류 등 문서 탐색이 중요한 모든 곳에 적용될 수 있습니다.</p><p><strong>스택:</strong></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text"><strong>Jina v5</strong></a> <strong>클러스터링 임베딩</strong>: 주제 그룹화를 위한 작업별 LoRA(Low-Rank Adaptation) 어댑터. <a href="https://www.elastic.co/blog/elastic-jina-ai">Jina가 Elastic과 함께하게 되었습니다</a>. <a href="https://www.elastic.co/docs/explore-analyze/elastic-inference/eis">Elastic Inference Service(EIS)</a>에서 Jina 모델을 네이티브로 사용할 수 있습니다.</p></li><li><p><strong>Elasticsearch</strong>: 확장 가능한 <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN</a>, <code>significant_text</code> 레이블 지정, 및 벡터 스토리지.</p></li><li><p><a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><strong>DiskBBQ:</strong></a> 근사 최근접 이웃(ANN) 검색 가속화를 위해 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq">더 나은 이진 양자화(BBQ)</a>와 계층적 K-평균 분할을 결합한 디스크 기반 벡터 인덱스 포맷입니다. 이 인덱스 분할은 벡터 검색 내부에서 이루어지며, 이 게시물에서 사용된 밀도 조사 클러스터링 알고리즘과는 별개입니다. <code>bbq_disk</code>은(는) 양자화된 벡터를 디스크에 저장하고 힙(Heap)에는 분할 메타데이터만 유지합니다. 덕분에 <code>bbq_hnsw</code>과(와) 비교했을 때 높은 재현율을 유지하면서도 리소스 요구 사양을 획기적으로 낮출 수 있습니다.</p></li><li><p><strong>전역 클러스터링 + 일별 시간적 연결:</strong> 탐색 및 스토리 진화.</p></li></ul><p><strong>준비 사항:</strong></p><ul><li><p>Elasticsearch 배포(Elastic Cloud, Elasticsearch Serverless 또는 Elastic Self-Managed 8.18+/9.0+) <code>bbq_disk</code> 을(를) 사용하려면 8.18 이상이 필요합니다. 선택 사항인 다양성 기반 검색기 섹션은 9.3 이상 또는 서버리스가 필요합니다.</p></li><li><p><a href="https://jina.ai/embeddings/">Jina API 키</a>Jina API 키: 무료 티어에는 1,000만 토큰이 포함되어 있으며, 이는 핵심 클러스터링 파이프라인(약 425만 토큰)을 감당할 수 있습니다. 선택 사항인 검색과 클러스터링 비교에는 두 번째 임베딩 패스를 사용합니다.</p></li><li><p><a href="https://bonobo.capi.gutools.co.uk/register/developer">Guardian API 키</a> (무료).</p></li></ul><h2>설정</h2><p>필요한 패키지 설치:</p>pip install elasticsearch pandas numpy plotly umap-learn python-dotenv pydantic-settings datasets requests<p>선택 사항(이 리포지토리에서 스크래핑 도우미를 실행하는 경우에만):</p>pip install beautifulsoup4<p>그런 다음 프로젝트 루트에 있는 <code>.env</code> 파일에 API 키를 구성하세요.</p>ELASTIC_CLOUD_ID=your-cloud-id        # or ELASTIC_HOST=https://...
ELASTIC_API_KEY=your-api-key
JINA_API_KEY=your-jina-key
GUARDIAN_API_KEY=your-guardian-key<p>이 노트북은 <code>load_dotenv(override=True)</code>을(를) 호출하므로, 로컬 <code>.env</code> 값이 우선적으로 적용됩니다.</p>Connected to Elasticsearch<h2>1부: 탐색 클러스터링 - 왜 임베딩을 클러스터링해야 할까요?</h2><p>대부분의 벡터 검색은 <em>쿼리</em>와 관련 <em>문서</em> 간의 일치 여부를 판단하도록 학습된 <strong>검색용 임베딩</strong>을 사용합니다. 검색에는 완벽하지만, 탐색에는 적합하지 않습니다. 쿼리 없이도 코퍼스에 어떤 주제가 존재하는지 찾으려면 유사한 문서를 함께 그룹화하는 임베딩이 필요합니다.</p><p>Jina v5는 <strong>작업별 LoRA(Low-Rank Adaptation) 어댑터</strong>를 통해 이 문제를 해결합니다. LoRA는 모델의 기본 가중치 대부분을 고정한 채, 대상 내부 레이어에 소규모의 저순위 업데이트를 추가합니다. 이를 통해 모델 전체를 재학습시키지 않고도 특정 작업에 맞춰 모델의 동작을 변화시킬 수 있습니다. 동일한 기본 모델이라도 <code>task</code> 매개변수 설정에 따라 서로 다른 임베딩을 생성합니다.</p><p>작업</p><p>학습 용도</p><p>사용 사례</p><p>retrieval.passage</p><p>쿼리-문서 일치</p><p>검색, Retrieval-Augmented Generation(RAG)</p><p>클러스터링</p><p>주제 그룹화(긴밀한 클러스터에 최적화됨)</p><p>탐색, 분류</p><p>클러스터링 어댑터는 동일한 주제의 문서들은 임베딩 공간에서 <em>더 가깝게</em>, 서로 다른 주제의 문서들은 <em>더 멀리</em> 배치되도록 학습되었습니다. 아래의 시각적 비교 자료를 통해 그 차이를 명확히 확인할 수 있습니다.</p><h3>검색과 클러스터링: 시각적 비교</h3><p>그 차이를 확인하기 위해, 두 가지 작업 유형을 각각 모두 적용하여 문서 샘플을 임베딩했습니다. 클러스터링은 원본 데이터인 1024차원의 임베딩 공간에서 수행됩니다. UMAP(Uniform Manifold Approximation and Projection)은 시각화를 위해 이러한 임베딩을 2차원으로 투영하는 용도로만 사용됩니다. UMAP은 데이터의 지역적 이웃 구조를 보존하므로, 클러스터 간의 분리를 비교하는 데 유용합니다.</p><p>아래는 동일한 480개의 문서 샘플에 두 가지 작업 유형을 각각 모두 적용하여 임베딩한 후, UMAP을 통해 2차원으로 투영한 결과입니다. 클러스터링 패널에서 더 긴밀하고, 더 명확히 분리된 색상 그룹을 찾습니다.</p>    Full dataset: 8,495 articles
    Sources: guardian: 5749, bbc: 2746
    Date range: 2025-02-01 to 2025-02-28


    Sample: 480 docs across 8 sections
    section
    Film              60
    World news        60
    Australia news    60
    Opinion           60
    Football          60
    US news           60
    Sport             60
    Business          60


    Clustering embeddings: 480
    Retrieval embeddings:  480


    UMAP projection complete<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b3733dccad212b6/6a1709407d8d67aaeb70e6a4/9bcf7a744900560c1c6c63a2dc3af2f9bfd33e11-1100x500.png" alt="검색과 클러스터링 임베딩의 UMAP 비교" /><p><em>검색용 임베딩(왼쪽)은 주제를 넓게 퍼뜨리는 반면, 클러스터링용 임베딩(오른쪽)은 동일한 문서에서도 더 긴밀하고 명확하게 구분된 그룹을 형성합니다.</em></p><p>클러스터링 임베딩은 더 긴밀하고 시각적으로도 훨씬 뚜렷하게 구별되는 그룹을 생성합니다. 검색 임베딩은 주제를 더 고르게 분산시켜 검색(세밀한 유사도)에 이상적이지만, 탐색의 경우 긴밀한 주제별 클러스터가 중요합니다.</p><p>이것이 바로 본 단계별 안내 나머지 과정에서 <code>task="clustering"</code>을(를) 사용하는 이유입니다.</p><h3>데이터셋 로딩</h3><p>코퍼스는 2025년 2월의 두 뉴스 소스를 결합합니다.</p><ul><li><p><strong>BBC 뉴스</strong>: <a href="https://huggingface.co/datasets/RealTimeData/bbc_news_alltime">RealTimeData/bbc_news_alltime</a> HuggingFace 데이터셋 활용.</p></li><li><p><strong>The Guardian</strong>: <a href="https://open-platform.theguardian.com/">Guardian Open Platform API</a>활용.</p></li></ul><p>여러 소스의 데이터를 사용하면, 클러스터링이 <em>소스만의 특정 스타일</em>이 아닌 <em>주제</em>를 제대로 찾아내는지 검증하는 데 도움이 됩니다.</p>    Total articles:  8,495
    
    Source breakdown:
    source
    guardian    5749
    bbc         2746
    
    Date range: 2025-02-01 → 2025-02-28
    Days covered: 28
    
    Sample article:
      Source:  guardian
      Title:   Carbon monoxide poisoning ruled out in death of Gene Hackman and wife, police sa
      Section: Film
      Text:    Authorities have ruled out that Gene Hackman and his wife, Betsy Arakawa, died from carbon monoxide poisoning earlier this week in their home in Santa Fe, New Mexico. The Santa Fe county sheriff, Adan...<h3>클러스터링 작업을 통한 임베딩</h3><p>모든 문서에 대해 Jina v5 API는 <code>task="clustering"</code> (으)로 호출됩니다. 임베딩은 디스크에 캐시되므로 이후 실행에서는 API를 완전히 건너뜁니다.</p><p>API 호출은 간단합니다. <code>task</code> 매개변수는 일반적인 임베딩 사용 방식과 구분되는 가장 핵심적인 차이점입니다.</p>payload = {
    "model": "jina-embeddings-v5-text-small",
    "input": texts,
    "task": "clustering",  # ← This selects the clustering LoRA adapter
}<p>아래 실행 시간은 캐시 적중이 적용되었습니다. API를 처음 실행할 때 걸리는 시간은 코퍼스 크기에 따라 달라집니다.</p>    Embeddings ready: 8,495 vectors of dimension 1024
    Time: 0.6s<h3>단일 Elasticsearch 인덱스로 색인</h3><p>탐색 클러스터링을 위해, 한 달 전체 데이터를 하나의 인덱스(<code>docs-clustering-all</code>)에 저장합니다. 일일 분할은 시간적 스토리 연결을 위해 나중에 이루어집니다.</p><p>인덱스 매핑 시 벡터 필드에 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_disk</code></a> (을)를 사용합니다.</p>{
  "embedding": {
    "type": "dense_vector",
    "dims": 1024,
    "index": true,
    "similarity": "cosine",
    "index_options": {
      "type": "bbq_disk"        // hierarchical k-means partitioning for ANN index lookup; separate from this post's clustering algorithm
    }
  }
}<p>1024차원의 float32 벡터는 용량이 4KB입니다. <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction"><code>bbq_disk</code></a>(은)는 계층적 K-평균을 사용하여 벡터를 작은 클러스터로 나누고, 이를 이진 양자화하여 저장한 뒤, 재계산을 위해 전체 정밀도 벡터를 디스크에 보관합니다. 오직 분할 메타데이터만 힙에 상주하므로, 방대한 코퍼스를 처리할 때도 메모리 사용량을 낮게 유지할 수 있습니다. 더 많은 힙을 감당할 수 있는 워크로드의 경우, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/bbq"><code>bbq_hnsw</code></a>(은)는 리소스 비용이 더 높지만 더 빠른 검색이 가능한 계층적으로 탐색 가능 작은 세계(HNSW) 그래프를 생성합니다.</p><p><a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector"><code>dense_vector</code></a> 필드 타입은 다양한 양자화 전략을 지원합니다. 여기서 사용된 1024차원 벡터와 같은 고차원 임베딩에는 <code>bbq_disk</code> 및 <code>bbq_hnsw</code> 방식이 가장 적합합니다.</p>    Indexed 8,495 documents into docs-clustering-all
    Time: 57.5s<h3>클러스터링: 밀도 조사 중심 분류</h3><p>HDBSCAN과 같은 전통적인 클러스터링 알고리즘은 N×d 크기의 전체 벡터 행렬을 메모리에 올려 둘 수 있고, 전체 데이터를 반복적으로 훑으며 업데이트를 수행할 수 있다는 가정하에 작동합니다. 1,024차원의 문서 8,495개라면 충분히 감당할 수 있는 크기(35MB 수준)입니다. 하지만 추가적인 인프라 없이는 수백만 건의 문서로 확장하기 어려운 접근 방식입니다.</p><p>이 알고리즘은 개념적으로 Voronoi 할당과 노이즈 플로어를 사용하는 KMeans++ 초기화 방식과 유사하지만, Elasticsearch <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN 검색</a>을 연산 프리미티브로 사용하여 거의 모든 작업을 서버 측에서 처리합니다.</p><ol><li><p><strong>전체 문서의 5%를 밀도 조사용으로 샘플링합니다</strong>(무작위 샘플, 최소 50개).</p></li><li><p>배치 처리된<a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><strong><code>msearch</code></strong></a> <strong>kNN</strong><strong>을 통해 밀도를 조사합니다.</strong> 각 조사 지점에서 kNN 쿼리를 실행해 주변의 평균 유사도를 기록합니다. 높은 평균 유사도는 임베딩 공간의 밀집 영역을 의미합니다. <a href="https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch"><code>msearch</code></a>(은)는 단일 HTTP 호출로 여러 검색 요청을 보낼 수 있는데, 이 기능이 여기서 매우 중요합니다. 밀도 조사 과정에서 수백 개의 kNN 쿼리가 생성되는데, 이를 배치로 처리함으로써 요청당 발생하는 오버헤드를 방지할 수 있기 때문입니다.</p></li><li><p><strong>다양성을 갖춘 고밀도 시드 선택</strong>: 밀도 중앙값 이상의 후보들을 밀도 내림차순으로 정렬한 뒤, 모든 기존 시드와의 코사인 유사도가 분리 임계값보다 낮은 경우에만 탐욕적으로 채택합니다. 이것이 유일한 클라이언트 측 연산입니다.(문서 8,000개 기준 약 0.01초 소요)</p></li><li><p><strong><code>msearch</code></strong>  <strong>kNN</strong>을 <strong>통해 모든 문서를 중심 기준으로 분류</strong>: 각 시드가 중심 역할을 수행하며, kNN 검색을 통해 유사도 임계값 이상의 인접 문서를 찾아냅니다. 각 문서는 가장 높은 점수를 반환한 중심에 할당됩니다. 규모가 작은 클러스터들은 해체되어 노이즈로 처리됩니다.</p></li></ol><p>고난도 작업은 Elasticsearch가 처리합니다. 즉, <code>msearch</code>(은)는 밀도 조사에, <code>msearch</code>(은)는 분류에, 그리고 <code>significant_text</code>(은)는 레이블 지정에 활용됩니다. 이 코퍼스(총 8,495개 문서)의 경우, 5%의 밀도 조사용 샘플을 통해 425개의 kNN 조사 쿼리가 실행됩니다. <code>msearch</code>(은)는 이를 9개의 HTTP 호출(배치 크기 50)로 배치 처리함으로써, 개별 조사마다 발생할 수 있는 요청 오버헤드를 방지합니다. 여기에 <code>bbq_disk</code>의 ANN 조회가 결합되어, 클러스터링 단계를 빠르고 확장 가능하게 유지해 줍니다. 클러스터링 단계에서는 속도를 위해 <a href="https://www.elastic.co/docs/deploy-manage/production-guidance/optimize-performance/approximate-knn-search"><code>num_candidates</code></a> 값을 최소화하여 kNN 쿼리를 실행합니다. 반면, 프로덕션 검색 쿼리에서는 지연 시간이 늘어나더라도 재현율을 높이기 위해 더 큰 <code>num_candidates</code> 값을 사용해야 합니다.</p><p>클러스터의 크기는 엄격한 <code>k</code> 제한이 아니라, 각 중심 주변의 임베딩 공간 밀도에 따라 자연스럽게 결정됩니다. 밀집된 주제 영역은 더 큰 클러스터를 형성하며, 지엽적인 주제 영역은 더 작은 클러스터를 형성합니다.</p><h4>KMeans나 HDBSCAN을 사용하지 않는 이유는 무엇일까요?</h4><p>KMeans는 구형 클러스터를 가정하며 전체 N×d 행렬을 메모리에 저장해야 합니다. 반면, 메모리에 수용 가능한 크기의 코퍼스라면 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.cluster.HDBSCAN.html">HDBSCAN</a>이 강력한 대안이 됩니다. 이는 클러스터의 모양에 구애받지 않으며, 밀도 시맨틱이 잘 정립되어 있습니다.</p><p>밀도 조사 중심 접근 방식은 다른 지엽적인 영역을 공략합니다. 바로 저장 공간, 검색, 클러스터링을 하나의 시스템 내에서 통합 처리하려 하거나, 데이터 규모 때문에 클라이언트 측에서의 행렬 연산이 실용적이지 못한 경우입니다. Elasticsearch kNN을 컴퓨팅 기본 요소로 사용하고, 임의의 클러스터 크기를 처리하며, 거의 모든 계산을 서버 측에서 수행합니다.</p>    Clustered global index in 31.6s
      Total clusters: 82
      Total noise:    2420 (28.5%)
      Density probes: 425 kNN queries via 9 _msearch HTTP calls<h4>노이즈 비율에 대한 이해</h4><p>약 28%의 노이즈 발생률은 의도적으로 설계된 것이며, 오류 모드가 아닙니다. 구성된 <code>similarity_threshold</code> 기준에 따라 어떤 밀집 클러스터에도 적합하지 않은 문서는, 어설픈 매칭을 강행하는 대신 미할당 상태로 남겨 둡니다. 이는 일종의 품질 검사기 역할을 합니다. 사설 칼럼, 단신, 혹은 일회성 기사들은 응집력 있는 그룹을 형성할 만큼의 주제적 밀도가 낮기 때문에 자연스럽게 클러스터링을 거부하게 됩니다.</p><p>이 임계값은 조정이 가능합니다. <code>similarity_threshold</code>(을)를 낮추면 더 공격적인 클러스터링이 이루어지며 (더 많은 문서가 할당되지만 군집의 결속력은 느슨해짐) 반대로 높이면 클러스터가 더 긴밀해지는 대신 노이즈 비율이 증가합니다. 다양한 뉴스 콘텐츠가 섞인 이번 코퍼스의 경우, 약 30%의 노이즈 비율은 운용상 적절한 수치입니다. 실제 배포 환경에서는 도메인별 품질 기준에 맞춰 임계값을 조정해야 합니다.</p><h3>significant_text를 활용한 자동 레이블</h3><p>이제 각 클러스터에 사람이 읽을 수 있는 레이블을 지정해야 합니다. Elasticsearch의 <code>significant_text</code> 집계는 백그라운드 세트(전체 코퍼스)와 비교했을 때, 포그라운드 세트(클러스터) 내에서 유독 자주 등장하는 용어들을 찾아냅니다.</p><p>내부적으로는 머신 러닝이나 거대 언어 모델(LLM) 호출 없이, 절대 빈도와 상대 빈도의 변화를 균형 있게 고려하는 통계적 휴리스틱(기본값으로 JLH 점수 사용)을 활용합니다 영국 정치에 관한 클러스터라면 <code>starmer</code>, <code>labour</code>, <code>downing</code> 같은 용어들이 등장할 것입니다. 이 용어들이 전체 뉴스 코퍼스에 비해 해당 클러스터 내에서 압도적으로 높은 비중을 차지하기 때문입니다.</p><p>이번 전체 프로세스에서 레이블은 <code>docs-clustering-all</code>을(를) 기준으로 직접 계산됩니다. 따라서 포그라운드와 백그라운드 모두 한 달 전체 분량에서 추출됩니다. 2부의 레이블링 작업에는 일별 인덱스 패턴(<code>docs-clustering-*</code>)이 사용됩니다. 이 와일드카드 패턴은 쿼리가 모든 일치 인덱스를 동시에 쿼리할 수 있게 하여, <code>significant_text</code>이(가) 더 넓은 백그라운드 바탕으로 더 명확한 대비 효과를 얻을 수 있도록 합니다.</p><p>최소한의 쿼리 형태는 다음과 같습니다.</p>{
  "size": 0,
  "query": { "term": { "cluster_id": "72" } },
  "aggs": {
    "label_terms": {
      "significant_text": {
        "field": "text",
        "size": 5,
        "filter_duplicate_text": true
      }
    }
  }
}<p><code>significant_text</code> 또한 품질 검사기 역할도 합니다. 유미의한 용어를 생성하지 않는 클러스터는 구별되는 어휘가 없습니다. 이는 일관성 없는 그룹으로, 오해의 소지가 있는 레이블을 지정하기보다는 다시 노이즈로 해체되어야 합니다.</p><p>가벼운 결정론적 정제 단계를 거쳐 노이즈가 섞인 레이블 용어(숫자 토큰, 일반 단어 등)를 제거하며, 필요한 경우 대표적인 헤드라인을 대신 사용합니다. 이를 통해 레이블의 가독성을 높이면서도 Elasticsearch 레이블을 네이티브로 유지할 수 있습니다.</p>    Sample cluster labels:
      cluster   3  (200 docs)  arsenal | mikel | villa
      cluster   1  (198 docs)  volodymyr | ukrainian | kyiv
      cluster   0  (196 docs)  hostages | hamas | israeli
      cluster   4  (187 docs)  scrum | rugby | borthwick
      cluster  52  (185 docs)  fossil | renewable | renewables
      cluster  10  (156 docs)  labour | gwynne | mps
      cluster  40  (151 docs)  novel | novels | literary
      cluster  11  (149 docs)  mewis | sarina | wiegman
      cluster  44  (143 docs)  flooding | rainfall | rain
      cluster  13  (131 docs)  doge | musk | elon
      cluster  12  (128 docs)  murder | insp | knockholt
      cluster   5  (124 docs)  putin | backstop | starmer


    Reassigned 35 docs from incoherent clusters to noise
    Total docs: 8,495
    Clustered:  6,040 (71.1%)
    Noise:      2,455 (28.9%)<h3>클러스터 시각화</h3><p>아래 시각화는 전체 클러스터링 과정에서 발견된 결과물입니다. 클러스터링된 문서와 노이즈 문서의 날짜별 구성 현황, 한 달 전체 데이터의 UMAP 투영도, 그리고 클러스터가이 소스가 아닌 주제를 기준으로 형성되었음을 보여 주는 소스 혼합 차트를 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4ed087d8b6dac2a0/6a17094260084b44543c4501/99099f5adaa945ae4097c50b0d7151c7dd28872e-1000x400.png" alt="클러스터링된 문서와 노이즈 문서의 일일 분포" /><p>2025년 2월 전체 기간에 걸친 클러스터링된 문서와 노이즈 문서의 일일 분포.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5ca320bc91131ab/6a17094366c4f95828f8bfbf/477c6c7177942955a942f85f5c881da50e517915-1100x700.png" alt="전체 문서에 대한 한 달간의 UMAP 투영도" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f554a6bc2bc367b/6a170945a929cfa7a1ae0947/4f4302556c8974c416842452cf33bca06e90b966-1100x700.png" alt="클러스터된 문서만 보여 주는 UMAP 투영도" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8e40c26f89ce5523/6a17094747d49c147d2d8974/327f96a79e382ef30614cb0570aa7fccd822b8f8-1100x700.png" alt="[단일 클러스터를 강조 표시한 UMAP 투영" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1dd2c19ae628f1d/6a1709481949f7a630e7a9a3/acfb1524a10e24d6ff2412e7c3ec0f2b3ac75193-900x600.png" alt="주제 기반 그룹화를 보여 주는 클러스터별 소스 혼합" /><p>UMAP 상의 각 색상별 섬은 클러스터를 나타냅니다. 이는 오직 임베딩 유사도만을 기반으로 발견된, 동일한 주제에 관한 기사 그룹입니다. 회색으로 표시된 노이즈 지점은 어떤 클러스터에도 명확히 속하지 않는 기사들입니다.(주로 짧은 사설, 또는 단발성 뉴스인 경우가 많음)</p><p>소스 분류 차트를 통해 각 클러스터에 BBC News와 The Guardian의 기사가 <strong>모두</strong> 포함되어 있음을 확인할 수 있습니다. 이 클러스터링 시스템은 <em>소스</em>가 아닌 <em>주제</em>를 찾아내고 있으며, 이는 비지도형 탐색이 생성해야 하는 가장 이상적인 결과입니다.</p><h3>다양성 기반 검색기(diversify retriever)를 사용하여 클러스터 범위를 탐색</h3><p>일반 kNN은 클러스터의 중심(밀집된 핵심)과 가장 유사한 문서를 반환합니다. 그러나 실제 클러스터는 하위 주제를 다룹니다. <a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/retrievers/diversify-retriever"><strong>다양성 기반 검색기</strong></a>는 최대 한계 관련성(MMR)을 사용하여, 클러스터 중심과 관련성이 높으면서도 <em>서로 다른</em> 문서들을 찾아냅니다.</p><p>핵심 매개변수는 <strong>λ(람다)</strong>입니다.</p><ul><li><p>λ = 1.0 → 순수한 관련성(일반 kNN과 동일)입니다.</p></li><li><p>λ = 0.0 → 순수한 다양성 (최대한 분산된 결과).</p></li><li><p>λ=0.5 → 균형: 주제와 관련이 있지만 다른 관점을 다룹니다.</p></li></ul><p>최소한의 검색기 요청 형태는 다음과 같습니다.</p>{
  "size": 8,
  "retriever": {
    "diversify": {
      "type": "mmr",
      "field": "embedding",
      "lambda": 0.5,
      "query_vector": "&lt;cluster-centroid-vector&gt;",
      "retriever": {
        "knn": {
          "field": "embedding",
          "query_vector": "&lt;cluster-centroid-vector&gt;",
          "k": 50,
          "num_candidates": 100
        }
      }
    }
  }
}<p>다양성 수준에서 <code>type</code>, <code>field</code>, <code>query_vector</code> 매개변수는 필수입니다. <code>field</code>(은)는 결과 간 유사도를 측정할 때 어떤 dense_vector 필드를 사용할지 MMR에 지시하며, <code>query_vector</code>(은)는 정확도 점수 산정의 기준점을 제공합니다.</p><p>이를 통해 단순히 '이 클러스터의 중심 내용이 무엇인가?'를 파악하는 수준을 넘어, '이 클러스터가 실제로 무엇을 다루고 있는가?' 라는 질문에 답할 수 있습니다.</p>    Exploring cluster 52 (185 docs)
    Label: fossil | renewable | renewables
    Centroid computed (dim=1024)


    ========================================================================
    Plain kNN (closest to centroid)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9710] Thirteen more oil and gas licences could be cancelled as ministers decide new guidance for fossil fuel extraction after a landmark court...
      3. [0.9699] Experts have accused the fossil fuel industry of seeking special treatment after lobbyists argued greenhouse gas emissions from oilfields...
      4. [0.9681] Burning wood is a terrible way of producing electricity . Chopping down trees destroys habitats for wildlife, and growing new trees cannot...
      5. [0.9649] Keir Starmer will do huge damage to the global fight against climate change if he gives in to political pressure and allows the development...
      6. [0.9641] Labour will next week be confronted with stark policy choices that threaten to expose the fault lines between the Treasury and the...
      7. [0.9638] The Drax power station near Selby in north Yorkshire burns imported wood pellets  The government has agreed a new funding arrangement with...
      8. [0.9581] If you care about the world we are handing on to future generations, the news on Thursday morning was dramatic. This January was the...
    
    ========================================================================
    Diversify retriever (MMR, lambda=0.5)
    ========================================================================
      1. [0.9738] Green campaigners fear ministers are poised to award billions of pounds in fresh subsidies to Drax power station, despite strong concerns...
      2. [0.9434] Oil and gas interests have waged a coordinated campaign to kill pro-electrification policies that ban gas connections in new buildings ,...
      3. [0.9303] It was interesting to read that new licences for oil and gas production in the North Sea are being delayed by legal action ( Thirteen more...
      4. [0.9139] The US energy secretary, Chris Wright, has said he “would love to see Australia get in the game of supplying uranium and maybe going down...
      5. [0.9077] Rachel Reeves was facing criticism on Saturday night as it was confirmed that a report she cited as evidence that a third ­runway at...
      6. [0.8996] When Margaret Thatcher opened the Hadley Centre for Climate Change in 1990 journalists suggested she was attempting to appear to be doing...
      7. [0.8993] The vast majority of governments are likely to miss a looming deadline to file vital plans that will determine whether or not the world has...
      8. [0.8987] European imports of seaborne gas shipments fell by a fifth last year to their lowest level since the pandemic, according to a new report,...
    
    Overlap: 1/8 documents appear in both result sets
    
    Avg pairwise similarity (lower = more diverse):
      Plain kNN:          0.9057
      Diversify retriever: 0.6965<p>일반 kNN 결과는 주제의 한 가지 관점에 대해서만 클러스터를 이룹니다. 즉, 중심 및 서로 간의 유사도가 가장 높은 문서들만 한데 모이게 됩니다. 다양성 기반 검색기는 동일한 클러스터의 서로 다른 패싯, 즉 하위 주제, 서로 다른 소스, 다양한 관점을 보여 줍니다.</p><p>다양성 지표가 이를 수치적으로 증명합니다. 다양성 기반 검색기 결과의 평균 쌍별 유사성이 더 낮게 나타나는데, 이는 검색된 문서가 더 넓은 범위를 다룬다는 것을 의미합니다.</p><p>이는 다음과 같은 경우에 유용합니다.</p><ul><li><p><strong>클러스터가 실제로 포괄하는 범위가 어디까지인지 이해하려면</strong>, 단순히 중심부뿐만 아니라 그 외곽 경계까지 살펴봐야 합니다.</p></li><li><p><strong>요약 생성</strong>. 다양한 대표 문서가 LLM에 더 나은 자료를 제공합니다.</p></li><li><p>사람이 직접 검토하거나 후속 레이블 지정에 사용할 <strong>대표 사례들을 찾아냅니다</strong>.</p></li><li><p><strong>품질 검사</strong>. 다양한 결과가 일관성이 없어 보이면 클러스터를 분할해야 할 수도 있습니다.</p></li></ul><h2>2부: 시간적 스토리 체인</h2><h3>일자별 스토리 추적</h3><p>1부에서는 주제 탐색을 위해 한달 전체를 전역으로 클러스터링했습니다. 시간적 흐름을 파악하기 위해, 동일한 밀도 조사 중심 분류를 <strong>일별 인덱스</strong>에 대해 매일 독립적으로 실행하면, 인접한 날짜 간에 클러스터가 서로 연결됩니다. 참고로 일별 클러스터는 1부의 전역 클러스터와는 별개입니다. 각 날짜마다 그날의 콘텐츠에 맞게 조정된 자체 클러스터 할당과 레이블이 생성됩니다.</p><h4><strong>연결 방식: 샘플링 후 쿼리</strong></h4><p>A일의 각 클러스터에 대하여:</p><ol><li><p>몇 개의 대표 문서들을 샘플링합니다.</p></li><li><p>B일의 인덱스를 대상으로 kNN 검색을 실행합니다.</p></li><li><p>B일의 각 클러스터에 검색 결과가 몇 건이나 도달하는지 계산합니다.</p></li><li><p>검색 결과의 비율이 임계값(kNN fraction ≥ 0.4)을 초과하면, 두 클러스터 사이의 링크를 기록합니다.</p></li></ol><p>이 과정은 빠르며(전체 문서가 아닌 클러스터당 몇 개의 문서만 쿼리하기 때문) Elasticsearch의 네이티브 kNN을 사용하므로 외부 도구가 필요하지 않습니다.</p>Preparing daily indices for temporal linkage...


Indexed 8,495 docs into 28 daily indices


Temporal links found: 808 in 145.4s

Strongest links:
  2025.02.01 'league | arsenal | premier' -&gt; 2025.02.02 'league | season | striker'  (100%)
  2025.02.03 'league | striker | loan' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.03 'score | operator | gedling' -&gt; 2025.02.04 'league | striker | season'  (100%)
  2025.02.12 'playoff | leg | bayern' -&gt; 2025.02.13 'league | players | injury'  (100%)
  2025.02.14 'league | injury | football' -&gt; 2025.02.15 'league | premier | football'  (100%)
  2025.02.18 'russia | ukraine | talks' -&gt; 2025.02.19 'saudi | russia | arabia'  (100%)
  2025.02.18 'football | league | bayern' -&gt; 2025.02.19 'league | manchester | players'  (100%)
  2025.02.21 'league | premier | manchester' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.21 'rugby | calcutta | brilliant' -&gt; 2025.02.22 'game | players | defeat'  (100%)
  2025.02.26 'metals | kyiv | ukrainian' -&gt; 2025.02.27 'ukraine | russia | talks'  (100%)<p>kNN 비율이 100%라는 것은 소스 클러스터에서 샘플링된 모든 문서가 동일한 타겟 클러스터에 도달했음을 의미하며, 이는 일자 간에 형성될 수 있는 가장 강력한 링크입니다. 위에서 확인된 대부분의 링크는 축구 관련 내용입니다. 이는 타당한 결과인데, 프리미어 리그 보도는 매일 발생하며 주제의 일관성 또한 매우 높기 때문입니다.</p><p><code>score | operator | gedling</code> → <code>league | striker | season</code>(으)로 이어지는 링크는 지엽적인 로컬 축구 클러스터(Gedling은 비리그 구단임)가 다음 날 더 광범위한 프리미어 리그 클러스터로 흡수되는 과정을 보여 주는 사례입니다. 이는 매일 다른 세부 단위로 다시 클러스터링이 이루어질 때 나타나는 자연스러운 현상입니다.</p><h3>스토리 체인 구축</h3><p>스토리 체인은 연속된 날짜에 걸쳐 서로 연결된 클러스터들의 시퀀스입니다.</p><p>각각의 쌍별 링크를 통해 월요일의 '영국 정치' 클러스터가 화요일의 클러스터와 연결된다는 사실을 알 수 있습니다. 월요일에 시작된 이야기가 한 주 동안 어떻게 전개되고, 금요일에 이르러 어떻게 잦아드는지, 체인을 통해 스토리의 전체 흐름을 파악할 수 있습니다.</p><p>체인은 kNN 비율 0.4 이상의 링크를 바탕으로 탐욕적 방식으로 구축됩니다. 이는 소스 클러스터에서 샘플링된 문서 중 최소 40%가 단일 타겟 클러스터에 매칭되었음을 의미합니다. 가장 이른 시점의 클러스터부터 시작하여, 알고리즘은 항상 가장 강력한 출력 링크를 따라갑니다.
</p>    Strong links (kNN fraction &gt;= 0.4): 244
    Story chains spanning 3+ days: 18
      Chain 1: 'ukrainian | kyiv | eastern' (19 days: Feb 3 → Feb 21)
      Chain 2: 'playing | opposition' (19 days: Feb 10 → Feb 28)
      Chain 3: 'tadhg | maro | cadan' (10 days: Feb 1 → Feb 10)
      Chain 4: 'invade | china | putin' (8 days: Feb 21 → Feb 28)
      Chain 5: 'elected | labour | leader' (7 days: Feb 12 → Feb 18)
      Chain 6: 'film | swift | awards' (6 days: Feb 2 → Feb 7)
      Chain 7: 'amendment | termination | reporting' (6 days: Feb 12 → Feb 17)
      Chain 8: 'officers | scene | police' (5 days: Feb 1 → Feb 5)<p>가장 긴 스토리 체인은 우크라이나-러시아 관련 보도를 19일 연속으로 추적했는데, 2025년 2월의 지속적인 지정학적 긴장 수위를 고려하면 당연한 결과라 할 수 있습니다. 두 번째로 긴 체인은 한 달 중 19일 동안 이어지는 프리미어 리그 축구 소식을 추적합니다. 더 짧은 체인으로는 시상식 시즌(영화/시상식, 6일), 식스 네이션스 럭비(10일), 그리고 영국 정치 리더십 관련 보도(7일) 등이 포착되었습니다. 각 체인은 알고리즘이 일자별 인덱스 간의 임베딩 유사도만을 바탕으로 순수하게 찾아낸 스토리의 흐름을 나타냅니다.</p><h3>Sankey: 스토리 흐름 시각화</h3><p>Sankey 다이어그램은 링크의 폭으로 연결 강도를 표현하는 흐름 시각화 도구입니다. 여기에서 각 수직 밴드는 하루를, 각 노드는 일별 클러스터(문서 수에 따른 크기)를 나타내며, 각각의 색상 경로는 시간을 따라 이어지는 하나의 스토리 체인을 추적합니다. 링크의 폭은 kNN 중첩 강도를 나타냅니다. 링크가 굵을수록 샘플링된 더 많은 문서가 타겟 클러스터에 도달했음을 의미합니다. 체인마다 색상이 일치하므로, 왼쪽에서 오른쪽으로 이어지는 하나의 색상 경로는 하나의 이야기 진행을 나타냅니다.</p><p>예를 들어, (가장 긴 경로 중 하나로 시각화된) 우크라이나-러시아 체인은 2월 초부터 셋째 주까지 계속해서 이어지며, 링크의 폭이 지속적으로 굵게 유지되는 것은 일자별로 강력한 주제적 연속성이 있었음을 보여 줍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8c243e0c5773440/6a17093fa6c2b98c86e7968c/100a60a7fb85da8ab3813fd071a82c93f2c3f318-1300x650.png" alt="2025년 2월 한 달간 전개되는 시간적 스토리 체인" /><p><em>2025년 2월 한 달간 전개되는 시간적 스토리 체인. 각 색상별 경로는 며칠 동안 지속되는 스토리를 나타내며, 링크 폭은 k-NN 중첩 강도를 보여 줍니다.</em></p><h2>이 접근 방식이 제공하는 이점</h2><p>이 단계별 안내에서는 Elasticsearch를 활용하여 비지도형 문서 클러스터링 파이프라인의 전 과정을 살펴보았습니다.</p><ol><li><p><strong>임베딩 클러스터링</strong>: Jina v5의 작업별 어댑터는 단순한 쿼리-문서 매칭을 넘어, 주제별 그룹화에 최적화된 임베딩을 생성합니다.</p></li><li><p><strong>전역 탐색 클러스터링</strong>: 한 달 전체를 하나의 인덱스에 클러스터링하면 일별 주제 탐색을 극대화할 수 있습니다.</p></li><li><p><strong>밀도 조사 중심 분류</strong>: 5%를 샘플링하고, <code>msearch</code> kNN을 통해 밀도를 조사하며, 밀도가 높은 다양한 시드를 선택하고, 모든 문서를 이 중심에 맞춰 분류합니다. 연산량이 많아 무거운 작업은 Elasticsearch가 처리하며, 시드 선택 과정만 클라이언트 측에서 실행됩니다.(약 0.01초 소요)</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significanttext-aggregation"><strong><code>significant_text</code></strong></a> <strong>레이블 지정</strong>: 유의성 검정을 통해 ML 모델이나 수동 주석 없이도 의미 있는 클러스터 레이블을 생성합니다. 유의미한 용어를 생성하지 않는 클러스터는 일관성이 없으며 노이즈로 강등됩니다. 이는 시스템에 내장된 품질 검사기입니다.</p></li><li><p><strong>시간적 스토리 연결</strong>: 일일 인덱스와 샘플 및 쿼리 교차 인덱스 kNN은 시간이 지남에 따라 스토리가 어떻게 진화하는지 추적합니다.</p></li></ol><p><strong>핵심 사항:</strong></p><ul><li><p>임베딩 작업 유형 설정이 핵심입니다. 클러스터링 임베딩을 사용하면 주제별 그룹이 훨씬 더 긴밀하게 형성됩니다.</p></li><li><p>Elasticsearch는 <a href="https://www.elastic.co/docs/solutions/search/vector/knn">kNN 검색</a>을 통해 저장 계층 <em>및</em> 클러스터링 엔진 역할을 수행할 수 있습니다.</p></li><li><p>밀도 조사 중심 분류는 거의 모든 연산을 서버 측에서 처리하며, 임베딩 공간의 밀도에 따라 자연스러운 크기의 클러스터를 생성합니다.</p></li><li><p><code>significant_text</code> 빠르고 해석 가능하며 자동 레이블링과 품질 검사 모두에 효과적입니다.</p></li></ul><p><strong>이 방식이 유용한 경우:</strong></p><ul><li><p>타임스탬프가 찍힌 텍스트는 있지만, 레이블이 지정된 학습 데이터 없이 주제를 탐색하고 싶을 때 유용합니다.</p></li><li><p>저장 공간, 벡터 검색, 레이블 지정 및 시간적 연결을 하나의 스택으로 해결하고 싶을 때 적합합니다.</p></li></ul><p><strong>탐색할 확장 기능:</strong></p><ul><li><p>다중 기간 클러스터링(주간, 월간 단위 집계)</p></li><li><p>클러스터 할당을 점진적으로 수행하여 실시간 데이터 수집이 가능합니다.</p></li><li><p>significant_text 용어를 시드로 활용해 LLM이 클러스터 요약문을 생성합니다.</p></li><li><p>규모가 커지면, 샘플링된 KMeans 중심은 밀도 기반 클러스터링을 위한 웜 스타트 시드로 사용될 수 있어, 조사 단계 비용을 줄여 줍니다.</p></li></ul><h2>직접 사용해 보기</h2><p>자신만의 타임스탬프 기반의 문서 코퍼스를 넣어 보세요. 날짜 정보가 포함된 텍스트 모음이라면 무엇이든 이 파이프라인에서 바로 작동합니다. 전체 노트북과 지원 코드는 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/unsupervised-document-clustering-elasticsearch-jina-embeddings">제공된 리포지토리</a>에서 확인할 수 있습니다.</p><ul><li><p><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"><strong>무료 Elastic Cloud 체험판 시작하기</strong></a>: 단 몇 분 만에 <code>bbq_disk</code> 기능이 지원되는 관리형 클러스터를 실행할 수 있습니다.</p></li><li><p><a href="https://www.elastic.co/elasticsearch/serverless"><strong>Elasticsearch Serverless 사용해 보기</strong></a>: 클러스터 관리가 필요 없으며, 자동으로 확장하고, 이 단계별 안내의 모든 기능을 지원합니다.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/unsupervised-document-clustering-elasticsearch-jina-embeddings</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[ML 연구]]></category>
    <category><![CDATA[Jina AI]]></category>
    <dc:creator><![CDATA[Matthew Adams]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4bd7dd10a7cd6dc8/6a17094a14b270581de3c5b6/662c00694c3e0c2fb2128098bdb6813df9e86a72-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[TSDS와 ILM의 만남: 늦게 도착하는 데이터를 거부하지 않는 시계열 데이터 스트림 설계]]></title>
    <description><![CDATA[TSDS 시간 제한이 ILM 단계와 상호 작용하는 방법과 늦게 도착하는 메트릭을 처리할 수 있는 정책 설계 방법]]></description>
    <content:encoded><![CDATA[<p>최근 한 고객의 메트릭 클러스터를 '모든 데이터를 hot 티어에 두는' 구조에서 hot/cold/frozen 아키텍처로 전환했습니다. 이미 수십 번은 해 본 익숙한 작업이었습니다. 그런데 몇 분 만에 Logstash에서 데이터가 더 이상 진행되지 않았습니다.</p><p>Elasticsearch는 늦게 도착한 메트릭을 거부하고 있었습니다. 이로 인해 파이프라인이 밀리기 시작했고, 더 많은 데이터가 늦게 도착하면서 거부가 더욱 늘어나게 되었습니다. 결국 파이프라인은 완전히 멈춰 섰습니다.</p><p>스냅샷에서 복원하고, 데이터를 다시 인덱싱하고, 수집 파이프라인을 다시 설계하여 복구해야 했습니다.</p><p>근본 원인은 인덱스 수명 주기 관리(ILM) 자체가 아니라, 시계열 데이터 스트림(TSDS)과 이를 통해 시간 제한 백킹(backing) 인덱스를 강제하는 방식에 있었습니다.</p><p>TSDS는 메트릭의 저장 공간 요구 사항을 40–70% 줄일 수 있지만, TSDS를 효율적으로 만드는 아키텍처 변경으로 인해 시간이 지남에 따라 인덱스의 동작 방식도 달라집니다. 이러한 변화는 ILM 정책을 설계하거나 수집 파이프라인에서 데이터가 늦게 도착할 수 있는 상황에서 중요합니다.</p><h2>요약</h2><p>TSDS를 사용하는 경우:</p><ul><li><p>백킹 인덱스는 특정 시간 범위에 해당하는 문서만 허용합니다.</p></li><li><p>인덱스가 cold 또는 frozen으로 이동한 뒤 데이터가 늦게 도착하면, Elasticsearch는 해당 문서를 수용하지 않거나(설정된 경우) failure store로 라우팅합니다.</p></li></ul><p>설계 규칙:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<h2>시계열 데이터 스트림이란 무엇인가요?</h2><p><em>시계열 데이터 스트림</em>(TSDS)은 메트릭 데이터에 최적화된 특수한 데이터 스트림입니다. 데이터는 관련 문서가 동일한 샤드 내에 위치하도록 라우팅되어 쿼리 및 검색에 최적화됩니다. Elasticsearch가 이를 수행하는 방법은 다음과 같습니다.</p><p>각 문서에는 다음이 포함됩니다.</p><ul><li><p>타임스탬프.</p></li><li><p>시계열을 식별하는 dimension 필드.</p></li><li><p>측정값을 나타내는 metric 필드.</p></li></ul><p>예를 들면 다음과 같습니다.</p><ul><li><p>호스트당 CPU 사용량.</p></li><li><p>서비스별 요청 지연 시간.</p></li><li><p>센서별 온도 측정값.</p></li></ul><p><em>dimensions</em>는 측정하고자 하는 대상을 식별하는 반면, <em>metrics</em>는 시간에 따라 변하는 값을 나타냅니다.</p><h3>dimensions</h3><p>dimensions는 측정되는 엔티티를 설명합니다.</p><p>예:</p>host.name
service.name
container.id<p>다음과 같이 매핑에서 정의합니다.</p>time_series_dimension: true<h3>metrics</h3><p>metrics는 숫자 값을 나타내며 다음을 사용하여 정의됩니다.</p>time_series_metric<p>일반적인 metric 유형:</p><ul><li><p>gauge: 오르내리는 값.</p></li><li><p>counter: 재설정될 때까지 증가하는 값.</p></li></ul><p>Elastic Agent는 주로 메트릭과 로그 데이터를 수집하므로, TSDS 인덱스를 직접 활성화하지 않은 경우에도 클러스터에 해당 인덱스가 여전히 존재할 수 있습니다.</p><h3>_tsid 필드</h3><p>Elasticsearch는 내부적으로 dimension 필드로부터 <code>_tsid</code> 값을 생성합니다. 이를 통해 동일한 dimensions를 가진 문서는 동일한 샤드로 라우팅되어 다음을 개선합니다.</p><ul><li><p>압축.</p></li><li><p>쿼리 로컬리티.</p></li><li><p>집계 성능.</p></li></ul><h2>주요 차이점: 시간 제한 백킹 인덱스</h2><p>기존 데이터 스트림은 항상 <em>쓰기 인덱스</em>라고 하는 가장 최근의 백킹 인덱스에 기록되지만, TSDS는 다르게 동작합니다.</p><p>각 TSDS 백킹 인덱스에는 정의된 시간 구간이 있으며 해당 구간에 해당하는 <code>@timestamp</code> 값을 가진 문서만 허용합니다.</p>GET _data_stream/my-metrics-data-stream


     "index_mode": "time_series",
     "time_series": {
       "temporal_ranges": [
         {
           "start": "2026-01-15T14:35:50.000Z",
           "end": "2026-03-16T11:34:40.000Z"
         }
       ]
     }<p>문서가 인덱싱되면 Elasticsearch는 해당 타임스탬프를 담당하는 백킹 인덱스로 라우팅하는데, 이는 기존의 인덱스와 달리 TSDS가 여러 백킹 인덱스에 동시에 쓰기를 수행할 수 있음을 의미합니다.</p><p>그 예는 다음과 같습니다.</p><ul><li><p>실시간 데이터 → 최신 인덱스.</p></li><li><p>지연된 데이터 → 해당 기간을 포함하는 이전 인덱스.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b001853af30d5f8/6a17dc2cfaa9137a7d93c751/31af2bb3b3dc24db8342e791e1db77a44659ba7a-1589x502.png" alt="최신 문서는 이전 인덱스로 라우팅되고, 현재 문서는 최신 인덱스로 라우팅되는 과정을 보여주는 타임라인입니다." /><h2>늦게 도착하는 데이터를 위한 설계</h2><p>실제 수집 파이프라인은 메트릭을 제시간에 완벽하게 전달하는 경우가 거의 없습니다. 메트릭은 네트워크 장애, 백로그, 배치 수집, 엣지 디바이스의 연결 끊김 등으로 인해 지연될 수 있으며, 다시 연결되면 밀린 데이터를 시작합니다.</p><p>기존 인덱스는 이러한 지연을 조용히 흡수하지만, TSDS는 그렇지 않습니다.</p><p>문서의 타임스탬프가 쓰기 가능한 백킹 인덱스의 범위를 벗어나면 Elasticsearch가 이를 거부하므로, ILM 정책은 늦게 도착하는 데이터를 반드시 고려해야 합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6e8eae1b2ddad142/6a17dc2e1d1b8335a793e35c/32a103b95b20e31615c214271e27811a7ee315ae-1999x691.png" alt="인덱스 수명 주기 타임라인" /><h2>핵심 제약 조건</h2><p>백킹 인덱스는 지연 데이터를 수용할 수 있을 만큼 충분히 오랫동안 쓰기 가능 상태를 유지해야 합니다.</p><p>실제로는 다음과 같다.</p>time_until_readonly &gt; maximum_expected_lateness<p>ILM은 롤오버 시점부터 인덱스 나이를 측정하므로, 운영 규칙은 다음과 같습니다.</p>warm_or_cold_min_age &gt; rollover_max_age + maximum_expected_lateness<p></p><p>예를 들어, 메트릭이 최대 6시간 늦게 도착할 수 있는 경우 인덱스는 롤오버 후 최소 6시간 동안 쓰기 가능한 상태로 유지되어야 합니다.</p><p></p><p>이러한 제약 조건을 고려하지 못한 것이 바로 앞서 설명한 데이터 수집 실패의 원인이었습니다. 늦게 도착한 데이터는 이전 인덱스로 전달됐는데, 해당 인덱스는 이미 cold 티어에 있어서 쓰기가 차단된 상태였습니다.</p><p></p><h2>거부된 문서를 처리하는 방법</h2><p>TSDS가 문서를 거부하면 Elasticsearch는 오류를 반환하는데, 이는 타임스탬프가 쓰기 가능한 인덱스 범위 내에 속하지 않음을 의미합니다. 데이터 수집 파이프라인이 이 오류를 어떻게 처리하느냐에 따라 데이터 손실이 발생할 수도 있고, 수집이 중단될 수도 있습니다.</p><p>거부된 문서를 처리하는 주요 메커니즘은 failure store입니다.</p><h3>failure store(Elasticsearch 9.1 이상에서 권장)</h3><p>Elasticsearch 9.1에서는 failure store를 도입하여 거부된 문서를 자동으로 수집합니다. Elasticsearch는 클라이언트에 오류를 반환하는 대신 실패한 문서를 데이터 스트림 내부의 전용 failure 인덱스에 기록합니다.</p><p>다음을 사용하여 오류를 검사할 수 있습니다:</p>GET metrics-myapp::failures/_search<p>failure store를 사용하면 수집 파이프라인이 거부 오류로 인해 막히는 것을 방지하면서, 실패한 데이터를 분석 또는 <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/reindex-tsds">재인덱싱</a>을 위해 보존할 수 있습니다.</p><h2>거부 관련 문제 모니터링</h2><p>늦게 도착하는 문제는 일반적으로 수집 이상 징후로 먼저 나타납니다. 다음과 같은 현상으로 먼저 감지될 수 있습니다.</p><ul><li><p>인덱싱 속도의 급격한 감소.</p></li><li><p>거부된 문서 수의 급증.</p></li><li><p>failure store 항목 수 증가.</p></li><li><p>파이프라인 입력과 출력 개수 간의 불일치.</p></li></ul><p>이러한 신호를 기반으로 한 알림을 통해 운영자는 파이프라인이 중단되기 전에 문제를 감지할 수 있습니다. 워크플로우, 머신 러닝 작업 등 다양한 메커니즘을 활용해 탐지 및 알림을 자동화할 수 있습니다.</p><h2>TSDS + ILM 마이그레이션 체크리스트</h2><p>TSDS로 메트릭 클러스터를 마이그레이션하거나, ILM 계층화를 도입하거나, 메트릭이 기본적으로 TSDS인 Elasticsearch 버전으로 업그레이드하는 경우, 먼저 다음 항목을 검토하세요.</p><h3><strong>1. 수집 지연 시간 측정</strong></h3><p>ILM 정책을 변경하기 전에 다음 사항을 확인하세요.</p><ul><li><p>정상적인 수집 지연 시간.</p></li><li><p>인시던트 상황에서의 최대 지연.</p></li><li><p>배치 파이프라인으로 인한 지연.</p></li></ul><p>ILM 설계는 현실적으로 발생할 수 있는 최대 지연을 반드시 고려해야 합니다.</p><h3><strong>2. 인덱스 시간 구간 확인</strong></h3><p>TSDS 백킹 인덱스를 확인합니다.</p>GET _data_stream/&lt;your-stream&gt;<p>다음을 확인하세요.</p><ul><li><p><code>time_series.start_time</code></p></li><li><p><code>time_series.end_time</code></p></li></ul><p>이러한 경계는 어떤 인덱스가 문서를 수용할 수 있는지를 결정합니다. 이러한 시간 구간을 이해하면 데이터가 어느 정도까지 지연될 수 있는지(거부되기 전까지)를 판단하는 데 도움이 됩니다.</p><h3><strong>3. 지연 데이터를 위한 hot 티어 크기 조정</strong></h3><p>백킹 인덱스는 지연 데이터를 처리할 수 있도록 충분한 기간 동안 쓰기 가능 상태를 유지해야 한다.</p><p>운영 규칙:</p><ul><li><p><code>warm_min_age &gt; rollover_max_age + maximum_expected_lateness</code></p></li></ul><p>메트릭이 6시간 늦게 도착할 수 있는 경우, 인덱스는 최소 6시간 동안 쓰기 가능 상태를 유지해야 한다는 점을 잊지 마세요.</p><h3><strong>4. 거부된 문서 처리 방식 결정</strong></h3><p>TSDS를 활성화하기 전에 전략을 선택하세요.</p><ul><li><p>failure store(Elasticsearch 9.1 이상에서 권장)</p></li><li><p>Logstash dead letter queue.</p></li><li><p>지연 데이터 처리를 위한 대체 인덱스.</p></li><li><p>제한적인 데이터 손실 허용.</p></li></ul><h3><strong>5. 데이터 수집 상태 모니터링</strong></h3><p>다음 항목에 대한 알림을 추가하세요.</p><ul><li><p>인덱싱 속도 하락.</p></li><li><p>거부된 문서.</p></li><li><p>failure store 증가</p></li><li><p>파이프라인 입력/출력 불일치.</p></li></ul><p>늦게 도착하는 데이터 문제는 종종 수집 이상 징후로 먼저 나타납니다.</p><h2>요약</h2><p>시계열 데이터 스트림은 메트릭 워크로드의 스토리지와 성능을 크게 개선하지만, 중요한 아키텍처 변경을 수반합니다. 백킹 인덱스는 시간 기반으로 제한되며, 이는 ILM의 동작 방식에 영향을 미칩니다.</p><p>TSDS를 사용하는 경우:</p><ul><li><p>인덱스는 지연 데이터를 수용할 수 있을 만큼 충분히 오랫동안 쓰기 가능 상태를 유지해야 합니다.</p></li><li><p>수집 파이프라인은 거부된 문서를 안정적으로 처리해야 합니다.</p></li></ul><p>기억해야 할 핵심 규칙은 다음과 같습니다:</p>warm_min_age &gt; rollover_max_age + maximum_expected_lateness<p>이 제약 조건을 중심으로 ILM 정책을 설계하면 TSDS는 메트릭 워크로드에 매우 효과적으로 작동합니다.</p><p>하지만 이를 무시하면 수집 파이프라인이 이러한 시간 경계를 직접 겪으며 문제를 발견하게 될 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/tsds-ilm-elasticsearch</guid>
    <category><![CDATA[인덱스 데이터]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Bret Wortman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcacf154aeacb29d8/6a17dc30dbb4ffc7ddfb557f/e4c46e4a6f746d9c845857e80de036f5d51cd4e7-1280x720.png" length="0" type="image/png"/>
    <pubDate>Thu, 02 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[LINQ to Elasticsearch ES|QL: C# 작성, Elasticsearch 쿼리]]></title>
    <description><![CDATA[Elasticsearch .NET 클라이언트의 새로운 LINQ to Elasticsearch ES|QL 제공자를 살펴보세요. 이 제공자를 사용하면 C# 코드를 작성하여 ES|QL 쿼리로 자동 변환할 수 있습니다.]]></description>
    <content:encoded><![CDATA[<p><strong>v9.3.4</strong> 및 <strong>v8.19.18</strong>부터 Elasticsearch .NET 클라이언트에는 런타임에 C# LINQ 표현식을 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/esql.html">Elasticsearch Query Language(ES|QL)</a> 쿼리로 변환하는 <a href="https://learn.microsoft.com/en-us/dotnet/csharp/linq/">Language Integrated Query (LINQ) </a>제공자가 포함되어 있습니다. ES|QL 스트링을 직접 작성하는 대신 <code>Where</code>, <code>Select</code>, <code>OrderBy</code>, <code>GroupBy</code> 및 기타 표준 연산자를 사용하여 쿼리를 작성합니다. 제공자는 변환, 매개변수화, 결과 역직렬화를 처리하며, 결과 세트 크기와 관계없이 메모리 사용량을 일정하게 유지하는 행별 스트리밍도 포함됩니다.</p><h2>첫 번째 쿼리</h2><p>먼저 Elasticsearch 인덱스에 맵핑되는 일반 CLR 객체(POCO)를 정의합니다. 속성 이름은 표준 <code>System.Text.Json</code> 속성(예: <code>[JsonPropertyName]</code>) 또는 구성된 <code>JsonNamingPolicy</code>을(를) 통해 ES|QL 열 이름으로 확인됩니다. 클라이언트의 나머지 부분에 적용되는 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization">소스 직렬화</a> 규칙이 여기에도 동일하게 적용됩니다.</p>using System.Text.Json.Serialization;

public class Product
{
    [JsonPropertyName("product_id")]
    public string Id { get; set; }

    public string Name { get; set; }

    public string Brand { get; set; }

    [JsonPropertyName("price_usd")]
    public double Price { get; set; }

    [JsonPropertyName("in_stock")]
    public bool InStock { get; set; }
}<p>유형이 지정되면 쿼리는 다음과 같습니다.</p>var minPrice = 100.0;
var brand = "TechCorp";

await foreach (var product in client.Esql.QueryAsync&lt;Product&gt;(q =&gt; q
    .From("products")
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10)))
{
    Console.WriteLine($"{product.Name}: ${product.Price}");
}<p>제공자는 이를 다음과 같은 ES|QL로 변환합니다.</p><p>참고할 만한 몇 가지 세부 사항은 다음과 같습니다.</p><ul><li><p><strong>속성 이름 확인:</strong> <code>p.Price</code>은(는) <code>[JsonPropertyName]</code> 속성 때문에 <code>price_usd</code>이(가) 되고, <code>p.Brand</code>은(는) 기본 camelCase 명명 정책에 따라 <code>brand</code>이(가) 됩니다.</p></li><li><p><strong>매개변수 캡처:</strong> C# 변수 <code>minPrice</code> 및 <code>brand</code> 은(는) 명명된 매개변수(<code>?minPrice</code>, <code>?brand</code>)(으)로 캡처됩니다. 이러한 변수는 JSON 페이로드에서 쿼리 스트링과 별도로 전송되므로 인젝션을 방지하고 서버 측 쿼리 계획 캐싱을 활성화합니다.</p></li><li><p><strong>스트리밍:</strong> <code>QueryAsync&lt;T&gt;</code> 은(는)<code>IAsyncEnumerable&lt;T&gt;</code>을(를) 반환합니다. 행은 Elasticsearch에서 도착하는 대로 한 번에 하나씩 구체화됩니다.</p></li></ul><p>다음과 같이 생성된 쿼리와 해당 매개변수를 실행하지 않고도 확인할 수 있습니다.</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Where(p =&gt; p.InStock &amp;&amp; p.Price &gt;= minPrice &amp;&amp; p.Brand == brand)
    .OrderByDescending(p =&gt; p.Price)
    .Take(10);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE (in_stock == true AND price_usd &gt;= 100) | SORT price_usd DESC | LIMIT 10

Console.WriteLine(query.ToEsqlString(inlineParameters: false));
// FROM products | WHERE (in_stock == true AND price_usd &gt;= ?minPrice AND brand == ?brand) | SORT price_usd DESC | LIMIT 10

var parameters = query.GetParameters();
// { "minPrice": 100.0, "brand": "TechCorp" }<h2>작동 원리: LINQ 핵심 개념 되짚어보기</h2><p>LINQ 제공자를 가능하게 하는 메커니즘은 <code>IEnumerable&lt;T&gt;</code>와(과) <code>IQueryable&lt;T&gt;</code>의 구분입니다.</p><p><code>IEnumerable&lt;T&gt;</code> 에 대해<code>.Where(p =&gt; p.Price &gt; 100)</code> 을(를) 호출하면 lambda는 런타임이 프로세스 중에 실행하는 일반 델리게이트인 <code>Func&lt;Product, bool&gt;</code> (으)로 컴파일됩니다. 이것이 LINQ-to-Objects입니다.</p><p><code>IQueryable&lt;T&gt;</code>에서 동일한 메서드를 호출하면 C# 컴파일러는 lambda를<code>Expression&lt;Func&lt;Product, bool&gt;&gt;</code> (으)로 래핑합니다. 이는 실행 가능한 형태가 아닌 코드의 <em>구조</em>를 나타내는 데이터 구조입니다. 표현식 트리는 런타임에 검사, 분석 및 다른 언어로 변환될 수 있습니다.</p>// IEnumerable: the lambda is a compiled delegate
IEnumerable&lt;Product&gt; local = products.Where(p =&gt; p.Price &gt; 100);

// IQueryable: the lambda is an expression tree, a data structure
IQueryable&lt;Product&gt; remote = queryable.Where(p =&gt; p.Price &gt; 100);<p><code>IQueryProvider</code> 인터페이스는 확장 지점입니다. 모든 제공자는 <code>CreateQuery&lt;T&gt;</code> 및 <code>Execute&lt;T&gt;</code>을(를) 구현하여 이러한 표현식 트리를 대상 언어로 변환할 수 있습니다. Entity Framework는 이를 사용하여 SQL을 출력합니다. LINQ to ES|QL 제공자는 이를 사용하여 ES|QL을 출력합니다.</p><p>위 쿼리의 표현식 트리는 다음과 같습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt521838e8b9c36649/6a1705b1839dfa5f40dcfdfe/f864cd18a390831f8d28503a29b5835efb1842f7-1000x720.png" alt="예제 쿼리의 표현식 트리입니다." /><p><em>예제 쿼리의 표현식 트리입니다.</em></p><p><code>Take</code> 은(는)<code>OrderByDescending</code> 을(를) 감싸고, 이는 <code>Where</code> 을(를) 감싸고, 이는 <code>From</code> 을(를) 감싸고, 이는 <code>EsqlQueryable&lt;Product&gt;</code> 상수를 감싸는 식으로 트리가 안쪽에서 바깥쪽으로 중첩됩니다. <code>Where</code> 술어는 그 자체로 <code>&amp;&amp;</code>, <code>&gt;=</code>, <code>==</code> 연산자에 대한 <code>BinaryExpression</code> 노드의 하위 트리이며, <code>MemberExpression</code> 은(는) 속성 액세스 및 <code>minPrice</code> 및 <code>brand</code> 변수에 대한 클로저 캡처를 위한 리프입니다. 이는 제공자가 최종 ES|QL을 생성하기 위해 거치는 데이터 구조입니다.</p><h2>작동 원리 살펴보기: 변환 파이프라인</h2><p>LINQ 표현식에서 쿼리 결과에 이르는 경로는 다음과 같이 6단계 파이프라인을 따릅니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt930670a505dd61ea/6a1705b3b339d58a54769ecf/2a2c772b63d720f61fc9a28b2f85668fa2db8d38-1999x1036.png" alt="변환 파이프라인 개요." /><p><em>변환 파이프라인 개요.</em></p><h3>1. 표현식 트리 캡처</h3><p><code>.Where()</code>, <code>.OrderBy()</code>, <code>.Take()</code> 및 기타 연산자를 <code>IQueryable&lt;T&gt;</code>에 연결하면 표준 LINQ 인프라가 표현식 트리를 구축합니다. <code>EsqlQueryable&lt;T&gt;</code>은(는) <code>IQueryable&lt;T&gt;</code>을(를) 구현하고 <code>EsqlQueryProvider</code>에 위임합니다.</p><h3>2. 번역</h3><p>쿼리가 실행될 때(열거, <code>ToList()</code> 호출 또는 <code>await foreach)</code> 사용), <code>EsqlExpressionVisitor</code>은(는) 표현식 트리를 안쪽에서 바깥쪽으로 탐색합니다. 각 LINQ 메서드 호출을 전문 방문자에게 전달합니다.</p><p>방문자</p><p>번역합니다</p><p>안으로</p><p>WhereClauseVisitor</p><p>.Where(predicate)</p><p>WHERE condition</p><p>SelectProjectionVisitor</p><p>.Select(선택기)</p><p>EVAL + KEEP + RENAME</p><p>GroupByVisitor</p><p>.GroupBy().Select()</p><p>STATS ... BY</p><p>OrderByVisitor</p><p>.OrderBy() / .ThenBy()</p><p>SORT 필드 [ASC\|DESC]</p><p>EsqlFunctionTranslator</p><p>EsqlFunctions.*, Math.*, 스트링 메서드</p><p>80개 이상의 ES|QL 함수</p><p>변환 과정에서 표현식에서 참조되는 C# 변수는 명명된 매개변수로 캡처됩니다.</p><h3>3. 쿼리 모델</h3><p>방문자는 스트링을 직접 생성하지 않습니다. 대신 <code>QueryCommand</code> 객체, 즉 불변의 중간 표현을 생성합니다. <code>FromCommand</code>, <code>WhereCommand</code>, <code>SortCommand</code>, <code>LimitCommand</code>은(는) 각각 하나의 ES|QL 처리 명령을 나타냅니다. 이들은 <code>EsqlQuery</code> 모델로 수집됩니다.</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt788c9936976f2f62/6a1705b50e2e4910da419ff0/2adc349b6cf655b96b7b3e826a134e8a17fe42fd-1999x1036.png" alt="쿼리 모델 및 명령 패턴입니다." /><p><em>쿼리 모델 및 명령 패턴입니다.</em></p><p>이 중간 모델은 표현식 트리 및 출력 형식에서 모두 분리되어 있습니다. 형식을 지정하기 전에 검사하거나, 가로채거나(<code>IEsqlQueryInterceptor</code>을(를) 통해), 수정할 수 있습니다.</p><h3>4. 형식 지정</h3><p><code>EsqlFormatter</code> 각 <code>QueryCommand</code>을(를) 순서대로 방문하여 최종 ES|QL 스트링을 생성합니다. 각 명령은 ES|QL이 처리 명령을 연결하는 데 사용하는 파이프(|) 연산자로 구분되어 한 줄로 표시됩니다. 특수 문자가 포함된 식별자는 백틱으로 자동 이스케이프 처리됩니다.</p><h3>5. 실행</h3><p>형식화된 ES|QL 스트링과 캡처된 매개변수는 JSON 페이로드로 Elasticsearch의 <code>/_query</code> 엔드포인트로 전송됩니다. <code>IEsqlQueryExecutor</code> 인터페이스는 계층형 패키지 아키텍처가 적용되는 전송 계층을 추상화합니다.</p><h3>6. 구체화</h3><p><code>EsqlResponseReader</code> 전체 결과 세트를 메모리에 버퍼링하지 않고 JSON 응답을 스트리밍합니다. 쿼리당 한 번씩 미리 계산되는 <code>ColumnLayout</code> 트리는 플랫 ES|QL 열 이름(예: <code>address.street</code>, <code>address.city</code>)을(를) 중첩된 POCO 속성에 맵핑합니다. 각 행은 <code>T</code> 인스턴스로 조립되어 <code>IEnumerable&lt;T&gt;</code> 또는 <code>IAsyncEnumerable&lt;T&gt;</code>을(를) 통해 한 번에 하나씩 제공됩니다.</p><h2>계층 아키텍처</h2><p>LINQ to ES|QL 기능은 다음과 같이 세 개의 패키지로 나뉩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt662bd0dd8861b6b6/6a1705b7a929cf7086ae08a2/41b8aae860ecdc2480edcb1c1d4cc9b03cfb78c9-1999x1036.png" alt="패키지 아키텍처." /><p><em>패키지 아키텍처.</em>
<a href="https://www.nuget.org/packages/Elastic.Esql"><strong><code>Elastic.Esql</code></strong></a> 은(는) 순수 변환 엔진입니다. HTTP 종속성이 전혀 없으며 표현식 방문자, 쿼리 모델, 포맷터 및 응답 판독기를 포함합니다. Elasticsearch 연결 없이 독립적으로 사용하여 ES|QL 쿼리를 구축하고 검사할 수 있어 테스트, 쿼리 로깅 또는 자체 실행 계층을 구축하는 데 유용합니다.</p>// Translation-only: no Elasticsearch connection needed
var provider = new EsqlQueryProvider();
var query = new EsqlQueryable&lt;Product&gt;(provider)
    .From("products")
    .Where(p =&gt; p.InStock)
    .OrderByDescending(p =&gt; p.Price);

Console.WriteLine(query.ToEsqlString());
// FROM products | WHERE in_stock == true | SORT price_usd DESC<p><a href="https://www.nuget.org/packages/Elastic.Clients.Esql"><strong><code>Elastic.Clients.Esql</code></strong></a> 경량 독립형 ES|QL 클라이언트입니다. <code>Elastic.Transport</code>을(를) 통해 <code>Elastic.Esql</code> 위에 HTTP 실행 기능을 추가합니다. 애플리케이션에 ES|QL만 필요하고 다른 Elasticsearch API는 필요하지 않은 경우, 이것이 최소한의 종속성 옵션입니다.</p><p><a href="https://www.nuget.org/packages/Elastic.Clients.Elasticsearch"><strong><code>Elastic.Clients.Elasticsearch</code></strong></a> 완전한 Elasticsearch .NET 클라이언트입니다. 이 또한 <code>Elastic.Esql</code>을(를) 기반으로 하며 <code>client.Esql</code> 네임스페이스를 통해 LINQ 제공자를 노출합니다. 대부분의 애플리케이션에 권장되는 진입점입니다.</p><p>두 실행 계층 패키지 모두 변환과 전송을 연결하는 전략 인터페이스인 <code>IEsqlQueryExecutor</code>의 자체 구현을 제공합니다.</p><p>세 패키지 모두 소스에서 생성된 <code>JsonSerializerContext</code>와(과) 함께 사용할 경우 Native AOT와 호환됩니다. 전체 클라이언트에 대한 자세한 내용은 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/source-serialization#native-aot">Native AOT 설명서</a>를 확인하세요.</p><h2>기본을 넘어서</h2><p>위의 예에서는 필터링, 정렬 및 페이지 매김을 다뤘습니다. 공급자는 더 광범위한 작업을 지원합니다.</p><h3>집계</h3><p><code>GroupBy</code><code>Select</code>의 집계 함수와 결합하면 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/stats-by"><code>STATS ... BY</code></a>(으)로 변환됩니다.</p>var stats = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .GroupBy(p =&gt; p.Brand)
    .Select(g =&gt; new
    {
        Brand = g.Key,
        Count = g.Count(),
        AvgPrice = g.Average(p =&gt; p.Price),
        MaxPrice = g.Max(p =&gt; p.Price)
    }));

// -&gt; FROM products | STATS COUNT(*), AVG(price_usd), MAX(price_usd) BY brand<h3>프로젝션</h3><p><code>Select</code>익명 유형은 <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/eval"><code>EVAL</code></a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/keep"><code>KEEP</code></a>, <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/rename"><code>RENAME</code></a> 명령을 생성합니다.</p>var query = client.Esql.CreateQuery&lt;Product&gt;()
    .Select(p =&gt; new { ProductName = p.Name, p.Price, p.InStock });

// -&gt; FROM products | KEEP name, price_usd, in_stock | RENAME name AS ProductName<h3>풍부한 함수 라이브러리</h3><p><code>EsqlFunctions</code> 클래스를 통해 날짜/시간, 스트링, 수학, IP, 패턴 매칭 및 스코어링을 포함한 80개 이상의 ES|QL 함수를 사용할 수 있습니다. 다음과 같이 표준 <code>Math.*</code> 및 <code>string.*</code> 메서드도 변환됩니다.</p>.Where(p =&gt; p.Name.Contains("Pro"))       // -&gt; WHERE name LIKE "*Pro*"
.Where(p =&gt; EsqlFunctions.CidrMatch(      // -&gt; WHERE CIDR_MATCH(ip, "10.0.0.0/8")
    p.IpAddress, "10.0.0.0/8"))<h3>조회 조인</h3><p>교차 인덱스 조회는 ES|QL <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/lookup-join"><code>LOOKUP JOIN</code></a>(으)로 변환됩니다.</p>var enriched = client.Esql.Query&lt;Product, object&gt;(q =&gt; q
    .LookupJoin&lt;Product, CategoryLookup, string, object&gt;(
        "category-lookup-index",
        product =&gt; product.Id,
        category =&gt; category.CategoryId,
        (product, category) =&gt; new { product.Name, category!.CategoryLabel }));<h3>원시 ES|QL 이스케이프 해치</h3><p>LINQ 제공자가 아직 지원하지 않는 ES|QL 기능의 경우 다음과 같이 원시 조각을 추가할 수 있습니다.</p>var results = client.Esql.Query&lt;Product&gt;(q =&gt; q
    .Where(p =&gt; p.InStock)
    .RawEsql("| EVAL discounted = price_usd * 0.9"));<h3>서버 측 비동기 쿼리</h3><p>장기 실행 쿼리의 경우 다음과 같이 서버에서 백그라운드 처리를 위해 제출하세요.</p>await using var asyncQuery = await client.Esql.SubmitAsyncQueryAsync&lt;Product&gt;(
    q =&gt; q.Where(p =&gt; p.InStock),
    asyncQueryOptions: new EsqlAsyncQueryOptions
    {
        WaitForCompletionTimeout = TimeSpan.FromSeconds(5),
        KeepAlive = TimeSpan.FromMinutes(10)
    });

await asyncQuery.WaitForCompletionAsync();
await foreach (var product in asyncQuery.AsAsyncEnumerable())
    Console.WriteLine(product.Name);<p>서버 측 비동기 쿼리는 일반적인 시간 초과 임계값을 초과할 수 있는 장기 실행 분석 쿼리/대규모 데이터 세트 처리에 특히 유용합니다. 또한 엄격한 HTTP 시간 초과를 적용하는 로드 밸런서, API 게이트웨이 또는 프록시가 있는 시간 초과에 민감한 환경에서도 유용합니다. 비동기 쿼리는 제출과 결과 검색을 분리하여 연결 끊김을 방지합니다.</p><h2>시작하기</h2><p>LINQ to ES|QL은 다음 버전부터 사용 가능합니다.</p><ul><li><p><strong>Elastic.Clients.Elasticsearch v9.3.4</strong> (9.x 브랜치)</p></li><li><p><strong>Elastic.Clients.Elasticsearch v8.19.18</strong> (8.x 브랜치)</p></li></ul><p>NuGet에서 설치:</p><p><code>dotnet add package Elastic.Clients.Elasticsearch</code></p><p>진입점은 <code>client.Esql</code> 에 있습니다.</p><p>메서드</p><p>반환</p><p>사용 사례</p><p>Query&lt;T&gt;(...)</p><p>IEnumerable&lt;T&gt;</p><p>동기식 실행</p><p>QueryAsync&lt;T&gt;(...)</p><p>IAsyncEnumerable&lt;T&gt;</p><p>비동기 스트리밍</p><p>CreateQuery&lt;T&gt;()</p><p>IEsqlQueryable&lt;T&gt;</p><p>고급 구성 및 검사</p><p>SubmitAsyncQueryAsync&lt;T&gt;(...)</p><p>EsqlAsyncQuery&lt;T&gt;</p><p>장기 실행 서버 측 쿼리</p><p>쿼리 옵션, 다중 필드 액세스, 중첩 객체, 다중 값 필드 처리 등 전체 기능에 대한 참조는 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/dotnet/linq-to-esql">LINQ to ES|QL 설명서</a>를 확인하세요.</p><h2>결론</h2><p>LINQ to ES|QL은 C# LINQ의 모든 표현력을 Elasticsearch의 ES|QL 쿼리 언어로 제공하여 쿼리 스트링을 직접 작성하지 않고도 강력한 형식의 조합 가능한 쿼리를 작성할 수 있도록 해줍니다. 자동 매개변수 캡처, 스트리밍 구체화, 그리고 독립 실행형 변환부터 전체 Elasticsearch 클라이언트까지 확장 가능한 계층형 패키지 아키텍처를 통해 모든 규모의 .NET 애플리케이션에 자연스럽게 통합됩니다. 최신 클라이언트를 설치하고 LINQ 표현식에서 인덱스를 지정하기만 하면 나머지는 제공자가 처리합니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/linq-esql-c-elasticsearch-net-client</guid>
    <category><![CDATA[ES|QL]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Florian Bernd,Martijn Laarman]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfa35fbcbbf4959f/6a1705b9dc55de19a4e00d07/e54132e915217063e9ed0ec45059c6cfc38e31dd-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[빠른 속도 대 정확도: 양자화된 벡터 검색의 리콜 측정하기]]></title>
    <description><![CDATA[최소한의 설정으로 Elasticsearch에서 벡터 검색의 리콜을 측정하는 방법을 설명합니다.]]></description>
    <content:encoded><![CDATA[<p>모두가 벡터 검색이 즉각적으로 이루어지기를 원합니다. 하지만 고차원 벡터는 무겁습니다. 단일 1,024차원 float-32 벡터는 상당한 메모리를 차지하며, 이를 수백만 개의 다른 벡터와 비교하는 것은 계산상 비용이 많이 듭니다.</p><p>이를 해결하기 위해 Elasticsearch와 같은 검색 엔진은 두 가지 주요 최적화 전략을 사용합니다.</p><ol><li><p><strong>근사 검색(계층적 탐색 가능 스몰 월드 [HNSW]):</strong> 모든 문서를 스캔하는 대신, 답이 있을 가능성이 높은 근처로 빠르게 이동할 수 있는 탐색 그래프를 구축합니다.</p></li><li><p><strong>양자화:</strong> 메모리 사용량을 줄이고 계산 속도를 높이기 위해 벡터를 압축합니다(예: 32비트 부동소수점에서 8비트 정수 또는 1비트 이진 값으로).</p></li></ol><p>하지만 최적화에는 <strong>정확성</strong>이라는 대가가 따르는 경우가 많습니다.</p><p>"만약 데이터를 압축하고 검색 중에 지름길을 택한다면 최상의 결과를 놓치게 될까요?" 혹은 "이러한 최적화가 검색 엔진의 관련성을 저하시킬까요?"라는 두려움이 당연히 생길 수 있습니다.</p><p>Elastic의 정량화가 결과를 저하시키지 않는다는 것을 증명하기 위해, <a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"><strong>DBPedia-14</strong></a><a href="https://huggingface.co/datasets/fancyzhx/dbpedia_14"> 데이터 세트</a>를 사용해 반복 가능한 테스트 하네스를 구축하여 Elasticsearch에서 기본 최적화를 사용할 때 속도와 얼마나 많은 정확도(특히, <strong>리콜</strong>)를 교환하는지 정확히 계산했습니다.</p><p>tldr: 생각보다 훨씬 적을 가능성이 높습니다. <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">여기에서 노트북</a>을 확인하고 직접 사용해 보세요.</p><h2><strong>정의(비전문가를 위한)</strong></h2><p>코드를 살펴보기 전에 먼저 몇 가지 용어를 정리해 보겠습니다.</p><ul><li><p><strong>정확도 대 리콜:</strong> <strong>정확도</strong>는 주관적입니다(좋은 것을 찾았는가?). <strong>리콜</strong>은 수학적입니다. 데이터베이스에 검색어와 수학적으로 <em>완벽하게</em> 일치하는 문서가 10개 있고 검색 엔진이 그 중 9개를 찾았다면 리콜 확률은 90%(또는 0.9)입니다.</p></li><li><p><strong>정확한 검색(평면 검색):</strong> 때때로 "무차별 대입" 방법이라고도 합니다. 검색 엔진은 색인에 있는 모든 문서를 스캔하여 거리를 계산합니다.</p><ul><li><p><em>장점:</em> 리콜이 100% 완벽합니다.</p></li><li><p><em>단점:</em> 계산 비용이 높고 규모가 커질수록 느려집니다.</p></li></ul></li><li><p><strong>근사 검색(HNSW):</strong> "지름길" 방식입니다. 검색 엔진이 <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">HNSW</a> 그래프를 구축합니다. 그래프를 탐색하여 최근접 이웃을 찾습니다.</p><ul><li><p><em>장점:</em> 매우 빠르고 확장 가능합니다.</p></li><li><p><em>단점:</em> 그래프 탐색이 너무 일찍 중단되면 이웃을 놓칠 가능성이 있습니다.</p></li></ul></li></ul><h2><strong>실험: 정확한 값과 근사치 비교</strong></h2><p>리콜을 테스트하기 위해 텍스트 분류 모델을 학습하고 평가하는 데 일반적으로 사용되는 14개 온톨로지 클래스에 걸친 제목과 초록으로 구성된 대규모 데이터 세트인 <strong>DBPedia-14</strong> 데이터 세트를 사용했습니다. 특히 "영화" 카테고리에 초점을 맞출 것입니다. 최적화된 프로덕션 설정을 수학적으로 완벽한 기준 데이터와 비교하고 싶었습니다.</p><p>이 실험에서는 <a href="https://www.elastic.co/search-labs/blog/jina-embeddings-v5-text">jina-embeddings-v5-text-small</a> 모델을 사용하고 있습니다. 이 모델은 텍스트 표현에 대한 업계 벤치마크를 선도하는 최첨단 다국어 모델입니다. 이 모델이 고성능 임베딩의 현재 표준을 정의하기 때문에 이 모델을 선택했습니다. Jina v5의 탁월한 정확도와 Elasticsearch의 네이티브 양자화 기능을 결합함으로써, 계산 효율성이 뛰어나면서도 검색 품질을 저하시키지 않는 검색 아키텍처를 구현할 수 있음을 보여줍니다.</p><p>이중 매핑으로 색인을 설정했습니다. 동일한 텍스트를 두 개의 서로 다른 필드에 동시에 수집했습니다.</p><ol><li><p><strong><code>content.raw</code></strong>유형: <code>flat</code>. 이로 인해 Elasticsearch는 전체 Float32 벡터에 대해 무차별 대입 스캔을 수행하게 됩니다. 이렇게 하면 정확한 일치 결과가 반환되며 기준선으로 사용됩니다.</p></li><li><p><strong><code>content</code></strong>유형 <code>semantic_text</code>. 기본값으로 HNSW + 더 나은 이진 양자화(BBQ)를 사용합니다. 이것이 근사 일치를 위한 표준적이고 최적화된 생산 설정입니다.</p></li></ol><h3><strong>Recall@10 테스트</strong></h3><p>메트릭으로는 Recall@10을 사용했습니다.</p><p>무작위로 영화 50편을 골라 두 필드에 대해 동일한 쿼리를 실행했습니다.</p><ul><li><p><strong>정확한 (평면)</strong> 검색에서 상위 10개 이웃의 ID가 [1, 2, 3... 10]인 경우.</p></li><li><p>그리고 <strong>대략적인 (HNSW)</strong> 검색은 ID [1, 2, 3... 9, 99]를 반환합니다.</p></li><li><p>상위 10개 중 9개를 정확히 찾아냈습니다. 점수는 <strong>0.9점</strong>입니다.</p></li></ul><p>다음은 사용한 매핑입니다.</p># The "Control Group": Forces exact brute-force scan
"raw": {
    "type": "semantic_text",
    "inference_id": ".jina-embeddings-v5-text-small",
    "index_options": {
        "dense_vector": {
            "type": "flat"
        }
    }
}<p><strong>결과: 성공의 "플랫 라인"</strong></p><p>전체 데이터 세트를 다시 로드하고 1,000~40,000개의 문서 색인 크기에 대해 테스트하는 확장성 테스트를 실행했습니다.</p><p>리콜 점수에 대한 자세한 내용은 다음과 같습니다.</p><p>문서</p><p>Recall@10 점수</p><p>1,000</p><p>1.000(100%)</p><p>5,000</p><p>0.998 (100%)</p><p>10,000</p><p>0.992 (99.4%)</p><p>20,000</p><p>0.999 (99.0%)</p><p>40,000</p><p>0.992 (98.8%)</p><p>결과는 놀라울 정도로 안정적이었습니다. 규모를 확장했음에도 불구하고, 근사 검색은 무차별 대입 방식의 정확한 검색과 <strong>99%를 넘는 일치율</strong>을 보였습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8168a0a4946bade7/6a170e154a531b61b536a9eb/a4bfacb1d0cce6fdf6df0e1a9d4fc5d4007a66da-1999x1209.png" alt="벡터 검색 안정성: 리콜 대 색인 크기" /><h2><strong>왜 그렇게 잘 작동했을까요?</strong></h2><p>벡터를 이진 값으로 압축하면 정확도가 더 많이 저하될 것으로 예상할 수 있습니다. 그렇게 되지 않는 이유는 Elasticsearch가 데이터 검색을 처리하는 방식에 있습니다.</p><p>오늘날 대부분의 임베딩 모델은 크기가 큰 Float32 벡터를 출력합니다. 검색 효율성을 높이기 위해 Elasticsearch는 고차원 벡터에 양자화를 사용합니다. 특히, 9.2 이후로는 기본적으로 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-9-1-bbq-acorn-vector-search">BBQ</a>를 사용합니다.</p><p>BBQ는 <strong>리스코어</strong> 메커니즘을 사용합니다.</p><ol><li><p><strong>트래버설:</strong> 검색 엔진은 압축(양자화된) 벡터를 사용하여 HNSW 그래프를 빠르게 탐색합니다. 벡터가 작기 때문에 성능 저하 없이 효율적으로 오버샘플링하여 더 많은 후보 목록(예: 거의 비슷한 상위 100개 문서)을 수집할 수 있습니다.</p></li><li><p><strong>리스코어:</strong> 후보 문서가 있으면 해당 문서에 대한 전체 정밀도 값을 검색하여 최종적이고 정확한 순위를 계산합니다.</p></li></ol><p>이는 무거운 작업을 위한 양자화의 속도와 최종 정렬을 위한 부동 소수점의 정밀도, 이 두 가지 장점을 모두 제공합니다.</p><h2><strong>더 나은 성과를 낼 수 있을까요?</strong></h2><p>여기에 표시된 결과는 기본 설정과 무작위 데이터 샘플링을 사용한 결과라는 점에 주목할 필요가 있습니다. 이걸 고성능 출발점이라고 생각해 보세요. Jina v5는 강력한 성능을 자랑하지만, 이러한 리콜 점수가 모든 데이터 세트에 대해 "만능 해결책"을 보장하는 것은 아닙니다. 모든 데이터 수집에는 고유한 특성이 있으며, 더 많은 성능을 끌어내기 위해 더 조정할 수는 있지만, 항상 자신의 특정 데이터와 벤치마킹하여 한계가 어디인지 확인해야 합니다.</p><h2><strong>결론</strong></h2><p>이것은 매우 작은 규모의 테스트입니다. 하지만 이 연습의 요점은 임베딩 모델이나 BBQ를 구체적으로 측정하는 것이 아니라, 최소한의 설정으로 데이터 세트의 리콜을 쉽게 측정하는 방법을 보여주기 위한 것입니다.</p><p>자신의 데이터로 이 테스트를 실행하려면 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/fast_vs_accurate_measuring_the_recall_of_quantized_vector_search/vector_recall_notebook.ipynb">여기에서 노트북을 확인</a>하여 직접 시도해 보세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/recall-vector-search-quantization</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Jeff Vestal]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt198c7085db96aa04/6a170e17cdacbfe88c7d2a86/09f03b9239d66c36763cdab3fafcdac207ff6d83-1280x720.png" length="0" type="image/png"/>
    <pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch의 HNSW를 위한 적응형 조기 종료]]></title>
    <description><![CDATA[Elasticsearch의 HNSW를 위한 새로운 적응형 조기 종료 전략을 소개합니다.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch는 근접 그래프상에서 벡터 검색을 수행하기 위해 <a href="https://www.elastic.co/search-labs/blog/hnsw-graph">계층적으로 탐색 가능한 작은 세계</a>(HNSW) 알고리즘을 사용합니다. HNSW는 k-최근접 이웃(KNN) 결과의 품질과 그에 수반되는 비용 사이에서 훌륭한 절충안을 제공하는 것으로 알려져 있습니다.</p><p>HNSW에서 검색은 그래프 내의 후보 노드들을 반복적으로 확장하며, 현재까지 발견된 최근접 이웃의 제한된 집합을 유지하는 방식으로 진행됩니다. 각 확장 단계마다 비용(벡터 연산, 디스크 임의 탐색 등)이 발생하며, 검색이 진행될수록 그 비용 대비 얻게 되는 한계 이익은 점차 감소하는 경향이 있습니다.</p><p>HNSW 그래프 탐색을 최적화하는 한 가지 방법은, 새로운 진짜 이웃을 찾을 한계 확률이 더 이상 증가하지 않을 때 탐색을 중단하는 것입니다. 이러한 이유로, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">Elasticsearch 9.2</a>에서는 새로운 <a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">조기 종료 메커니즘</a>을 도입했습니다. 이 방식은 그래프 노드를 방문해도 새로운 최근접 이웃을 충분히 찾지 못하는 상태가 고정된 횟수만큼 연속으로 발생하면 검색 프로세스를 중단합니다.</p><p>이 글은 다양한 데이터 세트와 데이터 분포에 더 잘 대응할 수 있도록, HNSW의 조기 종료 메커니즘을 개선한 방법을 안내합니다.</p><h2><strong>HNSW의 조기 종료</strong></h2><p>HNSW에서 검색은 근접 그래프 내의 후보 노드들을 반복적으로 확장하며, 현재까지 발견된 근접 이웃의 제한된 집합을 유지하는 방식으로 진행됩니다. 이는 그래프 전체를 방문하거나 특정 조기 종료 기준을 충족할 때까지 계속됩니다.</p><p>따라서 조기 종료는 반드시 항상 최적화를 위한 선택 사항인 것만은 아닙니다. <strong>검색 알고리즘 그 자체의 일부입니다</strong>. 탐색을 중단하기로 결정하는 그 순간이 효율성과 재현율 사이의 균형을 결정합니다. Elasticsearch에는 이미 HNSW 쿼리가 조기 종료될 수 있는 몇 가지 방법이 존재합니다.</p><ul><li><p>방문하는 노드의 최대 개수는 정해져 있습니다.</p></li><li><p>정해진 제한 시간에 도달했습니다.</p></li></ul><p>이러한 규칙들은 단순하고 예측 가능하지만, <strong>검색이 실제로 어떻게 진행되고 있는지에 대해서는 대체로 무관심합니다</strong>. 또한, 이러한 규칙들은 주로 최종 사용자에게 적절한 시간 내에 쿼리가 완료되도록 하기 위한 용도로 사용됩니다.</p><p><a href="https://www.elastic.co/search-labs/blog/hnsw-knn-search-early-termination">이전 블로그 게시물</a>에서 HNSW의 중복성이라는 개념을 소개한 바 있습니다. 요약하자면, HNSW가 새로운 최근접 이웃을 찾아내지 못하는 새로운 후보 노드들을 계속해서 평가할 때 중복 계산이 발생합니다.</p><h2><strong>인내심: 노력이 아닌 진척도를 측정하기</strong></h2><p><em>인내심</em>이라는 개념은 조기 종료의 기준을 <strong>노력이 아닌 진전</strong>을 중심으로 재정의합니다.</p><p>다음과 같은 질문을 던지는 대신:</p><p>“우리가 몇 걸음을 걸었습니까?”</p><p>새로운 질문은 다음과 같습니다:</p><p>“더 나은 결과를 찾을 가망이 없다고 판단하기까지, 얼마만큼의 연산 낭비를 감수할 수 있을까요?"</p><p>HNSW 검색 과정에서, 초기 탐색은 일반적으로 top-k 후보 집단에 대해 가장 비약적인 개선을 만들어냅니다. HNSW 그래프 탐색의 첫 단계에서는, 알고리즘이 쿼리 벡터에 점점 더 가까운 이웃들을 계속해서 발견함에 따라 이웃 집합이 계속해서 업데이트됩니다. 시간이 흐름에 따라, 검색이 수렴하면서 이러한 개선은 점차 드물어집니다. <a href="https://cs.uwaterloo.ca/~jimmylin/publications/Teofili_Lin_ECIR2025.pdf">인내심 기반 종료</a>는 이러한 패턴을 모니터링하며, 개선이 일정 기간 지속적으로 발생하지 않으면 검색을 종료합니다.</p><p>실제로 HNSW 그래프를 방문하는 동안, 후보 노드들을 거쳐 가며 큐 포화도를 함께 계산합니다. 이는 가장 최근의 그래프 노드를 방문하는 동안 변경되지 않고 그대로 남은 근접 이웃의 비율(또는 마지막 반복 과정에서 새로 추가된 이웃 수의 역수)을 측정합니다. 이러한 비율이 너무 많은 연속된 반복 과정 동안 과도하게 높아지면, 더 이상의 그래프 방문을 중단합니다</p><p>개념적으로 볼 때, 인내심은 HNSW 검색을 <strong>수익 체감의 과정</strong>으로 취급합니다. 수익이 정체되는 시점에 도달하면, 그래프를 계속해서 탐색하는 것은 실질적인 이익을 거의 주지 못합니다.</p><p>이러한 프레이밍은 강력합니다. 종료 시점을 임의로 정해진 고정된 한계치가 아니라, <em>관찰 가능한 결과</em>에 직접 연결하기 때문입니다.</p><p>이러한 스마트 조기 종료 기술을 사용하면, 거의 완벽한 상대적 재현율을 유지하면서도 HNSW 그래프 탐색 과정에서 방문하는 노드 수를 줄일 수 있다는 장점이 있습니다.</p><p>이를 시각화하기 위해, 몇 가지 데이터 세트인 FinancialQA, Quora과 모델인 JinaV3, E5-small 조합을 대상으로 인내심 기반 조기 종료(<em><code>et=static</code></em>와 HNSW 기본 작동 방식(<em><code>et=no</code></em>)을 비교하여, 방문한 노드 수에 따른 재현율의 변화량을 그래프로 그려볼 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfd0d692b9beb476a/6a170ef4dc55debf0be00e97/a9d07c5153ea64a2426c82487c36846030692bb9-1600x945.png" alt="HNSW를 위한 적응형 조기 종료 " /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93509c251a1b641e/6a170ef6dc55dea2b3e00e9b/dac56125c4b16d1b596c9876b6ca9ac7b2dc87fa-1600x944.png" alt="HNSW es를 위한 적응형 조기 종료" /><h2><strong>정적 임계값 및 HNSW 동역학</strong></h2><p>실제로, Elasticsearch는 이를 <strong>정적 임계값</strong>을 사용하여 구현했습니다. 하나의 임계값은 <strong>포화 임계값</strong>입니다. 이는 우리가 차선이라고 간주하는 포화 비율을 의미합니다. 또 다른 임계값은 <strong>인내심 임계값</strong>입니다. 이는 큐 포화도가 여전히 낮은 상태임에도, 방문을 계속 허용할 연속적인 그래프 노드 방문 횟수를 의미합니다.</p><p>Elasticsearch 9.2에 이 조기 종료 전략을 도입할 당시, 지연 시간과 메모리 소모 측면에서 이득을 보면서도 재현율은 최대한 유지할 수 있도록 보수적인 기본값을 선택하기로 결정했습니다. 이러한 이유로, 포화 임계값을 100%로 설정하고, 인내심 임계값은 KNN 쿼리에서 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-top-level-parameters:~:text=search%20request%20size.-,num_candidates,-(Optional%2C%20integer)%20The"><em><code>num_candidates</code></em></a>의 30% 수준(상한선 제한 있음)으로 설정했습니다.</p><p>많은 시나리오에서 이러한 설정들이 잘 작동하기도 했지만, 동일한 수의 이웃을 요청하는 두 쿼리라도 그 수렴 동작은 근본적으로 다를 수 있습니다. 어떤 쿼리는 밀집된 지역 이웃을 만나 빠르게 포화 상태에 도달하는 반면, 다른 쿼리는 경쟁력 있는 후보군을 찾기까지 길고 희소한 경로를 통과해야만 합니다. 후자가 효과적으로 처리하기 가장 어려운 것으로 나타났습니다.</p><p>그 결과, 때때로 다음과 같은 현상들이 관찰되었습니다:</p><ul><li><p>쉬운 쿼리에 대한 과도한 탐색.</p></li><li><p>까다로운 쿼리에 대한 조기 종료.</p></li></ul><p>따라서 고정된 임계값이 수렴에 대한 일괄적인 가정을 전제로 하는 반면, HNSW가 다양한 동역학에 더 잘 적응하도록 만들 수 있다는 점을 깨달았습니다.</p><h2><strong>HNSW 조기 종료 적응형 구현</strong></h2><p>적응형 조기 종료는 이 문제에 대해 기존과는 다른 각도에서 접근합니다. 미리 정의된 중단 임계값을 강제하는 대신, 알고리즘은 <strong>검색 역학 자체로부터 언제 멈춰야 할지를 추론합니다</strong>.</p><p>따라서 연속된 두 후보 사이의 큐 포화율을 비교하는 대신, 그래프 방문 중의 (<a href="https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm">웰포드 알고리즘을 사용한</a>) 이동 평균  및 표준 편차 와(과) 함께, 순간 평활 발견율 (마지막 방문 <em>i</em>에서 쿼리 <em>q</em>에 대해 도입된 새로운 이웃의 수)를 도입하기로 결정했습니다. 발견율에 관한 이러한 통계치들은 각 쿼리당 개별적으로 계산됩니다. 이 정보를 사용하여 각 쿼리의 특성에 맞춰 서로 다른 수준의 인내심을 적용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbfb1e123f1b026d/6a170ef7cf4f25d9bab2d216/1958be7ca4425ade66eaf621ada3533173183598-694x118.png" alt="" /><p>이전의 정적 임계값들은 이제 발견율 통계에 따라 적응형으로 변합니다. 포화 임계값은 이동 평균에 표준 편차를 더한 값이 되며, 인내심 수치는 표준 편차에 반비례하여 적응하고 확장되도록 만들었습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d4d91f464a9dc9d/6a170ef8d7c0223420de656a/f7ee4a55c24853b657df26052b275e8bd76cf0f9-654x156.png" alt="" /><p>조기 종료 규칙은 기존과 동일하게 유지됩니다. 즉, 순간 발견율이 적응형 포화 임계값보다 낮아질 때 포화가 발생합니다. 포화 상태가 적응형 인내심보다 더 많은 횟수의 연속적인 후보 방문 동안 지속되면, 그래프 방문이 중단됩니다</p><p>이러한 방식을 통해, KNN 쿼리의 <em><code>num_candidates</code></em> 매개변수(조기 종료 여부와 상관없이 항상 설정되어 있거나 기본값으로 남겨지는 값)에 의존하지 않으면서도, 각 쿼리와 벡터 분포에 맞춰 동적으로 더 잘 적응하는 동작을 구현할 수 있습니다.</p><p>FinancialQA 및 Quora에서 적응형 전략(<em><code>et=adaptive</code></em>)의 방문 노드당 재현율은 정적 전략(<em><code>et=static</code></em>) 및 기본 HNSW 동작(<em><code>et=no</code></em>)과 비교했을 때 더 높은 수치를 나타냅니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteab7ba53ae14da0e/6a170ef9961e69e072c4cfd5/2a906997d9a25d74c7038bd9661bc97581e7258e-1600x938.png" alt=" 적응형 전략 및 기본 HNSW 동작" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6fb9e672d3698200/6a170efb67045b7b2b45c2ab/3a114911e232c351dbb814cea20e8b0f1415a717-1600x925.png" alt="" /><p>적응형 조기 종료는 Elasticsearch 9.3부터 HNSW 밀집 벡터 필드에 기본으로 활성화되며, <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/index-modules#index-dense-vector-hnsw-early-termination">동일한 인덱스 수준 설정</a>을 통해 나중에 비활성화할 수도 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-elasticsearch-adaptive-early-termination</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <dc:creator><![CDATA[Tommaso Teofili]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt27b746cc1995e6b7/6a170efda29299de8ad010c6/e6d3186f609dd56dc5ffe33d70fa9e5cfa05b51f-1280x720.png" length="0" type="image/png"/>
    <pubDate>Mon, 02 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenSearch보다 최대 8배 빠른 Elasticsearch 벡터 검색]]></title>
    <description><![CDATA[OpenSearch와 Elasticsearch의 필터링된 벡터 검색 벤치마크를 비교하고, 컨텍스트 엔지니어링 시스템에서 벡터 검색 성능이 중요한 이유를 살펴봅니다.]]></description>
    <content:encoded><![CDATA[<h2>AI 에이전트와 컨텍스트 엔지니어링에서 검색 속도가 중요한 이유</h2><p>문서 코퍼스 2,000만 개에 대한 벤치마크에서 Elasticsearch는 필터링된 벡터 검색에서 OpenSearch 대비 최대 8배 높은 처리량을 제공하는 동시에, 테스트한 모든 구성에서 더 높은 Recall@100을 달성했습니다. 컨텍스트 엔지니어링에는 빠른 벡터 검색 이상이 필요합니다. 워크플로우가 반복되면서 하이브리드 검색 및 필터링과 같은 강력한 관련성 제어, 운영 간소화, 예측 가능한 성능이 필요합니다. 그러나 에이전트가 요청당 검색, 추론, 검색 루프를 여러 번 실행하는 경우가 많기 때문에 검색 지연 시간이 배가되므로, 이 문제를 개선하면 엔드투엔드 응답성 향상과 비용 절감 효과를 누릴 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9daec868eed84658/6a170bef6234e0c322db1a19/d5a52a07773f0942c2baa732dacfe782aac0f415-1600x683.png" alt="OpenSearch와 Elasticsearch 비교: 필터링된 벡터 검색 벤치마크 처리량" /><p>컨텍스트 엔지니어링에서 검색은 일회성 단계가 아닙니다. 에이전트와 애플리케이션은 검색 → 추론 → 검색과 같은 루프를 반복적으로 실행해 쿼리를 정제하고, 사실을 검증하며, 근거 있는 컨텍스트를 조립하고, 작업을 완료합니다. 이 패턴은 에이전트 워크플로우와 반복 Retrieval-Augmented Generation(RAG)에서 흔히 볼 수 있습니다. 사용자 요청당 검색이 여러 번 호출될 수 있으므로 응답 시간이 지연되거나 인프라 비용이 증가할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt718c72fb858e4c98/6a170bf00e2e496e7241a132/54ac476ff20a3cf93484298c9ae47612c12fc110-800x417.png" alt="컨텍스트 엔지니어링은 대규모 컨텍스트 풀을 제한된 LLM 컨텍스트 창으로 환합니다." /><h2>벡터 검색 성능이 중요한 이유</h2><p></p><p>쇼핑 어시스턴트가 '15인치 노트북이 들어가고, 방수 기능이 있으며, 금요일까지 배송 가능한 6만원 이하의 기내용 백팩을 찾는다'는 질문에 답하는 경우를 생각해 보겠습니다.</p><p>실제 환경에서 어시스턴트가 벡터 쿼리를 한 번만 실행하고 멈추는 경우가 거의 없습니다. 올바른 컨텍스트를 구축하기 위해 검색 루프를 실행하는데, 각 단계는 일반적으로 가용성, 지역, 배송 예정일, 브랜드 규칙 및 정책 적합성과 같은 필터에 의해 제한을 받습니다.</p><p><strong>1단계: 의도를 해석해 제약 조건으로 변환</strong></p><p>에이전트는 요청을 다음과 같은 구조화된 필터와 의미론적 쿼리로 변환합니다.</p><ul><li><p>필터: 재고 있음, 사용자의 우편번호로 배송 가능, 금요일까지 배송, 가격 6만원 미만, 유효한 상품 목록</p></li><li><p>벡터 쿼리: '기내 반입 가능한 백팩, 15인치 노트북, 방수'</p></li></ul><p><strong>2단계: 후보를 검색한 후 정제</strong></p><p>우수한 일치 항목을 놓치지 않기 위해 종종 변형된 검색을 반복합니다.</p><ul><li><p>'노트북 슬리브가 포함된 기내용 백팩'</p></li><li><p>'통근용 방수 백팩 15인치'</p></li><li><p>'기내용 경량 백팩'</p></li></ul><p>관련성이 없거나 사용할 수 없는 항목을 검색하는 것은 컨텍스트 낭비이므로 각 쿼리는 동일한 자격 필터를 사용합니다.</p><p><strong>3단계: 위험을 줄이기 위해 확장하여 세부 정보 확인</strong></p><p>그런 다음 에이전트는 최종 답변에 영향을 미치는 주요 속성을 확인하기 위해 데이터를 다시 검색합니다.</p><ul><li><p>재질 및 방수 기능 관련 문구</p></li><li><p>치수 및 노트북 수납 공간 크기</p></li><li><p>반품 정책 또는 보증 제약 조건</p></li><li><p>재고가 부족한 경우 대체 옵션</p></li></ul><p>이것이 검색, 추론, 검색, 조립을 거치는 다단계 컨텍스트 엔지니어링입니다.</p><h2>컨텍스트 엔지니어링에서 지연 시간과 재현율이 중요한 이유</h2><p>이러한 상호작용은 사용자 세션당 수십 건의 필터링된 검색 호출을 포함할 수 있습니다. 따라서 통화당 지연 시간이 엔드투엔드 응답 시간에 직접적인 영향을 미치며, 재현율이 낮으면 에이전트가 추가 재시도를 하거나 적격 항목을 놓치게 되어 응답 품질이 저하됩니다.</p><p>요점: 컨텍스트 엔지니어링 시스템에서 필터링된 근사 이웃(ANN)은 일회성 조회로 끝나지 않습니다. 이는 제약 조건 하에서 반복되는 작업이므로, 대형 언어 모델(LLM)이 가장 중요한 구성 요소인 경우에도 벡터 검색 성능이 지연 시간, 처리량, 비용에 즉각적으로 영향을 미칩니다.</p><h2>벤치마킹</h2><h3>결과</h3><p>그래프 2에서 각 점은 하나의 테스트 구성을 나타냅니다. 지연 시간이 짧고 재현율이 높은 최상의 결과는 왼쪽 상단에 표시됩니다. Elasticsearch의 결과가 OpenSearch보다 지속적으로 왼쪽 상단에 가깝게 위치한다는 점에서 동일한 워크로드 설정에서 더 나은 속도와 정확도를 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb562b30a600cc8e1/6a170bf2cf4f253582b2d1b2/c50d1df00968cac18149a2799e6242fbe49b66a0-1600x990.png" alt=" 그래프 2: 재현율 대 평균 지연 시간(재채점 1)." /><h4>주요 인사이트</h4><ul><li><p><code>s_n_r_value</code>: <code>size_numCandidates_rescoreOversample</code> 의 약어(k 및 numCandidates는 이러한 테스트에서 numCandidates와 동일하게 설정됨), 예를 들어 <code>100_500_1</code> 은 size=100, numCandidates=500 및 k=500, rescore oversample=1을 의미합니다.</p></li><li><p>재현율: 해당 구성에 대한 측정된 Recall@100</p></li><li><p>평균 지연 시간(ms): 쿼리당 평균 엔드투엔드 지연 시간</p></li><li><p>처리량: 초당 쿼리 수</p></li><li><p>재현율 %: Elasticsearch와 OpenSearch의 상대적 재현율 상승(Elasticsearch-OpenSearch)/OpenSearch</p></li><li><p>지연 시간 Xs: OpenSearch의 평균 지연 시간을 Elasticsearch의 평균 지연 시간으로 나눈 값</p></li><li><p>처리량 Xs: Elasticsearch 처리량을 OpenSearch 처리량으로 나눈 값</p></li></ul><p>엔진</p><p>`s_n_r_value`</p><p>재현율</p><p>평균 지연 시간(ms)</p><p>처리량</p><p>재현율 %</p><p>지연 시간 Xs</p><p>처리량 Xs</p><p>Elasticsearch</p><p>100_250_1</p><p>0.7704</p><p>25</p><p>534.75</p><p>9.70%</p><p>2.28</p><p>1.91</p><p>OpenSearch</p><p>100_250_1</p><p>0.7023</p><p>57.08</p><p>279.58</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_500_1</p><p>0.8577</p><p>25.42</p><p>524.14</p><p>7.20%</p><p>2.4</p><p>2</p><p>OpenSearch</p><p>100_500_1</p><p>0.8001</p><p>60.9</p><p>262.12</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_750_1</p><p>0.8947</p><p>29.67</p><p>528.09</p><p>5.72%</p><p>2.25</p><p>2.21</p><p>OpenSearch</p><p>100_750_1</p><p>0.8463</p><p>66.76</p><p>239.11</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1000_1</p><p>0.9156</p><p>29.65</p><p>534.5</p><p>4.66%</p><p>2.46</p><p>2.44</p><p>OpenSearch</p><p>100_1000_1</p><p>0.8748</p><p>72.88</p><p>219.01</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_1500_1</p><p>0.9386</p><p>31.84</p><p>497.3</p><p>3.38%</p><p>2.71</p><p>2.68</p><p>OpenSearch</p><p>100_1500_1</p><p>0.9079</p><p>86.16</p><p>185.4</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2000_1</p><p>0.9507</p><p>34.69</p><p>457.2</p><p>2.57%</p><p>2.98</p><p>2.96</p><p>OpenSearch</p><p>100_2000_1</p><p>0.9269</p><p>103.36</p><p>154.55</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_2500_1</p><p>0.9582</p><p>37.9</p><p>418.43</p><p>1.99%</p><p>3.28</p><p>3.26</p><p>OpenSearch</p><p>100_2500_1</p><p>0.9395</p><p>124.29</p><p>128.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_3000_1</p><p>0.9636</p><p>41.86</p><p>379.4</p><p>1.62%</p><p>3.46</p><p>3.44</p><p>OpenSearch</p><p>100_3000_1</p><p>0.9482</p><p>144.67</p><p>110.34</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_4000_1</p><p>0.9705</p><p>50.28</p><p>316.21</p><p>1.06%</p><p>3.87</p><p>3.85</p><p>OpenSearch</p><p>100_4000_1</p><p>0.9603</p><p>194.36</p><p>82.22</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_5000_1</p><p>0.9749</p><p>58.77</p><p>270.91</p><p>0.73%</p><p>4.43</p><p>4.41</p><p>OpenSearch</p><p>100_5000_1</p><p>0.9678</p><p>260.33</p><p>61.38</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_6000_1</p><p>0.9781</p><p>66.75</p><p>238.59</p><p>0.52%</p><p>4.91</p><p>4.89</p><p>OpenSearch</p><p>100_6000_1</p><p>0.973</p><p>327.44</p><p>48.81</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_7000_1</p><p>0.9804</p><p>74.64</p><p>213.49</p><p>0.38%</p><p>5.28</p><p>5.27</p><p>OpenSearch</p><p>100_7000_1</p><p>0.9767</p><p>394.24</p><p>40.53</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_8000_1</p><p>0.9823</p><p>82.28</p><p>193.59</p><p>0.27%</p><p>6.86</p><p>6.83</p><p>OpenSearch</p><p>100_8000_1</p><p>0.9797</p><p>564.14</p><p>28.33</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_9000_1</p><p>0.9837</p><p>90.08</p><p>176.96</p><p>0.16%</p><p>7.63</p><p>7.61</p><p>OpenSearch</p><p>100_9000_1</p><p>0.9821</p><p>687.25</p><p>23.25</p><p></p><p></p><p></p><p>Elasticsearch</p><p>100_10000_1</p><p>0.9848</p><p>97.64</p><p>163.31</p><p>0.08%</p><p>8.38</p><p>8.36</p><p>OpenSearch</p><p>100_10000_1</p><p>0.984</p><p>818.64</p><p>19.53</p><p></p><p></p><p></p><p>예를 들어, <code>100_9000_1</code>에서 OpenSearch는 검색당 평균 687밀리초를 기록하는 반면, Elasticsearch에서는 90밀리초이며, 10단계 검색 루프에서는 약 10x(687-90)=6초의 추가 대기 시간이 발생합니다. </p><p><a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search/jingra/results/20260220">전체 결과</a> 보기.</p><h3>방법론</h3><p>Python을 사용하여 쿼리를 전송하고 응답 시간 및 기타 통계를 추적하기 위해 다음 쿼리를 각 엔진에 전송했습니다. 벡터 검색 엔진의 성능은 고려할 후보 수, 재채점의 적극성, 반환할 컨텍스트의 양 등 핵심 매개변수를 어떻게 조정하느냐에 따라 달라진다는 점을 유의하시기 바랍니다. 이러한 설정은 재현율(올바른 답변을 찾을 가능성)과 대기 시간(결과를 얻는 속도)에 직접적인 영향을 미칩니다.</p><p>벤치마크 테스트에서는 에이전틱 검색 루프에서 일반적으로 조정하는 것과 동일한 후보, 재채점, 결과 크기 설정을 사용하여 해당 워크로드에서 Elasticsearch의 성능을 측정했습니다. 그런 다음 참조를 위해 동일한 설정으로 OpenSearch를 실행했습니다.</p><p>OpenSearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;": {
        "vector": [...],
        "k": &lt;NUMBER_OF_CANDIDATES&gt;,
        "method_parameters": {
          "ef_search": &lt;NUMBER_OF_CANDIDATES&gt;
        },
        "rescore": {
          "oversample_factor": &lt;OVERSAMPLE&gt;
        },
        "filter": {
          &lt;SOME_FILTER&gt;
        }
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: 클라이언트에 반환된 결과 수입니다. 이 벤치마크에서는 Recall@100을 계산하기 위해 결과 크기가 100입니다.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>: 최근접 이웃 후보의 수입니다.</p></li><li><p><code>"ef_search": &lt;NUMBER_OF_CANDIDATES&gt;</code>: 검사할 벡터의 개수.</p></li><li><p><code>"oversample_factor": &lt;OVERSAMPLE&gt;</code>: 재채점 전에 검색되는 후보 벡터의 수입니다.</p></li></ul><p>Elasticsearch</p>GET &lt;INDEX_NAME&gt;/_search
{
  "query": {
    "knn": {
      "field": "&lt;DENSE_VECTOR_FIELD_NAME&gt;",
      "query_vector": [...],
      "k": &lt;NUMBER_OF_CANDIDATES&gt;,
      "num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;,
      "rescore_vector": {
        "oversample": &lt;OVERSAMPLE&gt;
      },
      "filter": {
        &lt;SOME_FILTER&gt;
      }
    }
  },
  "size": &lt;RESULT_SIZE&gt;,
  "_source": {
    "excludes": [
      "&lt;DENSE_VECTOR_FIELD_NAME&gt;"
    ]
  }
}<ul><li><p><code>"size": &lt;RESULT_SIZE&gt;</code>: 클라이언트에 반환된 결과 수입니다. 이 벤치마크에서는 Recall@100을 계산하기 위해 결과 크기가 100입니다.</p></li><li><p><code>"k": &lt;NUMBER_OF_CANDIDATES&gt;</code>각 샤드에서 반환할 가장 가까운 이웃의 수입니다.</p></li><li><p><code>"num_candidates": &lt;NUMBER_OF_CANDIDATES&gt;</code>: <code>knn</code> 검색 수행 시 샤드별로 고려할 최근접 이웃 후보 수.</p></li><li><p><code>"oversample": &lt;OVERSAMPLE&gt;</code>: 재채점 전에 검색되는 후보 벡터의 수입니다.</p></li></ul><p>예</p><p><code>Knn</code> 쿼리(<code>100_500_1</code>)는 다음과 같습니다.</p><p>OpenSearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "search_catalog_embedding": {
        "vector": [...],
        "k": 500,
        "method_parameters": {
          "ef_search": 500
        },
        "rescore": {
          "oversample_factor": 1
        },
        "filter": {
          "term": {
            "valid": true
          }
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>Elasticsearch</p>GET search_catalog_128/_search
{
  "query": {
    "knn": {
      "field": "search_catalog_embedding",
      "query_vector": [...],
      "k": 500,
      "num_candidates": 500,
      "rescore_vector": {
        "oversample": 1
      },
      "filter": {
        "term": {
          "valid": true
        }
      }
    }
  },
  "size": 100,
  "_source": {
    "excludes": [
      "search_catalog_embedding"
    ]
  }
}<p>전체 구성, Terraform 스크립트, Kubernetes 매니페스트 및 벤치마킹 코드는 이 <a href="https://github.com/elastic/competitive-benchmarking-studies">저장소</a>의 <a href="https://github.com/elastic/competitive-benchmarking-studies/tree/main/es-9.3-vs-os-3.5-vector-search">es-9.3-vs-os-3.5-vector-search</a> 폴더에서 확인할 수 있습니다.</p><h3>클러스터 설정</h3><p>테스트는 각각 16개의 vCPU와 64GB RAM을 갖춘 6개의 e2-standard-16 클라우드 서버에서 실행되었습니다. 각 서버에서 검색 엔진 노드를 실행하는 각 Kubernetes 포드에 15개의 vCPU와 56GB RAM을 할당하였으며, 그 중 28GB는 JVM 힙을 위해 예약했습니다.</p><p>클러스터는 Elasticsearch 9.3.0과 OpenSearch 3.5.0 (Lucene 10.3.2)을 실행합니다. 이 벤치마크에서 두 시스템 모두 동일한 Lucene 버전을 사용하기 때문에, 관찰된 처리량과 지연 시간 차이는 Lucene에만 기인하는 것이 아니라 각 엔진이 필터링된 kNN(k-최근 이웃) 검색 및 재채점을 통합하고 실행하는 방식의 차이를 반영합니다. 3개의 기본 샤드와 1개의 복제본이 있는 단일 인덱스를 사용했습니다(따라서 노드당 1개씩 총 6개의 샤드).</p><p>또한 같은 지역 내 별도의 서버를 사용해 벤치마크 클라이언트를 실행하고 타이밍 통계를 수집했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7c7bdd567395d5b7/6a170bf3c1e8a56ee2f882f6/f81002c9186e4c2d3e92f49d72418fee9860fc5e-761x401.png" alt="Elasticsearch 및 OpenSearch 벤치마크를 위한 클러스터링" /><h3>데이터 세트</h3><p></p><p>이 벤치마크에서는 대규모 실제 환경에서의 필터링된 벡터 검색을 반영하도록 설계된 대규모 이커머스 스타일의 카탈로그 임베딩 데이터 세트(2,000만 개 문서)을 사용했습니다.</p><p></p><p>각 문서는 카탈로그 항목을 나타내며 다음을 포함합니다.</p><p></p><ul><li><p>128차원 고밀도 벡터 임베딩으로, 대략적인 kNN 검색에 사용됩니다.</p></li><li><p>필터링에 사용되는 구조화된 메타데이터 필드(예: 항목 유효성 및 가용성, 기타 카탈로그 제약 조건)는 적합한 하위 집합 내에서만 가장 가까운 이웃을 검색하는 일반적인 제작 패턴을 지원합니다.</p></li></ul><p></p><p>이 데이터 세트를 선택한 이유는 선택한 이유는 프로덕션 환경의 에이전틱 및 RAG 스타일 시스템에서 나타나는 핵심 성능 과제를 잘 반영하기 때문입니다. 벡터 유사도만으로는 충분하지 않고, 검색은 필터에 의해 제한을 받는 경우가 많고, 시스템은 해당 제약 조건 하에서 지연 시간을 낮게 유지하면서 높은 재현율을 달성해야 합니다. 소규모 QA 스타일 데이터 세트와 비교했을 때, 2,000만 개 문서 코퍼스는 실제 환경에서 필터링된 ANN 시스템이 직면하는 규모와 후보 압력을 더 잘 반영합니다.</p><h2>결론</h2><p>현대 AI 아키텍처, 특히 컨텍스트 엔지니어링을 중심으로 구축된 아키텍처에서 벡터 검색 속도는 사소한 구현 세부 사항으로 치부할 수 없습니다. 이는 효과를 배가시킵니다. 에이전트와 워크플로가 검색 → 추론 → 검색 과정을 반복할 때, 검색 성능은 엔드투엔드 지연 시간, 처리량, 모델에 입력되는 컨텍스트 품질에 직접적인 영향을 미칩니다.</p><p>벤치마크 테스트 결과, Elasticsearch는 정확한 문서 검색 여부가 중요한 시나리오(단순히 유사한 벡터를 검색하는 것이 아닌)에서 OpenSearch보다 지속적으로 더 높은 재현율과 더 낮은 지연 시간을 제공했습니다. 제어된 데이터 세트에서 차이는 분명하며, 실제 환경에서는 대규모 검색 호출 전반에 걸쳐 이러한 이점이 누적되어 응답성을 향상시키고, 용량 여유를 확대하며, 인프라 비용을 절감합니다.</p><h3>추가 읽기</h3><ol><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-overview">컨텍스트 엔지니어링이란 무엇인가요?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/series/context-engineering-hybrid-search-evolution">하이브리드 검색 및 컨텍스트 엔지니어링의 발전</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/context-engineering-relevance-ai-agents-elasticsearch">AI 에이전트를 위한 컨텍스트 엔지니어링에서 관련성의 영향</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/opensearch-vs-elasticsearch-filtered-vector-search</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Sachin Frayne]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b38e5114bbf098c/6a170bf560084b3a6c3c459d/fb7ee623925ca6696d643e437ce8efe5fe749079-1280x720.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch에서 NVIDIA cuVS를 활용하여 벡터 색인화 속도 최대 12배 향상: GPU 가속화 챕터 2]]></title>
    <description><![CDATA[Elasticsearch가 GPU 가속 벡터 색인화와 NVIDIA cuVS로 어떻게 거의 12배 더 높은 색인화 처리량을 달성하는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>올해 초 Elastic은 NVIDIA와의 <a href="https://ir.elastic.co/news/news-details/2025/Elastic-Brings-Enterprise-Data-to-NVIDIA-AI-Factories/default.aspx">협업</a>을 통해 Elasticsearch에 GPU 가속화를 도입하여 <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>와 통합한다고 발표했으며, <a href="https://www.nvidia.com/en-us/on-demand/session/gtc25-S71286/">NVIDIA GTC의 세션</a>과 다양한 <a href="https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia">블로그</a>에 자세히 설명되어 있습니다. 이 게시물은 NVIDIA 벡터 검색 팀과의 공동 엔지니어링 활동에 대한 업데이트입니다.</p><h2>요약</h2><p>먼저 현황을 간략하게 설명하겠습니다. Elasticsearch는 강력한 벡터 데이터베이스로서, 풍부한 기능과 대규모 유사성 검색을 위한 뛰어난 성능을 제공하며 자리매김했습니다. 스칼라 양자화, Better Binary Quantization(<a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a>), <a href="https://www.elastic.co/blog/accelerating-vector-search-simd-instructions">SIMD</a> 벡터 연산, 그리고 <a href="https://www.elastic.co/search-labs/blog/diskbbq-elasticsearch-introduction">DiskBBQ</a>와 같은 디스크 효율적인 알고리즘을 통해 벡터 워크로드를 효율적이고 유연하게 관리할 수 있는 다양한 옵션을 제공합니다.</p><p>NVIDIA cuVS를 벡터 검색 작업을 위한 호출 가능한 모듈로 통합함으로써 벡터 색인화 성능과 효율성을 크게 향상해 대규모 벡터 워크로드를 보다 효과적으로 지원하는 것을 목표로 합니다.</p><h2>당면 과제</h2><p>고성능 벡터 데이터베이스를 구축하는 데 있어 매우 어려운 과제 중 하나는 벡터 인덱스, 즉 <a href="https://arxiv.org/abs/1603.09320">HNSW</a> 그래프를 구성하는 것입니다. 인덱스 구축은 각 벡터가 다른 수많은 벡터와 비교되기 때문에 수백만, 심지어 수십억 개의 산술 연산으로 빠르게 진행됩니다. 또한 압축 및 병합과 같은 인덱스 수명 주기 작업은 색인화의 전체 컴퓨팅 오버헤드를 더 증가시킬 수 있습니다. 데이터 볼륨과 관련 벡터 임베딩이 기하급수적으로 증가함에 따라 대규모 병렬 처리와 높은 처리량의 연산을 위해 구축된 가속 컴퓨팅 GPU는 이러한 워크로드를 처리하는 데 최적의 위치에 있습니다.</p><h2>Elasticsearch-GPU 플러그인 입력</h2><p><a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS</a>는 GPU 가속 벡터 검색 및 데이터 클러스터링을 위한 오픈 소스 CUDA-X 라이브러리로, AI 및 추천 워크로드를 위한 빠른 인덱스 구축 및 임베딩 검색을 가능하게 합니다.</p><p>Elasticsearch는 커뮤니티가 개발하고 NVIDIA가 관리하는 오픈 소스 라이브러리인 <a href="https://mvnrepository.com/artifact/com.nvidia.cuvs/cuvs-java">cuvs-java</a>를 통해 cuVS를 사용합니다. cuvs-java 라이브러리는 경량이며, <a href="https://docs.rapids.ai/api/cuvs/nightly/c_api/">cuVS C API</a>를 기반으로 <a href="https://openjdk.org/projects/panama/">Panama</a> Foreign Function을 사용하여 cuVS 기능을 관용적인 Java 방식으로 노출하면서도 최신 기술과 뛰어난 성능을 유지합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc7fd7361099da05a/6a17e920be608670af00477f/5f6daa1eb07f704a6707d9e6b7ccb81d0abaa8c9-566x419.png" alt="Elasticsearch가 NVIDIA cuVS, CPU 및 GPU 색인화로 작동하는 방식" /><p>cuvs-java 라이브러리는 <a href="https://github.com/elastic/elasticsearch/pull/135545">새로운 Elasticsearch 플러그인</a>에 통합되어 있습니다. 따라서 GPU에서의 벡터 색인화는 동일한 Elasticsearch 노드와 프로세스에서 외부 코드나 하드웨어를 프로비저닝할 필요 없이 수행할 수 있습니다. 인덱스 구축 중에 cuVS 라이브러리가 설치되고 GPU가 존재하며 구성된 경우 Elasticsearch는 GPU를 사용하여 벡터 색인화 프로세스를 가속화합니다. 벡터는 GPU에 전달되어 <a href="https://arxiv.org/abs/2308.15136">CAGRA</a> 그래프를 구성합니다. 이 그래프는 HNSW 형식으로 변환되어 CPU에서 벡터를 즉시 검색할 수 있게 됩니다. 구축된 그래프의 최종 형식은 CPU에서 구축되는 것과 동일합니다. 이를 통해 Elasticsearch는 기본 하드웨어가 지원할 경우 높은 처리량의 벡터 색인화에 GPU를 활용할 수 있으며, 동시에 CPU 자원을 다른 작업(동시 검색, 데이터 처리 등)에 사용할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt485f55f29d6df5c4/6a17e922be6086dcf3004785/3ea255bd9bfd7983f78143c5eba999d2149d72be-671x356.png" alt="" /><h2>인덱스 구축 가속화</h2><p>Elasticsearch에 GPU 가속화를 통합하는 과정에서 cuvs-java에 여러 가지 개선 사항이 적용되었으며, 특히 효율적인 데이터 입출력 및 함수 호출에 중점을 두었습니다. 핵심적인 개선 사항 중 하나는 <a href="https://github.com/rapidsai/cuvs/blob/2cf5fa7666d703dccbe655f8214656b0952bb69b/java/cuvs-java/src/main/java/com/nvidia/cuvs/CuVSMatrix.java">cuVSMatrix</a>를 사용하여 Java 힙, 오프힙 또는 GPU 메모리에 저장된 벡터를 투명하게 모델링하는 것입니다. 이를 통해 데이터가 메모리와 GPU 간에 효율적으로 이동하여 수십억 개의 벡터를 불필요하게 복사하는 것을 방지할 수 있습니다.</p><p>이러한 기본 제로 카피 추상화 덕분에 GPU 메모리로의 전송과 그래프 검색이 모두 직접 이루어질 수 있습니다. 색인화하는 동안 벡터는 먼저 Java 힙의 메모리에 버퍼링된 다음 GPU로 전송되어 CAGRA 그래프를 구성합니다. 그 후 그래프는 GPU에서 검색되어 HNSW 형식으로 변환된 후 디스크에 유지됩니다.</p><p>병합 시점에 벡터는 이미 디스크에 저장되어 있으므로 Java 힙을 완전히 우회합니다. 인덱스 파일은 메모리 맵핑 방식으로 처리되며 데이터는 GPU 메모리로 직접 전송됩니다. 또한 이 설계는 float32 또는 int8과 같은 다양한 비트 폭을 쉽게 수용할 수 있으며 다른 양자화 방식으로도 자연스럽게 확장됩니다.</p><h2>자, 그럼 성능은 어떨까요?</h2><p>수치를 살펴보기 전에, 약간의 배경 설명이 필요합니다. Elasticsearch에서 세그먼트 병합은 보통 색인 과정 중 백그라운드에서 자동으로 실행되기 때문에, 이를 단독으로 분리해 벤치마크하기가 어렵습니다. 재현 가능한 결과를 얻기 위해, 이번에는 강제 병합을 사용하여 통제된 실험 환경에서 세그먼트 병합을 명시적으로 트리거했습니다. 강제 병합은 백그라운드 병합과 동일한 기본 병합 작업을 수행하므로, 실제 색인 워크로드에서는 정확한 향상 폭이 다를 수 있지만, 성능 개선 효과를 가늠하는 유용한 지표로 볼 수 있습니다.</p><p>이제 수치를 확인해 보겠습니다.</p><p>초기 벤치마크 결과는 매우 희망적입니다. 로컬로 연결된 NVMe 저장 공간이 있는 AWS <code>g6.4xlarge</code> 인스턴스에서 벤치마크를 실행했습니다. Elasticsearch의 단일 노드는 기본 최적의 색인화 스레드 수(8개 - 물리적 코어당 1개)를 사용하고 <a href="https://www.elastic.co/docs/reference/elasticsearch/index-settings/merge">병합 스로틀링</a>을 비활성화하도록 구성되었습니다(빠른 NVMe 디스크에는 적용하기 어렵습니다).</p><p>데이터 세트에는 <a href="https://github.com/elastic/rally-tracks/blob/master/openai_vector/README.md">OpenAI Rally 벡터 트랙</a>에서 가져온 1,536차원의 260만 개 벡터를 사용했으며, <a href="https://github.com/elastic/elasticsearch/pull/137072">base64 스트링</a>으로 인코딩하고 float32 <em>hnsw</em>로 색인화했습니다. 모든 시나리오에서 구성된 그래프는 최대 95%의 재현율을 달성했습니다. 결과는 다음과 같습니다.</p><ul><li><p><strong>색인화 처리량:</strong> 인메모리 버퍼 플러시 중 그래프 구성을 GPU로 이동하여 처리량이 약 12배 증가합니다.</p></li><li><p><strong>강제 병합:</strong> 색인화가 완료된 후에도 GPU는 세그먼트 병합을 계속 가속화하여 강제 병합 단계의 속도를 최대 7배까지 높입니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfea4ee13b5a3b10d/6a17e923e9ea879c6aa9c616/f60ea9ee5996e456f393ffd195ee7eada6e5a7c2-948x387.png" alt="" /><ul><li><p><strong>CPU 사용량:</strong> 그래프 구성을 GPU로 오프로드하면 평균 및 최대 CPU 사용량이 많이 감소합니다. 아래 그래프는 색인화 및 병합 중 CPU 사용량을 보여주며, 이러한 작업이 GPU에서 실행될 때 GPU 작업량이 얼마나 낮아지는지를 강조합니다. GPU 색인화 중 CPU 사용률이 낮아지면 CPU 사이클이 확보되어 검색 성능 향상에 활용할 수 있습니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt80ff9c53f9b6884a/6a17e925445de9ee4b4d0187/5e680a5fc41700a877f3d8b2e5ce18ebd3f37a0b-1600x562.png" alt="" /><ul><li><p><strong>재현율:</strong> CPU와 GPU 실행 간의 정확도는 사실상 동일하지만, GPU로 구축된 그래프는 재현율이 약간 더 높습니다.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5cbe084eca27b8e4/6a17e926faa913317093c8b7/48a2b7758606bd321712b7d8378cd2640e652a4e-1384x544.png" alt="" /><h2>가격 측면에서 비교</h2><p>앞선 비교에서는 의도적으로 동일한 하드웨어를 사용했으며 유일한 차이점은 색인 과정에서 GPU를 사용했는지 여부였습니다. 이 설정은 순수 연산 성능의 영향을 분리하여 파악하는 데 유용하지만 비용 관점에서의 비교도 가능합니다.</p><p>GPU 가속 구성과 거의 동일한 시간당 요금으로 CPU 전용 구성을 프로비저닝할 수 있습니다. 이 경우 비교 가능한 CPU 및 메모리 리소스가 약 두 배(32 vCPU(AMD EPYC), 64GB RAM)로 제공되며 색인 스레드 수를 16개로 두 배 늘릴 수 있습니다.</p><p>공정하고 일관된 비교를 위해 GPU를 명시적으로 비활성화한 상태로 AWS g6.8xlarge 인스턴스에서 CPU 전용 실험을 수행했습니다. 이를 통해 다른 모든 하드웨어 특성을 동일하게 유지하면서 GPU 가속과 CPU 전용 색인 간의 비용 대비 성능 트레이드오프를 평가할 수 있었습니다.</p><p>예상대로 더 강력한 CPU 인스턴스는 위 섹션의 벤치마크와 비교했을 때 향상된 성능을 보여줍니다. 그러나 이 더 강력한 CPU 인스턴스를 원래의 GPU 가속 결과와 비교해 보면 GPU는 여전히 상당한 성능 향상을 제공합니다. 색인화 처리량에서 <strong>약 5배</strong>, 강제 병합에서 <strong>약 6배</strong>의 성능 향상을 보이며, 동시에 최대 <strong>95%</strong>의 재현율을 달성하는 그래프를 구축할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94b5eb6f95ba307d/6a17e928abe0f255d4dfea35/8ffa58cae3ad175ef2932a351aeef4c34a1407b9-948x394.png" alt="" /><h2>결론</h2><p>엔드 투 엔드 시나리오에서 NVIDIA cuVS를 사용한 GPU 가속화는 색인화 처리량을 거의 12배 향상하고 강제 병합 지연 시간을 7배 단축하는 동시에 CPU 사용률을 크게 낮춥니다. 이는 벡터 색인화 및 병합 워크로드가 GPU 가속화를 통해 상당한 성능 향상을 얻을 수 있음을 보여줍니다. 비용을 고려한 비교에서도 GPU 가속화는 색인화 처리량을 약 5배, 강제 병합 작업 속도를 약 6배 향상하는 등 상당한 성능 향상을 제공합니다.</p><p>GPU 가속 벡터 색인화는 현재 Elasticsearch 9.3의 기술 미리 보기로 계획되어 있으며, 2026년 초에 출시될 예정입니다.</p><p>더 많은 소식을 기대해 주세요.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-gpu-accelerated-vector-indexing-nvidia</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik,Corey Nolet,Manas Singh,Mithun Radhakrishnan,Mayya Sharipova,Lorenzo Dematte,Ben Frederickson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1248d51633bd75d9/6a17e92ae9ea8714b3a9c61a/08f7469a4daaf67b7c5999585aae179b6680c78d-896x746.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Dec 2025 00:00:00 GMT</pubDate>
  </item>
  <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[하이브리드 검색 재랭킹을 통한 다국어 임베딩 모델 관련성 향상]]></title>
    <description><![CDATA[Elasticsearch에서 Cohere의 재랭커와 하이브리드 검색을 사용해 E5 다국어 임베딩 모델 검색 결과의 정확도를 개선하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>소개</h2><p><a href="https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch">이 시리즈의 마지막 파트에서는</a> Elastic의 사전 학습된 E5 모델(그리고 Hugging Face의 다른 다국어 텍스트 임베딩 모델)을 배포하는 과정을 살펴보고, Elasticsearch와 Kibana를 사용해 텍스트 데이터에서 고밀도 벡터 임베딩을 생성하는 방법에 대해 알아보았습니다. 이 블로그에서는 이러한 임베딩의 결과를 살펴보고 다국어 모델을 활용할 때 얻을 수 있는 중요한 이점을 강조합니다.</p><p>이제 색인 <code>coco_multilingual</code> 을 만들었으므로 검색을 수행하면 참조할 수 있도록 'en' 필드가 있는 여러 언어로 된 문서가 표시됩니다:</p># GET coco_multilingual/_search
    {
       "_index": "coco_multilingual",
       "_id": "WAiXQJYBgf6odR9bLohZ",
       "_score": 1,
       "_source": {
         "description": "Ein Parkmeßgerät auf einer Straße mit Autos",
         "en": "A row of parked cars sitting next to parking meters.",
         "language": "de",
         "vector_description": {...}
       }
     },
     . . .<h2>영어로 검색 수행하기</h2><p>영어로 검색을 수행해보고 얼마나 잘 검색되는지 확인해 보겠습니다:</p>GET coco_multi/_search
{
"size": 10,
"_source": [
  "description", "language", "en"
],
"knn": {
  "field": "vector_description.predicted_value",
  "k": 10,
  "num_candidates": 100,
  "query_vector_builder": {
    "text_embedding": {
      "model_id": ".multilingual-e5-small_linux-x86_64_search",
      "model_text": "query: kitty"
    }
  }
}
}{
       "_index": "coco_multi",
       "_id": "JQiXQJYBgf6odR9b6Yz0",
       "_score": 0.9334303,
       "_source": {
         "description": "Eine Katze, die in einem kleinen, gepackten Koffer sitzt.",
         "en": "A brown and white cat is in a suitcase.",
         "language": "de"
       }
     },
      {
       "_index": "coco_multi",
       "_id": "3AiXQJYBgf6odR9bFod6",
       "_score": 0.9281012,
       "_source": {
         "description": "Una bambina che tiene un gattino vicino a una recinzione blu.",
         "en": "A little girl holding a kitten next to a blue fence.",
         "language": "it"
       }
     },
     . . .<p>이 쿼리는 놀라울 정도로 단순해 보이지만, 내부적으로는 모든 언어의 모든 문서에서 'kitty'라는 단어가 포함된 숫자를 검색하고 있습니다. 그리고 벡터 검색을 수행하기 때문에 'kitty'와 관련이 있을 수 있는 모든 단어를 의미론적으로 검색할 수 있습니다: "고양이", "새끼 고양이", "고양이", "가토"(이탈리아어), "메오"(베트남어), 고양이(한국어), 猫(중국어) 등이 있습니다. 그 결과, 검색어가 영어로 되어 있어도 다른 모든 언어로 된 콘텐츠도 검색할 수 있습니다. 예를 들어, 고양이(<code>ying on something</code> )를 검색하면 이탈리아어, 네덜란드어 또는 베트남어로 된 문서도 표시됩니다. 효율성에 대해 이야기해 보세요!</p><h2>다른 언어로 된 콘텐츠 검색 수행하기</h2>GET coco_multi/_search
{  
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: kitty lying on something"
     }
   }
 }
}{
 "description": "A black kitten lays on her side beside remote controls.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "en"
},
{
 "description": "un gattino sdraiato su un letto accanto ad alcuni telefoni ",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "it"
},
{
 "description": "eine Katze legt sich auf ein ausgestopftes Tier",
 "en": "a cat lays down on a stuffed animal",
 "language": "de"
},
{
 "description": "Một chú mèo con màu đen nằm nghiêng bên cạnh điều khiển từ xa.",
 "en": "A black kitten lays on her side beside remote controls.",
 "language": "vi"
}
. . .<p>마찬가지로 한국어로 '고양이'로 키워드 검색을 수행하면 의미 있는 결과를 얻을 수 있습니다. 여기서 놀라운 점은 이 색인에는 한국어로 된 문서가 하나도 없다는 것입니다!</p>GET coco_multi/_search
{
 "size": 100,
 "_source": [
   "description", "language", "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 50,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: 고양이"
     }
   }
 }
} {
       {
         "description": "eine Katze legt sich auf ein ausgestopftes Tier",
         "en": "a cat lays down on a stuffed animal",
         "language": "de"
       }
     },
     {
       {
         "description": "Một con chó và con mèo đang ngủ với nhau trên một chiếc ghế dài màu cam.",
         "en": "A dog and cat lying  together on an orange couch. ",
         "language": "vi"
       }
     },<p>임베딩 모델은 공유 의미 공간에서 의미를 나타내므로 색인된 캡션과 다른 언어로 쿼리해도 관련 이미지를 검색할 수 있습니다.</p><h2>하이브리드 검색 및 재랭킹으로 관련성 높은 검색 결과 얻기</h2><p>예상대로 관련 결과가 나타나서 기쁘게 생각합니다. 하지만 이커머스나 가장 적합한 상위 5~10개의 결과로 범위를 좁혀야 하는 RAG 애플리케이션과 같은 실제 환경에서는 재랭크 모델을 사용하여 가장 관련성이 높은 결과의 우선 순위를 지정할 수 있습니다.</p><p>여기서 베트남어로 "고양이는 무슨 색인가요?"라고 묻는 쿼리를 수행하면 많은 결과가 나오지만 상위 1, 2위가 가장 관련성이 높지 않을 수 있습니다.</p>GET coco_multi/_search
{
 "size": 20,
 "_source": [
   "description",
   "language",
   "en"
 ],
 "knn": {
   "field": "vector_description.predicted_value",
   "k": 20,
   "num_candidates": 1000,
   "query_vector_builder": {
     "text_embedding": {
       "model_id": ".multilingual-e5-small_linux-x86_64_search",
       "model_text": "query: con mèo màu gì?"
     }
   }
 }
}<p>결과에는 모두 고양이 또는 어떤 형태의 색상이 언급되어 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt979f5944b1708042/6a17ef76420229ff6829f6aa/33e1e887dbbdd1066cfedc7375f5e3b46538529e-859x847.png" alt="" /><p>이제 개선해 봅시다! <a href="https://cohere.com/blog/rerank-3pt5">Cohere의</a>다국어 재랭크 모델을 통합하여 질문에 해당하는 추론을 개선해 보겠습니다.</p>PUT _inference/rerank/cohere_rerank
{
 "service": "cohere",
 "service_settings": {
   "api_key": "your_api_key",
   "model_id": "rerank-v3.5"
 },
 "task_settings": {
   "top_n": 10,
   "return_documents": true
 }
}


GET coco_multi/_search
{
"size": 10,
"_source": [
  "description",
  "language",
  "en"
],
"retriever": {
  "text_similarity_reranker": {
    "retriever": {
      "rrf": {
        "retrievers": [
          {
            "knn": {
              "field": "vector_description.predicted_value",
              "k": 50,
              "num_candidates": 100,
              "query_vector_builder": {
                "text_embedding": {
                  "model_id": ".multilingual-e5-small_linux-x86_64_search",
                  "model_text": "query: con mèo màu gì?" // English: What color is the cat?
                }
              }
            }
          }
        ],
        "rank_window_size": 100,
        "rank_constant": 0
      }
    },
    "field": "description",
    "inference_id": "cohere_rerank",
    "inference_text": "con mèo màu gì?"
  }
}
} {
       "_index": "coco_multi",
       "_id": "rQiYQJYBgf6odR9bBYyH",
       "_score": 1.5501487,
       "_source": {
         "description": "Hai cái điện thoại được đặt trên một cái chăn cạnh một con mèo con màu đen.",
         "en": "A black kitten lays on her side beside remote controls.",
         "language": "vi"
       }
     },
     {
       "_index": "coco_multi",
       "_id": "swiXQJYBgf6odR9b04uf",
       "_score": 1.5427427,
       "_source": {
         "description": "Một con mèo sọc nâu nhìn vào máy quay.", // Real translation: A brown striped cat looks at the camera 
         "en": "This cat is sitting on a porch near a tire.",
         "language": "vi"
       }
     },<p>이제 최고의 결과를 통해 저희 애플리케이션은 새끼 고양이의 색이 검은색 또는 줄무늬가 있는 갈색이라고 자신 있게 대답할 수 있습니다. 여기서 더욱 흥미로운 점은 벡터 검색이 실제로 원본 데이터 세트의 영어 캡션에서 누락된 부분을 찾아냈다는 점입니다. 참조 영어 번역에서 갈색 줄무늬 고양이를 놓쳤음에도 불구하고 이를 찾아낼 수 있습니다. 이것이 바로 벡터 검색의 힘입니다.</p><h2>결론</h2><p>이 블로그에서는 다국어 임베딩 모델의 유용성과 Elasticsearch를 활용하여 모델을 통합하여 임베딩을 생성하고 하이브리드 검색 및 재랭커로 관련성과 정확도를 효과적으로 개선하는 방법을 살펴봤습니다. 원하는 언어와 데이터 세트에 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">대해 즉시 사용 가능한 E5 모델을 사용하여 자체 클라우드</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">클러스터를</a> 생성하여 다국어 의미론적 검색을 사용해 볼 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-hybrid-search-reranking</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[운영]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf625e3f63fcd9f54/6a17ef7796142a61f8eb1bcd/d341b04acecc8eeec321f5404e1643447ecc8526-720x420.png" length="0" type="image/png"/>
    <pubDate>Mon, 03 Nov 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch에서 다국어 임베딩 모델 배포하기]]></title>
    <description><![CDATA[Elasticsearch에서 벡터 검색 및 언어 간 검색을 위한 e5 다국어 임베딩 모델을 배포하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<h2>소개</h2><p>전 세계 사용자가 있는 세계에서는 다국어 정보 검색(CLIR)이 매우 중요합니다. CLIR은 검색을 단일 언어로 제한하는 대신 <em>모든</em> 언어로 정보를 찾을 수 있도록 하여 사용자 경험을 개선하고 운영을 간소화합니다. 이커머스 고객이 자신의 언어로 상품을 검색하면 데이터를 미리 현지화할 필요 없이 적절한 결과가 표시되는 글로벌 시장을 상상해 보세요. 또는 학술 연구자들이 뉘앙스와 복잡성이 있는 논문을 모국어로 검색할 수 있으며, 출처가 다른 언어로 되어 있어도 검색할 수 있습니다.</p><p>다국어 텍스트 임베딩 모델을 사용하면 바로 그렇게 할 수 있습니다. 임베딩은 텍스트의 의미를 숫자 벡터로 표현하는 방법입니다. 이 벡터는 비슷한 의미를 가진 텍스트가 고차원 공간에서 서로 가깝게 위치하도록 설계되었습니다. 특히 다국어 텍스트 임베딩 모델은 여러 언어에서 동일한 의미를 가진 단어와 구문을 유사한 벡터 공간에 매핑하도록 설계되었습니다.</p><p>오픈 소스 다국어 E5와 같은 모델은 대개 대조 학습과 같은 기술을 사용하여 방대한 양의 텍스트 데이터를 학습합니다. 이 접근 방식에서 모델은 유사한 의미를 가진 텍스트 쌍(양의 쌍)과 서로 다른 의미를 가진 텍스트 쌍(음의 쌍)을 구별하는 방법을 학습합니다. 모델은 양성 쌍 간의 유사도는 최대화하고 음성 쌍 간의 유사도는 최소화하도록 생성하는 벡터를 조정하도록 학습됩니다. 다국어 모델의 경우 이 학습 데이터에는 서로 다른 언어로 된 텍스트 쌍이 포함되어 있어 모델이 여러 언어에 대한 공유 표현 공간을 학습할 수 있습니다. 이렇게 생성된 임베딩은 쿼리의 언어에 관계없이 텍스트 임베딩 간의 유사성을 사용하여 관련 문서를 찾는 교차 언어 검색을 비롯한 다양한 NLP 작업에 사용할 수 있습니다.</p><h2>다국어 벡터 검색의 이점</h2><ul><li><p><strong>뉘앙스</strong>: 벡터 검색은 키워드 매칭을 넘어 의미론적 의미를 포착하는 데 탁월합니다. 이는 언어의 맥락과 미묘한 차이를 이해해야 하는 작업에 매우 중요합니다.</p></li><li><p><strong>언어 간 이해</strong>: 쿼리와 문서가 서로 다른 어휘를 사용하는 경우에도 여러 언어에서 효과적으로 정보를 검색할 수 있습니다.</p></li><li><p><strong>연관성</strong>: 쿼리와 문서 간의 개념적 유사성에 초점을 맞춰 보다 관련성 높은 결과를 제공합니다.</p></li></ul><p>예를 들어, 여러 국가에서 소셜 미디어가 정치 담론에 미치는 영향( ")을 연구하는 학계 연구자(" )가 있다고 가정해 보겠습니다. 벡터 검색을 사용하면 "l'impatto dei social media sul discorso politico" (이탈리아어) 또는 "ảnh hưởng của mạng xã hội đối với diễn ngôn chính trị" (베트남어) 같은 검색어를 입력하면 관련 문서를 영문으로 찾을 수 있습니다, 스페인어 또는 기타 색인된 언어로 된 관련 문서를 찾아보세요. 벡터 검색은 정확한 키워드가 포함된 논문뿐만 아니라 소셜 미디어가 정치에 미치는 영향에 대한 <em>개념을</em> 논의하는 논문도 찾아내기 때문입니다. 이를 통해 연구의 폭과 깊이를 크게 향상시킬 수 있습니다.</p><h2>시작하기</h2><p>기본으로 제공되는 E5 모델을 사용하여 Elasticsearch를 사용하여 CLIR을 설정하는 방법은 다음과 같습니다. 여러 언어로 된 이미지 캡션이 포함된 <a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">오픈 소스 다국어 COCO 데이터셋을</a> 사용하여 두 가지 유형의 검색을 시각화해 보겠습니다:</p><ol><li><p>하나의 영어 데이터 세트에서 다른 언어로 된 쿼리 및 검색어, 그리고</p></li><li><p>여러 언어로 된 문서가 포함된 데이터 집합을 기반으로 여러 언어로 쿼리할 수 있습니다.</p></li></ol><p>그런 다음 하이브리드 검색과 재랭크의 힘을 활용하여 검색 결과를 더욱 개선할 것입니다.</p><h2>필수 구성 요소</h2><ul><li><p>Python 3.6+</p></li><li><p>Elasticsearch 8+</p></li><li><p>Elasticsearch Python 클라이언트: pip 설치 elasticsearch</p></li></ul><h2>데이터 세트</h2><p><a href="https://huggingface.co/datasets/romrawinjp/multilingual-coco">COCO 데이터 세트</a> 는 대규모 캡션 데이터 세트입니다. 데이터 세트의 각 이미지는 여러 언어로 캡션되어 있으며, 언어별로 여러 번역본을 사용할 수 있습니다. 데모용으로 각 번역을 개별 문서로 색인화하여 참조할 수 있도록 가장 먼저 제공되는 영어 번역과 함께 보여드리겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc7e508e9a7dffe8/6a17f3e2b1e113249579f394/d4f0632529c71a22fbdecf21c9f4f0bb64b8e69c-1600x567.png" alt="" /><h3>1단계: 다국어 COCO 데이터 세트 다운로드</h3><p>블로그를 단순화하고 쉽게 따라갈 수 있도록 여기서는 간단한 API 호출을 통해 restval의 처음 100개 행을 로컬 JSON 파일에 로드합니다. 또는 허깅페이스의 라이브러리 데이터셋을 사용하여 전체 데이터셋 또는 데이터셋의 하위 집합을 로드할 수도 있습니다.</p>import requests
import json
import os
### Download multilingual coco dataset into a json file (for easy viewing)
### Here we are retrieving first 100 rows for this example
### Alternatively, you can use `datasets` library from Hugging Face
url = "https://datasets-server.huggingface.co/rows?dataset=romrawinjp%2Fmultilingual-coco&amp;config=default&amp;split=restval&amp;offset=0&amp;length=100"
response = requests.get(url)


if response.status_code == 200:
   data = response.json()
   output_file = "multilingual_coco_sample.json" 
   ### Loading the downloaded content into a json file locally
   with open(output_file, "w", encoding="utf-8") as f:
       json.dump(data, f, indent=4, ensure_ascii=False)
   print(f"Data successfully downloaded and saved to {output_file}")
else:
   print(f"Failed to download data: {response.status_code}")
   print(response.text)<p>데이터가 JSON 파일에 성공적으로 로드되면 다음과 비슷한 내용이 표시됩니다:</p><p><code>Data successfully downloaded and saved to multilingual_coco_sample.json</code></p><h3>2단계: (Elasticsearch를 시작하고) Elasticsearch에서 데이터 색인하기</h3><p>a) 로컬 Elasticsearch 서버를 시작합니다.</p><p>b) Elasticsearch 클라이언트를 시작합니다.</p>from elasticsearch import Elasticsearch
from getpass import getpass


# Initialize Elasticsearch client
es = Elasticsearch(getpass("Host: "), api_key=getpass("API Key: "))


index_name = "coco"


# Create the index if it doesn't exist
if not es.indices.exists(index=index_name):
   es.indices.create(index=index_name, body=mapping)<p>c) 인덱스 데이터</p># Load the JSON data
with open('./multilingual_coco_sample.json', 'r') as f:
   data = json.load(f)


rows = data["rows"]
# List of languages to process
languages = ["en", "es", "de", "it", "vi", "th"]


# For each image, we will process each individual caption as its own document
bulk_data = []
for data in rows:
   row = data["row"]
   image = row.get("image")
   image_url = image["src"]


   # Process each language
   for lang in languages:
       # Skip if language not present in this row
       if lang not in row:
           continue


       # Get all descriptions for this language
 # along with first available English caption for reference
       descriptions = row[lang]
       first_eng_caption = row["en"][0]


       # Prepare bulk indexing data
       for description in descriptions:
           if description == "":
               continue
           # Add index operation
           bulk_data.append(
               {"index": {"_index": index_name}}
           )
           # Add document
           bulk_data.append({
               "language": lang,
               "description": description,
               "en": first_eng_caption,
               "image_url": image_url,
           })


# Perform bulk indexing
if bulk_data:
   try:
       response = es.bulk(operations=bulk_data)
       if response["errors"]:
           print("Some documents failed to index")
       else:
           print(f"Successfully bulk indexed {len(bulk_data)} documents")
   except Exception as e:
       print(f"Error during bulk indexing: {str(e)}")


print("Indexing complete!")<p>데이터가 색인되면 다음과 비슷한 내용이 표시됩니다:</p><p><code>Successfully bulk indexed 4840 documents</code></p><p><code>Indexing complete!</code></p><h3>3단계: E5 학습된 모델 배포하기</h3><p>Kibana에서 스택 관리 &gt; <strong>학습된 모델</strong> 페이지로 이동하고 .multilingual-e5-small_linux-x86_64에 대한 <strong>배포를</strong> 클릭합니다. 옵션을 선택합니다. 이 E5 모델은 Linux-x86_64에 최적화된 소규모 다국어 버전으로, 즉시 사용할 수 있습니다. '배포'를 클릭하면 배포 설정 또는 vCPU 구성을 조정할 수 있는 화면이 표시됩니다. 간단하게 하기 위해 사용량에 따라 배포를 자동으로 확장하는 적응형 리소스를 선택한 기본 옵션을 사용하겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbc09867063a8e6f/6a17f3e3148009a295b4889d/95cd8f352425d1db2d04b00c3c88d1e71d1ef19a-1600x440.png" alt="" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt264a2016341e9b6f/6a17f3f0e8fbce18de3a1aa7/1599d99949dda8267acc58f400a403a3af5373ef-1600x655.png" alt="" /><p>선택적으로 다른 텍스트 임베딩 모델을 사용하려는 경우 사용할 수 있습니다. 예를 들어, BGE-M3를 사용하려면 <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/eland/machine-learning#ml-nlp-pytorch">Elastic의 Eland Python 클라이언트를</a> 사용하여 HuggingFace에서 모델을 가져올 수 있습니다.</p>export MODEL_ID="bge-m3"
export HUB_MODEL_ID="BAAI/bge-m3"
export CLOUD_ID={{CLOUD_ID}}
export ES_API_KEY={{API_KEY}}
docker run -it --rm docker.elastic.co/eland/eland \
eland_import_hub_model --cloud-id $CLOUD_ID --es-api-key $ES_API_KEY --hub-model-id $HUB_MODEL_ID --es-model-id $MODEL_ID --task-type text_embedding --start<p>그런 다음 학습된 모델 페이지로 이동하여 가져온 모델을 원하는 구성으로 배포합니다.</p><h3>4단계: 배포된 모델을 사용하여 원본 데이터에 대한 임베딩을 벡터화하거나 생성합니다.</h3><p>임베딩을 생성하려면 먼저 텍스트를 가져와 추론 텍스트 임베딩 모델을 통해 실행할 수 있는 수집 파이프라인을 만들어야 합니다. 이 작업은 Kibana의 사용자 인터페이스 또는 Elasticsearch의 API를 통해 수행할 수 있습니다.</p><p><strong>Kibana 인터페이스를 통해 이 작업을 수행하려면</strong>, 학습된 모델을 배포한 후 <strong>테스트 </strong>버튼을 클릭합니다. 이렇게 하면 생성된 임베딩을 테스트하고 미리 볼 수 있습니다. <code>coco</code>인덱스에 대한 새 데이터 보기를 만들고, 데이터 보기를 새로 만든 코코 데이터 보기로 설정하고, 필드를 임베딩을 생성할 필드이므로 <code>description</code> 로 설정합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24a2b9a9a5111bbc/6a17f3f13e9e452c7fba15d5/cfe189e13dc118d325e7fb90bdace0c912e29f51-1088x1600.png" alt="" /><p>잘 작동합니다! 이제 수집 파이프라인을 생성하고 원본 문서를 재색인하고 파이프라인을 통과하여 임베딩이 포함된 새 인덱스를 생성할 수 있습니다. <strong>파이프라인 생</strong>성을 클릭하면 임베딩을 만드는 데 필요한 프로세서가 자동으로 채워지는 파이프라인 생성 프로세스를 안내합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte39c5ad52702103d/6a17f3f3e9ea87dd05a9c734/1e043c1c3279b66fbdf19c06b41e76e613043998-1600x1126.png" alt="" /><p>마법사는 데이터를 수집하고 처리하는 동안 장애를 처리하는 데 필요한 프로세서를 자동으로 채울 수도 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63e002a79d177cf4/6a17f3f596142a3e91eb1c48/8804d31b4f869078e3b2245040bbb0ab1720a94a-1600x1084.png" alt="" /><p>이제 수집 파이프라인을 만들어 보겠습니다. 파이프라인의 이름을 <code>coco_e5</code> 으로 지정합니다. 파이프라인이 성공적으로 생성되면, 마법사에서 원래 색인된 데이터를 새 색인으로 재색인하여 임베딩을 생성하는 데 즉시 파이프라인을 사용할 수 있습니다. <strong>색인 재생성을 </strong>클릭하여 프로세스를 시작합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39243d9ad1779fdf/6a17f3f696142a13eaeb1c4c/e34b1b18f5b24420d4581fe4d657c569926c2023-1600x1126.png" alt="" /><h2>보다 복잡한 구성의 경우, Elasticsearch API를 사용할 수 있습니다.</h2><p>일부 모델의 경우 모델 학습 방식에 따라 임베딩을 생성하기 전에 실제 입력에 특정 텍스트를 미리 추가하거나 추가해야 할 수 있으며, 그렇지 않으면 성능이 저하될 수 있습니다.</p><p>예를 들어, e5의 경우 모델은 입력 텍스트가 "passage: {content of passage}". 이를 위해 수집 파이프라인을 활용해 보겠습니다: 새로운 수집 파이프라인 <strong>벡터화_descriptions를</strong> 생성하겠습니다. 이 파이프라인에서는 임시 <code>temp_desc</code> 필드를 새로 만들고, "passage: "를 <code>description</code> 텍스트에 추가하고, 모델에서 <code>temp_desc</code> 을 실행하여 텍스트 임베딩을 생성한 다음 <code>temp_desc</code> 을 삭제합니다.</p>PUT _ingest/pipeline/vectorize_descriptions
{
"description": "Pipeline to run the descriptions text_field through our inference text embedding model",
"processors": [
 {
   "set": {
     "field": "temp_desc",
     "value": "passage: {{description}}"
   }
 },
 {
   "inference": {     
"field_map": {
       "temp_desc": "text_field"
     },
     "model_id": ".multilingual-e5-small_linux-x86_64_search",
     "target_field": "vector_description"
   }
 },
 {
   "remove": {
     "field": "temp_desc"
   }
 }
]
}<p>또한 생성된 벡터에 어떤 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-quantization">양자화 유형을</a> 사용할지 지정할 수도 있습니다. 기본적으로 Elasticsearch는 <code>int8_hnsw</code> 을 사용하지만 여기서는 각 차원을 단일 비트 정밀도로 축소하는 <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">Better Binary Quantization</a> (또는 <code>bqq_hnsw</code>)을 사용하려고 합니다. 이렇게 하면 메모리 사용량이 96% (또는 32배)로 줄어드는 대신 정확도는 더 높아집니다. 이 정량화 유형을 선택한 이유는 나중에 정확도 손실을 개선하기 위해 리랭커를 사용할 것이라는 것을 알고 있기 때문입니다.</p><p>이를 위해 <strong>coco_multi라는</strong> 새 인덱스를 생성하고 매핑을 지정합니다. 여기서 마법은 <strong>벡터_설명</strong>필드에 있으며, 여기서 인덱스_옵션의유형을 <strong>bbq_hnsw로</strong> 지정합니다.</p>PUT coco_multi
{
 "mappings": {
   "properties": {
     "description": {
       "type": "text"
     },
     "en": {
       "type": "text"
     },
     "image_url": {
       "type": "keyword"
     },
     "language": {
       "type": "keyword"
     },
     "vector_description.predicted_value": {
       "type": "dense_vector",
       "dims": 384,
       "index": "true",
       "similarity": "cosine",
       "index_options": {
         "type": "bbq_hnsw" 
       }
     }
   }
 }
}<p>이제 설명 필드를 '벡터화'하거나 임베딩을 생성하는 수집 파이프라인을 사용하여 원본 문서를 새 인덱스로 재색인할 수 있습니다.</p>POST _reindex?wait_for_completion=false
{
 "source": {
   "index": "coco"
 },
 "dest": {
   "index": "coco_multilingual",
   "pipeline": "vectorize_descriptions"
 }
}<p>여기까지입니다! 우리는 Elasticsearch와 Kibana로 다국어 모델을 성공적으로 배포했으며, Kibana 사용자 인터페이스 또는 Elasticsearch API를 통해 Elastic으로 데이터로 벡터 임베딩을 생성하는 방법을 단계별로 배웠습니다. 이 시리즈의 두 번째 파트에서는 다국어 모델 사용의 결과와 뉘앙스에 대해 살펴봅니다. 그 동안 자체 <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> 원하는 언어와 데이터 세트에서 <a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-e5">즉시 사용 가능한 E5 모델을 사용하여 다국어 시맨틱 검색을</a> 사용해 볼 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/multilingual-embedding-model-deployment-elasticsearch</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[운영]]></category>
    <dc:creator><![CDATA[Quynh Nguyen]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59254226694f93a6/6a17f3f81480098988b488a1/8f2aa7bebb6b2f701e274ba7282273f9ab4abed6-720x432.png" length="0" type="image/png"/>
    <pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[벡터 검색 필터링: 관련성 유지]]></title>
    <description><![CDATA[쿼리와 가장 유사한 결과를 찾기 위해 벡터 검색을 수행하는 것만으로는 충분하지 않습니다. 검색 결과의 범위를 좁히기 위해 필터링이 필요한 경우가 많습니다. 이 문서에서는 Elasticsearch와 Apache Lucene에서 벡터 검색을 위한 필터링이 어떻게 작동하는지 설명합니다.]]></description>
    <content:encoded><![CDATA[<p>벡터 검색만으로는 관련 검색 결과를 찾을 수 없습니다. 검색 결과의 범위를 좁히고 관련 없는 결과를 걸러내는 데 도움이 되는 필터링 기준을 사용하는 것은 매우 일반적입니다.</p><p>벡터 검색에서 필터링이 작동하는 방식을 이해하면 성능과 회상률의 균형을 맞추는 데 도움이 될 뿐만 아니라 필터링 사용 시 벡터 검색의 성능을 높이는 데 사용되는 몇 가지 최적화를 알아볼 수 있습니다.</p><h2>왜 필터링할까요?</h2><p>벡터 검색은 대규모 데이터 세트에서 관련 정보를 찾는 방법을 혁신적으로 개선하여 검색어와 의미적으로 유사한 항목을 발견할 수 있게 해줍니다.</p><p>하지만 단순히 비슷한 아이템을 찾는 것만으로는 충분하지 않습니다. 특정 기준이나 속성에 따라 검색 결과의 범위를 좁혀야 하는 경우가 많습니다.</p><p>이커머스 스토어에서 제품을 검색하고 있다고 상상해 보세요. 순수한 벡터 검색은 시각적으로 유사한 상품을 보여줄 수 있지만 가격대, 브랜드, 재고 여부 또는 고객 평점을 기준으로 필터링할 수도 있습니다. 필터링이 없으면 유사한 상품이 너무 많이 표시되어 원하는 상품을 정확히 찾기 어렵습니다.</p><p>필터링을 통해 검색 결과를 정밀하게 제어할 수 있으므로 검색된 항목이 의미적으로 일치할 뿐만 아니라 필요한 모든 요건을 충족하는지 확인할 수 있습니다. 이를 통해 훨씬 더 정확하고 효율적이며 사용자 친화적인 검색 환경을 제공합니다.</p><p>다양한 데이터 유형에 걸쳐 효과적인 필터링을 사용하는 것이 다른 벡터 데이터베이스와의 주요 차이점 중 하나인 Elasticsearch와 Apache Lucene의 장점입니다.</p><h2>정확한 벡터 검색을 위한 필터링</h2><p>정확한 벡터 검색을 수행하는 방법에는 크게 두 가지가 있습니다:</p><ul><li><p>dense_vector 필드에 <code>flat</code> 인덱스 유형을 사용합니다. 따라서 <code>knn</code> 검색은 대략적인 검색이 아닌 정확한 검색을 사용합니다.</p></li><li><p>벡터 함수를 사용하여 점수를 계산하는 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-script-score-query#vector-functions">스크립트_점수 쿼리를</a> 사용합니다. 모든 인덱스 유형에 사용할 수 있습니다.</p></li></ul><p>정확한 벡터 검색을 실행할 때는 모든 벡터가 쿼리와 비교됩니다. 이 시나리오에서는 필터를 통과한 벡터만 비교하면 되므로 필터링이 성능에 도움이 됩니다.</p><p>어쨌든 모든 벡터가 고려되므로 결과 품질에는 영향을 미치지 않습니다. 흥미롭지 않은 결과를 미리 필터링하여 작업 횟수를 줄일 수 있습니다.</p><p>적용된 필터로 인해 문서 수가 적은 경우 대략적인 검색 대신 정확한 검색을 실행하는 것이 더 효율적일 수 있으므로 이는 매우 중요합니다.</p><p>필터를 통과하는 문서가 1만 개 미만인 경우 정확한 검색을 사용하는 것이 좋습니다. <a href="https://www.elastic.co/search-labs/blog/better-binary-quantization-lucene-elasticsearch">BBQ</a> 인덱스가 비교에 훨씬 빠르므로 기준 인덱스가 10만 개 미만일 때는 정확한 검색을 사용하는 것이 좋습니다. 자세한 내용은 <a href="https://www.elastic.co/search-labs/blog/knn-exact-vs-approximate-search">이 블로그 게시물을</a> 확인하세요.</p><p>필터가 항상 매우 제한적인 경우에는 HNSW 기반 인덱스 유형 대신 <code>flat</code> 인덱스 유형을 사용하여 대략적인 검색 대신 정확한 검색에 초점을 맞춘 인덱싱을 고려할 수 있습니다. 자세한 내용은 <a href="https://www.elastic.co/docs/reference/elasticsearch/mapping-reference/dense-vector#dense-vector-params">index_options의 속성을</a> 참조하세요.</p><h2>대략적인 벡터 검색을 위한 필터링</h2><p>근사 벡터 검색을 실행할 때는 결과 정확도와 성능을 맞바꿉니다. HNSW와 같은 벡터 검색 데이터 구조는 수백만 개의 벡터에서 대략적인 가장 가까운 이웃을 효율적으로 검색합니다. 계산 비용이 많이 드는 벡터 비교를 최소한으로 수행하여 가장 유사한 벡터를 검색하는 데 중점을 둡니다.</p><p>즉, 다른 필터링 속성은 벡터 데이터의 일부가 아닙니다. 용어 사전, 게시 목록, 문서 값 등 데이터 유형마다 이를 찾고 필터링하는 데 효율적인 자체 인덱싱 구조가 있습니다.</p><p>이러한 데이터 구조가 벡터 검색 메커니즘과 분리되어 있다면 벡터 검색에 필터링을 적용하려면 어떻게 해야 할까요? 벡터 검색 후 필터를 적용하거나(사후 필터링) 벡터 검색 전에 필터를 적용하는(사전 필터링) 두 가지 옵션이 있습니다.</p><p>각 옵션에는 장단점이 있습니다. 더 자세히 알아봅시다!</p><h3>사후 필터링</h3><p>사후 필터링은 벡터 검색이 완료된 후 필터를 적용합니다. 즉, 필터는 가장 유사한 벡터 결과 상위 k개를 찾은 후에 적용됩니다.</p><p>물론 결과에 필터를 적용한 후 잠재적으로 k보다 적은 결과를 얻을 수 있습니다. 물론 벡터 검색에서 더 많은 결과를 검색할 수 있지만(k 값이 높을수록) 필터를 적용한 후에도 k 이상의 결과를 얻을 수 있을지는 확신할 수 없습니다.</p><p>사후 필터링의 장점은 벡터 검색의 런타임 동작을 변경하지 않는다는 점입니다. 벡터 검색은 필터링을 인식하지 못합니다. 하지만 검색되는 최종 결과 수는 변경됩니다.</p><p>다음은 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query">knn 쿼리를</a> 사용한 사후 필터링의 예입니다. 필터링 절이 knn 쿼리와 분리되어 있는지 확인합니다:</p>{
  "query": {
    "bool": {
      "must": {
        "knn": {
          "field": "image-vector",
          "query_vector": [54, 10, -2],
          "k": 5,
          "num_candidates": 50
        }
      },
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<p><a href="https://www.elastic.co/docs/reference/elasticsearch/rest-apis/filter-search-results#post-filter">포스트 필터를</a> 사용하여 KNN 검색에 포스트 필터링도 사용할 수 있습니다:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, 2],
    "k": 5,
    "num_candidates": 50
  },
  "post_filter": {
    "term": {
      "file-type": "png"
    }
  }
}<p>knn 검색에 명시적인 사후 필터 섹션을 사용해야 한다는 점에 유의하세요. 사후 필터를 사용하지 않는 경우 knn 검색은 <a href="https://www.elastic.co/docs/solutions/search/vector/knn#_combine_approximate_knn_with_other_features">사후 필터를 수행하는 대신 가장</a> 가까운 이웃 검색 결과를 다른 쿼리 또는 필터와 결합합니다.</p><h3>사전 필터링</h3><p>벡터 검색 전에 필터를 적용하면 먼저 필터를 충족하는 문서를 검색한 다음 해당 정보를 벡터 검색에 전달합니다.</p><p>Lucene은 <a href="https://github.com/apache/lucene/blob/7a60d7ce92392181e137361336e5196bd486cdd9/lucene/core/src/java/org/apache/lucene/util/BitSet.java">비트셋을</a> 사용하여 필터 조건을 충족하는 문서를 효율적으로 저장합니다. 그런 다음 벡터 검색은 조건을 충족하는 문서를 고려하여 HNSW 그래프를 탐색합니다. 결과에 후보를 추가하기 전에 유효한 문서의 비트 집합에 포함되어 있는지 확인합니다.</p><p>그러나 유효한 문서가 아니더라도 후보를 탐색하고 쿼리와 비교해야 합니다. HNSW의 효과는 그래프에서 벡터 간의 연결에 따라 달라지는데, 한 후보 탐색을 중단하면 이웃 후보도 건너뛸 수 있습니다.</p><p>주유소에 가기 위해 운전한다고 생각하세요. 주유소가 없는 도로를 버리면 목적지까지 갈 수 없을 가능성이 높습니다. 다른 길은 내가 원하는 길이 아닐 수도 있지만 목적지까지 <em>연결해</em> 줍니다. HNSW 그래프의 벡터도 마찬가지입니다!</p><p>따라서 사전 필터링을 적용하는 것이 필터를 적용하지 않는 것보다 성능이 떨어집니다. 검색에서 방문하는 <em>모든</em> 벡터에 대한 작업을 수행해야 하며 필터와 일치하지 않는 벡터는 버려야 합니다. 최고의 결과를 얻기 위해 더 많은 노력을 기울이고 더 많은 시간을 투자하고 있습니다.</p><p>다음은 Elasticsearch 쿼리 DSL에서 사전 필터링의 예입니다. 필터링 절이 이제 knn 섹션의 일부가 되었는지 확인합니다:</p>{
  "knn": {
    "field": "image-vector",
    "query_vector": [54, 10, -2],
    "k": 5,
    "num_candidates": 50,
    "filter": {
      "term": {
        "file-type": "png"
      }
    }
  }
}<p>사전 필터링은 <a href="https://www.elastic.co/docs/solutions/search/vector/knn#knn-search-filter-example">knn 검색과</a> <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-knn-query#knn-query-filtering">knn 쿼리</a> 모두에 사용할 수 있습니다:</p>{
  "query": {
    "knn": {
      "field": "image-vector",
      "query_vector": [-5, 9, -12],
      "k": 5,
      "filter": {
        "term": {
          "file-type": "png"
        }
      }
    }
  }
}<h4>사전 필터링 최적화</h4><p>사전 필터링의 성능을 보장하기 위해 적용할 수 있는 몇 가지 최적화가 있습니다.</p><p>필터가 매우 제한적인 경우 정확한 검색으로 전환할 수 있습니다. 비교할 벡터가 적을 때는 필터를 만족하는 소수의 문서에 대해 정확한 검색을 수행하는 것이 더 빠릅니다.</p><p>이것은 <a href="https://github.com/apache/lucene/blob/eb876b618da5d04c1ad14b04a48321638318493a/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L218">Lucene과</a> Elasticsearch에서 자동으로 적용되는 최적화입니다.</p><p>또 다른 최적화 방법은 필터를 만족하지 않는 벡터를 무시하는 것입니다. 대신 이 메서드는 필터를 통과한 필터링된 벡터의 이웃을 확인합니다. 이 접근 방식은 필터링된 벡터를 고려하지 않고 현재 경로에 연결된 벡터를 계속 탐색하므로 비교 횟수를 효과적으로 줄일 수 있습니다.</p><p>이 알고리즘은 ACORN-1이며, 그 과정은 <a href="https://www.elastic.co/search-labs/blog/filtered-hnsw-knn-search">이 블로그 게시물에</a> 자세히 설명되어 있습니다.</p><h2>문서 수준 보안을 사용한 필터링</h2><p><a href="https://www.elastic.co/docs/deploy-manage/users-roles/cluster-or-deployment-auth/controlling-access-at-document-field-level#document-level-security">DLS(문서 수준 보안)</a> 는 사용자 역할이 검색할 수 있는 문서를 지정하는 Elasticsearch 기능입니다.</p><p>DLS는 쿼리를 사용하여 수행됩니다. 역할에는 인덱스와 연결된 쿼리가 있을 수 있으며, 이 쿼리는 해당 역할에 속한 사용자가 인덱스에서 검색할 수 있는 문서를 효과적으로 제한합니다.</p><p>역할 쿼리는 필터로 사용되어 <a href="https://github.com/elastic/elasticsearch/blob/c3a1cb34294e902a9f46d7e840ea09965019f456/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/SecurityIndexReaderWrapper.java#L92">일치하는 문서를 검색하고</a> 비트셋으로 캐시됩니다. 그런 다음 이 비트셋은 기본 Lucene 리더를 래핑하는 데 사용되므로 쿼리에서 반환된 문서, 즉 인덱스에 존재하고 삭제되지 않은 문서만 <em>라이브</em>문서로 간주됩니다.</p><p>knn 쿼리를 수행하기 위해 리더에서 라이브 문서가 검색되므로 사용자가 사용할 <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L196">수 있는 문서만 고려됩니다.</a> 프리필터가 있는 경우 DLS 문서가 프리필터에 <a href="https://github.com/apache/lucene/blob/a211d30097a8e3264d3ef073a054bd31eb847231/lucene/core/src/java/org/apache/lucene/search/AbstractKnnVectorQuery.java#L204">추가됩니다</a>.</p><p>즉, DLS 필터링은 근사 벡터 검색을 위한 프리필터로 작동하며 성능에 미치는 영향과 최적화가 동일합니다.</p><p>정확한 검색을 사용하는 DLS는 필터를 적용하는 것과 동일한 이점이 있습니다. DLS에서 검색되는 문서가 적을수록 정확한 검색의 성능이 향상됩니다. DLS 역할이 매우 제한적인 경우 대략적인 검색 대신 정확한 검색을 사용하는 것도 고려할 수 있습니다.</p><h2>벤치마킹</h2><p>Elasticsearch에서는 벡터 검색 필터링이 효율적인지 확인하고자 합니다. 벡터 <a href="https://elasticsearch-benchmarks.elastic.co/#tracks/so_vector/nightly/default/90d">필터링에 대한 특정 벤치마크가</a> 있어 다양한 필터링을 통해 대략적인 벡터 검색을 수행하여 벡터 검색이 관련성 있는 결과를 최대한 빠르게 검색할 수 있도록 합니다.</p><p>ACORN-1 도입 시 <a href="https://elasticsearch-benchmark-analytics.elastic.co/app/dashboards#/view/43b63e80-5ba2-11ed-aede-a742809feed4?_g=(refreshInterval:(pause:!t,value:60000),time:(from:'2025-05-28T01:27:58.456Z',to:'2025-06-30T13:53:26.430Z'))&amp;_a=()">개선</a> 사항을 확인하세요. 필터를 통과한 벡터가 2개% 뿐인 테스트의 경우 쿼리 지연 시간은 원래 지속 시간의 55% 로 줄어듭니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt820cb0b715cf291c/6a17e227dbb4ff8d49fb5615/3eac3748a33376fc97d957364a5c1f5108d5c58b-1023x896.png" alt="" /><h2>결론</h2><p>필터링은 검색의 필수적인 부분입니다. 벡터 검색에서 필터링이 제대로 작동하는지 확인하고 장단점과 최적화를 이해하는 것이 효율적이고 정확한 검색의 성패를 좌우합니다.</p><p>필터링은 벡터 검색의 성능에 영향을 미칩니다:</p><ul><li><p>필터링을 사용하면 정확한 검색이 더 빠릅니다. 필터링이 충분히 제한적인 경우 대략적인 검색 대신 정확한 검색을 사용하는 것이 좋습니다. 이것은 Elasticsearch의 자동 최적화입니다.</p></li><li><p>사전 필터링 사용 시 대략적인 검색 속도가 느려집니다. 사전 필터링을 사용하면 검색 속도가 느려지는 대신 필터와 일치하는 상위 k개의 결과를 얻을 수 있습니다.</p></li><li><p>사후 필터링은 필터를 적용할 때 필터를 통해 필터링될 수 있으므로 반드시 상위 k개의 결과를 검색하지는 않습니다.</p></li></ul><p>행복한 필터링!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-search-filtering</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-search-filtering</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Lucene]]></category>
    <category><![CDATA[Elastic 내부]]></category>
    <dc:creator><![CDATA[Carlos Delgado]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39ede1736e0f1456/6a17e2282f4a5c5031fa8843/03b1dd4c7bda4fbabd8e374bc2e4f12d5be6ef5f-1600x1150.png" length="0" type="image/png"/>
    <pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[임베딩을 Elasticsearch 필드 유형에 매핑하기: semantic_text, dense_vector, sparse_vector]]></title>
    <description><![CDATA[semantic_text, dense_vector 또는 sparse_vector를 사용하는 방법과 시기, 그리고 임베딩 생성과의 관계에 대해 논의합니다.]]></description>
    <content:encoded><![CDATA[<p>정보 검색의 관련성과 정확성을 높이기 위한 임베딩의 사용은 지난 몇 년 동안 크게 증가했습니다. Elasticsearch와 같은 도구는 밀집 벡터, 희소 벡터, 시맨틱 텍스트와 같은 특수한 필드 유형을 통해 이러한 유형의 데이터를 지원하도록 발전해 왔습니다. 그러나 좋은 결과를 얻으려면 임베딩을 사용 가능한 Elasticsearch 필드 유형에 올바르게 매핑하는 방법을 이해하는 것이 필수적입니다: <code>semantic_text</code>, <code>dense_vector</code>, 및 <code>sparse_vector</code> 을 참조하세요.</p><p>이 문서에서는 이러한 필드 유형, 각 필드 유형이 언제 사용되는지, 색인 및 쿼리 중 임베딩 생성 및 사용 전략과 어떻게 연관되는지에 대해 설명합니다.</p><h2>고밀도 벡터 유형</h2><p>Elasticsearch의 <code>dense_vector</code> 필드 유형은 거의 모든 차원이 관련된 텍스트, 이미지, 오디오와 같은 데이터의 숫자 표현인 고밀도 벡터를 저장하는 데 사용됩니다. 이러한 벡터는 OpenAI, Cohere 또는 Hugging Face와 같은 플랫폼에서 제공하는 임베딩 모델을 사용하여 생성되며, 다른 문서와 정확한 용어를 공유하지 않더라도 데이터의 전체적인 의미적 의미를 포착하도록 설계되었습니다.</p><p>Elasticsearch에서 고밀도 벡터는 사용되는 모델에 따라 최대 4096개의 차원을 가질 수 있습니다. 예를 들어, 모든 MiniLM-L6-v2 모델은 384차원의 벡터를 생성하는 반면, OpenAI의 텍스트 임베딩-ada-002는 1536차원의 벡터를 생성합니다.</p><p><code>dense_vector</code> 필드는 사전 생성된 벡터를 사용하거나 사용자 정의 유사성 함수를 적용하거나 외부 모델과 통합하는 등 보다 강력한 제어가 필요한 경우 이러한 종류의 임베딩을 저장하는 기본 유형으로 일반적으로 채택됩니다.</p><h3>dense_vector 유형은 언제, 왜 사용하나요?</h3><p>고밀도 벡터는 문장, 단락 또는 전체 문서 간의 의미적 유사성을 포착하는 데 탁월합니다. 같은 용어가 아니더라도 텍스트의 전체적인 의미를 비교하는 것이 목표일 때 매우 효과적입니다.</p><p>고밀도 벡터 필드는 OpenAI, Cohere 또는 Hugging Face와 같은 플랫폼에서 제공하는 모델을 사용하는 외부 임베딩 생성 파이프라인이 이미 있고 이러한 벡터를 수동으로만 저장하고 쿼리하려는 경우에 이상적입니다. 이 유형의 필드는 임베딩 모델과의 호환성이 높고 생성 및 쿼리에서 완전한 유연성을 제공하므로 검색 중에 벡터를 생성, 색인 및 사용하는 방법을 제어할 수 있습니다.</p><p>또한 순위 로직을 조정해야 하는 경우를 위해 k-NN 또는 script_score와 같은 쿼리를 사용하여 다양한 형태의 시맨틱 검색을 지원합니다. 이러한 가능성으로 인해 고밀도 벡터는 검색 증강 세대(RAG), 추천 시스템, 유사도에 기반한 개인화된 검색과 같은 애플리케이션에 이상적입니다.</p><p>마지막으로 이 필드에서는 <code>cosineSimilarity</code>, <code>dotProduct</code> 또는 <code>l2norm</code> 와 같은 기능을 사용하여 관련성 로직을 사용자 지정하여 사용 사례의 필요에 따라 순위를 조정할 수 있습니다. </p><p>고밀도 벡터는 위에서 언급한 고급 사용 사례와 같은 유연성, 사용자 지정 및 호환성이 필요한 사용자에게 여전히 최고의 옵션입니다.</p><h3>고밀도 벡터 유형에 쿼리를 사용하는 방법은 무엇인가요?</h3><p><strong><code>dense_vector</code></strong> 로 정의된 필드에 대한 검색은 K-최근 이웃 쿼리를 사용합니다. 이 쿼리는 밀도 벡터가 쿼리 벡터에 가장 가까운 문서를 찾는 작업을 담당합니다. 다음은 고밀도 벡터 필드에 k-NN 쿼리를 적용하는 방법의 예시입니다:</p>{
  "knn": {
    "field": "my_dense_vector",
    "k": 10,
    "num_candidates": 50,
    "query_vector": [/* vector generated by model */]
  }
}<p>k-NN 쿼리 외에도 문서 점수를 사용자 정의할 필요가 있는 경우, 스크립트_스코어 쿼리를 사용하여 <strong>코사인 유사도, dotProduct 또는 l2norm과</strong> 같은 벡터 비교 함수와 결합하여 보다 제어된 방식으로 관련성을 계산할 수도 있습니다. 예시를 참조하세요:</p>{
"script_score": {
    "query": { "match_all": {} },
    "script": {
      "source": "cosineSimilarity(params.query_vector,
'my_dense_vector') + 1.0",
      "params": {
        "query_vector": [/* vector */]
      }
    }
  }
}<p>더 자세히 알아보고 싶으시다면 <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">Elasticsearch에서 벡터 검색을 설정하는 방법</a>문서를 살펴보는 것을 추천합니다.</p><p></p><h2>희소 벡터 유형</h2><p><strong><code>sparse_vector</code></strong> 필드 유형은 대부분의 값이 0이고 일부 용어에만 가중치가 있는 숫자 표현인 스파스 벡터를 저장하는 데 사용됩니다. 이 유형의 벡터는 SPLADE 또는 ELSER(Elastic Learned Sparse EncodeR)와 같은 용어 기반 모델에서 흔히 볼 수 있습니다.</p><h3>희소 벡터 유형을 언제, 왜 사용해야 하나요?</h3><p>스파스 벡터는 의미론적 지능을 유지하면서 어휘를 보다 정밀하게 검색해야 할 때 이상적입니다. 토큰/값 쌍으로 텍스트를 표시하고 관련 가중치가 있는 가장 관련성이 높은 용어만 강조 표시하여 명확성, 제어 및 효율성을 제공합니다.</p><p>이 유형의 필드는 텍스트에서 상대적 중요도에 따라 각 토큰에 다른 가중치를 할당하는 ELSER 또는 SPLADE 모델과 같이 용어를 기반으로 벡터를 생성할 때 특히 유용합니다.</p><p>쿼리에서 특정 단어의 영향력을 제어하려는 경우 희소 벡터 유형을 사용하면 용어의 가중치를 수동으로 조정하여 결과의 순위를 최적화할 수 있습니다.</p><p>주요 이점으로는 문서가 관련성이 있는 것으로 간주된 이유를 명확하게 이해할 수 있으므로 검색의 투명성과 모든 차원을 저장하는 고밀도 벡터와 달리 0이 아닌 값을 가진 토큰만 저장되므로 저장 효율성이 높다는 점이 있습니다.</p><p>또한, 스파스 벡터는 하이브리드 검색 전략에서 이상적인 보완재이며, 밀도 벡터와 결합하여 어휘 정확도와 의미 이해를 결합할 수도 있습니다.</p><h3>희소 벡터 유형에 쿼리를 사용하는 방법은 무엇인가요?</h3><p><strong><code>sparse_vector</code></strong> 쿼리를 사용하면 토큰/값 형식의 쿼리 벡터를 기반으로 문서를 검색할 수 있습니다. 아래 쿼리 예시를 참조하세요:</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "query_vector": {
        "token1": 0.6,
        "token2": 0.2,
        "token3": 0.9
      }
    }
  }
}<p>학습된 모델을 사용하려는 경우 쿼리 텍스트를 스파스 벡터로 자동 변환하는 추론 엔드포인트를 사용할 수 있습니다:</p>{
  "query": {
    "sparse_vector": {
      "field": "field_sparse",
      "inference_id": "the inference ID to produce the token/weights",
      "query": "search text"
    }
  }
}<p>이 주제를 더 자세히 알아보려면 <a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">학습된 ML 모델을 사용한 희소 벡터 임베딩 이해를</a> 읽어보시기 바랍니다.</p><h2>시맨틱 텍스트 유형</h2><p><strong><code>semantic_text</code></strong> 필드 유형은 Elasticsearch에서 시맨틱 검색을 사용하는 가장 간단하고 직관적인 방법입니다. 추론 엔드포인트를 통해 인덱싱 및 쿼리 시점에 임베딩 생성을 자동으로 처리합니다. 즉, 벡터를 수동으로 생성하거나 저장하는 것에 대해 걱정할 필요가 없습니다.</p><h3>시맨틱 텍스트는 언제, 왜 사용해야 하나요?</h3><p><code>semantic_text</code> 필드는 벡터를 수동으로 처리할 필요 없이 최소한의 기술적 노력으로 시작하고 싶은 분들에게 이상적입니다. 이 필드에서는 임베딩 생성 및 벡터 검색 매핑과 같은 단계를 자동화하여 더 빠르고 편리하게 설정할 수 있습니다.</p><p><strong>매핑, 임베딩 생성 및 수집 파이프라인을 수동으로 구성해야</strong> <strong>하는 복잡성을</strong> 제거하므로 단순성과 추상화를 중시하는 경우 사용을 고려해야 합니다.<code>semantic_text</code> 추론 모델을 선택하기만 하면 나머지는 Elasticsearch가 알아서 처리합니다.</p><p>주요 장점으로는 인덱싱과 쿼리 중에 수행되는 <strong>자동 임베딩 생성과</strong> 선택한 추론 모델을 지원하도록 사전 구성되어 <strong>바로 사용할 수 있는 매핑이</strong> 있습니다.</p><p>또한 이 필드에서는 <strong>긴 텍스트의 자동 분할(텍스트 청킹)을 기본적으로 지원하여</strong> 큰 텍스트를 각각 임베딩된 작은 구절로 나눌 수 있으므로 검색 정확도가 향상됩니다. 이는 특히 시맨틱 검색의 기본 엔지니어링을 다루지 않고도 빠르게 가치를 제공하고자 하는 팀의 생산성을 크게 향상시킵니다.</p><p>하지만 <code>semantic_text</code> 은 속도와 간편함을 제공하지만 이 접근 방식에는 몇 가지 한계가 있습니다. 시장 표준 모델을 사용할 수 있으며, Elasticsearch에서 추론 엔드포인트로 사용할 수 있는 한 사용할 수 있습니다. 그러나 <code>dense_vector</code> 필드에서 가능한 것처럼 <strong>외부에서 생성된 임베딩은 지원하지 않습니다</strong>.</p><p>벡터 생성 방식을 더 잘 제어하고 싶거나, 자체 임베딩을 사용하거나, 고급 전략을 위해 여러 필드를 결합해야 하는 경우 <code>dense_vector</code> 및 <code>sparse_vector</code> 필드는 보다 맞춤화된 또는 도메인별 시나리오에 필요한 유연성을 제공합니다.</p><h3>시맨틱 텍스트 유형에 쿼리를 사용하는 방법</h3><p><strong><code>semantic_text</code></strong> 이전에는 임베딩 유형(밀도형 또는 희소형)에 따라 다른 쿼리를 사용해야 했습니다. 희소 필드에는 <code>sparse_vector</code> 쿼리가 사용되었고, <code>dense_vector</code> 필드에는 KNN 쿼리가 필요했습니다.</p><p>시맨틱 텍스트 유형에서는 쿼리 벡터를 자동으로 생성하고 색인된 문서의 임베딩과 비교하는 <a href="https://www.elastic.co/docs/reference/query-languages/query-dsl/query-dsl-semantic-query">시맨틱 쿼리를</a> 사용하여 검색을 수행합니다. <strong><code>semantic_text</code></strong> 유형을 사용하면 쿼리를 포함할 추론 엔드포인트를 정의할 수 있지만, 아무것도 지정하지 않으면 인덱싱 중에 사용된 것과 동일한 엔드포인트가 쿼리에 적용됩니다.</p>{
  "query": {
    "semantic": {
      "field": "semantic_text_field",
      "query": "search text"
    }
  }
}<p>자세한 내용은 <a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch의 새로운 semantic_text 매핑 문서를 읽어보시기 바랍니다: 시맨틱 검색</a> 간소화.</p><h2>결론</h2><p>Elasticsearch에서 임베딩을 매핑하는 방법을 선택할 때는 벡터를 생성하는 방법과 벡터에 대해 필요한 제어 수준을 이해하는 것이 중요합니다. 시맨틱 텍스트 필드를 사용하면 자동 및 확장 가능한 시맨틱 검색이 가능하므로 많은 초기 사용 사례에 이상적입니다. 더 많은 제어, 미세 조정된 성능 또는 사용자 지정 모델과의 통합이 필요한 경우 고밀도 벡터 및 희소 벡터 필드는 필요한 유연성을 제공합니다.</p><p>이상적인 필드 유형은 사용 사례, 사용 가능한 인프라, 머신 러닝 스택의 성숙도에 따라 달라집니다. 가장 중요한 것은 Elastic이 현대적이고 적응력이 뛰어난 검색 시스템을 구축할 수 있는 도구를 제공한다는 점입니다.</p><h2>참고 자료</h2><ul><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">시맨틱 텍스트 필드 유형</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/sparse-vector.html">희소 벡터 필드 유형</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">고밀도 벡터 필드 유형</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">시맨틱 쿼리</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-sparse-vector-query.html">희소 벡터 쿼리</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">kNN 검색</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch의 새로운 의미론적 텍스트 매핑: 시맨틱 검색 간소화</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/sparse-vector-embedding">학습된 ML 모델을 사용한 스파스 벡터 임베딩 이해하기</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/mapping-embeddings-to-elasticsearch-field-types</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[매핑]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72cd3c2601b22886/6a17083b0c4857259901a9dc/f98fdff837db55b466780c0bae672aa6f6c3a966-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 13 May 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[더 나은 바이너리 정량화(BBQ)를 사용 사례에 구현하는 방법]]></title>
    <description><![CDATA[사용 사례에서 더 나은 이진 정량화(BBQ)를 구현하는 이유와 그 방법을 살펴보세요.]]></description>
    <content:encoded><![CDATA[<p>벡터 검색은 텍스트에 대한 시맨틱 검색이나 이미지, 동영상 또는 오디오에 대한 유사도 검색을 구현할 때 기초를 제공합니다. 벡터 검색에서 벡터는 방대하고 때로는 느릴 수 있는 데이터를 수학적으로 표현한 것입니다. 더 나은 이진 양자화(이하 BBQ)는 벡터의 압축 방법으로 작동합니다. 벡터를 축소하여 검색 및 처리 속도를 높이면서 적합한 일치 항목을 찾을 수 있습니다. 이 글에서는 벡터를 자동으로 채점하는 정량화된 인덱스에만 사용할 수 있는 필드인 BBQ와 rescore_vector에 대해 설명합니다.</p><p>이 문서에 언급된 모든 전체 쿼리와 출력은 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/how-and-why-bbq">Elasticsearch Labs 코드 리포지토리에서</a> 확인할 수 있습니다.</p><h2>사용 사례에서 더 나은 이진 정량화(BBQ)를 구현하는 이유는 무엇인가요?</h2>참고: BBQ의 수학적 원리에 대한 자세한 내용은 아래의 <a href="https://www.elastic.co/kr/search-labs/blog/bbq-implementation-into-use-case#further-learning">'추가 학습' 섹션을</a> 참조하세요. 이 블로그에서는 구현에 초점을 맞추고 있습니다.<p>수학은 흥미롭지만, 벡터 검색의 정확성을 유지하려면 그 이유를 완전히 파악하는 것이 중요합니다. 현재의 벡터 검색 알고리즘으로는 데이터 읽기 속도에 제한이 있다는 것이 밝혀졌기 때문에 궁극적으로 이것은 압축에 관한 것입니다. 따라서 해당 데이터를 모두 메모리에 넣을 수 있다면 스토리지에서 읽을 때보다 속도가 크게 향상됩니다<a href="https://sre.google/static/pdf/rule-of-thumb-latency-numbers-letter.pdf">(메모리는 SSD보다 약 200배 빠릅니다</a>).</p><p>몇 가지 유의해야 할 사항이 있습니다:</p><ul><li><p><a href="https://arxiv.org/pdf/1603.09320">HNSW</a> (계층 탐색 가능한 작은 세계)와 같은 그래프 기반 인덱스는 벡터 검색에 가장 빠릅니다.</p><ul><li><p>HNSW: 다층 그래프 구조를 구축하여 효율적인 고차원 유사도 검색을 가능하게 하는 근사 근사 이웃 검색 알고리즘입니다.</p></li></ul></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt760bd95c206bfa8f/6a17e2ad505ac393f7ad8a95/590f3b3c72a76023a38a0436cd9ff90a9f80e936-1964x1262.png" alt="HNSW: 다층 그래프 구조를 구축하여 효율적인 고차원 유사도 검색을 가능하게 하는 근사 근사 이웃 검색 알고리즘입니다." /><ul><li><p>HNSW는 메모리 또는 최악의 경우 스토리지에서 데이터를 읽는 속도에 의해 근본적으로 속도가 제한됩니다.</p><ul><li><p>이상적으로는 저장된 모든 벡터를 메모리에 로드할 수 있어야 합니다.</p></li></ul></li><li><p>임베딩 모델은 일반적으로 부동 소수점 숫자당 4바이트의 플로트32 정밀도를 가진 벡터를 생성합니다.</p></li><li><p>마지막으로, 보유하고 있는 벡터 및/또는 치수의 수에 따라 모든 벡터를 저장하기 위한 메모리가 매우 빠르게 부족해질 수 있습니다.</p></li></ul><p>이를 당연하게 생각하면, 수백 또는 수천 개의 차원을 가진 수백만 또는 수십억 개의 벡터를 수집하기 시작하면 문제가 빠르게 발생한다는 것을 알 수 있습니다. '<a href="https://www.elastic.co/kr/search-labs/blog/bbq-implementation-into-use-case#approximate-numbers-on-the-compression-ratios">압축률에 대한 대략적인</a>수치'라는 제목의 섹션에서 대략적인 수치를 확인할 수 있습니다.</p><h2>시작하려면 무엇이 필요하나요?</h2><p>시작하려면 다음이 필요합니다:</p><ul><li><p>Elastic Cloud 또는 온프레미스를 사용하는 경우, 8.18 이상의 Elasticsearch 버전이 필요합니다. BBQ는 8.16에 도입되었지만, 이 글에서는 8.18에 도입된 <code>vector_rescore</code> 을 사용합니다.</p></li><li><p>또한 클러스터에 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/ml-settings.html">머신 러닝(ML) 노드가</a> 있는지 확인해야 합니다. (참고: 모델을 로드하려면 최소 4GB의 ML 노드가 필요하지만 전체 프로덕션 워크로드에는 훨씬 더 큰 노드가 필요할 수 있습니다.)</p></li><li><p>서버리스를 사용하는 경우 벡터에 최적화된 인스턴스를 선택해야 합니다.</p></li><li><p>또한 벡터 데이터베이스에 대한 기본 지식이 필요합니다. Elastic의 벡터 검색 개념에 아직 익숙하지 않으시다면 먼저 다음 리소스를 확인해보시는 것이 좋습니다:</p><ul><li><p><a href="https://www.elastic.co/kr/search-labs/blog/elastic-vector-database-practical-example">Elastic Vector 데이터베이스 탐색</a></p></li><li><p><a href="https://www.elastic.co/kr/blog/retrieval-augmented-generation-explained">검색 증강 생성의 핵심 아이디어</a></p></li></ul></li></ul><h2>더 나은 바이너리 정량화(BBQ) 구현</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18df00df95ff2ca7/6a17e2af414c6411989450df/4d388078495566f0527e931e0c2e38facdce83c6-1503x748.png" alt="Elasticsearch bbq 구현." /><p>이 블로그는 간결하게 유지하기 위해 기본 제공 기능을 사용할 수 있는 경우 이를 사용합니다. 이 경우, 머신 러닝 노드에서 Elasticsearch 내부에서 직접 실행되는 <a href="https://www.elastic.co/kr/guide/en/machine-learning/8.17/ml-nlp-e5.html"><code>.multilingual-e5-small</code></a> 벡터 임베딩 모델이 있습니다. <code>text_embedding</code> 모델을 원하는 임베더<a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/infer-service-openai.html">(OpenAI</a>, <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/infer-service-google-ai-studio.html">Google AI Studio</a>, <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/infer-service-cohere.html">Cohere</a> 등)로 대체할 수 있습니다. 선호하는 모델이 아직 통합되지 않은 경우, <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/bring-your-own-vectors.html">자체 고밀도 벡터 임베딩을 가져올</a> 수도 있습니다.)</p><p>먼저, 주어진 텍스트에 대한 벡터를 생성하기 위해 추론 엔드포인트를 만들어야 합니다. 이 모든 명령은 Kibana <a href="https://www.elastic.co/kr/guide/en/kibana/8.18/console-kibana.html">개발자 도구 콘솔에서</a> 실행합니다. 이 명령은 <code>.multilingual-e5-small</code> 을 다운로드합니다. 아직 존재하지 않으면 엔드포인트가 설정되며, 실행하는 데 1분 정도 걸릴 수 있습니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/01-create-an-inference-endpoint-output.json">01-create-an-inference-endpoint-output.json</a> 파일에서 확인할 수 있습니다. </p>PUT _inference/text_embedding/my_e5_model
{
  "service": "elasticsearch",
  "service_settings": {
    "num_threads": 1,
    "model_id": ".multilingual-e5-small",
    "adaptive_allocations": {
      "enabled": true,
      "min_number_of_allocations": 1
    }
  }
}<p>반환되면 모델이 설정되고 다음 명령을 사용하여 모델이 예상대로 작동하는지 테스트할 수 있습니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/02-embed-text-output.json">02-embed-text-output.json</a> 파일에서 확인할 수 있습니다.</p>POST _inference/text_embedding/my_e5_model
{
  "input": "my awesome piece of text"
}<p>학습된 모델이 노드에 할당되지 않는 문제가 발생하면 모델을 수동으로 시작해야 할 수 있습니다.</p>POST _ml/trained_models/.multilingual-e5-small/deployment/_start<p>이제 임베딩 모델의 출력과 일치하도록 표준 텍스트 필드(<code>my_field</code>)와 384개의 차원을 가진 고밀도 벡터 필드(<code>my_vector</code>)의 두 가지 속성을 가진 새 매핑을 만들어 보겠습니다. 또한 <code>index_options.type to bbq_hnsw</code> 을 재정의합니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/03-create-byte-qauntized-index-output.json">03-create-byte-qauntized-index-output.json</a> 파일에서 확인할 수 있습니다.</p>PUT bbq-my-byte-quantized-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "bbq_hnsw"
        }
      }
    }
  }
}<p>Elasticsearch가 벡터를 생성하도록 하기 위해 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/ingest.html">수집 파이프라인을</a> 사용할 수 있습니다. 이 파이프라인에는 엔드포인트(<code>model_id</code>), 벡터를 생성하려는 <code>input_field</code>, 벡터를 저장할 <code>output_field</code>, 이 세 가지가 필요합니다. 아래의 첫 번째 명령은 추론 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/inference-apis.html">서비스를 </a>내부적으로 사용하는 추론 수집 파이프라인을 만들고, 두 번째 명령은 파이프라인이 올바르게 작동하는지 테스트합니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/04-create-and-simulate-ingest-pipeline-output.json">04-create-and-simulate-ingest-pipeline-output.json</a> 파일에서 확인할 수 있습니다. </p>PUT _ingest/pipeline/my_inference_pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "my_e5_model",
        "input_output": [
          {
            "input_field": "my_field",
            "output_field": "my_vector"
          }
        ]
      }
    }
  ]
}

POST _ingest/pipeline/my_inference_pipeline/_simulate
{
  "docs": [
    {
      "_source": {
        "my_field": "my awesome text field"
      }
    }
  ]
}<p>이제 아래의 처음 두 명령어로 문서를 추가하고 세 번째 명령어로 검색이 제대로 작동하는지 테스트할 준비가 되었습니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/05-bbq-index-output.json">05-bbq-index-output.json</a> 파일에서 확인할 수 있습니다. </p>PUT bbq-my-byte-quantized-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT bbq-my-byte-quantized-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p><a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch#lucene-benchmarking">이 게시물에서</a> 권장하는 바와 같이, 리스코어링과 오버샘플링은 압축의 이점을 활용하면서 높은 리콜 정확도를 유지하는 데 도움이 되므로 데이터 양이 많지 않은 경우 확장하는 것이 좋습니다. Elasticsearch 버전 8.18부터는 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.18/knn-search.html#dense-vector-knn-search-rescoring">rescore_vector를</a> 사용하여 이 작업을 수행할 수 있습니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/06-bbq-search-8-18-output.json">06-bbq-search-8-18-output.json</a> 파일에 있습니다.</p>GET bbq-my-byte-quantized-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "rescore_vector": {
              "oversample": 3
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<p>이 점수는 원시 데이터에 대해 얻을 수 있는 점수와 어떻게 비교될까요? 위의 모든 작업을 다시 수행하되 <code>index_options.type: hnsw</code> 을 사용하면 점수가 매우 비슷하다는 것을 알 수 있습니다. 예상 출력은 출력 폴더의 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/how-and-why-bbq/Outputs/07-raw-vector-output.json">07-raw-vector-output.json</a> 파일에서 확인할 수 있습니다.</p>PUT my-raw-vector-index
{
  "mappings": {
    "properties": {
      "my_field": {
        "type": "text"
      },
      "my_vector": {
        "type": "dense_vector",
        "dims": 384,
        "index_options": {
          "type": "hnsw"
        }
      }
    }
  }
}

PUT my-raw-vector-index/_doc/1?pipeline=my_inference_pipeline
{
    "my_field": "my awesome text field"
}

PUT my-raw-vector-index/_doc/2?pipeline=my_inference_pipeline
{
    "my_field": "some other sentence"
}

GET my-raw-vector-index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "knn": {
            "field": "my_vector",
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "my_e5_model",
                "model_text": "my awesome search field"
              }
            },
            "k": 10,
            "num_candidates": 100
          }
        }
      ]
    }
  },
  "_source": [
    "my_field"
  ]
}<h2>압축률에 대한 대략적인 수치</h2><p>벡터 검색으로 작업할 때 저장 공간과 메모리 요구 사항은 금방 큰 문제가 될 수 있습니다. 다음 분석은 다양한 양자화 기법이 벡터 데이터의 메모리 사용량을 어떻게 획기적으로 줄이는지 보여줍니다.</p><p>벡터(V)</p><p>치수(D)</p><p>원시(V x D x 4)</p><p>int8 (V x (D x 1 + 4))</p><p>int4 (V x (D x 0.5 + 4))</p><p>bbq (V x (D x 0.125 + 4))</p><p>10,000,000</p><p>384</p><p>14.31GB</p><p>3.61GB</p><p>1.83GB</p><p>0.58GB</p><p>50,000,000</p><p>384</p><p>71.53GB</p><p>18.07GB</p><p>9.13GB</p><p>2.89GB</p><p>100,000,000</p><p>384</p><p>143.05GB</p><p>36.14GB</p><p>18.25GB</p><p>5.77GB</p><h2>결론</h2><p>BBQ는 정확도 저하 없이 압축을 위해 벡터 데이터에 적용할 수 있는 최적화 기능입니다. 벡터를 비트로 변환하여 데이터를 효과적으로 검색하고 AI 워크플로우를 확장하여 검색을 가속화하고 데이터 저장 공간을 최적화할 수 있도록 지원합니다.</p><h2>추가 학습</h2><p>BBQ에 대해 더 자세히 알고 싶다면 다음 리소스를 확인하세요:</p><ul><li><p><a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">루씬과 Elasticsearch의 이진 정량화(BBQ)</a></p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/bit-vectors-elasticsearch-bbq-vs-pq">더 나은 바이너리 정량화(BBQ) 대 제품 정량화</a></p></li><li><p><a href="https://www.elastic.co/kr/search-labs/blog/optimized-scalar-quantization-elasticsearch">최적화된 스칼라 양자화: 더욱 향상된 바이너리 양자화</a></p></li><li><p><a href="https://www.youtube.com/watch?v=04NzMt2Nigc">더 나은 바이너리 정량화(BBQ): 바이트에서 BBQ로, 더 나은 벡터 검색의 비결, 벤 트렌트의 글</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/bbq-implementation-into-use-case</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[기본]]></category>
    <dc:creator><![CDATA[Sachin Frayne,Jessica Garson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3dd0495b536b2615/6a17e2b0414c6488459450e3/66842055367cdd795532b01c167f2a4b03dc65e3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch BBQ와 OpenSearch FAISS: 벡터 검색 성능 비교]]></title>
    <description><![CDATA[Elasticsearch BBQ와 OpenSearch FAISS의 성능 비교.]]></description>
    <content:encoded><![CDATA[<p><strong>이진 양자화를 통한 벡터 검색: BBQ가 포함된 Elasticsearch는 FAISS가 포함된 OpenSearch보다 5배 빠릅니다</strong>. Elastic은 특히 시맨틱 검색/벡터 검색 영역에서 Elasticsearch와 OpenSearch 간의 성능 차이를 명확히 해달라는 커뮤니티의 요청을 받았기 때문에 명확한 데이터 기반 비교를 제공하기 위해 이러한 성능 테스트를 수행했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta8792b5284e7a553/6a1709b9961e69a084c4cee6/7f4f8d08f7bee188423e4e65f0caefc7e34f0355-1600x681.png" alt="Elasticsearch BBQ와 OpenSearch FAISS - 속도 &amp; 처리량 리콜 비교" /><h2>이진 양자화 대결</h2><p>고차원 벡터를 원래 형태로 저장하는 것은 메모리 집약적일 수 있습니다. 양자화 기술은 이러한 벡터를 콤팩트한 표현으로 압축하여 메모리 사용량을 대폭 줄여줍니다. 그러면 검색이 압축된 공간에서 작동하므로 계산 복잡성이 줄어들고 특히 대규모 데이터 세트에서 검색 속도가 빨라집니다.</p><p>Elastic은 Lucene을 최고 성능의 벡터 엔진으로 만들기 위해 최선을 다하고 있습니다. 우리는 Lucene을 기반으로 Elasticsearch 8.16에서 <a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">더 나은 이진 정량화</a> (BBQ)를 도입했고 8.18과 9.0에서 이를 더욱 발전시켰습니다. BBQ는 플로트32 차원을 비트로 축소하는 새로운 <a href="https://www.elastic.co/kr/search-labs/blog/optimized-scalar-quantization-elasticsearch">스칼라 양자화</a> 방식을 기반으로 구축되어 높은 순위 품질을 유지하면서 최대 95%의% 메모리 절감 효과를 제공합니다.</p><p>반면 OpenSearch는 여러 벡터 엔진, 즉 nmslib(현재는 더 이상 사용되지 않음), Lucene 및 FAISS를 사용합니다. <a href="https://www.elastic.co/kr/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison">이전 블로그에서</a> 벡터 검색을 위해 Elasticsearch와 OpenSearch를 비교한 적이 있습니다. 세 가지 데이터 세트를 사용하여 두 제품에서 서로 다른 엔진 및 구성 조합을 테스트했습니다.</p><p>이 블로그에서는 현재 두 제품에서 사용할 수 있는 이진 양자화 알고리즘에 초점을 맞춥니다. <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally 트랙을 사용하여 BBQ와 함께 Elasticsearch를 테스트하고 <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">FAISS의 이진 정량화를 통해 OpenSearch를 테스트했습니다.</a></p><p>주요 목표는 동일한 리콜 수준에서 두 솔루션의 성능을 평가하는 것이었습니다. <em>리콜이란</em> 무엇을 의미하나요? 리콜은 검색 시스템에서 얼마나 많은 관련 결과를 성공적으로 검색했는지를 측정하는 지표입니다.</p><p>이 평가에서는 recall@k가 특히 중요한데, 여기서 <em>k는</em> 고려되는 상위 결과의 수를 나타냅니다. 따라서 <strong>리콜@10</strong>, <strong>리콜@50 및 리콜@100은</strong> 각각 검색된 상위 10개, 50개 및 100개의 항목에서 얼마나 많은 실제 관련성 있는 결과가 나타나는지 측정합니다. 리콜은 0에서 1(또는 0% ~ 100% 정밀도)의 척도로 표시됩니다. 이는 리콜이 항상 1(100%)인 정확한 KNN이 아닌 근사 KNN(ANN)에 대해 이야기하고 있기 때문에 중요합니다.</p><p><em>k의</em> 각 값에 대해 최종 순위를 적용하기 전에 고려되는 후보의 수인 <em>n도 </em>지정했습니다. 즉, Recall@10, Recall@50 및 Recall@100의 경우 시스템은 먼저 이진 양자화 알고리즘을 사용하여 <em>n개의</em> 후보를 검색한 다음 상위 <em>k개의</em> 결과에 예상되는 관련 항목이 포함되어 있는지 여부를 판단하기 위해 순위를 매깁니다.</p><p><em>n을</em> 제어함으로써 효율성과 정확성 사이의 균형을 분석할 수 있습니다. 일반적으로 <em>n이</em> 높을수록 순위를 매길 수 있는 후보가 많아져 리콜률이 <strong>높아지지만</strong> 지연 시간이 <strong>길어지고</strong> 처리량도<strong> 감소합니다 </strong>. 반대로 <em>n이</em> 낮을수록 검색 속도가 빨라지지만 초기 세트에 관련 후보가 너무 적게 포함될 경우 검색 회수율이 떨어질 수 있습니다.</p><p>이 비교에서 Elasticsearch는 동일한 설정에서 OpenSearch보다 더 낮은 지연 시간과 더 높은 처리량을 보여주었습니다.</p><h2>방법론</h2><p>전체 구성과 함께 Terraform 스크립트, Kubernetes 매니페스트 및 특정 Rally 트랙은 이 <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq">리포지토리에서</a> <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/tree/bbq/rally-custom/custom_tracks/elasticsearch/openai_vector_bq"><em>openai_vector_bq</em></a> 아래에서 확인할 수 있습니다.</p><p>이전 벤치마크와 마찬가지로 다음과 같이 구성된 Kubernetes 클러스터를 사용했습니다:</p><ul><li><p>3개의 <code>e2-standard-32</code> 시스템(128GB RAM 및 32 CPU)을 갖춘 Elasticsearch 9.0용 노드 풀 1개</p></li><li><p>OpenSearch 2.19용 노드 풀 1개, <code>e2-standard-32</code> 머신 3대(128GB RAM 및 32개 CPU)</p></li><li><p>2개의 <code>e2-standard-4</code> 머신(16GB RAM 및 4개의 CPU)이 있는 Rally용 노드 풀 1개</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934e988e96a5849f/6a1709bbacf08838f9be9ac1/169bb6033f6eebfd1b177b3446bf916fde4ee5c5-1600x856.png" alt="Elasticsearch BBQ와 Opensearch FAISS 방법론 설정" /><p>하나의 Elasticsearch 클러스터 버전 9.0과 하나의 OpenSearch 클러스터 버전 2.19를 설정했습니다.</p><p>Elasticsearch와 OpenSearch 모두 정확히 동일한 설정으로 테스트했습니다. OpenAI의 텍스트 임베딩-ada-002 <a href="https://openai.com/blog/new-and-improved-embedding-model">모델을 사용해 생성된</a> <a href="https://huggingface.co/datasets/BeIR/nq">임베딩으로 강화된 NQ 데이터 세트의</a> 250만 개의 문서를 사용하는 <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a> Rally 트랙을 <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/commit/b97d5d95c22c8cf862f2030964524bdd156a5da3">일부 수정하여 사용했습니다.</a></p>{
  "source-file": "open_ai_corpus-initial-indexing.json.bz2",
  "document-count": 2580961,
  "compressed-bytes": 32076749416,
  "uncompressed-bytes": 90263571686
}<p>검색 작업을 수행하기 위해 8개의 동시 클라이언트를 사용하여 다양한 리콜 수준(리콜@10, 리콜@50, 리콜@100)에서 측정된 지연 시간 및 처리량에 대한 결과 보고서입니다. 복제본 없이 단일 샤드를 사용했습니다.</p><p>다음과 같은 k-n-점수 조합을 실행했습니다. 10-2000-2000 또는 <em>k:10</em>, <em>n:2000</em> 및 <em>rescore:2000</em> 은 2000개의 결과에 대해 재점수('과대 표본 계수' 1에 해당)를 적용하여 n개의 후보(2000개) 중 상위 k(10개)를 검색합니다. 각 검색은 워밍업으로 1000회의 검색으로 10.000회 실행되었습니다:</p><p></p><p><u><strong>Recall@10</strong></u></p><ul><li><p>10-40-40</p></li><li><p>10-50-50</p></li><li><p>10-100-100</p></li><li><p>10-200-200</p></li><li><p>10-500-500</p></li><li><p>10-750-750</p></li><li><p>10-1000-1000</p></li><li><p>10-1500-1500</p></li><li><p>10-2000-2000</p></li></ul><p><u><strong>Recall@50</strong></u></p><ul><li><p>50-150-150</p></li><li><p>50-200-200</p></li><li><p>50-250-250</p></li><li><p>50-500-500</p></li><li><p>50-750-750</p></li><li><p>50-1000-1000</p></li><li><p>50-1200-1200</p></li><li><p>50-1500-1500</p></li><li><p>50-2000-2000</p></li></ul><p><u><strong>Recall@100</strong></u></p><ul><li><p>100-200-200</p></li><li><p>100-250-250</p></li><li><p>100-300-300</p></li><li><p>100-500-500</p></li><li><p>100-750-750</p></li><li><p>100-1000-1000</p></li><li><p>100-1200-1200</p></li><li><p>100-1500-1500</p></li><li><p>100-2000-2000</p></li></ul><p>벤치마크를 복제하기 위해, rally-elasticsearch와 rally-opensearch 모두에 대한 Kubernetes 매니페스트에는 모든 관련 변수가 컨피그맵에 외부화되어 있으며, <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-es-bq.yml">여기</a> (ES)와 <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/bbq/k8s/rally-openai_vector-os-bq.yml">여기</a> (OS)에서 확인할 수 있습니다. <em>search_ops</em> 매개변수는 k, n, rescore의 모든 조합을 테스트하도록 사용자 지정할 수 있습니다.</p><h3>OpenSearch Rally 구성</h3><p><code>/k8s/rally-openai_vector-os-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-os
  labels:
    app: rally-opensearch
data:
  user-tags.json: |
    {
      "product": "OpenSearch",
      "product-version": "OpenSearch-2.19.0",
      "product-label": "OpenSearch-2.19-faiss",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "ann_threshold": 0,
      "vector_mode": "on_disk",
      "compression_level": "32x",
      "vector_method_name": "hnsw",
      "vector_method_engine": "faiss",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>검색 인덱스 구성</h3><p>그런 다음 컨피그맵의 변수가 인덱스 구성에 사용되며, 일부 매개변수는 변경되지 않은 상태로 유지됩니다. OpenSearch의 1비트 양자화는 <a href="https://opensearch.org/docs/latest/search-plugins/knn/knn-vector-quantization/#binary-quantization">압축 수준을 "32배"로 설정하여 구성</a>합니다.</p><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {% if preload_pagecache %}
    "index.store.preload": [
      "vec", "vex", "vem", "veq", "veqm", "veb", "vebm"
    ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }},
    "index.knn": true,
    "index.knn.advanced.approximate_threshold": {{ ann_threshold | default(15000) }}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "knn_vector",
        "dimension": 1536,
        "space_type": "innerproduct",
        "data_type": "float",
        "mode": {{ vector_mode | default("in_memory") | tojson }},
        "compression_level": {{ compression_level | default("32x") | tojson }},
        "method": {
          "name": {{ vector_method_name | default("hnsw") | tojson }},
          "engine": {{ vector_method_engine | default("faiss") | tojson }},
          "parameters": {
            "ef_construction": 100,
            "m": 16
          }
        }
      }
    }
  }
}<h3>Elasticsearch Rally 구성</h3><p><code>/k8s/rally-openai_vector-es-bq.yml</code></p>apiVersion: v1
kind: ConfigMap
metadata:
  name: rally-params-es
  labels:
    app: rally-elasticsearch
data:
  user-tags.json: |
    {
      "product": "Elasticsearch",
      "product-version": "Elasticsearch-9.0.0-ade01164",
      "product-label": "Elasticsearch-9.0-BBQ",
      "benchmark-run": "19-feb-recall@100"
    }
  track-params.json: |
    {
      "mapping_type": "vectors-only-mapping-with-docid",
      "standalone_search_clients": 8,
      "standalone_search_iterations": 5000,
      "vector_index_type": "bbq_hnsw",
      "search_ops": [
        [100, 200, 200],
        [100, 250, 250],
        [100, 300, 300],
        [100, 500, 500],
        [100, 750, 750],
        [100, 1000, 1000],
        [100, 1200, 1200],
        [100, 1500, 1500],
        [100, 2000, 2000]
      ]
    }<h3>Elasticsearch 인덱스 구성</h3><p><code>index-vectors-only-mapping-with-docid-mapping.json</code></p>{
  "settings": {
    {# non-serverless-index-settings-marker-start #}
    {%- if build_flavor != "serverless" or serverless_operator == true -%}
    {% if preload_pagecache %}
    "index.store.preload": [ "vec", "vex", "vem", "veq", "veqm", "veb", "vebm" ],
    {% endif %}
    "index.number_of_shards": {{ number_of_shards | default(1) }},
    "index.number_of_replicas": {{ number_of_replicas | default(0) }}
    {%- endif -%}
    {# non-serverless-index-settings-marker-end #}
  },
  "mappings": {
    "dynamic": false,
    "properties": {
      "docid": {
        "type": "keyword"
      },
      "emb": {
        "type": "dense_vector",
        "element_type": "float",
        "dims": 1536,
        "index": true,
        "similarity": "dot_product",
        "index_options": {
          "type": {{ vector_index_type | default("bbq_hnsw") | tojson }},
          "ef_construction": 100,
          "m": 16
        }
      }
    }
  }
}<h2>결과</h2><p>결과를 해석하는 방법에는 여러 가지가 있습니다. 지연 시간과 처리량 모두에 대해 각 리콜 수준에 따라 단순화된 차트와 상세한 차트를 표시했습니다. 각 지표에 대해 "높을수록 좋다"고 생각하면 차이를 쉽게 알 수 있습니다. 그러나 지연 시간은 음수(낮을수록 좋음)인 반면 처리량은 양수입니다. 단순화된 차트에서는 <strong>(리콜/레이턴시) * 10000 </strong>(간단히 '속도'라고 함)과<strong> 리콜 * 처리량을</strong> 사용했으므로 두 지표 모두 속도가 빠르고 처리량이 많을수록 더 좋다는 의미입니다. 시작해 보겠습니다.</p><h3>리콜 @ 10 - 단순화</h3><p>이 수준의 리콜에서 Elasticsearch BBQ는 OpenSearch FAISS보다 최대 <strong>5배 </strong>(평균 3.9배) 빠르며 평균 <strong>3.2배 더 많은 처리량을</strong> 제공합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3de6b3460127a8ae/6a1709bcb339d55975769f6f/d580ad53e8974bd3aa75957c413a0136c4e465c5-1600x681.png" alt="Elasticsearch BBQ는 최대 5배(평균 3.9배) 빠르며, 속도와 처리량 Recall@10에서 OpenSearch FAISS보다 평균 3.2배 더 많은 처리량을 제공합니다." /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd47045013cac5e9d/6a1709be0e2e49599a41a088/18edce667fe36ab95033264ef8df6f352dda2425-2044x866.png" alt="Elasticsearch BBQ는 OpenSearch FAISS보다 최대 5배(평균 3.9배) 빠르며 평균 3.2배 더 많은 처리량을 제공합니다." /><h4>리콜 @ 10 - 상세</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0ecb02d4a2967dbd/6a1709bf14b270c04fe3c5c6/a7459b87e679f4ad963d0e2f1685499b40f6f050-1600x799.png" alt="자세한 리콜@10 지연 시간 비교 Elasticsearch BBQ와 Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt182da309ad6cfd39/6a1709c166c4f99077f8bfd8/c6036f55a13377654d296eb3148c7199e1965475-1600x799.png" alt="자세한 recall@10 처리량 비교 Elasticsearch BBQ와 Opensearch FAISS." /><p></p><p>작업</p><p>latency.mean</p><p>throughput.mean</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>10-100-100</p><p>11.70</p><p>513.58</p><p>0.89</p><p>Elasticsearch-9.0-BBQ</p><p>10-1000-100</p><p>27.33</p><p>250.55</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-1500-1500</p><p>35.93</p><p>197.26</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-200-200</p><p>13.33</p><p>456.16</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>10-2000-2000</p><p>44.27</p><p>161.40</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>10-40-40</p><p>10.97</p><p>539.94</p><p>0.84</p><p>Elasticsearch-9.0-BBQ</p><p>10-50-50</p><p>11.00</p><p>535.73</p><p>0.85</p><p>Elasticsearch-9.0-BBQ</p><p>10-500-500</p><p>19.52</p><p>341.45</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>10-750-750</p><p>22.94</p><p>295.19</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-100-100</p><p>35.59</p><p>200.61</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>10-1000-1000</p><p>156.81</p><p>58.30</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-1500-1500</p><p>181.79</p><p>42.97</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-200-200</p><p>47.91</p><p>155.16</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>10-2000-2000</p><p>232.14</p><p>31.84</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-40-40</p><p>27.55</p><p>249.25</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-50-50</p><p>28.78</p><p>245.14</p><p>0.92</p><p>OpenSearch-2.19-faiss</p><p>10-500-500</p><p>79.44</p><p>97.06</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>10-750-750</p><p>104.19</p><p>75.49</p><p>0.96</p><h3>리콜 @ 50 - 단순화</h3><p>이 수준의 리콜에서 Elasticsearch BBQ는 OpenSearch FAISS보다 <strong>최대 5배</strong> (평균 4.2배) 빠르며 평균 <strong>처리량이 3.9배(</strong> ) 더 많습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt486683f2c7a1d677/6a1709c2a6c2b995bde796a3/3189ffb330948b35854eeea9ae317d4846c14972-1600x681.png" alt="Recal @50 벡터 성능 비교 Elasticsearch BBQ와 Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba830c6741784682/6a1709c4a292990627d00ff7/607383d674dcf0b8f94bfb1a450063f52fcbeb15-2060x876.png" alt="Elasticsearch BBQ는 OpenSearch FAISS보다 최대 5배(평균 4.2배) 빠르며 평균 3.9배 더 많은 처리량을 제공합니다." /><h4>상세 결과 - 리콜 @ 50%</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt821775e7f87e5ede/6a1709c6a6c2b90af3e796a7/ebfffe0b776aad31dd03d315cfbf5aa098b41226-1600x789.png" alt="Recall@50 Elasticsearch BBQ 및 Opensearch FAISS 지연 시간 결과" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f47784ed1017c15/6a1709c7e8fbce0fbe39fbf5/ae20cf870a65c400a2112bbad62eb56e244f549a-1600x799.png" alt="Recall@50 Elasticsearch BBQ 및 Opensearch FAISS 처리량 결과" /><p></p><p>작업</p><p>지연 시간 평균</p><p>처리량 평균</p><p>평균 회수율</p><p>Elasticsearch-9.0-BBQ</p><p>50-1000-1000</p><p>25.71</p><p>246.44</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-1200-1200</p><p>28.81</p><p>227.85</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-150-150</p><p>13.43</p><p>362.90</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>50-1500-1500</p><p>33.38</p><p>202.37</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-200-200</p><p>12.99</p><p>406.30</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>50-2000-2000</p><p>42.63</p><p>163.68</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>50-250-250</p><p>14.41</p><p>373.21</p><p>0.92</p><p>Elasticsearch-9.0-BBQ</p><p>50-500-500</p><p>17.15</p><p>341.04</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>50-750-750</p><p>31.25</p><p>248.60</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>50-1000-1000</p><p>125.35</p><p>62.53</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-1200-1200</p><p>143.87</p><p>54.75</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-150-150</p><p>43.64</p><p>130.01</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>50-1500-1500</p><p>169.45</p><p>46.35</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-200-200</p><p>48.05</p><p>156.07</p><p>0.91</p><p>OpenSearch-2.19-faiss</p><p>50-2000-2000</p><p>216.73</p><p>36.38</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>50-250-250</p><p>53.52</p><p>142.44</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>50-500-500</p><p>78.98</p><p>97.82</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>50-750-750</p><p>103.20</p><p>75.86</p><p>0.96</p><h3>리콜 @ 100</h3><p>이 수준의 리콜에서 Elasticsearch BBQ는 OpenSearch FAISS보다 <strong>최대 5배 </strong>(평균 4.6배) 빠르며 평균 <strong>3.9배 더 많은 처리량을 </strong>제공합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt380eb9165343a7b3/6a1709c9acf0882669be9ac5/d3f29db64cbde9956de1fa3ae64a75f15141a2bb-1600x681.png" alt="100개의 Elasticsearch BBQ와 Opensearch FAISS 결과 비교" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt505ce8aa70898390/6a1709cb1949f73a37e7a9bb/10aff7f8c61fdac895b9ba9c5342baf239ba3ffc-2072x864.png" alt="Elasticsearch BBQ와 Opensearch FAISS 지연 시간 및 처리량 성능 비교" /><h4>상세 결과 - 리콜 @ 100</h4><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2a73e29940e9d6b/6a1709cccf4f257615b2d138/8790fdf9512b850447f6875fb69969f6f1d4da5f-1600x799.png" alt="세부 지연 시간 결과 - 100회 리콜 @ 100 Elasticsearch BBQ vs Opensearch FAISS" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c91ca21da03d96/6a1709ce0c4857d3fc01aa39/d47032fac6c288cd4eedd9f25001e417b2fa9d65-1600x787.png" alt="자세한 처리량 결과 - 100회 회상 @ 100 Elasticsearch BBQ 대 Opensearch FAISS." /><p></p><p>작업</p><p>latency.mean</p><p>throughput.mean</p><p>avg_recall</p><p>Elasticsearch-9.0-BBQ</p><p>100-1000-1000</p><p>27.82</p><p>243.22</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1200-1200</p><p>31.14</p><p>224.04</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-1500-1500</p><p>35.98</p><p>193.99</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-200-200</p><p>14.18</p><p>403.86</p><p>0.88</p><p>Elasticsearch-9.0-BBQ</p><p>100-2000-2000</p><p>45.36</p><p>159.88</p><p>0.95</p><p>Elasticsearch-9.0-BBQ</p><p>100-250-250</p><p>14.77</p><p>433.06</p><p>0.90</p><p>Elasticsearch-9.0-BBQ</p><p>100-300-300</p><p>14.61</p><p>375.54</p><p>0.91</p><p>Elasticsearch-9.0-BBQ</p><p>100-500-500</p><p>18.88</p><p>340.37</p><p>0.93</p><p>Elasticsearch-9.0-BBQ</p><p>100-750-750</p><p>23.59</p><p>285.79</p><p>0.94</p><p>OpenSearch-2.19-faiss</p><p>100-1000-1000</p><p>142.90</p><p>58.48</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1200-1200</p><p>153.03</p><p>51.04</p><p>0.95</p><p>OpenSearch-2.19-faiss</p><p>100-1500-1500</p><p>181.79</p><p>43.20</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-200-200</p><p>50.94</p><p>131.62</p><p>0.83</p><p>OpenSearch-2.19-faiss</p><p>100-2000-2000</p><p>232.53</p><p>33.67</p><p>0.96</p><p>OpenSearch-2.19-faiss</p><p>100-250-250</p><p>57.08</p><p>131.23</p><p>0.87</p><p>OpenSearch-2.19-faiss</p><p>100-300-300</p><p>62.76</p><p>120.10</p><p>0.89</p><p>OpenSearch-2.19-faiss</p><p>100-500-500</p><p>84.36</p><p>91.54</p><p>0.93</p><p>OpenSearch-2.19-faiss</p><p>100-750-750</p><p>111.33</p><p>69.95</p><p>0.94</p><h2>BBQ 개선 사항</h2><p>BBQ는 첫 출시 이후 많은 발전을 거듭해 왔습니다. 비교를 위해 8.16 버전에서 현재 버전과 함께 실행한 벤치마크를 포함시켰는데, 그 이후 리콜과 지연 시간이 어떻게 개선되었는지 확인할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f1f7ae1bb425a3a/6a1709cf67045bde8e45c192/45a0acfe5985bff28ccada76ec4eca190fe65f72-1600x799.png" alt="Elasticsearch 8.16 BBQ를 벤치마킹한 Elasticsearch 9.0 BBQ 지연 시간 리콜 개선 사항" /><p>Elasticsearch 8.18과 9.0에서는 벡터를 정량화하기 위한 핵심 알고리즘을 다시 작성했습니다. 따라서 8.16의 BBQ도 좋았지만 최신 버전은 훨씬 더 좋습니다. 자세한 내용은 <a href="https://www.elastic.co/kr/search-labs/blog/optimized-scalar-quantization-elasticsearch">여기와</a> <a href="https://www.elastic.co/kr/search-labs/blog/scalar-quantization-optimization">여기에서</a> 확인할 수 있습니다. 즉, 모든 벡터는 최적화된 스칼라 사분위수를 통해 개별적으로 정량화됩니다. 그 결과, 사용자는 성능 저하 없이 벡터 검색의 정확도가 높아져 Elasticsearch의 벡터 검색이 더욱 강력해지는 이점을 누릴 수 있습니다.</p><h2>결론</h2><p>이 Elasticsearch BBQ와 OpenSearch FAISS 간의 성능 비교에서 Elasticsearch는 벡터 검색에서 OpenSearch보다 훨씬 뛰어난 성능을 발휘하여 다양한 수준의 리콜에서 평균적으로 최대 5배 빠른 쿼리 속도와 3.9배 높은 처리량을 달성했습니다.</p><p>주요 결과는 다음과 같습니다:</p><ul><li><p><strong>Recall@10</strong>: Elasticsearch BBQ는 OpenSearch FAISS에 비해 최대 5배(평균 3.9배) 빠르며 평균 3.2배 더 많은 처리량을 제공합니다.</p></li><li><p><strong>Recall@50</strong>: Elasticsearch BBQ는 OpenSearch FAISS에 비해 최대 5배(평균 4.2배) 빠르며 평균 3.9배 더 많은 처리량을 제공합니다.</p></li><li><p><strong>Recall@100</strong>: Elasticsearch BBQ는 OpenSearch FAISS에 비해 최대 5배(평균 4.6배) 빠르며 평균 3.9배 더 많은 처리량을 제공합니다.</p></li></ul><p>이러한 결과는 특히 고차원 벡터 검색 시나리오에서 Elasticsearch BBQ의 효율성과 성능 이점을 강조합니다. Elasticsearch 8.16에 도입된 더 나은 이진 양자화(BBQ) 기술은 높은 순위 품질을 유지하면서 상당한 메모리 절감(~95%)을 제공하므로 대규모 벡터 검색 애플리케이션에 탁월한 선택이 될 수 있습니다.</p><p>Elastic에서는 RAG(검색 증강 생성)를 비롯한 검색 및 검색 사용 사례를 위한 최고의 벡터 데이터베이스를 제공하기 위해 Apache Lucene과 Elasticsearch를 개선하기 위해 끊임없이 혁신하고 있습니다. <a href="https://www.elastic.co/kr/search-labs/blog/optimized-scalar-quantization-elasticsearch">최근의 발전으로</a> 성능이 크게 향상되어 이전보다 벡터 검색 속도가 빨라지고 공간 효율성이 높아졌으며, 이는 루씬 10에서 얻은 이점을 기반으로 합니다. 이 블로그는 이러한 혁신의 또 다른 예시입니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-bbq-vs-opensearch-faiss</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd76ada1eebfe05cd/6a1709d1a29299ed29d00ffb/796de4829e29566f1f3efa2482f5c3e54b31b1d6-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[구글 클라우드의 버텍스 AI 플랫폼에서 기본 접지를 위한 Elasticsearch 벡터 데이터베이스]]></title>
    <description><![CDATA[Google Cloud의 Vertex AI를 위한 최초의 써드파티 기본 접지 엔진인 Elasticsearch가 어떻게 기업 데이터에 Gemini 모델을 접지하여 맞춤형 GenAI 환경을 구축할 수 있는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Elastic은 이제 Elasticsearch 벡터 데이터베이스가 기본적으로 지원되는 정보 검색 엔진으로 Google Cloud의 Vertex AI 플랫폼에 통합되어 사용자가 Google Gemini 모델의 멀티모달 강점을 Elasticsearch의 고급 AI 기반 시맨틱 및 하이브리드 검색 기능과 함께 활용할 수 있게 되었음을 발표하게 되어 기쁘게 생각합니다.</p><p>이제 개발자는 통합된 여정 내에서 로우코드의 유연한 방식으로 개인 데이터에 기반한 채팅 경험을 제공하는 RAG 애플리케이션을 만들 수 있습니다. 고객과 내부 직원을 위한 AI 에이전트를 구축하든, 소프트웨어 내에서 LLM 생성을 활용하든, Vertex AI 플랫폼은 최소한의 구성으로 손쉽게 Elasticsearch 정확도를 제공합니다. 이러한 통합을 통해 프로덕션 사용 사례에서 Gemini 모델을 더 쉽고 빠르게 채택할 수 있으며, GenAI를 PoC에서 실제 시나리오로 끌어올릴 수 있습니다.</p><p>이 블로그에서는 원활한 데이터 기반을 마련하고 완전히 사용자 정의 가능한 GenAI 애플리케이션을 구축하기 위해 Elasticsearch를 Google Cloud의 Vertex AI 플랫폼과 통합하는 방법을 안내해드립니다. 방법을 알아보세요.</p><h2>Elasticsearch를 통해 데이터에 기반한 Google Cloud의 Vertex AI 및 Gemini 모델</h2><p>이제 GenAI 애플리케이션을 만들기 위해 버텍스 AI 서비스 및 도구를 활용하는 사용자는 새로운 '접지' 옵션에 액세스하여 자신의 개인 데이터를 자동으로 대화 상호작용으로 가져올 수 있습니다. Elasticsearch는 이제 이 기능의 일부이며 두 가지 모두에서 사용할 수 있습니다:</p><ul><li><p>생성 시점에 Google의 Gemini 모델을 직접 강화하는 Vertex AI <a href="https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/inference">LLM API</a>(선호);</p></li><li><p>버텍스 AI 에이전트 빌더 에코시스템에서 에이전트 경험을 구축하는 데 사용되는 <a href="https://cloud.google.com/generative-ai-app-builder/docs/grounded-gen">Grounded Generation API입니다</a>.</p></li></ul><p>이 통합을 통해 가장 많이 다운로드되고 배포된 <a href="https://www.elastic.co/kr/elasticsearch/vector-database">벡터 데이터베이스인</a> Elasticsearch는 내부 최종 고객 대면 채팅에서 필요한 곳이면 어디든 관련 엔터프라이즈 데이터를 가져올 수 있으며, 이는 GenAI를 비즈니스 프로세스에 실제로 도입하는 데 매우 중요한 역할을 합니다.</p><p>앞서 언급한 API를 통해 개발자는 이 새로운 파트너 기능을 코드에 적용할 수 있습니다. 그러나 신속한 엔지니어링 및 테스트는 애플리케이션 개발의 중요한 단계이며 초기 발견의 놀이터 역할을 합니다. 이를 지원하기 위해 Elasticsearch는 사용자가 Vertex AI Studio 콘솔 도구 내에서 쉽게 평가할 수 있도록 설계되었습니다.</p><p>아래 그림과 같이 UI의 "사용자 정의 접지" 탭에서 원하는 매개변수(검색할 인덱스, 검색할 문서 수, 원하는 검색 템플릿)로 Elastic 엔드포인트를 구성하는 몇 가지 간단한 단계만 거치면 됩니다(작동하려면 UI와 아래 코드 예제에서 "ApiKey" 라는 단어와 함께 API 키를 입력해야 합니다). 이제 개인 지식으로 생성할 준비가 되었습니다!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d313968acca1fea/6a17e624b1e113278079f250/69b3d979d18fa90742d9397f57975c586edf5d9f-1003x710.gif" alt="Elasticsearch를 통해 데이터에 기반한 Google Cloud의 Vertex AI 및 Gemini 모델" /><h2>손쉽게 프로덕션에 사용할 수 있는 GenAI 애플리케이션</h2><p>Elastic과 Google Cloud는 개발자 우선의 포괄적이고 즐거운 경험을 제공하기 위해 노력하고 있습니다. LLM과 접지 생성 API 모두에서 기본적으로 Elastic에 연결하면 하나의 통합 호출로 접지하면서 불필요한 추가 API와 데이터 오케스트레이션을 피하고, Vertex AI에서 GAI 애플리케이션을 구축하는 동안 복잡성과 오버헤드를 줄일 수 있습니다.</p><p>두 시나리오에서 어떻게 작동하는지 살펴보겠습니다.</p><p>첫 번째 예제는 LLM API로 실행됩니다:</p>curl -X POST \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \https://us-central1-aiplatform.googleapis.com/v1beta1/projects/&lt;PROJECT_ID&gt;/locations/us-central1/publishers/google/models/gemini-2.0-flash-001:generateContent \
  -d '
{
  "contents": [
    {
      "role": "user",
      "parts": [
        {
          "text": "What's my company car policy?"
        }
      ]
    }
  ],
  "tools": [{
    "retrieval": {
      "externalApi": {
        "api_spec": "ELASTIC_SEARCH",
    "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
    "apiAuth": {
      "apiKeyConfig": {
            "apiKeyString": "ApiKey &lt;API_KEY&gt;"
      }
    },
    "elasticSearchParams": {
      "index": "&lt;my-index&gt;",
      "searchTemplate": "&lt;my-search-template&gt;"
    }
      }
    }
  }]
}<p>위의 예제에서 API의 <code>retrieval</code> 필드가 Gemini 2.0 Flash에 콘텐츠 생성을 요청하는 경우, 요청에 대한 검색 엔진을 컨텍스트에 맞게 설정할 수 있습니다. <code>api_spec</code> 을 "ELASTIC_SEARCH"로 설정하면 API 키와 클러스터 엔드포인트(요청을 Elastic 클러스터로 라우팅하는 데 필요), 데이터를 검색할 인덱스, 검색 로직에 사용할 검색 템플릿과 같은 추가 구성 매개변수를 사용할 수 있습니다.</p><p>마찬가지로 접지 생성 API를 사용하여 <code>groundingSpec</code> 파라미터를 설정하면 동일한 결과를 얻을 수 있습니다:</p>curl -X POST -H "Authorization: Bearer $(gcloud auth print-access-token)" -H "Content-Type: application/json" https://us-discoveryengine.googleapis.com/v1alpha/projects/&lt;PROJECT_ID&gt;/locations/global:generateGroundedContent -d '
{
  "contents": [{
    "role": "user",
    "parts": [{
      "text": "What do I need to patch a hole in my drywall?"
    }]
  }],
  "groundingSpec": {
    "groundingSources": [{
      "elasticSource": {
        "endpoint": "https://&lt;my-elastic-cluster&gt;.gcp.elastic-cloud.com:9243",
        "index": "&lt;my-index&gt;",
        "searchTemplate": "&lt;my-search-template",
        "apiKey": "projects/&lt;PROJECT_ID&gt;/secrets/api-key/versions/latest"
      }
    }]
  }
}
'<p>두 가지 접근 방식 모두에서 응답은 쿼리를 지원하기 위해 Elasticsearch에서 찾은 가장 관련성이 높은 비공개 문서와 연결된 관련 데이터 소스로 답변을 제공합니다.</p><p>하지만 단순함을 특정 요구사항과 사용 사례를 충족하기 위한 개인화 부족과 혼동해서는 안 됩니다. 이를 염두에 두고 검색 구성을 사용자의 시나리오에 맞게 완벽하게 조정할 수 있도록 설계했습니다.</p><h2>손끝에서 완벽하게 사용자 지정 가능한 검색: 검색 템플릿</h2><p>검색 시나리오를 최대한 맞춤 설정할 수 있도록 Google은 Google Cloud와 협력하여 잘 알려진 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/search-template.html">검색 템플릿을</a> 기반으로 환경을 구축했습니다. Elasticsearch 검색 템플릿은 동적이고 재사용 가능하며 유지 관리가 가능한 검색 쿼리를 생성하기 위한 훌륭한 도구입니다. 쿼리 구조를 미리 정의하고 재사용할 수 있습니다. 개발 시간을 절약하고 오류 발생 가능성을 줄이기 때문에 다른 매개변수로 유사한 쿼리를 실행할 때 특히 유용합니다. 템플릿에는 변수에 대한 자리 표시자를 포함할 수 있으므로 다양한 검색 요구 사항에 맞게 쿼리를 동적으로 조정할 수 있습니다.</p><p>접지를 위해 Vertex AI API와 Elasticsearch를 사용하는 동안, 위의 코드 스니펫과 같이 원하는 검색 템플릿을 참조해야 하며, 여기서 검색 로직이 구현되어 Elasticsearch로 푸시됩니다. Elastic 파워 유저는 검색 접근 방식을 비동기적으로 관리, 구성 및 업데이트하고 특정 인덱스, 모델 및 데이터에 맞게 조정할 수 있으며, Vertex AI 사용자, 웹 앱 개발자 또는 AI 엔지니어는 접지 API에서 템플릿의 이름만 지정하면 됩니다.</p><p>이 설계를 통해 완벽한 사용자 정의가 가능하여 광범위한 Elasticsearch 검색 기능을 Google Cloud AI 환경에서 마음대로 사용할 수 있으며, Elastic에 익숙하지 않은 개발자도 모듈성, 투명성, 사용 편의성을 보장합니다.</p><p>BM25 검색, 시맨틱 검색 또는 이 둘의 하이브리드 접근 방식이 필요할 때마다(이미 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/retrievers-overview.html">검색기를</a> 사용해 보셨나요?)? 단일 검색 API 호출로 구성 가능한 검색 기술), 검색 템플릿에서 사용자 지정 로직을 정의하면 Vertex AI가 자동으로 이를 활용할 수 있습니다.</p><p>이는 벡터와 결과를 관리하기 위해 선택한 임베딩 및 재랭크 모델에도 적용됩니다. 사용 사례에 따라 Elastic의 ML 노드에서 모델을 호스팅하거나, 추론 API를 통해 타사 서비스 엔드포인트를 사용하거나, 온프레미스에서 로컬 모델을 실행할 수 있습니다. 검색 템플릿을 통해 이 작업을 수행할 수 있으며, 다음 섹션에서 그 방법을 살펴보겠습니다.</p><h2>참조 템플릿으로 시작한 다음 나만의 템플릿을 만들 수 있습니다.</h2><p>빠르게 시작할 수 있도록 초기 참조용으로 사용할 수 있는 호환 가능한 검색 템플릿 샘플 세트를 제공했으며, 이를 기반으로 사용자 지정 템플릿을 수정하고 구축할 수 있습니다:</p><ul><li><p>ELSER 모델을 사용한 시맨틱 검색(스파스 벡터 및 청킹)</p></li><li><p>e5 다국어 모델을 사용한 시맨틱 검색(고밀도 벡터 및 청킹)</p></li><li><p>버텍스 AI 텍스트 임베딩 모델을 사용한 하이브리드 검색</p></li></ul><p>이 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/Cloud-Vertex-AI/search-templates">GitHub 리포지토리에서</a> 찾을 수 있습니다.</p><p>한 가지 예를 들어 보겠습니다. 제품 카탈로그에 Google Cloud의 Vertex AI API를 사용하여 임베딩을 생성하는 것입니다. 먼저, 아래와 같이 Elasticsearch에서 검색 템플릿을 생성해야 합니다:</p>PUT _scripts/google-template-knn
{
  "script": {
    "lang": "mustache",
    "source": {
      "_source": {
        "excludes": [ "title_embedding", "description_embedding", "images" ]
      },
        "size": "{{num_hits}}",
          "knn" : [
          { 
            "field": "description_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
              }
            },
            "boost": 0.4
          },
          {
            "field": "title_embedding",
            "k": 5,
            "num_candidates": 10,
            "query_vector_builder": {
              "text_embedding": {
                "model_id": "googlevertexai_embeddings_004",
                "model_text": "{{query}}"
            }
          },
          "boost": 0.6
          }
          ]
    }  
  }
}<p>이 예에서는 하나의 검색 내에서 두 개의 필드에 대해 KNN 검색을 실행합니다: <code>title_embedding</code> - 제품 이름이 포함된 벡터 필드와 제품 설명이 포함된 <code>description_embedding</code> 필드입니다.</p><p><code>excludes</code> 구문을 활용하여 처리 과정에서 노이즈를 유발하고 최종 답변의 품질에 영향을 줄 수 있는 불필요한 필드를 LLM에 반환하지 않도록 할 수 있습니다. 이 예제에서는 벡터와 이미지 URL이 포함된 필드를 제외했습니다.</p><p>벡터는 이전에 다음과 같이 정의된 Vertex AI 임베딩 API( <code>googlevertexai_embeddings_004</code>)에 대한 추론 엔드포인트를 통해 제출된 입력에 대해 쿼리 시점에 즉석에서 생성됩니다:</p>PUT /_inference/text_embedding/googlevertexai_embeddings_004
{
    "service": "googlevertexai",
    "service_settings": {
        "service_account_json": "&lt;your_service_account_key&gt;",
        "model_id": "text-embedding-004",
        "location": "us-central1",
        "project_id": "&lt;your_gcp_project&gt;"
    }
}<p>Elastic의 추론 API를 사용하는 방법에 대한 추가 정보는 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/inference-apis.html">여기에서</a> 확인할 수 있습니다.</p><p>이제 템플릿 검색을 테스트할 준비가 되었습니다:</p>GET product-catalog-with-embeddings/_search/template
{
  "id": "google-template-knn",
  "params": {
    "query": "What do I need to patch a hole in my drywall?",
    "index_name": "product-catalog-with-embeddings",
    "num_hits": 3
  }
}<p><code>params</code> 필드는 템플릿 스크립트에서 설정한 변수를 이중 중괄호 괄호로 대체합니다. 현재 Vertex AI LLM 및 Grounded Generation API는 다음과 같은 입력 변수를 Elastic에 전송할 수 있습니다:</p><ul><li><p>"쿼리" - 검색할 사용자 쿼리</p></li><li><p>"index_name" - 검색할 인덱스의 이름입니다.</p></li><li><p>"num_hits" - 최종 출력에서 검색할 문서 수</p></li></ul><p>다음은 샘플 출력입니다:</p>{
        "_index": "product-catalog-with-embeddings",
        "_id": "9ZQCm5IBcrGI1ivqV-f_",
        "_score": 0.4925191,
        "_ignored": [
          "description.keyword",
          "images.keyword"
        ],
        "_source": {
          "description": "DAP Eclipse Rapid Wall Repair Patch is a new, revolutionary product solution for repairing drywall damage. No more waiting for spackling to dry or messy sanding. DAP Eclipse allows you to patch drywall damage and paint immediately, allowing you to finish your project faster. This all-in-1, mess free solution not only provides a permanent, long-lasting repair but also superior impact resistance for areas that may see reoccurring impact, such as behind a door.",
          "availability": "InStock",
          "model_id": "googlevertexai_embeddings_004",
          "title": "4 in. Eclipse Wall Repair Patch (2-Pack)",
          "url": "https://www.myDIYwebsite.com/p/DAP-4-in-Eclipse-Wall-Repair-Patch-2-Pack-7079809164/317967195",
          "price": 23.96,
          "product_id": 317967195,
          "currency": "USD",
          "brand": "DAP"
        }<p>위의 쿼리는 바로 이전에 생성된 검색 템플릿을 참조할 때 Google Cloud의 Vertex AI가 Elasticsearch에서 백그라운드에서 실행하는 쿼리입니다. "건식 벽체에 어떤 패치가 필요하나요?"라고 질문하면 채팅 상담원이 일반적인 제안 대신 구체적인 제품을 추천해 드립니다!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte88a0c34242df6fa/6a17e6263e03d704504f2c2e/8c8b1374da7e9b2c17df8758acb448eed5b74e2d-1473x913.png" alt="Google의 Vertex AI 플랫폼에서 프롬프트 만들기" /><h2>Elastic과 Google Cloud를 통한 엔드투엔드 GenAI 여정</h2><p>Elastic은 Google Cloud와 협력하여 프로덕션에 바로 사용할 수 있는 엔드투엔드 GenAI 환경과 솔루션을 개발합니다. 방금 살펴본 바와 같이, Elastic은 벡터 검색 기능을 사용하여 원활하고 근거가 있는 Gemini 모델 프롬프트와 에이전트를 지원하는 Vertex AI 플랫폼의 UI와 SDK에 직접 통합된 최초의 ISV입니다. 또한, Elastic은 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/infer-service-google-vertex-ai.html">Vertex AI</a> 및 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/infer-service-google-ai-studio.html">Google AI Studio의</a>임베딩, 재순위 지정, 완성 모델과 통합되어 Google Cloud 환경을 벗어나지 않고도 벡터를 생성하고 순위를 지정하여 <a href="https://cloud.google.com/responsible-ai?hl=en">책임감 있는 AI </a>원칙을 보장합니다. 멀티모달 접근 방식을 지원함으로써 다양한 데이터 형식의 애플리케이션을 공동으로 촉진합니다.</p><p><a href="https://www.elastic.co/kr/search-labs/blog/vertex-ai-elasticsearch-playground-fast-rag-apps">플레이그라운드를</a> 통해 GenAI 검색 코드를 조정, 테스트 및 내보낼 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a978835a5418eb9/6a17e628fbc5f8abff491a6a/b1fc48dba1713cd01c4d7f3296587d0c4e6e7e0c-862x651.png" alt="Elastic과 Google Cloud를 통한 엔드투엔드 GenAI 여정 " /><p>하지만 검색 앱 구축에만 그치지 않습니다. Elastic은 Gemini 모델을 활용하여 <a href="https://www.elastic.co/kr/blog/elastic-google-vertex-ai-integration">Elastic AI 어시스턴트, 공격 탐색, 자동 가져오기 기능과</a> 같은 IT 운영을 강화함으로써 보안 분석가와 SRE가 저가치 작업에 대한 일상적인 피로를 줄이고 비즈니스 개선에 집중할 수 있도록 지원합니다. 또한 Elastic은 응답 시간, 토큰, 리소스와 같은 메트릭과 로그를 추적하여 최적의 성능을 보장하기 위해 <a href="https://www.elastic.co/kr/guide/en/integrations/current/gcp_vertexai.html">Vertex AI 사용량을 포괄적으로 모니터링할 수 있습니다.</a> 데이터 수집 및 임베딩 생성부터 하이브리드 검색을 통한 근거 마련에 이르기까지 전체 GenAI 라이프사이클을 함께 관리하며, LLM 기반 작업을 통해 GenAI 도구의 강력한 가시성 및 보안을 보장합니다.</p><h2>자세히 알아보고 사용해 보세요!</h2><p>이 기능을 사용해 보고 싶으신가요? 이 기능은 현재 구글 클라우드 프로젝트에서 정식 버전으로 출시되었습니다!</p><p>아직 시작하지 않으셨다면, Elastic Search AI Platform을 시작하고 기능을 살펴보는 가장 쉬운 방법 중 하나는 <a href="https://cloud.elastic.co/registration">무료 Elastic Cloud 체험판을</a> 사용하시거나 <a href="https://console.cloud.google.com/marketplace/product/elastic-prod/elastic-cloud?pli=1">Google Cloud Marketplace를</a> 통해 구독하는 것입니다.</p><p><em>이 게시물에 설명된 모든 기능의 출시와 시기는 Elastic의 단독 재량에 따라 결정됩니다. 현재 사용할 수 없는 기능이나 기능은 제때 또는 전혀 제공되지 않을 수 있습니다. Elastic, Elasticsearch 및 관련 상표는 미국 및 기타 국가에서 Elasticsearch N.V.의 상표, 로고 또는 등록 상표입니다. 기타 모든 회사 및 제품명은 해당 소유자의 상표, 로고 또는 등록 상표입니다.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-google-cloud-vertex-ai-native-grounding</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Valerio Arvizzigno]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt973959b32f2070d6/6a17e62a6317304ec1585a5e/d1f1c8860f1f0b989ad698a882f869de7284ab78-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[HNSW 그래프 병합 속도 향상]]></title>
    <description><![CDATA[여러 개의 HNSW 그래프를 작성하는 데 드는 오버헤드를 줄이기 위해, 특히 그래프 병합 비용을 줄이기 위해 저희가 해온 작업을 살펴보세요.]]></description>
    <content:encoded><![CDATA[<p>과거에는 여러 개의 <a href="https://www.elastic.co/kr/search-labs/blog/hnsw-graph">HNSW 그래프를</a> 검색해야 할 때 발생하는 몇 가지 문제와 이를 완화할 수 있는 방법에 대해 논의한<a href="https://www.elastic.co/kr/search-labs/blog/multi-graph-vector-search">적이 있습니다.</a> 당시 저희는 계획했던 몇 가지 추가 개선 사항을 암시했습니다. 이 게시물은 그 작업의 정점입니다.</p><p>왜 여러 개의 그래프를 사용해야 하나요? 이는 불변 세그먼트라는 루씬의 아키텍처 선택에 따른 부작용입니다. 대부분의 아키텍처 선택과 마찬가지로 장단점이 있습니다. 예를 들어, 저희는 최근에 서버리스 Elasticsearch를 정식 버전으로 출시했습니다. 이러한 맥락에서 우리는 효율적인 인덱스 복제, 인덱스와 쿼리 계산을 분리하고 독립적으로 자동 확장하는 기능 등 불변 세그먼트를 통해 매우 중요한 이점을 얻었습니다. 벡터 양자화의 경우 세그먼트 병합을 통해 데이터 특성에 맞게 파라미터를 업데이트할 수 있습니다. 이러한 맥락에서 데이터 특성을 측정하고 인덱싱 선택을 재검토할 수 있는 기회를 갖는다는 것은 다른 장점도 있다고 생각합니다.</p><p>이 글에서는 여러 개의 HNSW 그래프를 작성하는 데 드는 오버헤드를 크게 줄이고 특히 그래프를 병합하는 데 드는 비용을 줄이기 위해 수행한 작업에 대해 설명합니다.</p><h3>배경</h3><p>관리 가능한 세그먼트 수를 유지하기 위해 Lucene은 주기적으로 세그먼트를 병합해야 하는지 여부를 확인합니다. 이는 현재 세그먼트 수가 기본 세그먼트 크기와 병합 정책에 따라 결정되는 목표 세그먼트 수를 초과하는지 확인하는 것과 같습니다. 개수를 초과하면 제약 조건을 위반하는 동안 Lucene은 세그먼트 그룹을 병합합니다. 이 프로세스는 <a href="https://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html">다른 곳에</a> 자세히 설명되어 있습니다.</p><p>Lucene은 쓰기 증폭에서 대수적 증가를 달성하기 때문에 비슷한 크기의 세그먼트를 병합하는 방법을 선택합니다. 벡터 인덱스의 경우 쓰기 증폭은 벡터가 그래프에 삽입되는 횟수입니다. Lucene은 약 10개의 그룹으로 세그먼트를 병합하려고 시도합니다. 결과적으로 벡터는 대략 {10}\left (\frac{n}{n_0}\right )번 그래프에 삽입되며, 여기서  인덱스 벡터 수이고  예상되는 기본 세그먼트 벡터 수입니다. 대수적 증가로 인해 쓰기 증폭은 거대한 인덱스의 경우에도 한 자릿수입니다. 그러나 그래프를 병합하는 데 소요되는 총 시간은 쓰기 증폭에 선형적으로 비례합니다.</p><p>HNSW 그래프를 병합할 때 가장 큰 세그먼트의 그래프를 유지하고 다른 세그먼트의 벡터를 삽입하는 작은 최적화를 이미 수행했습니다. 이것이 위의 9/10 요소의 이유입니다. 아래에서는 병합하는 모든 그래프의 정보를 사용하여 훨씬 더 나은 결과를 얻을 수 있는 방법을 보여줍니다.</p><h3>HNSW 그래프 병합</h3><p>이전에는 가장 큰 그래프를 유지하고 벡터가 포함된 그래프를 무시하고 다른 그래프에서 벡터를 삽입했습니다. 아래에서 사용하는 핵심 인사이트는 우리가 폐기하는 각 HNSW 그래프에 포함된 벡터에 대한 중요한 근접성 정보가 포함되어 있다는 것입니다. 이 정보를 사용하여 적어도 일부 벡터의 삽입을 가속화하고자 합니다.</p><p>병합 정책을 구축하는 데 사용할 수 있는 원자 연산이므로 작은 그래프  )를 큰 그래프 )에 삽입하는 문제에 중점을 둡니다.</p><p>전략은 큰 그래프에 삽입할  정점 하위 집합을 찾는 것입니다. 그런 다음 작은 그래프에서 이러한 정점의 연결성을 사용하여 나머지 정점  삽입하는 속도를 높입니다. 아래에서는 작은 그래프와 큰 그래프에서 각각  및  를 사용하여 정점  이웃을 나타냅니다. 프로세스를 개략적으로 설명하면 다음과 같습니다.</p><p><code>MERGE-HNSW</code></p><p><code>Inputs </code><code> and </code></p><p><code>1</code><code>Find </code><code> to insert into </code><code> using COMPUTE-JOIN-SET</code>
<code>2</code><code>Insert each vertex </code><code> into </code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code><code>FAST-SEARCH-LAYER</code>
<code>7</code><code>SELECT-NEIGHBORS-HEURISTIC</code>
<code>8</code></p><p>아래에서 설명하는 절차를 사용하여 집합  계산합니다(1줄). 그런 다음 표준 HNSW 삽입 절차(2줄)를 사용하여  모든 정점을 큰 그래프에 삽입합니다. 삽입하지 않은 각 정점에 대해 삽입한 이웃 정점과 그 이웃 정점을 큰 그래프(4줄과 5줄)에서 찾습니다. 이 집합으로 시드된 <code>FAST-SEARCH-LAYER</code> 절차(6줄)를 사용하여 HNSW <a href="https://arxiv.org/pdf/1603.09320">논문</a> (7줄)에서 <code>SELECT-NEIGHBORS-HEURISTIC</code> 후보를 찾습니다. 사실상 <code>SEARCH-LAYER</code> 을 <code>INSERT</code> 방법(논문의 알고리즘 1)의 후보 집합을 찾는 것으로 대체하는 것이며, 그 외에는 변경 사항이 없습니다. 마지막으로 방금 삽입한 버텍스를  추가합니다(8행).</p><p>이 기능이 작동하려면  모든 버텍스에  이웃이 하나 이상 있어야 합니다. 실제로, 우리는  버텍스에 대해 최대 레이어 연결성인 일부   실제 HNSW 그래프에서는 정점 각도가 상당히 분산되어 있는 것을 관찰할 수 있습니다. 아래 그림은 Lucene HNSW 그래프의 최하위 레이어에 대한 일반적인 버텍스 도의 누적 밀도 함수를 보여줍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc815e1a9c3bb0d06/6a17e178ec0f89308a5a6564/44f001b3a1bc6627e172fed5c52a02cfdcb4cd66-1324x898.png" alt="HNSW 그래프: 버텍스 차수 분포 예시" /><p> 고정값을 사용하는 방법과 버텍스 차수의 함수로 만드는 방법을 살펴봤습니다. 두 번째 선택은 그래프 품질에 미치는 영향을 최소화하면서 속도를 크게 향상시킬 수 있으므로 다음과 같이 선택했습니다.</p><p>정의상 |는 작은 그래프에서 정점  차수와 같다는 점에 유의하세요. 하한이 2라는 것은 차수가 2보다 작은 모든 정점을 삽입한다는 의미입니다.</p><p>간단한 계산 인수를 통해  신중하게 선택하면   직접 삽입하기만 하면 된다는 것을 알 수 있습니다. 구체적으로 그래프의 끝 꼭지점 중 하나를  정확히 삽입하면 그래프의 가장자리에 색을 입힙니다.   최소  이웃을 가지려면  에지를 채색해야 한다는 것을 알 수 있습니다. 또한 다음을 기대합니다.</p><p>여기서  _U\left [N_s(U)|\right] 는 작은 그래프에서 평균 버텍스 차수입니다.  각 정점 u에 대해 최대  의 가장자리를 색칠합니다. 따라서 채색할 에지의 총 개수는 최대 _U\left [|N_s(U)|\right]입니다.  신중하게 선택하면 이 수의 가장자리에 가깝게 색을 칠할 수 있으므로 모든 정점을 포함하려면  다음을 만족해야 합니다.</p><p>이는  {1}{4}|V_s|=\frac{1}{5}|V_s|를 의미합니다.</p><p><code>SEARCH-LAYER</code> 이 런타임을 지배한다면 병합 시간을 최대  단축할 수 있음을 시사합니다. 쓰기 증폭의 대수적 증가를 감안할 때, 이는 매우 큰 인덱스의 경우에도 일반적으로 하나의 그래프를 작성할 때보다 빌드 시간이 두 배밖에 걸리지 않는다는 것을 의미합니다.</p><p>이 전략의 위험은 그래프 품질이 손상된다는 점입니다. 처음에는 노옵으로 시도했습니다 <code>FAST-SEARCH-LAYER</code>. 특히 단일 세그먼트로 병합할 때 지연 시간에 따른 리콜이 영향을 받을 정도로 그래프 품질이 저하되는 것으로 나타났습니다. 그런 다음 그래프를 제한적으로 검색하여 다양한 대안을 탐색했습니다. 결국 가장 효과적인 선택은 가장 단순한 것이었습니다. <code>SEARCH-LAYER</code> 을 사용하되 <code>ef_construction</code>. 이 매개변수화를 통해 우수한 품질의 그래프를 얻으면서도 병합 시간을 평균 30% 조금 넘게 줄일 수 있었습니다.</p><h3>조인 집합 계산</h3><p>좋은 조인 집합을 찾는 것은 HNSW 그래프 커버링 문제로 공식화할 수 있습니다. 욕심 휴리스틱은 최적의 그래프 커버를 근사화하기 위한 간단하고 효과적인 휴리스틱입니다. 우리가 취하는 접근 방식은 한 번에 하나씩 정점을 골라 이득이 감소하는 순서로  추가하는 방식입니다. 이득은 다음과 같이 정의됩니다:</p><p>여기서 는  벡터  개수를 나타내고  은 표시 함수입니다. 이득에는  추가한 버텍스 수의 변화, 즉 이 포함되는데, 이는 덜 커버되는 버텍스를 추가함으로써 목표에 더 가까워지기 때문입니다. 이득 계산은 아래 그림에서 중앙 주황색 버텍스에 대해 설명합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a353d56fa09af5f/6a17e17a2f4a5c33f7fa8825/fe11cd55a7d94d9e0b0f4d47ea309a99075c355d-540x474.png" alt="HNSW 그래프에서 조인 집합 J에 추가할 버텍스 게인" /><p>각 버텍스  대해 다음과 같은 상태를 유지합니다:</p><ol><li><p>오래되었는지 여부,</p></li><li><p>그 이득 ),</p></li><li><p> 인접한 정점의 개수를 로 표시합니다,</p></li><li><p>타이 브레이킹에 사용되는 [0,1] 범위의 난수입니다.</p></li></ol><p>조인 집합을 계산하는 의사 코드는 다음과 같습니다.</p><p><code>COMPUTE-JOIN-SET</code></p><p><code>Inputs </code></p><p><code>1</code>
<code>2</code>
<code>3</code><code>for </code><code> do</code>
<code>4</code>
<code>5</code>
<code>6</code>
<code>7</code><code>while </code><code> do
8</code><code> maximum gain vertex in </code>
<code>9</code><code>Remove the state for </code><code> from </code>
<code>10</code><code>if </code><code> is not stale then</code>
<code>11</code>
<code>12</code>
<code>13</code><code>for </code><code> do</code>
<code>14</code><code>mark </code><code> as stale if </code>
<code>15</code><code>mark neighbors of </code><code> stale if </code><code>
16</code><code>
17</code><code>else
18</code><code>
19</code><code>if </code><code> then
20</code><code>
21</code><code>return </code></p><p>먼저 1~5줄에서 상태를 초기화합니다.</p><p>메인 루프의 각 반복에서 처음에는 최대 이득 버텍스(8번째 줄)를 추출하여 무작위로 동점을 끊습니다. 변경하기 전에 버텍스의 게인이 오래되었는지 확인해야 합니다. 특히,  버텍스를 추가할 때마다 다른 버텍스의 이득에 영향을 미칩니다:</p><ol><li><p>모든 이웃이  추가 이웃을 가지고 있기 때문에 이득이 바뀔 수 있습니다 (14행).</p></li><li><p>이제 이웃이 완전히 커버되면 모든 이웃의 이득이 바뀔 수 있습니다(14~16줄).</p></li></ol><p>게인을 지연 방식으로 다시 계산하므로 버텍스의 게인을  삽입하려는 경우에만 다시 계산합니다(18~20행). 이득은 항상 감소하기 때문에 삽입해야 하는 버텍스를 놓칠 수 없습니다.</p><p>종료 시점을 결정하기 위해  추가한 버텍스의 총 이득을 추적하기만 하면 됩니다. 또한,  {exit}적어도 하나의 버텍스는 0이 아닌 이득을 가지므로 항상 진전이 있습니다.</p><h3>결과</h3><p>지원되는 세 가지 거리 메트릭(유클리드, 코사인, 내적 곱)을 모두 포함하는 네 가지 데이터 세트에 대해 실험을 진행했습니다:</p><ol><li><p>쿼라-E5-small: 522931개 문서, 384개 차원, 코사인 유사도를 사용합니다,</p></li><li><p>코히어-위키백과-v2: 1백만 개의 문서, 768개의 차원, 코사인 유사도를 사용합니다,</p></li><li><p>요점 1백만 문서, 960개 차원, 유클리드 거리 사용, 그리고</p></li><li><p>코히어-위키백과-v3: 1M 문서, 1024 크기, 최대 내부 제품 사용.</p></li></ol><p>각 데이터 세트에 대해 두 가지 양자화 수준을 평가합니다:</p><ol><li><p>int8 - 차원당 1바이트 정수를 사용하고</p></li><li><p>BBQ - 차원당 단일 비트를 사용합니다.</p></li></ol><p>마지막으로, 각 실험에서 두 가지 검색 깊이에서 검색 품질을 평가하고 인덱스를 구축한 후와 단일 세그먼트로 강제 병합한 후를 조사했습니다.</p><p>요약하면, 모든 경우에서 그래프 품질을 유지하면서 인덱싱 및 병합 속도를 일관되게 크게 향상시켜 검색 성능을 향상시켰습니다.</p><h4>실험 1: int8 정량화</h4><p>베이스라인에서 제안된 변경 사항인 후보까지의 평균 속도 향상은 다음과 같습니다:</p><p>색인 시간 속도 향상: <strong>1.</strong></p><p>강제 병합 속도 향상: <strong>1.</strong></p><p>이는 다음과 같은 실행 시간 분석에 해당합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8422be122ef1a9c9/6a17e17bbe6086522e00467d/5f9f6d487b4c74c4998aa47465912c1a9743e928-734x479.png" alt="기준 및 후보 병합 전략에 대한 인덱싱 및 병합 시간" /><p>정확성을 위해 정확한 시간은 다음과 같습니다.</p><p></p><p>색인</p><p></p><p>병합</p><p></p><p>데이터 세트</p><p>기준선</p><p>후보</p><p>구축</p><p>후보</p><p>쿼라-E5-small</p><p>112.41s</p><p>81.55s</p><p>113.81s</p><p>70.87s</p><p>위키-코히어-v2</p><p>158.1s</p><p>122.95s</p><p>425.20s</p><p>239.28s</p><p>요점</p><p>141.82s</p><p>119.26s</p><p>536.07s</p><p>279.05s</p><p>위키-코히어-v3</p><p>211.86s</p><p>168.22s</p><p>654.97s</p><p>414.12s</p><p>아래에는 여러 세그먼트가 있는 인덱스(모든 벡터를 인덱싱한 후 기본 병합 전략의 최종 결과)와 단일 세그먼트로 강제 병합한 후의 두 가지 검색 깊이인 recall@10과 recall@100에서 후보(점선)와 기준선을 비교한 리콜 대 지연 시간 그래프가 나와 있습니다. 곡선이 더 높고 왼쪽으로 갈수록 더 좋은데, 이는 더 낮은 지연 시간에서 더 높은 회상률을 의미합니다.</p><p>보시다시피, 여러 세그먼트 인덱스의 경우 Cohere v3 데이터 세트의 후보가 더 우수하고 다른 모든 데이터 세트의 경우 약간 떨어지지만 거의 비슷합니다. 단일 세그먼트로 병합한 후 리콜 곡선은 모든 경우에 대해 거의 동일합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf674622469bf6a90/6a17e17dfbc5f874a84919c9/9195297f19f90b172d832ba56bdada7b0d8a768b-985x392.png" alt="인덱스 구축 후 지연 시간 대비 @10 및 @100 리콜하기" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde89f7e94ac823f4/6a17e17fe9ea876429a9c507/c866f32d579ae2b841e92ca733dd29c0e214ac6b-986x386.png" alt="단일 세그먼트로 병합 후 지연 시간 대비 @10 및 @100 리콜하기" /><h4>실험 2: BBQ 정량화</h4><p>기준선에서 후보까지의 평균 속도 향상은 다음과 같습니다:</p><p>색인 시간 속도 향상: <strong>1.</strong></p><p>강제 병합 속도 향상: <strong>1.</strong></p><p>이는 다음과 같은 실행 시간 분석에 해당합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5b0eb305222fa5c2/6a17e18125daab514f08a18c/11be0cf6b63480dc703409329357ea4847b55051-740x415.png" alt="기준 및 후보 병합 전략에 대한 인덱싱 및 병합 시간" /><p>정확성을 위해 정확한 시간은 다음과 같습니다.</p><p></p><p>색인</p><p></p><p>병합</p><p></p><p>데이터 세트</p><p>기준선</p><p>후보</p><p>구축</p><p>후보</p><p>쿼라-E5-small</p><p>70.71s</p><p>58.25s</p><p>59.38s</p><p>40.15s</p><p>위키-코히어-v2</p><p>203.08s</p><p>142.27s</p><p>107.27s</p><p>85.68s</p><p>요점</p><p>110.35s</p><p>105.52s</p><p>323.66s</p><p>202.2s</p><p>위키-코히어-v3</p><p>313.43s</p><p>190.63s</p><p>165.98s</p><p>159.95s</p><p>여러 세그먼트 인덱스의 경우, 기준선이 약간 더 나은 코히어 v2를 제외한 거의 모든 데이터 세트에서 후보가 더 우수합니다. 단일 세그먼트 인덱스의 경우 리콜 곡선은 모든 경우에 대해 거의 동일합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb12174abeadbcdd1/6a17e1822f4a5c4cc2fa8829/7da1be27bab5a7f5f9c9a1882ea35761a4a0ba5c-973x383.png" alt="인덱스 구축 후 지연 시간 대비 @10 및 @100 리콜하기" /><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1b88d4f7fec9905/6a17e184414c64a8de9450b4/a26af30a56c55a12addee7e4fb7e8f606f366668-979x386.png" alt="단일 세그먼트로 병합된 지연 시간 대비 @10 및 @100을 기억해 보세요." /><h3>결론</h3><p>이 블로그에서 설명한 알고리즘은 곧 출시될 Lucene 10.2와 이를 기반으로 하는 Elasticsearch 릴리즈에서 사용할 수 있습니다. 사용자는 이 새 버전에서 병합 성능이 향상되고 인덱스 빌드 시간이 단축되는 이점을 누릴 수 있습니다. 이 변경은 벡터 및 하이브리드 검색을 위한 빠르고 효율적인 Lucene과 Elasticsearch를 만들기 위한 지속적인 노력의 일환입니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hnsw-graphs-speed-up-merging</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Thomas Veasey,Mayya Sharipova]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fca6a01d0541cfb/6a17e187033c8d46486bb0e1/49a6c880f5dedd0fa502ece5be124824ee218cc0-1792x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch에서 지연 상호작용 모델 확장하기 - 2부]]></title>
    <description><![CDATA[이 문서에서는 디스크 공간 사용량을 줄이고 계산 효율성을 개선하는 등 대규모 프로덕션 워크로드에 대비하여 지연 상호작용 벡터를 준비하는 기술을 살펴봅니다.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">ColPali에 대한 이전 블로그</a>에서는 Elasticsearch로 시각적 검색 애플리케이션을 생성하는 방법을 살펴보았습니다. 주로 ColPali와 같은 모델이 애플리케이션에 가져다주는 가치에 초점을 맞췄지만, 이러한 모델은 E5와 같은 바이인코더를 사용하는 벡터 검색에 비해 성능상의 단점이 있습니다.</p><p><a href="https://www.elastic.co/search-labs/blog/elastiacsearch-colpali-document-search">1부</a>의 예시를 바탕으로, 이 블로그는 다양한 기법과 Elasticsearch의 강력한 벡터 검색 도구를 사용하여 대규모 프로덕션 작업에 적합한 지연 상호작용 벡터를 준비하는 방법을 탐구합니다.</p><p>전체 코드 예시는 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/colpali">GitHub</a>에서 확인하실 수 있습니다.</p><h2>후기 상호작용 모델의 과제</h2><p>ColPali는 인덱스에 있는 문서에 대해 페이지당 1000개 이상의 벡터를 생성합니다.</p><p>이로 인해 지연 상호작용 벡터를 사용할 때 두 가지 문제가 발생합니다.</p><ol><li><p>디스크 공간: 이러한 모든 벡터를 디스크에 저장하면 상당한 저장 공간 사용량이 발생하며, 이는 확장 시 비용이 많이 들 수 있습니다.</p></li><li><p>계산: <code>maxSimDotProduct()</code> 비교를 사용하여 문서 순위를 매길 때는 각 문서에 대한 이러한 모든 벡터를 쿼리의 N 벡터와 비교해야 합니다.</p></li></ol><p>이러한 문제를 해결하기 위한 몇 가지 기술을 살펴보겠습니다.</p><h2>지연 상호작용 모델을 최적화하는 기술</h2><h3>비트 벡터</h3><p>디스크 공간을 줄이기 위해 이미지를 비트 벡터로 압축할 수 있습니다. 다음과 같은 간단한 Python 함수를 사용하여 멀티 벡터를 비트 벡터로 변환할 수 있습니다.</p>def to_bit_vectors(embeddings: list) -&gt; list:
    return [
        np.packbits(np.where(np.array(embedding) &gt; 0, 1, 0))
        .astype(np.int8)
        .tobytes()
        .hex()
        for embedding in embeddings
    ]<p>이 함수의 핵심 개념은 간단합니다. 0보다 큰 값은 1이 되고, 0보다 작은 값은 0이 됩니다. 그 결과 0과 1로 이루어진 배열이 생성되며, 이를 16진수 스트링으로 변환하여 비트 벡터를 나타냅니다.</p><p>인덱스 매핑을 위해 <code>element_type</code> 매개변수를 <code>bit</code>로 설정했습니다.</p>mappings = {
    "mappings": {
        "properties": {
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>인덱스에 새로운 비트 벡터를 모두 작성한 후, 다음 코드를 사용하여 비트 벡터의 순위를 매길 수 있습니다.</p>query = "What do companies use for recruiting?"
query_vector = to_bit_vectors(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimInvHamming(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaf0c8f999d68701/6a17f46696142a6f46eb1c56/51b989446e4099745971e1eac27d147a78d13e0a-1600x480.png" alt="" /><p>약간의 정확도를 희생하는 대신 비트 마스크, SIMD 등과 같은 최적화를 활용할 수 있는 해밍 거리(<code>maxSimInvHamming(...)</code>)를 사용할 수 있습니다. <a href="https://www.elastic.co/search-labs/blog/bit-vectors-in-elasticsearch">비트 벡터와 해밍 거리에 대한 자세한 내용은 블로그</a>에서 확인하세요.</p><p>대안으로, 쿼리 벡터를 비트 벡터로 변환하지 않고 완전한 충실도의 지연 상호작용 벡터로 검색할 수 있습니다.</p>query = "What do companies use for recruiting?"
query_vector = create_col_pali_query_vectors(query)
es_query = {
    "_source": False,
    "query": {
        "script_score": {
            "query": {
                "match_all": {}
            },
            "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                    "query_vector": query_vector
                }
            }
        }
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2fa6c89fb451b6e0/6a17f468af47b65da9cde0d9/89f3c795c6dc44b2f7d46801320288316b47e1b2-1600x488.png" alt="비트 벡터를 사용하여 지연 상호작용 모델을 최적화한 결과" /><p>이는 비대칭 유사도 함수를 사용하여 벡터를 비교합니다.</p><p></p><p>두 비트 벡터 사이의 일정한 해밍 거리에 대해 생각해 보겠습니다. 문서 벡터 <em>D</em>가 있다고 가정해 봅시다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd4b6ece1ec4dedac/6a17f4694b055d248143234e/366a70a23e37d403788327b7aefc873fd4482f5e-1235x86.png" alt="" /><p>그리고 쿼리 벡터 <em>Q</em>가 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7721df9c8f0d84f1/6a17f46b3e03d77cf54f2dcb/978ec31e0afbc0eaebf548a015b628c0cad84625-1247x84.png" alt="" /><p></p><p>간단한 이진 양자화는 벡터 <em>D</em>를 <code>10101101</code>로, <em>Q</em>를 <code>11111011</code>로 변환합니다. 해밍 거리를 찾으려면 직접적인 비트 연산이 필요하며, 이는 매우 빠릅니다. 이 경우 해밍 거리는 <code>01010110</code>이며 비트 수는 4입니다. 따라서 점수 계산은 해당 해밍 거리의 역수가 됩니다. 유사한 벡터일수록 해밍 거리가 작아지므로 이를 반전시키면 유사한 벡터일수록 더 높은 점수를 받을 수 있습니다. 구체적으로 여기서는 1/4 = <code>0.25</code>가 됩니다.</p><p>그러나 각 차원의 크기가 어떻게 줄어드는지 주목하세요. <code>1</code> 은 <code>1</code>입니다. 그래서 <em>Q</em>의 경우, <code>0.01</code>과 <code>0.79</code> 사이의 차이점이 사라집니다. <code>&gt;0</code>에 따라 단순히 양자화하기 때문에 Q 벡터가 양자화되지 않은 곳에서 작은 트릭을 수행할 수 있습니다. 이렇게 하면 매우 빠른 비트 단위 연산을 할 수는 없지만, D가 여전히 양자화되어 있기 때문에 스토리지 비용을 낮게 유지할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted50315518e2d599/6a17f46c414c6463709452d3/508e8498ba969534d2a0131d431c75735fc27cb9-1399x611.png" alt="" /><p>간단히 말해서, 이는 <em>Q</em>에 제공된 정보를 유지하여 거리 추정 품질을 향상시키고 저장 공간을 최소화합니다.</p><p>비트 벡터를 사용하면 디스크 공간과 쿼리 시 계산 부하를 크게 줄일 수 있습니다. 하지만 할 수 있는 일은 더 많습니다.</p><h3>평균 벡터</h3><p>수십만 개의 문서에 걸쳐 검색을 확장하려면 비트 벡터가 제공하는 성능상의 이점만으로는 충분하지 않습니다. 이러한 유형의 워크로드에 맞게 확장하려면 벡터 검색을 위해 Elasticsearch의 HNSW 인덱스 구조를 활용해야 합니다.</p><p>ColPali는 문서당 약 천 개의 벡터를 생성하는데, 이는 HNSW 그래프에 추가하기에는 너무 많습니다. 따라서 벡터의 수를 줄여야 합니다. 이를 위해 ColPali에 의해 생성된 모든 문서 벡터의 평균을 계산하여 이미지를 임베드할 때 문서의 의미에 대한 단일 표현을 만들 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc053425fbfcf8de8/6a17f46f148009faf1b488aa/7c3b4dffb70bd95f67deb35f8c01e73c5286c2ab-1476x1102.png" alt="모든 지연 상호작용 벡터에 대한 평균 벡터" /><p>현재로서는 Elastic 자체 내에서 이 작업이 불가능하며, Elasticsearch에서 벡터를 수집하기 전에 벡터를 전처리해야 합니다. </p><p>Logstash 또는 수집 파이프라인으로 이 작업을 수행할 수 있지만 여기서는 간단한 Python 함수를 사용하겠습니다:</p>def to_avg_vector(vectors):
    vectors_array = np.array(vectors)
    
    avg_vector = np.mean(vectors_array, axis=0)
    
    norm = np.linalg.norm(avg_vector)
    if norm &gt; 0:
        normalized_avg_vector = avg_vector / norm
    else:
        normalized_avg_vector = avg_vector

    return normalized_avg_vector.tolist()<p>또한 내적 유사도를 사용할 수 있도록 벡터를 정규화하고 있습니다.</p><p>모든 ColPali 벡터를 평균 벡터로 변환한 후, 이를 dense_vector 필드에 인덱싱할 수 있습니다.</p>mappings = {
    "mappings": {
        "properties": {
            "avg_vector": {
                "type": "dense_vector",
                "dims": 128,
                "index": True,
                "similarity": "dot_product"
            },
            "col_pali_vectors": {
                "type": "rank_vectors",
                "element_type": "bit"
            }
        }
    }
}

es.indices.create(index=INDEX_NAME, body=mappings)<p>이로 인해 지연 상호작용 벡터와 함께 더 많은 정보를 저장하게 되므로 전체 디스크 사용량이 증가할 것이라는 점을 고려해야 합니다. 추가로, 수십억 개의 벡터에 대한 검색을 확장할 수 있도록 HNSW 그래프를 보유하기 위해 추가 RAM을 사용할 것입니다. RAM 사용량을 줄이기 위해 우리는 인기 있는 <a href="https://www.elastic.co/search-labs/blog/optimized-scalar-quantization-elasticsearch">BBQ 기능</a>을 활용할 수 있습니다. 그 결과, 기존 방식으로는 불가능했던 대규모 데이터 세트에 대한 빠른 검색 결과를 얻을 수 있습니다.</p><p>이제 knn 쿼리로 검색하면 가장 관련성 높은 문서를 찾을 수 있습니다.</p>query = "What do companies use for recruiting?"
query_vector = to_avg_vector(create_col_pali_query_vectors(query))
es_query = {
    "_source": False,
    "knn": {
        "field": "avg_vector",
        "query_vector": query_vector,
        "k": 10,
        "num_candidates": 100
    },
    "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf4ac6b7fd444f935/6a17f471a29299d76ad02dc0/a500f9907020f032c3b1c7a48ed8c759dc2a1dd4-1600x498.png" alt="" /><p>불행히도 이전의 베스트 매칭은 3위로 떨어졌습니다.</p><p>이 문제를 해결하기 위해 다단계 검색을 수행할 수 있습니다. 첫 번째 단계에서는 knn 쿼리를 사용하여 수백만 개의 문서에서 쿼리에 가장 적합한 후보를 검색하고 있습니다. 두 번째 단계에서는 ColPali 지연 상호작용 벡터의 충실도가 높은 상위 k(여기서는 10)만 다시 순위를 매깁니다. </p>query = "What do companies use for recruiting?"
col_pali_vector = create_col_pali_query_vectors(query)
avg_vector = to_avg_vector(col_pali_vector)
es_query = {
  "_source": False,
  "retriever": {
    "rescorer": {
      "retriever": {
        "knn": {
          "field": "avg_vector",
          "query_vector": avg_vector,
          "k": 10,
          "num_candidates": 100
        }
      },
      "rescore": {
        "window_size": 10,
        "query": {
          "rescore_query": {
            "script_score": {
              "query": {
                "match_all": {}
              },
              "script": {
                "source": "maxSimDotProduct(params.query_vector, 'col_pali_vectors')",
                "params": {
                  "query_vector": col_pali_vector
                }
              }
            }
          }
        }
      }
    }
  },
  "size": 5
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt265edd5f37be40b7/6a17f4733e9e45583dba15e6/797f490860af87fcf29f34668a5c4511419c6fd0-1600x501.png" alt="지연 상호작용 모델 최적화를 위해 평균 벡터를 사용한 결과" /><p>여기서는 8.18에서 도입된 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.18/retriever.html#rescorer-retriever">재점수 검색기</a>를 사용하여 결과를 다시 순위 지정합니다. 점수를 재지정한 후 다시 가장 좋은 매치가 첫 번째 위치에 있음을 확인합니다. </p><p>참고: 프로덕션 애플리케이션에서는 최대 시뮬레이션 함수가 여전히 비교적 성능이 좋으므로 10보다 훨씬 높은 k를 사용할 수 있습니다.</p><h3>토큰 풀링</h3><p>토큰 풀링은 중복 정보(예: 흰색 배경 패치)를 풀링하여 멀티 벡터 임베딩의 시퀀스 길이를 줄입니다. 이 기법은 페이지의 신호 대부분을 보존하면서 임베딩 수를 줄입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d643e6ac908f640/6a17f4754b055d3783432352/09eae0b768f4b450e555d7e53e57561bd52de2c0-1412x1056.png" alt="지연 상호작용 모델 최적화를 위한 토큰 풀링" /><p>토큰 풀링은 클러스터링 알고리즘을 사용하여 문서 내에서 유사한 토큰 임베딩을 클러스터로 그룹화하는 방식으로 작동합니다. 그런 다음, 각 클러스터 내 벡터의 평균을 계산하여 단일한 집계 표현을 생성합니다. 이 집계된 벡터는 그룹 내의 원래 토큰을 대체하여 문서 신호의 상당한 손실 없이 총 벡터 수를 줄입니다.</p><p>ColPali 논문에서는 대부분의 데이터 세트에 대해 초기 풀 팩터 값을 3으로 제안하고 있는데, 이는 원래 성능의 97.8%를 유지하면서 총 벡터 수를 66.7%로 줄입니다. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d78a15c6944a1ea/6a17f477414c64a2929452d9/343cc9eaf54af8c7a125d4115838f3c7d5659de0-1600x1007.png" alt="지연 상호작용 모델 최적화를 위한 풀 요소" /><p>그러나 주의해야 합니다. 매우 밀도가 높고 텍스트가 많으며 공백이 거의 없는 문서를 포함하는 "Shift" 데이터 세트는 풀 요소가 증가함에 따라 성능이 급격히 저하됩니다.</p><p>풀링된 벡터를 생성하려면 colpali_engine 라이브러리를 사용할 수 있습니다.</p>from colpali_engine.compression.token_pooling import HierarchicalTokenPooler

pooler = HierarchicalTokenPooler(pool_factor=3) # test on your data for a good pool_factor

def pool_vectors(embedding: list) -&gt; list:
    tensor = torch.tensor(embedding).unsqueeze(0)
    pooled = pooler.pool_embeddings(tensor)
    return pooled.squeeze(0).tolist()<p>이제 차원이 약 66.7% 감소한 벡터를 가지고 있습니다. 평소처럼 인덱싱하고 <code>maxSimDotProduct()</code> 함수로 검색할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc460c14031814ede/6a17f4794202292bf629f72d/2412c5db7d79a590b96d42fe01f140c42f010612-1600x481.png" alt="지연 상호작용 모델 결과" /><p>검색 결과의 정확도가 약간 떨어지는 대신, 우수한 검색 결과를 얻을 수 있습니다.</p><p>힌트: pool_factor 값을 높이면(100~200) 평균 벡터 솔루션과 여기서 논의한 솔루션 사이의 중간 지점을 찾을 수도 있습니다. 문서당 약 5~10개의 벡터가 있을 때, 중첩된 필드에 인덱스를 만들어 HNSW 인덱스를 활용하는 것이 현실적이 됩니다.</p><h2>코스 인코더 vs. 지연 상호작용 vs. 바이 인코더</h2><p>지금까지 배운 바에 따르면, ColPali 또는 ColBERT와 같은 지연 상호작용 모델은 다른 AI 검색 기술과 비교할 때 어떤 위치에 있을까요?</p><p>최대 시뮬레이션 함수는 크로스 인코더에 비해 저렴하지만, 쿼리-문서 쌍마다 두 벡터를 비교하는 바이 인코더를 사용한 벡터 검색보다 여전히 더 많은 비교와 계산이 필요합니다. </p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfd97f3bba3e5b17/6a17f47be31791b0052d5943/75e1fc9e601aa7a6e88f137565c1919166c7a71c-1480x458.png" alt="코스 인코더 vs. 지연 상호작용 모델 vs. 바이 인코더" /><p>이러한 이유로, 지연 상호작용 모델은 일반적으로 상위 k 검색 결과의 순위를 재조정하는 데에만 사용하는 것을 권장합니다. 또한 이를 필드 유형 이름인 rank_vectors로 캡처합니다.</p><p>그렇다면 크로스 인코더는 어떨까요? 쿼리 시점에 실행 비용이 저렴하기 때문에 지연 상호작용 모델이 더 나은 것일까요? 종종 그렇듯이 답은 '상황에 따라 다르다'입니다. 크로스 인코더는 일반적으로 더 높은 품질의 결과를 생성하지만, 쿼리 문서 쌍이 트랜스포머 모델을 통해 완전한 패스를 수행해야 하기 때문에 많은 계산 자원이 필요합니다. 또한 벡터 인덱싱이 필요하지 않고 상태 비저장 방식으로 작동할 수 있다는 장점도 있습니다. 그 결과는 다음과 같습니다.</p><ul><li><p>디스크 공간 사용량 감소</p></li><li><p>더욱 간소화된 시스템</p></li><li><p>더 높은 검색 결과 품질</p></li><li><p>더 높은 대기 시간으로 인해 순위 재지정을 깊이 수행할 수 없음</p></li></ul><p>반면, 지연 상호작용 모델은 이 계산 일부를 인덱싱할 때 분산시켜 쿼리 비용을 낮출 수 있습니다. 그 대가로 벡터를 인덱싱해야 하므로 인덱스 파이프라인이 더 복잡해지고 이러한 벡터를 저장하는 데 더 많은 디스크 공간이 필요합니다.</p><p>특히 ColPali의 경우, 이미지에서 정보를 분석하는 것은 많은 데이터를 포함하기 때문에 매우 비용이 많이 듭니다. 이 경우 쿼리 시점에 이 정보를 평가하는 것은 너무 리소스 집약적이고 느리기 때문에 ColPali와 같은 후기 상호 작용 모델을 사용하는 것이 유리합니다. </p><p>ColBERT와 같은 지연 상호작용 모델의 경우, 대부분의 크로스 인코더(예: elastic-rerank-v1)처럼 텍스트 데이터를 기반으로 작동하기 때문에 디스크 공간 절약 및 간편성 측면에서 크로스 인코더를 사용하는 것이 더 유리할 수 있습니다.</p><p>사용 사례에 대한 장단점을 비교하고 Elasticsearch가 제공하는 다양한 도구를 실험하여 최고의 검색 애플리케이션을 구축하는 것이 좋습니다.</p><h2>결론</h2><p>이 블로그에서는 Elasticsearch에서 대규모 벡터 검색을 위한 ColPali와 같은 지연 상호작용 모델을 최적화하는 다양한 기술을 탐구했습니다. 지연 상호작용 모델은 검색 효율성과 순위 품질 사이에서 뛰어난 균형을 제공하지만, 저장 공간 및 계산과 관련된 문제점도 야기합니다.</p><p>이러한 과제를 해결하기 위해 다음을 살펴보았습니다.</p><ul><li><p><strong>비트 벡터</strong>를 사용하여 해밍 거리 또는 비대칭 최대 유사도와 같은 효율적인 유사도 계산을 활용하면서 디스크 공간을 크게 줄일 수 있습니다.</p></li><li><p>여러 임베딩을 하나의 밀집 표현으로 압축하기 위해 <strong>평균 벡터</strong>를 사용하여 HNSW 인덱싱을 통한 효율적인 검색을 가능하게 합니다.</p></li><li><p><strong>토큰 풀링</strong>을 통해 의미적 무결성을 유지관리하면서 중복된 임베딩을 지능적으로 병합하여 쿼리 시 계산 오버헤드를 줄입니다.</p></li></ul><p>Elasticsearch는 사용자의 요구에 따라 검색 애플리케이션을 맞춤 설정하고 최적화할 수 있는 강력한 툴킷을 제공합니다. 검색 속도, 순위 품질 또는 저장 공간 효율성 중 무엇을 우선시하든, 이러한 도구와 기술을 사용하면 실제 응용 분야에 필요한 성능과 품질의 균형을 맞출 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/late-interaction-model-colpali-scale</guid>
    <category><![CDATA[정확도]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Peter Straßer,Benjamin Trent]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt97a536033e6b0a56/6a17f47dfbc5f88c86491c0d/c780b78a07573f2df1cfef8b29a7109f839b0ab3-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 20 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[NVIDIA와 함께 Elasticsearch에서 GPU 가속 벡터 검색 살펴보기: 1장]]></title>
    <description><![CDATA[NVIDIA cuVS를 기반으로 하는 이 협력은 개발자들에게 Elasticsearch의 벡터 검색을 위한 GPU 가속을 제공하기 위한 것입니다.]]></description>
    <content:encoded><![CDATA[<p>Elastic 엔지니어링 조직에서는 한동안 벡터 데이터베이스 성능을 최적화하느라 바빴습니다. 우리의 사명: 최고의 벡터 데이터베이스를 만드는 것. 루씬과 엘라스틱서치를 최고의 벡터 데이터베이스로 만드는 것입니다. 하드웨어 가속 <a href="https://www.elastic.co/kr/blog/accelerating-vector-search-simd-instructions">CPU SIMD 명령어를</a> 통해 새로운 벡터 데이터 압축 혁신<a href="https://www.elastic.co/kr/search-labs/blog/better-binary-quantization-lucene-elasticsearch">(Better Binary Quantization, 일명 BBQ)</a>을 도입하고, 더 많은 이점을 위해 BBQ에 대한 알고리즘 접근 방식을 업데이트하여 기대치를 뛰어넘고 <a href="https://www.elastic.co/kr/search-labs/blog/filtered-hnsw-knn-search">필터링된 HNSW를 더욱 빠르게 만들었습니다</a>. 요점은 더 빠르고, 더 좋고, 더 효율적인(?) 환경을 구축한다는 것입니다. 벡터 데이터베이스를 통해 개발자들이 헝겊처럼 지저분한 문제를 해결할 수 있습니다!</p><p>효율성을 뒤처지지 않겠다는 사명의 일환으로, 여러분도 들어보셨을지도 모르는 이 신기한 컴퓨터 칩, 즉 엔비디아 GPU로 가속 기회를 모색하고 있습니다! (정말 안 들어보셨나요?).</p><p>성능에 집착할 때, 기하급수적으로 많은 데이터를 색인하는 방법, 데이터에서 인사이트를 검색하는 방법, ML 모델이 관련된 경우 이를 수행하는 방법 등 여러 가지 문제를 탐구해야 합니다. GPU가 있을 때 가능한 모든 혜택을 누릴 수 있어야 합니다.</p><p>이 포스팅에서는 NVIDIA 벡터 검색 팀과의 협업을 통해 Elasticsearch에서 GPU 가속 벡터 검색을 살펴봅니다. 이 작업은 개발자가 실제 Elasticsearch 기반 앱에 GPU와 CPU를 혼합하여 사용할 수 있는 사용 사례의 길을 열어줍니다. 신나는 시간!</p><h2>Elasticsearch GPU</h2><p>Elasticsearch 엔지니어링 팀이 벡터 검색 알고리즘을 위한 바인딩을 노출하는 개발자를 위한 오픈 소스 cuVS Java API 환경을 구축하는 데 도움을 주고 있다는 소식을 알려드리게 되어 기쁩니다. 이 작업은 파나마 FFI에 대한 이전 경험을 활용합니다. Elasticsearch와 Apache Lucene은 인덱싱 중에 NVIDIA cuVS API를 사용해 그래프를 구축합니다. 자, 너무 앞서 나갔으니 잠시 뒤로 돌아가 보겠습니다.</p><p>이 협업의 중심에는 오픈 소스 C++ 라이브러리인 <a href="https://developer.nvidia.com/cuvs">NVIDIA cuVS가</a> 있습니다. 더 높은 처리량, 더 낮은 지연 시간, 더 빠른 인덱스 구축 시간을 제공함으로써 벡터 검색에 GPU 가속을 도입하는 것을 목표로 합니다. 하지만 Elasticsearch와 Apache Lucene은 Java로 작성되어 있는데 어떻게 작동할까요?</p><p><a href="https://github.com/SearchScale/lucene-cuvs">lucene-cuvs와</a> Elastic-NVIDIA-SearchScale 협업을 통해 이를 Lucene 에코시스템에 도입하여 Elasticsearch에서 GPU 가속 벡터 검색을 탐색하세요. 최근 NVIDIA cuVS 25.02 릴리스에서는 cuVS용 Java API를 추가했습니다. 새로운 API는 실험 중이며 계속 발전해 나갈 예정이지만 현재 사용 가능합니다. Java에서 네이티브 함수 호출은 느리지 않나요? 더 이상은 아닙니다! 바인딩에는 새로운 <a href="https://openjdk.org/projects/panama/">파나마 FFI</a> (외부 함수 인터페이스)를 사용하고 있으며, 이는 Java에서 네이티브 다운콜에 대한 오버헤드를 최소화합니다.</p><p>저희는 한동안 <a href="https://www.elastic.co/kr/search-labs/blog/lucene-and-java-moving-forward-together">Elasticsearch와 Lucene에서 Panama FFI를</a> 사용해 왔습니다. 멋지네요! 하지만... 항상 "하지만"이 붙는 법이죠? FFI는 Java 버전에 따라 가용성 문제가 있습니다. 저희는 cuVS API를 Java 21로 컴파일하고 Java 22를 대상으로 하는 다중 릴리스 컨테이너 내에 구현을 캡슐화하여 이 문제를 극복했습니다. 이를 통해 Lucene과 Elasticsearch에서 직접 cuVS Java를 사용할 수 있습니다.</p><p>이제 cuVS Java API가 생겼으니 또 무엇이 필요할까요?</p><h2>CPU를 위한 두 가지 알고리즘 이야기</h2><p>Elasticsearch는 확장 가능한 근사 KNN 검색을 위해 <a href="https://arxiv.org/abs/1603.09320">HNSW 알고리즘을</a> 지원합니다. 그러나 GPU를 최대한 활용하기 위해 유니티는 GPU가 제공하는 높은 수준의 병렬 처리를 위해 특별히 설계된 다른 알고리즘인<a href="https://arxiv.org/pdf/2308.15136">CAGRA[</a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"><em>CUDA</em></a> <a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"><em>ANN</em></a> <a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136"></a><a href="https://arxiv.org/pdf/2308.15136">GRAph] 를 사용합니다.</a></p><p>CAGRA에 대한 지원을 추가하는 방법을 살펴보기 전에, Elasticsearch와 Lucene이 "코덱 형식"을 통해 인덱스 데이터에 액세스하는 방법을 살펴보겠습니다. 이는 다음과 같이 구성됩니다.</p><ol><li><p>온디스크 표현입니다,</p></li><li><p>데이터 읽기 및 쓰기를 위한 인터페이스입니다,</p></li><li><p>그리고 Lucene의 세그먼트 기반 아키텍처를 처리하기 위한 기계입니다.</p></li></ol><p>내부적으로 cuVS Java API를 사용하여 GPU에서 인덱싱하고 검색하는 새로운 KNN(k-근접 이웃) <a href="https://lucene.apache.org/core/10_1_0/core/org/apache/lucene/codecs/KnnVectorsFormat.html">벡터 형식을</a> 구현하고 있습니다. 여기에서 이 코덱 유형을 인덱스의 필드 유형에 대한 Elasticsearch의 매핑을 통해 '수직'으로 연결합니다. 따라서 기존 KNN 쿼리는 지원 인덱스가 CAGRA 그래프를 사용하든 HNSW 그래프를 사용하든 관계없이 계속 작동합니다. 물론 여기에는 많은 세부 사항이 포함되어 있으며, 향후 블로그에서 다룰 예정입니다. 다음은 GPU 가속 Elasticsearch의 하이레벨 아키텍처입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb6197b631a34f8b6/6a170b0da6c2b9e60ce7970b/be6b7356c03df4dee7230625c2c9af3b019f93be-756x510.png" alt="" /><p>이 새로운 코덱 형식의 기본값은 CAGRA입니다. 그러나 CPU에서 검색할 수 있도록 CAGRA 그래프를 HNSW 그래프로 변환하는 기능도 지원합니다.</p><h2>GPU에서 색인 및 검색: 몇 가지 "핵심" 결정 내리기</h2><p>인덱싱과 검색을 분리하는 Elasticsearch 서버리스의 상태 저장소 없는 <a href="https://www.elastic.co/kr/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">아키텍처를</a> 통해 이제 책임 소재가 명확하게 구분됩니다. 이러한 각각의 독립적인 책임을 수행할 수 있는 최고의 하드웨어 프로필을 선택합니다.</p><p>사용자들은 크게 두 가지 배포 전략을 고려할 것으로 예상됩니다:</p><ol><li><p>GPU에서 색인 및 검색: 색인하는 동안 CAGRA 그래프를 작성하여 검색 중에 사용하면 지연 시간이 매우 짧은 검색이 필요한 경우에 이상적입니다.</p></li><li><p>GPU에서 색인하고 CPU에서 검색합니다: 인덱싱하는 동안 CAGRA 그래프를 작성하고 이를 HNSW 그래프로 변환합니다. HNSW 그래프는 인덱스에 저장되며, 나중에 CPU에서 검색에 사용할 수 있습니다.</p></li></ol><p>이러한 유연성은 다양한 배포 모델을 제공하여 비용과 성능 간의 절충점을 제공합니다. 예를 들어, 인덱싱 서비스에서는 검색을 위해 저전력 CPU를 사용하면서 그래프를 적시에 효율적으로 작성하고 병합하기 위해 GPU를 사용할 수 있습니다.</p><h2>따라서 Elasticsearch에서 GPU 가속 벡터 검색을 위한 계획은 다음과 같습니다.</h2><p>비용과 성능의 균형을 맞출 수 있는 다양한 옵션을 제공하여 사용자에게 배포 전략으로 성능 향상과 유연성을 제공할 수 있기를 기대합니다. 이 작업이 자세히 소개된 <a href="https://www.nvidia.com/gtc/session-catalog/?tab.catalogallsessionstab=16566177511100015Kus&amp;search=Lucene#/">NVIDIA GTC 2025 세션은</a> 다음과 같습니다.</p><p>환상적인 협업을 보여준 NVIDIA와 SearchScale의 엔지니어링 팀에 감사의 말씀을 전합니다. 다음 블로그에서는 구현 세부 사항과 성능 분석에 대해 더 자세히 살펴보겠습니다. 호기심 모자를 꽉 잡아주세요 🎩!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/gpu-accelerated-vector-search-elasticsearch-nvidia</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Chris Hegarty,Hemant Malik]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt298e839e708ca11c/6a170b0fb339d560c2769fc2/38bc0377a6adce7eae0099f61902fdbbe644eb4a-1440x960.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 19 Mar 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[벡터 검색에 대한 간단한 소개]]></title>
    <description><![CDATA[이 글은 시맨틱 검색이라고도 하는 벡터 검색의 복잡성과 Elasticsearch에서 어떻게 구현되는지에 대해 자세히 설명하는 3편의 시리즈 중 첫 번째 글입니다.]]></description>
    <content:encoded><![CDATA[<p>이 글은 시맨틱 검색이라고도 하는 벡터 검색의 복잡성과 Elasticsearch에서 어떻게 구현되는지에 대해 자세히 설명하는 3편의 시리즈 중 첫 번째 글입니다.</p><p>이 첫 번째 파트에서는 벡터 임베딩의 기본 사항과 벡터 검색의 내부 작동 방식에 대한 일반적인 소개에 중점을 둡니다.</p><p>첫 번째 글에서 배운 모든 지식으로 무장하고 <a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">두 번째</a> 글에서는 Elasticsearch에서 벡터 검색을 설정하는 방법에 대해 자세히 안내합니다.</p><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">세 번째 파트</a>에서는 앞선 두 파트에서 배운 내용을 활용하고, 그 지식을 바탕으로 Elasticsearch에서 강력한 하이브리드 검색 쿼리를 작성하는 방법에 대해 자세히 알아보겠습니다.</p><p>이 글의 본론으로 들어가기 전에 시간을 거슬러 올라가 시맨틱 검색의 핵심 개념인 벡터의 역사에 대해 살펴봅시다.</p><h2>벡터는 새로운 것이 아닙니다.</h2><p>2022년 11월 ChatGPT가 등장한 이후 "벡터 검색"에 대해 듣거나 읽지 않고는 단 하루도 지나가지 않는다는 사실에 모두가 동의할 것입니다. 어디에나 있고 너무 널리 퍼져 있어 이제 막 나온 새로운 첨단 기술이라는 인상을 받기도 하지만, 사실 이 기술은 60년 이상 전부터 사용되어 온 기술입니다! 이 주제에 대한 연구는 1960년대 중반에 시작되었으며, 1978년 코넬 대학교의 정보 검색 전문가인 제라드 살튼과 그의 동료들이 최초의 연구 논문을 발표했습니다. 고밀도 및 희소 벡터 모델에 대한 Salton의 연구는 현대 벡터 검색 기술의 근간을 이루고 있습니다.</p><p>지난 20년 동안 그의 연구를 기반으로 한 다양한 <a href="https://db-engines.com/en/ranking/vector+dbms/all">벡터 DBMS가</a> 개발되어 시장에 출시되었습니다. 여기에는 2019년에 <a href="https://issues.apache.org/jira/browse/LUCENE-9004">벡터 검색 작업을</a> 시작한 Apache Lucene 프로젝트 기반의 Elasticsearch가 포함됩니다.</p><p>벡터는 이제 어디에나 있고 널리 퍼져 있으므로 벡터를 활용하기 전에 먼저 벡터의 기본 이론과 내부 작동 원리를 잘 이해하는 것이 중요합니다. 자세히 알아보기 전에 어휘 검색과 벡터 검색의 차이점을 간단히 살펴봄으로써 두 검색이 어떻게 다르고 어떻게 서로를 보완할 수 있는지 더 잘 이해할 수 있도록 하겠습니다.</p><h2>벡터 검색과 어휘 검색 비교</h2><p>벡터 검색을 소개하는 쉬운 방법은 익숙한 기존의 어휘 검색과 비교하는 것입니다. 일반적으로 시맨틱 검색이라고도 하는 벡터 검색과 어휘 검색은 작동 방식이 매우 다릅니다. 어휘 검색은 우리 모두가 Elasticsearch에서 수년 동안 사용해온 검색입니다. 간단히 요약하자면, 색인되고 쿼리되는 내용의 실제 의미를 이해하려고 노력하는 것이 아니라, 사용자가 쿼리에 입력하는 단어의 리터럴 또는 그 변형(어간, 동의어 등)을 TF-IDF와 같은 유사성 알고리즘을 사용하여 이전에 데이터베이스에 색인된 모든 리터럴과 <strong>어휘적으로</strong> 일치시키기 위해 많은 노력을 기울이는 것입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbb8e150865a5ec8/6a17e0c725daab50f408a16e/a4f3eba19599e5330e9b0d29ecdbbeac211cdab0-1600x583.png" alt="어휘 검색 예제" /><p>보시다시피 왼쪽 상단에 있는 세 개의 문서는 토큰화되어 분석되고 있습니다. 그런 다음 결과 용어가 역 인덱스에 색인되어 분석된 용어를 해당 용어가 포함된 문서 ID에 간단히 매핑합니다. 모든 용어는 한 번만 표시되며 어떤 문서에서도 공유되지 않는다는 점에 유의하세요. "멋진 독일어 선생님"을 검색하면 세 문서 모두 다른 점수로 일치하지만, 그 중 어느 것도 쿼리의 진정한 의미를 파악하지 못합니다.</p><p>아래 그림 2에서 볼 수 있듯이 다의어 또는 동음이의어, 즉 철자는 같지만 <strong>다른 의미</strong> (오른쪽, 손바닥, 방망이, 평균 등)를 가진 단어를 다룰 때는 더욱 까다로워집니다. 세 가지 의미를 가질 수 있는 '오른쪽'이라는 단어를 예로 들어 어떤 일이 일어나는지 살펴봅시다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltea0cc829ff2c8029/6a17e0c92f4a5c8107fa8811/ebb9cc1be03475bbf68269a8dcea5f08bcda0b64-1594x754.png" alt="어휘 검색으로 동음이의어 검색하기" /><p><em>"나는 옳지 않다"를</em> 검색하면 첫 번째 반환된 결과와 정반대의 의미를 가진 문서가 반환됩니다. 완전히 동일한 용어를 검색하지만 순서를 달리하여 다른 의미를 생성하는 경우(예: <em>"우회전</em> "과 <em>"우회전</em> ") 완전히 동일한 결과가 표시됩니다(예: 세 번째 문서 "우회전"). 물론 쿼리가 지나치게 단순화되어 있고 구문 검색과 같은 고급 쿼리를 사용하지는 않지만, 이는 어휘 검색이 색인된 내용과 검색된 내용의 진정한 의미를 이해하지 못한다는 것을 보여주는 예시입니다. 명확하지 않더라도 걱정하지 마세요. 세 번째 글에서 이 예시를 다시 살펴보고 벡터 검색이 이 경우에 어떻게 도움이 되는지 알아보겠습니다.</p><p><strong>구조화된</strong> 데이터를 색인하는 방법(매핑, 텍스트 분석, 수집 파이프라인 등)과 쿼리를 작성하는 방법(영리하게 만들어진 DSL 쿼리, 쿼리 용어 분석 등)을 제어할 수 있다면 어휘 검색 엔진으로 놀라운 일을 할 수 있다는 것은 의심의 여지가 없습니다! 어휘 검색 기능에 관한 Elasticsearch의 실적은 정말 놀랍습니다. 지난 몇 년 동안 어휘 검색 분야를 대중화하고 개선한 성과는 정말 놀랍습니다.</p><p>그러나 자유 텍스트 질문을 해야 하는 사용자에게 <a href="https://www.elastic.co/what-is/unstructured-data"><strong>비정형</strong></a><a href="https://www.elastic.co/what-is/unstructured-data"> 데이터</a> (이미지, 동영상, 오디오, 원시 텍스트 등)에 대한 쿼리 지원을 제공해야 하는 경우 어휘 검색만으로는 부족합니다. 또한 곧 살펴보겠지만 쿼리가 텍스트가 아닌 이미지일 수도 있습니다. 이러한 상황에서 어휘 검색이 부적절한 주된 이유는 비정형 데이터는 정형 데이터와 동일한 방식으로 색인화하거나 쿼리할 수 없기 때문입니다. 비정형 데이터를 다룰 때는 <strong>의미론이</strong> 중요한 역할을 합니다. 시맨틱이란 무엇을 의미하나요? 아주 간단하게 말하자면, 의미입니다!</p><p>이미지 검색 엔진(예: Google 이미지 검색 또는 렌즈)의 간단한 예를 들어 보겠습니다. 이미지를 끌어다 놓으면 Google 시맨틱 검색 엔진이 쿼리한 이미지와 가장 유사한 이미지를 찾아서 반환합니다. 아래 그림 3에서 왼쪽에는 독일 셰퍼드 사진이 있고 오른쪽에는 검색된 모든 유사한 사진이 표시되며, 첫 번째 결과는 제공된 사진과 동일한 사진(즉, 가장 유사한 사진)입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3e0a0fb3c300537a/6a17e0cbe8fbce69d13a185d/c0dff24d4379141abd1038c9c4b5577fd2dca802-1600x657.png" alt="시맨틱 검색 예제: 사진 검색하기" /><p>사람에게는 간단하고 논리적으로 들리지만 컴퓨터에게는 완전히 다른 이야기입니다. 이것이 바로 벡터 검색이 가능하게 하고 달성하는 데 도움이 되는 것입니다. 최근 전 세계가 목격했듯이 벡터 검색의 잠재력은 엄청납니다. 이제 후드를 열고 그 아래에 무엇이 숨어 있는지 알아봅시다.</p><h2>벡터 임베딩</h2><p>앞서 살펴본 것처럼 어휘 검색 엔진을 사용하면 텍스트와 같은 구조화된 데이터를 용어의 실제 의미와 관계없이 검색 시 일치할 수 있는 용어로 쉽게 토큰화할 수 있습니다. 그러나 비정형 데이터는 이미지, 동영상, 오디오 등 다양한 형태로 존재할 수 있으며, 동일한 토큰화 프로세스에 전혀 적합하지 않습니다. 또한 시맨틱 검색의 전체 목적은 데이터가 나타내는 의미에 따라 검색할 수 있도록 데이터를 색인하는 것입니다. 이를 어떻게 달성할 수 있을까요? 그 답은 두 단어에 있습니다: 바로 <strong>머신 러닝입니다</strong>! 더 정확하게는 딥러닝!</p><p><strong>딥러닝은</strong> 데이터의 진정한 의미를 점진적으로 추출할 수 있는 여러 처리 계층으로 구성된 인공 신경망 기반 모델에 의존하는 머신러닝의 특정 영역입니다. 이러한 신경망 모델이 작동하는 방식은 인간의 뇌에서 많은 영감을 받았습니다. 아래 그림 4는 입력 및 출력 계층과 여러 개의 숨겨진 계층이 있는 신경망의 모습을 보여줍니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ca5fd8f0e61d939/6a17e0cd7f6f152a67c09a53/aeea3bab3c29e1c591a9c9082f2e73e7d3990ef1-1600x1116.png" alt="벡터 검색의 신경망 레이어" /><p>신경망의 진정한 위업은 하나의 비정형 데이터를 일련의 부동 소수점 값으로 변환할 수 있다는 점인데, 이를 임베딩 <strong>벡터</strong> 또는 간단히 <strong>임베딩이라고</strong> 합니다. 인간은 벡터를 2차원 또는 3차원 공간에서 시각화하면 벡터가 무엇인지 꽤 잘 이해할 수 있습니다. 벡터의 각 구성 요소는 2D x-y 평면 또는 3D x-y-z 공간의 좌표를 나타냅니다.</p><p>그러나 신경망 모델이 작동하는 임베딩 벡터는 수백 또는 수천 개의 차원을 가질 수 있으며 단순히 다차원 공간의 한 점을 나타낼 수 있습니다. 각 벡터 차원은 비정형 데이터의 <strong>특징</strong> 또는 특성을 나타냅니다. 이미지를 2048차원의 임베딩 벡터로 변환하는 딥러닝 모델을 통해 이를 설명해 보겠습니다. 이 모델은 그림 3에서 사용한 독일 셰퍼드 그림을 아래 표에 표시된 임베딩 벡터로 변환합니다. 여기서는 첫 번째와 마지막 세 요소만 표시하지만 테이블에는 2,042개의 열/차원이 더 있습니다.</p><p></p><p>is_red</p><p>is_dog</p><p>blue_sky</p><p>…</p><p>no_gras</p><p>게르만 셰퍼드</p><p>is_tree</p><p>독일 셰퍼드 임베딩</p><p>0.0121</p><p>0.9572</p><p>0.8735</p><p>…</p><p>0.1198</p><p>0.9712</p><p>0.0512</p><p>각 열은 모델의 차원이며 기본 신경망이 모델링하고자 하는 기능 또는 특성을 나타냅니다. 모델에 주어진 각 입력은 해당 입력이 2048개의 각 차원과 얼마나 유사한지에 따라 특성화됩니다. 따라서 임베딩 벡터의 각 요소 값은 해당 입력과 특정 차원의 <strong>유사성을</strong> 나타냅니다. 이 예시에서는 모델이 개와 독일 셰퍼드 사이의 높은 유사성과 푸른 하늘의 존재를 감지한 것을 볼 수 있습니다.</p><p>용어의 일치 여부만 알 수 있는 어휘 검색과 달리, 벡터 검색을 사용하면 비정형 데이터 조각이 모델이 지원하는 각 차원과 얼마나 <em>유사한지</em> 훨씬 더 잘 파악할 수 있습니다. 이처럼 임베딩 벡터는 비정형 데이터를 환상적으로 의미적으로 표현하는 역할을 합니다.</p><h2>비법 소스</h2><p>이제 비정형 데이터가 딥러닝 신경망에 의해 여러 차원에 걸쳐 데이터의 유사성을 포착하는 임베딩 벡터로 잘게 쪼개지는 방법을 알았으니, 이러한 벡터의 매칭이 어떻게 작동하는지 이해해야 합니다. 그 답은 매우 간단합니다. 서로 <strong>가까운</strong> 벡터를 임베딩하면 <strong>의미적으로 유사한</strong> 데이터 조각을 나타냅니다. 따라서 벡터 데이터베이스를 쿼리할 때 먼저 모든 비정형 데이터 색인에 사용된 것과 동일한 모델을 사용하여 검색 입력(이미지, 텍스트 등)을 임베딩 벡터로 변환하고, 최종 목표는 해당 쿼리 벡터와 <strong>가장 가까운 이웃 벡터를</strong> 찾는 것입니다. 따라서 쿼리 벡터와 데이터베이스에 색인된 모든 기존 벡터 사이의 '거리' 또는 '유사성'을 측정하는 방법만 알아내면 됩니다.</p><h3>거리 및 유사성</h3><p>다행히도 두 벡터 사이의 거리를 측정하는 것은 벡터 산술 덕분에 쉽게 해결할 수 있는 문제입니다. 이제 Elasticsearch와 같은 최신 벡터 검색 데이터베이스에서 지원하는 가장 널리 사용되는 거리 및 유사도 함수에 대해 살펴보겠습니다. 경고, 수학이 앞서갑니다!</p><h4>L1 거리</h4><p>맨해튼 거리라고도 하는 두 벡터 x와 y의 L1 거리는 모든 요소의 쌍별 절대 차이를 합산하여 측정합니다. 당연히 거리 d가 작을수록 두 벡터는 더 가까워집니다. 아래에서 볼 수 있듯이 공식은 매우 간단합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2df2e9bc20883491/6a17e0ce033c8d006a6bb0bf/2b17bcedfbedde61117a3e7970af55bc62318c6c-312x102.png" alt="벡터 검색의 L1 거리 공식" /><p>시각적으로 L1 거리는 아래 그림 5와 같이 설명할 수 있습니다:</p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb90a33e6b1cbe00b/6a17e0d0414c64eb76945096/52075441892151536ed216081817a8e852566daa-474x464.png" alt="두 벡터 사이의 L1 거리 시각화하기" /><p>x = (1, 2), y = (4, 3)과 같은 두 벡터 x와 y를 예로 들면 두 벡터의 L1 거리는 | 1 - 4 | + | 2 - 3 | = 4가 됩니다.</p><h4>L2 거리</h4><p>유클리드 거리라고도 하는 두 벡터 x와 y의 L2 거리는 먼저 모든 요소의 쌍별 차이의 제곱을 합한 다음 그 결과의 제곱근을 구하여 측정합니다. 기본적으로 두 점 사이의 최단 경로(빗변이라고도 함)입니다. L1과 마찬가지로, 거리 d가 작을수록 두 벡터는 더 가까워집니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltae9b63fb593ae225/6a17e0d1af47b68379cddea8/b7675aa41f4f381e954c21dacd23a51a2dde6780-384x112.png" alt="벡터 검색의 L2 거리" /><p>L2 거리는 아래 그림 6에 나와 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32d913b46e07e79d/6a17e0d27f6f1582f7c09a57/bac99d08d6cf8a3a387a8acdc2e8f67357dad235-448x456.png" alt="두 벡터 사이의 L2 거리 시각화하기" /><p>L1 거리에서 사용한 것과 동일한 두 개의 샘플 벡터 x와 y를 재사용하면 이제 L2 거리를  계산할 수 있습니다. 10의 제곱근을 구하면 3.16이 됩니다.</p><p></p><h4>린프 거리</h4><p>체비셰프 또는 체스판 거리라고도 하는 두 벡터 x와 y의 Linf(무한대의 경우) 거리는 간단히 두 요소 사이의 최장 거리 또는 축/차원 중 하나를 따라 측정한 최장 거리로 정의됩니다. 공식은 매우 간단하며 아래와 같습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863411e15de16fa3/6a17e0d4505ac30939ad8a48/179ea6c6520a59acd97638313a2fb1b105e2ffed-436x82.png" alt="벡터 검색의 린프 거리 공식" /><p>린프 거리의 표현은 아래 그림 7에 나와 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt863ee7b51fd5fc15/6a17e0d56864a4772db686af/b75540d793cdc0645244d77dc36da9d5734ccbac-548x560.png" alt="두 벡터 사이의 선형 거리" /><p>다시, 동일한 두 개의 샘플 벡터 x와 y를 사용하여 최대 ( | 1 - 4 | , | 2 - 3 | ) = 최대 (3, 1) = 3으로 L 무한대 거리를 계산할 수 있습니다.</p><h4>코사인 유사도</h4><p>L1, L2, Linf와 달리 코사인 유사도는 두 벡터 x와 y 사이의 거리를 측정하는 것이 아니라 상대 각도, 즉 둘이 거의 같은 방향을 가리키고 있는지 여부를 측정합니다. 유사도 s가 높을수록 두 벡터가 "더 가깝다"는 의미입니다. 공식은 다시 매우 간단하며 아래와 같습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61d55341a6457ab7/6a17e0d7e9ea872b4ea9c4e2/931b6b90f0ee83e63a66496d06d6b4cef3affddd-304x72.png" alt="벡터 검색의 코사인 유사도 공식" /><p>두 벡터 간의 코사인 유사성을 표현하는 방법은 아래 그림 8에 나와 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50eb6399510b5380/6a17e0d83e9e45302cba139a/52092e8b5d366178e50a35f8c954ee8eaca76965-582x586.png" alt="두 벡터 간의 코사인 유사도" /><p>또한 코사인 값은 항상 [-1, 1] 간격에 있으므로 아래 그림 9와 같이 -1은 반대 유사성(즉, 두 벡터 사이의 180° 각도), 0은 무관한 유사성(즉, 90° 각도), 1은 동일성(즉, 0° 각도)을 의미합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e195c75a5089e/6a17e0da1d1b8355bc93e398/fd2690a845b89b69ff202bd04192893638538aec-1600x456.png" alt="벡터 검색의 코사인 유사도 스펙트럼" /><p>
다시 한 번 동일한 샘플 벡터 x와 y를 재사용하고 위의 공식을 사용하여 코사인 유사도를 계산해 보겠습니다. 먼저 두 벡터의 내적을  계산할 수 있습니다. 그런 다음 두 벡터의 길이(크기라고도 함)를 곱합니다:  + (4^2 + 3^2)^{1/2} = 11.18034. 마지막으로 도트 곱을 곱한 길이 10 / 11.18034 = 0.894427(즉, 26° 각도)로 나누면 1에 매우 가깝기 때문에 두 벡터는 매우 유사하다고 볼 수 있습니다.</p><h4>도트 제품 유사성</h4><p>코사인 유사도의 한 가지 단점은 두 벡터 사이의 각도만 고려하고 크기(즉, 길이)는 고려하지 않기 때문에 두 벡터가 대략 같은 방향을 가리키지만 한쪽이 다른 쪽보다 훨씬 길어도 둘 다 비슷한 것으로 간주된다는 점입니다. 스칼라 또는 내적 곱이라고도 하는 도트 곱 유사성은 벡터의 각도와 크기를 모두 고려하여 훨씬 더 정확한 유사성 지표를 제공함으로써 이를 개선합니다.</p><p>도트 제품 유사성을 계산하는 데는 두 가지 동등한 공식이 사용됩니다. 첫 번째는 앞서 코사인 유사도의 분자에서 살펴본 것과 동일합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f1c3369f8628457/6a17e0db1d1b832a1893e39c/52e2723926ab27cd96b688e805fae15f607073c8-482x104.png" alt="벡터 검색의 도트 곱 유사도 공식" /><p>두 번째 공식은 두 벡터의 길이에 두 벡터 사이의 각도의 코사인을 곱하기만 하면 됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt136dd2a749c2398a/6a17e0dc6df73108a80a0e1f/dc9b11fc67dd748d9f1f29b735f4726138cb7d39-452x70.png" alt="벡터 검색에서 단순화된 도트 곱 유사도 공식" /><p>도트 제품 유사성은 아래 그림 10에 시각화되어 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2c095e1c1948752/6a17e0dd6df731e30b0a0e23/1bda38cf82a1e1037f44b6e9657602c9efe1c0a6-558x574.png" alt="두 벡터 간의 도트 곱 유사성" /><p>마지막으로 샘플 x 및 y 벡터를 가져와 앞서 코사인 유사도 때와 마찬가지로 첫 번째 공식을 사용하여 (1 ) + (2 ) = 10으로 도트 곱 유사도를 계산합니다.</p><p>두 번째 공식을 사용하여 두 벡터의 길이를 곱합니다:  + (4^2 + 3^2)^{1/2} = 11.18034에 두 벡터 사이의 26° 각도의 코사인을 곱하면 11.18034 (26°) = 10을 구할 수 있습니다.</p><p>한 가지 주목할 점은 모든 벡터가 먼저 <strong>정규화되면</strong> (즉, 길이가 1이면) 도트 곱 유사도는 코사인 유사도(|x| |y| = 1이므로), 즉 두 벡터 사이의 각도의 코사인과 정확히 동일해진다는 점입니다. 나중에 살펴보겠지만, 벡터를 정규화하는 것은 벡터의 크기를 무의미하게 만들어 유사성이 단순히 각도에 초점을 맞추기 위해 채택하는 좋은 관행입니다. 또한 수십억 개의 벡터를 처리할 때 큰 문제가 될 수 있는 인덱싱 및 쿼리 시 거리 계산의 속도를 높여줍니다.</p><h3>빠른 요약</h3><p>지금까지 많은 정보를 살펴보았으니 잠시 멈춰서 현재 상황을 간단히 정리해 보겠습니다. 우리는 배웠습니다...</p><ul><li><p>...시맨틱 검색은 비정형 데이터를 다차원 임베딩 벡터로 변환하는 데 탁월한 딥러닝 신경망 모델을 기반으로 합니다.</p></li><li><p>...모델의 각 차원은 비정형 데이터의 기능 또는 특성을 나타냅니다.</p></li><li><p>...임베딩 벡터는 주어진 비정형 데이터 조각이 각 차원과 얼마나 유사한지를 나타내는 유사도 값의 시퀀스(각 차원에 대해 하나씩)입니다.</p></li><li><p>... 두 벡터가 "더 가깝게"(즉, 가장 가까운 이웃) 있을수록 의미적으로 유사한 개념을 더 많이 나타냅니다.</p></li><li><p>...거리 함수(L1, L2, Linf)를 사용하면 두 벡터가 얼마나 가까운지 측정할 수 있습니다.</p></li><li><p>...유사도 함수(코사인과 도트 곱)를 사용하면 두 벡터가 얼마나 같은 방향으로 향하고 있는지 측정할 수 있습니다.</p></li></ul><p></p><p>이제 마지막으로 살펴봐야 할 부분은 벡터 검색 엔진 자체입니다. 쿼리가 들어오면 먼저 쿼리를 벡터화한 다음 벡터 검색 엔진이 해당 쿼리 벡터에 가장 가까운 이웃 벡터를 찾습니다. 쿼리 벡터와 데이터베이스의 모든 벡터 사이의 거리 또는 유사성을 측정하는 무차별 대입 방식은 작은 데이터 세트에서는 효과가 있지만 벡터의 수가 증가하면 금방 부족해집니다. 다르게 말하면 수백만, 수십억, 심지어 수조 개의 벡터를 색인하고 합리적인 시간 내에 쿼리 벡터의 가장 가까운 이웃을 찾을 수 있는 방법은 무엇일까요? 따라서 정밀도를 크게 떨어뜨리지 않으면서 최대한 빠르게 가장 가까운 이웃을 찾아낼 수 있도록 최적의 벡터 인덱싱 방법을 찾아내야 합니다.</p><h3>벡터 검색 알고리즘 및 기법</h3><p>수년 동안 많은 연구팀이 매우 영리한 벡터 검색 알고리즘을 개발하기 위해 많은 노력을 기울여 왔습니다. 여기에서는 주요 기능에 대해 간략하게 소개하겠습니다. 사용 사례에 따라 어떤 것이 다른 것보다 더 적합한 경우도 있습니다.</p><h4>선형 검색</h4><p>앞서 데이터베이스에 존재하는 모든 벡터와 쿼리 벡터를 비교하는 무차별 대입 방식을 언급하면서 선형 검색 또는 플랫 인덱싱에 대해 간략하게 살펴봤습니다. 작은 데이터 세트에서는 잘 작동할 수 있지만, 벡터와 차원 수가 증가하면 성능이 급격히 저하됩니다(복잡도 O(n))).</p><p>다행히도 임베딩 벡터 사이의 거리를 미리 계산하고 클러스터, 트리, 해시 또는 그래프를 사용하여 유사한 벡터를 서로 가깝게 유지하는 방식으로 저장하고 구성하는 <strong>근사 최인접 이웃</strong> (ANN)이라는 보다 효율적인 접근 방식이 있습니다. 이러한 접근 방식은 일반적으로 100%% 정확도를 보장하지 않기 때문에 '근사치'라고 합니다. 궁극적인 목표는 유사한 벡터를 포함할 가능성이 가장 높은 영역에만 집중하기 위해 <strong>검색 범위를</strong> 최대한 빨리, 그리고 최대한 많이 줄이거나 <strong>벡터의 차원을 줄이는</strong> 것입니다.</p><h4>K-차원 트리</h4><p>K 차원 트리 또는 KD 트리는 이진 검색 트리를 일반화한 것으로, K 차원 공간에 포인트를 저장하고 벡터가 색인되는 작은 왼쪽과 오른쪽 트리로 검색 공간을 계속 이등분하여 작동합니다. 검색 시 알고리즘은 가장 가까운 이웃(그림 11의 녹색 점)을 찾기 위해 쿼리 벡터(그림 11의 빨간색 점) 주변의 트리 가지 몇 개를 방문하기만 하면 됩니다. k개 이상의 이웃이 요청되면 알고리즘이 더 많은 이웃을 찾을 때까지 노란색 영역이 확장됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e01707cd9fca57/6a17e0dfbe60866a90004653/e21af2d613112279089b6fa2166359233b10019d-829x860.png" alt="벡터 검색의 KD 트리 알고리즘" /><p>KD 트리 알고리즘의 가장 큰 장점은 일부 국소화된 트리 가지에만 빠르게 집중할 수 있어 대부분의 벡터를 고려 대상에서 제외할 수 있다는 점입니다. 그러나 이 알고리즘은 차원 수가 증가할수록 저차원 공간보다 더 많은 브랜치를 방문해야 하므로 효율성이 떨어집니다.</p><h4>반전된 파일 색인</h4><p>거꾸로 파일 인덱스(IVF) 방식은 서로 가까운 벡터를 공유 중심점에 할당하는 <strong>공간 분할</strong> 알고리즘이기도 합니다. 2D 공간에서는 그림 12와 같이 보로노이 다이어그램으로 이를 가장 잘 시각화할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b7e4fe6deff5ef3/6a17e0e17b54f940eb8b381c/33ddaaa818ab2f2a5fc87982c82c2a34cb849e33-640x640.png" alt="2D 공간에서 반전된 파일 인덱스의 보로노이 표현 " /><p>위의 2D 공간은 20개의 클러스터로 분할되어 있으며, 각 클러스터의 중심은 검은색 점으로 표시되어 있음을 알 수 있습니다. 공간의 모든 임베딩 벡터는 중심이 가장 가까운 클러스터에 할당됩니다. 검색 시 알고리즘은 먼저 쿼리 벡터에 가장 가까운 중심을 찾아 집중해야 할 클러스터를 파악한 다음, 해당 영역과 필요한 경우 주변 영역까지 제로화하여 가장 가까운 이웃을 찾을 수 있습니다.</p><p>이 알고리즘은 고차원 공간에서 사용할 때 KD 트리와 동일한 문제를 겪습니다. 이를 차원의 저주라고 하며, 공간의 부피가 너무 커져서 모든 데이터가 희박해 보이고 더 정확한 결과를 얻기 위해 필요한 데이터의 양이 기하급수적으로 늘어날 때 발생합니다. 데이터가 희소하면 이러한 공간 분할 알고리즘이 데이터를 클러스터로 구성하기가 더 어려워집니다. 다행히도 아래에 자세히 설명된 것처럼 이 문제를 완화하는 다른 알고리즘과 기술이 있습니다.</p><h4>양자화</h4><p>양자화는 임베딩 벡터의 정밀도를 낮춤으로써 데이터베이스의 전체 크기를 줄일 수 있는 <strong>압축 기반</strong>접근 방식입니다. 이는 부동 소수점 벡터 값을 정수 값으로 변환하는 <strong>스칼라 양자화(SQ)를</strong> 사용하여 달성할 수 있습니다. 이렇게 하면 데이터베이스의 크기가 8배까지 줄어들 뿐만 아니라 메모리 사용량도 감소하고 검색 시 벡터 간의 거리 계산 속도도 빨라집니다.</p><p>또 다른 기법은 <strong>제품 양자화(PQ)</strong> 로, 먼저 공간을 저차원 하위 공간으로 나눈 다음 클러스터링 알고리즘(K-평균과 유사)을 사용하여 서로 가까운 벡터를 각 하위 공간에 그룹화합니다.</p><p>양자화는 차원 수가 줄어드는 <strong>차원 축소</strong>, 즉 벡터가 단순히 짧아지는 차원 축소와는 다릅니다.</p><h4>계층적 탐색 가능한 작은 세계(HNSW)</h4><p>이름만 보고 복잡해 보이더라도 걱정하지 마세요, 실제로는 그렇지 않으니까요! 간단히 말해 계층적 탐색 가능한 작은 세계는 매우 인기 있고 효율적인 다층 그래프 기반 알고리즘입니다. 아파치 루씬을 비롯한 다양한 벡터 데이터베이스에서 사용됩니다. 아래 그림 13에서 HNSW의 개념적 표현을 볼 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7f4f4a8e393c8d53/6a17e0e3e8fbcefa193a1861/189ef9a8bec476e379222c454644ba4c1f952085-1400x840.png" alt="계층적 탐색 가능한 작은 세계(HNSW)" /><p>최상위 레이어에서는 벡터들 사이에 가장 긴 연결 고리를 가진 극소수의 벡터, 즉 유사성이 가장 낮은 연결된 벡터의 그래프를 볼 수 있습니다. 더 낮은 레이어로 내려갈수록 더 많은 벡터를 발견할 수 있으며, 그래프는 점점 더 밀도가 높아져 서로 가까운 벡터가 많아집니다. 가장 낮은 레이어에서 모든 벡터를 찾을 수 있으며, 가장 유사한 벡터가 서로 가장 가까운 곳에 위치합니다.</p><p>검색 시 알고리즘은 임의의 진입점의 최상위 레이어에서 시작하여 쿼리 벡터에 가장 가까운 벡터(회색 점으로 표시됨)를 찾습니다. 그런 다음 한 레이어 아래로 이동하여 가장 낮은 레이어에 도달하여 쿼리 벡터와 가장 가까운 이웃을 찾을 때까지 위 레이어에 남긴 동일한 벡터에서 시작하여 동일한 과정을 한 레이어씩 차례로 반복합니다.</p><h4>지역 민감 해싱(LSH)</h4><p>지금까지 소개한 다른 모든 접근 방식과 같은 맥락에서, 위치 정보에 민감한 해싱은 검색 속도를 높이기 위해 검색 공간을 대폭 줄이려고 합니다. 이 기술을 사용하면 임베딩 벡터가 유사성 정보를 보존하면서 해시값으로 변환되므로 검색 공간은 궁극적으로 탐색해야 하는 그래프나 트리 대신 조회할 수 있는 간단한 해시 테이블이 됩니다. 해시 기반 방법의 가장 큰 장점은 임의의 (큰) 수의 차원을 포함하는 벡터를 고정 크기 해시에 매핑할 수 있어 정밀도를 크게 떨어뜨리지 않으면서 검색 시간을 크게 단축할 수 있다는 것입니다.</p><p>일반적으로 데이터를 해싱하는 방법, 특히 벡터를 임베딩하는 방법에는 여러 가지가 있지만 이 글에서는 각 방법에 대해 자세히 다루지는 않겠습니다. 기존의 해시 방식은 일반적으로 매우 비슷해 보이는 데이터에 대해 매우 다른 해시를 생성합니다. 임베딩 벡터는 실수 값으로 구성되므로 벡터 산술에서 서로 매우 가까운 것으로 간주되는 두 개의 샘플 실수 값(예: 0.73 및 0.74)을 가져와 몇 가지 일반적인 해싱 함수를 통해 실행해 보겠습니다. 아래 결과를 보면 일반적인 해싱 함수는 입력 간의 유사성을 유지하지 못한다는 것을 알 수 있습니다.</p><p>해싱 기능</p><p>0.73</p><p>0.74</p><p>MD5</p><p>1342129d04cd2924dd06cead4cf0a3ca</p><p>0aec1b15371bd979cfa66b0a50ebecc5</p><p>SHA1</p><p>49d2c3e0e44bff838e1db571a121be5ea874e8d9</p><p>a534e76482ade9d9fe4bff3035a7f31f2f363d77</p><p>SHA256</p><p>99d03fc3771fe6848d675339fc49eeb1cb8d99a12e6358173336b99a2ec530ea</p><p>5ecbc825ba5c16856edfdaf0abc5c6c41d0d8a9c508e34188239521dc7645663</p><p>기존의 해싱 방식은 유사한 데이터 조각 간의 <em>해싱 충돌을 최소화하려고</em> 하지만, 지역 민감 해싱의 주요 목표는 정반대로, 즉 <em>해싱 충돌을 최대화하여</em> 유사한 데이터가 높은 확률로 같은 버킷에 속하도록 하는 것입니다. 이렇게 하면 다차원 공간에서 서로 가까이 있는 벡터를 임베딩하면 동일한 버킷에 속하는 고정된 크기의 값으로 해시됩니다. LSH는 해시된 벡터가 근접성을 유지할 수 있도록 해주기 때문에 데이터 클러스터링과 가장 가까운 이웃 검색에 매우 유용한 기술입니다.</p><p>모든 무거운 작업은 해시를 계산해야 하는 인덱싱 시점에 이루어지며, 검색 시에는 가장 가까운 임베딩 벡터가 포함된 버킷을 조회하기 위해 쿼리 벡터만 해시하면 됩니다. 후보 버킷이 발견되면 일반적으로 쿼리 벡터에 가장 가까운 이웃 벡터를 식별하기 위해 두 번째 라운드가 진행됩니다.</p><h2>결론을 내리겠습니다.</h2><p>벡터 검색을 소개하기 위해 이 글에서 꽤 많은 내용을 다루어야 했습니다. 어휘 검색과 벡터 검색의 차이점을 비교한 후, 딥러닝 신경망 모델이 어떻게 비정형 데이터의 의미를 파악하고 그 의미를 고차원 임베딩 벡터(모델의 각 차원에 따라 데이터의 유사성을 나타내는 부동 소수점 숫자 시퀀스)로 변환하는지에 대해 알아봤습니다. 벡터 검색과 어휘 검색은 경쟁하는 것이 아니라 상호 보완적인 정보 검색 기술이라는 점도 주목할 필요가 있습니다(이 시리즈의 3부에서 하이브리드 검색에 대해 자세히 살펴보겠습니다).</p><p>그 후, 벡터 검색의 기본 구성 요소인 거리(및 유사도) 함수를 도입하여 두 벡터의 근접성을 측정하고 벡터가 나타내는 개념의 유사성을 평가할 수 있게 했습니다.</p><p>마지막으로 트리, 그래프, 클러스터 또는 해시를 기반으로 하는 가장 인기 있는 벡터 검색 알고리즘과 기법의 다양한 종류를 살펴보았는데, 선형 무차별 대입 검색처럼 전체 공간을 둘러볼 필요 없이 다차원 공간의 특정 영역을 빠르게 좁혀 가장 가까운 이웃을 찾는 것이 목표입니다.</p><p>읽고 있는 내용이 마음에 드신다면 이 시리즈의 다른 부분도 꼭 확인해 보세요:</p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/vector-search-set-up-elasticsearch">2부: Elasticsearch에서 벡터 검색을 설정하는 방법</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/hybrid-search-elasticsearch">3부: Elasticsearch를 사용한 하이브리드 검색</a></p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/introduction-to-vector-search</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/introduction-to-vector-search</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70da374b8dc0c490/6a17e0e46864a473aab686b3/63eea8ea95b49e7241e539f65bf5aa3bb8823fff-1200x628.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[AI 에이전트 개발을 위해 Microsoft 시맨틱 커널용 Elasticsearch 벡터 스토어 커넥터를 사용하는 방법]]></title>
    <description><![CDATA[Microsoft 시맨틱 커널은 가벼운 오픈 소스 개발 키트로, AI 에이전트를 쉽게 빌드하고 최신 AI 모델을 C#, Python 또는 Java 코드베이스에 통합할 수 있습니다. Semantic Kernel Elasticsearch 벡터 스토어 커넥터가 출시됨에 따라, 이제 AI 에이전트 구축에 Semantic Kernel을 사용하는 개발자는 확장 가능한 엔터프라이즈급 벡터 스토어로 Elasticsearch를 플러그인하면서 Semantic Kernel 추상화를 계속 사용할 수 있습니다.]]></description>
    <content:encoded><![CDATA[<p><a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">Microsoft Semantic Kernel</a> 팀과 협력하여,<a href="https://learn.microsoft.com/en-us/semantic-kernel/overview/">Microsoft Semantic Kernel</a> (.NET) 사용자를 위한 <a href="https://github.com/elastic/semantic-kernel-net/">Semantic Kernel Elasticsearch 벡터 스토어 커넥터의 출시를 발표합니다.</a> 시맨틱 커널은 벡터 스토어의 보다 관련성 높은 데이터 기반 응답으로 대규모 언어 모델(LLM)을 개선하는 기능을 포함하여 엔터프라이즈급 AI 에이전트 구축을 간소화합니다. Semantic Kernel은 Elasticsearch와 같은 벡터 저장소와 상호 작용하기 위한 원활한 추상화 계층을 제공하여 레코드 컬렉션 생성, 나열, 삭제, 개별 레코드 업로드, 검색, 삭제와 같은 필수 기능을 제공합니다.</p><p><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/elasticsearch-connector?pivots=programming-language-csharp">즉시 사용 가능한 Semantic Kernel Elasticsearch 벡터 저장소 커넥터는</a> 개발자가 AI 에이전트를 구축하는 동안 매우 쉽게 Elasticsearch를 벡터 저장소로 플러그인할 수 있도록 Semantic Kernel <a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/?pivots=programming-language-csharp#the-vector-store-abstraction">벡터 저장소 추상화를</a> 지원합니다.</p><p>Elasticsearch는 오픈 소스 커뮤니티에서 강력한 기반을 가지고 있으며 최근 <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">AGPL 라이선스를</a> 채택했습니다. 이러한 도구는 오픈 소스 Microsoft 시맨틱 커널과 결합하여 강력하고 엔터프라이즈급 솔루션을 제공합니다. 다음 명령 <code>curl -fsSL https://elastic.co/start-local | sh </code>(자세한 내용은 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/run-elasticsearch-locally.html">start-local</a> 참조)을 실행하여 몇 분 안에 Elasticsearch를 시작하여 로컬에서 시작할 수 있으며, AI 에이전트를 프로덕션하는 동안 <a href="https://cloud.elastic.co/registration?onboarding_token=vectorsearch&amp;utm_source=semantickernel&amp;utm_content=documentation">클라우드 호스팅</a> 또는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.16/install-elasticsearch.html">자체 호스팅</a> 버전으로 이동할 수 있습니다.</p><p>이 블로그에서는 시맨틱 커널을 사용할 때 시맨틱 커널 <a href="https://github.com/elastic/semantic-kernel-net/">Elasticsearch 벡터 저장소 커넥터를</a> 사용하는 방법을 살펴봅니다. 향후 파이썬 버전의 커넥터가 제공될 예정입니다.</p><h2>높은 수준의 시나리오: 시맨틱 커널로 RAG 앱 구축하기 &amp; Elasticsearch</h2><p>다음 섹션에서는 예시를 살펴보겠습니다. 저희는 사용자의 질문을 입력으로 받아 답변을 반환하는 RAG(검색 증강 생성) 애플리케이션을 구축하고 있습니다. LLM으로 Azure OpenAI<a href="https://devblogs.microsoft.com/semantic-kernel/introducing-new-ollama-connector-for-local-models/">(로컬 LLM도</a> 사용 가능)를 사용하고, 벡터 저장소로 Elasticsearch를, 모든 구성 요소를 하나로 묶는 프레임워크로 Semantic Kernel(.net)을 사용할 것입니다.</p><p>RAG 아키텍처에 익숙하지 않다면 이 문서 <a href="https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag">(https://www.elastic.co/search-labs/blog/retrieval-augmented-generation-rag)</a>를 통해 간단히 소개할 수 있습니다.</p><p>답변은 질문과 관련된 컨텍스트가 제공된 LLM에 의해 생성되며, 이는 Elasticsearch 벡터스토어에서 검색됩니다. 응답에는 LLM에서 컨텍스트로 사용한 소스도 포함됩니다.</p><h3>RAG 예제</h3><p>이 구체적인 예에서는 사용자가 내부 호텔 데이터베이스에 저장된 호텔에 대해 질문할 수 있는 애플리케이션을 구축합니다. 사용자는 예를 들어 다양한 기준에 따라 특정 호텔을 검색하거나 호텔 목록을 요청할 수 있습니다.</p><p>예제 데이터베이스의 경우, 100개의 항목이 포함된 <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">호텔 목록을</a> 생성했습니다. 커넥터 데모를 최대한 쉽게 사용해 볼 수 있도록 샘플 크기는 의도적으로 작게 만들었습니다. 실제 애플리케이션에서 Elasticsearch 커넥터는 특히 매우 많은 양의 데이터로 작업할 때 'InMemory' 벡터 저장소 구현과 같은 다른 옵션에 비해 그 장점을 보여줄 것입니다.</p><p>전체 데모 애플리케이션은 Elasticsearch 벡터 스토어 커넥터 <a href="https://github.com/elastic/semantic-kernel-net/tree/main/Elastic.SemanticKernel.Playground">리포지토리에서</a> 확인할 수 있습니다.</p><p>필요한 NuGet 패키지를 추가하고 프로젝트에 지시문을 사용하는 것부터 시작하겠습니다:</p>dotnet add package "Elastic.Clients.Elasticsearch" -v 8.16.2
dotnet add package "Elastic.SemanticKernel.Connectors.Elasticsearch" -v 0.1.2
dotnet add package "Microsoft.Extensions.Hosting" -v 9.0.0
dotnet add package "Microsoft.SemanticKernel.Connectors.AzureOpenAI" -v 1.30.0
dotnet add package "Microsoft.SemanticKernel.PromptTemplates.Handlebars" -v 1.30.0using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

using Elastic.Clients.Elasticsearch;
using Elastic.Transport;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.VectorData;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Data;
using Microsoft.SemanticKernel.Embeddings;
using Microsoft.SemanticKernel.PromptTemplates.Handlebars;<p>이제 데이터 모델을 생성하고 시맨틱 커널 특정 속성을 제공하여 저장소 모델 스키마를 정의하고 텍스트 검색을 위한 몇 가지 힌트를 제공할 수 있습니다:</p>/// &lt;summary&gt;
/// Data model for storing a "hotel" with a name, a description, a  description embedding and an optional reference link.
/// &lt;/summary&gt;
public sealed record Hotel
{
	[VectorStoreRecordKey]
	public required string HotelId { get; set; }

	[TextSearchResultName]
	[VectorStoreRecordData(IsFilterable = true)]
	public required string HotelName { get; set; }

	[TextSearchResultValue]
	[VectorStoreRecordData(IsFullTextSearchable = true)]
	public required string Description { get; set; }

	[VectorStoreRecordVector(Dimensions: 1536, DistanceFunction.CosineSimilarity, IndexKind.Hnsw)]
	public ReadOnlyMemory&lt;float&gt;? DescriptionEmbedding { get; set; }

	[TextSearchResultLink]
	[VectorStoreRecordData]
	public string? ReferenceLink { get; set; }
}<p>저장 모델 스키마 속성(`VectorStore*`)은 Elasticsearch 벡터 스토어 커넥터의 실제 사용과 가장 관련이 있습니다:</p><p></p><ul><li><p><code>VectorStoreRecordKey</code> 를 사용하여 레코드 클래스의 속성을 벡터 저장소에 레코드가 저장되는 키로 표시할 수 있습니다.</p></li><li><p><code>VectorStoreRecordData</code> 를 사용하여 레코드 클래스의 프로퍼티를 '데이터'로 표시할 수 있습니다.</p></li><li><p><code>VectorStoreRecordVector</code> 를 사용하여 레코드 클래스의 속성을 벡터로 표시할 수 있습니다.</p></li></ul><p>이러한 모든 속성은 스토리지 모델을 추가로 사용자 지정하는 데 사용할 수 있는 다양한 선택적 매개변수를 허용합니다. 예를 들어 <code>VectorStoreRecordKey </code> 의 경우 다른 거리 함수 또는 다른 인덱스 유형을 지정할 수 있습니다.</p><p>이 예제의 마지막 단계에서는 텍스트 검색 속성(<code>TextSearch*</code>)이 중요합니다. 나중에 다시 설명하겠습니다.</p><p>다음 단계에서는 시맨틱 커널 엔진을 초기화하고 핵심 서비스에 대한 참조를 얻습니다. 실제 애플리케이션에서는 서비스 컬렉션에 직접 액세스하는 대신 종속성 <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection">주입을</a> 사용해야 합니다. 하드코딩된 구성 및 비밀도 마찬가지이며, 대신 <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">구성 공급자를</a> 사용하여 읽어야 합니다:</p>var builder = Host.CreateApplicationBuilder(args);

// Register AI services.
var kernelBuilder = builder.Services.AddKernel();

kernelBuilder.AddAzureOpenAIChatCompletion("gpt-4o", "https://my-service.openai.azure.com", "my_token");

kernelBuilder.AddAzureOpenAITextEmbeddingGeneration("ada-002", "https://my-service.openai.azure.com", "my_token");

// Register text search service.
kernelBuilder.AddVectorStoreTextSearch&lt;Hotel&gt;();

// Register Elasticsearch vector store.
var elasticsearchClientSettings = new ElasticsearchClientSettings(new Uri("https://my-elasticsearch-instance.cloud"))
    .Authentication(new BasicAuthentication("elastic", "my_password"));

kernelBuilder.AddElasticsearchVectorStoreRecordCollection&lt;string, Hotel&gt;("skhotels", elasticsearchClientSettings);

// Build the host.
using var host = builder.Build();

// For demo purposes, we access the services directly without using a DI context.

var kernel = host.Services.GetService&lt;Kernel&gt;()!;
var embeddings = host.Services.GetService&lt;ITextEmbeddingGenerationService&gt;()!;
var vectorStoreCollection = host.Services.GetService&lt;IVectorStoreRecordCollection&lt;string, Hotel&gt;&gt;()!;

// Register search plugin.
var textSearch = host.Services.GetService&lt;VectorStoreTextSearch&lt;Hotel&gt;&gt;()!;
kernel.Plugins.Add(textSearch.CreateWithGetTextSearchResults("SearchPlugin"));<p>이제 <code>vectorStoreCollection</code> 서비스를 사용하여 컬렉션을 생성하고 몇 가지 <a href="https://github.com/elastic/semantic-kernel-net/blob/main/Elastic.SemanticKernel.Playground/hotels.csv">데모 레코드를</a> 수집할 수 있습니다:</p>await vectorStoreCollection.CreateCollectionIfNotExistsAsync();

// CSV format: ID;Hotel Name;Description;Reference Link
var hotels = (await File.ReadAllLinesAsync("hotels.csv"))
    .Select(x =&gt; x.Split(';'));

foreach (var chunk in hotels.Chunk(25))
{
    var descriptionEmbeddings = await embeddings.GenerateEmbeddingsAsync(chunk.Select(x =&gt; x[2]).ToArray());
    
    for (var i = 0; i &lt; chunk.Length; ++i)
    {
        var hotel = chunk[i];
        await vectorStoreCollection.UpsertAsync(new Hotel
        {
            HotelId = hotel[0],
            HotelName = hotel[1],
            Description = hotel[2],
            DescriptionEmbedding = descriptionEmbeddings[i],
            ReferenceLink = hotel[3]
        });
    }
}<p>이는 시맨틱 커널이 어떻게 복잡한 벡터 저장소 사용을 몇 가지 간단한 메서드 호출로 줄이는지 보여줍니다.</p><p>내부적으로는 Elasticsearch에서 새 인덱스가 생성되고 필요한 모든 속성 매핑이 만들어집니다. 그런 다음 데이터 세트가 완전히 투명하게 스토리지 모델에 매핑되고 최종적으로 인덱스에 저장됩니다. 아래는 Elasticsearch에서 매핑이 어떻게 표시되는지 보여줍니다.</p>{
  "mappings": {
    "properties": {
      "descriptionEmbedding": {
        "dims": 1536,
        "index": true,
        "index_options": {
          "type": "hnsw"
        },
        "similarity": "cosine",
        "type": "dense_vector"
      },
      "hotelName": {
        "type": "keyword"
      },
      "description": {
        "type": "text"
      }
    }
  }
}<p><code>embeddings.GenerateEmbeddingsAsync()</code> 호출은 구성된 Azure AI 임베딩 생성 서비스를 투명하게 호출합니다.</p><p>이 데모의 마지막 단계에서는 더 많은 마법을 볼 수 있습니다.</p><p>사용자가 데이터에 대해 질문할 때 <code>InvokePromptAsync</code> 으로 한 번만 호출하면 다음 작업이 모두 수행됩니다:</p><p>1. 사용자의 질문에 대한 임베딩이 생성됩니다.</p><p>2. 벡터 스토어에서 관련 항목을 검색합니다.</p><p>3. 쿼리 결과가 프롬프트 템플릿에 삽입됩니다.</p><p>4. 최종 프롬프트 형태의 실제 쿼리가 AI 채팅 완성 서비스로 전송됩니다.</p>// Invoke the LLM with a template that uses the search plugin to
// 1. get related information to the user query from the vector store
// 2. add the information to the LLM prompt.
var response = await kernel.InvokePromptAsync(
    promptTemplate: """
                    Please use this information to answer the question:
                    {{#with (SearchPlugin-GetTextSearchResults question)}}
                      {{#each this}}
                        Name: {{Name}}
                        Value: {{Value}}
                        Source: {{Link}}
                        -----------------
                      {{/each}}
                    {{/with}}
                    
                    Include the source of relevant information in the response.

                    Question: {{question}}
                    """,
    arguments: new KernelArguments
    {
        { "question", "Please show me all hotels that have a rooftop bar." },
    },
    templateFormat: "handlebars",
    promptTemplateFactory: new HandlebarsPromptTemplateFactory());<p>이전에 데이터 모델에 정의했던 <code>TextSearch*</code> 속성을 기억하시나요? 이러한 속성을 사용하면 벡터 스토어에 있는 항목의 정보로 자동으로 채워지는 프롬프트 템플릿에서 해당 자리 표시자를 사용할 수 있습니다.</p><p>질문 "에 대한 최종 답변은 다음과 같습니다." 루프톱 바가 있는 호텔을 모두 보여주세요:</p>Console.WriteLine(response.ToString());

// &gt; The hotel that has a rooftop bar is Skyline Suites. You can find more information about this hotel [here](https://example.com/yz567).<p>정답은 hotels.csv의 다음 항목을 참조합니다.</p>9;
Skyline Suites;
Offering panoramic city views from every suite, this hotel is perfect for those who love the urban landscape. Enjoy luxurious amenities, a rooftop bar, and close proximity to attractions. Luxurious and contemporary.;
https://example.com/yz567<p>이 예는 Microsoft 시맨틱 커널을 사용하면 잘 고안된 추상화를 통해 복잡성을 크게 줄일 수 있을 뿐만 아니라 매우 높은 수준의 유연성을 구현할 수 있다는 것을 잘 보여줍니다. 예를 들어 코드 한 줄만 변경하면 코드의 다른 부분을 리팩터링하지 않고도 벡터 저장소나 사용되는 AI 서비스를 교체할 수 있습니다.</p><p>동시에 이 프레임워크는 '인보크프롬프트' 기능이나 템플릿 또는 검색 플러그인 시스템과 같은 방대한 고급 기능 세트를 제공합니다.</p><p>전체 데모 애플리케이션은 Elasticsearch 벡터 스토어 커넥터 리포지토리에서 확인할 수 있습니다.</p><h2>Elasticsearch로 가능한 다른 기능</h2><ul><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-search-simplified-semantic-text">Elasticsearch의 새로운 의미론적 텍스트 매핑: 시맨틱 검색 간소화</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/semantic-reranking-with-retrievers">검색기를 사용한 Elasticsearch의 시맨틱 재랭크</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">고급 RAG 기술 1부: 데이터 처리</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">고급 RAG 기술 2부: 쿼리 및 테스트</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-rag-with-llama3-opensource-and-elastic">Llama 3 오픈 소스 및 Elastic으로 RAG 구축하기</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/local-rag-agent-elasticsearch-langgraph-llama3">LangGraph, LLaMA3 및 Elasticsearch 벡터 저장소를 사용하여 로컬 에이전트를 처음부터 구축하는 튜토리얼</a></p></li></ul><h2>Elasticsearch &amp; 시맨틱 커널: 다음 단계는 무엇인가요?</h2><ul><li><p>.NET에서 GenAI 애플리케이션을 구축하는 동안 Elasticsearch 벡터 저장소를 Semantic Kernel에 쉽게 연결할 수 있는 방법을 보여드렸습니다. 다음 파이썬 통합을 기대해 주세요.</p></li><li><p>Semantic Kernel은 <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">하이브리드 검색</a>과 같은 고급 검색 기능에 대한 추상화를 구축하므로, .NET 개발자는 Elasticsearch 연결을 통해 Semantic Kernel을 사용하면서 이를 쉽게 구현할 수 있습니다.</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-connector-microsoft-semantic-kernel</guid>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[.NET]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Florian Bernd,Srikanth Manvi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d8725035e86f8a8/6a17fe447f6f1564f8c09d74/0564fe794e4c66d0507317822d7aa71826183d20-1311x762.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 06 Dec 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[이커머스 제품 카탈로그에 하이브리드 검색을 사용하는 방법]]></title>
    <description><![CDATA[패싯, 프로모션, 개인화 및 행동 분석을 사용하여 하이브리드 검색을 사용하여 이커머스 제품 카탈로그를 구축하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 전체 텍스트 검색 결과와 벡터 검색 결과를 결합하는 하이브리드 검색을 구현하는 방법을 설명합니다. 하이브리드 검색은 이 두 가지 접근 방식을 통합함으로써 두 가지 검색 전략의 장점을 모두 활용하여 검색 결과의 폭을 개선합니다.</p><p>하이브리드 검색을 통합하는 것 외에도 검색 솔루션을 더욱 강력하게 만드는 기능을 추가하는 방법을 보여드리겠습니다. 여기에는 패싯 및 개인화된 제품 프로모션이 포함됩니다. 또한 Elastic의 행동 분석 도구를 사용하여 사용자 상호 작용을 캡처하고 귀중한 인사이트를 생성하는 방법도 보여드립니다.</p><p>이 구현에서는 사용자가 검색 결과를 보고 상호 작용할 수 있는 인터페이스와 정보 반환을 담당하는 API를 모두 구축하는 방법을 살펴봅니다. 소스 코드가 있는 리포지토리에 액세스하려면 아래 링크를 참조하세요:</p><ul><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search</a></p></li><li><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store</a> </p></li></ul><p>이 가이드는 인덱스 생성부터 패싯 및 결과 개인화와 같은 고급 기능 구현에 이르기까지 여러 단계로 나누어 설명합니다. 마지막에는 이커머스 시나리오에서 사용할 수 있는 강력한 검색 솔루션이 준비됩니다.</p><h2>이커머스 하이브리드 검색을 위한 환경 설정</h2><p>구현을 시작하기 전에 환경을 설정해야 합니다. Elastic Cloud의 서비스 또는 컨테이너화된 솔루션을 사용하여 Elasticsearch를 관리하도록 선택할 수 있습니다. 컨테이너화를 선택하는 경우 Docker Compose를 통한 구성은 이 리포지토리에서 찾을 수 있습니다: <a href="https://github.com/andreluiz1987/product-store-search/blob/main/docker/docker-compose.yml">docker-compose.yml</a>.</p><h2>인덱스 생성 및 제품 카탈로그 수집</h2><p>색인은 이름, 설명, 사진, 카테고리, 태그 등의 필드가 포함된 화장품 카탈로그를 기반으로 만들어집니다. "name", "description," 등 전체 텍스트 검색에 사용되는 필드는 <code>text</code> 로 매핑되고, "category", "brand," 등 집계에 사용되는 필드는 <code>keyword</code> 로 매핑되어 패싯을 사용할 수 있습니다.</p><p>"설명" 필드는 제품에 대한 자세한 컨텍스트를 제공하므로 벡터 검색에 사용됩니다. 이 필드는 설명의 벡터 표현을 저장하는 <code>dense_vector,</code> 으로 정의됩니다.</p><p>인덱스 매핑은 다음과 같습니다:</p>{
   "mappings":{
      "properties":{
         "id":{
            "type":"keyword"
         },
         "brand":{
            "type":"text",
            "fields":{
               "keyword":{
                  "type":"keyword"
               }
            }
         },
         "name":{
            "type":"text"
         },
         "price":{
            "type":"float"
         },
         "price_sign":{
            "type":"keyword"
         },
         "currency":{
            "type":"keyword"
         },
         "image_link":{
            "type":"keyword"
         },
         "description":{
            "type":"text"
         },
         "description_embeddings":{
            "type":"dense_vector",
            "dims":384
         },
         "rating":{
            "type":"keyword"
         },
         "category":{
            "type":"keyword"
         },
         "product_type":{
            "type":"keyword"
         },
         "tag_list":{
            "type":"keyword"
         }
      }
   }
}<p>색인 생성을 위한 스크립트는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/infra/create_index.py">여기에서</a> 확인할 수 있습니다.</p><h2>임베딩 생성</h2><p>제품 설명을 벡터화하기 위해 모든 미니LM-L6-v2 모델을 사용합니다. 이 경우 애플리케이션은 인덱싱하기 전에 임베딩을 생성할 책임이 있습니다. 또 다른 옵션은 모델을 Elasticsearch 클러스터로 가져오는 것이지만, 이 로컬 환경에서는 애플리케이션 내에서 직접 벡터화를 수행하는 방법을 선택했습니다.</p><p><a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">Kaggle에서</a> 제공되는 화장품 데이터 세트를 사용하여 인덱스를 채우고, 데이터 수집의 효율성을 높이기 위해 일괄 처리를 사용했습니다. 동일한 수집 단계에서 "description" 필드에 대한 임베딩을 생성하고 새 필드 "description_embeddings" 로 인덱싱합니다.</p><p>전체 데이터 수집 프로세스는 리포지토리에서 제공되는 <strong>Jupyter Notebook을</strong> 통해 직접 추적하고 실행할 수 있습니다. 이 노트북은 데이터를 읽고, 처리하고, 색인하는 방법에 대한 단계별 가이드를 제공하여 쉽게 복제하고 실험할 수 있도록 해줍니다.</p><p>다음 링크에서 노트북에 액세스할 수 있습니다: <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/ingestion/ingestion.ipynb">수집 노트북.</a></p><h2>하이브리드 검색 구현</h2><p>이제 하이브리드 검색을 구현해 보겠습니다. 키워드 기반 검색의 경우 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">다중 일치</a> 쿼리를 사용하여 "name," " category," 및 "description 필드를 타겟팅합니다." 이렇게 하면 이러한 필드에 검색어가 포함된 문서가 검색됩니다.</p><p>벡터 검색의 경우 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">KNN 쿼리를</a> 사용합니다. 쿼리를 실행하기 전에 검색어를 벡터화해야 하며, 이는 입력어를 벡터화하는 방법을 사용하여 수행됩니다. 수집 시 사용된 것과 동일한 모델이 검색어에도 사용된다는 점에 유의하세요.</p><p>두 검색의 조합은 두 쿼리의 결과를 병합하고 노이즈를 줄여 검색 정확도를 높이는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">상호 순위 융합(RRF) </a>알고리즘을 사용하여 이루어집니다. RRF를 사용하면 키워드 기반 검색과 벡터 검색이 모두 함께 작동하여 사용자의 쿼리에 대한 이해도를 높일 수 있습니다.</p>query = {
   "retriever": {
       "rrf": {
           "retrievers": [
               {
                   "standard": {
                       "query": organic_query['query']
                   }
               },
               {
                   "knn": {
                       "field": "description_embeddings",
                       "query_vector": vector,
                       "k": 5,
                       "num_candidates": 20
                   }
               }
           ],
           "rank_window_size": 20,
           "rank_constant": 5
       }
   },
   "_source": organic_query['_source']
}<h3>결과 비교: 키워드 검색 대 하이브리드 검색</h3><p>이제 기존 키워드 검색과 하이브리드 검색의 결과를 비교해 보겠습니다. 키워드 검색을 사용하여 "건성 피부용 파운데이션" 을 검색하면 다음과 같은 결과가 표시됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcb5405df787bc8f5/6a17023e961e697ca1c4cdb4/52e717aa3c9c1fadfadb639f5fb77cf8e47e3b34-1600x1021.png" alt="결과 비교: 키워드 검색과 하이브리드 검색" /><ol><li><p><strong>중성/건성 피부를 위한 레브론 컬러스테이 메이크업설명</strong>: 레브론 컬러스테이 메이크업은 케이크, 퇴색 또는 문지르지 않는 가벼운 포뮬러로 오래 지속되는 커버력을 제공합니다. 타임 릴리스 \n기술이 적용된 이 오일 프리, 수분 밸런스 포뮬러는 특히 \n중성 또는 건성 피부에 지속적으로 수분을 공급하도록 제조되었습니다.\n특징: 최대 24시간 동안 메이크업이 편안하게 지속됨\n중간 커버부터 풀 커버까지 가능\n다양하고 아름다운 색조로 제공됨
</p></li><li><p><strong>메이블린 드림 스무스 무스 파운데이션설명</strong>: 좋아하는 이유독특한 크림 휘핑 파운데이션이 100% 아기처럼 부드러운 완벽함을 선사합니다.\n\n피부 14시간 동안 촉촉함이 유지되며 거칠거나 건조하지 않습니다\n경량 포뮬러가 완벽한 보습 커버력을 제공합니다\n모공에 매끄럽게 밀착되어 하루 종일 산뜻합니다\n무오일, 무향, 피부과 테스트, 알레르기 테스트, 논코메도제닉 \u2013원\u2019t 모공 막힘\n안전합니다. 민감한 피부를 위한 제품입니다.</p></li></ol><p><strong>분석</strong>: 건성 피부용 파운데이션( ")을 검색할 때" 검색어 키워드와 제품 제목 및 설명이 정확히 일치하는 결과를 얻었습니다. 하지만 이 매치가 항상 최선의 선택을 반영하는 것은 아닙니다. 예를 들어, 건성 피부를 위해 특별히 고안된 <strong>레브론 컬러스테이 메이크업 포 노멀/건성 피부용은</strong> 좋은 선택입니다. 오일 프리 제품이지만 지속적으로 수분을 공급할 수 있도록 포뮬러가 설계되었습니다. 반면 <strong>메이블린 드림 스무스 무스 파운데이션은</strong> 오일 프리이면서 수분 공급을 언급하고 있지만, 일반적으로 오일 프리 제품은 건성 피부에 필요한 추가 수분을 공급하기보다는 유분 조절에 중점을 두는 경향이 있기 때문에 지성 또는 복합성 피부에 더 권장되는 제품입니다. 이는 건성 피부를 가진 사람들의 특정 요구 사항을 완전히 충족하지 못하는 제품을 표시할 수 있는 키워드 기반 검색의 한계를 강조합니다.</p><p>이제 하이브리드 접근 방식을 사용하여 동일한 검색을 수행합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt412e38b327cf6a4f/6a17024066c4f94c52f8beb9/13b562a5120619355ba0861976102e208964a49f-1600x1021.png" alt="하이브리드 접근 방식을 사용하여 검색 수행" /><ol><li><p><strong>커버걸 아웃라스트 스테이 루미너스 파운데이션 크리미 내추럴 (820):설명</strong>: 커버걸 아웃라스트 스테이 루미너스 파운데이션은 촉촉한 마무리와 은은한 광채를 연출하기에 완벽한 제품입니다. 기름기가 없는 포뮬러로 하루 종일 지속되는 자연스러운 광채를 피부에 선사하는 오일 프리 제품입니다! 이 올데이 파운데이션은 피부에 수분을 공급하고 결점을 완벽하게 커버합니다.

<strong>분석: </strong>이 제품은 건성 피부 사용자에게 중요한 수분 공급을 강조하는 제품이기 때문에 적절하게 어울립니다. "촉촉한 피부" 및 "보송한 마무리" 라는 용어는 건성 피부를 위한 파운데이션을 찾는 사용자의 의도와 일치합니다. 벡터 검색은 수분 공급의 개념을 이해하고 이를 건성 피부를 위한 파운데이션의 필요성과 연결시켰을 가능성이 높습니다.
</p></li><li><p><strong>중성/건성 피부용 레브론 컬러스테이 메이크업:설명:</strong> 레브론 컬러스테이 메이크업은 케이크, 퇴색 또는 문지르지 않는 가벼운 포뮬러로 오래 지속되는 커버력을 제공합니다. 타임 릴리스 기술이 적용된 이 오일 프리 수분 밸런스 포뮬러는 특히 중성 또는 건성 피부를 위해 만들어져 지속적으로 수분을 공급합니다.

<strong>분석: </strong>이 제품은 건성 피부 사용자의 요구를 직접적으로 해결하며, 중성 또는 건성 피부용으로 만들어졌다고 명시적으로 언급하고 있습니다. "수분 밸런스 포뮬러(" )와 지속적인 수분 공급은 건성 피부에 적합한 파운데이션을 찾는 분들에게 적합합니다. 벡터 검색은 키워드 매칭뿐만 아니라 수분 공급에 초점을 맞추고 건성 피부를 타겟 고객층으로 구체적으로 언급했기 때문에 이 결과를 성공적으로 검색할 수 있었습니다.
</p></li><li><p><strong>세럼 파운데이션설명: </strong>세럼 파운데이션은 21가지의 다양한 쉐이드로 제공되는 가벼운 미디엄 커버리지 포뮬러입니다. 이 파운데이션은 매우 가벼운 세럼 느낌으로 자연스러워 보이는 적당한 커버력을 제공합니다. 점도가 매우 낮으며 제공된 펌프를 사용하거나 원하는 경우 별도로 구매할 수 있는 유리 드롭퍼(옵션)를 사용하여 추출할 수 있습니다.

<strong>분석:</strong> 이 경우 설명은 자연스러운 느낌의 가벼운 세럼 파운데이션을 강조하고 있는데, 이는 건성 피부를 가진 사람들이 종종 부드럽고 촉촉하며 케이크처럼 들뜨지 않는 마무리감을 제공하는 제품을 찾기 때문에 이들의 니즈에 부합하는 것입니다. 벡터 검색은 ' "건성 피부" '라는 용어가 명시적으로 언급되지 않았음에도 불구하고 가볍고 자연스러운 커버력과 세럼과 같은 텍스처, 수분 유지 및 편안한 사용감과 관련된 광범위한 맥락에서 건성 피부와 관련이 있는 것으로 판단한 것으로 보입니다.</p></li></ol><h2>패싯 구현</h2><p>패싯은 검색 결과를 효율적으로 구체화하고 필터링하는 데 필수적이며, 특히 전자상거래와 같이 다양한 제품이 있는 시나리오에서 사용자에게 보다 집중된 탐색 기능을 제공합니다. 카테고리, 브랜드, 가격 등의 속성에 따라 검색 결과를 조정할 수 있어 검색 정확도를 높일 수 있습니다. 이 기능을 구현하기 위해 인덱스 생성 단계에서 <code>keyword</code> 로 정의된 <code>category</code> 및 <code>brand</code> 필드에 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-terms-aggregation.html">용어 집계를</a> 사용합니다.</p>    query = build_query(term, categories, product_types, brands)
    query["aggs"] = {
        "product_types": {"terms": {"field": "product_type"}},
        "categories": {"terms": {"field": "category"}},
        "brands": {"terms": {"field": "brand.keyword"}}
    }<p>구현을 위한 전체 코드는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L144">여기에서</a> 확인할 수 있습니다.</p><p>건성 피부용 파운데이션 "검색의 패싯 결과" 를 아래에서 확인하세요:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31080652a60d8600/6a1702416234e0af7ddb18e7/e8907af359c021eaa38ec7a6a53f10c325ee8ade-1146x1248.png" alt="건성 피부용 파운데이션 &quot;검색의 패싯 결과&quot;" /><h2>결과 사용자 지정: 고정된 쿼리</h2><p>경우에 따라 검색 결과에서 특정 제품을 홍보하는 것이 유리할 수 있습니다. 이를 위해 특정 제품을 결과 상단에 표시할 수 있는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html"><strong>고정 쿼리를</strong></a> 사용합니다. 아래에서는 제품을 홍보하지 않고 "재단" 이라는 용어를 검색합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31e75c815262993e/6a170243509168ae42e1b95d/9396c4ed358e7a68eed47f17dd6915d0bad492aa-1600x1157.png" alt="제품 홍보 없이 &quot;재단&quot; 이라는 용어를 검색하세요." /><p>이 예제에서는 "글루텐 프리 태그가 있는 제품을 홍보할 수 있습니다." 제품 ID를 사용하면 검색 결과에서 우선순위를 지정할 수 있습니다. 구체적으로 다음 제품을 홍보할 예정입니다: <strong>세럼 파운데이션</strong> (ID: 1043), <strong>커버 파운데이션</strong> (ID: 1042), <strong>리얼리스트 인비저블 세팅 파우더</strong> (ID: 1039).</p>{
   "query":{
      "pinned":{
         "ids":[
            "1043",
            "1042",
            "1039"
         ],
         "organic":{
            "bool":{
               "must":[
                  {
                     "multi_match":{
                        "query":"foundation",
                        "fields":[
                           "name",
                           "category",
                           "description"
                        ]
                     }
                  }
               ]
            }
         }
      }
   }
}<p>특정 제품 ID를 사용하여 쿼리 결과에서 해당 제품이 우선순위를 갖도록 합니다. "쿼리 구조에는" 에 고정되어야 하는 제품 ID 목록(이 경우 ID 1043, 1042, 1039)이 상단에 포함되며, 나머지 결과는 "name", "category", "description" 필드에 텍스트 쿼리와 같은 조건을 조합하여 검색의 자연스러운 흐름을 따르는 구조로 되어 있습니다. 이렇게 하면 항목을 제어된 방식으로 홍보하여 가시성을 확보하는 동시에 나머지 검색은 일반적인 관련성을 기반으로 유지할 수 있습니다.</p><p>아래에서 프로모션된 제품에 대한 쿼리 실행 결과를 확인할 수 있습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a6d5cbef227f16/6a1702458b73cb68f0189ef3/8c1a547bfe31360c405eae0891c666635051a51b-1600x1039.png" alt="프로모션 제품에 대한 쿼리 실행 결과" /><p>전체 쿼리 코드는 <a href="https://github.com/elastic/elasticsearch-labs/blob/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/product-store-search/api/api.py#L112">여기에서</a> 확인할 수 있습니다.</p><h2>행동 분석으로 검색 행동 분석하기</h2><p>지금까지 검색 결과의 연관성을 개선하고 제품을 쉽게 찾을 수 있는 기능을 추가했습니다. 이제 사용자 검색 행동을 분석하여 결과가 있는 쿼리와 없는 쿼리, 검색 결과 클릭 등의 패턴을 식별하는 데 도움이 되는 기능을 포함시켜 검색 솔루션을 완성할 것입니다. 이를 위해 Elastic에서 제공하는 <strong>행동 분석</strong> 기능을 사용할 것입니다. 이를 통해 몇 단계만 거치면 사용자 검색 행동을 모니터링하고 분석하여 검색 환경을 최적화할 수 있는 귀중한 인사이트를 얻을 수 있습니다.</p><h3>행동 분석 컬렉션 만들기</h3><p>첫 번째 작업은 모든 행동 분석 이벤트를 수신할 컬렉션을 만드는 것입니다. 컬렉션을 생성하려면 <strong>검색 &gt; 행동 분석에서</strong> Kibana 인터페이스에 액세스하세요. 아래 예제에서는 <code>tracking-search</code> 라는 이름의 컬렉션을 만들었습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b4cb5480ab0bfef/6a1702474a531b189936a7e7/c05e533212a3690c5ea9f2d5226cbcdc901c482a-1600x1009.png" alt="행동 분석 - 컬렉션 이름 지정하기" /><h3>행동 분석을 인터페이스에 통합하기</h3><p>우리의 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/hybrid-search-for-an-e-commerce-product-catalogue/app-product-store">프론트엔드</a> 애플리케이션은 JavaScript로 개발되었으며, 행동 분석을 통합하기 위해 공식 Elastic 설명서에 설명된 단계에 따라 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-start.html#behavioral-analytics-start-ui-integration-js-client"><strong>행동 분석 JavaScript 추적기를</strong></a> 설치합니다.</p><h3>자바스크립트 트래커 구현하기</h3><p>이제 트래커 클라이언트를 애플리케이션으로 가져와 <code>trackPageView</code>, <code>trackSearch</code>, <code>trackSearchClick</code> 메서드를 사용하여 사용자 상호작용을 캡처하겠습니다.</p><p><strong>면책</strong> 조항: 사용자 상호작용 데이터를 수집하기 위해 도구를 사용하고 있지만, <strong>GDPR을</strong> 준수하는 것은 필수적입니다. 즉, 사용자에게 어떤 데이터가 수집되고 어떻게 사용되는지 명확하게 알리고 추적 거부 옵션을 제공해야 합니다. 또한 수집된 정보를 보호하고 데이터 액세스 및 삭제와 같은 사용자 권리를 존중하기 위해 강력한 보안 조치를 구현하여 모든 단계가 GDPR 원칙을 준수하도록 해야 합니다.
</p><p><strong>1단계: 트래커 인스턴스 만들기</strong></p><p>먼저 상호작용을 모니터링할 트래커 인스턴스를 생성합니다. 이 구성에서는 대상 엔드포인트, 컬렉션 이름 및 API 키를 정의합니다:</p>createTracker({
  endpoint: "https://endpoint:443",
  collectionName: "tracking-search",
  apiKey: "api-key"
});<p><strong>2단계: 페이지 조회수 캡처</strong></p><p>페이지 조회수를 추적하려면 <code>trackPageView</code> 이벤트를 구성하면 됩니다:</p>    trackPageView({
      page: {
        title: "home-page"
      },
    });<p><code>trackPageView</code> 이벤트에 대한 자세한 내용은 이 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-event-reference.html#behavioral-analytics-event-reference-pageview-fields">문서를</a> 참조하세요.</p><p><strong>3단계: 검색 쿼리 캡처</strong></p><p>사용자의 검색 작업을 모니터링하기 위해 <code>trackSearch</code> 방법을 사용합니다:</p>      trackSearch({
        search: {
          query: searchTerm,
          results: {
            items: documents,
            total_results: response.data.length,
          },
        },
      });<p>여기에서 검색어와 검색 결과를 수집하고 있습니다.</p><p><strong>4단계: 검색 결과 클릭 추적하기</strong></p><p>마지막으로 검색 결과의 클릭 수를 캡처하기 위해 <code>trackSearchClick</code> 방법을 사용합니다:</p>trackSearchClick({
      document: { id: product.id, index: "products-catalog"},
      search: {
        query: searchTerm,
        page: {
          current: 1,
          size: products.length,
        },
        results: {
          items: documents,
          total_results: products.length,
        },
        search_application: "app-product-store"
      },
    });<p>당사는 클릭한 문서의 ID와 검색어 및 검색 결과에 대한 정보를 수집합니다.</p><h3>Kibana에서 데이터 분석하기</h3><p>이제 사용자 상호작용 이벤트가 캡처되고 있으므로 검색 동작에 대한 귀중한 데이터를 얻을 수 있습니다. Kibana는 행동 분석 도구를 사용하여 이 행동 데이터를 시각화하고 분석합니다. 결과를 보려면 <strong>검색 &gt; 행동 분석 &gt; 내 컬렉션으로</strong> 이동하면 캡처된 이벤트의 개요가 표시됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdab4f4507c5a4732/6a17024860084b34ca3c4411/e219c4af2e01b0ccc1b9079459a07832835abc75-1600x1155.png" alt="Kibana에서 데이터 분석하기" /><p>이 개요에서는 인터페이스에 통합된 각 작업에 대해 캡처된 이벤트를 전반적으로 살펴볼 수 있습니다. 이 정보를 통해 사용자 검색 행동에 대한 귀중한 인사이트를 얻을 수 있습니다. 그러나 특정 시나리오와 더 관련성이 높은 메트릭으로 개인화된 대시보드를 만들고 싶다면, Kibana는 대시보드 구축을 위한 강력한 도구를 제공하여 메트릭의 다양한 시각화를 만들 수 있게 해줍니다.</p><p>아래에서는 시간 경과에 따른 가장 많이 검색된 용어, 결과가 없는 쿼리, 가장 많이 검색된 용어를 강조하는 워드 클라우드, 마지막으로 검색 액세스가 어디에서 발생하는지 파악하기 위한 지리적 시각화 등 몇 가지 시각화 및 차트를 만들어 모니터링했습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04ced4406436f942/6a17024a5091685116e1b961/84a2c39dab77a3f9366c258a3a45d2cbc7df125e-1600x689.png" alt="모니터링할 시각화 및 차트" /><h2>결론</h2><p>이 글에서는 키워드 검색과 벡터 검색을 결합한 하이브리드 검색 솔루션을 구현하여 사용자에게 보다 정확하고 관련성 높은 결과를 제공했습니다. 또한 고정 쿼리를 통해 패싯 및 결과 맞춤 설정과 같은 추가 기능을 사용하여 보다 완벽하고 효율적인 검색 환경을 만드는 방법도 살펴봤습니다.</p><p>또한 검색 엔진과 상호 작용하는 동안 사용자 행동을 포착하고 분석하기 위해 Elastic의 <strong>행동 분석을</strong> 통합했습니다. <code>trackPageView</code>, <code>trackSearch</code>, <code>trackSearchClick</code> 와 같은 방법을 사용하여 검색 쿼리, 검색 결과 클릭, 페이지 조회수를 모니터링하여 검색 행동에 대한 귀중한 인사이트를 얻을 수 있었습니다.</p><h2>참고 자료</h2><p>데이터 세트</p><p><a href="https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset">https://www.kaggle.com/datasets/shivd24coder/cosmetic-brand-products-dataset</a></p><p>트랜스포머</p><p><a href="https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2">https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2</a></p><p>상호 등급 융합</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html</a></p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever">https://www.elastic.co/guide/en/elasticsearch/reference/current/retriever.html#rrf-retriever</a></p><p>Knn 쿼리</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html</a></p><p>고정 쿼리</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-pinned-query.html</a></p><p>행동 분석 API</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html">https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-apis.html</a></p><p>https://www.elastic.co/guide/en/elasticsearch/reference/current/behavioral-analytics-overview.html</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-ecommerce</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Andre Luiz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt63c711d1bf9b2501/6a17024c66c4f9c2b7f8bebd/05578fc595a12f6b1ebf88a10a2a31e9971b545e-1200x628.png" length="0" type="image/png"/>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Blazor와 Elasticsearch로 검색 앱 구축하기]]></title>
    <description><![CDATA[Blazor와 Elasticsearch를 사용해 검색 애플리케이션을 구축하는 방법과 하이브리드 검색을 위해 Elasticsearch .NET 클라이언트를 사용하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 문서에서는 C# 기술을 활용하여 Blazor와 Elasticsearch를 사용하여 검색 애플리케이션을 구축하는 방법에 대해 알아봅니다. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/full-text-queries.html">전체</a> 텍스트, <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">시맨틱</a> 및 <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">하이브리드</a> 검색 쿼리를 실행하기 위해 <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/introduction.html">Elasticsearch .NET 클라이언트를 사용하겠습니다.</a></p><p><strong>참고</strong> 이전 버전의 Elasticsearch C# 클라이언트 <a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/7.17/nest.html">NEST에</a> 익숙한 경우, NEST 클라이언트 지원 중단 및 새로운 기능에 대한 이 <a href="https://www.elastic.co/search-labs/blog/net-client-evolution">블로그 포스팅을</a> 읽어보세요. <em>NEST는 이전 세대의 .NET 클라이언트가 </em><code>Elastic.Clients.Elasticsearch package</code></p><p></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8d1da68641fac0/6a17f65763173044d4585bfb/18f890286ca122cd97286c09ebf740208b802d0b-650x395.png" alt="블레이저 앱 빌드: 블레이저 다이어그램을 사용한 ESRE" /><ul><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-blazor?">블레이저란 무엇인가요?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#what-is-esre?">ESRE란 무엇인가요?</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#configuring-elser">ELSER 구성</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#indexing-data">데이터 인덱싱</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor#building-the-app-with-blazor-&amp;-elasticsearch">애플리케이션 구축</a></p></li></ul><h2>블레이저란 무엇인가요?</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91278ae422883e28/6a17f6592f4a5c3213fa8a95/622741915d016b68bf94f742332d10d736d60052-707x461.png" alt="블레이저 서버" /><p><a href="https://dotnet.microsoft.com/en-us/apps/aspnet/web-apps/blazor">Blazor는</a> 개발자가 클라이언트 또는 서버에서 실행되는 웹 애플리케이션을 빌드할 수 있도록 Microsoft에서 만든 오픈 소스 HTML, CSS 및 C# 기반 웹 프레임워크입니다. 또한 재사용 가능한 컴포넌트를 만들어 애플리케이션을 더 빠르게 빌드할 수 있으며, 개발자가 동일한 파일 내에서 C#으로 HTML 보기와 동작을 빌드할 수 있어 가독성 높고 깔끔한 코드를 유지할 수 있습니다. 또한 Blazor Hybrid를 사용하면 .NET 코드를 통해 네이티브 플랫폼 기능에 액세스하는 네이티브 모바일 앱을 빌드할 수 있습니다.</p><p>블레이저를 작업하기 좋은 프레임워크로 만드는 몇 가지 기능을 소개합니다:</p><ul><li><p>서버 측 및 클라이언트 측 렌더링 옵션</p></li><li><p>재사용 가능한 UI 구성 요소</p></li><li><p>SignalR로 실시간 업데이트</p></li><li><p>기본 제공 상태 관리</p></li><li><p>내장 라우팅 시스템</p></li><li><p>강력한 타이핑 및 컴파일 타임 검사</p></li></ul><h3>왜 블레이저인가요?</h3><p>Blazor는 다른 프레임워크 및 라이브러리에 비해 몇 가지 장점을 제공합니다. 개발자는 클라이언트 및 서버 코드 모두에 C#을 사용할 수 있으며, 강력한 타이핑 및 컴파일 타임 검사를 제공하여 안정성을 향상시킵니다. .NET 에코시스템과 원활하게 통합되어 .NET 라이브러리 및 도구를 재사용할 수 있으며, 강력한 디버깅 지원을 제공합니다.</p><h2>ESRE란 무엇인가요?</h2><p><a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">Elasticsearch Relevance Engine™ (ESRE)</a> 은 강력한 Elasticsearch 검색 엔진 위에 머신 러닝과 인공 지능을 사용해 <a href="https://www.elastic.co/guide/en/esre/current/learn.html">검색 애플리케이션을 구축하는 도구</a> 세트입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf313ba986de01b93/6a17d851505ac306fcad8975/4c0f2645ed1c27fe3ef61a1a9126adadfd8d5368-721x421.png" alt="esre" /><p>ESRE에 대해 자세히 알아보려면 <a href="https://www.elastic.co/search-labs/blog/introducing-elasticsearch-relevance-engine-esre">여기에서</a>인사이트가 담긴 블로그 게시물을 읽어보세요.</p><h2>ELSER 구성</h2><p>Elastic의 <a href="https://www.elastic.co/elasticsearch/elasticsearch-relevance-engine">ESRE</a> 기능을 활용하기 위해, 우리는 모델 공급자로 <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER를</a> 사용할 것입니다.</p><p><em>Elasticsearch의 ELSER 모델을 사용하려면, 플래티넘 또는 엔터프라이즈 라이선스가 있어야 하며 최소 4GB 크기의 전용 머신 러닝(ML) 노드가 있어야 합니다.</em> <a href="https://www.elastic.co/guide/en/machine-learning/8.15/ml-nlp-elser.html#elser-req"><em>여기에서 자세히 알아보세요.</em></a></p><p>추론 엔드포인트를 만드는 것부터 시작하세요:</p>PUT _inference/sparse_embedding/my-elser-model
{
  "service": "elser",
  "service_settings": {
    "num_allocations": 1,
    "num_threads": 1
  }
}<p>ELSER를 처음 사용하는 경우 모델이 백그라운드에서 로드될 때 502 잘못된 게이트웨이 오류가 발생할 수 있습니다. Kibana의 <code>Machine Learning &gt; Trained Models</code> 에서 모델의 상태를 확인할 수 있습니다. 배포가 완료되면 다음 단계로 진행할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltee295a3516338302/6a17f65b414c640deb94531d/a7ff94b72d337cde892165d744b9f42fba702a87-1440x649.png" alt="훈련된 모델 확인" /><h2>데이터 인덱싱</h2><p><a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor/books.zip">여기에서</a> 데이터 세트를 다운로드한 다음 Kibana를 사용하여 데이터를 가져올 수 있습니다. 이렇게 하려면 홈페이지로 이동하여 "데이터 업로드" 를 클릭합니다. 그런 다음 파일을 업로드하고 <code>Import</code> 을 클릭합니다. 마지막으로 <code>Advanced</code> 탭으로 이동하여 다음 매핑을 붙여넣습니다:</p>{
   "properties":{
      "authors":{
         "type":"keyword"
      },
      "categories":{
         "type":"keyword"
      },
      "longDescription":{
         "type":"semantic_text",
         "inference_id":"my-elser-model",
         "model_settings":{
            "task_type":"sparse_embedding"
         }
      },
      "pageCount":{
         "type":"integer"
      },
      "publishedDate":{
         "type":"date"
      },
      "shortDescription":{
         "type":"text"
      },
      "status":{
         "type":"keyword"
      },
      "thumbnailUrl":{
         "type":"keyword"
      },
      "title":{
         "type":"text"
      }
   }
}<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f07300647edd2ca/6a17f65dfaa913172a93ca0c/1aea0b9c51e275f89339f5d463fcaef799fc3943-1235x1083.png" alt="데이터 가져오기" /><p>시맨틱 및 전체 텍스트 쿼리를 실행할 수 있는 인덱스를 만들려고 합니다. <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-text.html">semantic_text</a> 필드 유형은 데이터 청크 및 임베딩을 처리합니다. <em>을 으로 색인하고 있으며, 필드를 과 text` 로</em> <em>모두 색인하려면</em> <em><code>longDescription</code></em>  <em><code>semantic_text</code></em><em>copy_to를 사용할 수</em><em><code>semantic_text</code></em> 있습니다.</p><h2>Blazor로 앱 구축 &amp; Elasticsearch</h2><h3>API 키</h3><p>가장 먼저 해야 할 일은 Elasticsearch에 대한 요청을 인증하기 위해 API 키를 생성하는 것입니다. API 키는 읽기 전용이어야 하며 <code>books-blazor</code> 인덱스에 대한 쿼리만 허용되어야 합니다.</p>POST /_security/api_key
{
  "name": "books-blazor-key",
  "role_descriptors": {
    "books-blazor-reader": {
      "indices": [
        {
          "names": ["books-blazor"],
          "privileges": ["read"]
        }
      ]
    }
  }
}<p>다음과 같은 내용이 표시됩니다:
</p>{
  "id": "XXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "books-blazor-key",
  "api_key": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "encoded": "XXXXXXXXXXXXXXXXXXXXXXXX=="
}<p><code>encoded</code> 응답 필드의 값은 나중에 필요하므로 저장합니다. <a href="https://www.elastic.co/cloud/">Elastic Cloud에서</a> 실행 중인 경우, 클라우드 ID도 필요합니다. ( <a href="https://www.elastic.co/search-labs/tutorials/install-elasticsearch/elastic-cloud#finding-your-cloud-id">여기에서</a> 찾을 수 있습니다).</p><h4>블레이저 프로젝트 만들기</h4><p>먼저 Blazor를 설치하고 <a href="https://dotnet.microsoft.com/en-us/learn/aspnet/blazor-tutorial/install">공식 지침에</a> 따라 샘플 프로젝트를 생성하세요.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb5c20b896fe1e98e/6a17f65f6317301566585bff/f5720867d960c91fd1b3c4ae9be174e062e6b2fc-975x830.png" alt="블레이저 튜토리얼 - 블레이저 프로젝트 만들기" /><p>프로젝트가 생성되면 폴더 구조와 파일은 다음과 같이 보일 것입니다:</p>BlazorApp/
|-- BlazorApp.csproj
|-- BlazorApp.sln
|-- Program.cs
|-- appsettings.Development.json
|-- appsettings.json
|-- Properties/
|   `-- launchSettings.json
|-- Components/
|   |-- App.razor
|   |-- Routes.razor
|   |-- _Imports.razor
|   |-- Layout/
|   |   |-- MainLayout.razor
|   |   |-- MainLayout.razor.css
|   |   |-- NavMenu.razor
|   |   `-- NavMenu.razor.css
|   `-- Pages/
|       |-- Counter.razor
|       |-- Error.razor
|       |-- Home.razor
|       `-- Weather.razor
|-- wwwroot/
|-- bin/
`-- obj/ <p><a href="https://blog.getbootstrap.com/2021/08/04/bootstrap-5-1-0/">템플릿</a> 애플리케이션에는 부트스트랩 v5.1.0이 포함됩니다. 스타일링을 위해.</p><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/net-api/8.0/installation.html">Elasticsearch .NET</a> 클라이언트를 설치하여 프로젝트 설정을 완료합니다:</p>dotnet add package Elastic.Clients.Elasticsearch<p>이 단계를 완료하면 페이지가 다음과 같이 표시됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3b90e394ded7ff56/6a17f660faa913d49f93ca10/ef9d2186c2cf49e78a307c4aa69c51632e84578d-940x529.png" alt="블레이저 헬로 월드" /><h3>폴더 구조</h3><p>이제 폴더를 다음과 같이 정리해 보겠습니다:</p>BlazorApp/
|-- Components/
|   |-- Pages/
|   |   |-- Search.razor
|   |   `-- Search.razor.css
|   `-- Elasticsearch/
|       |-- SearchBar.razor
|       |-- Results.razor
|       `-- Facet.razor
|-- Models/
|   |-- Book.cs
|   `-- Response.cs
`-- Services/
    `-- ElasticsearchService.cs<p>파일 설명:</p><ul><li><p>Components/Pages/Search.razor: 검색창, 결과, 필터가 포함된 메인 페이지입니다.</p></li><li><p>Components/Pages/Search.razor.css: 페이지 스타일.</p></li><li><p>Components/Elasticsearch/SearchBar.razor: 검색창 컴포넌트.</p></li><li><p>Components/Elasticsearch/Results.razor: 결과 컴포넌트.</p></li><li><p>Components/Elasticsearch/Facet.razor: 구성 요소를 필터링합니다.</p></li><li><p>Components/Svg/GlassIcon.razor: 검색 아이콘.</p></li><li><p>Components/_Imports.razor: 모든 컴포넌트를 가져옵니다.</p></li><li><p>Models/Book.cs: 여기에는 책 필드 스키마가 저장됩니다.</p></li><li><p>Models/Response.cs: 검색 결과, 패싯 및 총 조회수를 포함한 응답 스키마를 저장합니다.</p></li><li><p>Services/ElasticsearchService.cs: Elasticsearch 서비스. Elasticsearch에 대한 연결과 쿼리를 처리합니다.</p></li></ul><h4>초기 구성</h4><p>몇 가지 정리부터 시작하겠습니다.</p><p>파일을 삭제합니다:</p><ul><li><p>Components/Pages/Counter.razor</p></li><li><p>Components/Pages/Weather.razor</p></li><li><p>Components/Pages/Home.razor</p></li><li><p>컴포넌트/레이아웃/NavMenu.razor</p></li><li><p>Components/Layout/NavMenu.razor.css</p></li></ul><p><code>/Components/_Imports.razor</code> 파일을 확인하세요. 다음과 같은 가져오기 항목이 있어야 합니다:</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components<h4>프로젝트에 Elastic 통합하기</h4><p>이제 Elasticsearch 구성 요소를 가져와 보겠습니다:</p>@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using BlazorApp
@using BlazorApp.Components
@using BlazorApp.Components.Elasticsearch @* &lt;--- Add this line *@<p><code>/Components/Layout/MainLayout.razor</code> 파일에서 기본 사이드바를 제거하여 애플리케이션을 위한 더 많은 공간을 확보하겠습니다:</p>@inherits LayoutComponentBase

&lt;div class="page"&gt;
    &lt;main&gt;
        &lt;article class="content"&gt;
            @Body
        &lt;/article&gt;
    &lt;/main&gt;
&lt;/div&gt;

&lt;div id="blazor-error-ui"&gt;
    An unhandled error has occurred.
    &lt;a href="" class="reload"&gt;Reload&lt;/a&gt;
    &lt;a class="dismiss"&gt;🗙&lt;/a&gt;
&lt;/div&gt;<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt441012c5aeac85f8/6a17f661ec0f8949f15a67ac/55bfff607f9167a532f83ad074b479364a346c6e-816x472.png" alt="레이아웃에서 내비게이션 바 제거" /><p>이제 <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-8.0&amp;tabs=linux#secret-manager">사용자 비밀에</a> 대한 Elasticsearch 자격 증명을 입력해 보겠습니다:</p>dotnet user-secrets init
dotnet user-secrets set ElasticsearchCloudId "your Cloud ID"
dotnet user-secrets set ElasticsearchApiKey "your API Key"<p>이 접근 방식을 사용하면 .Net 8은 프로젝트 폴더 외부의 별도 위치에 중요한 데이터를 저장하고 <code>IConfiguration</code> 인터페이스를 사용하여 액세스할 수 있습니다. 이러한 변수는 동일한 사용자 비밀을 사용하는 모든 .Net 프로젝트에서 사용할 수 있습니다.</p><p>그런 다음 <code>Program.cs</code> 파일을 수정하여 시크릿을 읽고 Elasticsearch 클라이언트를 마운트해 보겠습니다:</p><p>먼저 필요한 라이브러리를 가져옵니다:</p>using BlazorApp.Services;
using Elastic.Clients.Elasticsearch;
using Elastic.Transport;<ul><li><p>BlazorApp.Services: Elasticsearch 서비스를 포함합니다.</p></li><li><p>Elastic.Clients.Elasticsearch: Elasticsearch 클라이언트 .Net 8 라이브러리를 가져옵니다.</p></li><li><p>Elastic.Transport: 요청을 인증하는 데 ApiKey 클래스를 사용할 수 있는 Elasticsearch 전송 라이브러리를 가져옵니다.</p></li></ul><p>둘째, <code>var app = builder.Build()</code> 줄 앞에 다음 코드를 삽입합니다:</p>// Initialize the Elasticsearch client.
builder.Services.AddScoped(sp =&gt;
{
    // Getting access to the configuration service to read the Elasticsearch credentials.
    var configuration = sp.GetRequiredService&lt;IConfiguration&gt;();
    var cloudId = configuration["ElasticsearchCloudId"];
    var apiKey = configuration["ElasticsearchApiKey"];

    if (string.IsNullOrEmpty(cloudId) || string.IsNullOrEmpty(apiKey))
    {
        throw new InvalidOperationException(
            "Elasticsearch credentials are missing in configuration."
        );
    }

    var settings = new ElasticsearchClientSettings(cloudId, new ApiKey(apiKey)).EnableDebugMode();
    return new ElasticsearchClient(settings);
});<p>이 코드는 사용자 비밀번호에서 Elasticsearch 자격 증명을 읽고 Elasticsearch 클라이언트 인스턴스를 생성합니다.</p><p>ElasticSearch 클라이언트 초기화 후, 다음 줄을 추가하여 Elasticsearch 서비스를 등록합니다:</p>builder.Services.AddScoped&lt;ElasticsearchService&gt;();<p>다음 단계는 <code>/Services/ElasticsearchService.cs</code> 파일에 검색 로직을 구축하는 것입니다:</p><p>먼저 필요한 라이브러리와 모델을 가져옵니다:</p>using BlazorApp.Models;
using Elastic.Clients.Elasticsearch;
using Elastic.Clients.Elasticsearch.QueryDsl;<p>둘째, <code>ElasticsearchService</code>, 생성자 및 변수를 추가합니다:</p>namespace BlazorApp.Services
{
    public class ElasticsearchService
    {
        private readonly ElasticsearchClient _client;

        // The logger is used to log information, warnings and errors about the Elasticsearch service and requests.
        private readonly ILogger&lt;ElasticsearchService&gt; _logger;

        public ElasticsearchService(
            ElasticsearchClient client,
            ILogger&lt;ElasticsearchService&gt; logger
        )
        {
            _client = client ?? throw new ArgumentNullException(nameof(client));
            _logger = logger;
        }
    }
}<h4>검색 구성</h4><p>이제 검색 로직을 구축해 보겠습니다:</p>private static Action&lt;RetrieverDescriptor&lt;BookDoc&gt;&gt; BuildHybridQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return retrievers =&gt;
        retrievers.Rrf(rrf =&gt;
            rrf.RankWindowSize(50)
                .RankConstant(20)
                .Retrievers(
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.MultiMatch(mm =&gt;
                                                mm.Query(searchTerm)
                                                    .Fields(
                                                        new[]
                                                        {
                                                            "title",
                                                            "shortDescription",
                                                        }
                                                    )
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        ),
                    retrievers =&gt;
                        retrievers.Standard(std =&gt;
                            std.Query(q =&gt;
                                q.Bool(b =&gt;
                                    b.Must(m =&gt;
                                            m.Semantic(sem =&gt;
                                                sem.Field("longDescription")
                                                    .Query(searchTerm)
                                            )
                                        )
                                        .Filter(filters.ToArray())
                                )
                            )
                        )
                )
        );
}

public static List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt; BuildFilters(
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = new List&lt;Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt;&gt;();

    if (selectedFacets != null)
    {
        foreach (var facet in selectedFacets)
        {
            foreach (var value in facet.Value)
            {
                var field = facet.Key.ToLower();
                if (!string.IsNullOrEmpty(field))
                {
                    filters.Add(m =&gt; m.Term(t =&gt; t.Field(new Field(field)).Value(value)));
                }
            }
        }
    }

    return filters;
}<ul><li><p><code>BuildFilters</code> 는 사용자가 선택한 패싯을 사용하여 검색 쿼리에 대한 필터를 작성합니다.</p></li><li><p><code>BuildHybridQuery</code> 는 전체 텍스트와 시맨틱 검색을 결합한 <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">하이브리드 검색</a> 쿼리를 작성합니다.</p></li></ul><p>다음으로 검색 방법을 추가합니다:</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");

        // Retrieve the hybrid query with filters applied.
        var retrieverQuery = BuildHybridQuery(searchTerm, selectedFacets);

        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Retriever(retrieverQuery)
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}

public static Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; FormatFacets(
    Elastic.Clients.Elasticsearch.Aggregations.AggregateDictionary aggregations
)
{
    var facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

    foreach (var aggregation in aggregations)
    {
        if (
            aggregation.Value
            is Elastic.Clients.Elasticsearch.Aggregations.StringTermsAggregate termsAggregate
        )
        {
            var facetName = aggregation.Key;
            var facetDictionary = ConvertFacetDictionary(
                termsAggregate.Buckets.ToDictionary(b =&gt; b.Key, b =&gt; b.DocCount)
            );
            facets[facetName] = facetDictionary;
        }
    }

    return facets;
}

private static Dictionary&lt;string, long&gt; ConvertFacetDictionary(
    Dictionary&lt;Elastic.Clients.Elasticsearch.FieldValue, long&gt; original
)
{
    var result = new Dictionary&lt;string, long&gt;();
    foreach (var kvp in original)
    {
        result[kvp.Key.ToString()] = kvp.Value;
    }
    return result;
}<ul><li><p><code>SearchBooksAsync</code>는 하이브리드 쿼리를 사용하여 검색을 수행하고 패싯을 구축하기 위한 집계가 포함된 결과를 반환합니다.</p></li><li><p><code>FormatFacets</code>는 집계 응답의 형식을 사전으로 지정합니다.</p></li><li><p><code>ConvertFacetDictionary</code>를 입력하면 패싯 사전이 더 읽기 쉬운 형식으로 변환됩니다.</p></li></ul><p>다음 단계는 검색 페이지의 결과로 인쇄될 Elasticsearch 쿼리의 <code>hits</code> 에서 반환된 데이터를 나타낼 모델을 만드는 것입니다.</p><p>먼저 <code>/Models/Book.cs</code> 파일을 만들고 다음을 추가합니다:</p>namespace BlazorApp.Models
{
    public class BookDoc
    {
        public string? Title { get; set; }
        public int? PageCount { get; set; }
        public string? PublishedDate { get; set; }
        public string? ThumbnailUrl { get; set; }
        public string? ShortDescription { get; set; }
        public LongDescription? LongDescription { get; set; }
        public string? Status { get; set; }
        public List&lt;string&gt;? Authors { get; set; }
        public List&lt;string&gt;? Categories { get; set; }
    }

    public class LongDescription
    {
        public string? Text { get; set; }
    }
}<p>그런 다음 <code>/Models/Response.cs</code> 파일에서 Elastic 응답을 설정하고 다음을 추가합니다:</p>namespace BlazorApp.Models
{
    public class ElasticResponse
    {
        public ElasticResponse()
        {
            Documents = new List&lt;BookDoc&gt;();
            Facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
        }

        public long TotalHits { get; set; }
        public List&lt;BookDoc&gt; Documents { get; set; }
        public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; Facets { get; set; }
    }
}<h4>기본 UI 구성</h4><p>다음으로 SearchBar 컴포넌트를 추가합니다. <code>/Components/Elasticsearch/SearchBar.razor</code> 파일을 열고 다음을 추가합니다:</p>@using System.Threading.Tasks

&lt;form @onsubmit="SubmitSearch"&gt;
  &lt;div class="input-group mb-3"&gt;
    &lt;input type="text" @bind-value="searchTerm" class="form-control" placeholder="Enter search term..." /&gt;
    &lt;button type="submit" class="btn btn-primary input-btn"&gt;
      &lt;span class="input-group-svg"&gt;
        Search
      &lt;/span&gt;
    &lt;/button&gt;
  &lt;/div&gt;
&lt;/form&gt;

@code {
  [Parameter]
  public EventCallback&lt;string&gt; OnSearch { get; set; }

  private string searchTerm = "";

  private async Task SubmitSearch()
  {
    await OnSearch.InvokeAsync(searchTerm);
  }
}<p>이 구성 요소에는 검색창과 검색을 수행하는 버튼이 포함되어 있습니다.</p><p>Blazor는 동일한 파일 내에서 C# 코드를 사용하여 동적으로 HTML을 생성할 수 있어 뛰어난 유연성을 제공합니다.</p><p>그 후 <code>/Components/Elasticsearch/Results.razor</code> 파일에서 검색 결과를 표시할 결과 컴포넌트를 빌드합니다:</p>@using BlazorApp.Models

@if (SearchResults != null &amp;&amp; SearchResults.Any())
{
  &lt;div class="row"&gt;
  @foreach (var result in SearchResults)
    {
      &lt;div class="col-12 mb-3"&gt;
        &lt;div class="card"&gt;
          &lt;div class="row g-0"&gt;
            &lt;div class="col-md-3 image-container"&gt;
              @if (!string.IsNullOrEmpty(result?.ThumbnailUrl))
              {
                &lt;img src="@result?.ThumbnailUrl" class="img-fluid rounded-start" alt="Thumbnail"&gt;
              }
              else
              {
                &lt;div class="placeholder"&gt;
                  @result?.Title
                &lt;/div&gt;
              }
            &lt;/div&gt;

            &lt;div class="col-md-9"&gt; &lt;!-- Adjusted to use the remaining 75% --&gt;
              &lt;div class="card-body"&gt;
                &lt;h4 class="card-title"&gt;
                  @result?.Title
                &lt;/h4&gt;

                &lt;div class="details-container"&gt;
                  &lt;div class=""&gt;

                    @if (result?.Authors?.Any() == true)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Authors: &lt;small class="text-muted"&gt;@string.Join(", ", result.Authors)&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Categories?.Any() == true)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Categories: &lt;small class="text-muted"&gt;@string.Join(", ", result.Categories)&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                  &lt;div class="numPages-status"&gt;
                    @if (result?.PageCount != null)
                    {
                      &lt;p class="card-text p-first"&gt;
                        Pages: &lt;small class="text-muted"&gt;@result.PageCount&lt;/small&gt;
                      &lt;/p&gt;
                    }

                    @if (result?.Status != null)
                    {
                      &lt;p class="card-text p-second"&gt;
                        Status: &lt;small class="text-muted"&gt;@result.Status&lt;/small&gt;
                      &lt;/p&gt;
                    }
                  &lt;/div&gt;
                &lt;/div&gt;

                &lt;div class="long-text-container"&gt;
                  &lt;p class="card-text"&gt;&lt;small class="text-muted"&gt;@result?.LongDescription?.Text&lt;/small&gt;&lt;/p&gt;
                &lt;/div&gt;
                @if (!string.IsNullOrEmpty(result?.PublishedDate))
                {
                  &lt;div class="date-container"&gt;
                    &lt;p class="card-text"&gt;
                      Published Date: &lt;small class="text-muted small-date"&gt;@FormatDate(result.PublishedDate)&lt;/small&gt;
                    &lt;/p&gt;
                  &lt;/div&gt;
                }
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    }
  &lt;/div&gt;
}
else if (SearchResults != null)
{
  &lt;p&gt;No results found.&lt;/p&gt;
}

@code {
  [Parameter]
  public List&lt;BookDoc&gt; SearchResults { get; set; } = new List&lt;BookDoc&gt;();

  private string FormatDate(string? date)
  {
    if (DateTime.TryParse(date, out DateTime parsedDate))
    {
      return parsedDate.ToString("MMMM dd, yyyy");
    }
    return "";
  }
}<p>마지막으로 검색 결과를 필터링하기 위해 패싯을 만들어야 합니다.</p><p><em>참고: 패싯은 사용자가 제품 유형, 가격대, 브랜드 등 특정 속성이나 카테고리를 기준으로 검색 결과의 범위를 좁힐 수 있는 필터입니다. 이러한 필터는 일반적으로 사용자가 검색 범위를 좁히고 관련 결과를 더 쉽게 찾을 수 있도록 클릭 가능한 옵션으로 표시되며, 종종 확인란 형태로 제공됩니다. Elasticsearch 컨텍스트에서 패싯은</em> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html"><em>집계를</em></a>사용하여 생성됩니다<em>.</em></p><p>다음 코드를 입력하여 패싯을 설정합니다. <code>/Components/Elasticsearch/Facet.razor</code> 파일에서 :</p>@if (Facets != null)
{
  &lt;div class="facets-container"&gt;
  @foreach (var facet in Facets)
    {
      &lt;h3&gt;@facet.Key&lt;/h3&gt;
      @foreach (var option in facet.Value)
      {
        &lt;div&gt;
          &lt;input type="checkbox" checked="@IsFacetSelected(facet.Key, option.Key)"
            @onclick="() =&gt; ToggleFacet(facet.Key, option.Key)" /&gt;
          @option.Key (@option.Value)
        &lt;/div&gt;
      }
    }
  &lt;/div&gt;
}


@code {
  [Parameter]
  public Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;? Facets { get; set; }

  [Parameter]
  public EventCallback&lt;Dictionary&lt;string, List&lt;string&gt;&gt;&gt; OnFacetChanged { get; set; }

  private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new();

  private void ToggleFacet(string facetName, string facetValue)
  {
    if (!selectedFacets.TryGetValue(facetName, out var facetValues))
    {
      facetValues = selectedFacets[facetName] = new List&lt;string&gt;();
    }

    if (!facetValues.Remove(facetValue))
    {
      facetValues.Add(facetValue);
    }

    OnFacetChanged.InvokeAsync(selectedFacets);
  }

  private bool IsFacetSelected(string facetName, string facetValue)
  {
    return selectedFacets.ContainsKey(facetName) &amp;&amp; selectedFacets[facetName].Contains(facetValue);
  }
}<p>이 구성 요소는 <code>author</code>, <code>categories</code>, <code>status</code> 필드에 있는 <code>terms</code> 집계에서 읽은 다음 필터 목록을 생성하여 Elasticsearch로 다시 보냅니다.</p><p>이제 모든 것을 종합해 보겠습니다.</p><p><code>/Components/Pages/Search.razor</code> 파일에서:</p>@page "/"
@rendermode InteractiveServer
@using BlazorApp.Models
@using BlazorApp.Services
@inject ElasticsearchService ElasticsearchService
@inject ILogger&lt;Search&gt; Logger

&lt;PageTitle&gt;Search&lt;/PageTitle&gt;

&lt;div class="top-row px-4 "&gt;

    &lt;div class="searchbar-container"&gt;
        &lt;h4&gt;Semantic Search with Elasticsearch and Blazor&lt;/h4&gt;

        &lt;SearchBar OnSearch="PerformSearch" /&gt;
    &lt;/div&gt;

    &lt;a href="https://www.elastic.co/search-labs/esre-with-blazor" target="_blank"&gt;About&lt;/a&gt;
&lt;/div&gt;

&lt;div class="px-4"&gt;

    &lt;div class="search-details-container"&gt;
        &lt;p role="status"&gt;Current search term: @currentSearchTerm&lt;/p&gt;
        &lt;p role="status"&gt;Total results: @totalResults&lt;/p&gt;
    &lt;/div&gt;

    &lt;div class="results-facet-container"&gt;
        &lt;div class="facets-container"&gt;
            &lt;Facet Facets="facets" OnFacetChanged="OnFacetChanged" /&gt;
        &lt;/div&gt;
        &lt;div class="results-container"&gt;
            &lt;Results SearchResults="searchResults" /&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

@code {
    private string currentSearchTerm = "";
    private long totalResults = 0;
    private List&lt;BookDoc&gt; searchResults = new List&lt;BookDoc&gt;();
    private Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt; facets = new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();
    private Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets = new Dictionary&lt;string, List&lt;string&gt;&gt;();

    protected override async Task OnInitializedAsync()
    {
        await PerformSearch();
    }

    private async Task PerformSearch(string searchTerm = "")
    {
        try
        {
            currentSearchTerm = searchTerm;

            var response = await ElasticsearchService.SearchBooksAsync(currentSearchTerm, selectedFacets);
            if (response != null)
            {
                searchResults = response.Documents;
                facets = response.Facets;
                totalResults = response.TotalHits;
            }
            else
            {
                Logger.LogWarning("Search response is null.");
            }

            StateHasChanged();
        }
        catch (Exception ex)
        {
            Logger.LogError(ex, "Error performing search.");
        }
    }

    private async Task OnFacetChanged(Dictionary&lt;string, List&lt;string&gt;&gt; newSelectedFacets)
    {
        selectedFacets = newSelectedFacets;
        await PerformSearch(currentSearchTerm);
    }
}<p>페이지가 작동 중입니다!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc750e55fff43de4b/6a17f6636864a4ab08b68931/5f0f25cb029a34df7f577a9f307278226ed07c3f-816x473.png" alt="블레이저 페이지 예시" /><p>보시다시피 이 페이지는 기능적이지만 스타일이 부족합니다. 좀 더 체계적이고 반응이 빠른 CSS를 추가해 보겠습니다.</p><p>레이아웃 스타일 교체를 시작해 보겠습니다. <code>Components/Layout/MainLayout.razor.css</code> 파일에서:</p>.page {
  position: relative;
  display: flex;
  flex-direction: column;
}

main {
  flex: 1;
}

#blazor-error-ui {
  background: lightyellow;
  bottom: 0;
  box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
  display: none;
  left: 0;
  padding: 0.6rem 1.25rem 0.7rem 1.25rem;
  position: fixed;
  width: 100%;
  z-index: 1000;
}

#blazor-error-ui .dismiss {
  cursor: pointer;
  position: absolute;
  right: 0.75rem;
  top: 0.5rem;
}<p><code>Components/Pages/Search.razor.css</code> 파일에 검색 페이지의 스타일을 추가합니다:</p>.input-group .input-group-svg {
  background: transparent;
  border: transparent;
  pointer-events: none;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.search-details-container {
  display: flex;
  justify-content: space-between;
  margin-top: 1rem;
}

.searchbar-container {
  padding-top: 2rem;
  display: flex;
  flex-direction: column; 
  flex-grow: 1;
  height: 100%;
  max-width: 100%; 
}

.searchbar-container h4 {
  margin: 0;
}

.top-row {
  margin-top: -1.1rem;
  position: relative; 
  background-color: hsl(216, 29%, 67%);
  border-bottom: 1px solid #d6d5d5;
  display: flex;
  align-items: center;
  height: 100%;
  padding: 0 1rem;
}

.top-row a {
  margin-left: auto;
  margin-top: -4rem; 
  color: #000000;
  text-decoration: none;
}

.top-row a:hover {
  text-decoration: underline;
}

@media (max-width: 640.98px) {
  .top-row {
    justify-content: space-between;
  }

  .top-row ::deep a,
  .top-row ::deep .btn-link {
    margin-left: 0;
  }
}

@media (min-width: 641px) {
  .top-row.auth ::deep a:first-child {
    flex: 1;
    text-align: right;
    width: 0;
  }

  .top-row,
  article {
    padding-left: 2rem !important;
    padding-right: 1.5rem !important;
  }
}<p>페이지가 더 보기 좋아지기 시작합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ecc49f404f18591/6a17f6654b055d959b432384/3dadd7ade2dd9070300a4e0514a4da2ae9cc9fb9-817x473.png" alt="검색 페이지에 스타일을 추가한 후 Blazor 페이지" /><p>이제 마지막 마무리를 해보겠습니다:</p><p>다음 파일을 만듭니다:</p><ul><li><p>Components/Elasticsearch/Facet.razor.css</p></li><li><p>Components/Elasticsearch/Results.razor.css</p></li></ul><p>그리고 <code>Facet.razor.css</code> 에 스타일을 추가합니다:</p>.facets-container {
  font-size: 15px;
  margin-right: 4rem;
  overflow-x: auto;
  white-space: nowrap;
  max-width: 300px;
}

.results-facet-container {
  display: flex;
  margin-top: 1rem;
  overflow-x: auto;
}

.results-facet-container &gt; * {
  flex-shrink: 0;
}<p><code>Results.razor.css</code> 의 경우 :</p>.image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  padding: 1rem;
  box-sizing: border-box;
}

.image-container img {
  max-width: 100%;
  height: auto;
  border-radius: 0.5rem;
}

.placeholder {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  width: 100%;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  font-size: 0.9rem;
  color: #888;
  text-align: center;
  padding: 1rem;
  border-radius: 0.5rem;
}

.card-body {
  padding: 1rem;
}

.details-container {
  display: flex;
  justify-content: space-between;
  padding: 1.5rem 0;
}

.date-container {
  margin-top: 1rem;
  display: flex;
  justify-content: flex-end;
}

.date-container .small-date {
  font-weight: bold;
}<p>최종 결과:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78bbd8e6f9e23988/6a17f6664b055d2ed8432388/3e68ec4a38775fdfbeaa1a990e6a1e11dfb081d8-816x472.png" alt="블레이저 앱 페이지 구축의 최종 결과" /><p>애플리케이션을 실행하려면 다음 명령을 사용할 수 있습니다:</p><p><code>dotnet watch</code></p><p>해냈어요! 이제 검색창을 사용해 Elasticsearch 색인에서 책을 검색하고 저자, 카테고리, 상태별로 결과를 필터링할 수 있습니다.</p><h3>전체 텍스트 및 시맨틱 검색 수행</h3><p>기본적으로 앱은 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-multi-match-query.html">전체 텍스트</a> 검색과 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-semantic-query.html">시맨틱 검색을</a> <a href="https://www.elastic.co/search-labs/tutorials/search-tutorial/vector-search/hybrid-search">모두</a> 사용하는 하이브리드 검색을 수행합니다. 전체 텍스트 검색과 시맨틱 검색을 위한 두 가지 방법을 별도로 만든 다음 사용자의 입력에 따라 쿼리를 작성하는 방법 중 하나를 선택하여 검색 로직을 변경할 수 있습니다.</p><p><code>/Services/ElasticsearchService.cs</code> 파일의 <code>ElasticsearchService</code> 클래스에 다음 메서드를 추가합니다:</p>private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildSemanticQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    return query =&gt;
        query.Bool(b =&gt;
            b.Must(m =&gt; m.Semantic(sem =&gt; sem.Field("longDescription").Query(searchTerm)))
                .Filter(filters.ToArray())
        );
}

private static Action&lt;QueryDescriptor&lt;BookDoc&gt;&gt; BuildMultiMatchQuery(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    var filters = BuildFilters(selectedFacets);

    if (string.IsNullOrEmpty(searchTerm))
    {
        return query =&gt; query.Bool(b =&gt; b.Filter(filters.ToArray()));
    }

    return query =&gt;
        query.Bool(b =&gt;
            b.Should(m =&gt;
                    m.MultiMatch(mm =&gt;
                        mm.Query(searchTerm).Fields(new[] { "title", "shortDescription" })
                    )
                )
                .Filter(filters.ToArray())
        );
}<p>두 방법 모두 <code>BuildHybridQuery</code> 방법과 유사하게 작동하지만 전체 텍스트 또는 시맨틱 검색만 수행합니다.</p><p>선택한 검색 방법을 사용하도록 <code>SearchBooksAsync</code> 방법을 수정할 수 있습니다:</p>public async Task&lt;ElasticResponse&gt; SearchBooksAsync(
    string searchTerm,
    Dictionary&lt;string, List&lt;string&gt;&gt; selectedFacets
)
{
    try
    {
        _logger.LogInformation($"Performing search for: {searchTerm}");
        
        // Modify the query builder to use the selected search method.
        var multiMatchQuery = BuildMultiMatchQuery(searchTerm, selectedFacets); // For full text search
        var semanticQuery = BuildSemanticQuery(searchTerm, selectedFacets); // For semantic search

        // In this case we will not use retrievers, but you can add them if you want to use them.
        var response = await _client.SearchAsync&lt;BookDoc&gt;(s =&gt;
            s.Index("elastic-blazor-books")
                .Query(multiMatchQuery) // Change this line to use different search methods, for example: .Query(semanticQuery) for semantic search
                .Aggregations(aggs =&gt;
                    aggs.Add("Authors", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Authors)))
                        .Add(
                            "Categories",
                            agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Categories))
                        )
                        .Add("Status", agg =&gt; agg.Terms(t =&gt; t.Field(p =&gt; p.Status)))
                )
        );

        if (response.IsValidResponse)
        {
            _logger.LogInformation($"Found {response.Documents.Count} documents");

            var hits = response.Total;
            var facets =
                response.Aggregations != null
                    ? FormatFacets(response.Aggregations)
                    : new Dictionary&lt;string, Dictionary&lt;string, long&gt;&gt;();

            var elasticResponse = new ElasticResponse
            {
                TotalHits = hits,
                Documents = response.Documents.ToList(),
                Facets = facets,
            };

            return elasticResponse;
        }
        else
        {
            _logger.LogWarning($"Invalid response: {response.DebugInformation}");
            return new ElasticResponse();
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error performing search");
        return new ElasticResponse();
    }
}<p>전체 신청서는 <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/esre-with-blazor">여기에서</a>확인할 수 있습니다.</p><h2>결론</h2><p>Blazor는 C#을 사용하여 웹 애플리케이션을 빌드할 수 있는 효과적인 프레임워크입니다. Elasticsearch는 검색 애플리케이션을 구축할 수 있는 강력한 검색 엔진입니다. 이 두 가지를 결합하면 강력한 검색 애플리케이션을 쉽게 구축할 수 있으며, ESRE의 강력한 기능을 활용하여 단시간에 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search.html">시맨틱 검색 환경을</a> 만들 수 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/search-app-with-esre-blazor</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[.NET]]></category>
    <dc:creator><![CDATA[Gustavo Llermaly]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte7424ac5f0b223b4/6a17f668414c641971945323/7ba0d6bec908bfcae966b7f38626fabd682c6f3d-1200x628.png" length="0" type="image/png"/>
    <pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch를 임베딩 저장소로 활용한 LangChain4j]]></title>
    <description><![CDATA[LangChain4j(Java용 LangChain)에는 임베딩 저장소로 Elasticsearch가 있습니다. 이를 사용하여 일반 Java로 RAG 애플리케이션을 빌드하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>
<a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">이전 포스트에서</a> LangChain4j가 무엇이며 어떻게 사용하는지 알아보았습니다:</p><ul><li><p><code>ChatLanguageModel</code> 를 구현하여 LLM과 토론하고 <code>ChatMemory</code></p></li><li><p>메모리에 채팅 기록을 유지하여 LLM과의 이전 토론의 맥락을 기억할 수 있습니다.</p></li></ul><p>이 블로그 게시물에서는 그 방법을 다룹니다:</p><ul><li><p>텍스트 예제에서 벡터 임베딩 만들기</p></li><li><p>Elasticsearch 임베딩 스토어에 벡터 임베딩 저장하기 </p></li><li><p>유사한 벡터 검색</p></li></ul><h2>임베딩 만들기</h2><p>임베딩을 만들려면 사용할 <code>EmbeddingModel</code> 을 정의해야 합니다. 예를 들어 <a href="https://www.elastic.co/search-labs/blog/langchain4j-llm-integration-introduction">이전 게시물에서</a> 사용한 것과 동일한 미스트랄 모델을 사용할 수 있습니다. 올라마와 함께 실행 중이었습니다:</p>EmbeddingModel model = OllamaEmbeddingModel.builder()
  .baseUrl(ollama.getEndpoint())
  .modelName(MODEL_NAME)
  .build();<p>모델은 텍스트에서 벡터를 생성할 수 있습니다. 여기에서 모델에서 생성된 차원 수를 확인할 수 있습니다:</p>Logger.info("Embedding model has {} dimensions.", model.dimension());
// This gives: Embedding model has 4096 dimensions.<p>텍스트에서 벡터를 생성하려면 다음을 사용할 수 있습니다:</p>Response&lt;Embedding&gt; response = model.embed("A text here");<p>또는 텍스트, 가격, 출시일 등을 필터링할 수 있도록 메타데이터를 제공하려는 경우 <code>Metadata.from()</code> 을 사용할 수 있습니다. 예를 들어, 여기에 게임 이름을 메타데이터 필드로 추가합니다:</p>TextSegment game1 = TextSegment.from("""
    The game starts off with the main character Guybrush Threepwood stating "I want to be a pirate!"
    To do so, he must prove himself to three old pirate captains. During the perilous pirate trials, 
    he meets the beautiful governor Elaine Marley, with whom he falls in love, unaware that the ghost pirate 
    LeChuck also has his eyes on her. When Elaine is kidnapped, Guybrush procures crew and ship to track 
    LeChuck down, defeat him and rescue his love.
""", Metadata.from("gameName", "The Secret of Monkey Island"));
Response&lt;Embedding&gt; response1 = model.embed(game1);
TextSegment game2 = TextSegment.from("""
    Out Run is a pseudo-3D driving video game in which the player controls a Ferrari Testarossa 
    convertible from a third-person rear perspective. The camera is placed near the ground, simulating 
    a Ferrari driver's position and limiting the player's view into the distance. The road curves, 
    crests, and dips, which increases the challenge by obscuring upcoming obstacles such as traffic 
    that the player must avoid. The object of the game is to reach the finish line against a timer.
    The game world is divided into multiple stages that each end in a checkpoint, and reaching the end 
    of a stage provides more time. Near the end of each stage, the track forks to give the player a 
    choice of routes leading to five final destinations. The destinations represent different 
    difficulty levels and each conclude with their own ending scene, among them the Ferrari breaking 
    down or being presented a trophy.
""", Metadata.from("gameName", "Out Run"));
Response&lt;Embedding&gt; response2 = model.embed(game2);<p>이 코드를 실행하려면 <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step5EmbedddingsTest.java">Step5EmbedddingsTest.java</a> 클래스를 확인하세요.</p><h2>벡터를 저장하기 위해 Elasticsearch 추가하기</h2><p>LangChain4j는 인메모리 임베딩 스토어를 제공합니다. 간단한 테스트를 실행할 때 유용합니다:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore = new InMemoryEmbeddingStore&lt;&gt;();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>하지만 이 데이터스토어는 모든 것을 메모리에 저장하고 서버에 무한한 메모리가 없기 때문에 훨씬 더 큰 데이터세트에서는 이 방법이 작동하지 않을 수 있습니다. 따라서 임베딩을 Elasticsearch에 저장하는 대신 "elastic" 데이터와 함께 확장 및 축소할 수 있습니다. 이를 위해 프로젝트에 Elasticsearch를 추가해 보겠습니다:</p>&lt;dependency&gt;
  &lt;groupId&gt;dev.langchain4j&lt;/groupId&gt;
  &lt;artifactId&gt;langchain4j-elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;${langchain4j.version}&lt;/version&gt;
&lt;/dependency&gt;

&lt;dependency&gt;
  &lt;groupId&gt;org.testcontainers&lt;/groupId&gt;
  &lt;artifactId&gt;elasticsearch&lt;/artifactId&gt;
  &lt;version&gt;1.20.1&lt;/version&gt;
  &lt;scope&gt;test&lt;/scope&gt;
&lt;/dependency&gt;<p>아시다시피, 프로젝트에 Elasticsearch TestContainers 모듈을 추가하여 테스트에서 Elasticsearch 인스턴스를 시작할 수 있도록 했습니다:</p>// Create the elasticsearch container
ElasticsearchContainer container =
  new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.15.0")
    .withPassword("changeme");

// Start the container. This step might take some time...
container.start();

// As we don't want to make our TestContainers code more complex than
// needed, we will use login / password for authentication.
// But note that you can also use API keys which is preferred.
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials("elastic", "changeme"));

// Create a low level Rest client which connects to the elasticsearch container.
client = RestClient.builder(HttpHost.create("https://" + container.getHttpHostAddress()))
  .setHttpClientConfigCallback(httpClientBuilder -&gt; {
    httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
    httpClientBuilder.setSSLContext(container.createSslContextFromCa());
    return httpClientBuilder;
  })
  .build();

// Check the cluster is running
client.performRequest(new Request("GET", "/"));<p>Elasticsearch를 임베딩 저장소로 사용하려면 "" 에서 LangChain4j 인메모리 데이터 저장소에서 Elasticsearch 데이터 저장소로 전환하기만 하면 됩니다:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>이렇게 하면 벡터가 Elasticsearch의 <code>default</code> 인덱스에 저장됩니다. 인덱스 이름을 더 의미 있는 이름으로 변경할 수도 있습니다:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .indexName("games")
    .restClient(client)
    .build();
embeddingStore.add(response1.content(), game1);
embeddingStore.add(response2.content(), game2);<p>이 코드를 실행하고 싶으시다면 <a href="https://github.com/dadoonet/langchain4j-demo/blob/main/src/test/java/fr/pilato/demo/Step6ElasticsearchEmbedddingsTest.java">Step6ElasticsearchEmbedddingsTest.java</a> 클래스를 확인하세요.</p><h2>유사한 벡터 검색</h2><p>유사한 벡터를 검색하려면 먼저 이전에 사용한 것과 동일한 모델을 사용하여 질문을 벡터 표현으로 변환해야 합니다. 이미 그렇게 했기 때문에 다시 하는 것은 어렵지 않습니다. 이 경우 메타데이터는 필요하지 않습니다:</p>String question = "I want to pilot a car";
Embedding questionAsVector = model.embed(question).content();<p>이 질문의 표현으로 검색 요청을 작성하고 임베딩 스토어에 첫 번째 상위 벡터를 찾도록 요청할 수 있습니다:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>이제 결과를 반복하여 메타데이터에서 가져온 게임 이름 및 점수와 같은 일부 정보를 인쇄할 수 있습니다:</p>result.matches().forEach(m -&gt; Logger.info("{} - score [{}]",
  m.embedded().metadata().getString("gameName"), m.score()));<p>예상한 대로 "아웃런" 이 첫 번째 히트작이 됩니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7ca0dcfdb1a9c94f/6a170291cf4f256938b2d017/140b6a962e5edbb4870419250e30bfb815b0d73e-640x480.gif" alt="아웃 런" />Out Run - score [0.86672974]
The Secret of Monkey Island - score [0.85569763]<p>이 코드를 실행하고 싶으시다면 <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L110-L129">Step7SearchForVectorsTest.java</a> 클래스를 확인하세요. </p><h2>비하인드 스토리</h2><p>Elasticsearch 임베딩 저장소의 기본 구성은 백그라운드에서 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-knn-query.html">대략적인 kNN 쿼리를</a> 사용하고 있습니다.</p>POST games/_search
{
  "query" : {
    "knn": {
      "field": "vector",
      "query_vector": [-0.019137882, /* ... */, -0.0148779955]
    }
  }
}<p>하지만 임베딩 스토어에 기본 설정(<code>ElasticsearchConfigurationKnn</code>)과 다른 설정(<code>ElasticsearchConfigurationScript</code>)을 제공하면 변경할 수 있습니다:</p>EmbeddingStore&lt;TextSegment&gt; embeddingStore =
  ElasticsearchEmbeddingStore.builder()
    .configuration(ElasticsearchConfigurationScript.builder().build())
    .indexName("games")
    .restClient(client)
    .build();<p><code>ElasticsearchConfigurationScript</code> 구현은 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html"><code>script_score</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html">함수를</a> 사용하여 쿼리를 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"><code>cosineSimilarity</code></a> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.15/query-dsl-script-score-query.html#vector-functions-cosine"></a>백그라운드에서 실행합니다.</p><p>기본적으로 전화할 때입니다:</p>EmbeddingSearchResult&lt;TextSegment&gt; result = embeddingStore.search(
  EmbeddingSearchRequest.builder()
    .queryEmbedding(questionAsVector)
    .build());<p>이제 호출합니다:</p>POST games/_search
{
  "query": {
    "script_score": {
      "script": {
        "source": "(cosineSimilarity(params.query_vector, 'vector') + 1.0) / 2",
        "params": {
          "queryVector": [-0.019137882, /* ... */, -0.0148779955]
        }
      }
    }
  }
}<p>이 경우 "주문" 결과는 변경되지 않지만 <code>cosineSimilarity</code> 호출은 근사치를 사용하지 않고 일치하는 각 벡터에 대한 코사인을 계산하기 때문에 점수만 조정됩니다:</p>Out Run - score [0.871952]
The Secret of Monkey Island - score [0.86380446]<p>이 코드를 실행하고 싶으시다면 <a href="https://github.com/dadoonet/langchain4j-demo/blob/9ec4b1d4c7c69821f143ddf272bbfed273c67b14/src/test/java/fr/pilato/demo/Step7SearchForVectorsTest.java#L132-L155">Step7SearchForVectorsTest.java</a> 클래스를 확인하세요.</p><h2>결론</h2><p>텍스트에서 임베딩을 얼마나 쉽게 생성할 수 있는지, 그리고 두 가지 다른 접근 방식을 사용하여 Elasticsearch에서 가장 가까운 이웃을 저장하고 검색하는 방법에 대해 알아보았습니다:</p><ul><li><p>기본값 <code>ElasticsearchConfigurationKnn</code> 옵션과 함께 대략적이고 빠른 <code>knn</code> 쿼리 사용</p></li><li><p><code>ElasticsearchConfigurationScript</code> 옵션과 함께 정확하지만 느린 <code>script_score</code> 쿼리 사용</p></li></ul><p>다음 단계는 여기서 배운 내용을 바탕으로 전체 RAG 애플리케이션을 구축하는 것입니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/langchain4j-elasticsearch-embedding-store</guid>
    <category><![CDATA[Java]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[David Pilato]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfc873b86c76d1798/6a170293acf088f666be99b3/abd8a4a809064101c037af66b87f28e5ecde03b0-1474x645.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 08 Oct 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[고급 RAG 기술 2부: 쿼리 및 테스트]]></title>
    <description><![CDATA[RAG 성능을 향상시킬 수 있는 기술을 논의하고 구현합니다. 2부 2부에서는 고급 RAG 파이프라인 쿼리 및 테스트에 중점을 둡니다.]]></description>
    <content:encoded><![CDATA[<p><em>모든 코드는 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>Searchlabs 리포지토리의 고급-걸레-기술 브랜치에서</em></a>찾을 수 있습니다<em>.</em></p><p>고급 RAG 기법에 대한 글 2부에 오신 것을 환영합니다! <a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1">이 시리즈의 1부에서는</a> 고급 RAG 파이프라인의 데이터 처리 구성 요소를 설정하고, 논의하고, 구현하는 방법을 살펴봤습니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="고급 RAG 파이프라인" /><p>이 부분에서는 쿼리 및 구현 테스트를 진행하겠습니다. 바로 본론으로 들어가 보겠습니다!</p><h3>목차</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#searching-and-retrieving,-generating-answers">검색 및 검색, 답변 생성</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#enriching-queries-with-synonyms">동의어로 쿼리 강화하기</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-hypothetical-document-embedding">HyDE(가상의 문서 임베딩)</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hybrid-search">하이브리드 검색</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#experiments">실험</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#summary-of-results">결과 요약</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-1-who-audits-elastic">테스트 1: 누가 Elastic을 감사하나요?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-2--total-revenue-2023">테스트 2: 총 수익 2023년</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-1">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-1">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-3-what-product-does-growth-primarily-depend-on-how-much">테스트 3: 성장은 주로 어떤 제품에 의존하나요? 얼마예요?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-2">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-2">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-4-describe-employee-benefit-plan">테스트 4: 직원 복리후생 계획 설명</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-3">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-3">SimpleRAG</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#test-5-which-companies-did-elastic-acquire">테스트 5: Elastic은 어떤 회사를 인수했나요?</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#advancedrag-4">AdvancedRAG</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#simplerag-4">SimpleRAG</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#conclusion">결론</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#appendix">부록</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#prompts">프롬프트</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#rag-question-answering-prompt">RAG 질문 답변 프롬프트</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#elastic-query-generator-prompt">Elastic 쿼리 생성기 프롬프트</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#potential-questions-generator-prompt">잠재적 질문 생성기 프롬프트</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#hyde-generator-prompt">HyDE 생성기 프롬프트</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#sample-hybrid-search-query">하이브리드 검색 쿼리 샘플</a></p></li></ul></li></ul><h2>검색 및 검색, 답변 생성</h2><p>첫 번째 질문은 주로 연례 보고서에서 찾을 수 있는 정보에 대해 물어보겠습니다. 어때요?</p>Who audits Elastic?"
<p>이제 몇 가지 기술을 적용하여 쿼리를 개선해 보겠습니다.</p><h3>동의어로 쿼리 강화하기</h3><p>먼저, 쿼리 문구의 다양성을 높이고 이를 Elasticsearch 쿼리로 쉽게 처리할 수 있는 형태로 바꿔보겠습니다. 쿼리를 OR 절의 목록으로 변환하기 위해 GPT-4o의 도움을 받겠습니다. 이 프롬프트를 작성해 보겠습니다:</p>
ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<p>쿼리에 적용하면 GPT-4o는 기본 쿼리의 동의어와 관련 어휘를 생성합니다.</p>'audits elastic OR 
elasticsearch audits OR 
elastic auditor OR 
elasticsearch auditor OR 
elastic audit firm OR 
elastic audit company OR 
elastic audit organization OR 
elastic audit service'
<p><code>ESQueryMaker</code> 클래스에서 쿼리를 분할하는 함수를 정의했습니다:</p>def parse_or_query(self, query_text: str) -&gt; List[str]:
    # Split the query by 'OR' and strip whitespace from each term
    # This converts a string like "term1 OR term2 OR term3" into a list ["term1", "term2", "term3"]
    return [term.strip() for term in query_text.split(' OR ')]
<p>이 OR 절의 문자열을 가져와 용어 목록으로 분할하여 주요 문서 필드에서 다중 일치를 수행할 수 있도록 하는 역할을 합니다:</p>["original_text", 'keyphrases', 'potential_questions', 'entities']
<p>마지막으로 이 쿼리로 마무리합니다:</p> 'query': {
    'bool': {
        'must': [
            {
                'multi_match': {
                'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
                'fields': [
                    'original_text',
                'keyphrases',
                'potential_questions',
                'entities'
                ],
                'type': 'best_fields',
                'operator': 'or'
                }
            }
      ]
<p>이렇게 하면 원래 쿼리보다 더 많은 기반을 포함하므로 동의어를 잊어버려 검색 결과가 누락되는 위험을 줄일 수 있습니다. 하지만 더 많은 일을 할 수 있습니다.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">맨 위로 돌아가기</a></p><h3>HyDE(가상의 문서 임베딩)</h3><p>이번에는 <a href="https://arxiv.org/abs/2212.10496">HyDE를</a> 구현하기 위해 GPT-4o를 다시 사용해 보겠습니다.</p><p>HyDE의 기본 전제는 가상의 문서, 즉 원래 쿼리에 대한 답변이 포함될 가능성이 있는 문서를 생성하는 것입니다. 문서의 사실 여부나 정확성은 문제가 되지 않습니다. 이를 염두에 두고 다음 프롬프트를 작성해 보겠습니다:</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<p>벡터 검색은 일반적으로 코사인 벡터 유사성을 기반으로 작동하므로, HyDE의 전제는 쿼리와 문서가 아닌 문서와 문서를 일치시킴으로써 더 나은 결과를 얻을 수 있다는 것입니다.</p><p>우리가 신경 쓰는 것은 구조, 흐름, 용어입니다. 사실과 다릅니다. GPT-4o는 다음과 같이 HyDE 문서를 출력합니다:</p>'Elastic N.V., the parent company of Elastic, the organization known for developing Elasticsearch, is subject to audits to ensure financial accuracy, 
regulatory compliance, and the integrity of its financial statements. The auditing of Elastic N.V. is typically conducted by an external, 
independent auditing firm. This is common practice for publicly traded companies to provide stakeholders with assurance regarding the company\'s 
financial position and operations.\n\nThe primary external auditor for Elastic is the audit firm Ernst &amp; Young LLP (EY). Ernst &amp; Young is one of the 
four largest professional services networks in the world, commonly referred to as the "Big Four" audit firms. These firms handle a substantial number 
of audits for major corporations around the globe, ensuring adherence to generally accepted accounting principles (GAAP) and international financial 
reporting standards (IFRS).\n\nThe audit process conducted by EY involves several steps. Initially, the auditors perform a risk assessment to identify 
areas where misstatements due to error or fraud could occur. They then design audit procedures to test the accuracy and completeness of financial statements,
 which include examining financial transactions, assessing internal controls, and reviewing compliance with relevant laws and regulations. Upon completion of 
 the audit, Ernst &amp; Young issues an audit report, which includes the auditor’s opinion on whether the financial statements are free from material misstatement 
 and are presented fairly in accordance with the applicable financial reporting framework.\n\nIn addition to external audits by firms like Ernst &amp; Young, 
 Elastic may also be subject to internal audits. Internal audits are performed by the company’s own internal auditors to evaluate the effectiveness of internal 
 controls, risk management, and governance processes.\n\nOverall, the auditing process plays a crucial role in maintaining the transparency and reliability of 
 Elastic\'s financial information, providing confidence to investors, regulators, and other stakeholders.'
<p>색인하려는 종류의 문서에 이상적인 후보처럼 꽤 그럴듯해 보입니다. 이를 임베드하여 하이브리드 검색에 사용하겠습니다.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">맨 위로 돌아가기</a></p><h3>하이브리드 검색</h3><p>이것이 바로 검색 로직의 핵심입니다. 어휘 검색 구성 요소는 생성된 OR 절 문자열이 됩니다. 고밀도 벡터 컴포넌트에는 HyDE 문서(일명 검색 벡터)가 내장됩니다. KNN을 사용하여 검색 벡터에 가장 가까운 여러 후보 문서를 효율적으로 식별합니다. 기본적으로 어휘 검색 컴포넌트를 <em>TF-IDF 및 BM25를 사용한 점수화라고</em> 부릅니다. 마지막으로, 어휘와 밀도 벡터 점수는 <a href="https://arxiv.org/abs/2407.01219">Wang 등이</a> 권장하는 30/70 비율을 사용하여 결합됩니다.</p>def hybrid_vector_search(self, index_name: str, query_text: str, query_vector: List[float], 
                         text_fields: List[str], vector_field: str, 
                         num_candidates: int = 100, num_results: int = 10) -&gt; Dict:
    """
    Perform a hybrid search combining text-based and vector-based similarity.

    Args:
        index_name (str): The name of the Elasticsearch index to search.
        query_text (str): The text query string, which may contain 'OR' separated terms.
        query_vector (List[float]): The query vector for semantic similarity search.
        text_fields (List[str]): List of text fields to search in the index.
        vector_field (str): The name of the field containing document vectors.
        num_candidates (int): Number of candidates to consider in the initial KNN search.
        num_results (int): Number of final results to return.

    Returns:
        Dict: A tuple containing the Elasticsearch response and the search body used.
    """
    try:
        # Parse the query_text into a list of individual search terms
        # This splits terms separated by 'OR' and removes any leading/trailing whitespace
        query_terms = self.parse_or_query(query_text)

        # Construct the search body for Elasticsearch
        search_body = {
            # KNN search component for vector similarity
            "knn": {
                "field": vector_field,  # The field containing document vectors
                "query_vector": query_vector,  # The query vector to compare against
                "k": num_candidates,  # Number of nearest neighbors to retrieve
                "num_candidates": num_candidates  # Number of candidates to consider in the KNN search
            },
            "query": {
                "bool": {
                    # The 'must' clause ensures that matching documents must satisfy this condition
                    # Documents that don't match this clause are excluded from the results
                    "must": [
                        {
                            # Multi-match query to search across multiple text fields
                            "multi_match": {
                                "query": " ".join(query_terms),  # Join all query terms into a single space-separated string
                                "fields": text_fields,  # List of fields to search in
                                "type": "best_fields",  # Use the best matching field for scoring
                                "operator": "or"  # Match any of the terms (equivalent to the original OR query)
                            }
                        }
                    ],
                    # The 'should' clause boosts relevance but doesn't exclude documents
                    # It's used here to combine vector similarity with text relevance
                    "should": [
                        {
                            # Custom scoring using a script to combine vector and text scores
                            "script_score": {
                                "query": {"match_all": {}},  # Apply this scoring to all documents that matched the 'must' clause
                                "script": {
                                    # Script to combine vector similarity and text relevance
                                    "source": """
                                    # Calculate vector similarity (cosine similarity + 1)
                                    # Adding 1 ensures the score is always positive
                                    double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;
                                    # Get the text-based relevance score from the multi_match query
                                    double text_score = _score;
                                    # Combine scores: 70% vector similarity, 30% text relevance
                                    # This weighting can be adjusted based on the importance of semantic vs keyword matching
                                    return 0.7 * vector_score + 0.3 * text_score;
                                    """,
                                    # Parameters passed to the script
                                    "params": {
                                        "query_vector": query_vector,  # Query vector for similarity calculation
                                        "vector_field": vector_field  # Field containing document vectors
                                    }
                                }
                            }
                        }
                    ]
                }
            }
        }

        # Execute the search request against the Elasticsearch index
        response = self.conn.search(index=index_name, body=search_body, size=num_results)
        # Log the successful execution of the search for monitoring and debugging
        logger.info(f"Hybrid search executed on index: {index_name} with text query: {query_text}")
        # Return both the response and the search body (useful for debugging and result analysis)
        return response, search_body
    except Exception as e:
        # Log any errors that occur during the search process
        logger.error(f"Error executing hybrid search on index: {index_name}. Error: {e}")
        # Re-raise the exception for further handling in the calling code
        raise e
<p>마지막으로 RAG 함수를 조합할 수 있습니다. 쿼리부터 답변까지 RAG는 이 흐름을 따릅니다:</p><ol><li><p>쿼리를 OR 절로 변환합니다.</p></li><li><p>HyDE 문서를 생성하고 임베드합니다.</p></li><li><p>둘 다 하이브리드 검색에 입력으로 전달합니다.</p></li><li><p>LLM의 컨텍스트 메모리(역 패킹) 역 패킹 예제에서 가장 관련성이 높은 점수가 "가장 최근의" 이 되도록 상위 n개의 결과를 검색하여 역 패킹합니다: 쿼리: "Elasticsearch 쿼리 최적화 기술" 검색된 문서(관련성 순으로 정렬):  LLM 컨텍스트에 대한 순서가 역순입니다:  순서를 반대로 하면 가장 관련성이 높은 정보(1)가 컨텍스트의 마지막에 표시되어 답변 생성 중에 LLM으로부터 더 많은 관심을 받을 수 있습니다.</p><ol><li><p>"부울 쿼리를 사용하여 여러 검색 기준을 효율적으로 결합할 수 있습니다."</p></li><li><p>"캐싱 전략을 구현하여 쿼리 응답 시간을 개선하세요."</p></li><li><p>"더 빠른 검색 성능을 위해 인덱스 매핑을 최적화하세요."</p></li><li><p>"더 빠른 검색 성능을 위해 인덱스 매핑을 최적화하세요."</p></li><li><p>"캐싱 전략을 구현하여 쿼리 응답 시간을 개선하세요."</p></li><li><p>"부울 쿼리를 사용하여 여러 검색 기준을 효율적으로 결합할 수 있습니다."</p></li></ol></li><li><p>생성할 컨텍스트를 LLM에 전달합니다.</p></li></ol>def get_context(index_name, 
                match_query, 
                text_query, 
                fields, 
                num_candidates=100, 
                num_results=20, 
                text_fields=["original_text", 'keyphrases', 'potential_questions', 'entities'], 
                embedding_field="primary_embedding"):

    embedding=embedder.get_embeddings_from_text(text_query)

    results, search_body = es_query_maker.hybrid_vector_search(
        index_name=index_name,
        query_text=match_query,
        query_vector=embedding[0][0],
        text_fields=text_fields,
        vector_field=embedding_field,
        num_candidates=num_candidates,
        num_results=num_results
    )

    # Concatenates the text in each 'field' key of the search result objects into a single block of text.
    context_docs=['\n\n'.join([field+":\n\n"+j['_source'][field] for field in fields]) for j in results['hits']['hits']]

    # Reverse Packing to ensure that the highest ranking document is seen first by the LLM.
    context_docs.reverse()
    return context_docs, search_body

def retrieval_augmented_generation(query_text):
    match_query= gpt4o.generate_query(query_text)
    fields=['original_text']

    hyde_document=gpt4o.generate_HyDE(query_text)

    context, search_body=get_context(index_name, match_query, hyde_document, fields)

    answer= gpt4o.basic_qa(query=query_text, context=context)
    return answer, match_query, hyde_document, context, search_body

<p>쿼리를 실행하여 답변을 확인해 보겠습니다:</p>According to the context, Elastic N.V. is audited by an independent registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of independent registered public accounting firm," which states:

"We have audited the accompanying consolidated balance sheets of Elastic N.V. [...] / s / pricewaterhouseco."
<p>멋지네요. 맞습니다.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">맨 위로 돌아가기</a></p><h2>실험</h2><p>지금 대답해야 할 중요한 질문이 있습니다. 이러한 구현에 많은 노력과 추가적인 복잡성을 투자하여 얻은 것은 무엇일까요?</p><p>간단한 비교를 해보겠습니다. 개선 사항 없이 기본 하이브리드 검색과 비교하여 구현한 RAG 파이프라인. 몇 가지 테스트를 실행하여 실질적인 차이를 발견할 수 있는지 확인해 보겠습니다. 방금 구현한 RAG를 AdvancedRAG라고 하고 기본 파이프라인을 SimpleRAG라고 하겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" alt="간단한 RAG 파이프라인" /><h4>결과 요약</h4><p>이 표에는 두 RAG 파이프라인에 대한 5가지 테스트 결과가 요약되어 있습니다. 답변의 세부 사항과 품질을 기준으로 각 방법의 상대적 우열을 판단했지만 이는 전적으로 주관적인 판단입니다. 이 표 아래에 실제 답변이 재현되어 있으므로 참고하시기 바랍니다. 그럼 이제 그 결과를 살펴보도록 하겠습니다!</p><p>SimpleRAG는 질문 1에 답할 수 없었습니다 &amp; 5. AdvancedRAG는 질문 2, 3, 4에 대해서도 훨씬 더 자세히 설명했습니다. 더 자세히 설명한 것을 바탕으로 저는 AdvancedRAG의 답변 품질이 더 좋다고 판단했습니다.</p><p>테스트</p><p>질문</p><p>고급RAG 성능</p><p>SimpleRAG 성능</p><p>AdvancedRAG 지연 시간</p><p>SimpleRAG 지연 시간</p><p>우승자</p><p>1</p><p>누가 Elastic을 감사하나요?</p><p>감사인으로 PwC를 올바르게 식별했습니다.</p><p>감사자를 식별하지 못했습니다.</p><p>11.6s</p><p>4.4s</p><p>AdvancedRAG</p><p>2</p><p>2023년 총 수익은 얼마였나요?</p><p>정확한 수익 수치를 제공했습니다. 전년도 수익에 대한 추가 컨텍스트가 포함되어 있습니다.</p><p>정확한 수익 수치를 제공했습니다.</p><p>13.3s</p><p>2.8s</p><p>AdvancedRAG</p><p>3</p><p>성장은 주로 어떤 제품에 의존하나요? 얼마예요?</p><p>핵심 동인으로 Elastic Cloud를 올바르게 식별했습니다. 전체 수익 컨텍스트 포함 &amp; 더 자세한 내용.</p><p>핵심 동인으로 Elastic Cloud를 올바르게 식별했습니다.</p><p>14.1s</p><p>12.8s</p><p>AdvancedRAG</p><p>4</p><p>직원 복리후생 계획 설명</p><p>퇴직 계획, 건강 프로그램 및 기타 혜택에 대한 포괄적인 설명을 제공합니다. 연도별 구체적인 기부 금액이 포함되어 있습니다.</p><p>보상, 퇴직 계획, 근무 환경, Elastic Cares 프로그램을 포함한 복리후생에 대한 좋은 개요를 제공했습니다.</p><p>26.6s</p><p>11.6s</p><p>AdvancedRAG</p><p>5</p><p>Elastic은 어떤 회사를 인수했나요?</p><p>보고서에 언급된 최근 인수를 올바르게 나열했습니다(CmdWatch, 빌드 시큐리티, 옵티마이즈). 일부 인수 날짜와 구매 가격을 제공했습니다.</p><p>제공된 컨텍스트에서 관련 정보를 검색하지 못했습니다.</p><p>11.9s</p><p>2.7s</p><p>AdvancedRAG</p><h4>테스트 1: 누가 Elastic을 감사하나요?</h4><p>AdvancedRAG</p>According to the context, Elastic N.V. is audited by an independent 
registered public accounting firm, PricewaterhouseCoopers (PwC). 
This information is found in the section titled "report of 
independent registered public accounting firm," which states:
"We have audited the accompanying consolidated balance sheets 
of Elastic N.V. [...] / s / pricewaterhouseco."
<p>SimpleRAG</p>According to the provided context, no specific auditing firm 
or company is mentioned as the auditor for Elastic. 
The context provided talks about various features, strengths, 
deployment options, and other details related to Elastic and 
its operations but does not specify which auditing firm is 
responsible for auditing Elastic's financial statements or 
internal controls.
<p><strong>요약</strong>: SimpleRAG는 PWC를 감사인으로 지정하지 않았습니다.</p><p>사실 꽤 놀랍습니다. SimpleRAG 측의 검색 실패로 보입니다. 감사 관련 문서가 검색되지 않았습니다. 다음 테스트에서는 난이도를 조금 낮춰보겠습니다.</p><h4>테스트 2: 총 수익 2023년</h4><p>AdvancedRAG</p>### Total Revenue for 2023

According to the provided context, the total revenue for the 
year ended April 30, 2023, was **$1,068,989,000**. This 
information can be found several times in the provided text. 
Specifically, one part states:

"The following table sets forth our results of operations 
for the periods presented in dollars and as a percentage of 
our total revenue. ... year ended April 30, 2023, ... 
total revenue 1,068,989".

Additionally, it is mentioned here as well:

"For the years ended April 30, 2023, 2022, and 2021, 
representing year-over-year growth of 24% for the year 
ended April 30, 2023... total revenue $1,068,989".

Therefore, the total revenue for 2023 is $1.069 billion.
<p>SimpleRAG</p>According to the context provided, the total revenue for the fiscal year ended April 30, 2023, is:

$1,068,989,000 (or approximately $1.1 billion).
<p><strong>요약</strong>: 두 RAG 모두 2023년 총 수익 1,068,989,000달러라는 정답을 얻었습니다.</p><p>둘 다 바로 여기에 있었습니다. AdvancedRAG가 더 광범위한 문서를 확보한 것 같나요? 물론 답변은 더 상세하고 지난 몇 년간의 정보를 포함하고 있습니다. 개선 사항을 고려할 때 예상되는 부분이지만, 아직 확정하기에는 이르다고 할 수 있습니다.</p><p>난이도를 높여 보겠습니다.</p><h4>테스트 3: 성장은 주로 어떤 제품에 의존하나요? 얼마예요?</h4><p>AdvancedRAG</p>### Product Growth Primarily Depends On:
According to the context, "our revenue is derived primarily from 
subscriptions of search, observability and security built into 
the elastic stack." The document mentions various strategies and 
factors contributing to the company’s growth which include investment 
in "elastic cloud," "elastic stack," and the conversion of free 
users to paid subscribers.

### Quantitative Data:
- **Revenue**: The document states, "Our total revenue was $1.1 billion, 
$862.4 million, and $608.5 million for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Subscription Revenue**: "Subscriptions accounted for 92%, 93%, 
and 93% of our total revenue for the years ended April 30, 2023, 
2022, and 2021, respectively."
- **Elastic Cloud Contribution**: "For the years ended April 30, 2023, 
2022, and 2021, Elastic Cloud contributed 40%, 35%, and 27% of our 
total revenue, respectively."
- **Customer Growth**: The number of customers increased from 
"over 15,000 as of April 30, 2021," to "approximately 20,200 as of 
April 30, 2023."

### Summary:
Product growth for the company primarily depends on the "Elastic Stack" 
(which includes search, observability, and security solutions) and 
particularly the "Elastic Cloud." The Elastic Cloud's growing contribution 
to revenue highlights its significance, which grew from 27% in 2021 to 40% in 2023.
<p>SimpleRAG</p>According to the provided context, growth primarily depends on **Elastic Cloud**, 
which is described as the company's "family of cloud-based offerings." 
The context also emphasizes the significance of Elastic Cloud in achieving 
the company's long-term growth potential and its role in increasing subscription revenue.

From the context:

- "Increasing adoption of Elastic Cloud... is an important growth opportunity 
for our business. Organizations are increasingly looking for hosted 
deployment alternatives with reduced administrative burdens."
- "We believe that offering Elastic Cloud is important for achieving 
our long-term growth potential, and we expect Elastic Cloud’s contribution 
to our subscription revenue to continue to increase."

In terms of the contribution to total revenue, here are the specific figures provided:

- Elastic Cloud contributed 40% of the total revenue for the year ended April 30, 2023.
- Elastic Cloud contributed 35% of the total revenue for the year ended April 30, 2022.
- Elastic Cloud contributed 27% of the total revenue for the year ended April 30, 2021.

This progression shows a clear upward trend in the importance and 
contribution of Elastic Cloud to the company's overall revenue.
<p><strong>요약</strong>: 두 RAG 모두 Elastic Cloud를 핵심 성장 동력으로 정확하게 파악했습니다. 그러나 AdvancedRAG에는 구독 수익과 고객 성장을 고려한 더 자세한 내용이 포함되어 있으며, 다른 Elastic 제품에 대해서도 명시적으로 언급하고 있습니다.</p><h4>테스트 4: 직원 복리후생 계획 설명</h4><p>AdvancedRAG</p>### Employee Benefit Plan Overview

The provided context outlines various elements of the employee benefit plan at 
Elastic, often referred to as "Elasticians." Here are the key components 
described in the texts:

1. **401(k) Plan**:
   - **Coverage**: The 401(k) plan is available to substantially all U.S. 
   employees who meet minimum age and service requirements.
   - **Contributions**: Elastic makes contributions to the 401(k) plan up to 
   6% of the participating employee’s W-2 earnings and wages.
   - **Expenses**: For the fiscal years ended April 30, Elastic recorded 
   expenses of $17.9 million (2023), $15.2 million (2022), and $11.4 million (2021) 
   related to the 401(k) plan.
   - **Defined-Contribution Plans in Other Countries**: Elastic has 
   defined-contribution plans in various other countries and recorded respective 
   expenses of $9.4 million (2023), $7.2 million (2022), and $5.1 million (2021).

2. **Stock-Based Compensation**:
   - **Types of Awards**: Stock options, restricted stock units (RSUs), 
   and shares under the Employee Stock Purchase Plan (ESPP).
   - **Fair Value Measurement**: Fair value of these stock awards is 
   measured using models like Black-Scholes.
   - **Employee Stock Purchase Plan (2022 ESPP)**: 
     - Started in 2022, it allows employees to acquire ordinary 
     shares at a discount (85% of the market value at the beginning 
     or end of the offering period).
     - Offering periods are approximately six months long.

3. **Total Rewards Compensation**:
   - **Components**: Includes cash compensation as well as equity awards, 
   reflecting a comprehensive interest in the well-being of the employees.
   - **Programs**: Market competitive medical and dental programs, 
   mental health, and holistic well-being programs.
   - **Paid Time Off**: Features 16 weeks of paid leave for new 
   parents and life-planning benefits.

4. **Retirement and Income Protection Plans**:
   - **401(k) Match**: Elastic provides a dollar-for-dollar 
   match up to 6% of eligible earnings up to a plan-limit 
   maximum for U.S.-based employees.
   - **Similar Plans Outside the U.S.**: Competitive retirement 
   plans available for employees based outside the United States.

5. **Flexible Work Environment**: 
   - **Distributed Workforce**: Elastic operates with a global 
   distributed workforce model, allowing employees to work remotely 
   and providing a broad candidate pool.
   
6. **Learning and Development**:
   - **Programs**: Access to on-demand video-based learning, 
   leadership development programs, and other specific training 
   to develop managers and leaders.

7. **Employee Engagement and Surveys**:
   - **Mechanisms**: Annual employee engagement survey and 
   mid-year pulse survey.
   - **Objectives**: To monitor morale, develop action plans, 
   and maintain high engagement scores.

8. **Onboarding Program**:
   - **X-School**: An extensive new-hire orientation program to 
   help new employees meet peers and learn about the company's 
   products and solutions.

In summary, Elastic's employee benefit plan encompasses a broad 
spectrum of financial, health, well-being, and professional development 
benefits designed to support and enhance the lives of its employees. 
The plan is both competitive and comprehensive, aiming to attract,
retain, and engage its global workforce.
<p>SimpleRAG</p>The employee benefit plan at the Company includes several key 
components aimed at enhancing the overall well-being and 
professional development of employees, referred to as "Elasticians." 

1. **Compensation and Benefits:**
   - The Company provides market-competitive compensation 
   packages which typically include cash compensation and equity awards.
   - Comprehensive medical and dental programs are offered, 
   emphasizing mental health and holistic well-being.
   - Paid time off programs feature:
     - 16 weeks of paid leave for new parents.
     - Life-planning benefits.
     - Travel reimbursements for certain healthcare services.

2. **Retirement and Income Protection Plans:**
   - In the United States, employees are eligible for a 401k 
   plan with a dollar-for-dollar match by the Company up to 6% 
   of eligible earnings, subject to a plan limit.
   - Similar competitive retirement and income protection plans 
   are available for employees outside the United States.

3. **Work Environment:**
   - The Company offers a flexible work environment, allowing most 
   employees to work from anywhere as often as they would like.
   - Physical offices around the world are available for employees 
   who prefer to work on-site or need a space for face-to-face interactions.

4. **Community Involvement:**
   - The Elastic Cares Program supports charitable organizations 
   that matter to employees on both local and global levels.
   - The program includes:
     - Donation matching.
     - Providing the Company's technology for free to certain 
     nonprofit organizations.
     - Volunteer time off initiative.
<p><strong>요약</strong>: AdvancedRAG는 미국 거주 직원을 위한 401K 플랜을 언급하고 미국 외 지역의 기여금 플랜을 정의하는 등 훨씬 더 깊이 있고 자세하게 설명합니다. 또한 건강 및 웰빙 플랜에 대해서는 언급하고 있지만 SimpleRAG에서 언급하는 Elastic Cares 프로그램은 누락되어 있습니다.</p><h4>테스트 5: Elastic은 어떤 회사를 인수했나요?</h4><p>AdvancedRAG</p>Elastic acquired several companies over the years to enhance its 
technology and market presence. According to the provided context, 
the specific acquisitions include:

1. **CmdWatch Security Inc.**: Acquired on September 17, 2021. 
The total purchase consideration was $77.8 million.
2. **Build Security Ltd.**: Acquired on September 2, 2021. The 
combined purchase price for Build Security Ltd. and another acquisition was $57.2 million.
3. **Optimyze Cloud Inc.**: Acquired on November 1, 2021. Part 
of the combined purchase price mentioned above with Build Security Ltd.
4. **Endgame, Inc.**: Although the context mentions an amended 
and restated stock incentive plan, it indicates that Endgame, Inc. was 
associated with Elastic, suggesting it could have been acquired at some point.

These acquisitions are part of Elastic's strategy to 
enhance the technology underlying its security and 
observability offerings.
<p>SimpleRAG</p>The provided context does not specify any companies that Elastic has acquired. 
Therefore, based on the context, there is no information available about the companies acquired by Elastic.
<p><strong>요약</strong>: SimpleRAG가 인수에 대한 관련 정보를 검색하지 않아 답변이 실패했습니다. AdvancedRAG는 보고서에 나열된 주요 인수 기업인 CmdWatch, Build Security 및 Optimyze를 올바르게 나열합니다.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">맨 위로 돌아가기</a></p><h2>결론</h2><p>테스트 결과, 고급 기술은 제시되는 정보의 범위와 깊이를 증가시켜 잠재적으로 RAG 답변의 품질을 향상시키는 것으로 나타났습니다.</p><p>또한 <code>Which companies did Elastic acquire?</code>, <code>Who audits Elastic</code> 같은 모호한 단어로 된 질문은 AdvancedRAG에서는 정답을 맞혔지만 SimpleRAG에서는 그렇지 않았기 때문에 신뢰도가 개선될 수 있습니다.</p><p>그러나 5건 중 3건의 경우, 하이브리드 검색을 포함하지만 다른 기술은 사용하지 않는 기본 RAG 파이프라인이 대부분의 핵심 정보를 포착하는 답변을 생성했다는 점을 유념할 필요가 있습니다.</p><p>데이터 준비 및 쿼리 단계에 LLM이 통합되어 있기 때문에 AdvancedRAG의 지연 시간은 일반적으로 SimpleRAG보다 2~5배 더 길다는 점에 유의해야 합니다. 이는 상당한 비용이므로 지연 시간보다 응답 품질이 우선시되는 상황에서만 AdvancedRAG가 적합할 수 있습니다.</p><p>데이터 준비 단계에서 클로드 하이쿠나 GPT-4o-mini와 같은 더 작고 저렴한 LLM을 사용하면 상당한 지연 비용을 줄일 수 있습니다. 답변 생성을 위해 고급 모델을 저장합니다.</p><p>이는 Wang 등의 연구 결과와 일치합니다. 연구 결과에서 알 수 있듯이 개선 사항은 비교적 점진적으로 이루어졌습니다. 간단히 말해, 간단한 기본 RAG를 사용하면 저렴하고 빠르게 부팅할 수 있으면서도 괜찮은 최종 제품을 만들 수 있습니다. 저에게는 흥미로운 결론입니다. 속도와 효율성이 중요한 사용 사례의 경우 SimpleRAG가 현명한 선택입니다. 마지막 한 방울의 성능까지 끌어올려야 하는 사용 사례의 경우 AdvancedRAG에 통합된 기술이 한 가지 방법을 제시할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt56b7067a9d41d5a8/6a171119acf0886fb4be9c45/ea811706b6adc4731d90b925a9fefa0ac15901b4-1440x1060.jpg" alt="왕 파이프라인" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2#table-of-contents">맨 위로 돌아가기</a></p><h2>부록</h2><h3>프롬프트</h3><h4>RAG 질문 답변 프롬프트</h4><p>쿼리 및 컨텍스트에 따라 LLM이 답변을 생성하도록 하기 위한 프롬프트입니다.</p>BASIC_RAG_PROMPT = '''
You are an AI assistant tasked with answering questions based primarily on the provided context, while also drawing on your own knowledge when appropriate. Your role is to accurately and comprehensively respond to queries, prioritizing the information given in the context but supplementing it with your own understanding when beneficial. Follow these guidelines:

1. Carefully read and analyze the entire context provided.
2. Primarily focus on the information present in the context to formulate your answer.
3. If the context doesn't contain sufficient information to fully answer the query, state this clearly and then supplement with your own knowledge if possible.
4. Use your own knowledge to provide additional context, explanations, or examples that enhance the answer.
5. Clearly distinguish between information from the provided context and your own knowledge. Use phrases like "According to the context..." or "The provided information states..." for context-based information, and "Based on my knowledge..." or "Drawing from my understanding..." for your own knowledge.
6. Provide comprehensive answers that address the query specifically, balancing conciseness with thoroughness.
7. When using information from the context, cite or quote relevant parts using quotation marks.
8. Maintain objectivity and clearly identify any opinions or interpretations as such.
9. If the context contains conflicting information, acknowledge this and use your knowledge to provide clarity if possible.
10. Make reasonable inferences based on the context and your knowledge, but clearly identify these as inferences.
11. If asked about the source of information, distinguish between the provided context and your own knowledge base.
12. If the query is ambiguous, ask for clarification before attempting to answer.
13. Use your judgment to determine when additional information from your knowledge base would be helpful or necessary to provide a complete and accurate answer.

Remember, your goal is to provide accurate, context-based responses, supplemented by your own knowledge when it adds value to the answer. Always prioritize the provided context, but don't hesitate to enhance it with your broader understanding when appropriate. Clearly differentiate between the two sources of information in your response.

Context:
[The concatenated documents will be inserted here]

Query:
[The user's question will be inserted here]

Please provide your answer based on the above guidelines, the given context, and your own knowledge where appropriate, clearly distinguishing between the two:
'''
<h4>Elastic 쿼리 생성기 프롬프트</h4><p>동의어로 쿼리를 보강하고 OR 형식으로 변환하는 프롬프트입니다.</p>ELASTIC_SEARCH_QUERY_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating Elasticsearch query strings. Your task is to create the most effective query string for the given user question. This query string will be used to search for relevant documents in an Elasticsearch index.

Guidelines:
1. Analyze the user's question carefully.
2. Generate ONLY a query string suitable for Elasticsearch's match query.
3. Focus on key terms and concepts from the question.
4. Include synonyms or related terms that might be in relevant documents.
5. Use simple Elasticsearch query string syntax if helpful (e.g., OR, AND).
6. Do not use advanced Elasticsearch features or syntax.
7. Do not include any explanations, comments, or additional text.
8. Provide only the query string, nothing else.

For the question "What is Clickthrough Data?", we would expect a response like:
clickthrough data OR click-through data OR click through rate OR CTR OR user clicks OR ad clicks OR search engine results OR web analytics

AND operator is not allowed. Use only OR.

User Question:
[The user's question will be inserted here]

Generate the Elasticsearch query string:
'''
<h4>잠재적 질문 생성기 프롬프트</h4><p>잠재적인 질문을 생성하고 문서 메타데이터를 보강하는 프롬프트입니다.</p>RAG_QUESTION_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating questions for Retrieval-Augmented Generation (RAG) systems. Your task is to analyze a given document and create 10 diverse questions that would effectively test a RAG system's ability to retrieve and synthesize information from this document.

Guidelines:
1. Thoroughly analyze the entire document.
2. Generate exactly 10 questions that cover various aspects and levels of complexity within the document's content.
3. Create questions that specifically target:
   a. Key facts and information
   b. Main concepts and ideas
   c. Relationships between different parts of the content
   d. Potential applications or implications of the information
   e. Comparisons or contrasts within the document
4. Ensure questions require answers of varying lengths and complexity, from simple retrieval to more complex synthesis.
5. Include questions that might require combining information from different parts of the document.
6. Frame questions to test both literal comprehension and inferential understanding.
7. Avoid yes/no questions; focus on open-ended questions that promote comprehensive answers.
8. Consider including questions that might require additional context or knowledge to fully answer, to test the RAG system's ability to combine retrieved information with broader knowledge.
9. Number the questions from 1 to 10.
10. Output only the ten questions, without any additional text, explanations, or answers.

Document:
[The document content will be inserted here]

Generate 10 questions optimized for testing a RAG system based on this document:
'''
<h4>HyDE 생성기 프롬프트</h4><p>HyDE를 사용하여 가상의 문서를 생성하는 프롬프트</p>HYDE_DOCUMENT_GENERATOR_PROMPT = '''
You are an AI assistant specialized in generating hypothetical documents based on user queries. Your task is to create a detailed, factual document that would likely contain the answer to the user's question. This hypothetical document will be used to enhance the retrieval process in a Retrieval-Augmented Generation (RAG) system.

Guidelines:
1. Carefully analyze the user's query to understand the topic and the type of information being sought.
2. Generate a hypothetical document that:
   a. Is directly relevant to the query
   b. Contains factual information that would answer the query
   c. Includes additional context and related information
   d. Uses a formal, informative tone similar to an encyclopedia or textbook entry
3. Structure the document with clear paragraphs, covering different aspects of the topic.
4. Include specific details, examples, or data points that would be relevant to the query.
5. Aim for a document length of 200-300 words.
6. Do not use citations or references, as this is a hypothetical document.
7. Avoid using phrases like "In this document" or "This text discusses" - write as if it's a real, standalone document.
8. Do not mention or refer to the original query in the generated document.
9. Ensure the content is factual and objective, avoiding opinions or speculative information.
10. Output only the generated document, without any additional explanations or meta-text.

User Question:
[The user's question will be inserted here]

Generate a hypothetical document that would likely contain the answer to this query:
'''
<h3>하이브리드 검색 쿼리 샘플</h3>{'knn': {'field': 'primary_embedding',
  'query_vector': [0.4265527129173279,
   -0.1712949573993683,
   -0.042020395398139954,
   ...],
  'k': 100,
  'num_candidates': 100},
 'query': {'bool': {'must': [{'multi_match': {'query': 'audits Elastic Elastic auditing Elastic audit process Elastic compliance Elastic security audit Elasticsearch auditing Elasticsearch compliance Elasticsearch security audit',
      'fields': ['original_text',
       'keyphrases',
       'potential_questions',
       'entities'],
      'type': 'best_fields',
      'operator': 'or'}}],
   'should': [{'script_score': {'query': {'match_all': {}},
      'script': {'source': '\n                                        double vector_score = cosineSimilarity(params.query_vector, params.vector_field) + 1.0;\n                                        double text_score = _score;\n                                        return 0.7 * vector_score + 0.3 * text_score;\n                                        ',
       'params': {'query_vector': [0.4265527129173279,
         -0.1712949573993683,
         -0.042020395398139954,
        ...],
        'vector_field': 'primary_embedding'}}}}]}},
 'size': 10}
]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf605c8246989df32/6a1711178b73cbc61d18a11d/8da40067835ab8b4dc12fe52a51a6c26858ad32f-1440x1095.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 15 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[고급 RAG 기술 1부: 데이터 처리]]></title>
    <description><![CDATA[RAG 성능을 향상시킬 수 있는 기술을 논의하고 구현합니다. 2부 중 1부에서는 고급 RAG 파이프라인의 데이터 처리 및 수집 구성 요소에 초점을 맞춥니다.]]></description>
    <content:encoded><![CDATA[<p><em>고급 RAG 기법에 대한 탐험의 1부입니다. </em><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2"><em>2부를 보려면 여기를 클릭하세요!</em></a></p><p>최근 논문 ' <a href="https://arxiv.org/abs/2407.01219">검색 증강 세대의 모범 사례 찾기</a> '에서는 RAG의 모범 사례 집합으로 수렴하는 것을 목표로 다양한 RAG 향상 기술의 효과를 실증적으로 평가하고 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt671704ff06a4011d/6a170b3ea929cf2d19ae09d8/dafa7250e7c4ead4d9b4aed7c407509131929749-1440x572.png" alt="왕이 추천하는 RAG 파이프라인" /><p>검색 품질을 개선하기 위해 제안된 몇 가지 모범 사례, 즉 <strong>문장 청킹, HyDE, 리버스 패킹을</strong> 구현해 보겠습니다.</p><p>간결성을 위해 효율성 향상에 초점을 맞춘 기술 <strong>(쿼리 분류 및 요약)</strong>은 생략하겠습니다.</p><p>또한 다루지 않았지만 개인적으로 유용하고 흥미로웠던 몇 가지 기술 <strong>(메타데이터 포함, 복합 다중 필드 임베딩, 쿼리 강화)</strong>도 구현할 예정입니다.</p><p>마지막으로 간단한 테스트를 실행하여 검색 결과 및 생성된 답변의 품질이 기준선에 비해 개선되었는지 확인합니다. 시작해보자!</p><h2>RAG 개요</h2><p>RAG는 외부 지식 기반에서 정보를 검색하여 생성된 답변을 풍부하게 함으로써 LLM을 향상시키는 것을 목표로 합니다. 도메인별 정보를 제공함으로써 LLM은 학습 데이터의 범위를 벗어난 사용 사례에 빠르게 적응할 수 있으며, 미세 조정보다 훨씬 저렴하고 최신 상태로 유지하기 쉽습니다.</p><p>RAG의 품질을 개선하기 위한 조치는 일반적으로 두 가지 트랙에 중점을 둡니다:</p><ol><li><p>지식창고의 품질과 명확성을 향상시킵니다.</p></li><li><p>검색 쿼리의 범위와 구체성 개선.</p></li></ol><p>이 두 가지 조치를 통해 LLM이 관련 사실과 정보에 접근할 수 있는 확률을 높이고, 따라서 오래되었거나 관련성이 없는 자신의 지식에 의존하거나 착각할 가능성을 줄이려는 목표를 달성할 수 있습니다.</p><p>방법의 다양성은 몇 문장으로 명확히 설명하기 어렵습니다. 더 명확하게 설명하기 위해 바로 구현으로 넘어가겠습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" alt="고급 RAG 파이프라인" /><h3>목차</h3><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#overview">개요</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">목차</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#set-up">설정</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#ingesting-processing-and-embedding-documents">문서 수집, 처리 및 임베드하기</a>  </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#data-ingestion">데이터 수집</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#sentence-level-token-wise-chunking">문장 수준의 토큰 단위 청킹</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#metadata-inclusion-and-generation">메타데이터 포함 및 생성</a> </p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#keyphrases-extracted-by-textrank">TextRank로 추출한 키프레이즈</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#potential-questions-generated-by-gpt-4o">GPT-4o에서 생성된 잠재적 질문</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#entities-extracted-by-spacy">Spacy에서 추출한 엔티티</a></p></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#composite-multi-field-embeddings">복합 멀티필드 임베딩</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#indexing-to-elastic">Elastic으로 인덱싱</a></p></li></ul></li></ul></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#cat-break">고양이 휴식</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#appendix">부록</a></p><ul><li><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#definitions">정의</a></p></li></ul></li></ul><h2>설정</h2><p><em>모든 코드는 </em><a href="https://github.com/elastic/elasticsearch-labs/tree/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques"><em>Searchlabs 리포지토리에서</em></a>찾을 수 있습니다<em>.</em></p><p>먼저 해야 할 일이 있습니다. 다음이 필요합니다:</p><ol><li><p>Elastic Cloud 배포</p></li><li><p>LLM API - 이 노트북에서는 Azure OpenAI에서 GPT-4o 배포를 사용하고 있습니다.</p></li><li><p>Python 버전 3.12.4 이상</p></li></ol><p><a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/main.ipynb">main.ipynb 노트북에서</a>모든 코드를 실행할 것입니다.</p><p>계속해서 리포지토리를 git으로 복제하고 supporting-blog-content/advanced-rag-techniques로 이동한 다음 다음 명령을 실행합니다:</p># Create a new virtual environment named 'rag_env'
python -m venv rag_env

# Activate the virtual environment (for Unix-based systems)
source rag_env/bin/activate

# (For Windows)
.\rag_env\Scripts\activate

# Install packages listed in requirements.txt
pip install -r requirements.txt
<p>이 작업이 완료되면 <em>.env</em> 파일을 만듭니다. 파일을 열고 다음 필드를 채웁니다( <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/.env.example"><em>.env.example에서</em></a> 참조). 유용한 의견을 주신 공동 저자 Claude-3.5에게 감사의 마음을 전합니다.</p># Elastic Cloud: Found in the 'Deployment' page of your Elastic Cloud 
# console
ELASTIC_CLOUD_ENDPOINT=""
ELASTIC_CLOUD_ID=""

# Elastic Cloud: Created during deployment setup or in 'Security' 
# settings
ELASTIC_USERNAME=""
ELASTIC_PASSWORD=""

# Elastic Cloud: The name of the index you created in Kibana or via API
ELASTIC_INDEX_NAME=""

# Azure AI Studio: Found in 'Keys and Endpoint' section of your Azure 
# OpenAI resource
AZURE_OPENAI_KEY_1=""
AZURE_OPENAI_KEY_2=""
AZURE_OPENAI_REGION=""
AZURE_OPENAI_ENDPOINT=""

# Azure AI Studio: Found in 'Deployments' section of your Azure OpenAI 
# resource
AZURE_OPENAI_DEPLOYMENT_NAME=""

# Using BAAI/bge-small-en-v1.5 because I think it is a good balance of 
# resource efficiency and performance. 
HUGGINGFACE_EMBEDDING_MODEL="BAAI/bge-small-en-v1.5"
<p>다음으로 수집할 문서를 선택하고 문서 폴더에 넣습니다. 이 글에서는 <a href="https://s201.q4cdn.com/217177842/files/doc_downloads/OtherDocuments/2023/AnnualMeeting/Annual-Report-Fiscal-Year-2023.pdf">Elastic N.V. 연례 보고서 2023을</a> 사용하겠습니다. 꽤 도전적이고 밀도가 높은 문서로, RAG 기술을 스트레스 테스트하기에 적합합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte292dc6030d496cc/6a170b40dc55de9b03e00dfc/e513b9d67adac43da794c25a5969b893127bbbe3-1440x395.jpg" alt="Elastic 연례 보고서 2023" /><p>이제 모든 준비가 완료되었으니 인제스트로 이동해 보겠습니다. <em>main.ipynb를</em> 열고 처음 두 셀을 실행하여 모든 패키지를 가져오고 모든 서비스를 초기화합니다.</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p><h2>문서 수집, 처리 및 임베드하기</h2><h3>데이터 수집</h3><ul><li><p><em>개인적 의견: 저는 라마인덱스의 편리함에 놀랐습니다. LLM과 라마인덱스가 나오기 전에는 다양한 형식의 문서를 수집하려면 여기저기서 난해한 패키지를 수집해야 하는 번거로운 과정이 필요했습니다. 이제 단일 함수 호출로 축소되었습니다. 야생.</em></p></li></ul><p><code>SimpleDirectoryReader</code> 은 <code>directory_path.</code> 파일에 있는 모든 문서를 로드합니다. <code>.pdf</code> 파일의 경우 문서 개체 목록을 반환하는데, 저는 작업하기 쉽다고 판단하여 Python 사전으로 변환합니다.</p># llamaindex_processor.py
from llama_index.core import SimpleDirectoryReader

class LlamaIndexProcessor:
   def __init__(self):
       pass 
   
   def load_documents(self, directory_path):
       ''' 
       Load all documents in directory
       '''
       reader = SimpleDirectoryReader(input_dir=directory_path)
       return reader.load_data()

# main.ipynb
llamaindex_processor=LlamaIndexProcessor()
documents=llamaindex_processor.load_documents('./documents/')
documents=[dict(doc_obj) for doc_obj in documents]
<p>각 사전에는 <code>text</code> 필드에 주요 콘텐츠가 포함되어 있습니다. 또한 페이지 번호, 파일 이름, 파일 크기 및 유형과 같은 유용한 메타데이터도 포함되어 있습니다.</p>{
  'id_': '5f76f0b3-22d8-49a8-9942-c2bbab14f63f',
  'metadata': {'page_label': '5',
   'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_path': '/Users/han/Desktop/Projects/truckasaurus/documents/Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf',
   'file_type': 'application/pdf',
   'file_size': 3724426,
   'creation_date': '2024-07-27',
   'last_modified_date': '2024-07-27'},
   'text': 'Table of Contents\nPage\nPART I\nItem 1. Business 3\n15 Item 1A. Risk Factors\nItem 1B. Unresolved Staff Comments 48\nItem 2. Properties 48\nItem 3. Legal Proceedings 48\nItem 4. Mine Safety Disclosures 48\nPART II\nItem 5. Market for Registrant's Common Equity, Related Stockholder Matters and Issuer Purchases of \nEquity Securities49\nItem 6. [Reserved] 49\nItem 7. Management's Discussion and Analysis of Financial Condition and Results of Operations 50\nItem 7A. Quantitative and Qualitative Disclosures About Market Risk 64\nItem 8. Financial Statements and Supplementary Data 66\nItem 9. Changes in and Disagreements With Accountants on Accounting and Financial Disclosure 100\n100\n101Item 9A. Controls and Procedures\nItem 9B. Other Information\nItem 9C. Disclosure Regarding Foreign Jurisdictions That Prevent Inspections 101\nPART III\n102\n102\n102\n102Item 10. Directors, Executive Officers and Corporate Governance\nItem 11. Executive Compensation\nItem 12. Security Ownership of Certain Beneficial Owners and Management, and Related Stockholder Matters  \nItem 13. Certain Relationships and Related Transactions, and Director Independence\nItem 14. Principal Accountant Fees and Services 102\nPART IV\n103\n105Item 15. Exhibits and Financial Statement Schedules  \nItem 16. Form 10-K Summary\nSignatures 106\ni',
   ...
}
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p><h3>문장 수준의 토큰 단위 청킹</h3><p>가장 먼저 해야 할 일은 일관성과 관리 용이성을 위해 문서를 표준 길이의 덩어리로 줄이는 것입니다. 임베딩 모델에는 고유한 토큰 제한(처리할 수 있는 최대 입력 크기)이 있습니다. 토큰은 모델이 처리하는 텍스트의 기본 단위입니다. 정보 손실(내용 잘림 또는 누락)을 방지하기 위해 긴 텍스트를 더 작은 세그먼트로 분할하여 이러한 제한을 초과하지 않는 텍스트를 제공해야 합니다.</p><p>청크는 성능에 상당한 영향을 미칩니다. 이상적으로는 각 청크가 독립된 정보를 나타내며 단일 주제에 대한 맥락 정보를 캡처하는 것이 좋습니다. 청킹 방법에는 단어 수에 따라 문서를 분할하는 단어 수준 청킹과 LLM을 사용하여 논리적 중단점을 식별하는 시맨틱 청킹이 있습니다.</p><p>단어 단위 청크는 저렴하고 빠르며 쉽지만 문장이 분리되어 문맥이 깨질 위험이 있습니다. 시맨틱 청크는 특히 116페이지에 달하는 Elastic 연례 보고서와 같은 문서를 처리하는 경우 속도가 느려지고 비용이 많이 듭니다.</p><p>중간 접근 방식을 선택해 보겠습니다. 문장 수준 청킹은 여전히 간단하지만 단어 수준 청킹보다 훨씬 저렴하고 빠르면서 문맥을 더 효과적으로 보존할 수 있습니다. 또한 슬라이딩 창을 구현하여 주변 문맥을 일부 캡처하고 단락 분할의 영향을 완화할 것입니다.</p># chunker.py 

import uuid
import re


class Chunker: 
    def __init__(self, tokenizer):
        self.tokenizer = tokenizer 
    
    def split_into_sentences(self, text):
        """Split text into sentences."""
        return re.split(r'(?&lt;=[.!?])\s+', text)
 
    def sentence_wise_tokenized_chunk_documents(self, documents, chunk_size=512, overlap=20, min_chunk_size=50):
        '''
        1. Split text into sentences.
        2. Tokenize using the provided tokenizer method.
        3. Build chunks up to the chunk_size limit.
        4. Create an overlap based on tokens - to preserve context.
        5. Only keep chunks that meet the minimum token size requirement.
        '''
        chunked_documents = []

        for doc in documents:
            sentences = self.split_into_sentences(doc['text'])
            tokens = []
            sentence_boundaries = [0]

            # Tokenize all sentences and keep track of sentence boundaries
            for sentence in sentences:
                sentence_tokens = self.tokenizer.encode(sentence, add_special_tokens=True)
                tokens.extend(sentence_tokens)
                sentence_boundaries.append(len(tokens))

            # Create chunks
            chunk_start = 0
            while chunk_start &lt; len(tokens):
                chunk_end = chunk_start + chunk_size

                # Find the last complete sentence that fits in the chunk
                sentence_end = next((i for i in sentence_boundaries if i &gt; chunk_end), len(tokens))
                chunk_end = min(chunk_end, sentence_end)

                # Create the chunk
                chunk_tokens = tokens[chunk_start:chunk_end]

                # Check if the chunk meets the minimum size requirement
                if len(chunk_tokens) &gt;= min_chunk_size:
                    # Create a new document object for this chunk
                    chunk_doc = {
                        'id_': str(uuid.uuid4()),
                        'chunk': chunk_tokens,
                        'original_text': self.tokenizer.decode(chunk_tokens),
                        'chunk_index': len(chunked_documents),
                        'parent_id': doc['id_'],
                        'chunk_token_count': len(chunk_tokens)
                    }

                    # Copy all other fields from the original document
                    for key, value in doc.items():
                        if key != 'text' and key not in chunk_doc:
                            chunk_doc[key] = value

                    chunked_documents.append(chunk_doc)

                # Move to the next chunk start, considering overlap
                chunk_start = max(chunk_start + chunk_size - overlap, chunk_end - overlap)

        return chunked_documents

# main.ipynb 
# Initialize Embedding Model
HUGGINGFACE_EMBEDDING_MODEL = os.environ.get('HUGGINGFACE_EMBEDDING_MODEL')
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

# Initialize Chunker
chunker=Chunker(embedder.tokenizer)
<p><code>Chunker</code> 클래스는 임베딩 모델의 토큰라이저를 받아 텍스트를 인코딩하고 디코딩합니다. 이제 20개의 토큰이 겹치는 512개씩의 토큰 덩어리를 만들겠습니다. 이를 위해 텍스트를 문장으로 분할하고, 해당 문장을 토큰화한 다음 토큰 한도를 초과하지 않고 더 이상 추가할 수 없을 때까지 토큰화된 문장을 현재 청크에 추가합니다.</p><p>마지막으로 임베드할 원본 텍스트로 문장을 다시 디코딩하여 <code>original_text</code> 라는 필드에 저장합니다. 청크는 <code>chunk</code> 라는 필드에 저장됩니다. 노이즈(일명 쓸모없는 문서)를 줄이기 위해 길이가 50토큰보다 작은 문서는 모두 폐기합니다.</p><p>문서에서 실행해 보겠습니다:</p>chunked_documents=chunker.sentence_wise_tokenized_chunk_documents(documents, chunk_size=512)
<p>그리고 다음과 같은 텍스트 덩어리를 다시 가져옵니다:</p>print(chunked_documents[4]['original_text'])

[CLS] the aggregate market value of the ordinary shares held by non - affiliates of the registrant, 
based on the closing price of the shares of ordinary shares on the new york stock exchange on 
october 31, 2022 ( the last business day of the registrant 's second fiscal quarter ), was 
approximately $ 6. 1 billion. [SEP] [CLS] as of may 31, 2023, the registrant had 97, 390, 886 
ordinary shares, par value €0. 01 per share, outstanding. [SEP] [CLS] documents incorporated by 
reference portions of the registrant 's definitive proxy statement relating to the registrant 's 2
023 annual general meeting of shareholders are incorporated by reference into part iii of this annual 
...
...
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p><h3>메타데이터 포함 및 생성</h3><p>문서를 정리했습니다. 이제 데이터를 보강할 차례입니다. 추가 메타데이터를 생성하거나 추출하고 싶습니다. 이 추가 메타데이터는 검색 성능에 영향을 미치고 향상시키는 데 사용할 수 있습니다.</p><p>문서 목록(파이썬 사전)과 프로세서 함수 목록을 받는 역할을 하는 <code>DocumentEnricher</code> 클래스를 정의하겠습니다. 이러한 함수는 문서의 <code>original_text</code> 열을 통해 실행되고 출력을 새 필드에 저장합니다.</p><p>먼저 <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/nltk_processor.py">TextRank를</a> 사용하여 키프레이즈를 추출합니다. TextRank는 단어 간의 관계에 따라 중요도에 순위를 매겨 텍스트에서 핵심 구문과 문장을 추출하는 그래프 기반 알고리즘입니다.</p><p>다음으로 <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/llm.py">GPT-4o를 사용하여 potential_questions를 생성하겠습니다</a>.</p><p>마지막으로 <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/entity_extractor.py"></a> <a href="https://spacy.io/">Spacy를</a> 사용하여 엔티티를 추출합니다.</p><p>각 코드에 대한 설명은 상당히 길고 복잡하기 때문에 여기서는 반복하지 않겠습니다. 관심이 있으시다면 아래 코드 샘플에 파일이 표시되어 있습니다.</p><p>데이터 보강을 실행해 보겠습니다:</p># documentenricher.py
from tqdm import tqdm

class DocumentEnricher:

    def __init__(self):
        pass 

    def enrich_document(self, documents, processors, text_col='text'):
        for doc in tqdm(documents, desc="Enriching documents using processors: "+str(processors)): 
            for (processor, field) in processors: 
                metadata=processor(doc[text_col])
                if isinstance(metadata, list):
                    metadata='\n'.join(metadata)
                doc.update({field: metadata})
 
# main.ipynb
# Initialize processor classes 
nltkprocessor=NLTKProcessor() // nltk_processor.py
entity_extractor=EntityExtractor() // entity_extractor.py
gpt4o = LLMProcessor(model='gpt-4o') // llm.py

# Initialize LLM
documentenricher=DocumentEnricher()

# Create new fields in the documents - These are the outputs of the processor functions.
processors=[
    (nltkprocessor.textrank_phrases, "keyphrases"),
    (gpt4o.generate_questions, "potential_questions"),
    (entity_extractor.extract_entities, "entities")
    ]

# .enrich_document() will modify chunked_docs in place. 
# To view the results, we'll print chunked_docs in the next few cells!
documentenricher.enrich_document(chunked_docs, text_col='original_text', processors=processors)
<p>그리고 결과를 살펴보세요:</p><h4>TextRank로 추출한 키프레이즈</h4><p>이러한 키문구는 청크의 핵심 주제를 대신하는 역할을 합니다. 쿼리가 사이버 보안과 관련이 있는 경우 이 항목의 점수가 높아집니다.</p>print(chunked_documents[25]['keyphrases'])

'elastic agent stop', 'agent stop malware', 
'stop malware ransomware', 'malware ransomware environment', 
'ransomware environment wide', 'environment wide visibility', 
'wide visibility threat', 'visibility threat detection', 
'sep cl key', 'cl key feature'
<h4>GPT-4o에서 생성된 잠재적 질문</h4><p>이러한 잠재적 질문은 사용자 쿼리와 직접적으로 일치하여 점수를 높일 수 있습니다. 현재 청크에 있는 정보를 사용하여 답변할 수 있는 질문을 생성하라는 메시지를 GPT-4o에 표시합니다.</p>print(chunked_documents[25]['potential_questions'])

1. What are the primary functions that Elastic Agent provides in terms of cybersecurity?
2. Describe how Logstash contributes to data management within an IT environment.
3. List and explain any key features of Logstash mentioned in the document.
4. How does Elastic Agent enhance environment-wide visibility in threat detection?
5. What capabilities does Logstash offer for handling data beyond simple collection?
6. In what ways does the document suggest that Elastic Agent stops malware and ransomware?
7. Can you identify any relationships between the functionalities of Elastic Agent and Logstash in an integrated environment?
8. What implications might the advanced threat detection capabilities of Elastic Agent have for organizational security policies?
9. Compare and contrast the roles of Elastic Agent and Logstash based on their described functions.
10. How might the centralized collection ability of Logstash support the threat detection capabilities of Elastic Agent?
<h4>Spacy에서 추출한 엔티티</h4><p>이러한 엔티티는 키프레이즈와 유사한 용도로 사용되지만 키프레이즈 추출이 놓칠 수 있는 조직 및 개인 이름을 캡처합니다.</p>print(chunked_documents[29]['entities'])

'appdynamics', 'apm data', 'azure sentinel', 
'microsoft', 'mcafee', 'broadcom', 'cisco', 
'dynatrace', 'coveo', 'lucidworks'
<p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p><h3>복합 멀티필드 임베딩</h3><p>이제 추가 메타데이터로 문서를 보강했으므로 이 정보를 활용하여 더욱 강력하고 컨텍스트를 인식하는 임베딩을 만들 수 있습니다.</p><p>프로세스의 현재 시점을 다시 한 번 살펴보겠습니다. 각 문서에는 네 가지 관심 분야가 있습니다.</p>{
    "chunk": "...",
    "keyphrases": "...", 
    "potential_questions": "...", 
    "entities": "..." 
}
<p>각 필드는 문서의 컨텍스트에 대한 다른 관점을 나타내며, 잠재적으로 LLM이 집중해야 할 핵심 영역을 강조할 수 있습니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84cb328fce6aae23/6a170b42964cea3e4408bbc4/aea1f513009a0c7c8545a79fad8f072a5bcae24c-1440x1067.jpg" alt="RAG의 메타데이터 강화 파이프라인" /><p>이러한 각 필드를 임베딩한 다음 복합 임베딩이라고 하는 임베딩의 가중치 합계를 생성하는 것이 계획입니다.</p><p>운이 좋으면 이 복합 임베딩을 통해 검색 동작을 제어하는 또 다른 조정 가능한 하이퍼파라미터를 도입하는 것 외에도 시스템이 컨텍스트를 더 잘 인식할 수 있게 됩니다.</p><p>먼저, main.ipynb 노트북의 시작 부분에서 가져온 로컬로 정의된 임베딩 모델을 사용해 각 필드를 임베드하고 각 문서를 제자리에 업데이트해 보겠습니다.</p># EmbeddingModel defined in embedding_model.py
embedder=EmbeddingModel(model_name=HUGGINGFACE_EMBEDDING_MODEL)

cols_to_embed=['keyphrases', 'potential_questions', 'entities']

embedding_cols=[]
for col in cols_to_embed:
    # Works on text input
    embedding_col=embedder.embed_documents_text_wise(chunked_documents, text_field=col)
    embedding_cols.append(embedding_col)
# Works on token input
embedding_col=embedder.embed_documents_token_wise(chunked_documents, token_field="chunk")
embedding_cols.append(embedding_col)
<p>각 임베딩 함수는 <code>_embedding</code> 접두사가 붙은 원래 입력 필드인 임베딩의 필드를 반환합니다.</p><p>이제 컴포지트 임베딩의 가중치를 정의해 보겠습니다:</p>embedding_cols=[
                'keyphrases_embedding',
                'potential_questions_embedding',
                'entities_embedding',
                'chunk_embedding']
combination_weights=[
                    0.1,
                    0.15,
                    0.05,
                    0.7
                ]
<p>가중치를 사용하면 사용 사례와 데이터의 품질에 따라 각 구성 요소에 우선순위를 지정할 수 있습니다. 직관적으로 이러한 가중치의 크기는 각 구성 요소의 시맨틱 값에 따라 달라집니다. 청크 텍스트 자체가 가장 풍부하기 때문에 가중치를 70% 으로 지정합니다. 엔티티가 가장 작고 조직 또는 사람 이름의 목록에 불과하므로 가중치를 5% 로 할당합니다. 이러한 값에 대한 정확한 설정은 사용 사례별로 경험적으로 결정해야 합니다.</p><p>마지막으로 가중치를 적용하는 함수를 작성하고 복합 임베딩을 만들어 보겠습니다. 공간을 절약하기 위해 모든 컴포넌트 임베딩도 삭제할 것입니다.</p>from tqdm import tqdm 
def combine_embeddings(objects, embedding_cols, combination_weights, primary_embedding='primary_embedding'):
    # Ensure the number of weights matches the number of embedding columns
    assert len(embedding_cols) == len(combination_weights), "Number of embedding columns must match number of weights"
    
    # Normalize weights to sum to 1
    weights = np.array(combination_weights) / np.sum(combination_weights)
    
    for obj in tqdm(objects, desc="Combining embeddings"):
        # Initialize the combined embedding
        combined = np.zeros_like(obj[embedding_cols[0]])
        
        # Compute the weighted sum
        for col, weight in zip(embedding_cols, weights):
            combined += weight * np.array(obj[col])
        
        # Add the new combined embedding to the object
        obj.update({primary_embedding:combined.tolist()})
        
        # Remove the original embedding columns
        for col in embedding_cols:
            obj.pop(col, None)

combine_embeddings(chunked_documents, embedding_cols, combination_weights)
<p>이것으로 문서 처리를 완료했습니다. 이제 다음과 같은 문서 개체 목록이 생겼습니다:</p>{ 'id_': '7fe71686-5cd0-4831-9e79-998c6dbeae0c', 'chunk': [2312, 14613, ...], 'original_text': 'if an emerging growth company, indicate by check mark if the registrant has elected not to use the extended ...', 'chunk_index': 3, 'chunk_token_count': 399, 'metadata': {'page_label': '3', 'file_name': 'Elastic_NV_Annual-Report-Fiscal-Year-2023.pdf', ... 'keyphrases': 'sep cl unk\ncheck mark registrant\ncl unk indicate\nunk indicate check\nindicate check mark\nprincipal executive office\naccelerate filer unk\ncompany unk emerge\nunk emerge growth\nemerge growth company', 'potential_questions': '1. What are the different types of registrant statuses mentioned in the document?\n2. Under what section of the Sarbanes-Oxley Act must registrants file a report on the effectiveness of their internal ...', 'entities': 'the effe ctiveness of\nsection 13\nSEP\nUNK\nsection 21e\n1934\n1933\nu. s. c.\nsection 404\nsection 12\nal', 'primary_embedding': [-0.3946287803351879, -0.17586839850991964, ...] }
<h4>Elastic으로 인덱싱</h4><p>Elastic Search에 문서를 대량으로 업로드해 보겠습니다. 이를 위해 저는 오래 전에 <a href="https://github.com/elastic/elasticsearch-labs/blob/advanced-rag-techniques/supporting-blog-content/advanced-rag-techniques/elastic_helpers.py"><code>elastic_helpers.py</code></a> 에서 Elastic Helper 함수 집합을 정의했습니다. 코드가 매우 길기 때문에 함수 호출을 살펴보는 데 집중하겠습니다.</p><p><code>es_bulk_indexer.bulk_upload_documents</code> 는 모든 사전 개체 목록에서 작동하며, Elasticsearch의 편리한 동적 매핑을 활용합니다.</p># Initialize Elasticsearch
ELASTIC_CLOUD_ID = os.environ.get('ELASTIC_CLOUD_ID')
ELASTIC_USERNAME = os.environ.get('ELASTIC_USERNAME')
ELASTIC_PASSWORD = os.environ.get('ELASTIC_PASSWORD')
ELASTIC_CLOUD_AUTH = (ELASTIC_USERNAME, ELASTIC_PASSWORD)
es_bulk_indexer = ESBulkIndexer(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)
es_query_maker = ESQueryMaker(cloud_id=ELASTIC_CLOUD_ID, credentials=ELASTIC_CLOUD_AUTH)

# Define Index Name
index_name=os.environ.get('ELASTIC_INDEX_NAME')


# Create index and bulk upload 
index_exists = es_bulk_indexer.check_index_existence(index_name=index_name)
if not index_exists:
    logger.info(f"Creating new index: {index_name}")
    es_bulk_indexer.create_es_index(es_configuration=BASIC_CONFIG, index_name=index_name)

success_count = es_bulk_indexer.bulk_upload_documents(
    index_name=index_name, 
    documents=chunked_documents, 
    id_col='id_',
    batch_size=32
)
<p>Kibana로 이동하여 모든 문서가 색인되었는지 확인합니다. 224개가 있어야 합니다. 이렇게 큰 문서치고는 나쁘지 않습니다!</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8efeface6effe01d/6a170b447d8d67652870e72a/1b3b07f6b98ceb65f6594ce4be83c5b0ed7e7cf9-1440x1380.jpg" alt="Kibana 색인" /><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p><h2>고양이 휴식</h2><p>잠시만요, 기사가 좀 무겁네요, 알아요. 내 고양이를 확인하세요:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc1db5595f71c12ff/6a170b450e2e49940241a0fe/baca4eb52b801b21ced97352cc55462f0a12d6b0-969x996.jpg" alt="한 파이프라인" /><p>사랑스러워요. 모자가 없어져서 어딘가에 훔쳐서 숨겨둔 것 같아요 :(</p><p>여기까지 오신 것을 축하드립니다 :)</p><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-2">2부에서는</a> RAG 파이프라인의 테스트 및 평가에 대해 알아보세요!</p><h2>부록</h2><h3>정의</h3><p><strong>1. 문장 청크</strong></p><ul><li><p>RAG 시스템에서 텍스트를 의미 있는 작은 단위로 나누기 위해 사용되는 전처리 기법입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>입력: 큰 텍스트 블록(예: 문서, 단락)</p></li><li><p>출력: 작은 텍스트 세그먼트(일반적으로 문장 또는 작은 문장 그룹)</p></li></ol></li><li><p><em>목적:</em> </p><ul><li><p>상황에 맞는 세분화된 텍스트 세그먼트 생성</p></li><li><p>보다 정확한 색인 및 검색 가능</p></li><li><p>RAG 시스템에서 검색된 정보의 관련성 향상</p></li></ul></li><li><p><em>특성:</em> </p><ul><li><p>세그먼트는 의미론적으로 의미가 있습니다.</p></li><li><p>독립적으로 색인 및 검색 가능</p></li><li><p>독립형 이해성을 보장하기 위해 일부 컨텍스트를 보존하는 경우가 많습니다.</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>검색 정밀도 향상</p></li><li><p>RAG 파이프라인에서 보다 집중적인 증강 지원</p></li></ul></li></ul><p><strong>2. HyDE(가상 문서 임베딩)</strong></p><ul><li><p>LLM을 사용하여 RAG 시스템에서 쿼리 확장을 위한 가상의 문서를 생성하는 기술입니다.</p></li><li><p><em>프로세스:</em>  </p><ol><li><p>LLM에 쿼리 입력</p></li><li><p>LLM은 쿼리에 대한 가상의 문서를 생성합니다.</p></li><li><p>생성된 문서 임베드</p></li><li><p>벡터 검색에 임베딩 사용</p></li></ol></li><li><p><em>주요 차이점:</em> </p><ul><li><p>기존 RAG: 쿼리를 문서와 일치시킵니다.</p></li><li><p>HyDE: 문서와 문서 매칭</p></li></ul></li><li><p><em>목적:</em> </p><ul><li><p>특히 복잡하거나 모호한 쿼리의 검색 성능 향상</p></li><li><p>짧은 쿼리보다 더 풍부한 의미론적 컨텍스트 캡처</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>LLM의 지식을 활용하여 쿼리를 확장합니다.</p></li><li><p>검색된 문서의 연관성을 잠재적으로 개선할 수 있습니다.</p></li></ul></li><li><p><em>도전 과제:</em> </p><ul><li><p>추가 LLM 추론이 필요하므로 지연 시간과 비용이 증가합니다.</p></li><li><p>성능은 생성된 가상 문서의 품질에 따라 달라집니다.</p></li></ul></li></ul><p><strong>3. 역포장</strong></p><ul><li><p>검색 결과를 LLM으로 전달하기 전에 순서를 바꾸기 위해 RAG 시스템에서 사용되는 기술입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>검색 엔진(예: Elasticsearch)은 관련성 내림차순으로 문서를 반환합니다.</p></li><li><p>순서가 뒤바뀌어 가장 관련성이 높은 문서가 마지막에 배치됩니다.</p></li></ol></li><li><p><em>목적:</em> </p><ul><li><p>해당 맥락에서 최신 정보에 더 집중하는 경향이 있는 LLM의 최신성 편향을 악용합니다.</p></li><li><p>LLM의 컨텍스트 창에서 가장 관련성 높은 정보가 "최신" 되도록 합니다.</p></li></ul></li><li><p><em>예시:</em> 원래 순서: [가장 관련성 높음, 두 번째 높음, 세 번째 높음, ...] 반전된 순서: [..., 세 번째로 많이, 두 번째로 많이, 가장 관련성 높음]</p></li></ul><p><strong>4. 쿼리 분류</strong></p><ul><li><p>쿼리에 RAG가 필요한지 아니면 LLM이 직접 답변할 수 있는지 판단하여 RAG 시스템 효율성을 최적화하는 기술입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>사용 중인 LLM에 맞는 사용자 지정 데이터 세트 개발</p></li><li><p>전문 분류 모델 훈련</p></li><li><p>모델을 사용하여 수신 쿼리 분류하기</p></li></ol></li><li><p><em>목적:</em> </p><ul><li><p>불필요한 RAG 처리를 방지하여 시스템 효율성 향상</p></li><li><p>가장 적절한 응답 메커니즘으로 직접 쿼리 보내기</p></li></ul></li><li><p><em>요구 사항:</em> </p><ul><li><p>LLM 전용 데이터 세트 및 모델</p></li><li><p>정확성 유지를 위한 지속적인 개선 사항</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>간단한 쿼리에 대한 계산 오버헤드 감소</p></li><li><p>RAG가 아닌 쿼리에 대한 응답 시간 개선 가능성</p></li></ul></li></ul><p><strong>5. 요약</strong></p><ul><li><p>RAG 시스템에서 검색된 문서를 압축하는 기술입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>관련 문서 검색</p></li><li><p>각 문서에 대한 간결한 요약 생성</p></li><li><p>RAG 파이프라인에서 전체 문서 대신 요약 사용</p></li></ol></li><li><p><em>목적:</em> </p><ul><li><p>필수 정보에 집중하여 RAG 성능 향상</p></li><li><p>관련성이 낮은 콘텐츠로 인한 노이즈 및 간섭 감소</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>잠재적으로 LLM 응답의 관련성 향상</p></li><li><p>컨텍스트 제한 내에서 더 많은 문서를 포함할 수 있습니다.</p></li></ul></li><li><p><em>도전 과제:</em> </p><ul><li><p>요약에서 중요한 세부 정보가 손실될 위험</p></li><li><p>요약 생성을 위한 추가 계산 오버헤드</p></li></ul></li></ul><p><strong>6. 메타데이터 포함</strong></p><ul><li><p>추가 컨텍스트 정보로 문서를 보강하는 기술입니다.</p></li><li><p><em>메타데이터의 유형:</em>  </p><ul><li><p>키문구</p></li><li><p>제목</p></li><li><p>날짜</p></li><li><p>저작자 세부 정보</p></li><li><p>블러브</p></li></ul></li><li><p><em>목적:</em> </p><ul><li><p>RAG 시스템에서 사용할 수 있는 컨텍스트 정보 증가</p></li><li><p>LLM에게 문서 콘텐츠와 관련성에 대한 보다 명확한 이해 제공</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>잠재적으로 검색 정확도 향상</p></li><li><p>문서 유용성을 평가하는 LLM의 능력 향상</p></li></ul></li><li><p><em>구현:</em> </p><ul><li><p>문서 전처리 중에 수행 가능</p></li><li><p>추가 데이터 추출 또는 생성 단계가 필요할 수 있습니다.</p></li></ul></li></ul><p><strong>7. 복합 멀티필드 임베딩</strong></p><ul><li><p>다양한 문서 구성 요소에 대해 별도의 임베딩을 생성하는 RAG 시스템용 고급 임베딩 기술입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>관련 필드 식별(예: 제목, 키문구, 광고 문구, 주요 콘텐츠)</p></li><li><p>각 필드에 대해 별도의 임베딩을 생성합니다.</p></li><li><p>검색에 사용할 수 있도록 이러한 임베딩을 결합하거나 저장하세요.</p></li></ol></li><li><p><em>표준 접근 방식과의 차이점:</em> </p><ul><li><p>기존: 전체 문서에 대한 단일 임베딩</p></li><li><p>합성: 다양한 문서 측면을 위한 다중 임베딩</p></li></ul></li><li><p><em>목적:</em> </p><ul><li><p>보다 미묘하고 컨텍스트를 인식하는 문서 표현 만들기</p></li><li><p>문서 내에서 더 다양한 소스의 정보를 캡처하세요.</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>모호하거나 다면적인 쿼리의 성능을 잠재적으로 개선합니다.</p></li><li><p>검색 시 다양한 문서 측면에 보다 유연한 가중치를 부여할 수 있습니다.</p></li></ul></li><li><p><em>도전 과제:</em> </p><ul><li><p>임베딩 스토리지 및 검색 프로세스의 복잡성 증가</p></li><li><p>보다 정교한 매칭 알고리즘이 필요할 수 있습니다.</p></li></ul></li></ul><p><strong>8. 쿼리 강화</strong></p><ul><li><p>검색 범위를 개선하기 위해 관련 용어로 원래 쿼리를 확장하는 기술입니다.</p></li><li><p><em>프로세스:</em> </p><ol><li><p>원본 쿼리 분석</p></li><li><p>동의어 및 의미적으로 연관된 구문 생성하기</p></li><li><p>다음 추가 용어를 사용하여 쿼리를 보강하세요.</p></li></ol></li><li><p><em>목적:</em> </p><ul><li><p>문서 말뭉치에서 잠재적인 일치 범위 늘리기</p></li><li><p>특정 언어 또는 기술 용어가 포함된 쿼리의 검색 성능 향상</p></li></ul></li><li><p><em>이점:</em> </p><ul><li><p>원래 쿼리 용어와 정확히 일치하지 않는 관련 문서를 검색할 수 있습니다.</p></li><li><p>쿼리와 문서 간의 어휘 불일치를 극복하는 데 도움이 될 수 있습니다.</p></li></ul></li><li><p><em>도전 과제:</em> </p><ul><li><p>신중하게 구현하지 않을 경우 쿼리 드리프트 위험</p></li><li><p>검색 프로세스에서 계산 오버헤드가 증가할 수 있습니다.</p></li></ul></li></ul><p><a href="https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1#table-of-contents">맨 위로 돌아가기</a></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/advanced-rag-techniques-part-1</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Han Xiang Choong]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9a4691874a19d8da/6a170b3f47d49c99f22d8a24/72b51ba2ae5e5977b56e5b915674753d6cfd0e56-1440x840.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 14 Aug 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch와 OpenSearch: 벡터 검색 성능 비교]]></title>
    <description><![CDATA[Elasticsearch는 벡터 검색에서 별도 설정 없이 OpenSearch보다 2~12배 더 빠릅니다.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison#up-to-12x-faster-out-of-the-box">요약: Elasticsearch는 최대 12배 더 빠릅니다</a> - Elastic에서는 커뮤니티로부터 시맨틱 검색/벡터 검색 영역에서 특히 Elasticsearch와 OpenSearch의 성능 차이를 명확히 해달라는 요청을 많이 받았습니다. 따라서 명확한 데이터 기반의 비교를 제공하기 위해 이 성능 테스트를 수행했으며, 그 취지는 모호함 없이 사용자에게 명확한 사실만을 전달한다는 데 있었습니다. 결과는 <strong>Elasticsearch가 벡터 검색에서 OpenSearch보다 최대 12배 더 빨라</strong>, 더 적은 컴퓨팅 리소스를 필요로 한다는 것을 보여줍니다. 이는 검색 및 조회용으로 Lucene을 최고의 벡터 데이터베이스로 통합하려는 Elastic의 노력을 반영합니다.</p><p>벡터 검색은 특히 AI, 머신 러닝 같은 분야에서 유사성 검색을 수행하는 방식을 혁신하고 있습니다. 벡터 임베딩 모델의 채택이 증가함에 따라 수백만 개의 고차원 벡터를 효율적으로 검색하는 능력이 중요해지고 있습니다.</p><p>벡터 데이터베이스를 지원하는 데 있어 Elastic과 OpenSearch는 눈에 띄게 다른 접근 방식을 취해 왔습니다. Elastic은 Elasticsearch와 함께 Apache Lucene을 최적화하는 데 막대한 투자를 진행했으며, 이를 통해 벡터 검색 애플리케이션 분야에서 업계 최고 수준의 옵션으로 자리매김했습니다. 이에 비해 OpenSearch는 Lucene의 범위를 넘어 다른 벡터 검색 구현을 통합하며 그 중점 분야를 확장했습니다. Elastic은 전략적으로 Lucene에 집중하여, Elasticsearch 버전에서 고도로 통합된 지원을 제공해 각 구성 요소가 서로의 기능을 보완하고 강화하는 향상된 기능 세트를 제공할 수 있게 되었습니다.</p><p>이 블로그에서는 다양한 구성과 벡터 엔진에 대해 Elasticsearch 8.14와 OpenSearch 2.14를 자세히 비교하고 있습니다. 이 성능 분석에서 Elasticsearch는 벡터 검색 작업에서 우수한 플랫폼임이 입증되었으며, 앞으로 출시될 <a href="https://www.elastic.co/search-labs/blog/vector-similarity-computations-ludicrous-speed">기능들</a>은 이러한 차이를 더욱 <a href="https://www.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">크게</a> 확대할 것입니다. OpenSearch와 비교했을 때 모든 벤치마크 트랙에서 뛰어난 성능을 보였으며, <strong>평균적으로 2~12배 더 빠른 성능을 제공했습니다</strong>. 이는 <code>so_vector</code> (2M 벡터, 768D), <code>openai_vector</code> (2.5M 벡터, 1536D), <code>dense_vector</code> (10M 벡터, 96D)를 포함한 다양한 벡터 수와 차원을 사용하는 시나리오에서 진행되었으며, 모든 항목은 Google 클라우드에서 필요한 인프라를 프로비저닝하기 위한 Terraform 스크립트와 테스트 실행용 Kubernetes 매니페스트와 함께 <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance">이 리포지토리</a>에서 확인할 수 있습니다.</p><p>이 블로그에 자세히 설명된 결과는 <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap">이전에 발표되고 제3자가 검증한 연구</a> 결과를 보완합니다. 해당 연구에서는 Elasticsearch가 가장 일반적인 검색 분석 작업(텍스트 쿼리, 정렬, 범위, 날짜 히스토그램, 용어 필터링)에서 OpenSearch보다 40%–140% 더 빠르다는 것을 보여줍니다. 이제 또 다른 차별화 요소인 벡터 검색을 추가할 수 있습니다.</p><h2>기본 설정으로 최대 12배 더 빠른 성능</h2><p>4개의 벡터 데이터 세트에 대한 집중 벤치마크에는 근사 KNN 및 정확한 KNN 검색이 모두 포함되었습니다. 다양한 크기, 차원, 구성을 고려하여 총 <code>40.189.820</code> 개의 캐시되지 않은 검색 요청에 대해 수행했습니다. 결과: <strong>Elasticsearch가 벡터 검색에서 OpenSearch보다 최대 12배 더 빨라</strong>, 더 적은 컴퓨팅 리소스를 필요로 한다는 것을 보여줍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt34b83c6eba3bcb6e/6a17d727dbb4ff18d6fb54fb/cdb26e91f085b90e9b12aeb8fee53b04d365ecae-1440x1156.webp" alt="p90 평균" /><p>그림 1: Elasticsearch와 OpenSearch의 다양한 조합에서 수행된 ANN 및 정확한 KNN 작업 그룹.</p><p><code>knn-10-100</code> 같은 그룹은  및  설정의 KNN 검색을 의미합니다. HNSW 벡터 검색에서 는 쿼리 벡터에 대해 검색할 최근접 이웃의 수를 결정합니다. 결과적으로 얼마나 많은 유사한 벡터를 찾을지 지정합니다. 은 각 세그먼트에서 검색할 후보 벡터의 수를 설정합니다. 후보자가 많을수록 정확도가 향상되지만 더 많은 계산 리소스가 필요합니다.</p><p>또한 다양한 양자화 기법과 엔진별 최적화를 활용하여 테스트했으며, 각 트랙, 작업, 벡터 엔진에 대한 자세한 결과는 아래에서 확인할 수 있습니다.</p><h2>정확한 KNN과 근사 KNN</h2><p>다양한 데이터 세트와 사용 사례를 다룰 때, 벡터 검색에 적합한 접근 방식은 달라집니다. 이 블로그에서 <code>knn-*</code> 로 표시된 모든 작업은 <code>knn-10-100</code> 처럼 <strong>근사 KNN</strong>을 사용하고, <code>script-score-*</code> 는 <strong>정확한 KNN</strong>을 참조합니다. 그렇다면 이 둘의 차이점은 무엇이며, 왜 중요할까요?</p><p>본질적으로 더 큰 데이터 세트를 처리할 때는, 확장성이 뛰어난 근사 K-최근접 이웃(ANN) 방법이 선호됩니다. 필터링 프로세스가 필요한 상대적으로 작은 데이터 세트의 경우, 정확한 KNN 방법이 이상적입니다.</p><p>정확한 KNN은 무차별 대입 방식을 사용하여 하나의 벡터와 데이터 세트의 다른 모든 벡터 사이의 거리를 계산합니다. 그런 다음 이 거리의 순위를 매겨 개의 최근접 이웃을 찾습니다. 이 방법은 정확한 일치를 보장하지만, 대규모의 고차원 데이터 세트에 대한 확장성 문제가 있습니다. 그러나 정확한 KNN이 필요한 경우가 많이 있습니다.</p><ul><li><p><strong>리스코어링</strong>: 어휘 또는 시맨틱 검색 후 벡터 기반 리스코어링을 포함하는 시나리오에서는 정확한 KNN이 필수적입니다. 예를 들어, 제품 검색 엔진에서 초기 검색 결과는 텍스트 쿼리(예: 키워드, 카테고리)를 기반으로 필터링할 수 있으며, 필터링된 항목과 연관된 벡터를 사용하여 보다 정확한 유사성 평가를 수행할 수 있습니다.</p></li><li><p><strong>개인화</strong>: 많은 사용자가 각각 비교적 적은 수(예: 1백만)의 고유 벡터로 표현될 때, 사용자별 메타데이터(예: user_id)로 색인을 정렬하고 벡터를 이용한 무차별 대입 스코어링이 효율적입니다. 이 접근 방식을 사용하면 개별 사용자 선호도에 맞춘 정확한 벡터 비교를 기반으로, 개인화된 추천이나 콘텐츠 제공이 가능합니다.</p></li></ul><p>따라서 정확한 KNN은 벡터 유사도에 기반한 최종 순위와 추천이 정확하며 사용자 선호도에 맞게 조정됩니다.</p><p>반면에 근사 KNN(ANN)은 대규모 고차원 데이터 세트에서 정확한 KNN보다 데이터를 더 빠르고 효율적으로 검색할 수 있는 방법을 사용합니다. 쿼리와 모든 지점 사이의 가장 가까운 정확한 거리를 측정하여 계산 및 확장 문제로 이어지는 무차별 대입 방식 대신, ANN은 특정 기술을 사용해 데이터 세트에서 검색 가능한 벡터의 인덱스와 차원을 효율적으로 재구성합니다. 이로 인해 약간의 부정확성이 발생할 수 있지만, 검색 프로세스의 속도가 크게 향상되어 대규모 데이터 세트를 처리하는 데 효과적인 대안이 됩니다.</p><p>이 블로그에서는 <code>knn-*</code> 로 표시된 모든 작업은 <code>knn-10-100</code> 과 같이 <strong>근사 KNN</strong>을 사용하고, <code>script-score-*</code> 는 <strong>정확한 KNN</strong>을 참조합니다.</p><h2>테스트 방법론</h2><p>Elasticsearch와 OpenSearch는 BM25 검색 작업을 위한 API 측면에서 비슷하지만, 후자가 전자의 포크이기 때문에 포크 이후에 도입된 벡터 검색에는 해당되지 않습니다. OpenSearch는 알고리즘에 있어서 Elasticsearch와는 다른 접근 방식을 취했습니다. <code>lucene</code> 외에도, 각각 고유한 구성과 제한 사항을 가진 2개의 다른 엔진인 <code>nmslib</code> 및 <code>faiss</code> 를 도입했습니다. 예를 들어, OpenSearch의 <code>nmslib</code> 은 많은 사용 사례에서 필수적인 기능인 필터를 허용하지 않습니다.</p><p>3개 엔진 모두 계층적으로 탐색 가능한 작은 세계(Hierarchical Navigable Small World, HNSW) 알고리즘을 사용합니다. HNSW 알고리즘은 근사 최근접 이웃을 검색하는 데 효율적이며 고차원 데이터를 처리할 때 특히 강력합니다. <code>faiss</code>는 두 번째 알고리즘인 <code>ivf</code>도 지원하지만, 데이터 세트에 대한 사전 학습이 필요하기 때문에, 여기서는 HNSW에만 집중하려고 합니다. HNSW의 핵심 아이디어는 데이터를 여러 계층의 연결된 그래프로 구성하는 것이며, 각 계층은 데이터 세트의 다양한 세분성을 나타냅니다. 검색은 가장 거친 보기를 제공하는 최상위 레이어에서 시작하여 기본 수준에 도달할 때까지 점점 더 세밀한 레이어로 진행됩니다.</p><p>두 검색 엔진은 모두 공정한 테스트 근거를 확보하기 위해 통제된 환경에서 동일한 조건으로 테스트되었습니다. 적용된 방법은 Elasticsearch, OpenSearch, Rally에 대한 전용 Node 풀을 사용하여 <a href="https://www.elastic.co/blog/elasticsearch-opensearch-performance-gap#testing-methodology">이전에 게시된 성능 비교</a>와 유사합니다. <a href="https://github.com/elastic/elasticsearch-opensearch-vector-performance/blob/main/terraform/main.tf">terraform 스크립트</a>는 (모든 소스와 함께) Kubernetes 클러스터를 프로비저닝하는 데 사용할 수 있습니다.</p><ul><li><p>3개의 <code>e2-standard-32</code> 머신(128GB RAM 및 32개의 CPU)이 있는 Elasticsearch용 Node 풀 1개</p></li><li><p>3개의 <code>e2-standard-32</code> 머신(128GB RAM 및 32개의 CPU)이 있는 OpenSearch용 Node 풀 1개</p></li><li><p>2개의 <code>t2a-standard-16</code> 머신(64GB RAM 및 16개의 CPU)이 있는 Rally용 Node 풀 1개</p></li></ul><p>각 '트랙'(또는 테스트)은 서로 다른 엔진, 서로 다른 구성, 서로 다른 벡터 유형을 포함하는 각 구성에 대해 10회씩 실행되었습니다. 트랙에는 트랙에 따라 1,000번에서 10,000번 반복되는 작업이 있습니다. 예를 들어 네트워크 시간 초과로 인해 트랙의 작업 중 하나가 실패하면, 모든 작업이 폐기되므로 모든 결과는 문제 없이 시작되고 완료된 트랙을 나타냅니다. 모든 테스트 결과는 통계적으로 검증되어, 개선이 우연이 아님을 보장합니다.</p><h2>자세한 결과</h2><p>왜 평균 지연 시간이 아닌 99번째 백분위수를 사용하여 비교해야 할까요? 한 예로 특정 지역의 평균 주택 가격을 들어 보겠습니다. 평균 가격은 비싼 지역을 나타낼 수 있지만, 자세히 살펴보면 대부분의 주택은 훨씬 낮은 가격에 거래되고 일부 고급 부동산만 평균 수치를 부풀리고 있는 것으로 드러날 수 있습니다. 이는 평균 가격이 해당 지역의 주택 가치 전체를 정확하게 나타내지 못할 수 있음을 보여줍니다. 이는 응답 시간를 조사하는 것과 유사하며, 평균값은 중요한 문제를 숨길 수 있습니다.</p><h4>작업</h4><ul><li><p>근사 KNN(k:10 n:50)</p></li><li><p>근사 KNN(k:10 n:100)</p></li><li><p>근사 KNN(k:100 n:1000)</p></li><li><p>근사 KNN(k:10 n:50) 및 키워드 필터 적용</p></li><li><p>근사 KNN(k:10 n:100) 및 키워드 필터 적용</p></li><li><p>근사 KNN(k:100 n:1000) 및 키워드 필터 적용</p></li><li><p>근사 KNN(k:10 n:100) 및 인덱싱 병행</p></li><li><p>정확한 KNN(스크립트 점수)</p></li></ul><h4>벡터 엔진</h4><ul><li><p><code>lucene</code> Elasticsearch와 OpenSearch 모두, 버전 9.10에서</p></li><li><p><code>faiss</code> OpenSearch에서</p></li><li><p><code>nmslib</code> OpenSearch에서</p></li></ul><h4>벡터 유형</h4><ul><li><p><code>hnsw</code> Elasticsearch 및 OpenSearch에서</p></li><li><p><code>int8_hnsw</code> Elasticsearch에서(자동 8비트 양자화 적용 HNSW: <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">링크</a>)</p></li><li><p><code>sq_fp16 hnsw </code>OpenSearch에서(자동 16비트 양자화 적용 HNSW: <a href="https://opensearch.org/docs/2.14/search-plugins/knn/knn-vector-quantization#faiss-16-bit-scalar-quantization">링크</a>)</p></li></ul><h4>기본 설정 상태에서 바로 사용 가능, 동시 세그먼트 검색 지원</h4><p>아시다시피, Lucene은 Java로 작성된 고성능 텍스트 검색 엔진 라이브러리로 Elasticsearch, OpenSearch, Solr 등 많은 검색 플랫폼의 중추적인 역할을 합니다. 핵심적으로 Lucene은 데이터를 세그먼트로 구성하며, 세그먼트는 본질적으로 독립된 인덱스로서 Lucene이 검색을 보다 효율적으로 실행할 수 있게 해 줍니다. 따라서 Lucene 기반 검색 엔진에 검색을 요청하면, 해당 세그먼트에서 순차적으로 또는 병렬로 검색이 실행됩니다.</p><p>OpenSearch는 동시 세그먼트 검색을 선택적 플래그로 도입했지만 기본적으로 사용하지 않습니다. <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/">여기</a>에 자세히 설명된 대로 특수 인덱스 설정 <code>index.search.concurrent_segment_search.enabled</code>을 사용하여 활성화해야 하며, 몇 가지 <a href="https://opensearch.org/docs/latest/search-plugins/concurrent-segment-search/#other-considerations">제한 사항</a>이 있습니다.</p><p>반면 Elasticsearch는 <a href="https://github.com/elastic/elasticsearch/pull/101230">기본 설정으로</a> 세그먼트를 동시에 검색합니다. 따라서 이 블로그에서 비교할 때 다양한 벡터 엔진과 벡터 유형뿐만 아니라 다양한 구성도 고려됩니다.</p><ul><li><p>Elasticsearch ootb: 기본 설정 상태에서 바로 사용 가능한 Elasticsearch, 동시 세그먼트 검색 지원;</p></li><li><p>OpenSearch ootb: 동시 세그먼트 검색이 기본적으로 활성화되지 않은 상태;</p></li><li><p>OpenSearch css: 동시 세그먼트 검색이 활성화된 상태</p></li></ul><p>이제 테스트한 각 벡터 데이터 세트에 대한 자세한 결과를 살펴보겠습니다.</p><h2>250만 개의 벡터, 1,536차원(openai_vector)</h2><p>가장 간단하지만 차원 면에서 가장 큰 트랙인 <a href="https://github.com/elastic/rally-tracks/edit/master/openai_vector">openai_vector</a>부터 시작합니다. 이 트랙은 OpenAI의 <a href="https://openai.com/blog/new-and-improved-embedding-model">text-embedding-ada-002 모델</a>을 사용하여 생성된 임베딩으로 강화된 <a href="https://huggingface.co/datasets/BeIR/nq">NQ 데이터 세트</a>를 사용합니다. 근사 KNN만 테스트하고 작업은 5개뿐이므로 가장 간단합니다. 인덱싱 없이 독립형으로, 인덱싱과 함께, 단일 클라이언트와 8개의 동시 클라이언트를 사용하여 테스트합니다.</p><h3>작업</h3><ul><li><p><strong>standalone-search-knn-10-100-multiple-clients</strong>: 8개의 클라이언트로 250만 개의 벡터 동시에 검색(k: 10, n:100)</p></li><li><p><strong>standalone-search-knn-100-1000-multiple-clients</strong>: 8개의 클라이언트로 250만 개의 벡터 동시에 검색(k: 100, n:1000)</p></li><li><p><strong>standalone-search-knn-10-100-single-client</strong>: 단일 클라이언트로 250만 개의 벡터 검색(k: 10, n: 100)</p></li><li><p><strong>standalone-search-knn-100-1000-single-client</strong>: 단일 클라이언트로 250만 개의 벡터 검색(k: 100, n: 1000)</p></li><li><p><strong>parallel-documents-indexing-search-knn-10-100</strong>: 250만 개의 벡터 검색하는 동시에 추가로 100,000개의 문서를 색인(k:10, n:100).</p></li></ul><p>평균 p99 성능은 다음과 같이 요약됩니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5791848eb0c12fb/6a17d72925daab9cae08a09e/eea0b2b49c690baada3e09d6968e513bfffe51a9-1440x318.webp" alt="openai_vector 표" /><p>여기서 k:10 및 n:100 조건에서 인덱싱(예: 읽기+쓰기)과 함께 벡터 검색을 수행할 때 Elasticsearch가 OpenSearch보다 <strong>3~ 8배 정도 빠르다</strong>는 것을 관찰했습니다. 또한 동일한  및  조건에서, 인덱싱 없이 수행할 경우 <strong>2~3배 더 빠르다</strong>는 결과가 나왔습니다. :100 및 :1000(<em>standalone-search-knn-100-1000-single-client</em> 및 <em>standalone-search-knn-100-1000-multiple-clients</em>)에서 Elasticsearch는 평균적으로 OpenSearch보다 <strong>2~7배</strong> 더 빠릅니다.</p><p>자세한 결과에서는 정확한 사례와 벡터 엔진을 비교해 보여줍니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdbe41d3187ced7ec/6a17d72a445de951c44cff4c/a7a761ed631d3e6211beb83d9d93d752d10123c9-1440x1728.webp" alt="openai_vector" /><h4>리콜</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969485</p><p>0.995138</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.781445</p><p>0.784817</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.96519</p><p>0.995422</p><p>OpenSearch-2.14.0@faiss</p><p>0.984154</p><p>0.98049</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.980012</p><p>0.97721</p><p>OpenSearch-2.14.0@nmslib</p><p>0.982532</p><p>0.99832</p><h2>1,000만 개의 벡터, 96차원(dense_vector)</h2><p>1천만 개의 벡터, 96차원으로 구성된 <a href="https://github.com/elastic/rally-tracks/tree/master/dense_vector">dense_vector</a>에서. <a href="https://big-ann-benchmarks.com/">Yandex DEEP1B</a> 이미지 데이터 세트를 기반으로 합니다. 데이터 세트는 <code>learn.350M.fbin</code>이라는 '샘플 데이터' 파일의 처음 1천만 개의 벡터로 생성됩니다. 검색 작업은 '쿼리 데이터' 파일 쿼리 <code>public.10K.fbin</code>의 벡터를 사용합니다.</p><p>Elasticsearch와 OpenSearch는 이 데이터 세트에서 매우 우수한 성능을 발휘합니다. 특히 일반적으로 읽기 전용 인덱스에서 수행되는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html">강제 병합</a> 이후에는 더욱 그렇습니다. 이는 인덱스를 조각 모음하여 검색 대상으로 단일 '테이블'을 구성하는 것과 유사합니다.</p><h3>작업</h3><p>각 작업은 100개의 요청으로 워밍업한 후 1,000개의 요청을 측정합니다.</p><ul><li><p><strong>knn-search-10-100</strong>: 1천만 개의 벡터 검색(k: 10, n: 100)</p></li><li><p><strong>knn-search-100-1000</strong>: 1천만 개의 벡터 검색(k: 100, n: 1000)</p></li><li><p><strong>knn-search-10-100-force-merge</strong>: 강제 병합 후 1천만 개의 벡터 검색(k: 10, n: 100)</p></li><li><p><strong>knn-search-100-1000-force-merge</strong>: 강제 병합 후 1천만 개의 벡터 검색(k: 100, n:1000)</p></li><li><p><strong>knn-search-100-1000-concurrent-with-indexing</strong>: 1천만 개의 벡터를 검색하면서 동시에 <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/challenges/default.json#L76C36-L76C37">데이터 세트의 5%</a> 업데이트(k: 100, n: 1000)</p></li><li><p><strong>script-score-query</strong>: <a href="https://github.com/elastic/rally-tracks/blob/master/dense_vector/queries.json">2천 개의 특정 벡터</a>에 대한 정확한 KNN 검색.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4629d06af85eb96c/6a17d72c6864a423e7b685dc/174995e0a2156d86359cdb7aa446dfaae6312ea4-1440x316.webp" alt="dense_vector" /><p>Elasticsearch와 OpenSearch 모두 근접 KNN에서 우수한 성능을 보였습니다. <em>knn-search-100-1000-force-merge</em>와 <em>knn-search-10-100-force-merge</em>에서 인덱스가 병합되면(즉, 세그먼트가 하나만 있는 경우) <code>nmslib</code> 와 <code>faiss</code>를 사용할 때 모두 약 15ms로 매우 근접하지만 OpenSearch가 다른 엔진보다 성능이 더 좋습니다.</p><p>그러나 <em>knn-search-10-100</em>과 <em>knn-search-100-1000</em>에서 인덱스에 여러 세그먼트가 있는 경우(인덱스가 문서에 대한 업데이트를 받는 일반적인 상황) Elasticsearch는 대기 시간을 약 7ms와 16ms로 유지하는 반면, 다른 모든 OpenSearch 엔진은 더 느립니다.</p><p>또한 인덱스를 동시에 검색하고 작성하는 경우<em>(knn-search-100-1000-concurrent-with-indexing</em>) Elasticsearch는 지연 시간을 15ms(13.8ms) 미만으로 유지하여 OpenSearch(49.3ms)보다 거의 <strong>4배 빠르고</strong>, 동시 세그먼트 검색이 활성화된 경우에도 여전히 더 빠릅니다(17.9ms). 하지만 차이가 너무 작아 의미 있는 수준이라고 보기는 어렵습니다.</p><p>정확한 KNN의 경우 차이가 훨씬 큽니다. Elasticsearch는 OpenSearch보다 <strong>6배 더 빠릅니다</strong>(약 260ms vs. 1600ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254f43bcaa3dbfc2/6a17d72ddbb4ffc780fb54ff/17aec6be31117440bc4d1f99984aed95df1c4f6b-1440x1728.webp" alt="dense_vector" /><h4>리콜</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>0.969843</p><p>0.996577</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>0.775458</p><p>0.840254</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>0.971333</p><p>0.996747</p><p>OpenSearch-2.14.0@faiss</p><p>0.9704</p><p>0.914755</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>0.968025</p><p>0.913862</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><h2>2백만 개의 벡터, 768차원(so_vector)</h2><p>이 <a href="https://github.com/elastic/rally-tracks/tree/master/so_vector">트랙</a> <code>so_vector</code>은 2022년 4월 21일에 <a href="https://archive.org/download/stackexchange/stackoverflow.com-Posts.7z">다운로드한 StackOverflow 게시물 덤프</a>에서 파생되었습니다. 질문 문서만 포함되어 있으며, 답변 문서는 모두 제거되었습니다. 각 질문 제목은 문장 변환기 모델 <a href="https://huggingface.co/sentence-transformers/multi-qa-mpnet-base-cos-v1">multi-qa-mpnet-base-cos-v1</a>을 사용하여 벡터로 인코딩되었습니다. 이 데이터 세트에는 처음 2백만 개의 질문이 포함되어 있습니다.</p><p>이전 트랙과 달리, 각 문서에는 필터링 및 하이브리드 검색을 통한 근접 KNN과 같은 테스트 기능을 지원하기 위해 벡터 외에 다른 필드가 포함되어 있습니다. OpenSearch용<code>nmslib</code> 는 <a href="https://opensearch.org/docs/latest/search-plugins/knn/filter-search-knn/#k-nn-search-with-filters">필터를 지원하지 않기 때문에</a> 이 테스트에는 포함되지 않았습니다.</p><h3>작업</h3><p>각 작업은 100개의 요청으로 워밍업한 후, 100개의 요청을 측정합니다. 테스트에는 16개의 검색 유형 * 2개의 서로 다른 k 값 * 3개의 서로 다른 n 값이 포함되어 있으므로, 단순화를 위해 작업을 그룹화했습니다.</p><ul><li><p><strong>knn-10-50</strong>: 필터 없이 2백만 개의 벡터 검색(k:10, n:50)</p></li><li><p><strong>knn-10-50-filtered</strong>: <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">필터를 적용</a>하여 2백만 개의 벡터 검색(k:10, n:50)</p></li><li><p><strong>knn-10-50-after-force-merge</strong>: 강제 병합 후 필터를 적용하여 2백만 개의 벡터 검색(k:10, n:50)</p></li><li><p><strong>knn-10-100</strong>: 필터 없이 2백만 개의 벡터 검색(k:10, n:100)</p></li><li><p><strong>knn-10-100-filtered</strong>: <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">필터를 적용하여</a> 2백만 개의 벡터 검색(k:10, n:100)</p></li><li><p><strong>knn-10-100-after-force-merge</strong>: 강제 병합 후 필터를 적용하여 2백만 개의 벡터 검색(k:10, n:100)</p></li><li><p><strong>knn-100-1000</strong>: 필터 없이 2백만 개의 벡터 검색(k:100, n:1000)</p></li><li><p><strong>knn-100-1000-filtered</strong>: <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json">필터를 적용하여</a> 2백만 개의 벡터 검색(k:100 , n:1000)</p></li><li><p><strong>knn-100-1000-after-force-merge</strong>: 강제 병합 후 필터를 적용하여 2백만 개의 벡터 검색(k:100, n:1000)</p></li><li><p><strong>exact-knn</strong>: <a href="https://github.com/elastic/rally-tracks/blob/master/so_vector/operations/default.json#L56">필터 적용 여부에 관계없이</a> 정확한 KNN 검색.</p></li></ul><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8b44e1306af35877/6a17d72f577262aca11bca3e/d4ed2982d55370cad4b2048b23ce97caa55017c0-1440x316.webp" alt="so_vector 표" /><p>Elasticsearch는 이 테스트에서 기본 설정 상태에서 OpenSearch보다 <strong>일관되게 빠릅니다</strong>. OpenSearch가 더 빠른 경우는 두 가지뿐이며, 그 차이도 크지 않습니다(<em>knn-10-100</em> 및 <em>knn-100-1000</em>). 필터와 함께 <em>knn-10-50</em>, <em>knn-10-100</em>, <em>knn-100-1000</em> 작업은 최대 <strong>7배</strong>(112ms vs 803ms)의 차이를 보입니다.</p><p>두 솔루션의 성능은 '강제 병합' 후에 균등해 지는 것으로 보이며, 이는 <em>knn-10-50-after-force-merge</em>, <em>knn-10-100-after-force-merge</em>, <em>knn-100-1000-after-force-merge</em> 에서 확인할 수 있습니다. 그러한 작업에서 <code>faiss</code>가 더 빠릅니다.</p><p>정확한 KNN의 성능은 다시 한 번 매우 다르게 나타났으며, 이번에는 Elasticsearch가 OpenSearch보다 <strong>13배 더 빨랐습니다</strong>(약 385ms vs 5262ms).</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ccc7c2df84559/6a17d7314b055de00d43203f/615cb9228eb05ddd2e9512b3a6a5bc88d4088a1a-1440x1440.webp" alt="so_vector" /><h4>리콜</h4><p></p><p>knn-recall-10-100</p><p>knn-recall-100-1000</p><p>knn-recall-10-50</p><p>Elasticsearch-8.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>Elasticsearch-8.14.0@lucene-int8_hnsw</p><p>1</p><p>0.986667</p><p>1</p><p>OpenSearch-2.14.0@lucene-hnsw</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@faiss-sq_fp16</p><p>1</p><p>1</p><p>1</p><p>OpenSearch-2.14.0@nmslib</p><p>0.9674</p><p>0.910303</p><p>0.976394</p><h2>Elasticsearch와 Lucene이 명백한 승자</h2><p>Elastic에서는 RAG(Retrieval-Augmented Generation)를 비롯한 검색 및 조회 사용 사례를 위한 최고의 벡터 데이터베이스를 제공할 수 있도록 Apache Lucene과 Elasticsearch를 끊임없이 혁신하고 있습니다. 최근의 발전으로 성능이 획기적으로 향상되어 벡터 검색이 이전보다 <a href="https://search-labs.elastic.co/search-labs/blog/elasticsearch-lucene-vector-database-gains">더 빠르고 공간 효율적</a>이 되었으며, 이는 Lucene 9.10에서 얻은 이점을 기반으로 합니다. 이 블로그는 최신 버전을 비교한 결과 Elasticsearch가 OpenSearch보다 최대 12배 빠르다는 연구를 소개합니다.</p><p>두 제품 모두 동일한 버전의 Lucene(<a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/release-notes-8.14.0.html">Elasticsearch 8.14 릴리즈 노트</a> 및 <a href="https://github.com/opensearch-project/OpenSearch/blob/2.14/release-notes/opensearch.release-notes-2.14.0.md">OpenSearch 2.14 릴리즈 노트</a>)을 사용한다는 점은 주목할 만합니다.</p><p>Elastic의 빠른 혁신은 온프레미스 및 Elastic Cloud 고객뿐만 아니라 당사의 <a href="https://www.elastic.co/search-labs/blog/stateless-your-new-state-of-find-with-elasticsearch">무상태(stateless) 플랫폼</a>을 사용하는 고객에게도 더 많은 혜택을 가져다 줄 것입니다. <a href="https://www.elastic.co/search-labs/blog/int4-scalar-quantization-in-lucene">int4에 대한 스칼라 양자화</a> 지원과 같은 기능은 <a href="https://www.elastic.co/search-labs/blog/evaluating-scalar-quantization">int8 테스트</a>와 마찬가지로, 엄격한 테스트를 통해 고객이 재현율 저하 없이 이러한 기술을 활용할 수 있도록 보장할 것입니다.</p><p>AI 및 머신 러닝 애플리케이션의 확산으로 인해 최신 검색 엔진에서 벡터 검색 효율성은 타협할 수 없는 기능이 되고 있습니다. 대용량·고복잡 벡터 데이터의 요구를 충족할 수 있는 강력한 검색 엔진을 찾고 있는 조직에게는 Elasticsearch가 확실한 해답입니다.</p><p>기존 플랫폼을 확장하든 새로운 프로젝트를 시작하든, 벡터 검색 요구 사항을 위해 Elasticsearch를 통합하는 것은 가시적이고 장기적인 이점을 얻을 수 있는 전략적 움직임입니다. 입증된 성능 우위를 바탕으로 Elasticsearch는 차세대 검색 혁신을 이끌 준비가 되어 있습니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/elasticsearch-opensearch-vector-search-performance-comparison</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Lucene]]></category>
    <dc:creator><![CDATA[Ugo Sangiorgi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5d70b25967c2194e/6a17d732b1e11383f879f0ca/13c3c0053e2968fb835ba2f90f34bec3a011b5c0-880x592.webp" length="0" type="image/webp"/>
    <pubDate>Wed, 26 Jun 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[벡터 유사도 측정 및 점수]]></title>
    <description><![CDATA[L1 &amp; L2 거리, 코사인 유사도, 도트 곱 유사도, 최대 내적 곱 유사도 등 Elasticsearch의 벡터 유사도 측정값과 점수를 살펴보세요.]]></description>
    <content:encoded><![CDATA[<p>자유 텍스트 검색이 필요하지만 Ctrl+F/Cmd+F로는 더 이상 검색이 되지 않을 때, 일반적으로 어휘 검색 엔진을 사용하는 것이 논리적으로 가장 좋은 선택입니다. 어휘 검색 엔진은 검색할 텍스트를 분석하고 검색 시 일치하는 용어로 토큰화하는 데는 탁월하지만, 색인 및 검색되는 텍스트의 진정한 의미를 이해하고 파악하는 데는 일반적으로 부족합니다.</p><p>바로 이 부분에서 벡터 검색 엔진이 빛을 발합니다. 동일한 텍스트를 색인화하여 그 텍스트가 나타내는 의미와 유사하거나 연관된 의미를 가진 다른 개념과의 관계를 기반으로 검색할 수 있습니다.</p><p>이 블로그에서는 벡터가 텍스트의 의미를 전달하는 데 얼마나 훌륭한 수학적 개념인지에 대해 간략하게 살펴보겠습니다. 그런 다음 이웃 벡터 검색, 즉 유사한 의미를 갖는 벡터를 검색할 때 Elasticsearch에서 지원하는 다양한 유사도 기술과 점수를 매기는 방법에 대해 자세히 알아보겠습니다.</p><h2>벡터 임베딩이란 무엇인가요?</h2><p>이 글에서는 벡터 임베딩의 복잡성에 대해 자세히 다루지 않습니다. 이 주제를 더 자세히 살펴보고 싶거나 계속하기 전에 입문서가 필요한 경우 <a href="https://www.elastic.co/kr/what-is/vector-embedding">다음 가이드를</a> 확인하는 것이 좋습니다.</p><p>간단히 말해, 벡터 임베딩은 머신 러닝 프로세스를 통해 얻어집니다(예 딥러닝 신경망)을 사용하여 모든 종류의 비정형 입력 데이터(예: 원시 텍스트, 이미지, 동영상, 사운드 등)를 그 의미와 관계를 전달하는 수치 데이터로 변환합니다. "비정형 데이터의 종류에 따라 각 유형의 데이터를" 이해하도록 학습된 다양한 종류의 머신 러닝 모델이 필요합니다.</p><p>각 벡터는 다차원 공간에서 특정 데이터의 위치를 점으로 표시하며, 해당 위치는 모델이 데이터를 특성화하는 데 사용하는 특징 집합을 나타냅니다. 차원 수는 머신 러닝 모델에 따라 다르지만 일반적으로 수백 개에서 수천 개까지 다양합니다. 예를 들어 <a href="https://platform.openai.com/docs/guides/embeddings">OpenAI 임베딩 모델은</a> 1536개의 치수를 자랑하는 반면, <a href="https://docs.cohere.com/reference/embed">Cohere 임베딩 모델은</a> 382개에서 4096개의 치수를 지원합니다. 최신 릴리즈부터 Elasticsearch dense_vector 필드 유형은 최대 4096개의 차원을 지원합니다.</p><p>벡터 임베딩의 진정한 장점은 비슷한 의미를 공유하는 데이터 포인트가 공간에서 서로 가깝게 배치된다는 것입니다. 또 다른 흥미로운 측면은 벡터 임베딩이 데이터 요소 간의 관계를 포착하는 데 도움이 된다는 점입니다.</p><h2>벡터는 어떻게 비교하나요?</h2><p>비정형 데이터가 머신러닝 모델에 의해 여러 차원에 걸쳐 데이터의 유사성을 포착하는 벡터 임베딩으로 잘게 쪼개진다는 것을 알았으니, 이제 이러한 벡터의 매칭이 어떻게 작동하는지 이해해야 합니다. 그 답은 매우 간단합니다.</p><p>서로 <strong>가까운</strong> 벡터 임베딩은 <strong>의미적으로 유사한</strong> 데이터 조각을 나타냅니다. 따라서 벡터 데이터베이스를 쿼리할 때 먼저 모든 비정형 데이터 색인에 사용된 것과 동일한 머신 러닝 모델을 사용하여 검색 입력(이미지, 텍스트 등)을 벡터 임베딩으로 변환하고, 최종 목표는 해당 쿼리 벡터에 <strong>가장 가까운 이웃 벡터를</strong> 찾는 것입니다. 따라서 쿼리 벡터와 데이터베이스에 색인된 모든 기존 벡터 사이의 "거리" 또는 "유사성" 을 측정하는 방법만 알아내면 됩니다.</p><h2>거리, 유사도 및 점수</h2><p>다행히도 두 벡터 사이의 거리 또는 유사성을 측정하는 것은 벡터 산술 덕분에 쉽게 해결할 수 있는 문제입니다. 이제 Elasticsearch에서 지원하는 가장 인기 있는 거리 및 유사도 함수에 대해 살펴보겠습니다. 경고, 수학이 앞서갑니다!</p><p>자세히 알아보기 전에 득점에 대해 간단히 살펴보겠습니다. 실제로 Lucene은 양수 점수만 허용합니다. 곧 소개할 모든 거리 및 유사도 함수는 두 벡터가 얼마나 가깝거나 유사한지를 측정할 수 있지만, 이러한 원시 수치는 음수가 될 수 있으므로 점수로 사용하기에는 적합하지 않습니다. 따라서 최종 점수는 거리 또는 유사도 값에서 양수가 되고 점수가 클수록 더 높은 순위(즉, 더 가까운 벡터)에 해당하는 방식으로 도출되어야 합니다.</p><h3>L1 거리</h3><p>맨해튼 거리라고도 하는 두 벡터  및  의 L1 거리는 모든 요소의 쌍별 절대 차이를 합산하여 측정합니다. , 거리가 작을수록 두 벡터는 더 가까워집니다. L1 거리 공식(1)은 아래에서 볼 수 있듯이 매우 간단합니다:</p><p>시각적으로 L1 거리는 아래 이미지(빨간색)와 같이 표시할 수 있습니다:</p><p>다음 두 벡터   산출됩니다.</p><p><strong>중요:</strong> L1 거리 함수는 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html#vector-functions-l1"></a> <code>script_score</code> DSL 쿼리를 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.11/dense-vector.html#dense-vector-params">사용한 정확한 벡터 검색</a> (일명 무차별 검색)에만 지원되며 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"><code>knn</code></a><a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/8.11/knn-search.html#approximate-knn"> </a> 검색 <a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"><code>knn</code></a><a href="https://www.elastic.co/kr/guide/en/elasticsearch/reference/current/query-dsl-knn-query.html"> 옵션 또는 DSL 쿼리를 사용한 대략적인 kNN 검색에는 지원되지</a> 않는다는 점에 유의할 필요가 있습니다.</p><h3>L2 거리</h3><p>유클리드 거리라고도 하는 두 벡터  및  의 L2 거리는 먼저 모든 요소의 쌍별 차이의 제곱을 합한 다음 그 결과의 제곱근을 구하여 측정합니다. 기본적으로 두 지점 사이의 최단 경로입니다. L1과 마찬가지로 , 의 거리가 작을수록 두 벡터는 더 가까워집니다:</p><p>아래 이미지에서 L2 거리는 빨간색으로 표시되어 있습니다:</p><p> 거리에 사용한 것과 동일한 두 개의 샘플 벡터  및  을 재사용하면 이제  거리를  수 있습니다.</p><p>점수는 두 벡터 사이의 거리가 작을수록 더 가까울수록(즉, 더 유사할수록) 점수가 높습니다. 따라서 점수를 도출하려면 거리 측정값을 반전시켜 가장 작은 거리가 가장 높은 점수를 얻도록 해야 합니다. L2 거리를 사용할 때 점수가 계산되는 방식은 아래 공식 (3)과 같습니다:</p><p>이전 예제의 샘플 벡터를 다시 사용하면, 그 점수는  됩니다. 서로 매우 가까운 두 벡터의 점수는 1에 가까워지고, 서로 매우 먼 두 벡터의 점수는 0에 가까워지는 경향이 있습니다.</p><p>L1과 L2 거리 함수에 대해 마무리하면서, 이를 비교하기 위한 좋은 비유는 뉴욕 맨해튼에 있는 두 개의 건물 A와 B를 생각하면 됩니다. A에서 B로 가는 택시는 L1 경로(거리와 도로)를 따라 주행해야 하지만, 새는 L2 경로(직선)를 이용할 수 있습니다.</p><h3>코사인 유사도</h3><p>L1 및 L2와 달리 코사인 유사도는 두 벡터  와  사이의 거리를 측정하는 것이 아니라 상대 각도, 즉 둘이 거의 같은 방향을 가리키고 있는지 여부를 측정합니다. 유사도  가 높을수록 두 벡터 사이의 각도  작아지므로 "가까울수록" 더 가깝고 "비슷할수록" 전달되는 의미가 더 커집니다.</p><p>이를 설명하기 위해 야생에서 서로 다른 방향을 바라보고 있는 두 사람을 생각해 보겠습니다. 아래 그림에서 파란색의 사람은 벡터  로 표시된 방향을, 빨간색의 사람은 벡터  의 방향을 바라보고 있습니다. 시선이 같은 방향을 향할수록(즉, 벡터가 가까워질수록) 파란색과 빨간색 영역으로 상징되는 시야가 더 많이 겹칩니다. 시야가 얼마나 겹치는지를 코사인 유사도라고 합니다. 그러나 사람 B는 사람 A보다 더 멀리 보입니다(즉, 벡터  이 더 깁니다). 사람 B는 수평선 너머 멀리 있는 산을 바라보고 있을 수 있고, 사람 A는 가까운 나무를 바라보고 있을 수 있습니다. 코사인 유사도의 경우 각도에 관한 것이므로 아무런 역할을 하지 않습니다.</p><p>이제 코사인 유사도를 계산해 보겠습니다. 공식 (4)는 매우 간단한데, 분자는 두 벡터의 점 곱으로 구성되고 분모에는 크기(즉, 길이)의 곱이 포함됩니다:</p><p> 와  사이의 코사인 유사도는 아래 이미지에 두 값 사이의 각도(빨간색)를 측정한 값으로 표시되어 있습니다:</p><p>이 코사인 유사도 값이 구체적으로 무엇을 의미하는지 설명하기 위해 잠시 우회해 보겠습니다. 코사인 함수를 묘사한 아래 이미지에서 볼 수 있듯이 값은 항상  간격으로 진동합니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc0e4eb98ebb64cf3/6a17da287b54f983818b3783/e31145284b8c27d0bd9e3d2831f811388134fd83-1188x272.png" alt="코스 함수" /><p>두 벡터가 유사하다고 간주되려면 각도가 가능한 한 예각이어야 하며, 이상적으로는  각도에 가까워야 하며, 이는  완벽한 유사성으로 요약됩니다. 즉, 벡터가...</p><ol><li><p>...서로<strong>가까워지면</strong> 각도의 코사인이  가까워집니다(즉,  가까워짐).</p></li></ol><ol><li><p>..<strong>.관련이 없는</strong> 경우 각도의 코사인이  가까워집니다(즉,  가까워짐).</p></li></ol><ol><li><p>..<strong>.반대로</strong>, 각도의 코사인은  가까워집니다(즉,  가까워집니다).</p></li></ol><p>이제 두 벡터 간의 코사인 유사도를 계산하는 방법을 알았고 결과 값을 해석하는 방법을 알았으므로, 동일한 샘플 벡터  와  을 재사용하고 앞에서 본 공식 (4)를 사용하여 코사인 유사도를 계산할 수 있습니다.</p><p>  가까운  코사인 유사도를 얻었는데, 이는 두 벡터가 어느 <strong>정도 유사하다는</strong> 의미, 즉 완벽하게 유사하지는 않지만 완전히 무관한 것도 아니며 반대되는 의미도 아니라는 것을 의미합니다.</p><p>코사인 유사도 값에서 양수 점수를 도출하려면 다음 공식(5)을 사용하여  구간 내에서 진동하는 코사인 유사도 값을  구간의 점수로 변환해야 합니다:</p><p>따라서 샘플 벡터  및  의 점수는 다음과 같습니다: \ .</p><h3>도트 제품 유사성</h3><p>코사인 유사도의 한 가지 단점은 두 벡터 사이의 각도만 고려하고 크기는 고려하지 않는다는 것입니다. 즉, 두 벡터가 대략 같은 방향을 가리키지만 한 벡터가 다른 벡터보다 훨씬 길면 둘 다 비슷한 것으로 간주됩니다. 스칼라 또는 내적 곱 유사도라고도 하는 도트 곱 유사도는 벡터의 각도와 크기를 모두 고려하여 보다 정확한 유사도 메트릭을 제공함으로써 이를 개선합니다. 벡터의 크기를 무관하게 만들기 위해 도트 곱 유사성은 벡터를 먼저 정규화해야 하므로 궁극적으로 단위 길이 1의 벡터만 비교하게 됩니다.</p><p>이전과 동일한 두 사람으로 다시 설명해 보겠습니다. 이번에는 두 사람의 시야 범위(즉, 방의 반경)가 정확히 같도록 원형 방 한가운데에 두 사람을 배치해 보겠습니다. 코사인 유사도와 마찬가지로, 같은 방향을 향해 더 많이 돌수록(즉, 벡터가 가까워질수록) 시야가 더 많이 겹칩니다. 그러나 코사인 유사성과는 반대로 두 벡터의 길이가 같고 두 영역의 표면이 같기 때문에 두 사람이 같은 거리에 위치한 똑같은 그림을 보고 있다는 뜻입니다. 이 두 영역이 얼마나 잘 겹치는지는 도트 제품의 유사성을 나타냅니다.</p><p>도트 곱 유사도 공식을 소개하기 전에 벡터를 어떻게 정규화할 수 있는지 간단히 살펴보겠습니다. 매우 간단하며 두 가지 간단한 단계로 완료할 수 있습니다:</p><ol><li><p>벡터의 크기를 계산합니다.</p></li><li><p>각 구성 요소를 1에서 얻은 크기로 나눕니다.</p></li></ol><p>벡터  앞서 코사인 유사성을 검토할 때 보았던 것처럼 그 크기  를 계산할 수 있습니다(즉, ). 그런 다음 벡터의 각 구성 요소를 그 크기로 나누면 다음과 같은 정규화된 벡터  를 구할 수 있습니다:</p><p>두 번째 벡터   가 생성됩니다:</p><p>도트 곱 유사도 공식을 도출하기 위해 아래와 같이 공식 (4)를 사용하여 정규화된 벡터  와  간의 코사인 유사도를 계산할 수 있습니다:</p><p>이제 두 정규화된 벡터의 크기가 모두 , 도트 곱 유사도 공식(6)은 간단히 두 정규화된 벡터의 도트 곱이 됩니다:</p><p>아래 이미지에서 정규화된 벡터  와  을 보면 한 벡터를 다른 벡터에 투영한 도트 곱의 유사성을 빨간색으로 표시할 수 있습니다.</p><p>새로운 공식 (6)을 사용하면 정규화된 두 벡터의 도트 곱 유사도를 계산할 수 있으며, 당연히 코사인과 정확히 동일한 유사도 값을 산출할 수 있습니다:</p><p>도트 곱 유사성을 활용할 때 벡터에 플로트 값이 포함되어 있는지 바이트 값이 포함되어 있는지에 따라 점수가 다르게 계산됩니다. 전자의 경우 아래 공식 (7)을 사용하여 코사인 유사도와 동일한 방식으로 점수를 계산합니다:</p><p>그러나 벡터가 바이트 값으로 구성된 경우에는 아래 공식 (8)과 같이 점수가 약간 다르게 계산되며, 여기서  벡터의 차원 수입니다:</p><p>또한 정확한 점수를 산출하기 위한 한 가지 제약 조건은 쿼리 벡터를 포함한 모든 벡터의 길이가 같아야 하지만 반드시 1은 아니어야 한다는 것입니다.</p><h3>내부 제품 유사성 극대화</h3><p>릴리스 8.11부터 벡터를 정규화할 필요가 없다는 점에서 도트 곱 유사도보다 제약이 덜한 새로운 유사도 함수가 추가되었습니다. 그 주된 이유는 <a href="https://www.elastic.co/kr/search-labs/blog/lucene-bringing-maximum-inner-product-to-lucene">다음 글</a>에서 자세히 설명하지만, 간단히 요약하자면 특정 데이터 세트는 벡터를 정규화하는 데 적합하지 않으며(예: <a href="https://www.elastic.co/kr/search-labs/blog/elasticsearch-cohere-embeddings-support">Cohere 임베딩</a>), 그렇게 하면 연관성 문제가 발생할 수 있습니다.</p><p>최대 내적 곱 유사도를 계산하는 공식은 점 곱 1(6)과 정확히 동일합니다. 아래 공식 (9)와 같이 유사성이 양수인지 음수인지에 따라 수식이 달라지는 조각별 함수를 사용하여 최대 내적 곱 유사성을 스케일링하여 점수를 계산하는 방식이 변경됩니다:</p><p>이 조각 함수는  구간에서 음의 최대 내적 유사도 값을 모두 스케일링하고  구간에서 양의 값을 모두 스케일링하는 기능을 수행합니다.</p><h2>요약</h2><p>수학적으로 말하자면 꽤나 어려운 과정이었지만, 여기 몇 가지 유용한 팁이 있습니다.</p><p>어떤 유사도 함수를 사용할 수 있는지는 궁극적으로 벡터 임베딩의 정규화 여부에 따라 달라집니다. 벡터가 이미 정규화되어 있거나 데이터 세트가 벡터 정규화와 무관한 경우(즉, 관련성이 저하되지 않는 경우) 벡터를 정규화하고 도트 곱 유사성을 사용하면 각 벡터의 길이를 계산할 필요가 없으므로 코사인보다 훨씬 빠르게 계산할 수 있습니다. 수백만 개의 벡터를 비교할 때 이러한 계산은 상당히 많이 합산될 수 있습니다.</p><p>벡터가 정규화되지 않은 경우 두 가지 옵션이 있습니다:</p><ol><li><p>벡터를 정규화할 수 없는 경우 코사인 유사도를 사용합니다.</p></li><li><p>벡터의 크기가 의미를 지니고 있어 점수에 기여하도록 하려면 새로운 최대 내적 곱 유사성을 사용합니다(예: Cohere 임베딩).</p></li></ol><p>이 시점에서 벡터 임베딩 간의 거리 또는 유사성을 계산하고 점수를 도출하는 방법을 이해하면 이해가 쉬울 것입니다. 이 글이 도움이 되었기를 바랍니다.</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/vector-similarity-measures-and-scoring</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Valentin Crettaz]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5a3e9d39849d3ed/6a17da31a2929960a3d02b3f/4d9e89678798b9de68357b5cc06dbbd8b9c6e5e8-1440x823.webp" length="0" type="image/webp"/>
    <pubDate>Mon, 13 May 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와 Go로 고퍼 헌팅을 위한 하이브리드 검색 사용]]></title>
    <description><![CDATA[Elasticsearch와 Elasticsearch Go 클라이언트를 사용해 키워드 검색과 벡터 검색을 결합하여 하이브리드 검색을 수행하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>이 시리즈의 이전 파트에서는 <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">기존 키워드 검색과</a> <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">벡터 검색에</a> Elasticsearch Go 클라이언트를 사용하는 방법을 보여드렸습니다. 세 번째 파트에서는 하이브리드 검색을 다룹니다. <a href="https://www.elastic.co/elasticsearch/">Elasticsearch와 Elasticsearch</a> <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Go 클라이언트를</a> 사용해 벡터 검색과 키워드 검색을 모두 결합하는 <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">방법에 대한 예를</a> 공유해드리겠습니다.</p><h2>필수 구성 요소</h2><p>이 시리즈의 1부와 마찬가지로 이 예제에도 다음과 같은 전제 조건이 필요합니다:</p><ol><li><p>Go 버전 1.21 이상 설치</p></li><li><p><a href="https://go.dev/doc/code">Go 문서에서</a>다루는 권장 구조와 패키지 관리를 사용하여 자신만의 Go 리포지토리를 만드세요.</p></li><li><p>위키피디아에서 친숙한 Gopher를 포함해 설치류 <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch#sources">기반</a> <a href="https://en.wikipedia.org/wiki/Gopher">페이지 세트로</a> 채워진 자체 Elasticsearch 클러스터를 생성합니다:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="위키피디아 고퍼 페이지" /><h2>Elasticsearch에 연결</h2><p>다시 한 번 말씀드리지만, 이 예제에서는 Go 클라이언트에서 제공하는 <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">Typed API를</a> 사용하겠습니다. 쿼리에 대한 보안 연결을 설정하려면 다음 중 하나를 사용하여 클라이언트를 구성해야 합니다:</p><ol><li><p>Elastic Cloud를 사용하는 경우 클라우드 ID 및 API 키</p></li><li><p>클러스터 URL, 사용자 이름, 비밀번호 및 인증서</p></li></ol><p>Elastic Cloud에 위치한 클러스터에 연결하면 다음과 같이 됩니다:</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>그러면 다음 섹션에서 설명하는 것처럼 <code>client</code> 연결을 검색에 사용할 수 있습니다.</p><h2>하이브리드 검색을 위한 수동 부스팅</h2><p>검색 알고리즘 세트를 결합할 때 기존 방식은 각 쿼리 유형을 강화하기 위해 상수를 수동으로 구성하는 것이었습니다. 구체적으로, 각 쿼리에 대해 요인을 지정하고 결합된 결과 집합을 예상 집합과 비교하여 쿼리의 리콜을 결정합니다. 그런 다음 여러 요소 집합에 대해 반복하여 원하는 상태에 가장 가까운 요소를 선택합니다.</p><p>예를 들어, 아래 예시와 같이 <code>0.8</code> 계수가 부스트된 단일 텍스트 검색 쿼리와 <code>0.2</code> 계수가 낮은 knn 쿼리를 결합하려면 두 쿼리 유형에 모두 <code>Boost</code> 필드를 지정하면 됩니다:</p>func HybridSearchWithBoost(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10
	var knnBoost float32 = 0.2
	var queryBoost float32 = 0.8

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			Boost:         &amp;knnBoost,
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {
					Query: term,
					Boost: &amp;queryBoost,
				},
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p>각 쿼리에 대해 <code>Boost</code> 옵션에 지정된 요소가 문서 점수에 추가됩니다. 검색 쿼리의 점수를 knn 쿼리보다 더 큰 폭으로 높이면 키워드 쿼리의 결과에 더 많은 가중치가 부여됩니다.</p><p>특히 검색 전문가가 아닌 경우 수동 부스팅의 어려움은 원하는 결과 집합으로 이어지는 요소를 파악하기 위해 튜닝이 필요하다는 점입니다. 원하는 결과 집합에 가장 근접한 값이 무엇인지 확인하기 위해 임의의 값을 시도해 보는 것입니다.</p><h2>하이브리드 검색의 상호 순위 퓨전 &amp; Go 클라이언트</h2><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">상호 순위 퓨전</a>(RRF)은 Elasticsearch 8.9의 하이브리드 검색을 위한 기술 미리보기로 출시되었습니다. 튜닝과 관련된 학습 곡선을 줄이고 결과 집합을 최적화하기 위해 요소를 실험하는 시간을 줄이는 것을 목표로 합니다.</p><p>RRF를 사용하면 아래 알고리즘에 따라 점수를 혼합하여 문서 점수를 다시 계산합니다:</p>score := 0.0
// q is a query in the set of queries (vector and keyword search)
for _, q := range queries {
    // result(q) is the results 
    if document in result(q) {
        // k is a ranking constant (default 60)
        // rank(result(q), d) is the document's rank within result(q) 
        // range from 1 to the window_size (default 100)
        score +=  1.0 / (k + rank(result(q), d))
    }
}

return score
<p>RRF 사용의 장점은 Elasticsearch 내에서 합리적인 기본값을 사용할 수 있다는 것입니다. 순위 상수 <code>k</code> 기본값은 <code>60</code> 입니다. 대규모 데이터 집합을 검색할 때 반환된 문서의 관련성과 쿼리 성능 간의 균형을 맞추기 위해 고려되는 각 쿼리에 대한 결과 집합의 크기는 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html#rrf-api">문서에</a> 설명된 대로 기본값이 <code>100</code> 인 <code>window_size</code> 의 값으로 제한됩니다.</p><p><code>k</code> 및 <code>windows_size</code> 는 아래 예시와 같이 Go 클라이언트의 <code>Rank</code> 메소드 내 <code>Rrf</code> 설정에서도 구성할 수 있습니다:</p>func HybridSearchWithRRF(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	var k = 10
	var numCandidates = 10

	// Minimum required window size for the default result size of 10
	var windowSize int64 = 10
	var rankConstant int64 = 42

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
			Field:         "text_embedding.predicted_value",
			K:             &amp;k,
			NumCandidates: &amp;numCandidates,
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).
		Query(&amp;types.Query{
			Match: map[string]types.MatchQuery{
				"title": {Query: term},
			},
		}).
		Rank(&amp;types.RankContainer{
			Rrf: &amp;types.RrfRank{
				WindowSize:   &amp;windowSize,
				RankConstant: &amp;rankConstant,
			},
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<h2>결론</h2><p>여기서는 Elasticsearch <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Go 클라이언트를 사용하여 Elasticsearch에서 벡터 검색과 키워드 검색을 결합하는</a> 방법에 대해 설명했습니다.</p><p>이 시리즈의 모든 코드는 <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 리포지토리에서</a> 확인하세요. 아직 읽어보지 않았다면 이 시리즈의 모든 코드를 <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">파트 1과</a> <a href="https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client">파트 2에서</a> 확인하세요.</p><p><em>즐거운 고퍼 사냥 되세요!</em></p><h2>리소스</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 가이드</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 클라이언트</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">벡터 검색이란 무엇인가요? | Elastic</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/rrf.html">상호 등급 융합</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 02 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Elasticsearch Go 클라이언트로 Elasticsearch에서 벡터 검색 수행]]></title>
    <description><![CDATA[실제 예제를 통해 Elasticsearch Go 클라이언트를 사용하여 Elasticsearch에서 벡터 검색을 수행하는 방법을 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>Go를 포함한 모든 프로그래밍 언어로 소프트웨어를 구축하는 것은 평생에 걸친 학습에 전념하는 것입니다. 대학과 직장 경력을 통해 Carly는 벡터 검색의 최신 및 최고의 구현을 포함하여 많은 프로그래밍 언어와 기술을 다뤄왔습니다. 하지만 그것만으로는 충분하지 않았습니다! 그래서 최근에 칼리도 바둑을 시작했습니다.</p><p>동물, 프로그래밍 언어, 친근한 작가와 마찬가지로 검색도 검색 사용 사례에 따라 결정하기 어려울 정도로 다양한 방식으로 진화해 왔습니다. 이 블로그에서는 벡터 검색에 <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">대한 개요와</a> <a href="https://www.elastic.co/elasticsearch/">함께 Elasticsearch와</a> <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 클라이언트를</a> 사용한 각 접근 방식의 예를 공유하겠습니다. 이 예제에서는 Elasticsearch와 Go에서 벡터 검색을 사용해 고퍼를 찾고 고퍼가 무엇을 먹는지 알아내는 방법을 보여드립니다.</p><h2>필수 구성 요소</h2><p>이 예제를 따라 하려면 다음 전제 조건이 충족되는지 확인하세요:</p><ol><li><p>Go 버전 1.21 이상 설치</p></li><li><p>를 사용하여 나만의 Go 리포지토리를 생성합니다.</p></li><li><p>위키피디아에서 친숙한 <a href="https://en.wikipedia.org/wiki/Gopher">Gopher를</a> 포함한 설치류 기반 페이지 세트로 채워진 자체 Elasticsearch 클러스터를 생성합니다:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a436c42996e5172/6a1704cf0c48573d1c01a957/34fa81a9b4c292634c719b8303a9b6b7506d7920-1440x662.png" alt="위키피디아 고퍼 페이지" /><h2>Elasticsearch에 연결</h2><p>이 예제에서는 Go 클라이언트에서 제공하는 <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/typedapi.html">Typed API를</a> 사용하겠습니다. 쿼리에 대한 보안 연결을 설정하려면 다음 중 하나를 사용하여 클라이언트를 구성해야 합니다:</p><ol><li><p>Elastic Cloud를 사용하는 경우 클라우드 ID와 API 키입니다.</p></li><li><p>클러스터 URL, 사용자 이름, 비밀번호 및 인증서를 입력합니다.</p></li></ol><p>Elastic Cloud에 위치한 클러스터에 연결하면 다음과 같이 됩니다:</p>func GetElasticsearchClient() (*elasticsearch.TypedClient, error) {
	var cloudID = os.Getenv("ELASTIC_CLOUD_ID")
	var apiKey = os.Getenv("ELASTIC_API_KEY")

	var es, err = elasticsearch.NewTypedClient(elasticsearch.Config{
		CloudID: cloudID,
		APIKey:  apiKey,
		Logger:  &amp;elastictransport.ColorLogger{os.Stdout, true, true},
	})

	if err != nil {
		return nil, fmt.Errorf("unable to connect: %w", err)
	}

	return es, nil
}
<p>그런 다음 다음 섹션에 표시된 것처럼 <code>client</code> 연결을 벡터 검색에 사용할 수 있습니다.</p><h2>벡터 검색</h2><p>벡터 검색은 벡터를 사용하여 검색 문제를 수학적 비교로 변환하여 이 문제를 해결하려고 시도합니다. 문서 임베딩 프로세스에는 모델을 사용하여 문서를 고밀도 벡터 표현 또는 단순히 숫자 스트림으로 변환하는 추가 단계가 있습니다. 이 접근 방식의 장점은 이미지나 오디오와 같은 텍스트가 아닌 문서를 쿼리와 함께 벡터로 변환하여 검색할 수 있다는 점입니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4b3b9b2ad1e8f46/6a1704d06234e0800fdb1927/b113358093e367358d684f7aaf0a6684ebb2d0dd-1440x653.png" alt="벡터 검색 다이어그램" /><p>간단히 말해서 벡터 검색은 벡터 거리 계산의 집합입니다. 아래 그림에서는 쿼리 <code>Go Gopher</code>의 벡터 표현을 벡터 공간의 문서와 비교하여 가장 가까운 결과(상수 <code>k</code> 로 표시됨)를 반환합니다:</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c736ecfc49bb038/6a1704d2cf4f25bcb6b2d075/54a17a4b41029644e7c66f33d428e8d01a6f4ce3-1184x743.png" alt="Gopher 벡터 스페이스 예제" /><p>문서 임베딩을 생성하는 데 사용하는 접근 방식에 따라 고퍼가 무엇을 먹는지 알아내는 방법은 두 가지가 있습니다.</p><h3>접근 방식 1: 나만의 모델 가져오기</h3><p>플래티넘 라이선스를 사용하면 모델을 업로드하고 추론 API를 사용하여 Elasticsearch 내에서 임베딩을 생성할 수 있습니다. 모델을 설정하는 데는 6단계가 있습니다:</p><ol><li><p>모델 리포지토리에서 업로드할 PyTorch 모델을 선택합니다. 이 예제에서는 임베딩을 생성하기 위해 Hugging Face의 <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">문장 트랜스포머/msmarco-MiniLM-L-12-v3를</a> 사용하고 있습니다.</p></li><li><p>Elasticsearch 클러스터에 대한 자격 증명과 작업 유형 <code>text_embeddings</code> 을 사용하여 <a href="https://www.elastic.co/guide/en/elasticsearch/client/eland/current/overview.html">Python용 Eland Machine Learning 클라이언트를</a> 사용하여 모델을 Elastic에 로드합니다. Eland가 설치되어 있지 않은 경우 아래와 같이 <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-import-model.html#ml-nlp-import-docker">Docker를 사용하여 가져오기 단계를 실행할</a> 수 있습니다:</p></li></ol>docker run -it --rm --network host \
    docker.elastic.co/eland/eland \
    eland_import_hub_model \
      --cloud-id $ELASTIC_CLOUD_ID \
      --es-api-key $ELASTIC_API_KEY \
      --hub-model-id sentence-transformers/msmarco-MiniLM-L-12-v3 \
      --task-type text_embedding
<ol><li><p>업로드가 완료되면 샘플 문서로 <code>sentence-transformers__msmarco-minilm-l-12-v3</code> 모델을 빠르게 테스트하여 임베딩이 예상대로 생성되는지 확인합니다:</p></li></ol><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8a399e1f50ecfdaf/6a1704d4ab7f08935edb9d78/fbbdde621361a487eb07304bed029c228d0f7aa6-1440x789.png" alt="Elastic Test 학습된 모델 예시" /><ol><li><p>추론 프로세서가 포함된 수집 파이프라인을 만듭니다. 이렇게 하면 업로드한 모델을 사용하여 벡터 표현을 생성할 수 있습니다:</p></li></ol>PUT _ingest/pipeline/search-rodents-vector-embedding-pipeline
{
  "processors": [
    {
      "inference": {
        "model_id": "sentence-transformers__msmarco-minilm-l-12-v3",
        "target_field": "text_embedding",
        "field_map": {
          "body_content": "text_field"
        }
      }
    }
  ]
}
<ol><li><p>각 문서에 대해 생성된 벡터 임베딩을 저장하기 위해 <code>dense_vector</code> 유형의 <code>text_embedding.predicted_value</code> 필드를 포함하는 새 인덱스를 만듭니다:</p></li></ol>PUT vector-search-rodents
{
  "mappings": {
    "properties": {
      "text_embedding.predicted_value": {
        "type": "dense_vector",
        "dims": 384,
        "index": true,
        "similarity": "cosine"
      },
      "text": {
        "type": "text"
      }
    }
  }
}
<ol><li><p>새로 생성된 수집 파이프라인을 사용하여 문서를 재색인하여 각 문서에 추가 필드 <code>text_embedding.predicted_value</code> 로 텍스트 임베딩을 생성합니다:</p></li></ol>POST _reindex
{
  "source": {
    "index": "search-rodents"
  },
  "dest": {
    "index": "vector-search-rodents",
    "pipeline": "search-rodents-vector-embedding-pipeline"
  }
}
<p>이제 아래 예시와 같이 새 인덱스 <code>vector-search-rodents</code> 를 사용하여 동일한 검색 API에서 <code>Knn</code> 옵션을 사용할 수 있습니다:</p>func VectorSearch(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Generate query vector using the same model used in the inference processor
			QueryVectorBuilder: &amp;types.QueryVectorBuilder{
				TextEmbedding: &amp;types.TextEmbedding{
					ModelId:   "sentence-transformers__msmarco-minilm-l-12-v3",
					ModelText: term,
				},
			}}).Do(context.Background())

	if err != nil {
		return nil, fmt.Errorf("error in rodents vector search: %w", err)
	}

	return getRodents(res.Hits.Hits)
}
<p>마샬링 해제를 통해 JSON 결과 객체를 변환하는 것은 키워드 검색 예제와 똑같은 방식으로 수행됩니다. 상수 <code>K</code> 및 <code>NumCandidates</code> 을 사용하여 반환할 이웃 문서의 수와 샤드당 고려할 후보의 수를 구성할 수 있습니다. 후보자 수를 늘리면 결과의 정확도는 높아지지만 더 많은 비교가 수행되므로 쿼리 실행 시간이 길어집니다.</p><p><code>What do Gophers eat?</code> 쿼리를 사용하여 코드를 실행하면 반환되는 결과는 아래와 유사하게 표시되며, 이전 키워드 검색과 달리 Gopher 문서에 요청된 정보가 포함되어 있음을 강조합니다:</p>[
  {ID:64f74ecd4acb3df024d91112 Title:Gopher - Wikipedia Url:https://en.wikipedia.org/wiki/Gopher} 
  {ID:64f74ed34acb3d71aed91fcd Title:Squirrel - Wikipedia Url:https://en.wikipedia.org/wiki/Squirrel} 
  //Other results omitted
]
<h3>접근 방식 2: 허깅 얼굴 추론 API</h3><p>또 다른 옵션은 Elasticsearch 외부에서 동일한 임베딩을 생성하여 문서의 일부로 수집하는 것입니다. 이 옵션은 Elasticsearch 머신 러닝 노드를 사용하지 않으므로 무료 티어에서 수행할 수 있습니다.</p><p>허깅 페이스는 무료로 사용할 수 있는 속도 제한이 없는 <a href="https://huggingface.co/docs/api-inference/index">추론 API를</a> 제공하며, 계정과 API 토큰을 사용하면 실험 및 프로토타이핑을 위해 동일한 임베딩을 수동으로 생성하여 시작할 수 있습니다. 프로덕션용으로는 권장하지 않습니다. 로컬에서 자체 모델을 호출하여 임베딩을 생성하거나 유료 API를 사용하는 것도 비슷한 방식으로 수행할 수 있습니다.</p><p>아래 함수 <code>GetTextEmbeddingForQuery</code> 에서는 쿼리 문자열에 대한 추론 API를 사용하여 <code>POST</code> 요청에서 엔드포인트로 반환된 벡터를 생성합니다:</p>// HuggingFace text embedding helper
func GetTextEmbeddingForQuery(term string) []float32 {
    // HTTP endpoint
    model := "sentence-transformers/msmarco-minilm-l-12-v3"
    posturl := fmt.Sprintf("https://api-inference.huggingface.co/pipeline/feature-extraction/%s", model)

    // JSON body
    body := []byte(fmt.Sprintf(`{
        "inputs": "%s",
        "options": {"wait_for_model":True}
    }`, term))

    // Create a HTTP post request
    r, err := http.NewRequest("POST", posturl, bytes.NewBuffer(body))

    if err != nil {
        log.Fatal(err)
        return nil
    }

    token := os.Getenv("HUGGING_FACE_TOKEN")
    r.Header.Add("Authorization", fmt.Sprintf("Bearer %s", token))

    client := &amp;http.Client{}
    res, err := client.Do(r)
    if err != nil {
        panic(err)
    }

    defer res.Body.Close()

    var post []float32
    derr := json.NewDecoder(res.Body).Decode(&amp;post)

    if derr != nil {
        log.Fatal(derr)
        return nil
    }

    return post
}
<p>그런 다음 <code>[]float32</code> 유형의 결과 벡터가 <code>QueryVectorBuilder</code> 옵션을 사용하는 대신 <code>QueryVector</code> 으로 전달되어 이전에 Elastic에 업로드한 모델을 활용합니다.</p>func VectorSearchWithGeneratedQueryVector(client *elasticsearch.TypedClient, term string) ([]Rodent, error) {
	vector, err := GetTextEmbeddingForQuery(term)
	if err != nil {
		return nil, err
	}

	if vector == nil {
		return nil, fmt.Errorf("unable to generate vector: %w", err)
	}

  var k = 10
	var numCandidates = 10

	res, err := client.Search().
		Index("vector-search-rodents").
		Knn(types.KnnSearch{
      # Field in document containing vector
			Field:         "text_embedding.predicted_value",
      # Number of neighbors to return
			K:             &amp;k,
      # Number of candidates to evaluate in comparison
			NumCandidates: &amp;numCandidates,
      # Query vector returned from Hugging Face inference API
			QueryVector:   vector,
		}).
		Do(context.Background())

	if err != nil {
		return nil, err
	}

	return getRodents(res.Hits.Hits)
}
<p><code>K</code> 및 <code>NumCandidates</code> 옵션은 두 옵션에 관계없이 동일하게 유지되며 동일한 모델을 사용하여 임베딩을 생성하기 때문에 동일한 결과가 생성됩니다.</p><h2>결론</h2><p>여기서는 Elasticsearch <a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Go 클라이언트를 사용하여 Elasticsearch에서 벡터 검색을 수행하는</a> 방법에 대해 설명했습니다. 이 시리즈의 모든 코드는 <a href="https://github.com/carlyrichmond/gopher-hunting-elasticsearch">GitHub 리포지토리에서</a> 확인하세요. <a href="https://www.elastic.co/search-labs/blog/hybrid-search-with-the-elasticsearch-go-client">3부에서는</a> <a href="https://www.elastic.co/search-labs/blog/perform-text-queries-with-the-elasticsearch-go-client">1부에서</a> 다룬 벡터 검색과 키워드 검색 기능을 Go에서 결합하는 방법에 대한 개요를 살펴봅니다.</p><p>그때까지 고퍼 사냥을 즐기세요!</p><h2>리소스</h2><ol><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html">Elasticsearch 가이드</a></p></li><li><p><a href="https://www.elastic.co/guide/en/elasticsearch/client/go-api/current/index.html">Elasticsearch Go 클라이언트</a></p></li><li><p><a href="https://www.elastic.co/what-is/vector-search">벡터 검색이란 무엇인가요? | Elastic</a></p></li></ol>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/perform-vector-search-with-the-elasticsearch-go-client</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <category><![CDATA[Go]]></category>
    <dc:creator><![CDATA[Carly Richmond,Laurent Saint-Félix]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdcae959696d55a9b/6a1704d5ab7f085a56db9d7c/491ef9efbb30b253e1d9e3b7f816a9a23c8f5264-721x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 01 Nov 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>
  <item>
    <title><![CDATA[Elasticsearch에서 NLP와 벡터 검색으로 챗봇 기능 강화하기]]></title>
    <description><![CDATA[벡터 검색과 NLP가 어떻게 챗봇 기능을 향상시키는지 살펴보고 Elasticsearch가 그 과정을 어떻게 촉진하는지 알아보세요.]]></description>
    <content:encoded><![CDATA[<p>대화형 인터페이스는 한동안 사용되어 왔으며 고객 서비스, 정보 검색, 작업 자동화 등 다양한 작업을 지원하는 수단으로 점점 더 인기를 얻고 있습니다. 일반적으로 음성 어시스턴트나 메시징 앱을 통해 액세스하는 이러한 인터페이스는 사용자가 보다 효율적으로 쿼리를 해결할 수 있도록 사람의 대화를 시뮬레이션합니다.</p><p>기술이 발전함에 따라 챗봇은 사용자에게 개인화된 경험을 제공하면서 더 복잡한 작업을 신속하게 처리하는 데 사용됩니다. 자연어 처리(NLP)를 통해 챗봇은 사용자의 언어를 처리하고 메시지의 의도를 파악하여 관련 정보를 추출할 수 있습니다. 예를 들어, 명명된 개체 인식은 텍스트의 주요 정보를 일련의 카테고리로 분류하여 추출합니다. 감정 분석은 감정 어조를 식별하고, 질문은 쿼리에 대한 '답변'을 식별합니다. NLP의 목표는 알고리즘이 인간의 언어를 처리하고 대량의 텍스트에서 관련 구절 찾기, 텍스트 요약, 새롭고 독창적인 콘텐츠 생성 등 기존에는 인간만이 할 수 있었던 작업을 수행할 수 있도록 하는 것입니다.</p><p>이러한 고급 NLP 기능은 <a href="https://www.elastic.co/what-is/vector-search">벡터 검색이라는</a> 기술을 기반으로 합니다. Elastic은 벡터 검색을 기본적으로 지원하여 정확하고 근사한 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search">kNN(k-최근접 이웃) 검색을</a> 수행하고, NLP를 지원하여 사용자 정의 또는 <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-model-ref.html#ml-nlp-model-ref">타사 모델을</a> Elasticsearch에서 직접 사용할 수 있습니다.</p><p>이 블로그 게시물에서는 벡터 검색과 NLP가 어떻게 챗봇 기능을 향상시키는지 살펴보고 Elasticsearch가 그 과정을 어떻게 촉진하는지 보여드리겠습니다. 벡터 검색에 대한 간략한 개요부터 시작하겠습니다.</p><h2>벡터 검색</h2><p>인간은 문자로 된 언어의 의미와 문맥을 이해할 수 있지만, 기계는 그렇지 못합니다. 벡터가 필요한 이유입니다. 기계는 텍스트를 벡터 표현(텍스트의 의미를 수치로 표현한 것)으로 변환함으로써 이러한 한계를 극복할 수 있습니다. 기존 검색과 비교했을 때, 벡터는 빈도에 기반한 키워드와 어휘 검색에 의존하는 대신 숫자 값에 대해 정의된 연산을 사용하여 텍스트 데이터를 처리할 수 있습니다.</p><p>이를 통해 벡터 검색은 쿼리 벡터가 주어진 유사성을 나타내기 위해 "임베딩 공간" 의 거리를 사용하여 유사한 개념이나 컨텍스트를 공유하는 데이터를 찾을 수 있습니다. 데이터가 유사하면 해당 벡터도 비슷해집니다.</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53a615a8ba0ac931/6a17d795fbc5f8b257491910/08542abf8108aace288745b1aca8579b476ddc1b-1440x618.png" alt="" /><p>벡터 검색은 NLP 애플리케이션뿐만 아니라 이미지 및 동영상 처리 등 비정형 데이터가 관련된 다양한 영역에서도 활용되고 있습니다.</p><p>챗봇 플로우에서는 사용자의 쿼리에 대해 여러 가지 접근 방식이 있을 수 있으며, 그 결과 더 나은 사용자 경험을 위해 정보 검색을 개선할 수 있는 다양한 방법이 있습니다. 각 대안에는 고유한 장점과 단점이 있으므로 사용 가능한 데이터와 리소스, 교육 시간(해당되는 경우) 및 예상 정확도를 고려하는 것이 중요합니다. 다음 섹션에서는 질문 답변 NLP 모델에 대한 이러한 측면을 다룹니다.</p><h2>질문 답변</h2><p>질문 답변(QA) 모델은 자연어로 묻는 질문에 답변하도록 설계된 NLP 모델의 한 유형입니다. 사용자가 여러 리소스에서 답변을 추론해야 하는 질문이 있는데 문서에 이미 존재하는 목표 답변이 없는 경우, 제너레이티브 QA 모델이 유용할 수 있습니다. 그러나 이러한 모델은 계산 비용이 많이 들고 도메인 관련 학습에 많은 양의 데이터가 필요하기 때문에 도메인 외의 질문을 처리하는 데 특히 유용할 수 있지만 일부 상황에서는 실용성이 떨어질 수 있습니다.</p><p>반면에 사용자가 특정 주제에 대해 질문이 있고 실제 답변이 문서에 있는 경우 추출형 QA 모델을 사용할 수 있습니다. 이러한 모델은 소스 문서에서 직접 답변을 추출하여 투명하고 검증 가능한 결과를 제공하므로 간단하고 효율적인 방식으로 질문에 답하고자 하는 기업이나 조직에 보다 실용적인 옵션이 됩니다.</p><p>아래 예는 <a href="https://huggingface.co/deepset/minilm-uncased-squad2">Hugging Face에서 사용할 수</a> 있고 Elasticsearch에 배포된 사전 학습된 추출 QA 모델을 사용하여 주어진 컨텍스트에서 답을 추출하는 방법을 보여줍니다:</p>POST _ml/trained_models/deepset__minilm-uncased-squad2/deployment/_infer
{
    "docs": [{"text_field": "Canvas is a data visualization and presentation application within Kibana. With Canvas, live data can be pulled directly from Elasticsearch and combined with colors, images, text, and other customized options to create dynamic, multi-page displays."}],
    "inference_config": {"question_answering": {"question": "What is Kibana Canvas?"}}
}


{
  "predicted_value": "a data visualization and presentation application",
  "start_offset": 10,
  "end_offset": 59,
  "prediction_probability": 0.28304219431376443
}
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-deploy-models.html">학습된 모델을 배포합니다.</a></p><p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-ner-example.html#ex-ner-ingest">추론 수집 파이프라인에 모델을 추가합니다.</a></p><p>사용자 쿼리를 처리하고 정보를 검색하는 다양한 방법이 있으며, 비정형 데이터를 다룰 때는 여러 언어 모델과 데이터 소스를 사용하는 것이 효과적인 대안이 될 수 있습니다. 이를 설명하기 위해 선택한 문서에서 추출한 데이터를 고려한 답변으로 쿼리에 응답하는 데 사용되는 챗봇의 데이터 처리 예시가 있습니다.</p><h2>챗봇 데이터 처리: NLP 및 벡터 검색</h2><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta418a9c54bb16cf9/6a17d7975772624b371bca43/c2d1a2f110e937b1d3e5df0d5caac3c906c98fb0-1440x748.png" alt="" /><p>위와 같이 챗봇의 데이터 처리는 크게 세 부분으로 나눌 수 있습니다:</p><ul><li><p><strong>벡터 처리:</strong> 이 파트에서는 문서를 벡터 표현으로 변환합니다.</p></li><li><p><strong>사용자 입력 처리:</strong> 사용자 쿼리에서 관련 정보를 추출하고 시맨틱 검색 및 하이브리드 검색을 수행하는 부분입니다.</p></li><li><p><strong>최적화:</strong> 이 부분에는 모니터링이 포함되며 챗봇의 안정성, 최적의 성능 및 우수한 사용자 경험을 보장하는 데 매우 중요합니다.</p></li></ul><h2>벡터 처리</h2><p><strong>처리</strong> 부분의 첫 번째 단계는 각 문서의 구성 요소를 결정한 다음 각 요소를 벡터 표현으로 변환하는 것입니다. 이러한 표현은 다양한 데이터 형식에 대해 생성할 수 있습니다.</p><p>사전 학습된 모델과 라이브러리를 포함하여 임베딩을 계산하는 데 사용할 수 있는 다양한 방법이 있습니다.</p><p>이러한 표현에 대한 검색 및 검색의 효과는 기존 데이터와 사용된 방법의 품질 및 관련성에 따라 달라진다는 점에 유의하세요.</p><p>벡터가 계산되면 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dense-vector.html">dense_vector</a> 필드 유형으로 Elasticsearch에 저장됩니다.</p>PUT &lt;target&gt;
{
  "mappings": {
    "properties": {
      "doc_part_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "doc_part" : {
        "type" : "keyword"
      }
    }
  }
}
<h2>챗봇 사용자 입력 처리</h2><p><strong>사용자</strong> 입장에서는 질문을 받은 후 질문을 진행하기 전에 가능한 모든 정보를 추출하는 것이 유용합니다. 이는 사용자의 의도를 파악하는 데 도움이 되며, 이 경우 이를 지원하기 위해 <a href="https://huggingface.co/dslim/bert-base-NER">NER(Named Entity Recognition) 모델을</a> 사용하고 있습니다. NER은 명명된 엔티티를 식별하고 미리 정의된 엔티티 카테고리로 분류하는 프로세스입니다.</p>POST _ml/trained_models/dslim__bert-base-ner/deployment/_infer
{
  "docs": { "text_field": "How many people work for Elastic?"}
}


{
  "predicted_value": "How many people work for [Elastic](ORG&amp;Elastic)?",
  "entities": [
    {
      "entity": "Elastic",
      "class_name": "ORG",
      "class_probability": 0.4993975435876747,
      "start_pos": 25,
      "end_pos": 32
    }
  ]
}
<p>필수 단계는 아니지만 구조화된 데이터나 위 또는 다른 NLP 모델 결과를 사용하여 사용자의 쿼리를 분류하면 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#knn-search-filter-example">필터를</a> 사용하여 kNN 검색을 제한할 수 있습니다. 이렇게 하면 처리해야 하는 데이터의 양을 줄여 성능과 정확도를 향상시킬 수 있습니다.</p>    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
<h2>시맨틱 검색 및 하이브리드 검색</h2><p>프롬프트는 사용자 쿼리에서 시작되고 챗봇은 가변성과 모호성이 있는 인간의 언어를 처리해야 하므로 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#semantic-search">시맨틱 검색이</a> 매우 적합합니다. Elasticsearch에서는 쿼리 문자열과 <a href="https://huggingface.co/sentence-transformers/msmarco-MiniLM-L-12-v3">임베딩 모델의</a> ID를 쿼리 벡터 빌더 객체에 전달하여 한 단계로 시맨틱 검색을 수행할 수 있습니다. 그러면 쿼리를 벡터화하고 kNN <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html">검색을</a> 수행하여 쿼리의 의미에 가장 가까운 상위 k개의 일치 항목을 검색합니다:</p>POST /&lt;target&gt;/_search
{
  "knn": {
    "field": "doc_part_vector",
    "k": 5,
    "num_candidates": 20,
    "query_vector_builder": {
      "text_embedding": {
        "model_id": "&lt;text-embedding-model-id&gt;",
        "model_text": "&lt;query_string&gt;"
      }
    }
  }
 }
<p><a href="https://www.elastic.co/guide/en/machine-learning/8.7/ml-nlp-text-emb-vector-search-example.html">엔드투엔드 예제: 텍스트 임베딩 모델을 배포하고 이를 시맨틱 검색에 사용하는 방법.</a> Elasticsearch는 <strong>희소 모델인</strong> Okapi BM25의 Lucene 구현을 사용해 텍스트 쿼리의 관련성 순위를 매기고, <strong>고밀도 모델은</strong> <strong>시맨틱 검색에</strong> 사용합니다. 텍스트 쿼리에서 <strong> 얻은,</strong> <strong>벡터</strong> 일치와 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html#_combine_approximate_knn_with_other_features"><strong>일치의 강점을</strong></a> <strong>모두 결합하려면 하이브리드 검색을 수행할</strong> 수 있습니다:</p>POST &lt;target&gt;/_search
{
  "query": {
          "match": {
            "content": {
              "query": "&lt;query_string&gt;"
            }
        }
  },
  "knn": {
    "field": "doc_part_vector",
    "query_vector_builder": {
      "text_embedding": {
    "model_id": "&lt;text-embedding-model-id&gt;",
     "model_text": "&lt;query_string&gt;"
      }
    },
    "filter": {
      "term": {
        "org": "Elastic"
      }
    }
  }
}
<h3>희소 모델과 고밀도 모델을 결합하면 최상의 결과를 얻을 수 있는 경우가 많습니다.</h3><p>희소 모델은 일반적으로 짧은 쿼리와 특정 용어에서 더 나은 성능을 발휘하는 반면, 고밀도 모델은 컨텍스트와 연관성을 활용합니다. 이러한 방법이 서로 어떻게 비교되고 보완되는지 자세히 알아보려면 검색을 위해 특별히 훈련된 두 가지 고밀도 모델과 BM25를 비교하여 벤치마킹해 보세요.</p><p>가장 관련성이 높은 결과는 일반적으로 사용자에게 제공된 첫 번째 답변일 수 있으며,_score는 반환된 문서의 <strong>관련성을</strong> 결정하는 데 사용되는 숫자입니다.</p><h2>챗봇 최적화</h2><p>챗봇의 사용자 경험, 성능 및 안정성을 개선하기 위해 하이브리드 스코어링을 적용하는 것 외에도 다음과 같은 접근 방식을 통합할 수 있습니다: <strong>감정 분석:</strong> 대화가 전개되는 동안 사용자의 댓글과 반응을 파악하기 위해 <a href="https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english">감성 분석 모델을</a> 통합할 수 있습니다:</p>POST _ml/trained_models/distilbert-base-uncased-finetuned-sst-2-english/deployment/_infer
{
  "docs": { "text_field": "That was not my question!"}
}


{
  "predicted_value": "NEGATIVE",
  "prediction_probability": 0.980080439016437
}
<p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data"><strong>GPT의 기능</strong></a> <strong>:</strong> 전반적인 경험을 향상시키기 위한 대안으로, Elasticsearch의 검색 정확도와 OpenAI의 GPT 질문 답변 기능을 결합하여 <a href="https://platform.openai.com/docs/guides/chat">채팅 완료 API를</a> 활용하여 이러한 상위 k 문서를 컨텍스트로 고려하여 사용자 모델이 생성한 응답으로 돌아갈 수 있습니다. <em>프롬프트: "이 질문에 답하세요 &lt;user_question&gt; 이 문서만 사용 &lt;top_search_result&gt;"</em></p><p><strong>관찰 가능성:</strong> 챗봇의 성능을 보장하는 것은 매우 중요하며, 이를 위해서는 모니터링이 필수적인 요소입니다. 챗봇 상호작용을 캡처하는 로그 외에도 응답 시간, 지연 시간 및 기타 관련 챗봇 메트릭을 추적하는 것이 중요합니다. 이를 통해 패턴과 추세를 파악하고 이상 징후를 탐지할 수 있으며<a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">, Elastic Observability</a> 도구를 사용하면 이러한 정보를 수집하고 분석할 수 있습니다.</p><h2>요약</h2><p>이 블로그 게시물에서는 NLP와 벡터 검색이 무엇인지 살펴보고 문서의 벡터 표현에서 추출한 데이터를 고려하여 사용자 쿼리에 응답하는 데 사용되는 챗봇의 예를 살펴봅니다.</p><p>앞서 살펴본 바와 같이 챗봇은 NLP와 벡터 검색을 사용하여 정형화된 타깃 데이터를 넘어서는 복잡한 작업을 수행할 수 있습니다. 여기에는 여러 데이터 소스 및 형식을 컨텍스트로 사용하여 특정 제품 또는 비즈니스 관련 쿼리에 대한 추천 및 답변을 제공하는 동시에 개인화된 사용자 경험을 제공하는 것도 포함됩니다.</p><p>사용 사례는 고객의 문의를 지원하는 고객 서비스 제공부터 단계별 안내, 권장 사항 제안 또는 작업 자동화를 통해 개발자의 문의를 돕는 것까지 다양합니다. 목표와 기존 데이터에 따라 다른 모델과 방법을 활용하여 더 나은 결과를 달성하고 전반적인 사용자 경험을 개선할 수도 있습니다.</p><p>다음은 유용할 수 있는 주제에 대한 몇 가지 링크입니다:</p><ol><li><p><a href="https://www.elastic.co/blog/how-to-deploy-natural-language-processing-nlp-getting-started">자연어 처리(NLP)를 배포하는 방법: 시작하기</a></p></li><li><p><a href="https://www.elastic.co/blog/overview-image-similarity-search-in-elastic">Elasticsearch의 이미지 유사도 검색 개요</a></p></li><li><p><a href="https://www.elastic.co/blog/chatgpt-elasticsearch-openai-meets-private-data">ChatGPT와 Elasticsearch: OpenAI와 개인 데이터의 만남</a></p></li><li><p><a href="https://www.elastic.co/blog/monitor-openai-api-gpt-models-opentelemetry-elastic">OpenTelemetry와 Elastic을 통한 OpenAI API 및 GPT 모델 모니터링</a></p></li><li><p><a href="https://www.elastic.co/blog/why-technology-leaders-need-vector-search">IT 리더가 검색 환경을 개선하기 위해 벡터 검색이 필요한 5가지 이유</a></p></li></ol><p>Elasticsearch에 NLP와 기본 벡터 검색을 통합하면 속도, 확장성, 검색 기능을 활용하여 정형 또는 비정형 데이터에 관계없이 대량의 데이터를 처리할 수 있는 매우 효율적이고 효과적인 챗봇을 만들 수 있습니다.</p><p>시작할 준비가 되셨나요? <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><p><em>이 블로그 게시물에서는 해당 소유자가 소유하고 운영하는 타사 생성 AI 도구를 사용했거나 참조했을 수 있습니다. Elastic은 타사 도구에 대한 어떠한 통제권도 없으며 해당 도구의 콘텐츠, 운영 또는 사용에 대해 어떠한 책임이나 의무도 지지 않으며 그러한 도구의 사용으로 인해 발생할 수 있는 손실이나 손해에 대해서도 책임을 지지 않습니다. 개인 정보, 민감한 정보 또는 기밀 정보가 포함된 AI 도구를 사용할 때는 주의를 기울여 주세요. 제출하는 모든 데이터는 AI 학습 또는 기타 목적으로 사용될 수 있습니다. 회원님이 제공한 정보가 안전하게 보호되거나 기밀로 유지된다는 보장은 없습니다. 사용하기 전에 생성 AI 도구의 개인정보 보호 관행과 이용 약관을 숙지해야 합니다.</em></p><p><em>Elastic, Elasticsearch 및 관련 상표는 미국 및 기타 국가에서 Elasticsearch N.V.의 상표, 로고 또는 등록 상표입니다. 기타 모든 회사 및 제품명은 해당 소유자의 상표, 로고 또는 등록 상표입니다.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/enhancing-chatbot-capabilities-with-nlp-and-vector-search-in-elasticsearch</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Priscilla Parodi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c545fc80b6d79d6/6a170214839dfad776dcfd6f/d968e646240cd3ef7c79b5124d562a5f951d812b-1440x840.png" length="0" type="image/png"/>
    <pubDate>Wed, 21 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[벡터 필드를 사용한 텍스트 유사도 검색]]></title>
    <description><![CDATA[이 게시물에서는 텍스트 임베딩과 Elasticsearch의 새로운 dense_vector 유형을 사용하여 유사성 검색을 지원하는 방법을 살펴봅니다.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/about/history-of-elasticsearch">레시피 검색 엔진으로</a> 시작한 Elasticsearch는 처음부터 빠르고 강력한 전체 텍스트 검색을 제공하도록 설계되었습니다. 이러한 뿌리를 고려할 때, 텍스트 검색을 개선하는 것은 벡터를 활용한 지속적인 작업의 중요한 동기가 되었습니다. Elasticsearch 7.0에서는 고차원 벡터를 위한 실험적인 필드 유형을 도입했으며, 이제 7.3 릴리즈에서는 이러한 벡터를 문서 채점에 사용할 수 있도록 지원합니다.</p><p>이 게시물은 텍스트 유사도 검색이라는 특정 기술에 중점을 두고 있습니다. 이 유형의 검색에서는 사용자가 짧은 자유 텍스트 쿼리를 입력하면 쿼리와의 유사성에 따라 문서 순위가 매겨집니다. 텍스트 유사성은 다양한 사용 사례에서 유용하게 사용될 수 있습니다:</p><ul><li><p><strong>질문-답변:</strong> 자주 묻는 질문 모음이 주어지면 사용자가 입력한 질문과 유사한 질문을 찾습니다.</p></li><li><p><strong>논문 검색:</strong> 연구 논문 모음에서 사용자의 검색어와 밀접한 관련이 있는 제목의 기사를 반환합니다.</p></li><li><p><strong>이미지 검색:</strong> 캡션이 있는 이미지 데이터 세트에서 사용자의 설명과 유사한 캡션이 있는 이미지를 찾습니다.</p></li></ul><p>유사성 검색에 대한 간단한 접근 방식은 문서가 쿼리와 얼마나 많은 단어를 공유하는지에 따라 순위를 매기는 것입니다. 그러나 공통 단어가 거의 없더라도 문서가 쿼리와 유사할 수 있으며, 보다 강력한 유사성 개념은 구문 및 <a href="https://en.wikipedia.org/wiki/Semantic_similarity">의미론적</a> 내용도 고려합니다.</p><p>자연어 처리(NLP) 커뮤니티에서는 단어와 문장을 숫자 벡터로 인코딩하는 텍스트 임베딩이라는 기술을 개발했습니다. 이러한 벡터 표현은 텍스트의 언어적 내용을 캡처하도록 설계되었으며 쿼리와 문서 간의 유사성을 평가하는 데 사용할 수 있습니다.</p><p>이 게시물에서는 텍스트 임베딩과 Elasticsearch의 dense_vector 유형을 사용하여 유사도 검색을 지원하는 방법을 살펴봅니다. 먼저 임베딩 기술에 대한 개요를 살펴본 다음, Elasticsearch를 사용한 간단한 유사성 검색 프로토타입을 단계별로 살펴보겠습니다.</p><strong>참고:</strong> 검색에서 텍스트 임베딩을 사용하는 것은 복잡하고 진화하는 영역입니다. 이 블로그는 특정 아키텍처나 구현에 대한 권장 사항이 아닙니다. <a href="https://www.elastic.co/what-is/vector-search">벡터 검색의</a> 강력한 기능으로 검색 환경을 개선하는 방법을 알아보려면 여기에서 시작하세요.<h2>텍스트 임베딩이란 무엇인가요?</h2><p>다양한 유형의 텍스트 임베딩과 기존 검색 방식과 비교하여 자세히 살펴보겠습니다.</p><h3>단어 임베딩</h3><p><a href="https://en.wikipedia.org/wiki/Word_embedding">단어 임베딩</a> 모델은 단어를 밀도가 높은 숫자 벡터로 표현합니다. 이러한 벡터는 단어의 의미적 속성을 포착하는 것을 목표로 하며, 벡터가 서로 가까운 단어는 의미적 의미 측면에서 유사해야 합니다. 좋은 임베딩에서는 벡터 공간의 방향이 단어 의미의 다양한 측면에 연결됩니다. 예를 들어, "캐나다" 의 벡터는 한 방향으로는 "프랑스" 에 가깝고 다른 방향으로는 "토론토" 에 가까울 수 있습니다.</p><p>NLP 및 검색 커뮤니티에서는 꽤 오랫동안 단어의 벡터 표현에 관심을 가져왔습니다. 지난 몇 년 동안 신경망을 사용하여 많은 전통적인 작업을 재검토하면서 단어 임베딩에 대한 관심이 다시 높아졌습니다. 단어 임베딩 알고리즘이 성공적으로 개발되었는데, <a href="https://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf">워드2vec과</a> <a href="https://nlp.stanford.edu/pubs/glove.pdf">GloVe가</a> 대표적입니다. 이러한 접근 방식은 대규모 텍스트 컬렉션을 활용하고 각 단어가 나타나는 문맥을 검토하여 벡터 표현을 결정합니다:</p><ul><li><p>word2vec 스킵그램 모델은 신경망을 훈련시켜 문장에서 단어 주변의 문맥 단어를 예측합니다. 네트워크의 내부 가중치는 임베딩이라는 단어를 부여합니다.</p></li><li><p>GloVe에서 단어의 유사성은 다른 문맥 단어와 얼마나 자주 나타나는지에 따라 달라집니다. 이 알고리즘은 단어 동시 발생 횟수에 대한 간단한 선형 모델을 학습합니다.</p></li></ul><p>많은 연구 그룹에서 Wikipedia나 Common Crawl과 같은 대규모 텍스트 말뭉치에 대해 사전 학습된 모델을 배포하고 있으므로 다운로드하여 다운스트림 작업에 편리하게 연결할 수 있습니다. 사전 학습된 버전을 직접 사용하는 경우도 있지만, 특정 대상 데이터 세트와 작업에 맞게 모델을 조정하는 것이 도움이 될 수 있습니다. 이는 종종 사전 학습된 모델에 '미세 조정' 단계를 실행하여 수행합니다.</p><p>단어 임베딩은 매우 강력하고 효과적인 것으로 입증되었으며, 이제 기계 번역 및 감정 분류와 같은 NLP 작업에서 개별 토큰 대신 임베딩을 사용하는 것이 일반적인 관행이 되었습니다.</p><h3>문장 임베딩</h3><p>최근에는 연구자들이 단어뿐만 아니라 긴 텍스트 섹션을 표현하는 임베딩 기술에 집중하기 시작했습니다. 현재 대부분의 접근 방식은 복잡한 신경망 아키텍처를 기반으로 하며, 의미 정보를 포착하는 데 도움을 주기 위해 학습 중에 레이블이 지정된 데이터를 통합하기도 합니다.</p><p>학습이 완료된 모델은 문장을 가져와 문맥에 따라 각 단어에 대한 벡터와 전체 문장에 대한 벡터를 생성할 수 있습니다. 단어 임베딩과 마찬가지로 많은 모델의 사전 학습된 버전이 제공되므로 사용자는 값비싼 학습 과정을 생략할 수 있습니다. 학습 과정은 매우 리소스 집약적일 수 있지만, 모델을 호출하는 것은 훨씬 더 가볍습니다. 문장 임베딩 모델은 일반적으로 실시간 애플리케이션의 일부로 사용할 수 있을 만큼 빠릅니다.</p><p>몇 가지 일반적인 문장 임베딩 기술로는 <a href="https://arxiv.org/abs/1705.02364">InferSent</a>, <a href="https://arxiv.org/abs/1803.11175">범용 문장 인코더</a>, <a href="https://arxiv.org/abs/1802.05365">ELMo</a>, <a href="https://arxiv.org/abs/1810.04805">BERT</a> 등이 있습니다. 단어 및 문장 임베딩을 개선하는 것은 활발한 연구 분야이며, 강력한 모델이 추가로 도입될 가능성이 높습니다.</p><h3>기존 검색 방식과 비교</h3><p>기존 정보 검색에서 텍스트를 숫자 벡터로 표현하는 일반적인 방법은 어휘의 각 단어에 하나의 차원을 할당하는 것입니다. 그런 다음 텍스트의 벡터는 어휘의 각 용어가 나타나는 횟수를 기반으로 합니다. 이러한 텍스트 표현 방식은 문장 구조와 관계없이 단순히 단어 발생 횟수만 계산하기 때문에 흔히 "단어 가방," 이라고도 합니다.</p><p>텍스트 임베딩은 몇 가지 중요한 점에서 기존의 벡터 표현과 다릅니다:</p><ul><li><p>인코딩된 벡터는 밀도가 높고 비교적 낮은 차원으로, 보통 100~1,000차원에 걸쳐 있습니다. 이와 대조적으로 단어의 가방 벡터는 희소하며 50,000개 이상의 차원으로 구성될 수 있습니다. 임베딩 알고리즘은 의미론적 의미를 모델링하기 위해 텍스트를 저차원 공간으로 인코딩합니다. 이상적으로 동의어 단어와 구문은 새 벡터 공간에서 비슷한 표현으로 끝납니다.</p></li><li><p>문장 임베딩은 벡터 표현을 결정할 때 단어의 순서를 고려할 수 있습니다. 예를 들어" 의 "튠이라는 문구는 "의" 과는 매우 다른 벡터로 매핑될 수 있습니다.</p></li><li><p>실제로 문장 임베딩은 텍스트의 큰 섹션에 잘 적용되지 않는 경우가 많습니다. 일반적으로 짧은 단락보다 긴 텍스트를 나타내는 데는 사용되지 않습니다.</p></li></ul><h2>유사도 검색에 임베딩 사용</h2><p>질문과 답변이 많이 모였다고 가정해 보겠습니다. 사용자가 질문을 하면 컬렉션에서 가장 유사한 질문을 검색하여 답변을 찾을 수 있도록 도와줍니다.</p><p>텍스트 임베딩을 사용하여 유사한 질문을 검색할 수 있도록 할 수 있습니다:</p><ul><li><p>인덱싱하는 동안 각 질문은 문장 임베딩 모델을 통해 실행되어 숫자 벡터를 생성합니다.</p></li><li><p>사용자가 쿼리를 입력하면 동일한 문장 임베딩 모델을 통해 실행되어 벡터를 생성합니다. 응답의 순위를 매기기 위해 각 질문과 쿼리 벡터 간의 벡터 유사도를 계산합니다. 임베딩 벡터를 비교할 때는 <a href="https://en.wikipedia.org/wiki/Cosine_similarity">코사인 유사도를</a> 사용하는 것이 일반적입니다.</p></li></ul><p><a href="https://github.com/jtibshirani/text-embeddings">이 리포지토리는</a> Elasticsearch에서 이를 어떻게 수행할 수 있는지에 대한 간단한 예를 보여줍니다. 기본 스크립트는 <a href="https://github.com/elastic/rally-tracks/tree/master/so">StackOverflow 데이터 세트에서</a> 최대 20,000개의 질문을 색인한 다음 사용자가 데이터 세트에 대해 자유 텍스트 쿼리를 입력할 수 있도록 합니다.</p><p>곧 스크립트의 각 부분을 자세히 살펴보겠지만 먼저 몇 가지 결과 예시를 살펴보겠습니다. 많은 경우, 이 방법은 쿼리와 색인된 질문 간에 단어가 많이 겹치지 않는 경우에도 유사성을 포착할 수 있습니다:</p><ul><li><p>"파일 압축하기" 반환 "폴더 압축/압축 풀기 &amp; 파일"</p></li><li><p>"" 반환 문자열이 IP인지 호스트 이름인지 어떻게 알 수 있나요? ""</p></li><li><p>"바이트를 복수로 변환" 반환 "파이썬에서 바이트를 부동 소수점 숫자로 변환하기"</p></li></ul><h3>구현 세부 정보</h3><p><a href="https://github.com/jtibshirani/text-embeddings/blob/blog/src/main.py">스크립트는</a> TensorFlow에서 임베딩 모델을 다운로드하고 생성하는 것으로 시작됩니다. Google의 범용 문장 인코더를 선택했지만 다른 많은 임베딩 방법을 사용할 수 있습니다. 스크립트는 추가 교육이나 미세 조정 없이 임베딩 모델을 그대로 사용합니다.</p><p>다음으로, 질문 제목, 태그, 그리고 벡터로 인코딩된 질문 제목에 대한 매핑을 포함하는 Elasticsearch 인덱스를 생성합니다:</p>"mappings": {
"properties": {
"title": {
"type": "text"
},
"title_vector": {
"type": "dense_vector",
"dims": 512
}
"tags": {
"type": "keyword"
},
...
}
}
<p>dense_vector에 대한 매핑에서 벡터에 포함될 차원 수를 지정해야 합니다. title_vector 필드를 색인할 때 Elasticsearch는 매핑에 지정된 것과 동일한 수의 차원을 가지고 있는지 확인합니다.</p><p>문서를 색인화하기 위해 임베딩 모델을 통해 질문 제목을 실행하여 숫자 배열을 얻습니다. 이 배열은 title_vector 필드에 있는 문서에 추가됩니다.</p><p>사용자가 쿼리를 입력하면 먼저 동일한 임베딩 모델을 통해 텍스트가 실행되고 쿼리 벡터 매개변수에 저장됩니다. 7.3 버전부터 Elasticsearch는 기본 스크립팅 언어로 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-script-score-query.html#vector-functions">cosineSimilarity 함수를</a> 제공합니다. 따라서 사용자 쿼리와의 유사성을 기준으로 질문의 순위를 매기기 위해 스크립트_스코어 쿼리를 사용합니다:</p>{
"script_score": {
"query": {"match_all": {}},
"script": {
"source": "cosineSimilarity(params.query_vector, 'title_vector') + 1.0",
"params": {"query_vector": query_vector}
}
}
}
<p>모든 새 쿼리에서 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.6/modules-scripting-using.html#prefer-params">스크립트()를 다시 컴파일하지 않도록</a>쿼리 벡터를 스크립트 매개변수로 전달해야 합니다. Elasticsearch는 음수 점수를 허용하지 않으므로 코사인 유사도에 음수 점수를 추가해야 합니다.</p><p>| <strong>참고:</strong> 이 블로그 게시물은 원래 Elasticsearch 7.3에서 사용할 수 있었던 <a href="https://www.elastic.co/guide/en/elasticsearch/reference/7.3/query-dsl-script-score-query.html#vector-functions">벡터 함수에 대해 다른 구문을</a> 사용했지만 7.6에서 더 이상 사용되지 않습니다.
|</p><h3>중요한 제한 사항</h3><p>script_score 쿼리는 제한적인 쿼리를 래핑하고 반환하는 문서의 점수를 수정하도록 설계되었습니다. 하지만 인덱스의 모든 문서에 대해 스크립트가 실행된다는 의미의 match_all 쿼리를 제공했습니다. 벡터는 문서 채점에는 사용할 수 있지만 초기 검색 단계에서는 사용할 수 없다는 것이 현재 Elasticsearch의 벡터 유사성의 제한 사항입니다. 벡터 유사성을 기반으로 한 검색 지원은 <a href="https://github.com/elastic/elasticsearch/issues/42326">현재 진행 중인 중요한</a> 작업 영역입니다.</p><p>모든 문서를 스캔하는 것을 피하고 빠른 성능을 유지하려면 match_all 쿼리를 보다 선택적인 쿼리로 대체할 수 있습니다. 검색에 사용할 수 있는 올바른 쿼리는 특정 사용 사례에 따라 달라질 수 있습니다.</p><p>위에서 몇 가지 고무적인 사례를 살펴봤지만, 결과가 노이즈가 많고 직관적이지 않을 수도 있다는 점에 유의해야 합니다. 예를 들어, "파일을 압축하는 경우" " 부분 .csproj에도 높은 점수를 부여합니다. 파일" 및 ".pyc를 피하는 방법 파일은". 그리고 메서드가 놀라운 결과를 반환하는 경우, 각 벡터 구성 요소의 의미가 불분명하고 해석 가능한 개념과 일치하지 않는 경우가 많아 문제를 디버그하는 방법이 항상 명확하지 않습니다. 단어 중복에 기반한 기존의 채점 기법을 사용하면 "이 문서가 높은 순위를 차지한 이유는 무엇인가요? 라는 질문에 답하기가 더 쉬워지는 경우가 많습니다."</p><p>앞서 언급했듯이 이 프로토타입은 임베딩 모델을 벡터 필드와 함께 사용할 수 있는 방법을 보여주는 예시일 뿐, 실제 제작에 사용할 수 있는 솔루션은 아닙니다. 새로운 검색 전략을 개발할 때는 자체 데이터에서 접근 방식이 어떻게 수행되는지 테스트하고 일치 검색어와 같은 강력한 기준과 비교하는 것이 중요합니다. 대상 데이터 세트에 대한 임베딩 모델을 미세 조정하거나 단어 수준 쿼리 확장 등 임베딩을 통합하는 다양한 방법을 시도하는 등 확실한 결과를 얻기 전에 전략을 크게 변경해야 할 수도 있습니다.</p><h2>결론</h2><p>임베딩 기술은 텍스트의 언어적 콘텐츠를 캡처할 수 있는 강력한 방법을 제공합니다. 임베딩을 색인하고 벡터 거리를 기반으로 점수를 매김으로써 단어 수준의 중첩을 넘어서는 유사성 개념을 사용하여 문서를 비교할 수 있습니다.</p><p>벡터 필드 유형을 기반으로 하는 더 많은 기능을 소개할 수 있기를 기대합니다. 검색에 벡터를 사용하는 것은 미묘한 차이가 있고 발전 중인 영역입니다. 언제나 그렇듯이 <a href="https://github.com/elastic/elasticsearch">Github과</a> <a href="https://discuss.elastic.co/">토론 포럼에서</a> 여러분의 사용 사례와 경험을 듣고 싶습니다!</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/text-similarity-search-with-vectors-in-elasticsearch</guid>
    <category><![CDATA[벡터 데이터베이스]]></category>
    <dc:creator><![CDATA[Julie Tibshirani]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91384bd99b05cd28/6a17e7e01d1b835cc593e467/c633ed737add7d22a7d65b3ca5c56480ef3d8b2c-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Oct 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>