Tech

GCP VertexAI Matching Engine in Production

2023. 05. 18

GCP VertexAI Matching Engine in Production_화해

GCP VertexAI Matching Engine in Production

 

 

 

Intro

 

안녕하세요, 데이터팀 AI파트 윤성민, 홍승표입니다.

최근 화해에서는 “사진 속 비슷한 제품 찾기”를 릴리즈하였습니다. 인물 사진을 올리면 사진 속의 인물이 바른 립스틱이나 립틴트와 비슷한 색을 내는 제품이 무엇인지 검색할 수 있는 기능인데요. 이 기능에서 AI파트는 유사한 색의 입술 발색 이미지와 제품을 실시간으로 추천해 줄 수 있는 REST API를 개발하는 역할을 담당하였습니다.

 

 

 

GCP VertexAI Matching Engine in Production_화해

 

 

 

위의 서비스 개발을 위해서 총 3가지 기능이 필요합니다.

  1. 얼굴에서 입술을 segmentation 하는 모델 개발
  2. 입술 segmentation의 Embedding을 구하는 모델 개발
  3. 빠르게 Embedding 간의 유사도를 계산할 수 있는 모듈 개발

이미 화해에서는 유저가 조회한 메이크업 제품과 비슷한 발색의 다른 제품을 추천하는 서비스를 제공하고 있었고, 위에서 필요한 기능 대부분은 그 과정에서 이미 구현한 상태였습니다. 다만 기존의 추천 서비스는 배치 파이프라인으로 제공하고 있었으나 이번의 ‘사진 속 비슷한 제품 찾기’는 유저들이 직접 업로드한 사진에 실시간으로 대응하는 구조가 필요했습니다.

 

 

 

 

 

 

서비스 구현을 위해서 아래와 같은 3가지의 독립적인 API를 설계하였습니다

  • Handler : 요청에 대한 로직을 수행하고 결과를 리턴하는 Endpoint
  • Segmentation : 이미지에서 입술 영역을 분할하는 Endpoint
  • SimilaritySearch : 가장 유사한 입술 발색 N개를 검색하는 Endpoint

AI파트는 GCP 인프라에서 대부분의 서비스를 개발, 배포하고 있기에 각각의 API를 GCP의 서비스를 활용하여 구현하였습니다. Handler는 Cloud Function을 사용하였고, Segmentation은 GPU 사용이 용이한 Vertex AI Endpoint를 사용하였습니다. Similarity Search에 대응하는 서비스는 최근 공개된 Matching Engine을 사용하였는데, 아직까지 많은 레퍼런스가 존재하지 않고 SDK 또한 명확하지 않은 부분이 있어 구현 과정에서 어려움이 존재하였습니다.

 

 

 

 

Vertex AI Matching Engine

 

검색 및 추천 시스템의 관점에서 발색이 가장 유사한 제품을 찾는 문제를 보면 전형적인 Vector Similarity Search 문제에 해당합니다. 이미 이미지에서 발색 정보에 대한 embedding vector를 구하는 모델이 있었으므로, 남은 과제는 주어진 벡터와 가장 가까운 벡터를 빠르게 검색하는 것이었습니다.

 

이러한 벡터 검색 서비스를 구현할 때 두 가지 과제를 마주하게 됩니다. 첫 번째는 검색 알고리즘 자체를 최적화하는 것이고, 두 번째는 최적화된 알고리즘을 서비스에 적용할 수 있도록 서버를 구축하는 것입니다. 벡터 검색 알고리즘은 이미 검색 및 추천 시스템 분야에서 오랫동안 연구가 이루어진 문제입니다. 따라서 Facebook의 FAISS, Google의 Vertex AI Matching Engine, Spotify의 Annoy 등 다양한 솔루션에서 이러한 알고리즘을 이미 구현하였습니다. 프로젝트 초기에는 저희에게 익숙한 FAISS를 사용하여 서비스를 개발하는 것을 검토하였습니다.

 

하지만 알고리즘을 실제 서비스에 적용하면서 만나는 문제들이 있습니다. 대부분의 솔루션은 라이브러리 형태로 제공되어 서비스에 적용하려면 API 설계부터 scaling 전략까지 다양한 고민을 해야 합니다. 화해 AI 파트에서는 운영 부담을 줄이고 비즈니스 로직에만 집중하고자 managed service 형태로 제공되는 벡터 검색 알고리즘을 사용하기로 했습니다. 그리고 GCP에 구축된 기존의 인프라를 활용하기 위하여 GCP의 Vertex AI Matching Engine을 사용하기로 결정하였습니다.

 

 

 

 

