NIRVANA

[ElasticSearch]시맨틱 검색(Semantic Search)이란? 본문

DataEngineering

[ElasticSearch]시맨틱 검색(Semantic Search)이란?

녜잉 2024. 9. 1. 17:13

시맨틱 검색이란?

  • 단어와 구문의 의미를 해석하는 검색 엔진 기술, 단순히 쿼리의 단어와 문자가 그대로 일치하는 콘텐츠가 아닌, 쿼리의 '의미'와 일치하는 콘텐츠를 반환
  • 자연어를 보다 정확하고 상황에 맞게 해석하여 검색 결과의 품질을 향상시키기 위해 사용 

 

시맨틱 검색 vs 키워드 검색

  • 키워드 검색은 단어와 단어, 단어와 동의어, 단어와 유사한 단어가 일치하는 결과를 반환하는 반면 시맨틱 검색은 쿼리에 포함된 단어의 의미와 일치하는 것을 찾음
    • 시맨틱 검색이 직접적인 단어 일치가 있는 결과를 생성하지 않을 수도 있지만 사용자의 의도와는 일치하는 경우도 존재
  • 키워드 검색 엔진의 경우 동의어나 단어 생략과 같은 쿼리 확장 혹은 완화 도구를 사용하고 오타 허용, 토큰화, 정규화와 같은 자연어 처리 및 이해 도구를 사용
  • 시맨틱 검색은 벡터 검색을 활용해서 의미와 일치하는 쿼리 결과를 반환하게 됨 

ex) '초콜릿 밀크' 라는 단어를 사용자가 입력 했을 때, 시맨틱 검색 엔진은 '초콜릿 밀크'와 '밀크 초콜릿'을 구별하게 됨.

쿼리의 키워드가 동일하더라도 쓰여진 순서에 따라서 의미가 변경 되는데, 시맨틱 검색은 이와 같은 의미를 이해하고 결과를 반환하는 것이 가능! 

 

시맨틱 검색 작동 방식 

  • 시맨틱 검색은 벡터 검색을 통해서 제공
    • 벡터 검색: 검색 가능한 정보의 세부 정보를 관련 용어나 항목의 필드 혹은 벡터로 인코딩한 후, 벡터를 비교하여 어떤 것이 가장 유사한지 결정 

  • 벡터 검색을 기반으로 구현된 시맨틱 검색에서는 쿼리 파이프라인의 양쪽 끝에서 동시에 작업을 진행하여 결과를 생성
    • 쿼리 실행 시, 검색 엔진은 쿼리를 (데이터와 관련된 컨텍스트의 수치 표현인) 임베딩으로 변환
    • KNN 알고리즘을 사용, 기존 문서의 벡터를 쿼리 벡터와 비교
    • 개념적 관련성을 기반으로 순위 생성 

1. 쿼리 실행 시, 검색 엔진에서 쿼리를 임베딩으로 변환한 후 벡터 공간에 저장

2. KNN 알고리즘을사용하여 기존 문서의 벡터와 1에서 변환된 쿼리 벡터를 비교

3. 가장 관련성이 높은 순으로 순위를 매김 

 

💡컨텍스트

시맨틱 검색에서 컨텍스트는 검색자의 지리적 위치, 쿼리 단어의 텍스트 컨텍스트 혹은 검색자의 검색 기록 컨텍스트와 같은 추가정보를 나타내는 역할을 맡게 됨. 이를 사용해서 시맨틱 검색이 데이터 세트에서 단어의 의미를 결정하며, 유사한 컨텍스트에서 사용될 수 있는 다른 단어도 식별하게 됨. 

ex) 사용자가 "football"을 검색했을 때, 미국에서는 축구(soccer)를 의미하고, 영국을 비롯한 다른 지역에서는 풋볼(football)을 의미하게 됨 → 시맨틱 검색은 사용자의 지리적 위치를 기반으로 결과를 구별 

 

 

시맨틱 서치 구현해보기 

데이터는 아래와 같이 준비했다

 

 

 

1) 엘라스틱 서치 인덱스 생성 및 인덱싱(문서 벡터화하기) 

인덱스를 생성하고 데이터를 기반으로 인덱싱을 진행한다. 원래는 작가 이름도 인덱스에 포함 했어야 했는데 깜빡해서(...) 일단은 id, book_name, book_review, DescriptionVector(리뷰를 벡터화한 데이터를 저장할 필드)로 인덱스를 생성했다. 

