본문 바로가기
dev/elasticsearch

Elasticsearch 시맨틱 검색(semantic search)

by igooo 2024. 11. 27.
728x90

개요

Elasticsearch의 semantic_text를 사용하여 시맨틱 검색을 사용하는 방법과, Full-text saerch 검색과는 어떤 차이점이 있는지 같이 알아본다.

 

Full-text search

Full-text 검색은 사용자가 입력한 문장을 언어에 맞는 형태소 분석기를 사용하여 각각의 품사로 분리하고, 분리된 단어들은 BM23(https://en.wikipedia.org/wiki/Okapi_BM25) 알고리즘에 따라 문서 안에 키워드의 발생 빈도, 문서의 길이 등을 값을 기준으로 평가하여 score가 놓은 문서를 찾는 방식이다.

단점은 형태소 분석기로 분리된 단어에 대해서만 검색이 가능하다. (ex 맛집으로 검색하면 식당이 들어간 문서는 검색되지 않는다.

동의어를 사용하면 처리가 가능하지만 사용자가 동의어를 모두 지정해야 한다.
https://www.elastic.co/kr/blog/boosting-the-power-of-elasticsearch-with-synonyms

 

ex) 흑백 요리사에 나온 맛집이라는 문장으로 검색을 하면 아래와 같은 검색 결과를 확인할 수 있다..

검색한 쿼리에 단어들이 포함된 문서들이 검색 결과에 포함되는 것을 볼 수 있다.

 

Semactic search

시맨틱 검색은 단어와 단어가 속한 구문의 의미를 해석하는 검색 방법이다. 즉 사용자가 입력한 검색 쿼리와 일치하는 단어가 있는 문서가 아닌 검색 쿼리와 의미적으로 일치하는 문서를 반환한다.

시맨틱 검색은 벡터 검색을 통해 제공되며, 벡터 검색이란 문장의 내용을 숫자 형태인 벡터로 변환한 다음, 벡터들 사이에 관계를 측정하여 입력한 쿼리와 위치적으로 가까운 문서를 반환하는 방식이다.

검색 데이터를 엠베딩(벡터화)해서 검색 엔진에 저장하고. 사용자의 검색 쿼리도 엠베딩하여 검색 엔징을 통해 입력한 검색어 기반 가장 가까운 문서에 대하여 응답한다.

 

 

Getting Started

Requirements

  • Elasticsearch (inference API 지원 버전)
  • Inference APIs (Text embedding)

예제에서는 Elasticsearch는 8.16.1 버전을 사용했고 Inference는 OpenAI에 Text Embedding API를 사용했다.

Elasticsearch는 다양한 Inference API를 제공하고 있으니 사용 가능한 서비스 기준으로 아래 문서를 참고하여 설정한다.

Inference API 참고 : https://www.elastic.co/guide/en/elasticsearch/reference/current/inference-apis.html

 

Inference endpoint 생성

Create inference API를 사용하여 inference endpoint를 생성한다.

PUT /_inference/text_embedding/openai-embeddings
{
	"service": "openai",
	"service_settings" : {
		"api_key: "{API_KEY}",
		"model_id": "text-embedding-ada-002",
		"url": "https://api.openai.com/v1/embeddings"
	}
}

 

URL에 path-param을 사용하여 openai-embedding 이름으로 inference id를 생성한다.

또한 openai에 사용하는 모델과 URL에 대한 설정을 해준다.

 

Inference별로 설정 방법이 다르기 때문에 사용하는 서비스에 맞춰 엔드포인트를 생성한다.

 

index mapping 생성

semantic-embeddings 인덱스에 대한 맵핑 정보를 설정한다.

인덱스 이름은 semantic-embeddingscontent 필드에는 시맨틱 검색을 위한 type으로 semantic_text로 설정하고, inference_id는 위에서 생성한 openai-embeddings 값을 설정해 준다.

PUT semantic-embeddings
{
	"mappings": {
		"properties": {
			"content": {
				"type": "semantic_text",
			 	"inference_id": "openai-embedding"
			}
		}
	}
}


# Response
{
	"inference_id": "openai-embeddings",
	"task_type": "text_embedding",
	"service": "openai",
	"service_settings": {
		"model_id": "text-embedding-ada-002",
		"url": "https://api.openai.com/v1/embeddings",
		"similarity": "dot_product",
		"dimesions": 1536,
		"rate_limit" {
			"requests_per_minute": 3000
		}
	},
	"chunking_settings": {
		"strategy": "sentence",
		"max_chunk_size": 250,
		"sentence_overlap": 1
	}
}

 

생성된 mapping 정보를 보면  inference  정보와 simiarity. dimesions 값을 확인할 수 있다.

이전 포스팅에 관련 내용을 참고한다. https://blog.igooo.org/116

 

데이터 로드 

일반 인덱스와 동일하게 문서를 인덱싱할 수 있다.

데이터를 저장하면 Elasticsearch 내부적으로 Inference API를 사용하여 데이터를 임베딩하고 변환된 Vector 정보를 저장한다.

POST /semantic-embeddings/_doc
{
	"content": "PC 게임 이용 중 개인정보에 등록된 퓨대폰 번호로 SMS 인증 및 본인 명의 휴대폰....."
}
Elasticseach 예제에서는 text파일에 데이터를 임의에 index로 로드하고 reindex API를 사용하여 시맨틱
검색으로 사용할 semantic-embeddings 인덱스로 다시 인덱스 한다.

 

 

Semactic-search

데이터가 모두 임베딩되어 저장된 이후에는 시맨틱 검색을 사용하여 데이터를 쿼리 할 수 있다. 일반 Full-text 검색과 다른 점은  query의 유형을  sematic으로 지정 후 sematic_text로 설정된 content 필드를 조회한다.

GET semantic-embeddings/_search
{
	"query" : {
		"semantic": {
			"field": "content",
			"query": "문의하고 싶어요"
		}
	},
    "size": 2
}

# Response
{
	"took": 532,
	"time_out": false,
	"_shards": {
		"total": 1,
		"successful": 1,
		"skipped": 0,
		"failed": 0
	},
	"hits": {
		"total": {
			"value": 12,
			"relation": "eq"
		},
		"max_score": 0.8905029,
		"hits": [
			{
				"_index": "semantic-embeddings",
				"_id": "yCvpbJMBh4hPZA9jwQ5D",
                "_score": 0.8905029,
                "_source": {
                	"content": {
                		"text": "PC 게임 이용 중 개인정보에 등록된 휴대폰 번호로 SMS 인증 및 본인 ....."
                		"inference": {
                			"inference_id": "openai-embedding",
                			"model_settings" :{
                				"task_type": "text_embedding",
                				"dimensions": 1536,
                				"similarity": "dot_product",
                				"element_type": "float"
                			}
                		},
                		"chunks: [
                			"text": "PC 게임 이용 중 개인정보에 등록된 휴대폰 번호로 SMS 인증 및 본인 .....",
                			"embeddings": [
                				-0.0021247189,
                				-0.006244204,                				
                				.......
                				-0.012235002
                			]
                		]
                	}
                }
			}.
            ......
            
		]
	}
    
}

 

