Model tidak membaca berkas — model membaca tensor dengan bentuk dan rentang nilai yang tepat. Pada bagian ini kita mengubah tumpukan gambar uint8 dari Bagian 1 menjadi tensor float 4 dimensi yang siap dikonsumsi nn.Conv2d, kemudian membagi data menjadi latih dan uji dengan cara yang menjaga keseimbangan kelas. Dua keputusan kecil ini (skala nilai dan bentuk tensor) menentukan apakah training loop pada Bagian 3 berjalan mulus sejak epoch pertama.
Normalisasi Piksel #
Gambar dari generator bernilai uint8 0-255. Nilai sebesar itu membuat gradien awal membengkak dan pelatihan menjadi tidak stabil, jadi kita membaginya dengan 255 agar seluruh piksel berada pada rentang 0-1:
import numpy as np
import torch
data = np.load("/tmp/daun_sintetis.npz")
X = data["X"] # uint8 (300, 16, 16), nilai 0-255
X_batch = torch.tensor(X.astype(np.float32) / 255.0).reshape(-1, 1, 16, 16)
print("sebelum : dtype", X.dtype, "| min", X.min(), "| maks", X.max())
print("sesudah : dtype", X_batch.dtype, "| min", round(float(X_batch.min()), 3),
"| maks", round(float(X_batch.max()), 3))
print("bentuk :", X.shape, "->", tuple(X_batch.shape))Output yang Dihasilkan:
sebelum : dtype uint8 | min 0 | maks 229
sesudah : dtype torch.float32 | min 0.0 | maks 0.898
bentuk : (300, 16, 16) -> (300, 1, 16, 16)Mari kita telaah tiga baris inti di atas:
X.astype(np.float32) / 255.0: pembagian dilakukan setelah konversi ke float32 — membagi integer uint8 dengan 255 di NumPy tetap menghasilkan float, tetapi konversi eksplisit memastikan tipe tensor PyTorch seragam sejak awal..reshape(-1, 1, 16, 16): tanda-1membiarkan NumPy menghitung jumlah sampel (300) sendiri; angka1di posisi kedua adalah dimensi kanal — gambar grayscale punya tepat satu kanal.torch.tensor(...): salinan data dibuat ke tensor PyTorch; nilai maksimum 229/255 ≈ 0.898 menegaskan seluruh rentang kini berada dalam 0-1.
Mengapa Bentuk Harus (N, 1, 16, 16) #
Layer nn.Conv2d pada PyTorch mengharapkan masukan 4 dimensi: (batch, kanal, tinggi, lebar). Tiga dimensi gambar (300, 16, 16) tidak punya slot kanal, sehingga reshape(-1, 1, 16, 16) wajib dilakukan sebelum forward pass. Untuk foto RGB nyata, slot kanal berisi 3 — satu per kanal warna; pembahasan lengkapnya ada di chapter Representasi Gambar
.
Loader pada src/dataset.py #
Kedua langkah di atas dipadatkan menjadi fungsi muat_daun yang dipakai seluruh skrip berikutnya:
def muat_daun(path_berkas=BERKAS_DATA):
"""Muat dataset daun; kembalikan X (N,1,16,16) 0-1, y, jumlah_bintak."""
pastikan_dataset(path_berkas)
data = np.load(path_berkas)
X = data["X"].astype(np.float32) / 255.0 # normalisasi 0-255 -> 0-1
X = X.reshape(-1, 1, 16, 16) # tambah kanal grayscale
y = data["y"].astype(np.int64)
jumlah_bintak = data["jumlah_bintak"].astype(np.int64)
return X, y, jumlah_bintak
def indeks_latih_uji(y, ukuran_uji=0.25, seed=SEED):
"""Bagi indeks sampel secara stratified; kembalikan (latih, uji)."""
seluruh = np.arange(len(y))
latih, uji = train_test_split(
seluruh, test_size=ukuran_uji, stratify=y, random_state=seed
)
return latih, ujiFungsi pastikan_dataset memeriksa keberadaan berkas .npz dan memanggil generator dari data/generate_data.py bila berkas belum pernah dibuat — pembaca yang melewatkan Bagian 1 tetap dapat menjalankan latih.py tanpa pesan galat. Sedangkan indeks_latih_uji mengembalikan indeks, bukan array data: indeks memungkinkan latih.py mengambil gambar sedangkan analisis_kesalahan.py mengambil gambar dan metadata jumlah_bintak dari baris yang sama, dengan seed yang sama.
Pembagian Stratified #
Dengan test_size=0.25, dataset 300 gambar terbagi menjadi 225 latih dan 75 uji. Argumen stratify=y menjaga proporsi kelas: 37-38 gambar per kelas pada data uji, bukan pembagian acak yang bisa timpang. Argumen random_state=SEED membuat pembagian identik di setiap eksekusi — inilah alasan latih.py dan analisis_kesalahan.py selalu mengevaluasi gambar uji yang persis sama.
Tensor kini berbentuk (300, 1, 16, 16) bernilai 0-1 dan pembagian data sudah terkunci. Bagian berikutnya merancang CNN kecil yang mengubah tensor tersebut menjadi dua skor kelas, lalu memutar training loop sampai akurasi uji melampaui 90 persen.