ML 8 — Triển khai & MLOps

13 thg 7, 2026 6 lượt xem
#machine-learning
#ai
#drift
#mlops
#deployment
#feature-store

ML 8 — Triển khai & MLOps

Đây là bài khép lại series Machine Learning thực chiến. Bảy bài trước dạy bạn cách xây một mô hình tốt: khung hoá bài toán và tránh rò rỉ dữ liệu, làm feature, chọn thuật toán, đánh giá bằng đúng chỉ số. Nhưng có một sự thật khó nghe: train xong không phải là hết — thực ra mới chỉ là đầu.

Một mô hình nằm trong notebook, dù AUC đẹp đến đâu, cũng tạo ra đúng không đồng giá trị. Mô hình chỉ sinh giá trị khi nó phục vụ dự đoán cho một hệ thống thật, và tiếp tục sinh giá trị chỉ khi nó được giám sát để không âm thầm hỏng đi. Có một câu quen thuộc trong ngành: phần lớn dự án ML không chết vì thuật toán kém, chúng chết ở khâu đưa vào production và duy trì. Notebook chạy được một lần; production đòi hỏi mô hình chạy được mỗi ngày, với dữ liệu chưa từng thấy, trong khi thế giới bên ngoài liên tục thay đổi.

MLOps (Machine Learning Operations) là tập các thực hành để làm việc đó: đóng gói, triển khai, versioning, giám sát, và tự động hoá vòng đời mô hình. Nó là con lai giữa kỹ thuật ML và DevOps — và với ngân hàng/tín dụng, còn cộng thêm một lớp quản trị rủi ro mô hình khắt khe. Bài này là bản đồ thực chiến cho toàn bộ khâu đó.

1. Vòng đời MLOps

Trước khi đi vào chi tiết, hãy nhìn bức tranh toàn cảnh. MLOps không phải quy trình một chiều "train rồi deploy" mà là một vòng lặp khép kín: mô hình được huấn luyện, đăng ký, triển khai, giám sát, và khi phát hiện suy giảm thì quay lại huấn luyện phiên bản mới.

Ba vòng phản hồi đáng chú ý: train → registry → deploy là đường phát hành; monitor → retrain là đường tự chữa lành; và champion/challenger là cơ chế thay mô hình cũ bằng mô hình mới một cách an toàn. Cả bài này sẽ đi lần lượt qua từng mắt xích.

2. Đóng gói & serving

Serialize mô hình

Một mô hình đã train là một đối tượng trong bộ nhớ; để dùng lại ở nơi khác, bạn phải serialize nó ra file.

  • joblib / pickle: cách phổ biến nhất trong hệ sinh thái scikit-learn. joblib được ưu tiên vì nén tốt với các mảng NumPy lớn. Nhược điểm: file gắn chặt với phiên bản thư viện (Python, scikit-learn) — load bằng phiên bản khác có thể vỡ, nên phải ghim version môi trường.
  • ONNX (Open Neural Network Exchange): định dạng trung lập, tách mô hình khỏi framework. Xuất mô hình sang ONNX rồi chạy bằng runtime tối ưu (ONNX Runtime) — nhanh hơn, độc lập ngôn ngữ (có thể phục vụ bằng C++/Java), phù hợp khi cần độ trễ thấp hoặc triển khai đa nền tảng.

Đóng gói cả pipeline, không chỉ model

Đây là lỗi kinh điển khiến mô hình sập lặng lẽ trên production. Lúc train, bạn làm rất nhiều tiền xử lý: điền giá trị thiếu, chuẩn hoá số, one-hot encode. Nếu bạn chỉ lưu bước cuối (mô hình) mà quên phần preprocessing, thì lúc serving phải viết lại logic tiền xử lý bằng tay — và chỉ cần lệch một chút (scale khác, thứ tự cột khác, giá trị mean điền khác) là bạn có training/serving skew: mô hình nhận đầu vào không giống lúc train, dự đoán sai mà không báo lỗi.

Cách chống: đóng gói toàn bộ thành một Pipeline scikit-learn duy nhất (nối lại kỹ thuật ở ML 1). Preprocessing và model đi chung trong một artifact, fit cùng nhau, lưu cùng nhau, load cùng nhau.

# (minh hoạ) đóng gói cả preprocessing + model thành 1 artifact
import joblib
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.impute import SimpleImputer
from sklearn.ensemble import GradientBoostingClassifier

num_cols = ["thu_nhap", "du_no", "so_the"]
cat_cols = ["nhom_no", "khu_vuc"]

