강의소개
앞선 4, 5강에서 sparse / dense embedding을 활용해 문서 검색을 하는 방법에 대해 실습해보았습니다. 실습에서는 수만개 단위의 데이터였지만, 실제 문서 검색이 이루어지길 원하는 실제 상황에서는 그 문서의 수가 기하급수적으로 늘어나게 됩니다. 위키피디아 문서에서 검색하는 상황을 가정하더라도 5백만개 이상의 문서에서 검색을 수행해야 하고, 실제로는 수천만 ~ 억개의 문서가 존재할 수 있습니다. 이런 상황에서는 앞선 강의에서 배운 모든 문서들에 대해 검색을 수행하는 방법이 굉장히 오랜 시간과 많은 자원을 요구하게 됩니다.
이번 6강에서는 이렇게 scale이 커진 상황에서 어떻게 효율적으로 검색을 수행할 수 있을지에 대해 배워볼 예정입니다. 먼저 similarity search의 개념에 대해 간략하게 복습해본 후, 보다 효율적인 approximate search가 무엇인지 알아보겠습니다. 그리고 approximate search를 편리하게 사용할 수 있도록 도와주는 FAISS를 활용하는 방법에 대해 알아보고 실습해보겠습니다.
Further Reading
문제는, passage 개수가 많아질수록 연산량이 증가하게 된다. 따라서 어떻게하면 real time으로 원하는 passage를 찾을 수 있을까?**
# brute-force로 모든 벡터와 쿼리를 비교하는 가장 단순한 인덱스 만들기
#pruning과 SQ을 활용하지 않기에 학습이 불필요함
# 준비
d = 64 # 벡터의 차원
nb = 100000 # DB 크기
nq = 10000 # 쿼리 개수
xb = np.random.random((nb, d)).astype('float32') #DB 예시
xq = np.random.random((nq, d)).astype('float32') #쿼리 예시
# 인덱스 만들기
index = faiss.IndexFlatL2(d) # 인덱스 빌드하기, dimension 정의
index.add(xb) # 인덱스에 벡터 추가하기
# 검색하기
k = 4 # top-k : 4개
D, I = index.search(xq, k) # 검색하기
# D : 쿼리와의 거리
# I : 검색된 passage 벡터의 인덱스
# IVF 인덱스 만들기
# 클러스터링을 통해서 가까운 클러스너 내 벡터들만 비교
# 빠른 검색 가능
# 클러스터 내에서는 여전히 전체 벡터와 거리 비교(flat)
# IVF 인덱스
nlist = 100 # 클러스터 개수
quantizer = faiss.IndexFlatL2(d) # 클러스터에서 거리를 재는 방법을 지정(여기선 L2)
index = faiss.IndexIVFFlat(quantizer, d, nlist) # Inverted FIle 만들기
# quantizer를 이용하여 n개의 클러스터를 만들기 위한 사전 준비 작업
index.train(xb) # 클러스터 학습하기
#xb가 매우 큰 경우 일부만 샘플링하여 train 시킴
#단, add 시에는 모든 xb 사용
index.add(xb) # 클러스터에 벡터 추가하기
D,I = index.search(xq, k) # 검색하기
# 검색하기
k = 4 # top-k : 4개
D, I = index.search(xq, k) # 검색하기
# 벡터 압축 기법(PQ) 활용하기
# SQ보다 더 압축이 많이 되지만, 일단은 SQ를 해보고 필요할 경우 PQ를 이용해보아요..
# 전체 벡터를 저장하지 않고 압축된 벡터만을 저장
# 메모리 사용량을 줄일 수 있음
# IVF-PQ 인덱스
nlist = 100 # 클러스터 개수
m = 8 # subquantizer 개수
quantizer = faiss.IndexFlatL2(d) # 클러스터에서 거리를 재는 방법을 지정(여기선 L2)
index = faiss.IndexIVFPQ(quantizer, d, nlist, m, 8) # 각각의 sub-vector가 8bits로 인코딩 됨
# quantizer를 이용하여 n개의 클러스터를 만들기 위한 사전 준비 작업
index.train(xb)
index.add(xb)
D,I = index.search(xq, k) # 검색하기
# 검색하기
k = 4 # top-k : 4개
D, I = index.search(xq, k) # 검색하기
# GPU의 빠른 연산속도를 활용할 수 있음
# gpu에 모든 vector를 올리는 것이기에, exhaustive search 등 거리 계산을 위해 행렬곱 등에서 유리
# 다만, GPU 메모리 제한이나 메모리 random access 시간이 느린 것이 단점
# 또한 gpu를 사용하므로써 라이브러리를 제한적으로 사용해야 함
# IndexFlatL2만 사용 가능하며, 다른 search 알고리즘을 사용하기 어려움
# 단일 GPU 인덱스
res = faiss.StandardGpuResources() # 단일 GPU 사용하기
index_flat = faiss.IndexFlatL2(d)
#gpu 인덱스로 옮기기
gpu_index_flat = faiss.index_cpu_to_gpu(res, 0, index_flat)
gpu_index_flat.add(xb)
D,I = index.search(xq, k) # 검색하기
# 여러 GPU를 활용하여 연산 속도를 한층 더 높일 수 있음
# 대부분의 경우 Multi GPUs까지는 불필요하므로 일단 싱글 GPU 방법부터 적용
cpu_index = faiss.IndexFlatL2(d)
#gpu 인덱스로 옮기기
gpu_index_flat = faiss.index_cpu_to_all_gpus(cpu_index)
gpu_index_flat.add(xb)
D,I = index.search(xq, k) # 검색하기