Dataset menyediakan vektor 64 fitur dan sepuluh kelas; kini kita merancang jaringan yang menjembatani keduanya. Pilihan arsitektur untuk proyek ini adalah MLP tiga layer tersembunyi — cukup dalam untuk menangkap pola goresan tulisan tangan, namun cukup kecil agar pelatihannya selesai dalam hitungan detik di CPU. Seluruh definisi model hidup dalam satu berkas src/model.py sehingga skrip pelatihan dan skrip inferensi mengimpor model yang sama.

Arsitektur yang Dipilih #

Alur data pada model ini berjalan lurus dari vektor piksel ke skor kelas:

TahapUkuranPeran
Masukan64piksel gambar yang telah distandardisasi
fc1 + ReLU + Dropout64layer tersembunyi pertama
fc2 + ReLU + Dropout64layer tersembunyi kedua
fc3 (keluaran)10logits untuk digit 0-9

Definisi lengkapnya dalam src/model.py:

python src/model.py — class MLPDigit
"""Model MLP untuk klasifikasi digit tulisan tangan 8x8.

Dataset: sklearn.datasets.load_digits -> 1797 gambar 8x8 piksel, 10 kelas.
Arsitektur: 64 -> 64 -> 64 -> 10 dengan aktivasi ReLU dan Dropout.
"""

import torch
import torch.nn as nn


class MLPDigit(nn.Module):
    """Multilayer perceptron untuk vektor 64 piksel gambar digit."""

    def __init__(self, masukan=64, tersembunyi=64, kelas=10, dropout=0.2):
        super().__init__()
        self.fc1 = nn.Linear(masukan, tersembunyi)
        self.fc2 = nn.Linear(tersembunyi, tersembunyi)
        self.fc3 = nn.Linear(tersembunyi, kelas)
        self.aktivasi = nn.ReLU()
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        x = x.flatten(start_dim=1)  # (N, 8, 8) atau (N, 64) -> (N, 64)
        x = self.dropout(self.aktivasi(self.fc1(x)))
        x = self.dropout(self.aktivasi(self.fc2(x)))
        return self.fc3(x)  # logits (N, 10)


def hitung_jumlah_parameter(model):
    """Hitung total elemen seluruh tensor parameter sebuah model."""
    return sum(p.numel() for p in model.parameters())

Mari kita telaah kode di atas baris per baris:

  1. class MLPDigit(nn.Module): seluruh model PyTorch mewarisi nn.Module; mekanisme ini yang mendaftarkan setiap nn.Linear agar parameternya terlacak optimizer — pola yang dibahas di chapter Membangun Model dengan nn.Module .
  2. __init__ dengan argumen bawaan: masukan=64, tersembunyi=64, kelas=10 membuat arsitektur mudah diubah dari satu tempat tanpa menyentuh forward.
  3. self.fc1 hingga self.fc3: tiga nn.Linear membentuk rantai 64-64-64-10; nama fc (fully-connected) adalah konvensi umum untuk layer padat.
  4. x.flatten(start_dim=1): mem ratakan masukan (N, 8, 8) menjadi (N, 64) sambil menjaga dimensi batch — model menerima kedua bentuk masukan.
  5. self.dropout(...) setelah ReLU: Dropout mematikan 20 persen neuron secara acak saat pelatihan sehingga model tidak bergantung pada satu jalur fitur tertentu (lihat Fungsi Aktivasi dan pembahasan regularisasi).
  6. return self.fc3(x) tanpa aktivasi: layer terakhir menghasilkan logits mentah karena CrossEntropyLoss milik kita mengharapkan logits, bukan probabilitas.
  7. hitung_jumlah_parameter: menjumlahkan p.numel() seluruh parameter — angka yang kita klaim di laporan harus bisa dihitung ulang dari sini.

Menguji Model Sebelum Dilatih #

Berkas tests/test_model.py memeriksa hal-hal yang murah diuji sebelum pelatihan: bentuk keluaran dari dua bentuk masukan, jumlah parameter, serta perilaku Dropout pada mode train dan eval. Inti pemeriksaannya:

python tests/test_model.py — pemeriksaan bentuk dan jumlah parameter
model = MLPDigit()

# 1. Bentuk keluaran: masukan (N, 8, 8) maupun (N, 64) -> (N, 10).
x_kotak = torch.rand(4, 8, 8)
x_datar = torch.rand(4, 64)
assert model(x_kotak).shape == (4, 10), "bentuk keluaran (N, 8, 8) salah"
assert model(x_datar).shape == (4, 10), "bentuk keluaran (N, 64) salah"

# 2. Jumlah parameter sesuai arsitektur 64-64-64-10.
diharapkan = (64 * 64 + 64) + (64 * 64 + 64) + (64 * 10 + 10)
jumlah = hitung_jumlah_parameter(model)
assert jumlah == diharapkan, f"jumlah parameter {jumlah} != {diharapkan}"

Output yang Dihasilkan (python3 tests/test_model.py):

text
parameter       : 8970
bentuk keluaran : (4, 10)
SEMUA TEST LULUS

Model memiliki 8.970 parameter — angka yang diperoleh dari (64×64+64) + (64×64+64) + (64×10+10), bukan tebakan. Pengujian mode eval memastikan dua forward memberi hasil identik karena Dropout mati, sementara mode train sengaja diperiksa mengubah keluaran antar pemanggilan.

Model terdefinisi dan lolos uji bentuk, tetapi bobotnya masih acak — akurasinya hari ini hanya sekitar peluang tebakan acak. Bagian berikutnya menyusun pipeline yang mengubah 8.970 parameter acak tersebut menjadi pengenalan digit dengan akurasi di atas 95 persen.