Transfer learning dimulai dari satu keputusan: model pretrained mana yang dipakai sebagai titik awal. Model nyata seperti ResNet18 menawarkan bobot hasil jutaan gambar, tetapi ukurannya puluhan megabyte dan dirancang untuk masukan 224x224 — tidak praktis untuk diuji penuh di CPU dalam proyek ini. Karena itu kita membuat pengganti yang setara secara struktural: backbone CNN kecil yang dilatih pada tugas A (mengenali digit 0-4), lalu bobotnya disimpan layaknya berkas model pretrained yang diunduh orang lain. Bagian ini membangun backbone tersebut dan membahas kriteria di balik pilihannya.
Kriteria Pemilihan #
Tiga pertanyaan yang sama berlaku untuk model pretrained nyata maupun buatan sendiri:
| Kriteria | ResNet18 nyata | Backbone proyek ini |
|---|---|---|
| Domain masukan | foto RGB 224x224 | citra grayscale 16x16 |
| Fitur bawahannya | tepi, tekstur, warna | goresan garis dan lengkungan |
| Ukuran pelatihan ulang | head saja: ~2 ribu parameter | head saja: 2565 parameter |
Kuncinya: fitur level bawah (detektor garis, tepi, bercak) berguna lintas tugas selama domain masukannya mirip — digit 0-4 dan digit 5-9 berbagi jenis goresan yang sama, persis seperti foto natural berbagi tekstur dengan foto daun.
Arsitektur Backbone #
Backbone pada src/pretrained.py adalah dua blok convolution-pooling yang menghasilkan vektor 512 fitur:
class CNNBackbone(nn.Module):
"""Backbone konvolusi: citra 16x16 -> vektor 512 fitur."""
def __init__(self):
super().__init__()
self.ciri = nn.Sequential(
nn.Conv2d(1, 16, kernel_size=3, padding=1), # 16x16 -> 16x16
nn.ReLU(),
nn.MaxPool2d(2), # 16x16 -> 8x8
nn.Conv2d(16, 32, kernel_size=3, padding=1), # 8x8 -> 8x8
nn.ReLU(),
nn.MaxPool2d(2), # 8x8 -> 4x4
nn.Flatten(), # 32 x 4 x 4 = 512
)
def forward(self, x):
return self.ciri(x) # (N, 512)
class ModelGabung(nn.Module):
"""Gabungan backbone + head agar bisa dipakai latih_epoch/akurasi."""
def __init__(self, backbone, kepala):
super().__init__()
self.backbone = backbone
self.kepala = kepala
def forward(self, x):
return self.kepala(self.backbone(x))Mari kita telaah dua class di atas baris per baris:
nn.Conv2d(1, 16, ...)lalunn.Conv2d(16, 32, ...): jumlah kanal bertingkat — layer pertama belajar pola sederhana, layer kedua menyusunnya menjadi pola lebih besar, pola hierarki yang dibahas chapter Arsitektur CNN .nn.Flatten()menutup backbone: keluaran berupa vektor fitur (N, 512), bukan skor kelas — backbone memang tidak boleh tahu tugas akhirnya, karena head akan diganti pada Bagian 3.ModelGabung: pembungkus backbone + head menjadi satunn.Modulesehingga fungsi pelatihan dan evaluasi milikpretrained.pydapat dipakai ulang olehtransfer.pydandari_nol.pytanpa duplikasi.
Menyiapkan Dua Tugas dari Satu Dataset #
Fungsi siapkan_data membelah dataset digits menjadi tugas A dan tugas B:
def siapkan_data():
"""Muat digits; kembalikan tensor tugas A dan B + statistik A.
Normalisasi memakai mean/deviasi data tugas A -- statistik dataset
asal backbone ikut dipakai untuk tugas B. Citra 8x8 diperbesar ke
16x16 (bilinear) agar dua blok pooling punya ruang resolusi.
"""
digits = load_digits()
X = digits.data.astype("float32").reshape(-1, 1, 8, 8) / 16.0
y = digits.target.astype("int64")
XA, yA = X[y <= 4], y[y <= 4] # tugas A: digit 0-4
XB, yB = X[y >= 5], y[y >= 5] - 5 # tugas B: digit 5-9 direlabel 0-4
mu, sigma = float(XA.mean()), float(XA.std())
XA = F.interpolate(torch.tensor((XA - mu) / sigma), size=(16, 16),
mode="bilinear", align_corners=False)
XB = F.interpolate(torch.tensor((XB - mu) / sigma), size=(16, 16),
mode="bilinear", align_corners=False)
return XA, torch.tensor(yA), XB, torch.tensor(yB), mu, sigmaTiga keputusan menonjol di sini. Pertama, y[y >= 5] - 5 melabel ulang digit 5-9 menjadi 0-4 sehingga kedua tugas berbagi format label. Kedua, statistik normalisasi mu, sigma dihitung dari tugas A lalu diterapkan juga ke tugas B — meniru praktik memakai statistik dataset asal model pretrained, bukan dataset target. Ketiga, F.interpolate memperbesar citra 8x8 ke 16x16 agar dua blok max-pooling tidak menggerus resolusi sampai habis.
Melatih dan Menyimpan Backbone #
Fungsi latih_backbone melatih backbone bersama head sementara selama 10 epoch, lalu membuang head itu — hanya bobot backbone yang disimpan:
python3 src/pretrained.pyOutput yang Dihasilkan:
tugas A: 720 latih + 181 uji (digit 0-4) -> melatih backbone 10 epoch
akurasi tugas A: 1.000
backbone tersimpan: /tmp/backbone_digit.pt (4800 parameter)Backbone mengklasifikasikan digit 0-4 dengan sempurna pada data uji tugas A, lalu bobotnya berpindah ke /tmp/backbone_digit.pt melalui torch.save(backbone.state_dict(), ...) — pola simpan-muat yang dibahas chapter Penyimpanan dan Pemuatan Model
. Head tugas A ikut dibuang karena klasifikasinya tidak relevan untuk tugas B.
Backbone 4800 parameter kini tersimpan dan teruji. Bagian berikutnya menyiapkan sisi penerima manfaatnya: dataset tugas B yang sengaja dibuat kecil.