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:

python
"""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:

text
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:

  1. 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.
  2. Baris 20-31 (DatasetSintetis): seluruh kontrak Dataset terpenuhi dalam dua method. __init__ mengubah array menjadi tensor sekali di awal; __getitem__ mengembalikan pasangan (fitur, label) pada satu indeks; __len__ melaporkan jumlah sampel sehingga DataLoader tahu kapan iterasi selesai.
  3. Baris 34-38: dataset[0] dan len(dataset) bekerja persis seperti indexing Python biasa — itulah sebabnya class ini terasa akrab dipakai, dan cara tercepat menguji Dataset custom adalah mengakses satu sampelnya langsung.
  4. Baris 40-47 (DataLoader): batch_size=4 menjatah empat sampel per batch; shuffle=True mengacak urutan; generator ber-seed 42 membuat urutan acak itu reproducible — angka yang sama muncul setiap eksekusi; num_workers=0 memuat batch di proses utama.
  5. Baris 49-52 (iterasi): DataLoader adalah iterator — perulangan for memintanya batch demi batch. Delapan belas sampel menghasilkan empat batch penuh plus satu batch sisa berisi 2 sampel, terlihat jelas pada X.shape=(2, 3) di batch terakhir.
  6. Baris 54-62 (TensorDataset): bila data sudah berupa tensor, TensorDataset menyingkat pekerjaan tanpa class tulisan sendiri. Dengan shuffle=False, batch pertama berisi empat sampel pertama dataset dalam urutan aslinya.

Parameter DataLoader yang Sering Ditemui #

ParameterPeranCatatan praktis
batch_sizejumlah sampel per batchnilai lazim 32-256; contoh memakai 4 agar bentuk batch mudah dibaca
shufflemengacak urutan tiap epochTrue untuk data latih, False untuk evaluasi agar hasil terurut
num_workersjumlah proses pengisi batch0 pada contoh; nilai lebih besar memuat batch paralel saat data berat
drop_lastmembuang batch terakhir yang tak penuhberguna saat operasi mensyaratkan ukuran batch seragam
generatorsumber acak untuk shufflediberi manual_seed agar urutan batch reproducible
pin_memorymenyalin batch ke memori terkuncihanya 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.