Bản chất của việc tìm kiếm trong cơ sở dữ liệu vector là thực hiện các phép toán hình học để xác định độ gần nhau giữa các điểm dữ liệu. Để đo lường "độ gần" này, chúng ta sử dụng các độ đo khoảng cách (distance metrics) khác nhau.

Bài học này sẽ mổ xẻ công thức toán học, hành vi không gian, và trường hợp ứng dụng thực tế của 4 độ đo kinh điển: Euclidean (L2), Cosine, Dot Product, và Manhattan (L1). Bên cạnh đó, chúng ta sẽ học kỹ thuật chuẩn hóa véc-tơ để tối ưu hóa hiệu năng tìm kiếm lên gấp nhiều lần.

3.1 Khoảng cách Euclidean (L2 Metric)

Khoảng cách Euclidean (Euclidean Distance / L2 Metric) đại diện cho khoảng cách đường thẳng ngắn nhất (đoạn thẳng) nối liền hai điểm trong không gian đa chiều.

Công thức toán học cho hai véc-tơ $\mathbf{u}$ và $\mathbf{v}$ trong không gian $d$ chiều:

\[d_{L2}(\mathbf{u}, \mathbf{v}) = \sqrt{\sum_{i=1}^{d} (u_i - v_i)^2}\]

Ý nghĩa ký hiệu:

  • $\mathbf{u}, \mathbf{v}$: Hai vector đa chiều cần so sánh.
  • $u_i, v_i$: Tọa độ của vector tại chiều thứ $i$.
  • $d$: Tổng số chiều của không gian đặc trưng.

Khoảng cách Euclidean cực kỳ nhạy cảm với độ dài tuyệt đối (magnitude) của véc-tơ. Do đó, L2 thường được sử dụng khi độ lớn của dữ liệu chứa thông tin quan trọng (ví dụ: trong xử lý ảnh, khi độ sáng hoặc mật độ pixel cần được giữ nguyên giá trị tuyệt đối).

euclidean_l2.js
function euclideanL2(u, v) {
  let sum = 0;
  for (let i = 0; i < u.length; i++) {
    const diff = u[i] - v[i];
    sum += diff * diff;
  }
  return Math.sqrt(sum);
}

console.log("L2 Distance:", euclideanL2([1, 2, 3], [4, 5, 6])); // = 5.196

3.2 Tương đồng Cosine (Cosine Similarity)

Khác biệt hoàn toàn với Euclidean, Tương đồng Cosine (Cosine Similarity) chỉ tập trung đo lường góc lệch giữa hai véc-tơ mà bỏ qua hoàn toàn độ dài tuyệt đối của chúng.

Công thức toán học:

\[\text{sim}_{\text{cos}}(\mathbf{u}, \mathbf{v}) = \frac{\mathbf{u} \cdot \mathbf{v}}{\|\mathbf{u}\| \|\mathbf{v}\|} = \frac{\sum u_i v_i}{\sqrt{\sum u_i^2} \sqrt{\sum v_i^2}}\]

Trong đó, phần tử số là tích vô hướng, và phần mẫu số là tích độ dài (L2 norm) của hai véc-tơ. Khoảng cách tương đồng Cosine (Cosine Distance) thường được biểu diễn bằng $1 - \text{sim}_{\text{cos}}$.

Độ đo này tối ưu cho các tác vụ xử lý ngôn ngữ tự nhiên (NLP) hoặc tìm kiếm văn bản. Ví dụ, một bài viết ngắn chứa từ "công nghệ" 2 lần và một bài viết dài chứa từ "công nghệ" 20 lần sẽ có hướng véc-tơ tương tự nhau (góc lệch rất nhỏ), mặc dù độ dài véc-tơ của bài viết dài lớn hơn rất nhiều.

cosine_similarity.js
function cosineSim(u, v) {
  let dot = 0;
  let normU = 0;
  let normV = 0;
  for (let i = 0; i < u.length; i++) {
    dot += u[i] * v[i];
    normU += u[i] * u[i];
    normV += v[i] * v[i];
  }
  if (normU === 0 || normV === 0) return 0;
  return dot / (Math.sqrt(normU) * Math.sqrt(normV));
}

console.log("Cosine similarity:", cosineSim([3, 4], [6, 8])); // = 1 (trùng hướng hoàn toàn)
ℹ️ So sánh Euclidean vs Cosine