쿼리로 입력한 문의하고 싶어요에 가장 가까운 문서는 문의라는 단어가 포함되어 있지는 않지만,  시맨틱 검색을 통해 결과적으로  사용자가 입력한 쿼리와 가장 가까운 2개의 문서를 반환하였다.

 

추가적으로 Full-text 검색과는 다르게 검색 요청마다 사용자가 입력한 쿼리를 임베딩 하는 과정도 필요하기 때문에 쿼리 처리 시간이 "took": 532가(532ms) 걸린 것을 볼 수 있다. (실제 서비스에 사용 가능한지는 성능테스트가 필요하겠다.)

처리 속도 향상을 위해서는 더 작은 embedding 모델을 사용하거나
임베딩 API를 내부적으로 서빙하여 제공하는 방법이 있다. 

 

참고적으로 Elasticsearch 서버에서 curl 명령을 사용하여 동일한 API를 호출해 보면 아래와 같은 시간이  걸린다.

$ time curl https://api.openapi.com/v1/embedding

real  0m0.690s
user  0m0.063s
sys   0m0.004s

 

다음 포스팅에는 Full-text 검색과 시맨틱 검색을 같이 사용하는 하이브리드 검색에 대하여 같이 알아본다.

 

참고

https://www.elastic.co/kr/what-is/semantic-search

https://www.elastic.co/guide/en/elasticsearch/reference/current/semantic-search-semantic-text.html