ML 1 — Quy trình Machine Learning thực chiến

13 thg 7, 2026 7 lượt xem
#machine-learning
#ai
#scikit-learn
#workflow
#data-leakage

ML 1 — Quy trình Machine Learning thực chiến

Đa số khoá học Machine Learning bắt đầu bằng thuật toán. Nhưng khi bạn làm dự án thật, phần khó nhất không phải chọn thuật toán — mà là khung hoá bài toán cho đúng, chia dữ liệu cho trung thực, và tránh những cái bẫy khiến mô hình đẹp trên máy nhưng sập ngay khi lên production. Bài này là bản đồ tổng thể cho cả series Machine Learning thực chiến: đi qua toàn bộ vòng đời của một dự án ML cổ điển (classical ML — khác hẳn series LLM), với công cụ chủ lực là scikit-learn.

Đây là ML cho dữ liệu dạng bảng (tabular): hồ sơ khách hàng, log giao dịch, bảng tính. Nếu bạn mới, hãy đọc AI 3 — Machine Learning cổ điển để nắm các thuật toán nền tảng; bài này tập trung vào quy trình bao quanh chúng.

1. Khung hoá bài toán (problem framing)

Sai lầm đắt nhất trong ML không xảy ra ở dòng code nào cả — nó xảy ra khi bạn giải sai bài toán. Trước khi mở Jupyter, hãy dịch bài toán kinh doanh thành bài toán ML rõ ràng.

Ví dụ: "Giảm tỷ lệ khách hàng rời bỏ (churn)" là bài toán kinh doanh. Nó chưa phải bài toán ML. Để khung hoá, trả lời bốn câu hỏi:

  • Đơn vị dự đoán là gì? Mỗi khách hàng? Mỗi khách hàng mỗi tháng? Mỗi giao dịch? Đây quyết định một dòng dữ liệu (một mẫu) đại diện cho cái gì.
  • Target (biến mục tiêu) là gì? Với churn: nhãn nhị phân "khách này có rời trong 90 ngày tới không?". Phải định nghĩa chính xác khoảng thời gianthế nào là rời.
  • Đây là bài toán loại nào? Dự đoán nhãn rời rạc → phân loại (classification). Dự đoán một con số liên tục (ví dụ số ngày còn ở lại) → hồi quy (regression).
  • Đo thành công bằng gì? Không phải "accuracy" một cách máy móc — mà chỉ số nào phản ánh giá trị kinh doanh. Với churn mất cân bằng, accuracy vô dụng; recall/precision/AUC mới có nghĩa. Chi tiết ở ML 5 — Đánh giá.

Có giám sát, không giám sát và reinforcement

Ba trường phái lớn:

  • Học có giám sát (supervised): có dữ liệu đã gán nhãn (X → y), học để dự đoán y cho X mới. Chiếm phần lớn ML thực chiến. Chia làm phân loại (target rời rạc: churn/không churn, gian lận/hợp lệ) và hồi quy (target liên tục: giá nhà, doanh thu, số dư).
  • Học không giám sát (unsupervised): không có nhãn, tìm cấu trúc ẩn — phân cụm khách hàng (clustering), giảm chiều, phát hiện bất thường.
  • Reinforcement learning: tác nhân học qua thử-sai với phần thưởng (robot, game, đấu giá thời gian thực). Rất mạnh nhưng ít gặp trong bài toán tabular hằng ngày; chỉ nhắc để bạn biết vị trí của nó.

Phần lớn series này xoay quanh supervised learning cho tabular data.

2. Vòng đời của một dự án ML

ML không phải quy trình một chiều mà là vòng lặp. Bạn hiếm khi làm đúng ngay lần đầu; sức mạnh nằm ở tốc độ lặp.

Bảy giai đoạn:

  1. Xác định vấn đề — khung hoá như mục 1.
  2. Thu thập & hiểu dữ liệu (EDA): khám phá dữ liệu (Exploratory Data Analysis). Phân bố mỗi cột, giá trị thiếu, ngoại lai, tương quan với target, và — quan trọng nhất — dữ liệu này được sinh ra lúc nào. Câu hỏi "tại thời điểm dự đoán thật, mình có cột này chưa?" sẽ cứu bạn khỏi rất nhiều rò rỉ.
  3. Chuẩn bị & feature: làm sạch, mã hoá, tạo đặc trưng. Cả một bài riêng: ML 2 — Feature engineering.
  4. Chọn & huấn luyện model: bắt đầu từ mô hình đơn giản, không nhảy ngay vào ensemble phức tạp.
  5. Đánh giá: đo trên tập tách biệt bằng đúng chỉ số. Xem ML 5.
  6. Triển khai: đưa mô hình vào phục vụ (API, batch job).
  7. Giám sát: theo dõi hiệu năng thực tế, phát hiện data drift, huấn luyện lại. Xem ML 8 — Deployment & MLOps.

