Elasticsearch에서 이미지 유사성 검색을 구현하는 방법

blog-thumb-website-search.png

단 몇 단계로 Elastic에서 이미지 유사성 검색을 구현하는 방법을 알아보세요. 애플리케이션 환경 설정을 시작한 다음 NLP 모델을 가져오고, 마지막으로 이미지 집합에 대한 임베딩 생성을 완료하면 됩니다.

Elastic에서 이미지 유사성 검색 개요 보기 >> 

환경 설정 방법

첫 번째 단계는 애플리케이션의 환경을 설정하는 것입니다. 다음은 일반적인 요구 사항입니다.

  • Git
  • Python 3.9
  • Docker
  • 수백 개의 이미지

최상의 결과를 얻으려면 수백 개의 이미지를 사용하는 것이 중요합니다.

작업 폴더로 이동하여 생성된 리포지토리 코드를 확인합니다. 그런 다음 리포지토리 폴더로 이동합니다.

$ git clone https://github.com/radoondas/flask-elastic-image-search.git
$ cd flask-elastic-image-search

Python을 사용하여 코드를 실행할 예정이므로 모든 요구 사항이 충족되고 환경이 준비되었는지 확인해야 합니다. 이제 가상 환경을 생성하고 모든 종속성을 설치합니다.

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

Elasticsearch 클러스터 및 임베딩 모델

계정에 로그인하여 Elasticsearch 클러스터를 구동합니다. 다음과 같이 작은 클러스터를 설정합니다. 

  • 2GB 메모리의 핫 노드 1개
  • 4GB 메모리의 ML(머신 러닝) 노드 1개(Elasticsearch로 가져올 NLP 모델이 최대 1.5GB의 메모리를 사용하므로 이 노드의 크기가 중요합니다.)

배포가 준비되면 Kibana로 이동하여 머신 러닝 노드의 용량을 확인합니다. 보기에 하나의 머신 러닝 노드가 표시됩니다. 현재는 로드된 모델이 없습니다.

Eland 라이브러리를 사용하여 OpenAI로부터 CLIP 임베딩 모델을 업로드합니다. Eland는 Elasticsearch에서 데이터를 탐색하고 분석하기 위한 Python Elasticsearch 클라이언트로, 텍스트와 이미지를 모두 처리할 수 있습니다. 이 모델을 사용하여 텍스트 입력에서 임베딩을 생성하고 일치하는 이미지를 쿼리합니다. 자세한 내용은 Eland 라이브러리의 설명서에서 확인하세요.

다음 단계에서는 Elasticsearch 엔드포인트가 필요합니다. 이는 배포 세부 정보 섹션의 Elasticsearch 클라우드 콘솔에서 가져올 수 있습니다.

엔드포인트 URL을 사용하여 리포지토리의 루트 디렉터리에서 다음 명령을 실행합니다. 그러면 Eland 클라이언트가 Elasticsearch 클러스터에 연결하고 모델을 머신 러닝 노드에 업로드합니다. –url 파라미터를 사용하여 실제 클러스터 URL을 참조합니다. 예를 들어, 아래에서는  ‘image-search.es.europe-west1.gcp.cloud.es.io'를 클러스터 URL로 참조합니다.

--url https://elastic:<password>@image-search.es.europe-west1.gcp.cloud.es.io:443

Eland 가져오기 명령을 입력합니다.

$ eland_import_hub_model --url https://elastic:<password>@<URL>:443 \
  --hub-model-id sentence-transformers/clip-ViT-B-32-multilingual-v1 \
  --task-type text_embedding --ca-certs app/conf/ess-cloud.cer \
  --start

출력은 다음과 같은 형태가 됩니다.

2022-12-12 13:40:52,308 INFO : Establishing connection to Elasticsearch
2022-12-12 13:40:52,327 INFO : Connected to cluster named 'image-search-8.6.1' (version: 8.5.3)
2022-12-12 13:40:52,328 INFO : Loading HuggingFace transformer tokenizer and model 'sentence-transformers/clip-ViT-B-32-multilingual-v1'
2022-12-12 13:41:03,032 INFO : Creating model with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'
2022-12-12 13:41:03,050 INFO : Uploading model definition
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:42<00:00,  3.01 parts/s]
2022-12-12 13:41:45,902 INFO : Uploading model vocabulary
2022-12-12 13:41:46,120 INFO : Starting model deployment
2022-12-12 13:41:52,825 INFO : Model successfully imported with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'

업로드는 연결 상태에 따라 몇 분 정도 걸릴 수 있습니다. 완료되면 머신 러닝 Kibana 페이지, 즉 Menu -> Analytics -> Machine Learning -> Model management ->Trained models에서 훈련된 모델의 목록을 확인합니다. NLP Clip 모델이 ‘started’ 상태인지 확인합니다.

