Python Faiss를 사용하여 벡터를 저장하고 검색하는 방법은 다음과 같습니다. Faiss는 고차원 벡터의 효율적인 유사성 검색 및 클러스터링을 위한 라이브러리입니다.

### 1\. Faiss 설치

Faiss를 사용하려면 먼저 설치해야 합니다. CPU 버전을 설치하려면 다음 명령어를 사용합니다. (GPU가 있다면 `faiss-gpu`를 설치)

```bash
pip install faiss-cpu numpy
```

### 2\. Faiss 인덱스 생성 및 벡터 저장

Faiss는 다양한 인덱스 타입을 제공합니다. 가장 기본적인 `IndexFlatL2` (유클리디안 거리)와 `IndexFlatIP` (내적)를 사용하여 벡터를 저장하고 검색하는 방법을 보여드리겠습니다.

#### 기본 개념

  * **인덱스 (Index)**: Faiss에서 벡터를 저장하고 검색하는 데 사용되는 핵심 데이터 구조입니다.
  * **벡터 차원 (d)**: 저장할 벡터의 차원 (예: 128, 768 등).
  * **유사도 측정 (Metric)**:
      * **L2 (Euclidean Distance)**: 벡터 간의 기하학적 거리. 값이 작을수록 유사합니다.
      * **IP (Inner Product)**: 내적. 벡터가 정규화(normalize)되어 있다면 코사인 유사도와 동일합니다. 값이 클수록 유사합니다.

#### 예제 코드

```python
import faiss
import numpy as np

# 1. 벡터 데이터 준비 (고객 정보 예시)
# 가상의 고객 벡터 데이터 (각 고객은 128차원 벡터로 표현됨)
# 실제로는 RDBMS에서 추출한 피처를 기반으로 임베딩된 벡터가 될 것입니다.
dimension = 128  # 벡터의 차원
num_customers = 10000  # 고객 수

# float32 타입으로 numpy 배열 생성 (Faiss는 float32를 선호)
customer_vectors = np.random.rand(num_customers, dimension).astype('float32')

# 각 고객 벡터에 해당하는 ID (예: 고객 ID)
# Faiss IndexFlatL2/IP는 기본적으로 벡터의 인덱스(0, 1, 2...)를 ID로 사용하지만,
# IndexIDMap 등을 사용하면 외부 ID와 매핑할 수 있습니다.
customer_ids = np.arange(num_customers).astype('int64')

# 2. Faiss 인덱스 생성

# 2.1. IndexFlatL2 (유클리디안 거리 기반)
# 가장 단순한 인덱스로, 모든 벡터에 대해 브루트 포스(Brute-Force) 검색을 수행합니다.
# 정확도가 100% 보장되지만, 데이터가 많아질수록 느려집니다.
index_l2 = faiss.IndexFlatL2(dimension)
print(f"IndexFlatL2가 학습이 필요한가요? {index_l2.is_trained}") # IndexFlat 계열은 학습이 필요 없음

# 2.2. IndexFlatIP (내적 기반)
# 코사인 유사도를 사용하려면 벡터를 정규화(L2-normalize)한 후 IndexFlatIP를 사용합니다.
index_ip = faiss.IndexFlatIP(dimension)
print(f"IndexFlatIP가 학습이 필요한가요? {index_ip.is_trained}")

# 3. 벡터를 인덱스에 추가 (저장)
# Faiss는 add 메서드를 통해 벡터를 인덱스에 추가합니다.
index_l2.add(customer_vectors)
index_ip.add(faiss.normalize_L2(customer_vectors)) # IP 인덱스에는 정규화된 벡터 추가

print(f"IndexFlatL2에 저장된 총 벡터 수: {index_l2.ntotal}")
print(f"IndexFlatIP에 저장된 총 벡터 수: {index_ip.ntotal}")

# 4. 인덱스 검색 (유사한 고객 찾기)
k = 5 # 가장 유사한 상위 k개 벡터 찾기

# 쿼리 벡터 (새로운 고객의 벡터 또는 특정 상품의 벡터 등)
query_vector = np.random.rand(1, dimension).astype('float32')

# L2 인덱스에서 검색
distances_l2, indices_l2 = index_l2.search(query_vector, k)
print("\n--- L2 거리 기반 검색 결과 ---")
print("거리 (작을수록 유사):", distances_l2)
print("인덱스 (고객 ID와 매핑):", indices_l2)

# IP 인덱스에서 검색 (코사인 유사도)
# 쿼리 벡터도 반드시 정규화해야 합니다.
query_vector_normalized = faiss.normalize_L2(query_vector)
distances_ip, indices_ip = index_ip.search(query_vector_normalized, k)
print("\n--- IP (코사인 유사도) 기반 검색 결과 ---")
print("유사도 (클수록 유사):", distances_ip)
print("인덱스 (고객 ID와 매핑):", indices_ip)


# 5. 인덱스 저장 및 로드
# 인덱스를 파일로 저장하여 나중에 다시 사용할 수 있습니다.
index_filename_l2 = "customer_vectors_l2.faiss"
index_filename_ip = "customer_vectors_ip.faiss"

faiss.write_index(index_l2, index_filename_l2)
faiss.write_index(index_ip, index_filename_ip)
print(f"\n인덱스가 '{index_filename_l2}'와 '{index_filename_ip}' 파일로 저장되었습니다.")

# 저장된 인덱스 로드
loaded_index_l2 = faiss.read_index(index_filename_l2)
loaded_index_ip = faiss.read_index(index_filename_ip)

print(f"로드된 IndexFlatL2에 저장된 총 벡터 수: {loaded_index_l2.ntotal}")
print(f"로드된 IndexFlatIP에 저장된 총 벡터 수: {loaded_index_ip.ntotal}")

# 로드된 인덱스로 검색 수행
distances_loaded_l2, indices_loaded_l2 = loaded_index_l2.search(query_vector, k)
print("\n--- 로드된 L2 인덱스 검색 결과 ---")
print("거리:", distances_loaded_l2)
print("인덱스:", indices_loaded_l2)

distances_loaded_ip, indices_loaded_ip = loaded_index_ip.search(query_vector_normalized, k)
print("\n--- 로드된 IP 인덱스 검색 결과 ---")
print("유사도:", distances_loaded_ip)
print("인덱스:", indices_loaded_ip)
```