3. Chia dữ liệu: nền tảng của mọi kết luận trung thực

Nếu chỉ được nhớ một điều từ bài này, hãy nhớ điều này: mô hình phải được đánh giá trên dữ liệu nó chưa từng thấy. Nếu không, mọi con số bạn báo cáo đều là ảo tưởng.

Chia dữ liệu thành ba phần:

  • Train: để mô hình học tham số.
  • Validation (val): để bạn điều chỉnh — chọn mô hình, chỉnh siêu tham số. Mô hình không học trực tiếp trên đây, nhưng quyết định của bạn có "học" từ nó.
  • Test: để đo lần cuối, chỉ chạm một lần khi mọi thứ đã chốt. Đây là ước lượng trung thực nhất cho hiệu năng production.

Vì sao cần test tách biệt hẳn val? Vì khi bạn thử 50 cấu hình rồi chọn cái tốt nhất trên val, bạn đã vô tình khớp với val — nó không còn "chưa từng thấy" nữa. Test là trọng tài độc lập giữ cho bạn thành thật.

# (minh hoạ)
from sklearn.model_selection import train_test_split

# Tach test ra truoc tien va khoa lai
X_trainval, X_test, y_trainval, y_test = train_test_split(
    X, y, test_size=0.20, random_state=42, stratify=y
)

# Chia phan con lai thanh train / validation
X_train, X_val, y_train, y_val = train_test_split(
    X_trainval, y_trainval, test_size=0.25, random_state=42, stratify=y_trainval
)
# Ket qua ~ 60% train, 20% val, 20% test

Hai chi tiết đáng lưu ý:

  • stratify=y: giữ tỷ lệ các lớp giống nhau ở mọi tập. Với churn 5%, không stratify có thể khiến tập test gần như không có ca churn nào — vô nghĩa.
  • random_state=42: cố định hạt giống ngẫu nhiên để kết quả tái lập được (reproducibility). Không có nó, mỗi lần chạy ra một con số khác, không ai kiểm chứng được bạn.

Cross-validation

Khi dữ liệu ít, chia cứng một tập val có thể may rủi. Cross-validation (thường k-fold) chia train thành k phần, lần lượt lấy một phần làm val và huấn luyện trên k−1 phần còn lại, rồi lấy trung bình. Ước lượng ổn định hơn và dùng hết dữ liệu. Chi tiết và các biến thể ở ML 5 — Đánh giá.

Dữ liệu chuỗi thời gian: tuyệt đối không xáo trộn

Đây là nơi người ta sập bẫy nhiều nhất. Với dữ liệu có yếu tố thời gian (giao dịch theo ngày, hành vi khách hàng theo tháng), train_test_split ngẫu nhiên là sai nghiêm trọng: nó có thể đặt dữ liệu tháng 12 vào train và tháng 6 vào test — tức bạn đang dùng tương lai để dự đoán quá khứ. Đó là nhìn trộm tương lai (look-ahead), và điểm số sẽ đẹp giả tạo.

Cách đúng: chia theo thời gian. Train trên giai đoạn cũ, val/test trên giai đoạn mới hơn. scikit-learn có TimeSeriesSplit để cross-validation đúng cách — luôn train trên quá khứ, kiểm trên tương lai:

# (minh hoạ)
from sklearn.model_selection import TimeSeriesSplit

# Gia su du lieu da sap xep theo thoi gian tang dan
tscv = TimeSeriesSplit(n_splits=5, gap=0)
for train_idx, val_idx in tscv.split(X):
    X_tr, X_va = X.iloc[train_idx], X.iloc[val_idx]
    # train_idx luon nam TRUOC val_idx ve thoi gian

Tham số gap cho phép chừa một khoảng giữa train và val — hữu ích khi nhãn của bạn được xác định sau một độ trễ (ví dụ: churn 90 ngày sau).

4. Baseline: luôn có điểm neo trước khi phức tạp

Trước khi huấn luyện bất cứ mô hình "xịn" nào, hãy dựng một baseline ngớ ngẩn. Nghe phản trực giác, nhưng đây là kỷ luật thực chiến quan trọng bậc nhất.

Baseline là mô hình đơn giản nhất có thể:

  • Phân loại: luôn đoán lớp đa số. Hoặc một luật thủ công ("khách không đăng nhập 60 ngày → churn").
  • Hồi quy: luôn đoán giá trị trung bình (hoặc trung vị).

