Faiss는 Facebook Lab에서 만든 벡터 검색 엔진입니다.
Faiss는 벡터 갬색 엔진이고 유사도 검색을 하거나 추천, 기계학습로 만든 모델을 활용해서 응용 서비스를 만들 때 사용합니다.
별거 아닌거처럼 보이지만 불가능한 것을 가능하게 만들어 주는 매우 유용한 라이브러려입니다.
라이브러리이기 때문에 자체로 서비를 제공하는 것은 아니고 이 라이브러리를 이용해서 Backend, Frontend 서비스를 개발하거나 응용 프로그램에 넣을 수 있습니다.
벡터 검색 엔진
벡터 검색 엔진이 뭔지를 설명해야 하는데요. 보통 그래프 서치라고도합니다. 이것들은 주로 수치를 찾는 것을 말하는데 지도검색 같은데서도 사용하는 것으로 매우 쓸모가 많은 엔진입니ㅏㄷ.
일반적으로 검색 엔진이라고 말하면 흔히 텍스트를 검색하는 것을 생각합니다. 구글의 웹 검색, 네이버 검색, 다음 검색 같은 것은 검색 포털이요. 그게 아니면 Elastic Search나 Lucene갈은 검색 엔진을 생각할 텐데요.
하지만 벡터 검색은 텍스트가 아닌 벡터를 빠른 속도로 찾는 것을 말합니다. 벡터는 수열을 말합니다.
아래와 같이 10개의 숫자가 묶여 있으면 이걸 10차원 벡터라고 합니다. 숫자가 100개 있으면 100차원 벡터, 1000개면 1000차원 벡터입니다.
1 2 3 4 |
[-0.00709967 -0.01956441 0.03790117 -0.00150699 -0.02145613 -0.06128123 0.04965064 -0.05588596 0.08241648 -0.05128423] |
이런 것들이 수억개가 있고 수억개 중에 어떤 벡터와 가장 가까운 벡터를 찾아야 한다면 문제가 어려워집니다.
가장 가까운 것을 주어진 입력 벡터와 수억개의 벡터를 모두 하나씩 연결해서 서로의 거리를 계산한 다음 가장 가까운 것을 찾아야 하기 때문입니다.
가장 가까운 것을 찾는데 수십분이 걸릴 수 있습니다. 이러면 실제 서비스에서는 쓸 수 없습니다.
어떤 사용자가 온라인 서적 판매사이트에 접속했을 때 그 사람에게 책을 추천해줘야 하는데 추천할 책 목록을 검색하는데 10분씩 걸린다면 서비스에 적용하지 못합니다. 다른 서비스도 마찬가지구요.
Faiss는 인덱싱 방식을 다르게 해서 데이터가 많아도 짧게는 밀리초 단위 길게는 수초 이내에 결과를 찾아 줍니다. 즉 온라인 추천 서비스에 빠르게 적용하는 추천 시스템 등을 개발하는데 사용할 수 있습니다.
Python Faiss library
Faiss는 Python wrapper를 공식 지원하고 있습니다. c++로 만들어졌으니까 다른 언어로도 연결해서 사용할 수 있습니다. Go lang이나 Node.js, Kotlin 같은 것을 쓰면 Python 보다는 성능이 더 좋을 것입니다.
깃헙 레파지토리: https://github.com/facebookresearch/faiss
레파지토리에 있는 것을 설치해도 되고 그냥 pip를 이용해서 설치해도 됩니다.
1 2 3 |
pip3 install faiss-cpu |
gpu 버전을 설치하고 싶으면 gpu 버전ㅇ로 명시해서 설치하면 됩니다.
1 2 3 |
pip3 install faiss-gpu |
사용법은 매뉴얼을 봐야 하겠지만 기본 사용법은 쉽습니다.
Faiss로 유클리디안 거리로 벡터 검색하기
아래 코드는 유클리디안 거리(Euclidean Distance)로 찾는 예제입니다.
이런 것은 KNN (K-nearest-neighbor) 와 같은 기계학습 모델에 사용하는 것입니다. KNN은 판별 모델에서 사용할때 매우 강력한 알고리즘이지만 검색할 때 너무 느리고 자원을 많이 사용하는 문제로 인해서 실제로는 거의 사용을 못하는 알고리즘이지만 Faiss를 이용하면 이걸 쓸 수 있습니다.
Faiss 색인을 생성할 때 벡터의 차원을 지정해주고, Index의 유형도 결정을 해줘야 하는 것이 중요합니다. 검색은 입력한 k의 갯수만큼 리턴하게 되어 있고 벡터의 색인 번호와 거리를 리턴하게 되어 있늡니다.
색인 번호는 그냥 입력한 입력한 벡터의 순번입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import faiss import numpy as np import random # Euclidean distance 기반으로 가장 가까운 벡터를 찾는다. # 랜덤으로 10차원 벡터를 10개 생성 vectors = [[random.uniform(0, 1) for _ in range(10)] for _ in range(10)] # 10차원짜리 벡터를 검색하기 위한 Faiss index 생성 index = faiss.IndexFlatL2(10) # Vector를 numpy array로 바꾸기 vectors = np.array(vectors).astype(np.float32) # 아까 만든 10x10 벡터를 Faiss index에 넣기 index.add(vectors) # query vector를 하나 만들기 query_vector = np.array([[random.uniform(0, 1) for x in range(10)]]).astype(np.float32) print("query vector: {}".format(query_vector)) # 가장 가까운 것 10개 찾기 distances, indices = index.search(query_vector, 10) # 결과룰 출력하자 idx = 0 for i in indices: print("v{}: {}, distance={}".format(idx+1, vectors[i], distances[idx])) idx += 1 |
Faiss로 코사인 유사도로 검색하기
유클리디안 거리(Euclidean Distance)로 가장 가까운 벡터를 찾으면 특정 차원의 양적 수치에 따라는 거리가 가깝다고 판별되는 편향의 문제가 있습니다. 이게 문제가 될 때가 있고 그렇지 않을 때가 있는데 이것은 문제의 도메인에 따라 다릅니다. 그러니까 문제가 주어진 환경에 따라 그때그때 다르다는 뜻입니다.
이런 문제를 피하는 방법은 유사도를 계산할 때 거리측정 방법을 유클리디안 거리를 사용하지 않고 코사인 유사도를 사용해서 벡터의 방향이 가까운 것을 찾는 것입니다. 보통 검색엔진들도 이 방법을 기본으로 사용합니다.
Faiss도 이걸 지원하는데 예제는 아래 코드를 보시면 되고 앞서 설명했던 유클리디안 거리 기반의 검색과 다른 점은 index를 생성할 때 타입을 다르게 생성해야 하고 벡터를 노말라이즈 해줘야 한다는 것입니다. 벡터가 이미 노말라이즈되어 있다면 안해도 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import faiss import numpy as np import random # 코사인 유사도 (Cosine Similarity) 를 이용해서 가장 가까운 벡터를 찾으려면 몇가지를 바꿔줘야 한다. # 코사인 유사도 (Cosine Similarity) 를 사용하려면 벡터 내적으로 색인하는 index를 만들면 된다. # 코사인 유사도를 계산하라면 벡터 내적을 필연적으로 계산해야 하기 때문이다. # 랜덤으로 10차원 벡터를 10개 생성 vectors = [[random.uniform(0, 1) for _ in range(10)] for _ in range(100)] # 10차원짜리 벡터를 검색하기 위한 Faiss index를 생성 # 생성할 때 Inner Product을 검색할 수 있는 index를 생성한다. index = faiss.IndexFlatIP(10) # 아래는 위와 동일하다. # index = faiss.index_factory(300, "Flat", faiss.METRIC_INNER_PRODUCT) # Vector를 numpy array로 바꾸기 vectors = np.array(vectors).astype(np.float32) # vectors를 노말라이즈 해준다. faiss.normalize_L2(vectors) # 아까 만든 10x10 벡터를 Faiss index에 넣기 index.add(vectors) # query vector를 하나 만들기 query_vector = np.array([[random.uniform(0, 1) for x in range(10)]]).astype(np.float32) print("query vector: {}".format(query_vector)) # 가장 가까운 것 10개 찾기 distances, indices = index.search(query_vector, 50) # 결과룰 출력하자. idx = 0 for i in indices: print("v{}: {}, distance={}".format(idx+1, vectors[i], distances[idx])) idx += 1 |
노트북 코드
위 코드의 노트북은 깃헙 레파지토리에 올려 두었습니다.
https://github.com/euriion/python-exams/blob/main/faiss/faiss-exam.ipynb
다음 번에는 기회가 되면 Faiss를 이용한 간단하고 빠른 추천 엔진을 만드는 예제를 올려보겠습니다.