pre = ColumnTransformer([
    ("num", Pipeline([("imp", SimpleImputer(strategy="median")),
                      ("sc",  StandardScaler())]), num_cols),
    ("cat", OneHotEncoder(handle_unknown="ignore"), cat_cols),
])

pipe = Pipeline([("pre", pre),
                 ("clf", GradientBoostingClassifier(random_state=42))])
pipe.fit(X_train, y_train)

# Lưu 1 file duy nhất — chứa cả cách biến đổi lẫn mô hình
joblib.dump(pipe, "credit_scoring_v3.joblib")

# Khi serving: load đúng artifact đó, đầu vào là dữ liệu THÔ
model = joblib.load("credit_scoring_v3.joblib")
proba = model.predict_proba(X_raw_moi)[:, 1]   # không cần tiền xử lý tay

Batch scoring vs real-time API

Hai mô hình phục vụ, chọn theo nhu cầu nghiệp vụ:

  • Batch scoring: chấm điểm cả một lô dữ liệu theo lịch (ví dụ mỗi đêm chấm lại điểm rủi ro toàn bộ khách hàng, ghi kết quả vào bảng). Đơn giản, chịu tải tốt, rẻ. Phù hợp khi không cần kết quả tức thì — báo cáo, danh sách nhắc nợ, phân khúc marketing. Thường điều phối bằng Airflow: một DAG kéo dữ liệu → load model → predict → ghi kết quả.
  • Real-time API (REST): bọc mô hình sau một endpoint HTTP; hệ thống gửi một hồ sơ, nhận về điểm số trong vài chục mili-giây. Phù hợp khi cần quyết định ngay tại thời điểm giao dịch — duyệt khoản vay tại quầy, chặn giao dịch gian lận. Phức tạp hơn: phải lo độ trễ (P95/P99), khả năng mở rộng, độ sẵn sàng.
# (minh hoạ) real-time API tối giản với FastAPI
from fastapi import FastAPI
import joblib, pandas as pd

app = FastAPI()
model = joblib.load("credit_scoring_v3.joblib")

@app.post("/score")
def score(record: dict):
    df = pd.DataFrame([record])          # 1 hồ sơ thô
    p = float(model.predict_proba(df)[0, 1])
    return {"pd": p, "decision": "reject" if p > 0.5 else "approve",
            "model_version": "v3"}

Container hoá

Dù batch hay real-time, cách chuẩn để đóng gói môi trường chạy là container. Bọc mô hình + thư viện + phiên bản Python vào một Docker image; image đó chạy giống hệt trên máy dev, staging và prod — hết cảnh "trên máy tôi chạy được". Khi cần mở rộng nhiều bản sao và tự phục hồi, điều phối bằng Kubernetes. (Nối lại nền tảng Docker/K8s trong series DevOps.)

3. Versioning & khả năng tái lập

Trong phần mềm thường, bạn chỉ cần version code. Trong ML, một dự đoán được quyết định bởi ba thứ, và cả ba đều phải version được:

  1. Code — Git, như mọi dự án.
  2. Dữ liệu — cùng code nhưng train trên dữ liệu khác cho mô hình khác. Dùng DVC (Data Version Control) để version dataset lớn: DVC lưu con trỏ nhẹ trong Git còn dữ liệu thật nằm ở kho ngoài, cho phép "checkout" đúng phiên bản dữ liệu đã dùng.
  3. Model — artifact đã train, kèm metric, tham số, và dữ liệu/code đã sinh ra nó.

Experiment tracking với MLflow giải quyết việc này. Mỗi lần train là một run được ghi lại: tham số, chỉ số, artifact model, và môi trường. Bạn không còn phải nhớ "mô hình đẹp hôm qua dùng learning rate bao nhiêu" — nó nằm trong log.

# (minh hoạ) MLflow: track experiment + đăng ký model
import mlflow, mlflow.sklearn
from sklearn.metrics import roc_auc_score

mlflow.set_experiment("credit_scoring")
with mlflow.start_run(run_name="gbc_v3"):
    pipe.fit(X_train, y_train)
    auc = roc_auc_score(y_val, pipe.predict_proba(X_val)[:, 1])

    mlflow.log_param("model", "GradientBoosting")
    mlflow.log_param("data_version", "2026-06-snapshot")   # nối DVC
    mlflow.log_metric("val_auc", auc)

    # Đăng ký vào Model Registry với tên nghiệp vụ
    mlflow.sklearn.log_model(pipe, name="model",
                             registered_model_name="credit_scoring")

