Mở đầu: Giai đoạn tốn nhân lực nhất — và cũng dễ sai nhất

Bài 1 đã chỉ ra rằng thu thập & làm sạch dữ liệu là giai đoạn tốn nhân lực nhất trong 6 giai đoạn vòng đời AI product — nhưng "tốn nhân lực" chưa nói hết mức độ rủi ro của nó. Một quyết định lọc dữ liệu sai ở giai đoạn này không chỉ lãng phí — nó có thể khiến model học ra một thế giới quan lệch lạc mà không ai phát hiện được cho tới khi sản phẩm đã phục vụ hàng triệu người dùng. Nguyên tắc kinh điển "garbage in, garbage out" chưa bao giờ đúng hơn thế trong huấn luyện AI quy mô lớn.

Bài học này đi từ câu hỏi "dữ liệu lấy từ đâu và có được phép dùng không", qua kỹ thuật khử trùng lặp và lọc chất lượng, đến hai khái niệm dễ bị bỏ qua — data drift và class imbalance — và kết thúc bằng cạm bẫy nguy hiểm nhất của cả giai đoạn: lọc quá tay khiến dữ liệu mất đa dạng và khuếch đại thiên lệch sẵn có.


📚 Điều kiện tiên quyết
Nên đọc Bài 1: Vòng Đời Một AI Product Thật trước — bài này đào sâu đúng giai đoạn 1 trong 6 giai đoạn đã giới thiệu ở đó. Không yêu cầu kiến thức toán/ML.

1. Nguồn dữ liệu & Vấn đề pháp lý

Dữ liệu huấn luyện một model ngôn ngữ lớn thường đến từ nhiều nguồn hỗn hợp: crawl công khai trên web, sách/báo có bản quyền đã mua license, mã nguồn mở, và đôi khi là dữ liệu do người dùng đóng góp. Mỗi nguồn mang một rủi ro pháp lý khác nhau — và đội Data Engineering không thể tự ý quyết định "cứ crawl được là dùng được".

  • Bản quyền (Copyright): nội dung có bản quyền không tự động được phép dùng để huấn luyện chỉ vì nó công khai trên Internet — đây là vùng pháp lý còn nhiều tranh cãi và khác nhau giữa các quốc gia.
  • Dữ liệu cá nhân (PII): tên, số điện thoại, địa chỉ, số căn cước — phải được rà soát và loại bỏ/ẩn danh hoá trước khi đưa vào tập huấn luyện, đặc biệt để tuân thủ các quy định như GDPR.
  • Giấy phép sử dụng (License): mỗi tập dữ liệu công khai (Common Crawl, Wikipedia, kho mã nguồn mở...) có điều khoản sử dụng riêng — đội Legal/Compliance phải duyệt từng nguồn trước khi đưa vào pipeline, không phải sau khi đã train xong.

Một ví dụ đơn giản hoá của bước rà soát PII bằng regex (thực tế dùng model NER chuyên dụng chính xác hơn nhiều, đoạn dưới chỉ minh hoạ ý tưởng cơ bản):

scrub-pii.js
function scrubPII(text) {
  return text
    // Số điện thoại dạng phổ biến (không bắt hết mọi định dạng quốc tế)
    .replace(/\b\d{3}[-.\s]?\d{3}[-.\s]?\d{4}\b/g, "[SỐ ĐIỆN THOẠI ĐÃ ẨN]")
    // Email
    .replace(/\b[\w.-]+@[\w.-]+\.\w+\b/g, "[EMAIL ĐÃ ẨN]")
    // Số căn cước/hộ chiếu dạng chuỗi số dài liền nhau
    .replace(/\b\d{9,12}\b/g, "[SỐ ĐỊNH DANH ĐÃ ẨN]");
}

// Lưu ý: regex CHỈ bắt được các mẫu đã biết trước — production dùng model
// Named Entity Recognition (NER) huấn luyện riêng để bắt tên người, địa chỉ...
// mà regex không thể mô tả bằng pattern cố định.
⚠️ Regex không phải giải pháp đầy đủ cho PII
Regex chỉ bắt được các mẫu có cấu trúc cố định (số điện thoại, email). Nó KHÔNG thể phát hiện tên người, địa chỉ nhà, hay thông tin nhạy cảm diễn đạt bằng ngôn ngữ tự nhiên ("nhà tôi ở số 12 đường Láng"). Các pipeline thật luôn kết hợp regex với model NER chuyên dụng, và vẫn chấp nhận một tỷ lệ sót nhất định — đây là lý do vì sao rà soát dữ liệu không bao giờ được coi là "xong 100%".

2. Dedup & Lọc chất lượng

Dữ liệu crawl từ web chứa rất nhiều bản sao gần giống nhau — một bài báo được đăng lại trên 5 trang tin, một bài blog bị "spin" (viết lại sơ sài) để né kiểm duyệt trùng lặp. Nếu không khử trùng lặp (dedup), model sẽ học lệch trọng số về phía nội dung được lặp lại nhiều lần một cách ngẫu nhiên, không phản ánh mức độ quan trọng thật của nội dung đó.

