MLOps 2 — Theo dõi thí nghiệm & tái lập

13 thg 7, 2026 6 lượt xem
#ai
#mlops
#mlflow
#reproducibility
#experiment-tracking

MLOps 2 — Theo dõi thí nghiệm & tái lập

bài tổng quan MLOps ta đã phác thảo vòng đời mô hình. Bài này đi sâu vào mắt xích đầu tiên và thường bị xem nhẹ nhất: experiment tracking (theo dõi thí nghiệm) và reproducibility (khả năng tái lập). Đây là nền móng — nếu bạn không tái lập được một mô hình, thì mọi thứ phía sau (registry, deploy, giám sát, giải trình) đều xây trên cát.

Nỗi đau: "không tái lập được"

Hãy hình dung một tình huống rất thật ở đội mô hình tín dụng. Ba tháng trước, một data scientist train một scorecard (mô hình chấm điểm tín dụng) đạt AUC 0.82 trên tập validation. Model đó được đưa vào production. Hôm nay, kiểm toán nội bộ yêu cầu: "Chứng minh lại kết quả 0.82. Train từ đúng dữ liệu, đúng tham số, cho ra đúng con số đó."

Và cả đội tê liệt. Vì:

  • Mất dấu tham số. Notebook đã bị sửa 20 lần. max_depth lúc train là 5 hay 6? learning_rate là 0.05 hay 0.1? Không ai nhớ, cell đã bị ghi đè.
  • Mất dấu dữ liệu. Query lấy dữ liệu là WHERE created_at < '2026-03-01' — nhưng từ đó tới nay có thêm bản ghi được backfill vào quá khứ, có khách hàng cập nhật thông tin. Cùng một câu query hôm nay trả về tập khác.
  • Mất dấu kết quả. Con số 0.82 nằm trong output một cell notebook đã bị clear. Có một file model_final_v2_real.pkl nhưng không rõ nó ứng với lần chạy nào.
  • Không so sánh được các lần chạy. Đội đã thử hàng chục cấu hình, nhưng kết quả rải rác trong screenshot Slack, file Excel, và trí nhớ. Không có cách nào đặt cạnh nhau để nói "cấu hình A tốt hơn B vì..."

Đây không phải chuyện lười. Đây là thiếu hạ tầng. ML khác phần mềm truyền thống ở chỗ kết quả phụ thuộc đồng thời vào code + dữ liệu + tham số + môi trường + tính ngẫu nhiên. Chỉ version code (như dev thường làm với git) là chưa đủ. Một mô hình là hàm của bốn năm thứ, và bạn phải ghim tất cả.

Experiment tracking là gì

Ý tưởng cốt lõi rất đơn giản: mỗi lần bạn chạy huấn luyện (một "run"), hãy tự động ghi lại đầy đủ ngữ cảnh của lần chạy đó vào một nơi tập trung, thay vì để nó bay hơi trong notebook.

Với mỗi run, ta ghi:

NhómVí dụVì sao cần
Params (siêu tham số)max_depth=5, learning_rate=0.05, n_estimators=300, tên feature setĐể biết cấu hình nào tạo ra kết quả
MetricsAUC, KS, Gini, log-loss, accuracy — kể cả theo từng epoch/iterationĐể đo và so sánh chất lượng
Artifactsfile model (.pkl), biểu đồ ROC, ma trận nhầm lẫn, bảng feature importanceĐể lấy lại được sản phẩm của run
Code versiongit commit SHA lúc trainĐể checkout đúng code đã dùng
Dataset versionhash/snapshot của tập train, khoảng thời gian dữ liệuĐể nạp lại đúng dữ liệu
Môi trườngphiên bản Python, thư viện (scikit-learn 1.4.2...), OSĐể loại trừ sai lệch do lib đổi hành vi

Các run được gom vào một experiment — một "dự án con" cho một bài toán, ví dụ experiment credit_scorecard_2026Q2. Trong experiment đó có hàng chục, hàng trăm run. Đây là đơn vị tổ chức giúp bạn không lạc.

Điểm mấu chốt: khi đủ sáu nhóm trên được ghi cho mỗi run, câu hỏi của kiểm toán ("chứng minh lại 0.82") trở thành thao tác vài phút: mở run tương ứng, đọc params, checkout code theo SHA, nạp lại data theo version, dựng lại môi trường, chạy — ra đúng số.

MLflow — công cụ ví dụ chính