### 다양한 Faiss 인덱스 타입

`IndexFlatL2`나 `IndexFlatIP`는 데이터 양이 적을 때(수십만 개 이하) 적합합니다. 데이터 양이 많아지면 Brute-Force 검색은 너무 느려지므로, **Approximate Nearest Neighbor (ANN)** 알고리즘 기반의 인덱스를 사용해야 합니다. Faiss는 이를 위해 다양한 인덱스 타입을 제공합니다:

  * **`IndexIVFFlat`**: Inverted File Index. 대규모 데이터셋에 적합하며, `nlist`(클러스터 수)와 `nprobe`(검색할 클러스터 수) 파라미터를 통해 속도-정확도 트레이드오프를 조절합니다. `train()` 메서드를 통해 벡터 분포를 학습하는 과정이 필요합니다.
  * **`IndexPQ` (Product Quantization)**: 벡터를 작은 서브 벡터로 분할하여 각 서브 벡터를 압축합니다. 메모리 사용량을 크게 줄이고 검색 속도를 향상시킵니다.
  * **`IndexIVFPQ`**: IVF와 PQ를 결합한 형태로, 매우 큰 데이터셋에서 효율적인 검색을 가능하게 합니다.
  * **`IndexHNSWFlat` (Hierarchical Navigable Small World)**: 그래프 기반의 인덱스로, 매우 빠른 검색 속도와 높은 정확도를 제공합니다. 메모리 사용량이 다소 높을 수 있습니다.

#### ANN 인덱스 사용 예시 (IndexIVFFlat)