화면에 ML 작업 및 훈련된 모델 동기화 필요 메시지가 표시되면 링크를 클릭하여 모델을 동기화합니다.

이미지 임베딩 생성 방법

Elasticsearch 클러스터를 설정하고 임베딩 모델을 가져온 후에는 이미지 데이터를 벡터화하고 데이터 세트의 모든 이미지에 대해 각각 이미지 임베딩을 생성해야 합니다.

이미지 임베딩을 생성하려면 간단한 Python 스크립트를 사용하세요. 이 스크립트는 create-image-embeddings.py에서 찾을 수 있습니다. 이 스크립트는 이미지의 디렉터리를 트래버스하고 개별 이미지 임베딩을 생성합니다. 그리고 이름과 상대 경로가 있는 문서를 생성하고, 제공된 매핑을 사용하여 Elasticsearch 인덱스  my-image-embeddings’에 이를 저장합니다.

모든 이미지(사진)를 ‘app/static/images’ 폴더에 넣습니다. 하위 폴더가 있는 디렉터리 구조를 사용하여 이미지를 정리된 상태로 유지합니다. 모든 이미지가 준비되면 몇 가지 파라미터를 사용하여 스크립트를 실행합니다.

합리적인 결과를 얻으려면 최소한 수백 개의 이미지가 있어야 합니다. 이미지 수가 너무 적으면 검색할 범위가 매우 작고 검색 벡터까지의 거리가 매우 비슷하기 때문에 기대하는 결과를 얻을 수 없습니다.

image_embeddings 폴더에서 스크립트를 실행하고 변수에 대한 값을 사용합니다.

$ cd image_embeddings
$ python3 create-image-embeddings.py \
  --es_host='https://image-search.es.europe-west1.gcp.cloud.es.io:443' \
  --es_user='elastic' --es_password=<password> \
  --ca_certs='../app/conf/ess-cloud.cer'

이미지 수, 이미지 크기, CPU 및 네트워크 연결 상태에 따라 이 작업에 시간이 다소 걸립니다. 전체 데이터 세트를 처리하기 전에 적은 수의 이미지로 실험해 보세요.
스크립트가 완료되면 Kibana 개발자 도구를 사용하여 my-image-embeddings 인덱스가 존재하고 해당 문서가 있는지 확인할 수 있습니다.

GET _cat/indices/my-image-embeddings?v

health status index               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   my-image-embeddings vfA3wOheT1C79R-PceDyXg   1   1       1222            0     24.4mb         12.2mb

문서를 보면 예시와 같이 매우 유사한 JSON 객체를 확인할 수 있습니다. images 폴더 안에 이미지 이름, 이미지 ID 및 상대 경로가 표시됩니다. 이 경로는 프론트엔드 애플리케이션에서 검색 시 이미지를 올바르게 표시하는 데 사용됩니다.
JSON 문서에서 가장 중요한 부분은 CLIP 모델에 의해 생성된 고밀도 벡터를 포함하는 ‘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"
   }
}

Flask 애플리케이션을 사용하여 이미지 검색

이제 환경이 모두 설정되었으므로 다음 단계로 넘어가 Elastic에서 개념 증명으로 제공하는 Flask 애플리케이션을 통해 실제로 자연어를 사용하여 이미지를 검색하고 유사한 이미지를 찾을 수 있습니다. 이 웹 애플리케이션에는 손쉽게 이미지를 검색할 수 있도록 지원하는 간단한 UI가 있습니다. 이 프로토타입 Flask 애플리케이션은 GitHub 리포지토리에서 액세스할 수 있습니다. 

이 애플리케이션은 백그라운드에서 두 가지 작업을 수행합니다. 검색 상자에 검색 문자열을 입력하면 머신 러닝 _infer 엔드포인트를 사용하여 텍스트가 벡터화됩니다. 그런 다음 고밀도 벡터가 포함된 쿼리가 이 벡터가 있는 my-image-embeddings 인덱스에 대해 실행됩니다.

예시에서 이 두 가지 쿼리를 볼 수 있습니다. 첫 번째 API 호출은 _infer 엔드포인트를 사용하며, 그 결과는 고밀도 벡터입니다.

POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "endless route to the top"}
    ]
}

두 번째 작업인 검색 쿼리에서는 이 고밀도 벡터를 활용하여 점수별로 이미지를 정렬합니다.

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
}

Flask 애플리케이션을 실행하려면 리포지토리의 루트 폴더로 이동하여 .env 파일을 구성합니다. 구성 파일의 값은 Elasticsearch 클러스터에 연결하는 데 사용됩니다. 다음 변수에 대한 값을 삽입해야 합니다. 이 값들은 이미지 임베딩 생성에 사용된 것과 동일한 값입니다.

  • ES_HOST='URL:PORT'
  • ES_USER='elastic'
  • ES_PWD='password'