MLflow là bộ công cụ mã nguồn mở phổ biến nhất cho việc này. Nó có bốn thành phần:

  • MLflow Tracking — ghi và truy vấn params/metrics/artifacts (trọng tâm bài này).
  • MLflow Projects — đóng gói code train thành dạng chạy lại được (có file mô tả môi trường và entry point).
  • MLflow Models — định dạng đóng gói mô hình chuẩn để deploy nhiều nơi.
  • MLflow Model Registry — kho quản lý phiên bản model đã duyệt (sẽ đào sâu ở bài 5 — Model Registry & Deploy).

Ghi thủ công: log_param / log_metric / log_artifact

API cơ bản xoay quanh khối mlflow.start_run(). Ví dụ minh hoạ (đây là code Python minh hoạ, không phải SQL chạy được):

import mlflow
import mlflow.sklearn
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score

mlflow.set_tracking_uri("http://mlflow.internal.ncb:5000")
mlflow.set_experiment("credit_scorecard_2026Q2")

params = {"max_depth": 5, "learning_rate": 0.05, "n_estimators": 300}

with mlflow.start_run(run_name="gbdt_baseline"):
    # 1) Ghi siêu tham số
    mlflow.log_params(params)
    # ghi thêm ngữ cảnh dữ liệu để tái lập
    mlflow.log_param("data_snapshot", "credit_train@2026-06-01")
    mlflow.log_param("feature_set", "fs_credit_v3")

    model = GradientBoostingClassifier(**params, random_state=42)
    model.fit(X_train, y_train)

    # 2) Ghi metric (có thể ghi theo từng bước với tham số step=)
    proba = model.predict_proba(X_valid)[:, 1]
    auc = roc_auc_score(y_valid, proba)
    mlflow.log_metric("auc_valid", auc)
    mlflow.log_metric("gini_valid", 2 * auc - 1)

    # 3) Ghi artifact: mô hình + biểu đồ
    mlflow.sklearn.log_model(model, artifact_path="model")
    mlflow.log_artifact("roc_curve.png")   # file biểu đồ đã lưu sẵn

Vài lưu ý quan trọng:

  • log_metric nhận tham số step= để ghi metric theo epoch/iteration. Nhờ đó UI vẽ được đường loss/AUC theo thời gian huấn luyện — cực hữu ích để phát hiện overfit.
  • log_artifact ghi bất kỳ file nào (biểu đồ, báo cáo, sample dữ liệu, file cấu hình). log_model là biến thể đặc biệt, đóng gói model theo định dạng MLflow Models kèm cả "flavor" (sklearn, xgboost...) để load lại đúng cách.
  • Ghi luôn data_snapshotfeature_set như param là mẹo thực chiến: nó biến câu hỏi dữ liệu thành một trường tra cứu được, không phải chuyện phải đi hỏi lại người train.

autolog: ghi tự động

Ghi thủ công dễ sót. mlflow.autolog() (hoặc bản riêng cho từng framework như mlflow.sklearn.autolog()) tự động bắt params, metrics và model mà không cần gọi tay từng dòng:

import mlflow
mlflow.sklearn.autolog()   # bật trước khi fit

with mlflow.start_run():
    model.fit(X_train, y_train)   # params, metrics, model được ghi tự động

Thực tế nên kết hợp: dùng autolog() để bắt phần chuẩn, rồi log_param/log_metric bổ sung những thứ đặc thù nghiệp vụ (KS, Gini theo phân khúc khách, ngưỡng cắt điểm...).

Tổ chức, so sánh run và UI

MLflow Tracking đi kèm một UI web. Ở đó, mỗi experiment hiện dưới dạng bảng: mỗi dòng là một run, mỗi cột là một param hoặc metric. Bạn:

  • Lọc/sắp xếp run theo metric (ví dụ sắp theo auc_valid giảm dần để tìm run tốt nhất).
  • Chọn nhiều run và "Compare" — UI vẽ biểu đồ song song, bảng chênh lệch param, giúp trả lời "run A hơn run B nhờ đổi gì".
  • Đào vào một run để tải lại model, xem biểu đồ, đọc git SHA và môi trường.

Đây chính là thứ thay thế cho screenshot Slack và file Excel: một sổ cái duy nhất, tra cứu và so sánh được.

Các công cụ khác (nhắc ngắn)

