Để một cơ sở dữ liệu vector có thể lưu trữ và truy vấn thông tin, dữ liệu đầu vào trước tiên phải được biến đổi thành dạng véc-tơ. Quy trình chuyển đổi tự động này được gọi là Pipeline nhúng dữ liệu (Embeddings Pipeline).
Bài học này sẽ hướng dẫn bạn chi tiết cơ chế hoạt động bên trong của các mô hình nhúng dữ liệu, sự tiến hóa từ các kỹ thuật tần suất từ đơn giản (TF-IDF) sang mô hình mạng nơ-ron ngữ nghĩa dày đặc, và cách tối ưu hóa hiệu năng tính toán véc-tơ trên môi trường trình duyệt client-side.
2.1 Từ dữ liệu thô đến không gian vector: Cơ chế của Embedding Model
Bản chất của một mô hình nhúng dữ liệu (Embedding Model) là một mạng nơ-ron học sâu (Deep Neural Network) đã được huấn luyện trên hàng tỷ tài liệu. Mạng nơ-ron này hoạt động như một hàm toán học ánh xạ: Nó nhận đầu vào là một chuỗi văn bản thô (hoặc hình ảnh, âm thanh) và biến đổi nó qua hàng triệu trọng số toán học để xuất ra một mảng số thực có kích thước cố định.
Quy trình này diễn ra qua 3 bước cốt lõi:
- Token hóa (Tokenization): Cắt văn bản đầu vào thành các mảnh nhỏ hơn gọi là tokens (có thể là từ, cụm từ hoặc ký tự đơn lẻ).
- Mã hóa ngữ cảnh (Contextual Encoding): Đưa các tokens qua mạng Transformer. Các lớp Attention của Transformer tính toán sự liên quan của các từ với nhau, nắm bắt cấu trúc ngữ pháp và ngữ cảnh cụ thể của câu.
-
Gom tụ (Pooling): Gộp thông tin đặc trưng của toàn bộ câu/đoạn văn thành một véc-tơ đại
diện duy nhất (thông thường bằng cách lấy trung bình cộng các véc-tơ của từng token hoặc lấy giá trị của
token đặc biệt
[CLS]).
// Giả lập tích hợp mô hình nhúng client-side qua API
async function getEmbedding(text) {
const response = await fetch("https://api.openai.com/v1/embeddings", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
"input": text,
"model": "text-embedding-3-small"
})
});
const data = await response.json();
return data.data[0].embedding; // Mảng 1536 chiều
}
2.2 Không gian biểu diễn ngữ nghĩa (Semantic Feature Spaces) & Biểu diễn toán học
Không gian véc-tơ nơi lưu trữ embeddings được gọi là Không gian biểu diễn ngữ nghĩa (Semantic Feature Space). Hãy tưởng tượng mỗi chiều hướng trong không gian này đại diện cho một thuộc tính đặc trưng ẩn (latent feature) của dữ liệu.
Nếu mô hình có $d$ chiều, nghĩa là dữ liệu được định vị bởi tọa độ $d$ điểm số đặc trưng:
\[V = [x_1, x_2, \dots, x_d] \in \mathbb{R}^d\]Khi hai thực thể có ý nghĩa tương đương (ví dụ: "vua" và "hoàng đế"), mạng nơ-ron sẽ tính ra các điểm số đặc trưng tương tự nhau trên hầu hết các chiều, khiến khoảng cách hình học giữa hai véc-tơ này trong không gian $\mathbb{R}^d$ cực kỳ nhỏ.
Mối quan hệ ngữ nghĩa trong không gian embeddings có tính tuyến tính toán học rất thú vị. Một ví dụ kinh
điển đã được chứng minh trong thực tế: nếu ta lấy véc-tơ của từ "Vua", trừ đi véc-tơ của từ
"Nam", rồi cộng thêm véc-tơ của từ "Nữ", kết quả sẽ cho ra một véc-tơ nằm rất
gần với véc-tơ biểu diễn của từ "Nữ hoàng".
2.3 Quá trình Token hóa (Tokenization) và Vector hóa tần suất từ
Trước khi các mô hình Transformer sâu ra đời, các hệ thống tìm kiếm sử dụng các bộ véc-tơ hóa dựa trên thống kê tần suất từ xuất hiện, phổ biến nhất là TF-IDF (Term Frequency - Inverse Document Frequency).
Công thức tính TF (tần suất từ xuất hiện trong tài liệu $d$):
\[TF(t, d) = \frac{f_{t, d}}{\sum_{t'} f_{t', d}}\]Công thức tính IDF (tầm quan trọng của từ trên toàn bộ tập dữ liệu $D$):
\[IDF(t, D) = \log\left(\frac{|D|}{|\{d \in D : t \in d\}|}\right) + 1\]Điểm số TF-IDF cuối cùng: $TF\text{-}IDF(t, d, D) = TF(t, d) \times IDF(t, D)$.
Véc-tơ TF-IDF có số chiều đúng bằng kích thước từ vựng của toàn bộ hệ thống. Đây là các véc-tơ thưa thớt (sparse vectors) vì hầu hết các giá trị trong mảng là 0 (do một câu thông thường chỉ chứa một phần rất nhỏ của bộ từ vựng).
Bảng dưới đây so sánh sự khác biệt bản chất giữa véc-tơ tần suất thưa thớt truyền thống và véc-tơ nhúng dày đặc hiện đại:
| Đặc tính | Véc-tơ thưa thớt (TF-IDF / BM25) | Véc-tơ dày đặc (Neural Embeddings) |
|---|---|---|
| Số chiều (Dimensions) | Rất lớn (bằng kích thước từ điển, từ $10^4$ đến $10^6$ chiều). | Cố định và nhỏ gọn (thường từ $256$ đến $1536$ chiều). |
| Mật độ dữ liệu | Hầu hết là số 0, chỉ một vài vị trí chứa giá trị. | Tất cả các vị trí đều chứa số thực khác 0. |
| Hiểu ngữ cảnh | Không. Chỉ đếm tần suất xuất hiện ký tự thô. | Có. Hiểu được từ đồng nghĩa, cấu trúc ngữ pháp phức tạp. |
| Tác vụ phù hợp | Tìm kiếm từ khóa chính xác, mã số, số điện thoại. | Tìm kiếm ngữ nghĩa ngữ cảnh, so khớp hình ảnh, âm thanh. |
Dưới đây là mã nguồn JS cài đặt bộ trích xuất véc-tơ TF-IDF tối giản để bạn hiểu cơ chế hoạt động cơ bản của nó:
function calculateTFIDF(doc, corpus) {
const words = doc.toLowerCase().split(" ");
const allDocsWords = corpus.map(d => d.toLowerCase().split(" "));
// Tính TF cho từ "học"
const tf = words.filter(w => w === "học").length / words.length;
// Tính IDF cho từ "học"
const docCount = allDocsWords.filter(wordsArr => wordsArr.includes("học")).length;
const idf = Math.log(corpus.length / (docCount || 1)) + 1;
return tf * idf;
}
const corpus = ["Tôi học lập trình C", "Lập trình Web cơ bản"];
console.log("TF-IDF của từ 'học' trong câu 1:", calculateTFIDF(corpus[0], corpus));
2.4 Quản lý và xử lý Batch Embedding
Khi xây dựng ứng dụng thực tế trên môi trường client-side, việc gọi API nhúng tuần tự từng câu văn đơn lẻ (Sequential Request) sẽ tốn rất nhiều thời gian chờ mạng. Do đó, chúng ta cần triển khai cơ chế Ghép lô (Batching) và Hàng đợi concurrency để gom các đoạn văn bản lại, gửi 1 truy vấn API gom cụm để tối ưu băng thông.
Song song đó, việc thiết lập Bộ nhớ đệm (Caching) trên Client-side bằng LocalStorage hoặc IndexedDB để lưu trữ véc-tơ của các văn bản đã tính toán trước đó là vô cùng thiết yếu để giảm chi phí API và triệt tiêu độ trễ mạng đối với dữ liệu lặp.
class EmbeddingBatchQueue {
constructor(batchSize = 5) {
this.batchSize = batchSize;
this.pending = [];
this.timer = null;
}
add(text) {
return new Promise((resolve) => {
this.pending.push({ text, resolve });
if (this.pending.length >= this.batchSize) {
this.flush();
} else if (!this.timer) {
// Tự động flush sau 50ms nếu không đủ mẻ
this.timer = setTimeout(() => this.flush(), 50);
}
});
}
async flush() {
clearTimeout(this.timer);
this.timer = null;
const batch = this.pending.splice(0, this.batchSize);
if (batch.length === 0) return;
console.log(`Gửi Batch API cho ${batch.length} dòng văn bản.`);
// Giả lập API gọi gộp
const vectors = batch.map(() => [Math.random(), Math.random()]);
batch.forEach((task, idx) => task.resolve(vectors[idx]));
}
}
2.5 Case study: Xử lý nghẽn cổ chai (bottleneck) hiệu năng khi xử lý Embedding thời gian thực trên các thiết bị Edge/Mobile
Khi chúng ta sử dụng các mô hình Transformer chạy trực tiếp trên thiết bị (ONNX Runtime, Transformers.js) thay vì gọi API đám mây, CPU/GPU của điện thoại di động sẽ phải gánh toàn bộ hàng tỷ phép nhân ma trận.
Nếu chạy tính toán mô hình học sâu ngay trên luồng xử lý chính của trình duyệt (Main Thread), giao diện người dùng (UI) sẽ bị đóng băng hoàn toàn, không thể click hay cuộn trang. Giải pháp thiết yếu là đưa toàn bộ pipeline xử lý token và suy luận mô hình sang chạy ngầm ở Web Worker.
Lỗi phổ biến nhất khi tích hợp Transformers.js trên trình duyệt là tải trực tiếp tệp trọng số mô hình lớn (vài trăm MB) trên luồng chính. Trình duyệt sẽ lập tức báo cảnh báo Unresponsive Page và người dùng có xu hướng tắt tab ngay lập tức.
Dưới đây là đoạn mã thiết lập Web Worker để chạy ngầm tiến trình sinh Embedding mà không ảnh hưởng tới UI chính:
// Mã nguồn chạy bên trong worker
self.onmessage = async function(e) {
const { text } = e.data;
// Giả lập phép toán nhân ma trận nặng nề tốn 300ms
const start = performance.now();
while (performance.now() - start < 300) {}
const mockVector = [0.245, -0.912, 0.451];
// Trả kết quả về cho luồng giao diện chính
self.postMessage({ text, vector: mockVector });
};
2.6 Thực hành: Không gian ngữ nghĩa tương tác
Để thấy rõ ý tưởng "embedding gần nhau ⇒ ngữ nghĩa tương đồng" mà không cần tải một mô hình Transformer
thật, demo dưới đây dùng một tập 12 câu tiếng Việt thuộc 3 chủ đề (thú cưng, chứng khoán, ẩm thực), với
tọa độ embedding đã được giảm chiều thủ công xuống 2D (chỉ để trực quan hóa trên mặt
phẳng — mô hình thật thường xuất ra 256-1536 chiều). Chọn một câu truy vấn, hệ thống sẽ tính
euclideanDistance() thật (cùng hàm dùng xuyên suốt series) tới 11 câu còn lại và nối đường
tới 3 láng giềng gần nhất:
import { euclideanDistance } from './vdb-engine.js';
// Toa do 2D da GIAM CHIEU THU CONG de minh hoa - mo hinh that xuat 256-1536 chieu.
// 3 chu de duoc dat cach xa nhau trong mat phang de gia lap cum ngu nghia.
const phrases = [
{ text: 'con mèo dễ thương đang ngủ trên ghế sofa', vec: [1.2, 1.5], topic: 'pets' },
{ text: 'chú chó con chạy nhảy ngoài sân vườn', vec: [1.6, 1.1], topic: 'pets' },
{ text: 'nuôi cá vàng trong bể kính nhỏ', vec: [0.8, 1.8], topic: 'pets' },
{ text: 'con vẹt biết bắt chước tiếng người', vec: [1.4, 2.0], topic: 'pets' },
{ text: 'giá cổ phiếu VN30 tăng mạnh phiên sáng nay', vec: [8.5, 1.4], topic: 'finance' },
{ text: 'nhà đầu tư chốt lời trên thị trường chứng khoán', vec: [8.9, 1.9], topic: 'finance' },
{ text: 'chỉ số VN-Index vượt mốc 1300 điểm', vec: [8.1, 1.1], topic: 'finance' },
{ text: 'quỹ đầu tư mua vào cổ phiếu ngân hàng', vec: [9.3, 1.6], topic: 'finance' },
{ text: 'phở bò Hà Nội nước dùng thơm đậm đà', vec: [4.8, 8.6], topic: 'food' },
{ text: 'bánh mì kẹp thịt nướng ăn buổi sáng', vec: [5.2, 8.1], topic: 'food' },
{ text: 'gỏi cuốn tôm thịt chấm nước mắm chua ngọt', vec: [4.4, 9.0], topic: 'food' },
{ text: 'bún chả Hà Nội ăn kèm rau sống', vec: [5.6, 8.9], topic: 'food' },
];
function findNearest(queryIdx, k = 3) {
const query = phrases[queryIdx];
const distances = phrases
.map((p, i) => ({ i, text: p.text, topic: p.topic, distance: euclideanDistance(query.vec, p.vec) }))
.filter((r) => r.i !== queryIdx);
distances.sort((a, b) => a.distance - b.distance);
return distances.slice(0, k);
}
// Ve: to mau theo topic (indigo/cyan/orange), khoanh do quanh diem truy van,
// ke duong mong toi 3 lang gieng gan nhat tra ve boi findNearest().
📖 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 code mẫu JavaScript cài đặt TF-IDF vectorizer thủ công và Batch Queue Manager để chạy thử nghiệm trên máy tính của bạn:
Tải về vectordb_embeddings_demo.js
Bình luận