본문 바로가기

ML/LLM

2-1. vector embedding 구현하기 (with faiss)

 

2 vector embedding 구현하기 (with elastic search)에서 이어집니다.[ https://uiandwe.tistory.com/1398

 

 

 

elastic search에서 지원하는  Dense Vector칼럼의 차원 수는 최대 2048입니다. 즉, LLM에서 임베딩된 길이가 2048 이상인 경우엔 해당 모델은 elastic search에서 사용할수 없습니다.

저의 경우 4096 차원을 가진 모델로 테스트 하려 했지만 아래와 같은 에러 메시지와 함께 실행되지 않았습니다.

BadRequestError(400, 'mapper_parsing_exception', 'The number of dimensions for field [question_embedding] should be in the range [1, 2048] but was [4096]')

 

 

그래서 elastic search 이외에 vector 검색을 지원하는 vector DB중 사용한 faiss에 대한 소개와 예제 글입니다.

 

Faiss-CPU는 벡터 검색 및 유사성 탐색을 위한 빠르고 효율적인 라이브러리로, 벡터 임베딩 작업을 간편하게 구현할 수 있습니다. 이 글에서는 Faiss-CPU를 활용하여 벡터 임베딩을 구현하는 방법을 알아보겠습니다.

 

 

 

Faiss-CPU 소개

Faiss-CPU는 Facebook AI Research(Faiss)에서 개발된 라이브러리로, 대규모 벡터 세트에 대한 빠른 유사성 검색을 위한 도구입니다.
CPU 버전인 Faiss-CPU는 벡터 임베딩과 관련된 다양한 작업에 사용됩니다.

https://github.com/facebookresearch/faiss

 

GitHub - facebookresearch/faiss: A library for efficient similarity search and clustering of dense vectors.

A library for efficient similarity search and clustering of dense vectors. - GitHub - facebookresearch/faiss: A library for efficient similarity search and clustering of dense vectors.

github.com

 

 

 

 

1. 설치

!pip install faiss-cpu

 

 

 

 

2. insert 및 검색 구현

 

이번에는 'beomi/llama-2-ko-7b' 모델을 사용해서 embedding을 구현합니다.

llama2를 기본 베이스로 사용하여, 벡터는 4096의 차원을 가집니다.

import torch
from transformers import AutoModel, AutoTokenizer
from sentence_transformers import SentenceTransformer, util
from transformers import AutoConfig, PretrainedConfig, PreTrainedModel
from torch import Tensor
import torch.nn as nn
import torch.nn.functional as F
import torch.fx



tokenizer = AutoTokenizer.from_pretrained('beomi/llama-2-ko-7b')
model = AutoModel.from_pretrained('beomi/llama-2-ko-7b')
model = model.to("cpu")



def text_embedding(text):
    seq_ids = tokenizer(text, return_tensors='pt')["input_ids"]
    embedding = model(seq_ids)["last_hidden_state"].mean(axis=[0,1]).detach().numpy()

    return embedding

 

 

테스트를 위해 주어진 문장들에 대한 벡터 임베딩 간 코사인 유사도를 계산하여 유사성을 평가해봤습니다.

scipy 라이브러리의 cosine 함수를 사용하여 벡터 간 코사인 유사도를 계산합니다.
계산된 코사인 유사도는 두 벡터 간의 유사성을 나타내며, 값은 0과 1 사이로, 1에 가까울수록 유사도가 높다는 것을 의미합니다.

문장의 유사성이 잘 표현되는것으로 보입니다.

a = text_embedding("스마트스토어 가맹 문의할 수 있는곳이 어디에요?")
b = text_embedding("스마트스토어 가맹 문의")
c = text_embedding("스마트스토어 ")
d = text_embedding("스마트스토어  할인 하나요?")


from scipy.spatial.distance import cosine

print(1-cosine(a, b))
print(1-cosine(a, c))
print(1-cosine(b, c))
print(1-cosine(a, d))


# 0.8679439425468445
# 0.7463093400001526
# 0.9065462946891785
# 0.6853222846984863

 

 

벡터로 사용할 문장을 로드 합니다.

중요한 부분은 마지막 부분으로 faiss에 넣을수 있는 float type은 무조건 float32 형태를 가져야 합니다. (아니면 에러 발생)

from tqdm import tqdm


# csv 로드
df = pd.read_csv("카테고리구조작업중.csv")

# vector 변환 후 dataframe으로 만들어야 함 
text = df['vector_답변']

vectors = []
for row in tqdm(text):
    vectors.append(text_embedding(row))


temp_vectors = np.array(vectors, dtype=np.float32) # 무조건 float32 타입이여야함 아니면 에러 발생

 

 

Faiss를 사용하여 임베딩된 벡터를 인덱싱을 하는 부분입니다.

faiss.IndexFlatIP은 Inner Product(내적)을 사용하는 인덱스로, 유사성 검색을 할 때 내적을 활용

 

faiss.normalize_L2은 벡터를 L2 norm(각 성분의 제곱의 합을 루트로 씌워준 값)으로 정규화(normalization)합니다. 이는 각 벡터를 길이가 1인 단위 벡터로 만들어줍니다. 정규화를 통해 내적 연산 시 벡터의 크기에 영향을 받지 않도록 합니다.

 

index.add은 정규화된 벡터를 Faiss의 인덱스에 추가합니다.

import faiss

vector_dimension = temp_vectors.shape[1]
index = faiss.IndexFlatIP(vector_dimension) # IndexFlatL2 대신에 IndexFlatIP
temp_vectors = np.array(temp_vectors, dtype=np.float32) # 무조건 float32 타입이여야함

faiss.normalize_L2(temp_vectors)
index.add(temp_vectors)

 

 

search_text를 임베딩하여 검색 벡터를 생성 후 해당 벡터값으로 faiss에 입력된 모든 벡터와 비교합니다.
결과를 유사도에 따라 내림차순으로 정렬하여 반환합니다.

# search_text = '주차비 얼마에요?'
search_text = '스마트스토어 어떻게 해요?'
search_vector = text_embedding(search_text)
_vector = np.array([search_vector], dtype=np.float32) # 무조건 float32 타입이여야함
faiss.normalize_L2(_vector)


k = index.ntotal
distances, ann = index.search(_vector, k=k)
results = pd.DataFrame({'distances': distances[0], 'ann': ann[0]})

merge = pd.merge(results, df, left_on='ann', right_index=True)
merge.sort_values(by=['distances'], axis=0, ascending=False)

 

 

 

결론

만일 적은양의 텍스트를 embedding을 통해서 서비스를 해야 한다면 Faiss-CPU만으로도 충분한 성능을 발휘 할 수 있습니다. (데이터양이 많다면, 다른 vectorDB를 고려해야 합니다.) faiss와 같은 벡터 디비를 통해 쉽게 벡터 임베딩을 구현할수 있습니다. 이를 통해 대규모 벡터 데이터에 대한 빠르고 효율적인 검색이 가능하며, 자신만의 서비스를 빠르게 만들수 있을겁니다.