Matching Engine Deployment Process

 

Matching Engine을 사용하기 위해서는 크게 2가지 과정이 필요합니다. 첫 번째는 유사도 검색의 기준이 되는 인덱스를 생성하는 과정입니다. 두 번째는 생성된 인덱스를 엔드포인트에 배포하는 과정입니다.

 

 

인덱스 생성

 

1. Index 파일 생성


# index.jsonl
{"id":"42", ..., "embedding":[0.03828058, 0.01165061, 0.05325993, 0.00832186, 0.0183081, 0.9237269, 0.16310854, 0.33953208]}
{"id":"43", ..., "embedding":[0.4412613, 0.049029034, 0.19611613, 0.24514517, 0.39223227, 0.5883484, 0.34320325, 0.2941742]}

 

우선 검색 대상이 될 임베딩을 위와 같은 JSONL포맷으로 저장해야 합니다. 각 라인은 실제 검색 대상이 되는 embedding과 임베딩에 대한 메타데이터인 id를 포함해야 합니다.

 

 

2. Matching Engine Index 생성


index = aiplatform.MatchingEngineIndex.create_tree_ah_index(
    display_name="my-index",
    contents_delta_uri="gs://my-bucket/my-folder",
    dimensions=8,
    approximate_neighbors_count=150,
    distance_measure_type="SQUARED_L2_DISTANCE",
)

 

저장된 인덱스 파일은 google-cloud-aiplatform SDK를 통해 MatchingEngine Index를 생성할 수 있습니다. 각 필드의 의미는 다음과 같습니다.

  • display_name : 저장된 인덱스의 이름
  • contents_delta_uri : GCS에 저장된 인덱스 JSONL파일을 포함하는 디렉토리의 uri
  • dimensions : 저장된 임베딩의 차원수
  • approximate_neighbors_count : 근사 검색을 통해 찾을 이웃의 기본 개수
  • distance_measure_type : 유사도 계산을 위한 산식

여기서 distance_measure_type은 DOT_PRODUCT_DISTANCE, SQUARED_L2_DISTANCE, L1_DISTANCE, COSINE_DISTANCE 총 4가지를 지원합니다. 또 contents_delta_uri는 GCS의 uri만 받을 수 있습니다.

 

 

 

GCP VertexAI Matching Engine in Production_화해

생성된 Matching Engine Index

 

 

 

Matching Engine은 빠른 벡터 검색을 위해 Anisotropic Vector Quantization([1][2])이라는 알고리즘을 통해 벡터들의 근사치로 구성된 인덱스를 생성합니다. 인덱스 생성 시간은 약 40분 정도 소요됩니다.

 

 

인덱스 배포

 

1. Matching Engine Index Endpoint 생성

from google.cloud import aiplatform_v1beta1

project_name = "my-project" # 엔드포인트를 생성할 GCP 프로젝트 이름
location = "my-location" # 엔드포인트를 생성할 GCP 프로젝트 리전(e.g. us-central1)
endpoint_display_name = "my-endpoint" # GCP 콘솔에 표시할 엔드포인트 이름
network = "my-vpc-network" # 엔드포인트를 운용할 GCP VPC 네트워크 이름

parent = f"projects/{project_name}/locations/{location}"
endpoint_url = f"{location}-aiplatform.googleapis.com"
index_endpoint_client = aiplatform_v1beta1.IndexEndpointServiceClient(
    client_options={"api_endpoint": endpoint_url}
)
index_endpoint = {
    "display_name": endpoint_display_name,
    "network": network,
}
r = index_endpoint_client.create_index_endpoint(
    parent=parent, index_endpoint=index_endpoint
)
result = r.result()
my_endpoint = aiplatform.MatchingEngineIndexEndpoint(result.name)

Matching Engine Index Endpoint 생성 코드

 

실제 생성된 인덱스에 접근하기 위해서는 Matching Engine Index Endpoint에 인덱스를 배포하여 Endpoint를 통해 값을 얻어야 합니다. 이를 위해 우선 위의 코드를 사용하여 Endpoint를 생성해야 합니다. network는 Endpoint에 사용될 VPC 네트워크로 꼭 지정을 해줘야만 생성이 가능합니다. 이후 Endpoint에 접근하여 검색을 하는 서비스(e.g. Cloud Function 등)도 해당 VPC를 사용해야만 성공적으로 response를 받을 수 있습니다.

 

 

