Membawa model ke produksi: export joblib/ONNX, membangun REST API dengan FastAPI, containerization dengan Docker, batch inference untuk data besar, lalu monitoring data drift, degradasi model, dan kebijakan retraining yang jelas.

Setelah di episode 17 kita membangun pipeline MLOps, saatnya tiba bagi model untuk keluar dari notebook dan melayani pengguna: di balik API, di dalam aplikasi mobile, atau dalam batch job malam hari. Episode ini menjawab pertanyaan paling praktis: bagaimana men-deploy model agar cepat, aman, dan bisa dipantau?
Mengapa deployment dipisahkan dari training? Karena keduanya punya kebutuhan berbeda. Training butuh fleksibilitas dan eksplorasi; serving butuh stabilitas, kecepatan, dan keamanan. Model di produksi harus merespons dalam milidetik, tidak boleh bocor ke penyerang (episode 16), dan harus terpantau terus-menerus.
Langkah pertama: simpan model terlatih ke file yang bisa dimuat kembali kapan saja.
import joblib
joblib.dump(pipeline, "model/churn_pipeline.joblib")
loaded = joblib.load("model/churn_pipeline.joblib")
print(loaded.predict(X_new))Kelebihan joblib: simpel, memuat seluruh Pipeline (preprocessing + model) sekaligus. Kelemahan: hanya bisa dimuat oleh Python versi dan library yang serupa — versi scikit-learn tidak sama bisa gagal.
ONNX (Open Neural Network Exchange) adalah format model standar yang bisa dijalankan di banyak platform dan bahasa (Python, C++, Java, mobile). Untuk serving produksi, ONNX biasanya lebih cepat dan ringan:
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
initial_types = [("float_input", FloatTensorType([None, X.shape[1]]))]
onnx_model = convert_sklearn(pipeline, initial_types=initial_types)
with open("model/churn.onnx", "wb") as f:
f.write(onnx_model.SerializeToString())Untuk tree-based (XGBoost, LightGBM), ekspor ONNX sering menaikkan kecepatan inference signifikan — investasi yang layak saat trafik naik.
FastAPI adalah pilihan standar untuk serving model di 2026: cepat, type-safe, dan dokumentasi otomatis.
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
app = FastAPI(title="Churn Predictor")
model = joblib.load("model/churn_pipeline.joblib")
class Input(BaseModel):
umur: float
pendapatan: float
kota: str
class Output(BaseModel):
churn_probability: float
churn: bool
@app.post("/predict", response_model=Output)
def predict(data: Input):
import pandas as pd
df = pd.DataFrame([data.model_dump()])
proba = model.predict_proba(df)[0, 1]
return Output(churn_probability=round(proba, 4), churn=proba > 0.5)Jalankan dan uji:
uvicorn app:app --host 0.0.0.0 --port 8000
curl -s -X POST http://localhost:8000/predict \
-H "Content-Type: application/json" \
-d '{"umur": 34, "pendapatan": 5.2, "kota": "jakarta"}'FastAPI memberi dokumentasi interaktif otomatis di http://localhost:8000/docs — tempat menguji endpoint tanpa menulis curl manual.
Tip
Pydantic BaseModel di atas sekaligus validasi input: request dengan tipe salah (umur berupa string) ditolak otomatis dengan HTTP 422. Ini lapisan pertahanan pertama dari adversarial input yang kita bahas di episode 16.
Supaya server model jalan konsisten di mesin mana pun — laptop, CI, server produksi — bungkus dengan Docker:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py model/ /app/
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]docker build -t churn-api .
docker run -p 8000:8000 churn-apiImage Docker adalah artefak deploy yang lengkap — versi library, kode, dan model terkurung di dalamnya. Ini memecahkan masalah "berjalan di mesin saya, tapi tidak di server" dan menjadi dasar CD di episode 17.
Tidak semua prediksi harus real-time. Untuk skor jutaan customer setiap malam, batch inference jauh lebih efisien:
model = joblib.load("model/churn_pipeline.joblib")
df = pd.read_csv("customers_today.csv")
proba = model.predict_proba(df)[:, 1]
df["churn_proba"] = proba
df.to_parquet("scores/churn_today.parquet")Perbedaan mendasar:
| Mode | Kapan dipakai | Latency | Volume |
|---|---|---|---|
| Online (API) | Realtime, interaktif | Milidetik | Per-request |
| Batch | Rutin, bukan realtime | Menit-jam | Jutaan baris |
| Streaming | Data mengalir terus | Detik | Kontinu |
Model di produksi itu seperti mesin pabrik — harus dipantau, atau rusaknya baru terasa saat sudah terlambat.
Ingat PSI dari episode 17. Di produksi, kita memantau secara berkala:
def monitor_production(sample):
drift_score = psi(
np.histogram(X_train[:, 0])[0],
np.histogram(sample[:, 0])[0],
)
if drift_score > 0.25:
alert("Drift signifikan terdeteksi di fitur 0!")
return drift_scoreGejala degradasi yang harus dipantau:
| Gejala | Kemungkinan penyebab |
|---|---|
| Distribusi input bergeser | Data drift — demografi pelanggan berubah |
| Prediksi melenceng dari feedback | Concept drift — perilaku target berubah |
| Latency naik | Beban naik / model membesar |
| Skor monitoring turun | Feedback label tidak cocok |
Kebijakan retraining yang jelas menjawab pertanyaan "kapan model diganti?":
Aturan yang perlu ditetapkan sejak awal:
Pipeline, bukan model mentah, agar preprocessing identik.Pada episode 18 ini, kalian telah membawa model ke produksi.
Inti yang harus dibawa pulang:
Di episode 19 selanjutnya kita memperdalam model yang sudah kita pakai: gradient boosting lanjutan & XGBoost deep dive — hyperparameter penting, kontrol overfitting, GPU acceleration, benchmark tabular, dan interpretasi dengan SHAP. Sampai jumpa di episode 19!