Semantic search using byte-quantized vectors

Cloud_ Ghost·2025년 9월 8일

opensearch

목록 보기
15/23

https://docs.opensearch.org/latest/tutorials/vector-search/vector-operations/semantic-search-byte-vectors/

바이트 양자화 벡터를 사용한 의미론적 검색

이 튜토리얼은 Cohere Embed 모델과 바이트 양자화 벡터를 사용하여 의미론적 검색을 구축하는 방법을 보여줍니다. 바이트 양자화 벡터 사용에 대한 자세한 정보는 "바이트 벡터" 및 "의미론적 검색"을 참조하세요.

Cohere Embed v3 모델은 여러 embedding_types를 지원합니다. 이 튜토리얼에서는 바이트 양자화 벡터를 인코딩하기 위해 INT8 타입을 사용할 것입니다.

Cohere Embed v3 모델은 여러 입력 타입을 지원합니다. 이 튜토리얼에서는 다음 입력 타입을 사용합니다:

  • search_document: 벡터 데이터베이스에 저장하려는 텍스트(문서 형태)가 있을 때 이 입력 타입을 사용합니다.
  • search_query: 벡터 데이터베이스에서 가장 관련성 높은 문서를 찾기 위한 검색 쿼리를 구조화할 때 이 입력 타입을 사용합니다.

입력 타입에 대한 자세한 정보는 Cohere 문서를 참조하세요.

이 튜토리얼에서는 두 개의 모델을 생성할 것입니다:

  1. 수집용 모델: input_typesearch_document인 모델
  2. 검색용 모델: input_typesearch_query인 모델

your_로 시작하는 접두사가 있는 자리 표시자를 자신의 값으로 바꾸세요.

1단계: 수집용 임베딩 모델 생성

search_document 입력 타입을 지정하여 Cohere 모델용 커넥터를 생성하세요:

POST /_plugins/_ml/connectors/_create
{
    "name": "Cohere embedding connector with int8 embedding type for ingestion",
    "description": "Test connector for Cohere embedding model",
    "version": 1,
    "protocol": "http",
    "credential": {
        "cohere_key": "your_cohere_api_key"
    },
    "parameters": {
        "model": "embed-english-v3.0",
        "embedding_types": ["int8"],
        "input_type": "search_document"
    },
    "actions": [
        {
            "action_type": "predict",
            "method": "POST",
            "headers": {
                "Authorization": "Bearer ${credential.cohere_key}",
                "Request-Source": "unspecified:opensearch"
            },
            "url": "https://api.cohere.ai/v1/embed",
            "request_body": "{ \"model\": \"${parameters.model}\", \"texts\": ${parameters.texts}, \"input_type\":\"${parameters.input_type}\", \"embedding_types\": ${parameters.embedding_types} }",
            "pre_process_function": "connector.pre_process.cohere.embedding",
            "post_process_function": "\n    def name = \"sentence_embedding\";\n    def data_type = \"FLOAT32\";\n    def result;\n    if (params.embeddings.int8 != null) {\n      data_type = \"INT8\";\n      result = params.embeddings.int8;\n    } else if (params.embeddings.uint8 != null) {\n      data_type = \"UINT8\";\n      result = params.embeddings.uint8;\n    } else if (params.embeddings.float != null) {\n      data_type = \"FLOAT32\";\n      result = params.embeddings.float;\n    }\n    \n    if (result == null) {\n      return \"Invalid embedding result\";\n    }\n    \n    def embedding_list = new StringBuilder(\"[\");\n    \n    for (int m=0; m<result.length; m++) {\n      def embedding_size = result[m].length;\n      def embedding = new StringBuilder(\"[\");\n      def shape = [embedding_size];\n      for (int i=0; i<embedding_size; i++) {\n        def val;\n        if (\"FLOAT32\".equals(data_type)) {\n          val = result[m][i].floatValue();\n        } else if (\"INT8\".equals(data_type) || \"UINT8\".equals(data_type)) {\n          val = result[m][i].intValue();\n        }\n        embedding.append(val);\n        if (i < embedding_size - 1) {\n          embedding.append(\",\");  \n        }\n      }\n      embedding.append(\"]\");  \n      \n      // workaround for compatible with neural-search\n      def dummy_data_type = 'FLOAT32';\n      \n      def json = '{' +\n                   '\"name\":\"' + name + '\",' +\n                   '\"data_type\":\"' + dummy_data_type + '\",' +\n                   '\"shape\":' + shape + ',' +\n                   '\"data\":' + embedding +\n                   '}';\n      embedding_list.append(json);\n      if (m < result.length - 1) {\n        embedding_list.append(\",\");  \n      }\n    }\n    embedding_list.append(\"]\");  \n    return embedding_list.toString();\n    "
        }
    ]
}

