Model pada bagian sebelumnya masih berbobot acak; bagian ini mengubahnya melalui pipeline pelatihan yang lengkap namun tetap ringkas. Seluruh alur — memuat data, membagi latih/uji, menstandardisasi, mengocok batch, memutar training loop, hingga menyimpan checkpoint — hidup dalam satu skrip src/latih.py yang bisa dijalankan ulang kapan pun dengan hasil identik. Kita memakai cetakan training loop dari chapter Training Loop
, jadi fokus pembahasan kita adalah keputusan-keputusan yang khas proyek ini.
Menyiapkan Data Sekali Jalan #
Fungsi pertama pada src/latih.py meringkas seluruh penyiapan data dari Bagian 1:
def muat_data():
"""Muat digits, bagi latih/uji, lalu standardisasi dari data latih."""
kumpulan = load_digits()
X, y = kumpulan.data, kumpulan.target # (1797, 64) dan (1797,)
X_latih, X_uji, y_latih, y_uji = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=SEED
)
# Statistik standardisasi hanya dari data latih agar tidak bocor ke uji.
rata2 = X_latih.mean(axis=0)
simpangan = X_latih.std(axis=0)
simpangan[simpangan == 0] = 1.0 # piksel konstan: jaga pembagi tetap valid
X_latih = (X_latih - rata2) / simpangan
X_uji = (X_uji - rata2) / simpangan
return X_latih, y_latih, X_uji, y_uji, rata2, simpanganTiga keputusan pada fungsi ini layak dicermati. Pertama, stratify=y menjaga proporsi sepuluh kelas pada 450 sampel uji tetap seimbang — tanpa argumen ini, kelas tertentu bisa kekurangan wakil di data uji. Kedua, random_state=SEED membuat pembagian yang sama setiap eksekusi, sehingga angka akurasi di Bagian 4 dapat direproduksi. Ketiga, penjaga simpangan[simpangan == 0] = 1.0 menangani tiga piksel konstan yang kita temukan pada Bagian 1 — tanpa penjaga ini loss menjadi tak hingga dan pelatihan berhenti belajar.
Data latih kemudian dibungkus TensorDataset dan DataLoader dengan batch_size=32, shuffle=True, serta generator=torch.Generator().manual_seed(SEED) — urutan kocokan pun tertentu sehingga seluruh sesi pelatihan deterministik. Pemilihan Adam dengan learning rate 0.001 mengikuti pembahasan chapter Loss dan Optimizer
.
Training Loop yang Memutar Semuanya #
Inti src/latih.py menyalin cetakan baku — dan itu memang tujuannya: pola yang sama dikenali di hampir semua proyek PyTorch:
for epoch in range(1, jumlah_epoch + 1):
model.train()
total_loss = 0.0
for X_batch, y_batch in loader:
optimizer.zero_grad()
logits = model(X_batch)
loss = criterion(logits, y_batch)
loss.backward()
optimizer.step()
total_loss += loss.item()
loss_latih = total_loss / len(loader)
if epoch == 1 or epoch % 5 == 0:
akurasi = evaluasi_akurasi(model, X_uji_t, y_uji_t)
print(f"epoch {epoch:2d} | loss latih {loss_latih:.4f} "
f"| akurasi uji {akurasi:.3f}")Empat langkah dalam loop dalam (zero_grad → forward → backward → step) persis seperti yang dibedah pada chapter asalnya; model.train() di awal epoch mengaktifkan Dropout, dan evaluasi_akurasi memanggil model.eval() di dalamnya agar pengukuran akurasi uji bebas efek Dropout. Pencatatan hanya pada epoch ke-1 dan kelipatan lima menjaga keluaran tetap pendek.
Menjalankan Pelatihan #
Dari folder proyek, pelatihan dijalankan tanpa argumen:
python3 src/latih.pyOutput yang Dihasilkan:
data : 1347 latih, 450 uji
model : MLP 64-64-64-10 (8970 parameter)
pelatihan : Adam lr=0.001, batch 32, 15 epoch
epoch 1 | loss latih 2.1211 | akurasi uji 0.709
epoch 5 | loss latih 0.2925 | akurasi uji 0.938
epoch 10 | loss latih 0.1265 | akurasi uji 0.971
epoch 15 | loss latih 0.0743 | akurasi uji 0.973Setelah tabel epoch, skrip mencetak akurasi uji akhir beserta confusion matrix (kita bedah isinya pada Bagian 4), lalu menyimpan checkpoint:
torch.save(
{
"state_dict": model.state_dict(),
"rata2": torch.tensor(rata2, dtype=torch.float32),
"simpangan": torch.tensor(simpangan, dtype=torch.float32),
},
path_keluaran,
)Checkpoint berisi state_dict (bobot terlatih) plus statistik standardisasi — kedua hal ini wajib dibawa bersama agar prediksi.py mengolah gambar baru dengan cara yang persis sama seperti saat pelatihan, sesuai pola chapter Penyimpanan dan Pemuatan Model
.
Loss turun dari 2.1211 ke 0.0743 dan akurasi uji naik dari 0.709 ke 0.973 dalam 15 epoch — sekitar delapan detik pada CPU. Angka akhirnya bagus, tetapi satu angka saja belum bercerita apa pun tentang pola kesalahan. Bagian berikutnya membaca confusion matrix untuk menjawabnya.