Bỏ qua để đến nội dung
Search & RAG

3.8 Fuzzy search (2): n-gram và độ tương đồng tập hợp

Ý tưởng: quên chuỗi đi, coi mỗi từ là một tập hợp các đoạn ngắn. Hai từ giống nhau thì hai tập giao nhau nhiều. Đo giao tập là phép tra index — nhanh hơn DP hàng bậc độ lớn.

Thêm đệm ở hai đầu để ký tự đầu/cuối cũng được ghi nhận (cách pg_trgm làm):

"khau" -> { " k", " kh", "kha", "hau", "au " } 5 trigram
"khao" -> { " k", " kh", "kha", "hao", "ao " } 5 trigram
similarity(A,B)=ABAB\mathrm{similarity}(A,B) = \frac{\lvert A \cap B\rvert}{\lvert A \cup B\rvert}

Ví dụ 1 — khau vs khao (một lỗi thay ký tự cuối):

Giao" k", " kh", "kha"3
Hợp5 + 5 − 3 → 7
similarity3/7 = 0,429

Ngưỡng mặc định của pg_trgm0,3khớp. ✅

Ví dụ 2 — khau vs khoa (hai lỗi, đã tính ở 3.7 là distance 2):

Giao" k", " kh"2
Hợp5 + 5 − 2 → 8
similarity2/8 = 0,25

Dưới ngưỡng 0,3 → không khớp. ❌

Hai ví dụ này cho thấy điều quan trọng nhất của mục này:

n-gram similarity và edit distance là hai thước đo khác nhau, không thay thế nhau. khau/khoa có distance 2 (đủ gần theo Levenshtein) nhưng similarity 0,25 (quá xa theo trigram). Chọn thước đo là chọn hành vi sản phẩm, không phải chọn cách hiện thực.

Quy luật chung: n-gram phạt nặng lỗi ở đầu chuỗi (phá nhiều trigram cùng lúc) và phạt nhẹ lỗi ở giữa chuỗi dài. Edit distance thì không quan tâm lỗi ở đâu — và ngược lại, nó phạt chuỗi ngắn rất nặng (babe là distance 1, tức 50% chuỗi bị đổi).

Vì sao nó nhanh: n-gram là một inverted index

Phần tiêu đề “Vì sao nó nhanh: n-gram là một inverted index”

Đảo chiều đúng thủ thuật của 3.2, nhưng ở mức ký tự:

" k" -> { khau, khao, khoa, khong, ... }
" kh" -> { khau, khao, khoa, khong, ... }
"kha" -> { khau, khao, khai, ... }

Truy vấn khau → tra 5 trigram → hợp các danh sách → đếm số lần mỗi term xuất hiện → đó chính là |A ∩ B|, có được mà không hề so sánh chuỗi. Chỉ những term qua ngưỡng mới đi tiếp sang bước kiểm chứng.

Đây cũng chính là index đã dùng cho substring ở 3.6 — một cấu trúc, hai chức năng.

Postgres — cách rẻ nhất để có fuzzy tử tế:

CREATE EXTENSION pg_trgm;
CREATE INDEX ON docs USING gin (title gin_trgm_ops);
SELECT title, similarity(title, 'mat khau') AS sim
FROM docs
WHERE title % 'mat khau' -- % dùng ngưỡng pg_trgm.similarity_threshold
ORDER BY sim DESC LIMIT 10;

Cùng index này còn tăng tốc LIKE '%mat kha%' — hai nhu cầu, một index.

Elasticsearch — có hai đường, và chúng khác nhau:

CáchCơ chếGhi chú
fuzziness: AUTOLevenshtein automaton (3.9)Giới hạn cứng ≤ 2 phép sửa. AUTO = 0 sửa với từ 1–2 ký tự, 1 với 3–5, 2 với >5
ngram / edge_ngram analyzerindex n-gram như term thườngKhông giới hạn khoảng cách, nhưng index phình rất nhanh — đặt min_gram/max_gram cẩn thận
BẫyChuyện gì xảy raChữa
Ngưỡng quá thấpmọi truy vấn khớp mọi thứ, precision sụpĐo trên golden set, đừng đoán. 0,3 là điểm bắt đầu, không phải đáp án
n-gram trên field dàitập trigram của một đoạn văn gần như phủ hết bảng chữChỉ dùng n-gram cho field ngắn: tên, tiêu đề, từ khoá
Fuzzy trên cả truy vấn nhiều từ"mật khẩu bị lỗi" với fuzzy từng từ → nổ tổ hợp ứng viênFuzzy chỉ term không tìm ra kết quả nào, giữ term khác khớp chính xác
Phần 3 — Nền tảng kỹ thuật