2. Matching Engine Index 배포

위 과정에서 생성된 엔드포인트에 인덱스를 배포하면 Matching Engine을 사용할 수 있습니다. 인덱스 배포 또한 위 코드와 같이 Python SDK를 통해 가능합니다. 각 필드의 의미는 아래와 같습니다.

my_endpoint.deploy_index(
    index=index,
    deployed_index_id="my-deployed-index-01",
    display_name="my-deployed-index",
    reserved_ip_ranges="my-ip-range",
)

 

  • index : 배포 대상이 되는 인덱스
  • deployed_index_id : 배포된 인덱스를 식별할 수 있는 문자열 ID (프로젝트 내에서 유일해야 함)
  • display_name : 배포된 인덱스의 display명
  • reserved_ip_ranges : 배포된 인덱스에서 사용할 수 있는 VPC 네트워크 IP 범위

 

 

 

GCP VertexAI Matching Engine in Production_화해

생성된 Matching Engine Index Endpoint

 

 

 

엔드포인트가 정상적으로 생성되었다면, GCP 콘솔에서 위와 같이 엔드포인트를 확인할 수 있습니다.

 

 

인덱스 업데이트

index = aiplatform.MatchingEngineIndex(index_id)
index.update_embeddings(
    contents_delta_uri="gs://my-bucket/my-folder",
		is_complete_overwrite=True,
)

 

검색 대상이 추가되거나 삭제되면 인덱스를 업데이트해야할 필요도 있습니다. 이때 Matching Engine Index를 생성하는 것과 유사한 방법으로 업데이트할 수 있습니다. 임베딩이 저장된 JSONL파일을 GCS에 업로드하고 위와 같은 코드를 통해 인덱스를 업데이트합니다. is_complete_overwrite는 전체 인덱스를 새롭게 update 하는 옵션입니다.

 

 

인덱스 검색


# 8차원 벡터 1개를 쿼리로 설정
queries = [
	[1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0],
]

# 앞서 생성한 MatchingEngineIndexEndpoint 객체를 활용하여 검색합니다.
# 가장 가까운 3개 벡터를 가져옵니다.
retrieved_vectors = my_endpoint.match(
    deployed_index_id="my-deployed-index-01",
    queries=queries,
    num_neighbors=3,
)

# 쿼리 벡터가 1개이므로, 첫 번째 쿼리 벡터에 대한 검색 결과를 list of dict로 변환합니다.
result_dicts = [
    {
       "vectorId": int(n.id), 
       "distance": float(n.distance),
    }
    for n in retrieved_vectors[0]
]

 

배포된 인덱스를 이용해 Vector Similarity Search를 할 수 있습니다. 위의 코드는 8차원의 임베딩값의 쿼리와 가장 유사한 3개의 아이템을 가져오는 예시 코드입니다.

 

 

 

 

Outro

 

Vector Similarity Search는 많은 ML 서비스에서 마주치는 문제이지만 이를 실시간 요청 서비스에 녹여내려면 많은 고민과 노력이 필요합니다. 화해 유저의 일상에서 필요한 정보를 바로 그 순간에 실시간으로 제공하는 서비스는 쉽지 않지만 충분히 가치가 있다고 믿었기에 시도해야 했습니다. 화해 AI 파트에서는 GCP에서 제공하는 Vertex AI Matching Engine을 적용함으로써 서비스를 구현하는데 필요한 핵심 로직에만 집중하여 빠르게 서비스를 릴리즈 할 수 있었습니다.

 

해당 기능은 링크를 통해 바로 체험할 수 있으니 많이 방문해 주세요.

 

 

 

 

참고자료

 

 

GCP VertexAI Matching Engine in Production


이 글이 마음에 드셨다면 데이터팀의 다른 콘텐츠도 확인해보세요!

#02 그 많은 화해 데이터는 어떻게 사용되나요?

화해 Data & Experiment Festival(D.E.F)

 

 

  • Computer vision
  • VertexAI
  • AI
  • Image retrieval
  • GCP
  • Matching Engine
  • 데이터팀
avatar image

데이터팀 | AI파트

AI 기술로 화해의 새로운 가치를 만들어 갑니다

연관 아티클