Artefak dari Bagian 1 perlu dibungkus menjadi layanan agar aplikasi lain — aplikasi web, aplikasi seluler, atau skrip batch — bisa memakai model lewat protokol standar tanpa mengimpor kode pelatihan. Skrip src/api.py membangun REST API dengan FastAPI mengikuti desain endpoint yang dibahas pada chapter REST API untuk Model
: satu endpoint pemeriksaan kesehatan untuk sistem pemantau, satu endpoint prediksi untuk pengguna.
Model Dimuat Sekali saat Proses Menyala #
Baris-baris pembuka src/api.py adalah keputusan performa terpenting dalam seluruh proyek:
from fastapi import FastAPI
from pydantic import BaseModel, field_validator
from latih_model import muat_model
MODEL, KONFIG, METRIK = muat_model()
NORMALISASI = KONFIG["preprocessing"]["normalisasi"]
RENTANG = KONFIG["preprocessing"]["rentang_masukan"]
AMBANG = KONFIG["ambang_keyakinan"]
app = FastAPI(title="API Digit Mini", version=KONFIG["versi"])Pemuatan terjadi di level modul, bukan di dalam handler. Saat uvicorn menjalankan api:app, baris muat_model() dieksekusi sekali sebelum server menerima permintaan pertama; setelah itu setiap request /predict cukup membaca model yang sudah berada di memori. Memuat model di dalam handler akan membaca berkas 13757 byte dan membangun ulang jaringan pada setiap permintaan — pola yang benar untuk demo, keliru untuk produksi. Tiga konstanta kontrak (normalisasi, rentang, ambang) juga dibaca dari konfigurasi sekali di sini, bukan ditempel sebagai angka di dalam handler.
Validasi Input dari Skema, Bukan dari Kode #
Pydantic memvalidasi body permintaan sebelum menyentuh model:
class PermintaanPrediksi(BaseModel):
"""Skema body POST /predict: 64 nilai piksel 0..16."""
fitur: list[float]
@field_validator("fitur")
@classmethod
def cek_panjang_dan_rentang(cls, nilai):
if len(nilai) != 64:
raise ValueError("fitur harus berisi tepat 64 angka")
if nilai and (min(nilai) < RENTANG[0] or max(nilai) > RENTANG[1]):
raise ValueError(
f"nilai piksel harus di rentang {RENTANG[0]}..{RENTANG[1]}")
return nilai
class HasilPrediksi(BaseModel):
"""Skema respons POST /predict."""
kelas: int
label: str
probabilitas: float
keyakinan_cukup: bool
versi: strValidator memeriksa dua sifat: tepat 64 nilai (satu gambar 8x8) dan seluruhnya dalam rentang 0..16 yang diambil dari konfigurasi. Permintaan yang melanggar ditolak otomatis dengan status 422 sebelum handler berjalan — model tidak pernah menerima masukan tidak sahih. Skema respons ikut dideklarasikan sehingga kontrak keluaran terdokumentasi di OpenAPI.
Dua Endpoint #
Handler /health melaporkan status beserta metadata model; handler /predict menjalankan preprocessing persis seperti pelatihan lalu menghitung probabilitas:
@app.post("/predict", response_model=HasilPrediksi)
def predict(permintaan: PermintaanPrediksi):
"""Prediksi digit dari 64 nilai piksel gambar 8x8."""
import torch
x = torch.tensor([permintaan.fitur], dtype=torch.float32) / NORMALISASI
with torch.no_grad():
probabilitas = torch.softmax(MODEL(x), dim=1)[0]
kelas = int(probabilitas.argmax())
nilai = float(probabilitas[kelas])
return HasilPrediksi(
kelas=kelas,
label=f"digit_{kelas}",
probabilitas=round(nilai, 4),
keyakinan_cukup=nilai >= AMBANG,
versi=KONFIG["versi"],
)Tiga detail pada handler ini wajib dibedah. Pembagian / NORMALISASI mengulang preprocessing pelatihan (piksel 0..16 menjadi 0..1) — layanan yang preprocessing-nya berbeda dari pelatihan akan menghasilkan prediksi yang meleset meski bobotnya benar. torch.no_grad() mematikan pelacakan gradient pada jalur inferensi. Terakhir, field keyakinan_cukup membandingkan probabilitas terhadap ambang 0.9 dari konfigurasi, sehingga pemanggil API bisa menandai prediksi yang perlu ditinjau ulang.
Menjalankan python3 src/api.py mencetak ringkasan layanan tanpa membuka port:
== inference API digit mini ==
model : mlp-digit-mini versi 1.0.0
akurasi val : 0.9511
ambang : 0.9 (probabilitas di bawah ambang ditandai)
endpoint :
GET,HEAD /docs
GET,HEAD /docs/oauth2-redirect
GET /health
GET,HEAD /openapi.json
POST /predict
GET,HEAD /redoc
menjalankan server pengembangan:
uvicorn api:app --host 127.0.0.1 --port 8000
dokumentasi interaktif: http://127.0.0.1:8000/docsServer sungguhan dinyalakan dari folder src/ dengan perintah uvicorn api:app --host 127.0.0.1 --port 8000; endpoint /docs lalu menyajikan dokumentasi interaktif yang dibangkitkan otomatis dari skema Pydantic, sehingga pemanggil baru bisa mempelajari kontrak request-response tanpa membaca kode sumber.
Satu kebiasaan kecil pada handler patut dijelaskan karena terlihat janggal: import torch ditulis di dalam fungsi predict, bukan di baris atas berkas. Modul torch sebenarnya sudah dimuat oleh latih_model saat proses menyala, jadi import lokal ini hanya mengambil referensi dari cache modul — biayanya nyaris nol. Manfaatnya, berkas api.py tetap bisa dibaca sebagai “kerangka layanan” tanpa dependensi berat di baris import, dan tubuh handler memperlihatkan secara eksplisit pustaka apa yang dipakai di jalur prediksi.
API sudah berdiri; tinggal membuktikan bahwa endpoint-nya bekerja benar dari sisi pemanggil — tugas Bagian 3.