Bỏ qua

Bài 4: Embeddings & Vector Databases

Tổng quan

Embeddings chuyển đổi text thành vector số để có thể tính toán semantic similarity. Vector databases lưu trữ và tìm kiếm hàng triệu vectors hiệu quả. Đây là nền tảng kỹ thuật của RAG.


1. Embeddings

Khái niệm

Embedding là quá trình ánh xạ text → vector trong không gian nhiều chiều, sao cho các text có nghĩa tương đồng thì vector gần nhau.

"con chó"     → [0.2, 0.8, 0.1, ..., 0.4]   (768 dimensions)
"chó cưng"    → [0.2, 0.7, 0.2, ..., 0.3]   ← Gần với "con chó"
"xe hơi"      → [0.9, 0.1, 0.8, ..., 0.2]   ← Xa với "con chó"

Sentence-transformers

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("sentence-transformers/paraphrase-multilingual-mpnet-base-v2")

sentences = [
    "Hợp đồng lao động phải được ký kết bằng văn bản.",
    "Thỏa thuận làm việc cần có chữ ký giấy tờ.",
    "Giá cổ phiếu VNM hôm nay tăng 2%."
]

embeddings = model.encode(sentences)
print(embeddings.shape)  # (3, 768)

OpenAI Embeddings

from openai import OpenAI

client = OpenAI()

response = client.embeddings.create(
    model="text-embedding-3-small",  # 1536 dims, rẻ hơn
    # model="text-embedding-3-large", # 3072 dims, chất lượng cao hơn
    input="Điều kiện thành lập công ty TNHH là gì?"
)

vector = response.data[0].embedding
print(len(vector))  # 1536

Vietnamese Models

Model Dimensions Đặc điểm
intfloat/multilingual-e5-large 1024 Tốt cho tiếng Việt, open-source
BAAI/bge-m3 1024 State-of-the-art multilingual, hỗ trợ dense + sparse
keepitreal/vietnamese-sbert 768 Fine-tuned riêng cho tiếng Việt
text-embedding-3-small (OpenAI) 1536 Trả phí, tiện lợi
# BGE-M3 - recommended cho tiếng Việt
from FlagEmbedding import BGEM3FlagModel

model = BGEM3FlagModel("BAAI/bge-m3", use_fp16=True)

# Dense embeddings
embeddings = model.encode(
    ["Luật doanh nghiệp Việt Nam 2020"],
    batch_size=12,
    max_length=8192,
)["dense_vecs"]

2. Similarity Metrics

Cosine Similarity

Đo góc giữa 2 vectors - không phụ thuộc vào magnitude (độ dài vector).

cosine_sim(A, B) = (A · B) / (|A| × |B|)

Kết quả: -1 đến 1
  1.0  → Giống hệt nhau
  0.0  → Vuông góc (không liên quan)
 -1.0  → Đối lập
import numpy as np

def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# Với normalized vectors: cosine_sim = dot product
# → Hầu hết embedding models normalize output → chỉ cần dot product

Dùng khi: Embedding không được normalize theo magnitude - muốn so sánh hướng, không phải độ lớn.

Dot Product

dot(A, B) = Σ(Aᵢ × Bᵢ)

Dùng khi: Vectors đã được L2-normalized (hầu hết embedding models modern). Nhanh hơn cosine.

Euclidean Distance

euclidean(A, B) = √Σ(Aᵢ - Bᵢ)²

Dùng khi: Magnitude quan trọng; ít dùng trong NLP hơn cosine/dot.

Tóm tắt

Metric Khi nào dùng Lưu ý
Cosine Default choice cho text Normalize trước nếu chưa normalize
Dot Product Vectors đã L2-normalized Nhanh nhất
Euclidean Khi magnitude có ý nghĩa Ít dùng cho NLP

3. Approximate Nearest Neighbor (ANN)

Tìm kiếm chính xác (brute-force) trong 1 triệu vectors 768 chiều:

1,000,000 × 768 phép tính dot product × latency
→ Quá chậm cho production

ANN hi sinh một chút accuracy để đổi lấy tốc độ tìm kiếm.

HNSW - Hierarchical Navigable Small World

HNSW xây dựng đồ thị phân cấp: lớp trên ít nodes (highway), lớp dưới nhiều nodes (local roads).

Layer 2 (thưa nhất):   A ------- F
                        |         |
Layer 1:           A - C - D - F - H
                   |   |   |   |   |
Layer 0 (dày nhất): A-B-C-D-E-F-G-H-I

Tìm kiếm: Bắt đầu từ entry point trên cùng → greedy search xuống → O(log n)

Đặc điểm HNSW:

  • Rất nhanh (sub-millisecond với hàng triệu vectors)
  • RAM-based → cần load vào memory
  • Phù hợp: Chroma, Qdrant, Weaviate

IVF - Inverted File Index

Chia vectors thành các clusters (Voronoi cells). Khi tìm kiếm, chỉ search trong nprobe clusters gần nhất.

Build time: K-means clustering → centroids
Search:     1. Tìm nprobe clusters gần query nhất
            2. Brute-force search trong các clusters đó

Đặc điểm IVF:

  • Disk-friendly - phù hợp dataset rất lớn (billions of vectors)
  • Cần tune nlist (số clusters) và nprobe (số clusters search)
  • Phù hợp: FAISS, pgvector

4. Vector Databases