MLflow không phải lựa chọn duy nhất:

  • Weights & Biases (W&B) — dịch vụ tracking (chủ yếu SaaS) mạnh về trực quan hoá, dashboard cộng tác, và theo dõi huấn luyện deep learning thời gian thực. Trải nghiệm UI/biểu đồ thường được đánh giá cao hơn.
  • DVC (Data Version Control) — không phải công cụ tracking metric, mà là version control cho dữ liệu và model. DVC lưu con trỏ (hash) tới file dữ liệu/model lớn trong git, còn file thật để trên object storage (S3/GCS/MinIO). Nó giải quyết đúng mảnh "dataset version" mà git thuần không làm nổi — và bổ trợ rất tốt cho MLflow.

Trong môi trường ngân hàng nội bộ (on-prem, siết dữ liệu ra ngoài), MLflow self-host + DVC + object storage nội bộ là combo phổ biến vì kiểm soát được toàn bộ dữ liệu trong hạ tầng của mình.

Tái lập (reproducibility) — ghim mọi biến số

Tracking cho bạn biết một run gồm những gì. Reproducibility đảm bảo bạn chạy lại được ra đúng kết quả. Hai việc khác nhau. Muốn tái lập, phải ghim từng nguồn biến thiên:

  1. Pin seed (khử ngẫu nhiên). Đặt random_state/seed cố định cho mọi thứ có yếu tố ngẫu nhiên: chia train/test, khởi tạo model, shuffle, sampling. Không pin seed thì mỗi lần chạy ra số khác nhau dù mọi thứ còn lại giống hệt.
  2. Pin dependency. Ghim phiên bản thư viện qua requirements.txt (pip) hoặc environment.yml (conda) với version chính xác (scikit-learn==1.4.2, không phải scikit-learn). Nhiều lib thay đổi thuật toán mặc định giữa các version — không pin là mở cửa cho sai lệch âm thầm.
  3. Version code. Ghi git commit SHA vào run. Tái lập = checkout đúng SHA đó. Không train từ code chưa commit ("dirty working tree").
  4. Version data. Dữ liệu là biến khó ghim nhất. Dùng DVC hoặc snapshot bất biến (ví dụ mỗi kỳ train ghi ra một bảng/parquet đóng băng, đặt tên theo ngày, không bao giờ sửa). Ghi hash/tên snapshot vào run.
  5. Version config. Tách siêu tham số và cấu hình ra file (config.yaml) và version nó cùng code, thay vì hardcode rải rác trong notebook.
  6. Container hoá. Đóng gói code + dependency + môi trường hệ thống vào một Docker image (lưu ý phải tin cậy CA của proxy công ty nếu build sau tường lửa doanh nghiệp, kẻo pip/apk fail SSL). Container loại bỏ luôn biến "máy tôi chạy được, máy anh thì không" — kể cả version OS và thư viện hệ thống cũng được ghim.

Công thức gọn để nhớ: kết quả có thể tái lập = code (git SHA) + data (DVC/snapshot) + config (yaml) + môi trường (Docker + dependency pin) + seed cố định. Thiếu một mảnh là mất khả năng giải trình.

Vì sao tái lập là chuyện sống còn với ngân hàng

Ở startup, tái lập là "chất lượng kỹ thuật tốt". Ở ngân hàng, nó là yêu cầu pháp lý và quản trị rủi ro.

  • Giải trình với kiểm toán/thanh tra. Cơ quan quản lý (và kiểm toán nội bộ) có quyền yêu cầu chứng minh: mô hình chấm điểm tín dụng này được xây thế nào, trên dữ liệu nào, cho ra kết quả gì. Nếu không tái lập được, ngân hàng không chứng minh được mô hình đúng như đã khai báo — rủi ro tuân thủ nghiêm trọng.
  • Chứng minh kết quả và phòng thủ quyết định. Khi một khách hàng bị từ chối vay khiếu nại, hoặc khi có tranh chấp, ngân hàng phải chỉ ra được chính xác phiên bản mô hình nào đã ra quyết định đó, với tham số và dữ liệu nào.
  • Model risk management. Khung quản trị rủi ro mô hình đòi hỏi mọi mô hình phải có "dấu vết" đầy đủ, được validate độc lập. Validation độc lập chỉ khả thi khi bên thứ hai tái lập lại được kết quả của bên xây mô hình.

Nói cách khác, với một mô hình scorecard tín dụng, experiment tracking không phải tiện ích — nó là bằng chứng. "AUC 0.82" mà không tái lập được thì trước kiểm toán nó bằng không.

Sơ đồ luồng experiment tracking