중요: OpenSearch와의 호환성을 보장하기 위해 실제 임베딩 타입이 INT8이더라도 후처리 함수에서 data_type(응답의 inference_results.output.data_type 필드에 출력됨)은 FLOAT32로 설정되어야 합니다.

응답에서 커넥터 ID를 기록해 두세요. 모델을 등록할 때 사용할 것입니다.

커넥터 ID를 제공하여 모델을 등록하세요:

POST /_plugins/_ml/models/_register?deploy=true
{
    "name": "Cohere embedding model for INT8 with search_document input type",
    "function_name": "remote",
    "description": "test model",
    "connector_id": "your_connector_id"
}

응답에서 모델 ID를 기록해 두세요. 다음 단계에서 사용할 것입니다.

모델 ID를 제공하여 모델을 테스트하세요:

POST /_plugins/_ml/models/your_embedding_model_id/_predict
{
    "parameters": {
        "texts": ["hello", "goodbye"]
    }
}

응답에는 추론 결과가 포함됩니다:

{
    "inference_results": [
        {
            "output": [
                {
                    "name": "sentence_embedding",
                    "data_type": "FLOAT32",
                    "shape": [1024],
                    "data": [20, -11, -60, -91, ...]
                },
                {
                    "name": "sentence_embedding",
                    "data_type": "FLOAT32",
                    "shape": [1024],
                    "data": [58, -30, 9, -51, ...]
                }
            ],
            "status_code": 200
        }
    ]
}

2단계: 데이터 수집

먼저 수집 파이프라인을 생성하세요:

PUT /_ingest/pipeline/pipeline-cohere
{
  "description": "Cohere embedding ingest pipeline",
  "processors": [
    {
      "text_embedding": {
        "model_id": "your_embedding_model_id_created_in_step1",
        "field_map": {
          "passage_text": "passage_embedding"
        }
      }
    }
  ]
}

다음으로 벡터 인덱스를 생성하고 passage_embedding 필드의 data_typebyte로 설정하여 바이트 양자화 벡터를 저장할 수 있도록 하세요:

PUT my_test_data
{
  "settings": {
    "index": {
      "knn": true,
      "knn.algo_param.ef_search": 100,
      "default_pipeline": "pipeline-cohere"
    }
  },
  "mappings": {
    "properties": {
      "passage_text": {
        "type": "text"
      },
      "passage_embedding": {
        "type": "knn_vector",
        "dimension": 1024,
        "data_type": "byte",
        "method": {
          "name": "hnsw",
          "space_type": "l2",
          "engine": "lucene",
          "parameters": {
            "ef_construction": 128,
            "m": 24
          }
        }
      }
    }
  }
}

마지막으로 테스트 데이터를 수집하세요:

POST _bulk
{ "index" : { "_index" : "my_test_data" } }
{ "passage_text" : "OpenSearch는 데이터 집약적인 애플리케이션을 위한 솔루션을 구축하는 유연하고 확장 가능한 오픈 소스 방법입니다. 내장된 성능, 개발자 친화적인 도구, 그리고 기계 학습, 데이터 처리 등을 위한 강력한 통합으로 데이터를 탐색, 풍부화, 시각화하세요." }
{ "index" : { "_index" : "my_test_data"} }
{ "passage_text" : "BM25는 키워드를 포함하는 쿼리에서 좋은 성능을 보이지만 쿼리 용어의 의미론적 의미를 포착하지 못하는 키워드 기반 알고리즘입니다. 의미론적 검색은 키워드 기반 검색과 달리 검색 맥락에서 쿼리의 의미를 고려합니다. 따라서 의미론적 검색은 자연어 이해가 필요한 쿼리에서 좋은 성능을 보입니다." }