```python
import faiss
import numpy as np

dimension = 128
num_customers = 100000 # 더 많은 고객
customer_vectors = np.random.rand(num_customers, dimension).astype('float32')

# 1. Quantizer 정의
# IndexIVFFlat은 벡터를 클러스터링하여 저장하므로, 먼저 벡터를 어떤 기준으로 나눌지 결정하는 quantizer가 필요합니다.
# 일반적으로 IndexFlatL2를 quantizer로 사용합니다.
quantizer = faiss.IndexFlatL2(dimension)

# 2. IndexIVFFlat 생성
# nlist: 클러스터(inverted list)의 개수. 데이터 양에 따라 적절히 설정 (보통 sqrt(N) 또는 4*sqrt(N)).
nlist = 100
index_ivf = faiss.IndexIVFFlat(quantizer, dimension, nlist, faiss.METRIC_L2) # L2 거리 기준

# 3. 학습 (Training)
# IVFFlat 인덱스는 벡터 분포를 학습하는 과정이 필요합니다.
# 이 학습 데이터는 실제 데이터셋의 대표성을 띄어야 합니다.
# 학습은 한 번만 수행하면 됩니다.
print(f"IndexIVFFlat가 학습이 필요한가요? {index_ivf.is_trained}")
if not index_ivf.is_trained:
    print("인덱스 학습 중...")
    index_ivf.train(customer_vectors) # 학습 데이터는 전체 데이터의 일부일 수도 있습니다.
    print("인덱스 학습 완료.")

# 4. 벡터 추가 (저장)
index_ivf.add(customer_vectors)
print(f"IndexIVFFlat에 저장된 총 벡터 수: {index_ivf.ntotal}")

# 5. 검색
k = 5
query_vector = np.random.rand(1, dimension).astype('float32')

# 검색 시 nprobe 파라미터를 설정하여 검색할 클러스터 수를 조절합니다.
# nprobe 값이 클수록 정확도가 높아지지만 속도는 느려집니다.
index_ivf.nprobe = 10 # 기본값 1. nlist의 10~20% 정도로 설정하기도 합니다.

distances_ivf, indices_ivf = index_ivf.search(query_vector, k)
print("\n--- IndexIVFFlat 검색 결과 ---")
print("거리:", distances_ivf)
print("인덱스:", indices_ivf)

# 6. 저장 및 로드
faiss.write_index(index_ivf, "customer_vectors_ivfflat.faiss")
loaded_index_ivf = faiss.read_index("customer_vectors_ivfflat.faiss")
print(f"로드된 IndexIVFFlat에 저장된 총 벡터 수: {loaded_index_ivf.ntotal}")
```

### RDBMS 데이터와 Faiss 연동 흐름

1.  **RDBMS에서 고객 데이터 추출**: 고객 테이블에서 필요한 피처(성별, 연령, 방문 기록 등)를 조회합니다.
2.  **피처 엔지니어링 및 임베딩**: 추출된 데이터를 기반으로 각 고객을 대표하는 벡터를 생성합니다. 이 과정에서 `scikit-learn`의 전처리 도구나 딥러닝 임베딩 모델(예: Keras/PyTorch)을 활용할 수 있습니다.
3.  **Faiss 인덱스 구축**: 생성된 고객 벡터들을 Faiss 인덱스에 `add()` 메서드를 사용하여 추가합니다.
4.  **Faiss 인덱스 저장**: `faiss.write_index()`를 사용하여 인덱스를 디스크에 저장합니다.
5.  **추천 시스템 활용**:
      * **고객 유사도 기반 추천**: 특정 고객의 벡터를 쿼리하여 Faiss 인덱스에서 가장 유사한 고객들을 찾습니다. 찾은 고객들의 구매 이력이나 선호 상품을 바탕으로 현재 고객에게 상품을 추천합니다.
      * **상품 유사도 기반 추천**: 상품 또한 벡터로 표현될 수 있다면 (예: 상품의 속성, 이미지, 텍스트 설명), 고객이 선호하는 상품 벡터와 유사한 다른 상품들을 Faiss를 통해 찾아 추천할 수 있습니다.
      * **실시간 추천**: 고객의 실시간 행동(방문한 페이지 세그먼트)을 즉시 벡터화하고, 이 벡터를 쿼리하여 Faiss에서 유사한 상품이나 콘텐츠를 실시간으로 추천할 수 있습니다.

Faiss는 오직 벡터 유사도 검색 기능만을 제공하므로, 고객 ID와 벡터의 매핑, 그리고 실제 고객 정보 조회는 여전히 RDBMS나 NoSQL DB를 함께 사용하여 관리해야 합니다. 예를 들어, Faiss 검색 결과로 나오는 인덱스(ID)를 사용하여 RDBMS에서 실제 고객 정보나 상품 정보를 조회하는 방식입니다.

반응형

'Python, PySpark' 카테고리의 다른 글

RNN in python with Keras, PyTorch  (0) 2025.01.20
torch.nn.Embedding()  (0) 2025.01.04
NewsClassifier  (0) 2025.01.04
Python Scheduler  (0) 2024.12.10
networkx - sql과 함께  (1) 2024.10.24

+ Recent posts