Elasticsearch Vector Search 사용 가이드

Elasticsearch Vector Search 사용 가이드

개요

Elasticsearch 는 NoSQL의 특성을 지니고 있으면서, 분산처리를 통해 실시간으로 빠른 검색을 제공하도록 설계되었습니다. 특히, 대량의 비정형 데이터 검색이 가능하며 전문 검색(Full-Text) 을 지원하고 있습니다. 이러한 특성은 벡터를 사용하여 필요한 텍스트 검색을 하는 것에도 큰 이점이 있습니다.

벡터 검색 이란, 문서나 쿼리가 일반 텍스트가 아닌 벡터로 표시되는 검색을 말합니다. 벡터 검색은 머신 러닝(ML)을 활용하여 이미지, 오디오 또는 비디오 등의 컨텐츠들을 벡터 표현으로 변경하고, 최근접 이웃 알고리즘을 사용하여 유사한 데이터를 찾습니다.

예를 들어, 일치하는 키워드를 찾는 기존 검색 형태와는 다르게 설명이 포함된 비디오들 중에서 사용자의 의견와 가장 유사한 비디오를 찾습니다. 또한, 유사한 문서를 검색할 때 가장 쉬운 방법은 입력한 단어와 문서에 포함 되어있는 단어가 일치하는 것이 얼마나 많은 지를 확인하는 것인데, 실제로는 완전히 일치하지 않더라도 비슷하거나 연관성이 있는 데이터가 있을 수도 있기 때문에 이러한 것들까지 고려해서 확인을 해야 합니다.

벡터 임베딩 은 이처럼 동의어나 연관성을 포함하여 검색 할 수 있게 의미를 담고 있으며, 임베딩 공간의 거리를 사용하여 유사성을 나타냅니다.

참고
Samsung Cloud Platform에서 Elasticsearch 벡터 검색은 Enterprise 버전만 사용 가능하며, OSS 버전에서는 X-pack 지원이 되지 않아 사용이 불가 합니다.

벡터(텍스트) 임베딩

임베딩(Embedding) 이란, 사람이 사용하는 언어나 이미지는 01 로만 이루어진 컴퓨터 입장에서 그 의미를 파악하기가 어렵기 때문에, 기계가 이해할 수 있는 숫자의 나열인 벡터로 바꾼 결과 혹은 그 일련의 과정을 말합니다.

임베딩 기법의 종류는 여러 가지가 있는데, Elasticsearch는 그 중에서 단어/문장 임베딩과 가장 관련이 있습니다.

  • 단어수준 임베딩은 word2vec , GloVe 모델 등이 있습니다.
  • 문장수준 임베딩에는 ELMoBERT 등이 있습니다.

단어수준 임베딩 은 가장 단순한 형태의 임베딩이라 할 수 있으며, 유사도 계산과 시각화를 가능하도록 하는 과정입니다. 그러나 단어 수준 임베딩의 가장 큰 문제점은 ‘동음이의어(Homonym)’ 를 분간하기 어렵다는 점입니다.

이를 보완하기 위해 문장 수준 임베딩에서는 개별 단어가 아닌 문장의 문맥적 의미를 임베딩 안에 포함하여 전이 학습(Transfer Learning) 의 효과와 성능이 좋습니다.

Elasticsearch에서는 이러한 텍스트(단어/문장) 임베딩dense_vector 유형을 사용하여 유사도를 검색합니다.

벡터 검색 준비하기

ElasticSearch 7.0 버전부터 고차원 벡터(Vector) Type 을 지원했고, 7.3 버전부터는 벡터를 문서 스코어링(Document Scoring) 에 활용할 수 있도록 개선되었습니다.

ElasticSearch는 현재 Dense Vector 라는 Field Type을 지원하며, 자세한 내용은 아래와 같습니다.

  1. 단어 임베딩 모델에서는 하나의 단어를 하나의 Dense vector로 표현 합니다.
  2. 주로 kNN (k-nearest neighbor) 검색에 사용됩니다.
  3. 집계(aggregations) 또는 정렬(sorting)을 지원하지 않습니다.
  4. script_score 쿼리에서 문서의 순위를 매기는 데 사용됩니다.
  5. 필드가 가지는 차원 값(Dimensions)은 2048을 초과 할 수 없습니다.