Vòng lặp này chạy nhiều lần cho tới khi có run đủ tốt. Điểm chuyển tiếp quan trọng: từ experiment sang registry. Một run trong experiment chỉ là "một lần thử". Khi được chọn và duyệt, nó được đăng ký thành một phiên bản model chính thức trong Model Registry — nơi có trạng thái (Staging/Production), quyền phê duyệt, và dấu vết ai duyệt lúc nào. Đó là ranh giới giữa "phòng thí nghiệm" và "hệ thống chịu trách nhiệm", và là nội dung của bài 5. Trước đó, bài 3 — Feature Store sẽ giải quyết mảnh "dataset/feature version" một cách hệ thống hơn.

Use case thực tế

Bối cảnh. Đội mô hình NCB xây lại scorecard tín dụng cá nhân cho quý 2/2026. Bài toán: dự đoán xác suất vỡ nợ (PD) trong 12 tháng, chọn giữa GBDT và logistic regression, tối ưu KS.

Thiết lập tracking.

  • Tạo experiment credit_scorecard_2026Q2 trên MLflow self-host nội bộ.
  • Đóng băng dữ liệu train thành snapshot bất biến credit_train@2026-06-01 (parquet trên MinIO nội bộ, quản bằng DVC). Ghi tên snapshot làm param mỗi run.
  • Bật mlflow.sklearn.autolog() và bổ sung log_metric cho KS, Gini theo 3 phân khúc thu nhập.
  • Mỗi run ghi git SHA và requirements.txt đã pin; train trong Docker image chuẩn của đội.

Kết quả 4 run tiêu biểu:

RunModelTham số chínhAUC validKS
run_01Logistic30 feature, no reg0.760.34
run_02GBDTdepth=4, lr=0.1, n=2000.800.41
run_03GBDTdepth=5, lr=0.05, n=3000.820.44
run_04GBDTdepth=8, lr=0.05, n=5000.830.43

Dùng chức năng Compare trên UI, đội thấy run_04 tuy AUC cao hơn nhưng đường loss theo step cho thấy overfit (khoảng cách train/valid nới rộng) và KS lại thấp hơn run_03. Chọn run_03 làm ứng viên production.

Ba tháng sau, kiểm toán yêu cầu chứng minh. Thay vì tê liệt như kịch bản đầu bài, đội mở run_03: đọc data_snapshot=credit_train@2026-06-01, checkout git SHA đã ghi, dựng lại Docker image từ requirements.txt pin sẵn, chạy lại với random_state=42. Ra đúng AUC 0.82, KS 0.44. Toàn bộ mất khoảng 15 phút, và biên bản giải trình đính kèm link run trên MLflow. Kiểm toán chấp nhận vì kết quả tái lập được từng con số.

Chi phí bỏ ra: vài giờ dựng hạ tầng tracking một lần. Giá trị nhận lại: khả năng đứng vững trước thanh tra, và loại bỏ hoàn toàn hạng mục "mô hình không giải trình được" khỏi báo cáo rủi ro.

Ghi nhớ

  • ML "không tái lập được" vì kết quả phụ thuộc đồng thời vào code + data + params + môi trường + tính ngẫu nhiên — version mỗi code là chưa đủ.
  • Experiment tracking: với mỗi run, ghi 6 nhóm — params, metrics (kể cả theo epoch), artifacts, code version (git SHA), dataset version, môi trường — vào một nơi tập trung.
  • MLflow có 4 phần: Tracking / Projects / Models / Registry. API cốt lõi: log_param, log_metric (có step=), log_artifact, và autolog() để ghi tự động. Kết hợp autolog + log thủ công cho metric nghiệp vụ.
  • Run gom vào experiment; UI cho phép sort + compare để chọn run tốt nhất thay vì screenshot Slack.
  • W&B mạnh về trực quan hoá; DVC giải quyết version dữ liệu/model — bổ trợ MLflow chứ không thay thế.
  • Reproducibility = pin seed + pin dependency (requirements/conda) + version code (git SHA) + version data (DVC/snapshot) + version config + container hoá (Docker).
  • Với ngân hàng, tái lập là yêu cầu tuân thủ: giải trình mô hình với kiểm toán/thanh tra, phòng thủ quyết định tín dụng, và cho phép validation độc lập.
  • Ranh giới quan trọng: từ experiment sang Model Registry — một run được chọn và duyệt mới trở thành phiên bản model chính thức (xem bài 5).

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 15

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