langchain / / 2024. 6. 27. 06:50

[langchain] 앙상블 검색기(Ensemble Retriever)

EnsembleRetriever는 retriever 리스트를 입력으로 받아 이들의 get_relevant_documents() 메서드 결과를 합치고, 상호 순위 융합(Reciprocal Rank Fusion) 알고리즘을 기반으로 결과를 재정렬한다.

다양한 알고리즘의 강점을 활용함으로써, EnsembleRetriever는 단일 알고리즘보다 더 나은 성능을 달성할 수 있다.

가장 일반적인 패턴은 희소형 검색기(BM25 같은)와 밀집형 검색기(임베딩 유사성 같은)를 결합하는 것이다. 이 두 가지의 강점이 상호 보완적이기 때문이다. 이는 "하이브리드 검색"으로도 알려져 있다. 희소형 검색기는 키워드를 기반으로 관련 문서를 찾는 데 뛰어난 반면, 밀집형 검색기는 의미적 유사성을 기반으로 관련 문서를 찾는 데 뛰어나다.

아래 예제에서는 BM25Retriever와 FAISS용 Retriever를 조합한 앙상블 Retriever를 사용한 예이다.

BM25는 주어진 쿼리에 대해 문서와의 연관성을 평가하는 랭킹 함수로 사용되는 알고리즘으로,TF-IDF 계열의 검색 알고리즘 중 SOTA 인 것으로 알려져 있다.

우선 rank_bm25를 설치한다.

pip install rank_bm25

소스코드

from dotenv import load_dotenv
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings

load_dotenv()

question = "갤럭시 S21의 특징은?"

doc_list_1 = [
    "Galaxy S9의 특징은 저렴하다는 것이다",
    "Galaxy S9의 배터리는 3000 mAh이다",
    "Galaxy S10의 카메라는 Triple rear cameras이다. ",
    "Galaxy S20의 Display는 6.2-inch Dynamic AMOLED이다.",
    "Galaxy S20의 저장공간은 128G이다",
    "Galaxy S21의 Ram은 8GB이다",
]


embedding = OpenAIEmbeddings()
faiss_vectorstore = FAISS.from_texts(
    doc_list_1, embedding, metadatas=[{"source": 2}] * len(doc_list_1)
)
faiss_retriever = faiss_vectorstore.as_retriever(search_kwargs={"k": 2})
faiss_results = faiss_retriever.invoke(question)

print(f"####################### vector 검색 결과 #################")
for doc in faiss_results:
    print(doc.page_content)

bm25_retriever = BM25Retriever.from_texts(
    doc_list_1, metadatas=[{"source": 1}] * len(doc_list_1)
)
bm25_retriever.k = 2

bm25_result = bm25_retriever.invoke(question)
print(f"####################### bm25 검색 결과 #################")
for doc in bm25_result:
    print(doc.page_content)

ensemble_retriever = EnsembleRetriever(
    retrievers=[bm25_retriever, faiss_retriever], weights=[0.5, 0.5]
)

docs = ensemble_retriever.invoke(question)

print(f"####################### ensemble 검색 결과 #################")
for doc in docs:
    print(doc.page_content)

실행결과

####################### vector 검색 결과 #################
Galaxy S20의 Display는 6.2-inch Dynamic AMOLED이다.
Galaxy S21의 Ram은 8GB이다
####################### bm25 검색 결과 #################
Galaxy S21의 Ram은 8GB이다
Galaxy S20의 저장공간은 128G이다
####################### ensemble 검색 결과 #################
Galaxy S21의 Ram은 8GB이다
Galaxy S20의 Display는 6.2-inch Dynamic AMOLED이다.
Galaxy S20의 저장공간은 128G이다

위의 코드는 3가지를 모두 실행해본 결과이다.

  1. Vector Retriever로 검색
  2. BM25로 검색
  3. Ensemble Retriever로 검색

우선 "갤럭시 S21의 특징"으로 검색을 했고 1번의 Vector 검색 결과를 보면 S20의 특징이 우선 조회되는 것을 알 수 있다. S20과 S21보다는 특징이라는 키워드로 모든 제품의 특징이 우선 조회되는 듯 하다. 그래서 S21로 검색이 우선 되게 2번에서와 같이 BM25를 사용하게 되면 S21이 우선 조회가 되는 것을 알 수 있다.

3번에서는 Vector와 BM25의 조회 결과를 조합하는 EnsembleRetriever를 사용하는 예제이다. EnsembleRetriever의 검색결과에서는 S21이 우선 검색되는 것을 확인할 수 있다.

참고

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유