MLflow Model Registry là nơi quản lý vòng đời từng phiên bản mô hình. Lưu ý: hệ "stages" cũ (Staging/Production/Archive) đã bị deprecated từ MLflow 2.9; cách khuyến nghị hiện nay là dùng alias (ví dụ @champion, @challenger) và tag gắn vào từng version. Hạ tầng serving trỏ vào models:/credit_scoring@champion; khi muốn thay mô hình, bạn chỉ gán lại alias sang version mới — không phải sửa code serving, và rollback chỉ là gán alias về version cũ.

4. CI/CD cho ML (MLOps)

CI/CD phần mềm quen thuộc: push code → chạy test → build → deploy. CI/CD cho ML kế thừa ý tưởng đó nhưng có một khác biệt cốt lõi: bạn phải test cả mô hình, không chỉ code.

Một pipeline train → test → deploy tự động điển hình:

  1. Trigger (commit code, dữ liệu mới, hoặc theo lịch).
  2. Train lại trên dữ liệu mới nhất, log vào MLflow.
  3. Test mô hình — không phải unit test hàm, mà là các validation gate: AUC trên tập test có vượt ngưỡng tối thiểu không? Có tốt hơn champion đang chạy không? Fairness có đạt không? Độ trễ dự đoán có nằm trong giới hạn không?
  4. Nếu qua tất cả gate → đẩy lên staging, kiểm thử tích hợp.
  5. Qua staging → promote lên prod (gán alias @champion).

Điểm hay: khi mọi lần chuyển version đều chạy đúng bộ kiểm tra tự động này, bạn có governance mặc định thay vì governance tuỳ tâm trạng. Việc điều phối các bước train/score theo lịch thường giao cho Airflow (nối lại series data engineering).

5. Giám sát

Đây là mắt xích bị bỏ quên nhiều nhất — và là nơi giá trị bị âm thầm rò rỉ. Mô hình không "báo lỗi 500" khi hỏng; nó vẫn trả về những con số trông rất bình thường, chỉ là sai dần. Có ba lớp giám sát:

Lớp 1 — Hiệu năng dự đoán

Đo trực tiếp AUC/precision/recall trên dữ liệu production. Vấn đề: nhãn thường đến trễ. Một khoản vay chấm hôm nay phải vài tháng sau mới biết có vỡ nợ không. Trong khoảng trống đó, bạn không thể tính accuracy — nên phải giám sát đầu vào thay vì đợi kết quả.

Lớp 2 — Data drift (dịch chuyển dữ liệu)

Phân phối đầu vào thay đổi so với lúc train (còn gọi là covariate shift). Ví dụ: lúc train thu nhập trung bình khách là 15 triệu, sáu tháng sau là 25 triệu — mô hình đang thấy dữ liệu nó chưa từng học. Hai công cụ chuẩn:

  • PSI (Population Stability Index): so phân phối một biến giữa tập tham chiếu (train) và tập hiện tại, bằng cách chia thành các bin. Rất phổ biến trong tín dụng (nối lại credit monitoring).
  • KS test (Kolmogorov–Smirnov): so sánh phân phối tích luỹ, hợp cho biến liên tục.
(minh hoạ) Quy tắc đọc PSI  —  PSI = Σ (a_i − e_i) × ln(a_i / e_i)
  a_i = tỉ lệ quan sát ở bin i (actual, dữ liệu hiện tại)
  e_i = tỉ lệ kỳ vọng ở bin i (expected, dữ liệu lúc train)

  PSI < 0.10   → ổn định, không dịch chuyển đáng kể  → không hành động
  0.10 – 0.20  → dịch chuyển vừa                      → theo dõi sát, cảnh báo
  PSI > 0.20   → dịch chuyển lớn                       → điều tra + cân nhắc retrain

KS: so CDF hai mẫu; p-value < 0.05 → khác phân phối có ý nghĩa thống kê.
Lưu ý: ngưỡng chỉ là quy ước công nghiệp — hiệu chỉnh theo rủi ro nghiệp vụ.

Lớp 3 — Concept drift (dịch chuyển quan hệ)

Nguy hiểm và tinh vi hơn: quan hệ X → y thay đổi, dù phân phối X có thể không đổi. Nghĩa là cùng một hồ sơ khách hàng, nhưng "thế nào là rủi ro" đã khác — do khủng hoảng kinh tế, đổi chính sách, đổi hành vi. PSI/KS trên đầu vào có thể không bắt được loại này; phải phát hiện qua suy giảm hiệu năng khi nhãn trễ về, hoặc qua proxy metric.