Nếu hai vector có cùng hướng nhưng độ dài khác nhau, khoảng cách Cosine giữa chúng sẽ bằng 0 (rất gần), trong khi khoảng cách Euclidean có thể rất lớn (rất xa). Ngược lại, đối với các vector đã chuẩn hóa về cùng độ dài bằng 1, khoảng cách Euclidean và Cosine có tính chất đồng dạng đơn điệu (monotonically equivalent).

3.3 Tích vô hướng (Dot Product / Inner Product)

Tích vô hướng (Dot Product / Inner Product) tính toán tổng các tích chéo của tọa độ tương ứng:

\[\mathbf{u} \cdot \mathbf{v} = \sum_{i=1}^{d} u_i v_i\]

Độ đo này kết hợp cả hai yếu tố: góc lệchđộ lớn tuyệt đối của véc-tơ. Điểm số tích vô hướng càng cao, hai vector càng tương đồng.

Nếu các véc-tơ đầu vào đã được chuẩn hóa L2 về độ dài bằng 1 ($\|\mathbf{u}\| = 1$ và $\|\mathbf{v}\| = 1$), thì tích vô hướng chính là bằng Tương đồng Cosine:

\[\mathbf{u} \cdot \mathbf{v} = \text{sim}_{\text{cos}}(\mathbf{u}, \mathbf{v})\]
dot_product.js
function dotProd(u, v) {
  let sum = 0;
  for (let i = 0; i < u.length; i++) {
    sum += u[i] * v[i];
  }
  return sum;
}

console.log("Dot Product:", dotProd([1, 2], [3, 4])); // = 11
🕳️ Cạm bẫy: Tích vô hướng chưa chuẩn hóa

Nếu bạn sử dụng tích vô hướng thô (unnormalized) làm độ đo tìm kiếm tương đồng trên cơ sở dữ liệu, các tài liệu dài hoặc chứa nhiều từ khóa lặp lại (có độ dài vector rất lớn) sẽ luôn đạt điểm số cực cao và đè bẹp các kết quả chính xác khác. Hãy chắc chắn chuẩn hóa vector trước khi lập chỉ mục tích vô hướng.

3.4 Khoảng cách Manhattan (L1 Metric)

Khoảng cách Manhattan (Manhattan Distance / L1 Metric) đo lường khoảng cách di chuyển theo các trục tọa độ vuông góc (giống như cách di chuyển của taxi trên các khối nhà hình ô bàn cờ tại Manhattan).

Công thức toán học:

\[d_{L1}(\mathbf{u}, \mathbf{v}) = \sum_{i=1}^{d} |u_i - v_i|\]

L1 thường được sử dụng trong các bài toán không gian rời rạc hoặc dữ liệu dạng đặc trưng phân tán cao (sparse features) nơi sự khác biệt nhỏ trên mỗi chiều cần được cộng dồn tuyến tính thay vì phóng đại lên qua phép bình phương như L2.

manhattan_l1.js
function manhattanL1(u, v) {
  let sum = 0;
  for (let i = 0; i < u.length; i++) {
    sum += Math.abs(u[i] - v[i]);
  }
  return sum;
}

console.log("L1 Distance:", manhattanL1([1, 2], [4, 6])); // = 7

3.5 Phân tích so khớp thực tế: Tại sao Dot Product chuẩn hóa lại nhanh hơn Cosine Similarity

Trong các hệ thống cơ sở dữ liệu sản xuất (Production Systems), hiệu năng của từng phép tính khoảng cách đóng vai trò sống còn.

Phép tính Tương đồng Cosine đòi hỏi:

  1. Tính tích vô hướng (nhân chéo cộng dồn).
  2. Tính độ dài vector u (căn bậc hai của tổng bình phương).
  3. Tính độ dài vector v (căn bậc hai của tổng bình phương).
  4. Thực hiện phép nhân hai căn bậc hai ở mẫu số, sau đó làm phép chia số thực.

Các phép toán căn bậc hai (Math.sqrt) và phép chia số thực dấu phẩy động là những phép tính cực kỳ tốn chu kỳ CPU (CPU cycles).

Bằng cách thực hiện chuẩn hóa L2 trước (L2 Normalization) đối với mọi vector ngay khi đưa vào cơ sở dữ liệu (Database Ingestion), độ dài các vector luôn luôn bằng 1. Khi truy vấn, chúng ta chỉ cần chạy phép tính Tích vô hướng (Dot Product) thô: chỉ gồm phép nhân và phép cộng tuyến tính đơn giản, hoàn toàn loại bỏ phép căn và phép chia.

💡 Mẹo tối ưu hóa hiệu năng

