Huấn luyện được một mô hình có accuracy cao mới chỉ là một nửa công việc. Nửa còn lại — thường khó hơn nhiều — là đưa mô hình đó vào production một cách ổn định, có thể tái lập, giám sát được và cập nhật liên tục. MLOps (Machine Learning Operations) là tập hợp các nguyên tắc và công cụ để làm việc đó. Bài này đi từ vòng đời ML end-to-end, cách lưu & version mô hình, các kiểu phục vụ, đóng gói REST API bằng FastAPI, experiment tracking với MLflow, feature store, CI/CD cho ML và giám sát drift trong production.
MLOps là sự giao thoa giữa Machine Learning, DevOps và Data Engineering: áp dụng các thực hành kỹ thuật phần mềm (versioning, automation, testing, CI/CD, monitoring) vào toàn bộ vòng đời của mô hình học máy. Mục tiêu là biến việc đưa mô hình ra production từ một sự kiện thủ công, mong manh, "chỉ chạy trên máy của tôi" thành một quy trình lặp lại được, tự động và đáng tin cậy.
Một con số hay được trích dẫn trong ngành: khoảng 87% dự án ML không bao giờ lên được production (theo các khảo sát của VentureBeat, Gartner...). Lý do hầu như không phải vì thuật toán kém, mà vì các vấn đề kỹ thuật vận hành:
Vòng đời ML end-to-end (ML lifecycle) là một vòng lặp liên tục chứ không phải đường thẳng:
| Giai đoạn | Nội dung |
|---|---|
| 1. Business & data understanding | Xác định bài toán, metric thành công, thu thập & khám phá dữ liệu (EDA). |
| 2. Data preparation | Làm sạch, feature engineering, tách train/val/test, versioning dữ liệu. |
| 3. Model development | Thử nghiệm thuật toán, tuning, tracking experiment (params/metrics/artifacts). |
| 4. Evaluation | Đánh giá offline trên test set, so sánh với baseline, kiểm tra fairness/robustness. |
| 5. Deployment | Đóng gói, chọn kiểu phục vụ (batch/online/edge), release có kiểm soát (canary/shadow). |
| 6. Monitoring | Theo dõi data drift, concept drift, performance, latency; cảnh báo & quyết định retrain. |
Sau khi train xong, mô hình (bao gồm các tham số đã học) cần được serialize ra file để tải lại lúc phục vụ. Các lựa chọn phổ biến trong hệ sinh thái Python:
| Định dạng | Dùng khi nào | Ưu / nhược |
|---|---|---|
| pickle | Serialize bất kỳ object Python nào, mặc định của Python. | Tiện, nhưng gắn chặt với phiên bản Python/thư viện; không an toàn khi load file từ nguồn không tin cậy (có thể chạy mã tuỳ ý). |
| joblib | Chuẩn de-facto cho mô hình scikit-learn (nhiều mảng NumPy lớn). | Nhanh & nén tốt hơn pickle với numpy array; vẫn cùng hạn chế về tương thích phiên bản. |
| ONNX | Cần chạy mô hình ở môi trường/ngôn ngữ khác (C++, Java, mobile), tối ưu tốc độ inference. | Định dạng trung lập giữa các framework; runtime tối ưu (ONNX Runtime); tách khỏi Python. |
| Framework-native | TensorFlow SavedModel, PyTorch state_dict/TorchScript cho deep learning. | Giữ đầy đủ đồ thị tính toán; hợp với serving chuyên dụng (TF Serving, TorchServe). |
# Lưu & nạp mô hình scikit-learn bằng joblib import joblib joblib.dump(model, "model_v3.joblib") # lưu model = joblib.load("model_v3.joblib") # nạp lại # Xuất sang ONNX (từ sklearn) from skl2onnx import to_onnx onx = to_onnx(model, X_train[:1].astype("float32")) with open("model.onnx", "wb") as f: f.write(onx.SerializeToString())
Model Registry là kho tập trung để quản lý các mô hình đã đăng ký: mỗi mô hình có nhiều version, mỗi version gắn stage (Staging, Production, Archived), kèm metadata (ai train, metric, data version, commit code). Nó cho phép promote/rollback mô hình mà không đổi code phía serving — service chỉ trỏ tới "mô hình ở stage Production". MLflow, SageMaker và Vertex AI đều có registry.
Không phải mô hình nào cũng cần một API real-time. Chọn kiểu phục vụ theo yêu cầu độ trễ và cách tiêu thụ dự đoán:
| Kiểu | Cách hoạt động | Độ trễ | Hợp cho |
|---|---|---|---|
| Batch prediction | Chạy định kỳ (cron/Airflow) trên một khối dữ liệu lớn, ghi kết quả vào DB/warehouse để dùng sau. | Phút → giờ | Chấm điểm churn hằng đêm, gợi ý email hằng ngày, scoring danh sách khách hàng. |
| Online / Real-time API | Service (REST/gRPC) nhận request và trả dự đoán ngay lập tức, đồng bộ. | Mili-giây | Duyệt gian lận lúc thanh toán, gợi ý khi user đang lướt, chấm điểm tín dụng tức thời. |
| Streaming | Tiêu thụ sự kiện liên tục từ Kafka/Kinesis, dự đoán theo luồng và phát kết quả ra topic khác. | Cận real-time | Phát hiện bất thường IoT, giám sát giao dịch liên tục, cập nhật feature real-time. |
| Edge / On-device | Mô hình chạy ngay trên thiết bị (mobile, camera, IoT) không cần gọi server. | Cực thấp, offline được | Nhận diện khuôn mặt trên điện thoại, xe tự lái, khi mạng không ổn định/riêng tư. |
FastAPI là framework Python phổ biến nhất để bọc mô hình thành REST API online: hiệu năng cao (dựa trên ASGI/uvicorn), validate input tự động qua Pydantic, và sinh docs OpenAPI/Swagger sẵn tại /docs. Mẫu chuẩn: nạp mô hình một lần lúc khởi động (không nạp trong mỗi request), rồi định nghĩa endpoint /predict:
# app.py from fastapi import FastAPI from pydantic import BaseModel, Field import joblib, numpy as np # Nạp mô hình MỘT LẦN khi service khởi động model = joblib.load("model_v3.joblib") app = FastAPI(title="Churn Predictor", version="3.0") # Schema input: Pydantic validate kiểu & ràng buộc tự động class Customer(BaseModel): tenure: int = Field(..., ge=0, description="Số tháng gắn bó") monthly_charges: float = Field(..., ge=0) total_charges: float contract_type: int # đã encode 0/1/2 class Prediction(BaseModel): churn: bool probability: float @app.get("/health") def health(): return {"status": "ok"} @app.post("/predict", response_model=Prediction) def predict(c: Customer): x = np.array([[c.tenure, c.monthly_charges, c.total_charges, c.contract_type]]) prob = float(model.predict_proba(x)[0, 1]) return Prediction(churn=prob > 0.5, probability=round(prob, 4))
Chạy local: uvicorn app:app --host 0.0.0.0 --port 8000. Test nhanh:
curl -X POST http://localhost:8000/predict \ -H "Content-Type: application/json" \ -d '{"tenure":5,"monthly_charges":80.5,"total_charges":402.5,"contract_type":0}' # => {"churn": true, "probability": 0.7213}
Docker hoá để đảm bảo môi trường đồng nhất giữa dev và production:
# Dockerfile FROM python:3.11-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt COPY app.py model_v3.joblib ./ EXPOSE 8000 CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
docker build -t churn-api:3.0 . docker run -p 8000:8000 churn-api:3.0
/health cho readiness probe của Kubernetes; chạy nhiều worker (--workers 4) hoặc dùng Gunicorn quản uvicorn; log mỗi request & dự đoán để phục vụ monitoring; giới hạn kích thước input để tránh abuse.Khi thử hàng chục cấu hình mô hình, bạn cần ghi lại một cách có hệ thống: tham số nào cho metric nào, artifact (mô hình, biểu đồ) ở đâu. MLflow là công cụ mã nguồn mở phổ biến nhất, gồm bốn thành phần: Tracking (log params/metrics/artifacts), Projects (đóng gói code tái lập), Models (định dạng chuẩn để deploy), và Model Registry (quản lý version + stage).
import mlflow, mlflow.sklearn from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import f1_score, roc_auc_score mlflow.set_experiment("churn-prediction") with mlflow.start_run(run_name="rf-baseline"): params = {"n_estimators": 300, "max_depth": 12} model = RandomForestClassifier(**params).fit(X_train, y_train) proba = model.predict_proba(X_test)[:, 1] pred = (proba > 0.5) # 1) Log tham số mlflow.log_params(params) # 2) Log metric mlflow.log_metric("f1", f1_score(y_test, pred)) mlflow.log_metric("auc", roc_auc_score(y_test, proba)) # 3) Log artifact + mô hình, đăng ký vào Registry mlflow.log_artifact("feature_importance.png") mlflow.sklearn.log_model(model, "model", registered_model_name="churn-rf")
Mở UI bằng mlflow ui để so sánh các run cạnh nhau. Sau đó có thể promote một version lên Production trong Registry và service serving load qua URI models:/churn-rf/Production.
Versioning dữ liệu với DVC: Git version code rất tốt nhưng không hợp để lưu file dữ liệu lớn. DVC (Data Version Control) lưu con trỏ nhỏ (hash) trong Git còn dữ liệu thật ở remote storage (S3, GCS), cho phép git checkout một commit rồi dvc checkout để lấy đúng phiên bản dữ liệu tương ứng — chìa khoá của reproducibility.
dvc init dvc add data/train.csv # tạo train.csv.dvc (con trỏ), thêm vào .gitignore git add data/train.csv.dvc .gitignore dvc remote add -d storage s3://my-bucket/dvcstore dvc push # đẩy dữ liệu lên remote
Trong tổ chức lớn, cùng một feature (vd "tổng chi tiêu 30 ngày của khách hàng") được nhiều mô hình dùng chung, và phải nhất quán giữa lúc train và lúc serve. Feature Store (Feast, Tecton, Databricks Feature Store...) là hệ thống trung tâm để định nghĩa, tính toán, lưu trữ và phục vụ feature một cách chuẩn hoá. Nó giải quyết đúng vấn đề training-serving skew.
| Thành phần | Vai trò |
|---|---|
| Offline store | Lưu lịch sử feature (warehouse/parquet) để tạo training set và tính "point-in-time correct" — tránh data leakage từ tương lai. |
| Online store | Kho low-latency (Redis, DynamoDB) chứa giá trị feature mới nhất để serving real-time đọc trong vài mili-giây. |
| Feature registry | Danh mục định nghĩa feature (tên, kiểu, nguồn, cách tính) để tái sử dụng & khám phá giữa các team. |
| Materialization | Quá trình đồng bộ feature đã tính từ offline sang online store theo lịch. |
CI/CD trong ML phức tạp hơn phần mềm thông thường vì có thêm hai "tài sản" thay đổi: dữ liệu và mô hình, không chỉ code. Người ta hay chia thành ba đường ống:
Các loại test đặc thù cho ML (ngoài unit test code thông thường):
| Loại test | Kiểm tra gì |
|---|---|
| Data validation | Schema, kiểu dữ liệu, khoảng giá trị, tỉ lệ null, số lượng category (dùng Great Expectations, pandera). |
| Model quality gate | Metric trên test set phải ≥ ngưỡng và không tệ hơn mô hình production hiện tại. |
| Behavioral test | Invariance (đổi feature không liên quan thì dự đoán không đổi), directional (tăng thu nhập thì xác suất duyệt vay tăng). |
| Serving test | API trả đúng schema, latency dưới ngưỡng, load test, kiểm tra parity giữa batch & online. |
Không giống phần mềm thông thường (đúng thì cứ đúng mãi), mô hình ML xuống cấp theo thời gian vì thế giới thay đổi trong khi mô hình đứng yên. Có hai loại "trôi" cần phân biệt:
| Hiện tượng | Định nghĩa | Ví dụ |
|---|---|---|
| Data drift (covariate shift) | Phân phối input P(X) thay đổi, quan hệ X→y vẫn giữ. | Nhóm khách hàng mới trẻ hơn hẳn tập train; giá cả lạm phát dịch chuyển. |
| Concept drift | Quan hệ P(y|X) thay đổi — cùng input nhưng nhãn đúng đã khác. | Hành vi gian lận thay đổi; sau COVID thói quen mua sắm khác hẳn. |
| Label/prediction drift | Phân phối đầu ra P(y_hat) lệch so với lúc train. | Tỉ lệ dự đoán "churn" đột ngột tăng gấp đôi. |
Cách phát hiện drift trên từng feature/đầu ra:
PSI < 0.1 ổn định, 0.1–0.25 dịch chuyển vừa (theo dõi), > 0.25 dịch chuyển mạnh (cần hành động). Rất phổ biến trong tín dụng.# PSI đơn giản giữa phân phối tham chiếu và hiện tại import numpy as np def psi(ref, cur, bins=10): edges = np.percentile(ref, np.linspace(0, 100, bins + 1)) edges[0], edges[-1] = -np.inf, np.inf r = np.histogram(ref, edges)[0] / len(ref) c = np.histogram(cur, edges)[0] / len(cur) r, c = np.clip(r, 1e-6, None), np.clip(c, 1e-6, None) return np.sum((c - r) * np.log(c / r)) print(psi(ref_scores, live_scores)) # > 0.25 => cảnh báo drift
Khi nào retrain? Có ba chiến lược, thường kết hợp: (1) theo lịch — retrain định kỳ (hằng tuần/tháng) đơn giản, dễ vận hành; (2) theo trigger — retrain khi PSI/KS vượt ngưỡng hoặc metric tụt dưới SLA; (3) online/continual — cập nhật liên tục, phức tạp và rủi ro nhất. Kèm theo là alerting: đẩy metric drift & performance lên Prometheus/Grafana hoặc công cụ chuyên dụng (Evidently, WhyLabs, Arize) và bắn cảnh báo qua Slack/PagerDuty khi vượt ngưỡng.
Pipeline hoặc feature store) cho cả train lẫn serve, và viết test parity so sánh output hai phía trên cùng input.