Transfer learning baru terasa nilainya ketika data langka, jadi tugas B sengaja dibuat miskin: dari 896 gambar digit 5-9, hanya 30 dipakai sebagai data latih. Sisanya menjadi kolam data uji yang besar (200 sampel) supaya kualitas generalisasi terukur pada sampel yang tidak pernah dilihat model. Bagian ini membahas fungsi pengambil sampelnya dan satu syarat eksperimen yang tidak boleh dilanggar: transfer.py, dari_nol.py, dan bandingkan.py harus melihat 30 sampel latih yang persis sama.

Mengambil Sampel dengan Seed Tetap #

Seluruh pengambilan sampel terjadi di satu fungsi pada src/pretrained.py:

python src/pretrained.py — fungsi siapkan_tugas_b
def siapkan_tugas_b(jumlah_latih=30, jumlah_uji=200):
    """Ambil sampel tugas B: sedikit latih + banyak uji, seed tetap.

    Pengambilan memakai permutasi np.random.default_rng(SEED) sehingga
    transfer.py, dari_nol.py, dan bandingkan.py melihat data yang sama.
    """
    _, _, XB, yB, _, _ = siapkan_data()
    rng = np.random.default_rng(SEED)
    urutan = rng.permutation(len(XB))
    latih = urutan[:jumlah_latih]
    uji = urutan[jumlah_latih:jumlah_latih + jumlah_uji]
    return {
        "X_latih": XB[latih], "y_latih": yB[latih],
        "X_uji": XB[uji], "y_uji": yB[uji],
    }

Mari kita telaah fungsi di atas baris per baris:

  1. siapkan_data(): tugas B dipanggil dari penyiapan Bagian 1 — normalisasi memakai statistik tugas A dan resolusi 16x16 sudah termasuk, sehingga sampel siap dikonsumsi backbone tanpa preprocessing tambahan.
  2. rng.permutation(len(XB)): satu permutasi dari generator ber-seed 42 menentukan seluruh pembagian; latih adalah 30 indeks pertama dan uji 200 indeks berikutnya.
  3. Pemanggilan ulang menjamin data sama: karena np.random.default_rng(42) selalu menghasilkan permutasi yang sama, setiap skrip yang memanggil siapkan_tugas_b() menerima tensor yang identik bit demi bit — prasyarat perbandingan yang adil di Bagian 4.

Memeriksa Sampel yang Ditarik #

Pemeriksaan ringkas memastikan pembagian berjalan seperti diharapkan. Simpan potongan berikut sebagai src/periksa_dataset.py, lalu jalankan dari folder proyek:

python Pemeriksaan sampel tugas B
import torch

from pretrained import siapkan_tugas_b

data = siapkan_tugas_b()

print("bentuk X_latih :", tuple(data["X_latih"].shape))
print("bentuk X_uji   :", tuple(data["X_uji"].shape))
print("kelas latih    :", torch.bincount(data["y_latih"]).tolist(),
      "(digit 5-9 -> label 0-4)")
print("kelas uji      :", torch.bincount(data["y_uji"]).tolist())
bash
python3 src/periksa_dataset.py

Output yang Dihasilkan:

text
bentuk X_latih : (30, 1, 16, 16)
bentuk X_uji   : (200, 1, 16, 16)
kelas latih    : [7, 7, 7, 4, 5] (digit 5-9 -> label 0-4)
kelas uji      : [35, 45, 41, 37, 42]

Keluarannya menjawab dua hal sekaligus. Bentuk (30, 1, 16, 16) menegaskan jumlah sampel, satu kanal grayscale, dan resolusi 16x16 hasil interpolasi Bagian 1. Sebaran kelas [7, 7, 7, 4, 5] memperlihatkan kenyataan data kecil: jumlah wakil tiap kelas timpang — kelas 3 (digit 8) hanya beranggotakan 4 gambar — persis seperti kondisi dataset langka di dunia nyata yang tidak selalu seimbang. Data uji 200 sampel justru terbagi hampir rata, sehingga akurasi uji tidak berat sebelah.

Mengapa Data Uji Harus Besar #

Ketika data latih hanya 30 sampel, setiap keputusan model diuji pada 200 sampel yang tidak pernah dilihatnya. Pada Bagian 4, selisih akurasi transfer versus dari nol mencapai 0.155 — setara 31 dari 200 gambar uji; dengan data uji yang lebih kecil, selisih sebesar itu mudah tenggelam dalam fluktuasi acak. Ukuran uji yang cukup adalah bagian dari desain eksperimen, bukan pelengkap — pembahasan lengkapnya ada di chapter Reproducible Experiments .

Dataset tugas B kini terkunci: 30 sampel latih timpang yang meniru data langka, dan 200 sampel uji yang representatif. Bagian berikutnya memasangkan data ini dengan backbone Bagian 1 — membekukannya, memasang head baru, dan melatih hanya bagian yang perlu belajar.