Dataset pada bagian sebelumnya menyimpan gambar dua dimensi; meratakannya menjadi vektor 256 piksel seperti pada proyek digit akan membuang informasi tetangga antar piksel. Convolutional neural network bekerja sebaliknya: filter kecil menyapu gambar dan mendeteksi pola lokal di mana pun pola itu berada — tepat yang dibutuhkan untuk mengenali telinga kucing yang bergeser dua piksel. Arsitektur kita sengaja dibagi menjadi dua sub-modul, ciri dan kepala, karena pembagian inilah yang memungkinkan transfer learning pada bagian berikutnya.
Arsitektur dan Alur Bentuk Tensor #
Definisi lengkap model hidup di src/model.py:
import torch
import torch.nn as nn
class CNNHewan(nn.Module):
"""Arsitektur Conv-Pool-Conv-Pool-FC untuk 3 kelas hewan."""
def __init__(self, jumlah_kelas=3):
super().__init__()
self.ciri = nn.Sequential(
nn.Conv2d(1, 8, kernel_size=3, padding=1), # (N, 8, 16, 16)
nn.ReLU(),
nn.MaxPool2d(2), # (N, 8, 8, 8)
nn.Conv2d(8, 16, kernel_size=3, padding=1), # (N, 16, 8, 8)
nn.ReLU(),
nn.MaxPool2d(2), # (N, 16, 4, 4)
)
self.kepala = nn.Sequential(
nn.Flatten(), # (N, 256)
nn.Linear(16 * 4 * 4, 32),
nn.ReLU(),
nn.Linear(32, jumlah_kelas),
)
def forward(self, x):
return self.kepala(self.ciri(x))Mari kita telaah alur bentuk tensor pada kode di atas:
- Masukan
(N, 1, 16, 16): empat dimensi mengikuti konvensi PyTorch — batch, channel, tinggi, lebar; satu channel karena gambar kita grayscale. Conv2d(1, 8, kernel_size=3, padding=1): delapan filter 3x3 masing-masing menghasilkan satu peta ciri berukuran sama (16x16) berkatpadding=1; kedalaman naik dari 1 ke 8 — mekanisme ini dibedah di chapter Convolution Layer .MaxPool2d(2): setiap blok 2x2 diringkas menjadi satu nilai terbesar sehingga resolusi turun menjadi 8x8; dua kali penurunan resolusi menghasilkan 4x4 — perannya dijelaskan di chapter Pooling Layer .self.kepaladenganFlatten: peta ciri akhir(N, 16, 4, 4)diratakan menjadi vektor 256 nilai, lalu duaLinearmemetakan vektor tersebut ke 3 logits kelas.self.ciriterpisah dariself.kepala: pemisahan ini bukan gaya penulisan — pada Bagian 3 kita akan membekukan seluruhself.ciridan menggantiself.kepalatanpa menyentuh layer convolution.
Total parameter model: 80 + 1168 + 8224 + 99 = 9.571 — dihitung dari bobot dan bias tiap layer, bukan angka karangan.
Menguji Model Sebelum Dilatih #
Berkas tests/test_model.py memeriksa bentuk keluaran, peta ciri, jumlah parameter, dan — yang paling khas proyek ini — perilaku pembekuan:
model.train()
for parameter in model.ciri.parameters():
parameter.requires_grad_(False)
loss = nn.CrossEntropyLoss()(model(x), torch.tensor([0, 1, 2, 0]))
loss.backward()
for parameter in model.ciri.parameters():
assert parameter.grad is None, "ciri dibekukan tetapi menerima gradien"Baris requires_grad_(False) memutus jalur gradien menuju layer convolution, dan pengujian memastikan parameter.grad benar-benar None setelah backward() — bukan sekadar nol. Inilah mekanisme yang dipakai transfer learning: optimizer yang hanya menerima parameter kepala tidak akan pernah menggeser bobot convolution.
Output yang Dihasilkan (python3 tests/test_model.py):
parameter : 9571 (ciri 1248 + kepala 8323)
bentuk keluaran : (4, 3)
SEMUA TEST LULUSModel teruji dan strukturnya siap dibekukan sebagian. Bagian berikutnya memanfaatkan desain itu pada skenario data sedikit: berapa banyak yang bisa dipelajari model transfer bila hanya 24 gambar tersedia?