이미지 유사성 검색의 5가지 기술 구성 요소

blog-search-results-white-720x420.png

이 블로그 게시물 시리즈의 첫 번째 파트에서는 이미지 유사성 검색을 소개하고 복잡성을 줄이고 구현을 용이하게 할 수 있는 개괄적인 아키텍처를 살펴봅니다. 이 블로그에서는 이미지 유사성 검색 애플리케이션을 구현하는 데 필요한 각 구성 요소에 대한 기본 개념과 기술적 고려 사항을 설명합니다. 다음 구성 요소에 대해 알아봅니다.

  1. 임베딩 모델: 벡터 검색을 적용하는 데 필요한 데이터의 숫자 표현을 생성하는 머신 러닝 모델
  2. 추론 엔드포인트: 임베딩 모델을 Elastic의 데이터에 적용하는 API
  3. 벡터 검색: 유사성 검색이 최근접 이웃 검색과 연동되는 방식
  4. 이미지 임베딩 생성: 숫자 표현의 생성을 대규모 데이터 세트로 확장
  5. 애플리케이션 로직: 대화형 프론트엔드가 백엔드의 벡터 검색 엔진과 통신하는 방법

이 다섯 가지 구성 요소를 살펴보면 Elastic에서 벡터 검색을 적용하여 좀 더 직관적인 검색 경험을 구현할 수 있는 방법에 대한 청사진을 얻을 수 있습니다.

Elastic Platform 이미지 유사성 검색

1. 임베딩 모델

자연어 또는 이미지 데이터에 유사성 검색을 적용하려면 데이터를 벡터 임베딩이라고도 하는 숫자 표현으로 변환하는 머신 러닝 모델이 필요합니다. 이 예시에서는 다음과 같이 사용됩니다.

  • NLP ‘트랜스포머’ 모델이 자연어를 벡터로 변환합니다.
  • OpenAI CLIP(Contrastive Language-Image Pre-training) 모델이 이미지를 벡터화합니다.

트랜스포머 모델은 언어 번역, 텍스트 분류 또는 명명된 엔티티 인식과 같은 다양한 방식으로 자연어 데이터를 처리하도록 훈련된 머신 러닝 모델입니다. 이 모델은 인간 언어의 패턴과 구조를 학습하도록 주석이 달린 텍스트 데이터가 있는 대규모 데이터 세트를 사용해 훈련됩니다. 

이미지 유사성 애플리케이션은 주어진 텍스트인 자연어 설명과 일치하는 이미지를 찾습니다. 이러한 종류의 유사성 검색을 구현하려면 텍스트와 이미지 모두에 대해 훈련되고 텍스트 쿼리를 벡터로 변환할 수 있는 모델이 필요합니다. 그런 다음 유사한 이미지를 찾는 데 이 모델을 사용할 수 있습니다. 

Elasticsearch에서 NLP 모델을 업로드하고 사용하는 방법 자세히 알아보기 >>

CLIP은 OpenAI에서 개발한 대규모 언어 모델로, 텍스트와 이미지를 모두 처리할 수 있습니다. 이 모델은 입력으로 주어진 약간의 텍스트로 이미지의 텍스트 표현을 예측하도록 훈련됩니다. 여기에는 모델이 정확한 예측을 할 수 있도록 이미지의 시각적 표현과 텍스트 표현을 서로 일치시키는 학습 과정이 포함됩니다. 

CLIP의 또 다른 중요한 측면은 특별히 훈련되지 않은 작업을 수행할 수 있는 ‘제로 샷’ 모델이라는 점입니다. 예를 들어 훈련 중에 본 적이 없는 언어 간에 번역하거나 이전에 본 적이 없는 범주로 이미지를 분류할 수 있습니다. 따라서 CLIP은 매우 유연하고 다재다능한 모델입니다.

CLIP 모델을 사용하여 이미지를 벡터화하고, 다음에 설명된 대로 Elastic의 추론 엔드포인트를 사용하고, 아래 섹션 3에 설명된 대로 대규모 이미지 세트에 대한 추론을 실행합니다.

2. 추론 엔드포인트

NLP 모델이 Elasticsearch에 로드되면 실제 사용자 쿼리를 처리할 수 있습니다. 먼저 Elasticsearch _infer 엔드포인트를 사용하여 쿼리 텍스트를 벡터로 변환해야 합니다. 이 엔드포인트는 기본적으로 Elastic에서 NLP 모델을 사용하는 기본 내장 방법을 제공하며 외부 서비스를 쿼리할 필요가 없으므로 아주 간단하게 구현할 수 있습니다.
POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "A mountain covered in snow"}
    ]
}

3. 벡터(유사성) 검색

벡터 임베딩을 사용하여 쿼리와 문서를 모두 색인하면 유사한 문서가 임베딩 공간에서 쿼리의 최근접 이웃이 됩니다. 이를 실현하기 위해 널리 사용되는 알고리즘 중 하나가 쿼리 벡터에 가장 가까운 k 벡터를 찾는 kNN(k-최근접 이웃)입니다. 그러나 일반적으로 이미지 검색 애플리케이션에서 처리하는 방대한 데이터 세트의 경우, kNN에는 매우 많은 컴퓨팅 리소스가 필요하며 이는 과도한 실행 시간으로 이어질 수 있습니다. 이에 대한 해결책으로, ANN(근사 최근접 이웃) 검색은 고차원 임베딩 공간에서 대규모 데이터 세트를 효율적으로 실행하지만, 대신 정확도가 감소합니다.