Tất cả ba lớp cần alerting: vượt ngưỡng thì tự động cảnh báo (Slack/email/dashboard). Nhưng lưu ý quan trọng: không phải mọi tín hiệu drift đều đòi hành động — hãy xác nhận drift thật sự làm giảm chất lượng dự đoán trước khi tốn công retrain.

6. Retraining

Khi nào train lại? Ba chiến lược, thường kết hợp:

  • Theo lịch: train lại định kỳ (hằng tháng/quý). Đơn giản, dễ dự đoán, nhưng có thể train thừa (dữ liệu chưa đổi) hoặc train thiếu (đổi giữa hai kỳ).
  • Theo trigger drift: PSI/KS/metric vượt ngưỡng thì kích hoạt retrain. Phản ứng đúng lúc hơn, nhưng cần hạ tầng giám sát tốt.
  • Theo suy giảm hiệu năng: khi nhãn về và thấy metric tụt dưới ngưỡng.

Có mô hình mới rồi, đừng bao giờ thay thẳng vào prod. Ba mẫu triển khai an toàn:

  • Champion / challenger: mô hình đang chạy là champion; mô hình mới là challenger chạy song song, so kết quả trên cùng dữ liệu. Chỉ khi challenger thắng có ý nghĩa thống kê mới hoán đổi alias.
  • Shadow deployment: challenger nhận y hệt traffic thật và tính dự đoán, nhưng kết quả không dùng để ra quyết định — chỉ để ghi log so sánh. An toàn tuyệt đối vì không ảnh hưởng khách hàng.
  • A/B test: chia lưu lượng (ví dụ 90% champion, 10% challenger) và đo tác động nghiệp vụ thật (tỉ lệ vỡ nợ, tỉ lệ duyệt) giữa hai nhánh.

Xu hướng chuẩn hiện nay là triển khai tiệm tiến (progressive delivery): shadow → challenger có ngưỡng ý nghĩa → promote toàn phần khi qua gate → tự động rollback nếu gate fail. Triết lý: làm cho sự thất bại có thể phục hồi được.

7. Feature store

Nhớ lại training/serving skew ở mục 2? Ở quy mô nhiều mô hình, vấn đề còn lớn hơn: cùng một feature (ví dụ "tổng dư nợ 90 ngày qua") có thể được tính theo hai code khác nhau ở lúc train và lúc serving — và lệch nhau.

Feature store là hệ thống tập trung để định nghĩa, tính, lưu và chia sẻ feature, đảm bảo cùng một định nghĩa dùng cho cả train lẫn serving. Nó có hai mặt:

  • Offline store: dữ liệu lịch sử lớn, dùng để train và batch scoring (thường trên kho dữ liệu/data lake).
  • Online store: phiên bản độ trễ thấp của cùng feature đó, dùng cho real-time API (thường trên key-value store nhanh) — trả về feature cho một khách trong vài mili-giây.

Lợi ích: nhất quán train/serving, tái sử dụng feature giữa các nhóm, và một nguồn sự thật duy nhất cho feature. Không phải dự án nào cũng cần feature store; nó đáng đầu tư khi bạn có nhiều mô hình dùng chung feature.

8. Governance mô hình

Với ngân hàng và tín dụng, đây không phải tuỳ chọn — cơ quan quản lý sẽ soi. Model Risk Management (quản trị rủi ro mô hình) bao gồm:

  • Tài liệu & lineage: với mỗi dự đoán, phải tái dựng được chính xác nó đã dùng version dữ liệu nào, giá trị feature nào, version model nào tại thời điểm đó. Đây là điều regulator kỳ vọng và cũng là thứ cứu bạn khi điều tra sự cố.
  • Giải thích được (explainability): vì sao mô hình từ chối khoản vay này? Với mô hình cây/ensemble, SHAP cho phép quy đóng góp từng feature vào từng dự đoán (nối lại ML 4 — Trees & SHAP).
  • Fairness / bias: kiểm tra mô hình không phân biệt đối xử theo nhóm nhạy cảm (giới, vùng miền), một cách hệ thống và có tài liệu.
  • Audit trail: ai duyệt version nào, khi nào, dựa trên kết quả kiểm thử gì.

Tin vui: nếu bạn đã làm tốt mục 3 (versioning) và mục 4 (CI/CD với validation gate), phần lớn governance đã có sẵn — vì mỗi lần chuyển version đều để lại dấu vết kiểm tra tự động.

