ANN
Approximate Nearest Neighbor
근사 최근접 이웃 검색. 정확도를 조금 희생해 속도 향상.
Approximate Nearest Neighbor
근사 최근접 이웃 검색. 정확도를 조금 희생해 속도 향상.
ANN(Approximate Nearest Neighbor, 근사 최근접 이웃)은 고차원 벡터 공간에서 주어진 쿼리와 가장 유사한 벡터를 빠르게 찾는 알고리즘입니다. 정확한 최근접 이웃(Exact NN)은 모든 데이터를 비교해야 하지만, ANN은 정확도를 약간 희생하는 대신 검색 속도를 수십~수백 배 향상시킵니다.
ANN의 필요성은 LLM과 RAG 시스템의 부상으로 급증했습니다. OpenAI의 text-embedding-ada-002는 1536차원, Cohere의 embed-multilingual-v3.0은 1024차원 벡터를 생성합니다. 수억 개의 문서 임베딩에서 밀리초 내에 유사한 문서를 찾으려면 브루트포스(O(n)) 대신 ANN(O(log n))이 필수입니다.
주요 ANN 알고리즘으로는 HNSW(Hierarchical Navigable Small World), IVF(Inverted File Index), PQ(Product Quantization) 등이 있습니다. HNSW는 그래프 기반으로 높은 정확도와 빠른 검색 속도를 제공하며, IVF-PQ는 메모리 효율적입니다. Faiss, Annoy, ScaNN, Milvus 등 다양한 라이브러리가 이 알고리즘들을 구현합니다.
실무에서 ANN은 벡터 데이터베이스(Pinecone, Weaviate, Qdrant)의 핵심 엔진입니다. 검색 정확도는 recall@k로 측정하고, 지연 시간(latency)과 처리량(throughput)을 함께 최적화해야 합니다. 보통 recall@10이 95% 이상이면 프로덕션 품질로 간주하며, p99 latency 10ms 이하를 목표로 합니다.
"""
ANN(Approximate Nearest Neighbor) 검색 예제
FAISS 라이브러리를 사용한 HNSW 인덱스 구축 및 검색
"""
import numpy as np
import faiss
import time
# 랜덤 시드 고정
np.random.seed(42)
# 파라미터 설정
dimension = 768 # 임베딩 차원 (BERT 기준)
num_vectors = 100000 # 인덱싱할 벡터 수
num_queries = 100 # 쿼리 수
top_k = 10 # 반환할 이웃 수
# 샘플 데이터 생성 (실제로는 임베딩 모델 출력 사용)
print("벡터 데이터 생성 중...")
database_vectors = np.random.random((num_vectors, dimension)).astype('float32')
query_vectors = np.random.random((num_queries, dimension)).astype('float32')
# L2 정규화 (코사인 유사도 사용시 필수)
faiss.normalize_L2(database_vectors)
faiss.normalize_L2(query_vectors)
# 1. Exact Search (브루트포스) - 비교 기준
print("\n=== Exact Search (Flat Index) ===")
index_flat = faiss.IndexFlatIP(dimension) # Inner Product = Cosine Similarity (정규화 후)
index_flat.add(database_vectors)
start = time.time()
distances_exact, indices_exact = index_flat.search(query_vectors, top_k)
exact_time = time.time() - start
print(f"검색 시간: {exact_time*1000:.2f}ms ({num_queries}개 쿼리)")
# 2. ANN Search (HNSW) - 근사 검색
print("\n=== ANN Search (HNSW Index) ===")
# HNSW 파라미터: M=32 (그래프 연결 수), efConstruction=40 (인덱스 빌드 품질)
M = 32
index_hnsw = faiss.IndexHNSWFlat(dimension, M)
index_hnsw.hnsw.efConstruction = 40 # 인덱스 빌드 품질
# 인덱스 구축
build_start = time.time()
index_hnsw.add(database_vectors)
build_time = time.time() - build_start
print(f"인덱스 빌드 시간: {build_time:.2f}s")
# efSearch 조절로 정확도-속도 트레이드오프 조절
index_hnsw.hnsw.efSearch = 64 # 검색 품질 (높을수록 정확, 느림)
start = time.time()
distances_ann, indices_ann = index_hnsw.search(query_vectors, top_k)
ann_time = time.time() - start
print(f"검색 시간: {ann_time*1000:.2f}ms ({num_queries}개 쿼리)")
# 3. Recall 계산 (정확도 측정)
def calculate_recall(exact_indices, ann_indices, k):
"""Recall@k: ANN 결과에 Exact 결과가 얼마나 포함되었는지"""
recall_sum = 0
for i in range(len(exact_indices)):
exact_set = set(exact_indices[i][:k])
ann_set = set(ann_indices[i][:k])
recall_sum += len(exact_set & ann_set) / k
return recall_sum / len(exact_indices)
recall = calculate_recall(indices_exact, indices_ann, top_k)
speedup = exact_time / ann_time
print(f"\n=== 성능 비교 ===")
print(f"Recall@{top_k}: {recall:.2%}")
print(f"속도 향상: {speedup:.1f}x")
print(f"메모리 사용: {index_hnsw.ntotal * dimension * 4 / 1024 / 1024:.1f}MB")
"1000만 문서를 인덱싱해야 하는데, Exact KNN은 쿼리당 500ms가 걸립니다. HNSW 기반 ANN으로 전환하면 recall 98%를 유지하면서 5ms 이하로 줄일 수 있습니다. Pinecone이나 Weaviate 같은 매니지드 벡터 DB를 쓰면 운영 부담도 줄어듭니다."
"ANN 알고리즘 선택은 워크로드에 따라 다릅니다. 검색이 주요 작업이고 데이터가 자주 변하지 않으면 HNSW가 좋고, 실시간 업데이트가 많으면 IVF-PQ가 낫습니다. ann-benchmarks.com에서 실제 데이터셋별 성능을 비교해볼 수 있습니다."
"현재 recall이 85%인데 검색 품질 이슈가 있습니다. HNSW의 efSearch를 64에서 128로 올리면 recall이 95%로 향상되지만 latency가 2배 증가합니다. 하이브리드 검색으로 ANN 결과를 가져온 후 리랭킹하는 방식도 고려해봅시다."
efSearch나 nprobe 같은 파라미터를 높이면 정확도가 올라가지만 속도가 느려집니다. 워크로드에 맞는 최적점을 찾아야 하며, 프로덕션 전 벤치마크가 필수입니다.
HNSW 인덱스는 빌드 시간이 오래 걸리고, 벡터 삭제가 어렵습니다. 실시간 업데이트가 많은 경우 IVF 계열이나 증분 인덱싱을 지원하는 솔루션을 고려하세요.
HNSW는 벡터 자체 외에 그래프 구조를 메모리에 유지합니다. 1억 개의 768차원 벡터는 약 300GB 메모리가 필요할 수 있습니다. PQ(Product Quantization)로 압축하면 10배 이상 줄일 수 있습니다.