Elastic에서는 _search 엔드포인트가 최근접 이웃 검색과 근사 최근접 이웃 검색을 모두 지원합니다. kNN 검색에 아래 코드를 사용해 보세요. 여기에서는 your-image-index의 모든 이미지에 대한 임베딩을 image_embedding 필드에서 사용할 수 있다고 가정합니다. 다음 섹션에서는 임베딩을 생성하는 방법에 대해 설명합니다.

#  Run kNN search against <query-embedding> obtained above
POST <your-image-index>/_search
{
  "fields": [...],
  "knn": {
    "field": "image_embedding",
    "k": 5,
    "num_candidates": 10,
    "query_vector": <query-embedding>
  }
}

Elastic의 kNN에 대해 자세히 알아보려면 https://www.elastic.co/guide/en/elasticsearch/reference/current/knn-search.html 문서를 참조하세요.

4. 이미지 임베딩 생성

위에서 언급한 이미지 임베딩은 이미지 유사성 검색의 성능에 매우 중요한 영향을 미칩니다. 위 코드에서 you-image-index라고 했던 이미지 임베딩을 보관하는 별도의 인덱스에 이미지 임베딩을 저장해야 합니다. 이 인덱스는 이미지당 문서와 컨텍스트 필드 및 이미지의 고밀도 벡터(이미지 임베딩) 해석 필드로 구성됩니다. 이미지 임베딩은 저차원 공간의 이미지를 나타냅니다. 유사한 이미지가 이 공간의 가까운 지점에 매핑됩니다. 원시 이미지는 해상도에 따라 크기가 몇 MB가 될 수 있습니다.

이러한 임베딩이 생성되는 방법에 대한 구체적인 세부 사항은 각기 다를 수 있습니다. 일반적으로 이 프로세스에는 이미지에서 특징을 추출한 다음 수학 함수를 사용하여 저차원 공간에 매핑하는 작업이 포함됩니다. 일반적으로 이 함수는 저차원 공간에서 특징을 표현하는 가장 좋은 방법을 학습하기 위해 대규모 이미지 데이터 세트를 사용해 훈련됩니다. 임베딩 생성은 일회성 작업입니다.

이 블로그에서는 이를 위해 CLIP 모델을 사용합니다. CLIP 모델은 OpenAI에서 배포하며 좋은 출발점을 제공합니다. 분류하려는 이미지 유형이 CLIP 모델을 훈련하는 데 사용된 일반 공개 데이터에 얼마나 많이 포함되어 있는지에 따라, 원하는 성능을 달성하기 위해서는 특수 사용 사례에 맞춤형으로 임베딩 모델을 훈련해야 할 수도 있습니다.

Elastic에서 임베딩 생성은 수집 단계에서 이루어져야 하므로, 검색과는 별도의 프로세스에서 다음 단계에 따라 수행됩니다.

  1. CLIP 모델을 로드합니다.
  2. 모든 이미지에 대해 다음을 수행합니다.
    1. 이미지를 로드합니다.
    2. 모델을 사용하여 이미지를 평가합니다.
    3. 생성된 임베딩을 문서에 저장합니다.
    4. 문서를 Elasticsearch 데이터 저장소에 저장합니다.

의사 코드는 이러한 단계를 더 구체적으로 만듭니다. 전체 코드는 예시 리포지토리에서 액세스할 수 있습니다.

...
img_model = SentenceTransformer('clip-ViT-B-32')
...
for filename in glob.glob(PATH_TO_IMAGES, recursive=True):
    doc = {}
    image = Image.open(filename)
    embedding = img_model.encode(image)
    doc['image_name'] = os.path.basename(filename)
    doc['image_embedding'] = embedding.tolist()
    lst.append(doc)
...

또는 아래 그림을 참조하세요.

처리 후의 문서는 다음과 같은 형태가 됩니다. 중요한 부분은 고밀도 벡터 표현이 저장되는 ‘image_embedding’ 필드입니다.

{
   "_index": "my-image-embeddings",
   "_id": "_g9ACIUBMEjlQge4tztV",
   "_score": 6.703597,
   "_source": {
     "image_id": "IMG_4032",
     "image_name": "IMG_4032.jpeg",
     "image_embedding": [
       -0.3415695130825043,
       0.1906963288784027,
       .....
       -0.10289803147315979,
       -0.15871885418891907
       ],
     "relative_path": "phone/IMG_4032.jpeg"
   }
}

5. 애플리케이션 로직

이러한 기본 구성 요소를 기반으로 구축하면 모든 요소를 종합하고 로직을 적용하여 대화형 이미지 유사성 검색을 구현할 수 있습니다. 주어진 설명과 일치하는 이미지를 대화식으로 검색하려고 할 때 어떤 작업이 필요한지 개념적인 부분을 살펴보겠습니다.