Kỹ thuật dedup thực tế không so sánh từng cặp văn bản (quá chậm với hàng tỷ tài liệu) mà dùng near-dup hashing — băm mỗi tài liệu thành một "chữ ký" ngắn sao cho hai tài liệu giống nhau cho ra chữ ký giống nhau, dù nội dung không hoàn toàn khớp từng ký tự:

near-dup-hash.js
// Minh hoạ tối giản ý tưởng "shingling + hashing" đứng sau MinHash thật.
// Production dùng MinHash/SimHash với hàng trăm hash function — đây chỉ dùng 1
// hash function để lộ rõ cơ chế cốt lõi: cắt văn bản thành các "shingle" (cụm
// N từ liên tiếp) rồi lấy shingle có giá trị hash NHỎ NHẤT làm chữ ký đại diện.
function shingles(text, n = 3) {
  const words = text.toLowerCase().split(/\s+/);
  const result = [];
  for (let i = 0; i <= words.length - n; i++) {
    result.push(words.slice(i, i + n).join(" "));
  }
  return result;
}

function simpleHash(str) {
  let h = 0;
  for (let i = 0; i < str.length; i++) h = (h * 31 + str.charCodeAt(i)) >>> 0;
  return h;
}

function fingerprint(text) {
  const hashes = shingles(text).map(simpleHash);
  return Math.min(...hashes); // chữ ký = hash nhỏ nhất trong mọi shingle
}

// Hai văn bản gần giống nhau (chỉ khác vài từ) có xác suất cao ra CÙNG MỘT
// fingerprint, vì phần lớn shingle của chúng trùng nhau -> phát hiện trùng lặp
// mà không cần so sánh trực tiếp từng cặp văn bản.
🔬 Đào sâu: Vì sao không so khớp từng cặp văn bản trực tiếp
Với 1 tỷ tài liệu, so sánh mọi cặp là \(\binom{10^9}{2} \approx 5 \times 10^{17}\) phép so sánh — bất khả thi dù có cụm máy tính lớn. Kỹ thuật MinHash thật (không đơn giản hoá như ví dụ trên) dùng hàng trăm hash function độc lập, ước lượng độ tương đồng Jaccard giữa hai tập shingle chỉ bằng cách đếm số hash function cho ra kết quả trùng nhau — biến bài toán \(O(n^2)\) thành gần như \(O(n)\) khi kết hợp với kỹ thuật gom nhóm (Locality-Sensitive Hashing).

Sau dedup, bước tiếp theo là lọc chất lượng — loại bỏ nội dung rác (boilerplate, văn bản quá ngắn, ngôn ngữ không xác định được) bằng bộ lọc heuristic đơn giản, hoặc bằng một model phân loại chất lượng (classifier filter) đã huấn luyện riêng để chấm điểm mỗi tài liệu:

quality-heuristics.js
function passesHeuristicFilter(doc) {
  const wordCount = doc.text.split(/\s+/).length;
  const symbolRatio = (doc.text.match(/[^\w\s]/g) || []).length / doc.text.length;
  return (
    wordCount >= 50 && // loại văn bản quá ngắn (thường là boilerplate/lỗi crawl)
    symbolRatio < 0.3 && // loại văn bản đầy ký tự lạ (spam/mã hoá lỗi)
    doc.detectedLanguage !== "unknown" // loại văn bản không xác định được ngôn ngữ
  );
}

3. Data Drift & Class Imbalance

Ngay cả khi dữ liệu đã được lọc "sạch" tại thời điểm thu thập, hai vấn đề sau vẫn âm thầm làm giảm chất lượng model theo thời gian hoặc ngay từ đầu:

Tiêu chí Data Drift Class Imbalance
Bản chất Phân bố dữ liệu THAY ĐỔI theo thời gian so với lúc train Phân bố dữ liệu LỆCH ngay từ đầu giữa các nhóm/nhãn
Ví dụ cụ thể Model học xu hướng ngôn ngữ 2023, người dùng 2026 dùng từ lóng mới model chưa từng thấy 95% dữ liệu tiếng Anh, 5% còn lại chia cho hơn 100 ngôn ngữ khác
Phát hiện bằng cách nào So sánh phân bố dữ liệu production hiện tại với phân bố tập train (Bài 6: Giám sát) Thống kê số lượng mẫu/nhóm ngay trong bước thu thập (bài này)
Cách xử lý phổ biến Train lại định kỳ với dữ liệu mới, giám sát liên tục Oversampling nhóm thiểu số, hoặc chủ động thu thập thêm nguồn
💡 Mẹo: Giữ lại metadata gốc, đừng chỉ giữ văn bản đã lọc
Khi lọc dữ liệu, đừng xoá hẳn thông tin về nguồn gốc, thời điểm thu thập, và lý do loại bỏ — những metadata này chính là nguyên liệu để phát hiện data drift sau này (so sánh phân bố theo thời gian) và để điều tra khi model có hành vi bất thường. Ném đi văn bản gốc + giữ lại "văn bản đã lọc" là quyết định không thể đảo ngược.

