
RAG 시스템을 주제로 프로젝트를 진행 중에 있는데 vector store로 FAISS를 도입하려고 한다. chroma를 깨작 만져본 적이 있는데 이번 기회에 FAISS로 vector store를 구축해보려고 한다.
FAISS에 대해 간략하게 소개하자면 대규모 벡터 검색을 효율적으로 처리하기 위한 Facebook AI Research의 오픈소스 라이브러리이다. FAISS는 특히 대규모, 고차원의 데이터를 효율적으로 처리하는데 있어 강점이 있다.
지금 진행하는 프로젝트는 row 하나당 sequence가 길수록 성능이 좋아질 것이라고 판단했고, 이미지 데이터를 활용하는 방안도 생각중에 있기 때문에 chroma보다는 멀티모달에 강한 FAISS를 선택했다.
# colab
!pip install langchain langchain_community faiss-cpu
추가적으로 임베딩 모델을 허깅페이스에서 다운로드 받기 위해서 sentence-transformers 패키지도 install 한다.
# colab
!pip install -U sentence-transformers
임베딩 모델인 jhgan/ko-sroberta-multitask는 128토큰이 최대 입력 한도이다. 따라서 토큰 수에 따른 데이터 청킹이 필요하다.
from langchain_community.document_loaders.csv_loader import CSVLoader
loader_file = CSVLoader(
file_path='YOUR_CSV_FILE_PATH',
encoding='' # cp949 or utf-8
)
file = loader_file.load()
TokenTextSplitter 는 토큰 수를 기반으로 청크를 생성할 때 유용하다. 토큰 수의 기반 모델은 OpenAI의 tiktoken을 사용한다.
!pip install tiktoken
tiktoken을 먼저 설치해준다.
from langchain_text_splitters import TokenTextSplitter
text_splitter = TokenTextSplitter(
chunk_size=100, # 청크 크기를 100으로 설정
chunk_overlap=0, # 청크 간 중복을 0으로 설정
add_start_index=True # 청크의 시작 인덱스 표기
)
# csv파일을 청크로 분할
splits = text_splitter.split_documents(file) # document 형식으로 불러온 file을 청크로 분할
print("분할된 csv의 첫번재 청크 : ",splits[0])
print("분할된 청크의 개수 : ", len(splits))
임베딩 모델은 주최 측에서 몇 가지 모델 안에서 선택하도록 제한을 두었는데 그 중에서 jhgan/ko-sroberta-multitask 모델로 진행했다.
Similarity search와 Maximum marginal relevance search 사이에서 고민했는데 PoC 단계이므로 Similarity search로 실험했다.
from langchain.embeddings import HuggingFaceEmbeddings
modelPath = "jhgan/ko-sroberta-multitask"
model_kwargs = {'device':'cpu'}
encode_kwargs = {'normalize_embeddings': True} # 벡터간의 cosine similarity 계산에서 크기는 제외된 방향성만 고려
embeddings_model = HuggingFaceEmbeddings(
model_name=modelPath,
model_kwargs=model_kwargs,
encode_kwargs=encode_kwargs
)
from langchain_community.vectorstores import FAISS
from langchain_community.vectorstores.utils import DistanceStrategy
vectorstore = FAISS.from_documents(documents = splits,
embedding = embeddings_model,
distance_strategy = DistanceStrategy.COSINE
)
vectorstore
similarity_search 함수를 활용해서 테스트 쿼리에 벡터 서치가 잘 적용되는지 확인했다.
similarity_search 메서드는 주어진 쿼리와 가장 유사한 문서들을 검색한다.
주요 매개변수는 다음과 같다.
query(str): 유사한 문서를 찾기 위한 검색 쿼리 텍스트
k(int): 반환할 문서 수. 기본값은 4
filter(Optional[Union[Callable, Dict[str, Any]]]): 메타데이터 필터링 함수 또는 딕셔너리. 기본값은 None
fetch_k(int): 필터링 전에 가져올 문서 수. 기본값은 20
query = '20대가 많이 찾는 맛집은 어디인가요?'
docs = vectorstore.similarity_search(query)
print(len(docs))
print(docs[0].page_content)
동작 방식
- 쿼리 인코딩
- 주어진 쿼리 문자열을 벡터로 변환한다. vectorstore를 생성할 때 사용된 임베딩 모델을 사용하여 수행된다.
- 유사도 검색
- 쿼리 벡터와 벡터 스토어에 저장된 벡터들 간의 가장 유사한 벡터들을 찾아낸다.
- 이 때, 유사도 계산 방법은 vectorstore 생성 시 지정된 distance_strategy에 따라 결정된다.
- 결과 반환
- 검색 결과로 얻어진 문서들을 유사도 순으로 정렬하여 반환한다.
최종적으로 생성된 vector db를 로컬에 저장해 놓는다. 지정한 폴더 위치에 index.faiss, index.pkl 두 가지 파일이 생성되면 성공이다.
vectorstore.save_local('YOUR_LOCAL_PATH')
FAISS가 다른 vector db와는 다르게 인덱스의 개념이 있다고해서 조금 어렵게 다가왔는데 막상 구현은 크게 차이가 없는 것 같다.
FAISS로 vector db를 구축하는데 일단 성공은 했지만 성능 개선이 필요하다.
reference