텍스트 쿼리의 경우, 입력은 ‘장미’처럼 간단한 하나의 단어가 될 수도 있고 ‘눈이 덮인 산’처럼 좀 더 확장된 설명이 될 수도 있습니다. 또는 이미지를 제공하고 해당 이미지와 유사한 이미지를 요청할 수도 있습니다.

서로 다른 형식을 사용하여 쿼리를 공식화하더라도, 둘 다 기본 벡터 검색에서 동일한 순서의 단계, 즉 임베딩(‘고밀도’ 벡터)으로 표현된 문서에 대한 쿼리(kNN)를 사용하여 실행됩니다. 이전 섹션에서 Elasticsearch가 대규모 이미지 데이터 세트에 필요한 매우 빠르고 확장 가능한 벡터 검색을 실행할 수 있도록 하는 메커니즘을 설명했습니다. 효율성을 위해 Elastic에서 kNN 검색을 조정하는 방법에 대해 자세히 알아보려면 이 문서를 참조하세요.

  • 그렇다면 위에서 설명한 논리를 어떻게 구현할 수 있을까요? 아래 흐름도를 보면 정보가 어떻게 흐르는지 확인할 수 있습니다. 사용자가 텍스트 또는 이미지로 발행한 쿼리는 입력 유형에 따라 임베딩 모델에 의해 벡터화됩니다(텍스트 설명의 경우 NLP 모델, 이미지의 경우 CLIP 모델).  
  • 두 모델 모두 입력 쿼리를 숫자 표현으로 변환하고 결과를 Elasticsearch의 고밀도 벡터  유형([number, number, number...])에 저장합니다.
  • 그런 다음 벡터 표현은 kNN 검색에 사용되어 유사한 벡터(이미지)를 찾고, 해당 벡터가 결과로 반환됩니다.

추론: 사용자 쿼리 벡터화

백그라운드에서 애플리케이션은 Elasticsearch의 추론 API에 요청을 보냅니다. 텍스트 입력의 경우, 다음과 같은 형태입니다.

POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "A mountain covered in snow"}
    ]
}

이미지의 경우, 아래의 단순화된 코드를 사용하여 CLIP 모델로 단일 이미지를 처리할 수 있습니다. CLIP 모델은 미리 Elastic 머신 러닝 노드에 로드해 두어야 합니다.

model = SentenceTransformer('clip-ViT-B-32')
image = Image.open(file_path)  
embedding = model.encode(image)

그러면 다음과 같이 512길이 어레이의 Float32 값이 반환됩니다.

{
  "predicted_value" : [
    -0.26385045051574707,
    0.14752596616744995,
    0.4033305048942566,
    0.22902603447437286,
    -0.15598160028457642,
    ...
  ]
}

검색: 유사한 이미지의 경우

검색은 두 입력 유형 모두에서 동일하게 작동합니다. 이미지 임베딩이 있는 인덱스(my-image-embeddings)에 대해 kNN 검색 정의가 포함된 쿼리를 보냅니다. 이전 쿼리("query_vector": [ ... ])의 고밀도 벡터를 넣고 검색을 실행합니다.

GET my-image-embeddings/_search
{
  "knn": {
    "field": "image_embedding",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
    -0.19898493587970734,
    0.1074572503566742,
    -0.05087625980377197,
    ...
    0.08200495690107346,
    -0.07852292060852051
  ]
  },
  "fields": [
    "image_id", "image_name", "relative_path"

  ],
  "_source": false
}

Elasticsearch의 응답은 Elastic에 문서로 저장된 kNN 검색 쿼리를 기반으로 가장 일치하는 이미지를 제공합니다.

아래 흐름 그래프는 대화형 애플리케이션이 사용자 쿼리를 처리하는 동안 이동하는 단계를 요약한 것입니다.

  1. 프론트엔드인 대화형 애플리케이션을 로드합니다.
  2. 사용자가 관심이 있는 이미지를 선택합니다.
  3. 애플리케이션이 CLIP 모델을 적용하여 이미지를 벡터화하고 결과 임베딩을 고밀도 벡터로 저장합니다.
  4. 애플리케이션이 Elasticsearch에서 kNN 쿼리를 시작하여 임베딩을 가져와 최근접 이웃을 반환합니다.
  5. 애플리케이션이 응답을 처리하고 하나 이상의 일치하는 이미지를 렌더링합니다.

대화형 이미지 유사성 검색을 구현하는 데 필요한 주요 구성 요소와 정보 흐름을 이해했으므로 이제 시리즈의 마지막 파트를 살펴보고 이를 실제로 구현하는 방법을 알아보실 수 있습니다. 애플리케이션 환경을 설정하고, NLP 모델을 가져오고, 마지막으로 이미지 임베딩 생성을 완료하는 방법에 대한 단계별 가이드가 제공됩니다. 그러고 나면 키워드 없이 자연어로 이미지를 검색하실 수 있습니다. 

이미지 유사성 검색 설정 시작하기 >>