PART 06의 find는 수천 명까지는 잘 동작하지만, 수만 명을 넘으면 모든 임베딩을 일일이 비교하는 방식이 느려진다고 했습니다. 이 장에서는 그 한계를 넘는 도구, faiss를 다룹니다. faiss는 수백만 개의 벡터 중에서 가장 가까운 것을 눈 깜짝할 새에 찾아 주는 검색 엔진입니다.
왜 전수 비교가 느려지는가
PART 04에서 1:N 식별은 등록된 N명과 모두 비교한다고 했습니다. N이 1만이면 매 검색마다 1만 번, 100만이면 100만 번의 거리 계산입니다. 이렇게 전부 하나씩 비교하는 방식을 전수 탐색(brute force)이라 합니다. 정확하지만 N이 커질수록 느려지는 것이 당연합니다.
faiss는 이 문제를 색인(index)으로 해결합니다. 미리 벡터들을 똑똑하게 정리해 두어, 질의가 들어오면 후보를 빠르게 좁혀 가장 가까운 것을 찾습니다. 도서관에서 모든 책을 한 권씩 확인하는 대신 분류 색인으로 바로 찾아가는 것과 같습니다.
코사인 검색을 위한 준비
InsightFace의 normed_embedding은 길이가 1로 정규화되어 있어, 두 벡터의 내적이 곧 코사인 유사도가 됩니다. faiss에서 내적 기반 검색은 IndexFlatIP(Inner Product)로 합니다.
# 파일: faiss_search.py"""InsightFace 임베딩을 faiss로 검색한다(코사인)."""import numpy as npimport faiss# gallery_embeddings: 등록된 얼굴들의 정규화 임베딩 (N, 512) float32gallery = np.array(gallery_embeddings, dtype="float32")faiss.normalize_L2(gallery) # 코사인을 위해 정규화index = faiss.IndexFlatIP(512) # 내적(코사인) 인덱스, 512차원index.add(gallery) # 갤러리 전체 등록# 질의 임베딩도 같은 방식으로 정규화query = np.array([query_embedding], dtype="float32")faiss.normalize_L2(query)D, I = index.search(query, k=5) # 가장 가까운 5개print("유사도:", D[0]) # 내적값(코사인), 클수록 닮음print("인덱스:", I[0]) # 갤러리에서의 위치
index.search(query, k=5)는 가장 가까운 5명의 유사도(D)와 위치(I)를 돌려줍니다. I로 갤러리의 이름을 찾으면 "이 얼굴은 누구"가 됩니다. IndexFlatIP는 사실 전수 탐색이지만 faiss가 고도로 최적화해, 같은 전수라도 직접 짠 반복문보다 훨씬 빠릅니다.
더 큰 규모 — 근사 검색
수십만 명까지는 IndexFlatIP로 충분하지만, 수백만 명을 실시간으로 검색하려면 전수 탐색조차 부담입니다. 이때는 약간의 정확도를 내주고 속도를 크게 얻는 근사 최근접 검색(ANN)을 씁니다. faiss는 여러 종류를 제공합니다.
| 인덱스 | 방식 | 특징 |
|---|---|---|
IndexFlatIP |
전수(정확) | 정확하지만 N에 비례해 느려짐 |
IndexIVFFlat |
군집 후보군 | 후보를 좁혀 빠름, 약간 근사 |
IndexHNSWFlat |
그래프 탐색 | 매우 빠름, 메모리 더 씀 |
IndexFlatIP는 정확도가 생명인 소~중규모에, IndexIVFFlat·IndexHNSWFlat은 속도가 중요한 대규모에 씁니다. PART 02의 검출기 선택, PART 06의 모델 선택과 똑같이 "정확도와 속도의 맞바꿈"입니다.
정확·간단] B -->|아니오| D[IVF/HNSW
근사·빠름]
인덱스를 저장하고 불러오기
갤러리가 크면 인덱스 구축에도 시간이 걸리므로, 한 번 만든 인덱스는 파일로 저장해 재사용합니다.
# 파일: faiss_io.pyimport faissfaiss.write_index(index, "faces.index") # 저장index = faiss.read_index("faces.index") # 불러오기
이름 목록은 인덱스와 같은 순서로 따로 저장해 두었다가(예: 리스트를 pickle로), 검색 결과의 I로 이름을 찾으면 됩니다. PART 06의 .pkl 캐시와 같은 발상의, 더 확장성 있는 버전입니다.
실무 팁. 사람이 추가·삭제될 때 인덱스 관리가 운영의 핵심입니다.
IndexFlatIP는add로 추가가 간단하지만 삭제는 번거롭습니다. 자주 바뀌는 갤러리라면, 인덱스와 이름 목록을 함께 버전 관리하고 주기적으로 통째로 재구축하는 편이 디버깅하기 쉽습니다. 처음부터 복잡한 근사 인덱스를 쓰기보다,IndexFlatIP로 시작해 규모가 정말 커질 때 옮기는 것을 권합니다.
이 장에서 기억할 것
faiss는 대규모 임베딩에서 가장 가까운 것을 빠르게 찾는 검색 엔진으로, InsightFace의 정규화 임베딩과 IndexFlatIP(내적=코사인)로 간단히 1:N 검색을 만듭니다. 수만까지는 정확한 IndexFlatIP, 수백만은 근사 검색(IVF·HNSW)으로 속도를 얻습니다. 인덱스는 파일로 저장해 재사용하고, 이름 목록을 같은 순서로 관리합니다. 다음 장에서는 이것을 모아 1:N 대용량 얼굴 검색 시스템을 완성합니다.