So sánh tổng quan

DB Index Deployment Ưu điểm Nhược điểm
Chroma HNSW Local/Docker Đơn giản nhất, tốt cho dev Không scale tốt
FAISS HNSW/IVF In-memory/Library Meta-backed, cực nhanh Không có UI, không persist tự động
Qdrant HNSW Local/Cloud Filtering mạnh, Rust backend Setup phức tạp hơn Chroma
Pinecone Managed Cloud only Serverless, zero ops Phí cao, vendor lock-in
pgvector HNSW/IVF PostgreSQL Tích hợp SQL, ACID Chậm hơn specialized DBs

Chroma - Quickstart

import chromadb
from sentence_transformers import SentenceTransformer

# Setup
client = chromadb.PersistentClient(path="./chroma_db")
collection = client.get_or_create_collection(
    name="legal_docs",
    metadata={"hnsw:space": "cosine"}
)

# Embed function tùy chỉnh
model = SentenceTransformer("intfloat/multilingual-e5-large")

# Thêm documents
documents = [
    "Điều 14 Luật Doanh nghiệp: Công ty TNHH có từ 2 đến 50 thành viên.",
    "Điều 46: Thành viên góp vốn chịu trách nhiệm trong phạm vi vốn góp.",
]

embeddings = model.encode(documents).tolist()

collection.add(
    ids=["doc_1", "doc_2"],
    embeddings=embeddings,
    documents=documents,
    metadatas=[{"source": "luat-dn-2020", "dieu": "14"},
               {"source": "luat-dn-2020", "dieu": "46"}],
)

# Query
query = "Số lượng thành viên tối đa của công ty TNHH?"
query_embedding = model.encode([query]).tolist()

results = collection.query(
    query_embeddings=query_embedding,
    n_results=3,
    where={"source": "luat-dn-2020"},  # Metadata filter
)

for doc, meta, dist in zip(
    results["documents"][0],
    results["metadatas"][0],
    results["distances"][0]
):
    print(f"[{dist:.3f}] {doc[:80]}...")

FAISS - Hiệu năng cao

import faiss
import numpy as np
from sentence_transformers import SentenceTransformer

model = SentenceTransformer("intfloat/multilingual-e5-large")
dim = 1024  # Dimension của multilingual-e5-large

# Tạo index
index = faiss.IndexFlatIP(dim)   # Inner Product (dot product)
# index = faiss.IndexHNSWFlat(dim, 32)  # HNSW với M=32

# Index vectors
corpus = ["văn bản 1", "văn bản 2", ...]
vectors = model.encode(corpus, normalize_embeddings=True)
index.add(vectors.astype(np.float32))

# Save/Load
faiss.write_index(index, "legal.faiss")
index = faiss.read_index("legal.faiss")

# Search
query_vec = model.encode(["câu truy vấn"], normalize_embeddings=True)
distances, indices = index.search(query_vec.astype(np.float32), k=5)

Qdrant - Production-ready

from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct

client = QdrantClient(url="http://localhost:6333")

# Tạo collection
client.create_collection(
    collection_name="legal_docs",
    vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)

# Upsert vectors
client.upsert(
    collection_name="legal_docs",
    points=[
        PointStruct(
            id=1,
            vector=embeddings[0].tolist(),
            payload={"text": "...", "source": "luat-dn-2020", "dieu": 14}
        ),
    ]
)

# Search với filter
from qdrant_client.models import Filter, FieldCondition, MatchValue

results = client.search(
    collection_name="legal_docs",
    query_vector=query_embedding.tolist(),
    query_filter=Filter(
        must=[FieldCondition(key="source", match=MatchValue(value="luat-dn-2020"))]
    ),
    limit=5,
)

pgvector - SQL Integration

-- Enable extension
CREATE EXTENSION IF NOT EXISTS vector;

-- Tạo bảng
CREATE TABLE legal_chunks (
    id SERIAL PRIMARY KEY,
    content TEXT,
    source VARCHAR(100),
    embedding vector(1024)
);

-- Index HNSW
CREATE INDEX ON legal_chunks USING hnsw (embedding vector_cosine_ops);

-- Insert
INSERT INTO legal_chunks (content, source, embedding)
VALUES ('Điều 14 Luật Doanh nghiệp...', 'luat-dn-2020', '[0.1, 0.2, ...]');

-- Semantic search
SELECT content, source,
       1 - (embedding <=> '[0.3, 0.1, ...]'::vector) AS similarity
FROM legal_chunks
ORDER BY embedding <=> '[0.3, 0.1, ...]'::vector
LIMIT 5;

Tóm tắt: Chọn Vector DB cho dự án

graph TD
    A{Quy mô?} -->|Dev / POC| B[Chroma - đơn giản nhất]
    A -->|< 10M vectors, self-hosted| C[Qdrant - cân bằng tốt]
    A -->|Đã dùng PostgreSQL| D[pgvector - tiện tích hợp]
    A -->|Serverless, không muốn ops| E[Pinecone - managed]
    A -->|Research, cần speed tối đa| F[FAISS - fastest]
Tình huống Khuyến nghị
Prototype nhanh Chroma
Production self-hosted Qdrant
Đã có PostgreSQL stack pgvector
Cần scale tự động Pinecone
Tiếng Việt, open-source embedding BAAI/bge-m3 hoặc multilingual-e5-large