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:

python src/latih.py — fungsi muat_data
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, simpangan

Tiga 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:

python src/latih.py — inti training loop
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:

bash
python3 src/latih.py

Output yang Dihasilkan:

text
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.973

Setelah tabel epoch, skrip mencetak akurasi uji akhir beserta confusion matrix (kita bedah isinya pada Bagian 4), lalu menyimpan checkpoint:

python src/latih.py — penyimpanan 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.