준비되면 기본 폴더에서 flask 애플리케이션을 실행한 후 시작될 때까지 기다립니다.

# In the main directory 
$ flask run --port=5001

애플리케이션이 시작되면 아래와 유사한 출력이 표시되며, 마지막 부분에 URL이 표시되어 애플리케이션에 액세스할 수 있습니다.

flask run --port=5001
 * Serving Flask app 'flask-elastic-image-search.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit

축하합니다! 이제 애플리케이션이 실행되고 있으며 http://127.0.0.1:5001에서 인터넷 브라우저를 통해 액세스할 수 있습니다.

이미지 검색 탭으로 이동하여 이미지를 가장 잘 설명하는 텍스트를 입력합니다. 키워드가 아닌 단어나 설명적인 텍스트를 사용해 보세요.

아래 예에서 입력한 텍스트는 ‘endless route to the top’입니다. 결과는 데이터 세트에서 표시됩니다. 사용자가 결과 집합에서 특정 이미지를 좋아하는 경우, 해당 이미지 옆에 있는 버튼을 클릭하면 이와 유사한 이미지들이 표시됩니다. 사용자는 이 작업을 수없이 수행하고 이미지 데이터 세트에서 자신만의 경로를 구축할 수 있습니다.

검색은 단순히 이미지를 업로드하는 방식으로도 작동합니다. 이 애플리케이션이 이미지를 벡터로 변환하고 데이터 세트에서 유사한 이미지를 검색합니다. 이렇게 하려면 세 번째 탭인 Similar Image로 이동하여 디스크에서 이미지를 업로드하고 Search를 누릅니다.

Elasticsearch에서 사용하는 NLP(sentence-transformers/clip-ViT-B-32-multilingual-v1 모델은 다국어 모델로, 여러 언어로 추론을 지원하므로 자신의 언어로 이미지를 검색해 보세요. 그런 다음 영어 텍스트를 사용해서도 결과를 확인해 보세요.

사용된 모델은 일반 모델로, 매우 정확하지만 사용 사례나 기타 요인에 따라 결과가 달라질 수 있다는 점에 유의해야 합니다. 더 높은 정확도가 필요한 경우 일반 모델을 조정하거나 자체 모델을 개발해야 합니다. CLIP 모델은 시작점으로 사용하도록 제공되는 모델입니다.

코드 요약

전체 코드는 GitHub 리포지토리에서 찾을 수 있습니다. 애플리케이션의 주요 로직을 구현하는 routes.py에서 코드를 검사하고 계실 수 있는데요. 명확한 경로 정의 외에도 _infer 및 _search 엔드포인트(infer_trained_modelknn_search_images)를 정의하는 모델에 집중해야 합니다. 이미지 임베딩을 생성하는 코드는 create-image-embeddings.py 파일에 있습니다.

요약

이제 Flask 앱을 설정했으므로 간편하게 여러분만의 이미지 세트를 검색할 수 있습니다! Elastic은 플랫폼 내에서 벡터 검색의 네이티브 통합을 제공하므로 외부 프로세스와의 통신이 필요 없습니다. PyTorch를 사용하여 개발하셨을 수 있는 맞춤형 임베딩 모델을 개발하고 사용할 수 있는 유연성이 제공됩니다.

시맨틱 이미지 검색은 이미지 검색에 대한 기존의 다른 접근 방식과 비교하여 다음과 같은 이점을 제공합니다.

  • 더 높은 정확도: 벡터 유사성은 이미지의 텍스트 메타 설명에 의존하지 않고 컨텍스트와 연관성을 파악합니다.
  • 더 나은 사용자 경험: 어떤 키워드가 정확한지 추측하는 대신, 찾고 있는 것을 설명하거나 샘플 이미지를 제공하시면 됩니다.
  • 이미지 데이터베이스 분류: 이미지를 분류하는 것에 대해 걱정하지 마세요. 유사성 검색은 이미지를 정리하지 않고도 이미지 더미에서 관련 이미지를 찾을 수 있습니다.

여러분의 사용 사례가 텍스트 데이터에 더 의존한다면, 이전 블로그에서 시맨틱 검색 구현 및 텍스트에 자연어 처리 적용에 대해 자세히 알아보실 수 있습니다. 텍스트 데이터에 벡터 유사성과 전통적인 키워드 점수 체계를 함께 사용하면 양쪽 모두의 장점을 누릴 수 있습니다.
시작할 준비가 되셨나요? Elastic의 가상 이벤트 허브에서 벡터 검색 워크숍에 등록하고, 온라인 토론 포럼에서 커뮤니티에 참여해 보세요.