Vì sao? Vì nó cho bạn thước đo tối thiểu. Nếu dataset churn có 95% khách ở lại, một mô hình "luôn đoán ở lại" đã đạt 95% accuracy. Nếu mô hình cây quyết định cầu kỳ của bạn đạt 94%, nó tệ hơn baseline — dù con số nghe rất oách. Không có baseline, bạn không biết mình đang tiến bộ hay chỉ đang tự lừa.

# (minh hoạ)
from sklearn.dummy import DummyClassifier
from sklearn.metrics import f1_score

baseline = DummyClassifier(strategy="most_frequent")
baseline.fit(X_train, y_train)
print("Baseline F1:", f1_score(y_val, baseline.predict(X_val)))
# Moi mo hinh that su phai vuot qua con so nay moi dang xet

Nguyên tắc: đơn giản trước, đo bằng eval, rồi mới lặp lên phức tạp. Đừng nhảy vào tuning hyperparameter khi chưa vượt baseline.

5. Pipeline scikit-learn và cách chống data leakage

Đây là phần công cụ quan trọng nhất của bài. Rò rỉ dữ liệu (data leakage) là khi thông tin lẽ ra không có tại thời điểm dự đoán lọt vào quá trình huấn luyện. Hậu quả kinh điển: điểm offline đẹp long lanh, nhưng production sập thảm hại — vì mô hình đã "gian lận".

Một dạng leakage tinh vi và phổ biến: tiền xử lý trên toàn bộ dữ liệu. Ví dụ, bạn chuẩn hoá (scale) cột thu nhập bằng cách trừ trung bình và chia độ lệch chuẩn tính trên cả dataset — bao gồm cả tập test. Thế là thống kê của test đã "rò" vào bước huấn luyện. Tương tự với điền giá trị thiếu (impute) bằng trung bình toàn bộ, hay mã hoá target.

Nguyên tắc vàng, được chính scikit-learn nhấn mạnh: fit chỉ trên train, transform áp lên cả train lẫn test. Trung bình/độ lệch chuẩn/danh mục... phải học chỉ từ train, rồi mới áp cho test.

Làm thủ công thì rất dễ quên một bước. Giải pháp: gói mọi thứ vào PipelineColumnTransformer. Pipeline đảm bảo khi bạn gọi .fit(), toàn bộ tiền xử lý chỉ học từ train; khi gọi .predict() trên test, nó tự động chỉ transform. Nó cũng khiến cross-validation an toàn — mỗi fold tự fit lại tiền xử lý trên phần train của fold đó.

# (minh hoạ)
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression

num_cols = ["tuoi", "thu_nhap", "so_du"]
cat_cols = ["goi_dich_vu", "khu_vuc"]

# Tien xu ly rieng cho cot so va cot phan loai
numeric = Pipeline([
    ("impute", SimpleImputer(strategy="median")),
    ("scale", StandardScaler()),
])
categorical = Pipeline([
    ("impute", SimpleImputer(strategy="most_frequent")),
    ("onehot", OneHotEncoder(handle_unknown="ignore")),
])

preprocess = ColumnTransformer([
    ("num", numeric, num_cols),
    ("cat", categorical, cat_cols),
])

# Goi tien xu ly + model vao MOT doi tuong
model = Pipeline([
    ("prep", preprocess),
    ("clf", LogisticRegression(max_iter=1000, class_weight="balanced")),
])

model.fit(X_train, y_train)      # median/mean/scale HOC chi tu train
preds = model.predict(X_test)    # test chi duoc transform, khong fit

Toàn bộ tiền xử lý và mô hình giờ là một đối tượng: fit một lần, predict một lần, lưu một file, phục vụ một chỗ. Đây cũng là artifact bạn mang thẳng lên production (xem ML 8 — MLOps).

Điểm mặt các dạng leakage thường gặp

  • Thống kê toàn bộ dữ liệu khi scale/impute/encode (đã nói trên) → dùng Pipeline.
  • Dùng thông tin tương lai: một cột thực ra được cập nhật sau thời điểm dự đoán. Ví dụ, cột "ngày đóng tài khoản" khi dự đoán churn — nó chính là câu trả lời trá hình.
  • Rò rỉ target: một feature là biến thể của nhãn (ví dụ "tổng phí phạt trả chậm" khi dự đoán vỡ nợ — nó chỉ tồn tại vì khách đã vỡ nợ).
  • Trùng lặp giữa train và test: cùng một khách xuất hiện ở cả hai (chia theo dòng thay vì theo khách).

Cách phòng chung: chia dữ liệu trước tiên, gói tiền xử lý trong Pipeline, và với mỗi feature luôn hỏi "tại thời điểm dự đoán thật, mình có giá trị này chưa?".

6. Overfitting, underfitting và bias–variance

