Menyimpan model untuk keperluan riset dan menyimpannya untuk produksi menuntut hal yang berbeda. Saat bereksperimen di notebook, sebuah berkas checkpoint.pth yang berisi bobot sudah cukup — kode arsitektur, normalisasi, dan label kelas semuanya hidup di sel yang sama. Begitu model harus melayani pengguna, setiap informasi itu justru tidak boleh bergantung pada notebook: sistem produksi memuat model dari kumpulan berkas yang berdiri sendiri. Chapter ini menyusun paket artefak produksi yang memuat bobot, konfigurasi arsitektur, parameter preprocessing, versi, dan metadata, lalu membuktikan bahwa paket tersebut cukup untuk memulihkan model dengan prediksi yang identik.
Artefak Riset versus Artefak Produksi #
Artefak riset menjawab kebutuhan penulisnya sendiri: cepat disimpan, cukup untuk melanjutkan eksperimen. Artefak produksi menjawab kebutuhan sistem lain yang memuatnya tanpa konteks notebook. Tabel berikut memetakan isi minimum sebuah paket produksi:
| Komponen | Isi | Fungsi saat pemuatan |
|---|---|---|
model.pth | state_dict (tensor parameter) | mengisi bobot ke objek model yang dibangun ulang |
config.json | arsitektur, preprocessing, label kelas | membangun model dan mengolah input tanpa menebak |
| Versi | penanda rilis, misal 1.0.0 | mencocokkan paket dengan kode pemuat yang sesuai |
| Metadata | versi PyTorch, asal data, penulis | audit dan penelusuran saat terjadi masalah |
Pola state_dict sudah kita kuasai pada chapter Penyimpanan dan Pemuatan Model
; di sini pola itu diperluas menjadi paket lengkap. Konfigurasi preprocessing wajib ikut disimpan karena model terlatih pada data yang sudah dinormalisasi — tanpa nilai rata-rata dan simpangan yang sama, input produksi tidak akan pernah menempati rentang yang dipelajari model.
Format .pt, TorchScript, dan ONNX #
Ada tiga keluarga format yang sering dipakai saat membawa model keluar dari sesi training:
| Format | Isi berkas | Kapan dipakai |
|---|---|---|
.pt / .pth (state_dict + config) | tensor + JSON metadata | pemuatan ulang di kode Python yang sama; paling fleksibel |
| TorchScript | graph terkompilasi | model tanpa dependensi kode Python — kini deprecated, digantikan torch.export |
| ONNX | graph standar lintas framework | inference di runtime lain (C++, ONNX Runtime) — dibahas di chapter ONNX dan Model Export |
Praktik: Menyusun dan Memuat Paket Produksi #
Berkas paket_produksi.py melatih MLP kecil, menyusun folder paket_klasifikasi berisi model.pth dan config.json, lalu memuat ulang model hanya dari isi paket — seolah dilakukan oleh proses lain yang tidak mengenal sesi training:
"""Menyusun paket artefak produksi: bobot + konfigurasi + versi.
Alur: latih MLP kecil -> susun paket produksi (model.pth + config.json
di dalam satu folder) -> muat ulang model hanya dari isi paket ->
verifikasi prediksi identik dengan model asli -> bersihkan berkas demo.
"""
import json
import shutil
from pathlib import Path
import sklearn
import torch
import torch.nn as nn
from sklearn.datasets import make_moons
from torch.utils.data import DataLoader, TensorDataset
PAKET = Path("paket_klasifikasi")
class MLP(nn.Module):
"""MLP dua layer untuk klasifikasi dua kelas."""
def __init__(self, jumlah_fitur, jumlah_hidden, jumlah_kelas):
super().__init__()
self.satu = nn.Linear(jumlah_fitur, jumlah_hidden)
self.dua = nn.Linear(jumlah_hidden, jumlah_kelas)
def forward(self, x):
return self.dua(torch.relu(self.satu(x)))
torch.manual_seed(42)
X, y = make_moons(n_samples=256, noise=0.20, random_state=42)
rata_rata = X.mean(axis=0)
simpangan = X.std(axis=0)
X_norm = (X - rata_rata) / simpangan
loader = DataLoader(
TensorDataset(
torch.tensor(X_norm, dtype=torch.float32),
torch.tensor(y, dtype=torch.long),
),
batch_size=32,
shuffle=True,
num_workers=0,
generator=torch.Generator().manual_seed(42),
)
model = MLP(2, 12, 2)
kriteria = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.5)
for _ in range(20):
for X_batch, y_batch in loader:
optimizer.zero_grad()
loss = kriteria(model(X_batch), y_batch)
loss.backward()
optimizer.step()
model.eval()
# ---------- 1. Susun paket produksi ----------
PAKET.mkdir(exist_ok=True)
torch.save(model.state_dict(), PAKET / "model.pth")
konfigurasi = {
"versi": "1.0.0",
"arsitektur": {
"jenis": "MLP",
"jumlah_fitur": 2,
"jumlah_hidden": 12,
"jumlah_kelas": 2,
},
"preprocessing": {
"rata_rata": rata_rata.tolist(),
"simpangan": simpangan.tolist(),
},
"label": ["kelas_0", "kelas_1"],
"dibuat_dengan": {"torch": torch.__version__, "sklearn": sklearn.__version__},
}
(PAKET / "config.json").write_text(
json.dumps(konfigurasi, indent=2), encoding="utf-8"
)
print("isi paket produksi:")
for nama in sorted(p.name for p in PAKET.iterdir()):
ukuran = (PAKET / nama).stat().st_size
print(f" {nama:12s} {ukuran:6d} byte")
# ---------- 2. Muat ulang hanya dari isi paket ----------
cfg = json.loads((PAKET / "config.json").read_text(encoding="utf-8"))
arsitektur = cfg["arsitektur"]
model_muat = MLP(
arsitektur["jumlah_fitur"],
arsitektur["jumlah_hidden"],
arsitektur["jumlah_kelas"],
)
model_muat.load_state_dict(
torch.load(PAKET / "model.pth", weights_only=True)
)
model_muat.eval()
X_baru = (X - cfg["preprocessing"]["rata_rata"]) / cfg["preprocessing"]["simpangan"]
X_baru = torch.tensor(X_baru, dtype=torch.float32)
with torch.no_grad():
logits_asli = model(X_baru)
logits_muat = model_muat(X_baru)
print("\nversi paket :", cfg["versi"])
print("prediksi model asli == muat :", torch.equal(logits_asli, logits_muat))
prediksi = logits_muat.argmax(dim=1).tolist()[:3]
print("tiga prediksi pertama :", [cfg["label"][i] for i in prediksi])
# ---------- 3. Bersihkan berkas demo ----------
shutil.rmtree(PAKET)
print("folder paket_klasifikasi dihapus dari bundle")Output yang Dihasilkan:
isi paket produksi:
config.json 437 byte
model.pth 2569 byte
versi paket : 1.0.0
prediksi model asli == muat : True
tiga prediksi pertama : ['kelas_1', 'kelas_1', 'kelas_0']
folder paket_klasifikasi dihapus dari bundleMari kita telaah kode di atas baris per baris:
- Baris 34-36 (
rata_rata,simpangan): statistik preprocessing dihitung sebelum training dan disimpan terpisah — keduanya nanti masuk keconfig.json, bukan tertanam di dalam bobot. - Baris 58 (
torch.save(model.state_dict(), ...)): hanya tensor parameter yang ditulis kemodel.pth. Kode classMLPtetap berada di sisi pemuat, sehingga berkas tidak mengikat lokasi definisi class. - Baris 59-75 (kamus
konfigurasi): lima kunci membentuk kontrak pemuatan — arsitektur untuk membangun model, preprocessing untuk mengolah input, label untuk menerjemahkan indeks kelas, versi untuk pencocokan, dan metadatadibuat_denganuntuk audit. - Baris 81-91 (pemuatan ulang): objek model dibangun murni dari nilai
config.json, bukan dari konstanta yang ditulis tangan. Jika hidden layer diubah menjadi 24 pada training berikutnya, pemuat tetap benar tanpa perubahan kode. - Baris 93-96 (verifikasi):
torch.equalmembandingkan logits elemen demi elemen — hasilTruemembuktikan paket memulihkan model sampai ke bit terakhir, termasuk lewat jalur preprocessing yang dibaca dari JSON.
Paket yang berdiri sendiri seperti ini adalah syarat awal sebelum model bisa disajikan ke pengguna. Pertanyaan berikutnya bukan lagi apa yang disimpan, melainkan bagaimana model itu menerima permintaan dan mengembalikan hasil — urusan pola model serving yang kita bahas pada chapter berikutnya. Panduan resmi serialisasi PyTorch tersedia di docs.pytorch.org/docs/stable/notes/serialization.html .