Khi các vector đã được chuẩn hóa về độ dài bằng 1, phép tính tìm kiếm lân cận gần nhất sử dụng Dot Product (được tối ưu hóa bằng tập lệnh SIMD trên CPU hoặc song song hóa trên GPU) có thể nhanh hơn gấp 3 - 5 lần so với chạy Cosine Similarity truyền thống trên tập dữ liệu lớn.

normalization_equivalence.js
function normalize(vec) {
  const norm = Math.sqrt(vec.reduce((sum, v) => sum + v*v, 0));
  return vec.map(v => v / (norm || 1));
}

const u = [3, 4];
const v = [0, 5];

const normU = normalize(u);
const normV = normalize(v);

// Dot product của vector chuẩn hóa tương đương Cosine Similarity của vector gốc
const cosineDistance = 1 - cosineSim(u, v);
const dotProductDist = 1 - dotProd(normU, normV);

console.log("Hai giá trị bằng nhau tuyệt đối:", Math.abs(cosineDistance - dotProductDist) < 1e-9);

3.6 Thực hành: So sánh trực quan các độ đo

Điểm mấu chốt cần khắc sâu: cùng một tập điểm dữ liệu, cùng một điểm truy vấn, nhưng thứ hạng 3 lân cận gần nhất (top-3 nearest neighbors) có thể hoàn toàn khác nhau tùy theo độ đo được chọn. Demo dưới đây cố định 9 điểm dữ liệu 2 chiều và 1 điểm truy vấn $Q = (4, 1)$ — hãy đổi độ đo ở dropdown và quan sát 3 đường nối tô đậm thay đổi:

📐 So sánh Top-3 lân cận theo từng độ đo (vdb-engine.js thật)
Đang tính top-3 lân cận theo Euclidean (L2)…
metrics_demo.js (đúng logic đang chạy ở tab Xem trước)
import { calculateDistance } from './vdb-engine.js';

// Diem truy van co dinh - gan goc toa do de con so cosine de doc
const query = { id: 'Q', vec: [4, 1] };

// 9 diem du lieu co dinh, chon toa do de lam ro nghich ly:
// - A(12,3) RAT XA query theo Euclidean nhung CUNG HUONG tuyet doi -> top-1 khi doi Cosine
// - C(3,1) RAT GAN query theo Euclidean nhung se bi A/G/I vuot mat khi xet Cosine
const dataset = [
  { id: 'A', vec: [12, 3] },
  { id: 'B', vec: [0.5, 4] },
  { id: 'C', vec: [3, 1] },
  { id: 'D', vec: [5, 6] },
  { id: 'E', vec: [-3, 8] },
  { id: 'F', vec: [6, -2] },
  { id: 'G', vec: [1, 0.3] },
  { id: 'H', vec: [8, 9] },
  { id: 'I', vec: [4.5, 1.2] },
];

function topKByMetric(metric, k = 3) {
  const scored = dataset.map((pt) => ({
    id: pt.id,
    vec: pt.vec,
    // calculateDistance() luon tra ve "cang nho cang gan" cho ca 4 metric:
    // - euclidean/manhattan: khoang cach thuc
    // - cosine: 1 - cosine similarity
    // - dot: -dotProduct (dao dau de dung chung logic sap xep tang dan)
    score: calculateDistance(query.vec, pt.vec, metric),
  }));
  scored.sort((a, b) => a.score - b.score);
  return scored.slice(0, k);
}

// Doi metric va so sanh: top-3 CUA CUNG MOT TAP DIEM co the hoan toan khac nhau
console.log('Euclidean top-3:', topKByMetric('euclidean').map((r) => r.id));
console.log('Manhattan top-3:', topKByMetric('manhattan').map((r) => r.id));
console.log('Cosine top-3:   ', topKByMetric('cosine').map((r) => r.id));
console.log('Dot top-3:      ', topKByMetric('dot').map((r) => r.id));

📖 Tài liệu tham khảo

Tải file code thực hành minh họa bài học

Tải tệp tin thực hành chứa cài đặt L2, L1, Cosine, Dot Product và chương trình benchmark so sánh hiệu năng của các độ đo:

Tải về vectordb_metrics_demo.js

Bài viết liên quan trong series

Bài 4: Lưu trữ hỗn hợp Hybrid — Phân tách Vector & Metadata Bài 2: Pipeline Nhúng Dữ Liệu (Embeddings) & Không Gian Đặc Trưng Quay lại Lộ trình Vector DB Series

Bình luận