Hai thất bại đối lập khi huấn luyện:

  • Underfitting (khớp thiếu): mô hình quá đơn giản, không nắm được quy luật — điểm kém cả trên train lẫn test. Đây là bias cao.
  • Overfitting (khớp thừa): mô hình học thuộc lòng cả nhiễu của train — điểm train đẹp mê ly nhưng test tệ. Đây là variance cao.

Dấu hiệu nhận biết rất trực quan: so sánh điểm train và điểm val. Cả hai đều thấp → underfitting. Train cao vượt trội val → overfitting. Cả hai cùng cao và sát nhau → bạn đang đi đúng.

Cân bằng giữa bias và variance là nghệ thuật trung tâm của ML. Bài này chỉ giới thiệu; cách chỉnh cụ thể (regularization, độ phức tạp mô hình, hyperparameter) nằm ở ML 7 — Mất cân bằng & tuning.

Use case thực tế: dự báo churn / vỡ nợ

Gộp mọi thứ lại vào một bài toán ngân hàng quen thuộc — dự đoán khách hàng vỡ nợ khoản vay (default). Cách tư duy giống hệt bài toán churn.

Khung hoá:

  • Đơn vị dự đoán: mỗi khoản vay, đánh giá tại thời điểm giải ngân.
  • Target: nhị phân — khoản vay này có quá hạn > 90 ngày trong 12 tháng tới không? → phân loại.
  • Chỉ số thành công: vì số ca vỡ nợ hiếm (mất cân bằng), dùng AUC-ROC và precision/recall, không dùng accuracy. Bài toán này liên quan trực tiếp tới Credit scorecard.

Chia dữ liệu: dữ liệu vay có yếu tố thời gian rõ rệt — điều kiện kinh tế thay đổi. Train trên các khoản giải ngân 2020–2023, test trên 2024. Không xáo trộn ngẫu nhiên; và nhớ gap vì nhãn cần 12 tháng mới xác định (một khoản giải ngân cuối 2023 chưa biết kết quả tại thời điểm huấn luyện).

Chống leakage: loại bỏ mọi cột chỉ tồn tại sau khi vỡ nợ (số lần nhắc nợ, phí phạt, ngày tất toán sớm). Đây là target leakage kinh điển làm mô hình đạt AUC 0.99 offline rồi vô dụng thật.

Baseline: DummyClassifier đoán "không vỡ nợ" đạt accuracy rất cao (vì đa số trả đúng hạn) nhưng recall = 0 — vô dụng. Baseline này cho thấy ngay tại sao phải nhìn recall chứ không nhìn accuracy, và đặt ra ngưỡng recall/AUC mà mô hình thật phải vượt.

Từ đây, một Pipeline như mục 5 (impute + scale + one-hot + LogisticRegression với class_weight="balanced") là baseline có học rất mạnh để bắt đầu lặp.

Ghi nhớ

  • Khung hoá trước, code sau. Xác định đơn vị dự đoán, target, loại bài toán và chỉ số thành công. Giải sai bài toán là lỗi đắt nhất.
  • Chia dữ liệu ngay từ đầu: train để học, val để chỉnh, test chỉ chạm một lần. Test là trọng tài trung thực.
  • Dữ liệu thời gian: chia theo thời gian, không xáo trộn. Dùng TimeSeriesSplit; cân nhắc gap khi nhãn có độ trễ.
  • Luôn có baseline (đoán đa số/trung bình). Mô hình phải vượt baseline mới đáng xét.
  • Gói tiền xử lý trong Pipeline + ColumnTransformer. fit chỉ trên train, transform cho test — đây là lá chắn số một chống data leakage.
  • Với mỗi feature, hỏi: "tại thời điểm dự đoán thật, mình có giá trị này chưa?" Nếu không → leakage.
  • Đơn giản trước, đo bằng eval, rồi lặp. Cố định random_state, version hoá dữ liệu và code để tái lập được.

Bài tiếp theo đi sâu vào bước tạo đặc trưng — nơi phần lớn hiệu năng được quyết định: ML 2 — Feature engineering.

Bài viết liên quan

Phân biệt AI/ML/DL, các kiểu học máy, quy trình ML end-to-end và thuật ngữ nền tảng.

13 thg 7, 2026 14

Chất lượng dữ liệu, xử lý thiếu/ngoại lai, mã hoá, chuẩn hoá, tạo đặc trưng và tránh data leakage.

13 thg 7, 2026 13

Mô hình ngôn ngữ lớn hoạt động ra sao, token, context window, tham số sinh, và kỹ thuật viết prompt.

13 thg 7, 2026 13

Retrieval-Augmented Generation, vector database, tool calling, agent và MCP để xây ứng dụng LLM thực tế.

13 thg 7, 2026 11