Model yang kita bangun pada chapter sebelumnya belum bisa dilatih berulang jika datanya masih berupa dua tensor lepas di memori. Training loop membutuhkan data dalam bentuk batch — potongan berukuran tetap yang mengalir satu per satu — karena memuat seluruh dataset sekaligus tidak lagi mungkin begitu data membengkak ke jutaan sampel. PyTorch memecah masalah ini menjadi dua peran terpisah: Dataset menjawab “data apa dan bagaimana mengambil satu sampel”, sedangkan DataLoader menjawab “bagaimana data disajikan menjadi batch”. Chapter ini membangun keduanya dari data sintetis kecil, lalu memeriksa bentuk batch yang dihasilkan oleh iterasi DataLoader sungguhan.
Dua Peran: Dataset dan DataLoader #
Kontrak Dataset cuma dua method: __len__ memberi tahu jumlah sampel, dan __getitem__ mengembalikan satu sampel pada indeks tertentu. DataLoader kemudian bekerja seperti jembatan — mengambil sampel dari Dataset, menjatahnya menjadi batch, mengacak urutannya bila diminta, lalu menyerahkan batch tersebut kepada perulangan training. Alur lengkapnya tampak seperti ini:
Pemisahan peran ini penting karena keduanya berubah secara mandiri: cara memuat satu sampel gambar dari disk berbeda dengan cara membaca baris tabel, tetapi loop training tidak perlu tahu perbedaan itu — loop hanya menerima batch. Panduan resmi lengkap tersedia di docs.pytorch.org/docs/stable/data.html .
Class Dataset Custom #
Berkas dataset_custom.py berisi class DatasetSintetis yang membungkus array NumPy 18 sampel, DataLoader dengan batch_size=4, serta pembanding TensorDataset:
"""Dataset custom dan DataLoader: dari array NumPy menjadi batch tensor.
Alur: data sintetis NumPy -> class Dataset custom (__len__, __getitem__)
-> DataLoader dengan batch_size=4 dan shuffle terkendali generator ->
iterasi batch. Ditutup pembanding ringan TensorDataset untuk data yang
sudah berupa tensor.
"""
import numpy as np
import torch
from torch.utils.data import DataLoader, Dataset, TensorDataset
rng = np.random.default_rng(42)
fitur = rng.normal(size=(18, 3)).round(3)
label = (fitur.sum(axis=1) > 0).astype(np.int64)
print("data sintetis :", fitur.shape[0], "sampel,", fitur.shape[1], "fitur")
print("distribusi label:", np.bincount(label).tolist())
class DatasetSintetis(Dataset):
"""Bungkus array fitur dan label menjadi objek berindeks tensor."""
def __init__(self, fitur, label):
self.fitur = torch.tensor(fitur, dtype=torch.float32)
self.label = torch.tensor(label, dtype=torch.long)
def __len__(self):
return self.fitur.shape[0]
def __getitem__(self, idx):
return self.fitur[idx], self.label[idx]
dataset = DatasetSintetis(fitur, label)
sampel_awal, label_awal = dataset[0]
nilai_rapi = [round(nilai, 3) for nilai in sampel_awal.tolist()]
print("dataset[0] : fitur", nilai_rapi, "| label", label_awal.item())
print("len(dataset) :", len(dataset))
generator = torch.Generator().manual_seed(42)
loader = DataLoader(
dataset,
batch_size=4,
shuffle=True,
num_workers=0,
generator=generator,
)
print("iterasi DataLoader (batch_size=4, shuffle=True):")
for nomor, (X_batch, y_batch) in enumerate(loader, start=1):
print(f" batch {nomor}: X.shape={tuple(X_batch.shape)} "
f"y.shape={tuple(y_batch.shape)} label={y_batch.tolist()}")
tensor_dataset = TensorDataset(
torch.tensor(fitur, dtype=torch.float32),
torch.tensor(label, dtype=torch.long),
)
loader_urut = DataLoader(tensor_dataset, batch_size=4, shuffle=False)
X_awal, y_awal = next(iter(loader_urut))
print("TensorDataset batch pertama (shuffle=False):")
print(" X.shape =", tuple(X_awal.shape), " y.shape =", tuple(y_awal.shape))
print(" label =", y_awal.tolist())Output yang Dihasilkan:
data sintetis : 18 sampel, 3 fitur
distribusi label: [7, 11]
dataset[0] : fitur [0.305, -1.04, 0.75] | label 1
len(dataset) : 18
iterasi DataLoader (batch_size=4, shuffle=True):
batch 1: X.shape=(4, 3) y.shape=(4,) label=[1, 1, 0, 0]
batch 2: X.shape=(4, 3) y.shape=(4,) label=[1, 0, 1, 1]
batch 3: X.shape=(4, 3) y.shape=(4,) label=[1, 0, 0, 1]
batch 4: X.shape=(4, 3) y.shape=(4,) label=[1, 1, 0, 1]
batch 5: X.shape=(2, 3) y.shape=(2,) label=[0, 1]
TensorDataset batch pertama (shuffle=False):
X.shape = (4, 3) y.shape = (4,)
label = [1, 0, 0, 1]Mari kita telaah kode di atas baris per baris:
- Baris 13-15 (data sintetis):
rng.normal(size=(18, 3))membuat 18 sampel berisi 3 fitur, sementara label diturunkan dari aturan sederhana — jumlah ketiga fitur positif berarti kelas 1 — sehingga data punya pola yang bisa dipelajari model, bukan derau murni. - Baris 20-31 (
DatasetSintetis): seluruh kontrakDatasetterpenuhi dalam dua method.__init__mengubah array menjadi tensor sekali di awal;__getitem__mengembalikan pasangan(fitur, label)pada satu indeks;__len__melaporkan jumlah sampel sehinggaDataLoadertahu kapan iterasi selesai. - Baris 34-38:
dataset[0]danlen(dataset)bekerja persis seperti indexing Python biasa — itulah sebabnya class ini terasa akrab dipakai, dan cara tercepat mengujiDatasetcustom adalah mengakses satu sampelnya langsung. - Baris 40-47 (
DataLoader):batch_size=4menjatah empat sampel per batch;shuffle=Truemengacak urutan;generatorber-seed 42 membuat urutan acak itu reproducible — angka yang sama muncul setiap eksekusi;num_workers=0memuat batch di proses utama. - Baris 49-52 (iterasi):
DataLoaderadalah iterator — perulanganformemintanya batch demi batch. Delapan belas sampel menghasilkan empat batch penuh plus satu batch sisa berisi 2 sampel, terlihat jelas padaX.shape=(2, 3)di batch terakhir. - Baris 54-62 (
TensorDataset): bila data sudah berupa tensor,TensorDatasetmenyingkat pekerjaan tanpa class tulisan sendiri. Denganshuffle=False, batch pertama berisi empat sampel pertama dataset dalam urutan aslinya.
Parameter DataLoader yang Sering Ditemui #
| Parameter | Peran | Catatan praktis |
|---|---|---|
batch_size | jumlah sampel per batch | nilai lazim 32-256; contoh memakai 4 agar bentuk batch mudah dibaca |
shuffle | mengacak urutan tiap epoch | True untuk data latih, False untuk evaluasi agar hasil terurut |
num_workers | jumlah proses pengisi batch | 0 pada contoh; nilai lebih besar memuat batch paralel saat data berat |
drop_last | membuang batch terakhir yang tak penuh | berguna saat operasi mensyaratkan ukuran batch seragam |
generator | sumber acak untuk shuffle | diberi manual_seed agar urutan batch reproducible |
pin_memory | menyalin batch ke memori terkunci | hanya berdampak pada pipeline GPU |
Ukuran batch sebenarnya mengatur kompromi kualitas gradien: batch penuh memberi gradien persis tetapi mahal dihitung, satu sampel memberi gradien paling bising namun paling cepat, sedangkan mini-batch 32-256 berada di tengah — gradien cukup akurat, komputasi efisien, dan kebisingan kecilnya justru membantu training keluar dari dataran rendah. Perbandingan matematis ketiganya sudah kita bedah pada chapter Gradient Descent ; pada praktik PyTorch, angka 32 menjadi titik awal yang jarang mengecewakan.
Kini pasokan batch tersedia. Chapter berikutnya merangkai semuanya menjadi cetakan yang dipakai hampir setiap proyek PyTorch: training loop lengkap dari epoch hingga zero_grad, lengkap dengan validasi tiap epoch.