Việc phát hiện data drift trong sản xuất sẽ được nói kỹ ở Bài 6 (Giám sát & Lặp lại) — bài đó đóng vai trò "vòng phản hồi" quay lại chính bước thu thập dữ liệu này. Nếu bạn muốn hiểu cách biểu diễn dữ liệu văn bản thành vector để so sánh phân bố (dùng cho cả dedup nâng cao và phát hiện drift), xem thêm Series 12 — Embeddings/Word2Vec.

4. Cạm bẫy: Lọc quá tay & Khuếch đại thiên lệch (Bias Amplification)

Đây là cạm bẫy nguy hiểm nhất của cả giai đoạn thu thập dữ liệu — và nghịch lý ở chỗ nó xảy ra chính vì đội ngũ dữ liệu đang cố làm điều đúng: lọc bỏ nội dung "chất lượng thấp".

🕳️ Cạm bẫy: Bộ lọc chất lượng không trung lập
Một classifier chấm điểm "chất lượng văn bản" luôn được huấn luyện từ một tập dữ liệu mẫu do con người gán nhãn — và tập mẫu đó gần như luôn thiên về văn phong phổ biến (tiếng Anh chuẩn mực, văn phong học thuật/báo chí). Hậu quả: văn bản viết bằng phương ngữ, ngôn ngữ ít tài nguyên, hoặc văn phong non-chuẩn (mạng xã hội, diễn đàn) bị chấm điểm thấp hơn một cách hệ thống — không phải vì nội dung kém giá trị, mà vì nó "khác" với những gì bộ lọc từng thấy. Càng đặt ngưỡng lọc cao, tỷ lệ những nhóm này bị loại càng tăng nhanh hơn hẳn so với nhóm đa số — đây chính là điều demo tương tác bên dưới sẽ cho bạn thấy trực tiếp bằng số liệu.

Hệ quả thực tế: model cuối cùng "giỏi" tiếng Anh chuẩn mực nhưng yếu hẳn với ngôn ngữ ít tài nguyên hoặc văn phong đời thường — không phải vì thiếu dữ liệu thô, mà vì chính bước lọc "để làm sạch" đã loại bỏ chúng không cân xứng. Đây là ví dụ kinh điển của bias amplification: một thiên lệch nhỏ trong bộ lọc (huấn luyện từ mẫu không đại diện) bị khuếch đại thành thiên lệch lớn trong tập dữ liệu cuối cùng.

5. Thực hành tương tác: Data Quality Filter Lab

Bộ dữ liệu đồ chơi dưới đây gồm 60 "tài liệu" chia làm 5 nhóm nguồn (tin tức, học thuật, diễn đàn, mạng xã hội, ngôn ngữ ít tài nguyên) — nhóm "Ngôn ngữ ít tài nguyên" được cố ý gán điểm chất lượng thấp hơn thực tế để mô phỏng đúng thiên lệch của bộ phân loại đã nói ở mục 4. Kéo thanh trượt ngưỡng chất lượng và quan sát tỷ lệ giữ lại của từng nhóm thay đổi KHÔNG đồng đều:

🧪 Data Quality Filter Lab
aisys-data-pipeline.js (trích)
export function computeFilterResult(threshold, dedupEnabled) {
  const afterDedup = dedupEnabled ? applyDedup(DOCUMENTS) : DOCUMENTS;
  const kept = afterDedup.filter((d) => d.quality >= threshold);
  // ... tính % giữ lại theo TỪNG NHÓM để lộ rõ chênh lệch giữa các nhóm
  // khi ngưỡng tăng dần (xem toàn bộ hàm trong file tải về bên dưới).
}

Thử kéo ngưỡng lên trên 60 và so sánh: nhóm "Học thuật/Paper" gần như không đổi, trong khi nhóm "Ngôn ngữ ít tài nguyên" và "Mạng xã hội" rơi rụng nhanh hơn hẳn — đúng cơ chế bias amplification vừa mô tả. Nếu bạn muốn xem cách một hệ RAG xử lý dữ liệu đã qua các bước lọc này để tìm kiếm ngữ nghĩa, xem thêm Series 16 — Chunking & Vector DB.

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

File JavaScript aisys-data-pipeline.js — bộ dữ liệu đồ chơi 60 tài liệu/5 nhóm, hàm dedup + lọc chất lượng, và hàm render demo tương tác dùng trong bài:

Tải về aisys-data-pipeline.js

📖 Tài liệu tham khảo

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

Bài 1: Vòng Đời Một AI Product Thật Bài 3: Huấn Luyện Phân Tán (Khái Niệm) Quay lại Lộ trình Kỹ Thuật Hệ Thống AI

Bình luận