Index에 벡터 필드 생성

ElasticSearch에서 벡터 검색을 사용하려면 먼저 인덱스에 벡터 필드를 만들어야 합니다.

배경색 변경
PUT position-sample
{
  "mappings": {
    "properties": {
      "position-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": false
      },
      "size": {
        "type": "long"
      }
    }
  }
}
PUT position-sample
{
  "mappings": {
    "properties": {
      "position-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": false
      },
      "size": {
        "type": "long"
      }
    }
  }
}
코드블록. Index Vector Field 생성 예제

위 예제에서는 “size”“position-vector” 두 개의 필드가 있는 “position-sample” 라는 인덱스를 만듭니다.

  • “size” 필드는 “long” 유형입니다.
  • “position-vector” 필드는 3차원의 “dense_vector” 유형입니다.

벡터를 사용하여 문서 Indexing

벡터가 있는 문서를 인덱싱하려면 요청에 벡터 필드를 포함해야 합니다.

배경색 변경
POST position-sample/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "position-vector": [-84.095, 435.33, 951.2], "size": 1024 }
{ "index": { "_id": "2" } }
{ "position-vector": [219, 234, -52.0], "size": 512 }
{ "index": { "_id": "3" } }
{ "position-vector": [0.59, -98.2, 24.8], "size": 2048 }
POST position-sample/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "position-vector": [-84.095, 435.33, 951.2], "size": 1024 }
{ "index": { "_id": "2" } }
{ "position-vector": [219, 234, -52.0], "size": 512 }
{ "index": { "_id": "3" } }
{ "position-vector": [0.59, -98.2, 24.8], "size": 2048 }
코드블록. Document Indexing 예제

벡터 검색 실행하기

Elasticsearch에서는 백터 검색을 위해 두 가지 방법을 지원합니다.

  • Exact kNN

    • 해당 포인트에 대해 벡터 공간에서 유사성 메트릭 (Euclidean거리 또는 코사인 유사도 등) 입니다.
    • 측정된 쿼리 벡터에 “가장 가까운 이웃” 을 K개 찾을 수 있습니다.
  • aNN(approximate Knn) :

    • 일반적으로 텍스트나 이미지에 대한 임베딩 모델들은 일반적으로 적게는 100개에서 많게는 1000개 이상의 요소들로 구성된 고차원 벡터를 생성합니다.
    • 이런 고차원 벡터에서 가장 가까운 이웃을 찾는 것은 매우 어렵기 때문에 가장 효율적인 방법을 고민해야 합니다.
      • 여러가지 방법이 있지만 속도를 향상 시키기 위해 정확도를 희생하는 것이 일반적입니다.
      • 이는 aNN 알고리즘이 항상 진정한 K개의 최근접 벡터 검색 결과 값을 보장해주지 않는다는 것을 의미합니다.
      • 그럼에도 불구하고, 좋은 성능과 효율적인 실행을 유지하면서 대규모 Data Sets으로 확장 할 수 있기 때문에 널리 이용되고 있습니다.
    • Elasticsearch는 aNN 검색을 위해 HNSW 알고리즘을 사용하고 있습니다.

Exact kNN 벡터 검색

script_score 벡터 함수와 쿼리를 사용합니다.

배경색 변경
POST position-sample/_search
{
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "range" : {
              "size" : {
                "gte": 100
              }
            }
          }
        }
      },
      "script": {
        "source": "cosineSimilarity(params.queryVector, 'position-vector') + 1.0",
        "params": {
          "queryVector": [30.9, -908, 523]
        }
      }
    }
  }
}
POST position-sample/_search
{
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "range" : {
              "size" : {
                "gte": 100
              }
            }
          }
        }
      },
      "script": {
        "source": "cosineSimilarity(params.queryVector, 'position-vector') + 1.0",
        "params": {
          "queryVector": [30.9, -908, 523]
        }
      }
    }
  }
}
코드블록. Script_Score 쿼리를 사용한 Vector Search 예제