from sentence_transformers import SentenceTransformer
from elasticsearch import Elasticsearch

import pandas as pd 

model = SentenceTransformer('all-MiniLM-L6-v2')

es =  Elasticsearch(
    "https://localhost:9200",
    basic_auth=("elastic","엘라스틱서치 비번"),
    ca_certs="인증서 경로",
)

#print(es.ping())
df= pd.read_csv("book_data.csv", encoding='cp949')

#인덱스 생성 
index_name = 'book_semantic_search'

if not es.indices.exists(index=index_name):
    es.indices.create(index=index_name,body={
        "mappings": {
            "properties": {
                "bookId": {
                    "type": "integer"  
                },
                "bookName": {
                    "type": "text" 
                },
                "bookReview": {
                    "type": "text"  
                },
                "DescriptionVector": { 
                    "type": "dense_vector",  #벡터 임베딩 저장 필드 
                    "dims": 384  #벡터 차원 수 
                }
            }
        }
    })

#인덱싱 진행 
for _, row in df.iterrows():
    
    #book_review를 벡터 임베딩 진행 
    vector = model.encode(row['book_review']).tolist()
    
    es.index(index=index_name, id=row['id'], body={
        "bookName": row['book_name'],
        "bookReview": row['book_review'],
        "DescriptionVector": vector
    })

 

 

도서 검색해보기 

knn 쿼리를 사용하여 벡터 간 거리를 기반으로 가장 가까운 K개를 반환할 수 있도록 했다. 

# -*- encoding: euc-kr-*-

from sentence_transformers import SentenceTransformer
from elasticsearch import Elasticsearch

es =  Elasticsearch(
    "https://localhost:9200",
    basic_auth=("elastic","엘라스틱서치 비번"),
    ca_certs="인증서 경로",
    verify_certs=False
)

model = SentenceTransformer('all-MiniLM-L6-v2')

query = "공상 과학 소설 읽고 싶은데 추천해줘라"
query_vector = model.encode(query).tolist()

response = es.search(index="book_semantic_search", body={
    "size": 2,  # 검색할 문서 수
    "query": {
        "knn": {
            "field": "DescriptionVector",
            "query_vector": query_vector,
        },
    }
})

for hit in response['hits']['hits']:
    source = hit['_source']
    print(f"책 이름: {source.get('bookName')}")
    print(f"책 리뷰: {source.get('bookReview')}")
    print(f"스코어: {hit['_score']}")
    print("----------------------")

 

es.search() 함수

  • 엘라스틱 서치에서 검색 쿼리를 실행하고 결과를 반환하는 데 사용되는 함수
  • 지정된 인덱스에서 쿼리를 실행하고, 결과를 반환 
  • 주요 파라미터
    • index: 검색할 인덱스의 이름을 지정, 여러 인덱스를 동시에 검색도 가능
    • body: 검색 쿼리와 관련 내용을 정의하는 JSON 객체, 검색 데이터의 조건을 포함 
response = es.search(index="index_name", body={
    "query": {
        "match": {
            "field": "value"
        }
    }
})

 

body의 query는 검색 조건을 정의하는 데에 사용된다. size(반환할 문서의 최대 개수), from(검색 결과에서 처음 반환할 문서의 인덱스), sort(검색 결과 정렬 방식), _source(반환할 문서 필드 지정), aggs(집계 쿼리, 데이터 집계 및 분석에 활용) 등을 사용해 query를 작성한다. 

 

 

 

결과 확인하기

나름 잘 나오는 것을 확인할 수 있다!

 


 

사실 진짜 간단하게 진행했기 때문에 로맨스 소설을 검색했는데 삼체가 반환되고, 컴퓨터 과학 공부하고 싶다고 했는데 세상을 가리키는 말은 숲이 대규모 설계 시스템 책 보다 위에 나오게 된다...ㅎ 

 

모델도 바꿔보고 책 리뷰도 책의 특징이 잘 드러날 수 있도록 변경하면 성능이 더 향상 되지 않을까?? 

 

 

참고:

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

 

시맨틱 검색이란 무엇인가? | 시맨틱 검색 종합 안내서

시맨틱 검색을 정의하고 어떻게 작동하는지 알아보세요. 키워드 검색과 어떤 차이가 있는지, 이점은 무엇인지, 시맨틱 검색을 어떻게 시작하는지 확인해 보세요. ...

www.elastic.co