3단계: 의미론적 검색 구성

search_query 입력 타입을 가진 임베딩 모델에 대한 커넥터를 생성하세요:

POST /_plugins/_ml/connectors/_create
{
    "name": "Cohere embedding connector with int8 embedding type for search",
    "description": "Test connector for Cohere embedding model. Use this connector for search.",
    "version": 1,
    "protocol": "http",
    "credential": {
        "cohere_key": "your_cohere_api_key"
    },
    "parameters": {
        "model": "embed-english-v3.0",
        "embedding_types": ["int8"],
        "input_type": "search_query"
    },
    "actions": [
        {
            "action_type": "predict",
            "method": "POST",
            "headers": {
                "Authorization": "Bearer ${credential.cohere_key}",
                "Request-Source": "unspecified:opensearch"
            },
            "url": "https://api.cohere.ai/v1/embed",
            "request_body": "{ \"model\": \"${parameters.model}\", \"texts\": ${parameters.texts}, \"input_type\":\"${parameters.input_type}\", \"embedding_types\": ${parameters.embedding_types} }",
            "pre_process_function": "connector.pre_process.cohere.embedding",
            "post_process_function": "[위와 동일한 후처리 함수]"
        }
    ]
}

응답에서 커넥터 ID를 기록해 두세요. 모델을 등록할 때 사용할 것입니다.

커넥터 ID를 제공하여 모델을 등록하세요:

POST /_plugins/_ml/models/_register?deploy=true
{
    "name": "Cohere embedding model for INT8 with search_query input type",
    "function_name": "remote",
    "description": "test model",
    "connector_id": "your_connector_id"
}

응답에서 모델 ID를 기록해 두세요. 쿼리 실행 시 사용할 것입니다.

모델 ID를 제공하여 벡터 검색을 실행하세요:

POST /my_test_data/_search
{
  "query": {
    "neural": {
      "passage_embedding": {
        "query_text": "semantic search",
        "model_id": "your_embedding_model_id",
        "k": 100
      }
    }
  },
  "size": "1",
  "_source": ["passage_text"]
}

응답에는 쿼리 결과가 포함됩니다:

{
  "took": 143,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 2,
      "relation": "eq"
    },
    "max_score": 9.345969e-7,
    "hits": [
      {
        "_index": "my_test_data",
        "_id": "_IXCuY0BJr_OiKWden7i",
        "_score": 9.345969e-7,
        "_source": {
          "passage_text": "BM25는 키워드를 포함하는 쿼리에서 좋은 성능을 보이지만 쿼리 용어의 의미론적 의미를 포착하지 못하는 키워드 기반 알고리즘입니다. 의미론적 검색은 키워드 기반 검색과 달리 검색 맥락에서 쿼리의 의미를 고려합니다. 따라서 의미론적 검색은 자연어 이해가 필요한 쿼리에서 좋은 성능을 보입니다."
        }
      }
    ]
  }
}

주요 특징 및 이점

바이트 양자화의 장점

  • 메모리 효율성: FLOAT32 대비 75% 메모리 절약
  • 성능 향상: 더 빠른 벡터 계산 및 검색
  • 비용 절감: 저장 공간 및 연산 비용 감소

이중 모델 구조의 이유

  • 수집용 모델: search_document 타입으로 문서를 최적화하여 인코딩
  • 검색용 모델: search_query 타입으로 쿼리를 최적화하여 인코딩
  • 성능 최적화: 각 용도에 특화된 임베딩으로 검색 정확도 향상

구현 시 주의사항

  • Cohere API 키 필요
  • 후처리 함수에서 호환성을 위해 data_type을 FLOAT32로 설정
  • 벡터 필드의 data_type은 반드시 byte로 설정
  • 차원 수(1024)는 모델과 인덱스 매핑에서 일치해야 함
profile
행복합시다~

0개의 댓글