위 예제에서는 “script_score” 쿼리를 사용하여 쿼리 벡터와의 코사인 유사성을 기반으로 각 문서의 점수를 매깁니다.

  • “cosineSimilarity” 함수는 문서 벡터와 쿼리 벡터 간의 코사인 유사성을 계산하고 결과에 "+1.0" 을 추가하여 점수가 항상 양수인지 확인합니다.
  • 해당 검색 쿼리를 실행하면 ElasticSearch는 쿼리 벡터와의 유사성에 따라 정렬된 문서 목록을 반환합니다.

벡터 검색에 aNN 사용

knn 옵션을 사용하여 index 값이 true 인 하나 이상의 dense_vector 필드를 검색 합니다.

배경색 변경
PUT picture-index
{
  "mappings": {
    "properties": {
      "picture-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": true,
        "similarity": "l2_norm"
      },
      "name-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": true,
        "similarity": "l2_norm"
      },
      "name": {
        "type": "text"
      },
      "file-type": {
        "type": "keyword"
      }
    }
  }
}
PUT picture-index
{
  "mappings": {
    "properties": {
      "picture-vector": {
        "type": "dense_vector",
        "dims": 5,
        "index": true,
        "similarity": "l2_norm"
      },
      "name-vector": {
        "type": "dense_vector",
        "dims": 3,
        "index": true,
        "similarity": "l2_norm"
      },
      "name": {
        "type": "text"
      },
      "file-type": {
        "type": "keyword"
      }
    }
  }
}
코드블록. aNN Index Vector Field 생성 예제

위 예제에서는 총 4 개의 필드가 있는 “picture index” 라는 인덱스를 만듭니다.

  • “name” 필드는 “text” 유형입니다. 이는 텍스트 검색을 위해 분석됨을 의미합니다.
  • 반면에 “picture vector” 필드는 5차원, “name vector“ 필드는 3차원입니다.

예시 코드

배경색 변경
POST picture-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "picture-vector": [7, 10, 4, 17, 81], "name-vector": [1, 1, 9], "name": "beautiful sun", "file-type": "png" }
{ "index": { "_id": "2" } }
{ "picture-vector": [20, 12, 13, 2, 5], "name-vector": [18, 5, 27], "name": "blue sky", "file-type": "jpg" }
{ "index": { "_id": "3" } }
{ "picture-vector": [15, 10, 2, 47, 17], "name-vector": [9, 5, 40], "name": "wave", "file-type": "png" }
POST picture-index/_bulk?refresh=true
{ "index": { "_id": "1" } }
{ "picture-vector": [7, 10, 4, 17, 81], "name-vector": [1, 1, 9], "name": "beautiful sun", "file-type": "png" }
{ "index": { "_id": "2" } }
{ "picture-vector": [20, 12, 13, 2, 5], "name-vector": [18, 5, 27], "name": "blue sky", "file-type": "jpg" }
{ "index": { "_id": "3" } }
{ "picture-vector": [15, 10, 2, 47, 17], "name-vector": [9, 5, 40], "name": "wave", "file-type": "png" }
코드블록. aNN Document Indexing 예제
배경색 변경
POST picture-index/_search
{
  "knn": {
    "field": "picture-vector",
    "query_vector": [1, 29, 60, 14, 54],
    "k": 5,
    "num_candidates": 100
  },
  "fields": [ "name", "file-type" ]
}
POST picture-index/_search
{
  "knn": {
    "field": "picture-vector",
    "query_vector": [1, 29, 60, 14, 54],
    "k": 5,
    "num_candidates": 100
  },
  "fields": [ "name", "file-type" ]
}
코드블록. aNN 쿼리를 사용한 KNN Vector Search 예제