9. So với vận hành LLM

Nhiều nguyên tắc MLOps chuyển thẳng sang vận hành LLM: versioning, giám sát, đánh giá, triển khai an toàn. Nhưng có khác biệt cốt lõi:

  • ML cổ điển: bạn train lại mô hình trên dữ liệu của mình; "phiên bản" là artifact model; drift đo bằng PSI/KS trên feature.
  • LLM: thường bạn không train lại mô hình nền; bạn version prompt, ngữ cảnh, và tài liệu RAG. Giám sát tập trung vào chất lượng đầu ra, chi phí token, độ trễ, và các lỗi như ảo giác (hallucination) — thay vì drift phân phối feature dạng bảng.

Điểm chung: cả hai đều là hệ thống sống, cần giám sát và có vòng lặp cải tiến. Điểm khác: đòn bẩy điều chỉnh khác nhau (retrain vs prompt/context).

Use case thực tế — Đưa mô hình scoring tín dụng lên production

Đặt cạnh nhau toàn bộ bài học:

  1. Đóng gói: pipeline scikit-learn (impute → scale → encode → GradientBoosting) dump bằng joblib thành credit_scoring_v3.joblib, ghim version thư viện.
  2. Đăng ký: log run vào MLflow (AUC, tham số, data_version), đăng ký vào registry tên credit_scoring, gán alias @champion cho version qua validation gate.
  3. Triển khai: duyệt vay tại quầy cần tức thì → real-time API (FastAPI trong container, trỏ models:/credit_scoring@champion); song song, mỗi đêm batch score lại toàn bộ danh mục bằng một DAG Airflow để cập nhật cảnh báo sớm.
  4. Giám sát: mỗi tháng tính PSI cho các biến chính (thu nhập, dư nợ, nhóm nợ) so với tập train tham chiếu. Biến "thu nhập" nhảy lên PSI = 0.27 → vượt 0.20 → cảnh báo.
  5. Điều tra & retrain: xác nhận drift làm giảm chất lượng (nhóm nợ thực tế lệch dự đoán). Kích hoạt pipeline retrain trên dữ liệu 6 tháng gần nhất, sinh version 4 làm challenger.
  6. Thay an toàn: chạy challenger ở shadow mode hai tuần, so log; challenger thắng có ý nghĩa → gán @champion sang v4. Rollback = gán alias về v3, không sửa code.
  7. Governance: SHAP giải thích từng quyết định từ chối; toàn bộ lineage (dữ liệu + model + kết quả gate) lưu lại cho audit.

Vòng lặp khép kín đúng như sơ đồ mục 1 — và đó là điểm khác biệt giữa "một mô hình đẹp trong notebook" và "một tài sản đang sinh giá trị trong ngân hàng".

Ghi nhớ

  • Train xong mới là bắt đầu. Giá trị chỉ có khi mô hình phục vụ được giám sát; phần lớn dự án ML chết ở khâu production.
  • Đóng gói cả pipeline, không chỉ model — chống training/serving skew. Serialize bằng joblib/pickle hoặc ONNX; đóng gói bằng container.
  • Chọn kiểu serving theo nghiệp vụ: batch cho việc không cần tức thì, real-time API cho quyết định tại chỗ.
  • Version cả ba: code (Git) + dữ liệu (DVC) + model (MLflow registry). Track experiment để tái lập được.
  • Giám sát ba lớp: hiệu năng (nhãn trễ), data drift (PSI/KS), concept drift (quan hệ X→y). PSI > 0.2 là mốc cảnh báo quen thuộc — nhưng chỉ là quy ước, hãy xác nhận drift thật sự gây hại trước khi retrain.
  • Thay mô hình an toàn: champion/challenger, shadow deployment, A/B; triển khai tiệm tiến để thất bại có thể phục hồi.
  • Feature store cho nhất quán feature train/serving khi có nhiều mô hình; online vs offline.
  • Governance là bắt buộc với tín dụng/ngân hàng: lineage, giải thích (SHAP), fairness, audit — làm tốt versioning + CI/CD thì governance có sẵn.
  • LLMOps dùng chung nhiều nguyên tắc nhưng khác đòn bẩy: version prompt/context thay vì train lại.

Bài trước trong series: ML 5 — Đánh giá mô hình · ML 4 — Trees & Ensembles. Liên quan: AI 8 — MLOps & Ethics (tổng quan) · LLM 7 — Đưa LLM ra production · Credit 9 — Giám sát & EWS · Airflow 1 — Tổng quan.

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