DataLens

Dataset dan DataLoader

Bungkus data dalam Dataset, biarkan DataLoader menangani pembagian batch dan pengacakan, dan tulis training loop yang lebih rapi yang dipakai setiap proyek PyTorch.

Level: Menengah · 8 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Idenya

Modul sebelumnya memotong tensor secara manual untuk membuat mini-batch. Cara itu bisa untuk 300 baris di memori. Proyek sungguhan punya data yang tidak muat di memori (ribuan gambar), butuh pemrosesan per contoh (membaca file, augmentasi gambar), dan sebaiknya dimuat secara paralel sementara GPU melakukan training. PyTorch membaginya menjadi dua bagian:

Dataset hanyalah dua method

Di modul sebelumnya, training loop memotong batch dari tensor secara manual. Itu bisa untuk data kecil, tapi merepotkan dan tidak bisa dipakai untuk data yang tidak muat di memori. Jawaban PyTorch adalah membungkus datanya dalam sebuah Dataset: class yang tahu ada berapa contoh dan bagaimana mengambil satu contoh:

import numpy as np
import pandas as pd
import torch
from torch.utils.data import Dataset, DataLoader


class ChurnDataset(Dataset):
    def __init__(self, frame, features, target):
        self.X = torch.tensor(frame[features].to_numpy(), dtype=torch.float32)
        self.y = torch.tensor(frame[target].to_numpy(), dtype=torch.float32)

    def __len__(self):
        return len(self.y)

    def __getitem__(self, i):
        return self.X[i], self.y[i]


churn = pd.read_csv("churn.csv")
FEATURES = ["tenure_months", "orders_90d", "days_since_last_order", "support_tickets", "app_user"]
dataset = ChurnDataset(churn, FEATURES, "churned")
print(len(dataset))
x0, y0 = dataset[0]
print(x0, y0)
400
tensor([12.,  7., 47.,  1.,  0.]) tensor(0.)

Itulah seluruh kontraknya (special method Python, dari modul class). Untuk data yang sudah berupa tensor, TensorDataset(X, y) melakukan hal yang sama dalam satu baris.

DataLoader membuat batch

Dataset memberikan satu contoh setiap kalinya. Pelatihan membutuhkan batch berisi contoh-contoh yang sudah diacak. DataLoader duduk di atas Dataset dan melakukan persis itu:

torch.manual_seed(0)
loader = DataLoader(dataset, batch_size=64, shuffle=True)
print(len(loader))
X_batch, y_batch = next(iter(loader))
print(X_batch.shape, y_batch.shape)
print(sum(len(yb) for _, yb in loader))
7
torch.Size([64, 5]) torch.Size([64])
400

Membagi sebuah Dataset

random_split membagi Dataset menjadi bagian training dan validasi:

from torch.utils.data import random_split

generator = torch.Generator().manual_seed(42)
train_set, val_set = random_split(dataset, [300, 100], generator=generator)
train_loader = DataLoader(train_set, batch_size=32, shuffle=True)
val_loader = DataLoader(val_set, batch_size=100)
print(len(train_set), len(val_set), len(train_loader), len(val_loader))
300 100 10 1

generator yang diberi seed membuat pembagiannya bisa direproduksi, seperti random_state di scikit-learn. (Untuk pembagian terstratifikasi atau preprocessing yang di-fit hanya pada baris training, membagi DataFrame dengan scikit-learn lebih dulu lalu membuat dua Dataset sering kali lebih sederhana.)

Training loop yang lebih rapi

Dengan loader, loop dari modul sebelumnya menjadi:

from torch import nn

mean = dataset.X[train_set.indices].mean(dim=0)
std = dataset.X[train_set.indices].std(dim=0)

torch.manual_seed(0)
model = nn.Sequential(nn.Linear(5, 16), nn.ReLU(), nn.Linear(16, 1))
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
loss_fn = nn.BCEWithLogitsLoss()

for epoch in range(1, 31):
    model.train()
    for X_b, y_b in train_loader:
        optimizer.zero_grad()
        loss = loss_fn(model((X_b - mean) / std).squeeze(1), y_b)
        loss.backward()
        optimizer.step()

    model.eval()
    correct = 0
    with torch.no_grad():
        for X_b, y_b in val_loader:
            probs = torch.sigmoid(model((X_b - mean) / std).squeeze(1))
            correct += ((probs >= 0.5).float() == y_b).sum().item()
    if epoch in (1, 10, 30):
        print(f"epoch {epoch:2d}  validation accuracy {correct / len(val_set):.2f}")
epoch  1  validation accuracy 0.73
epoch 10  validation accuracy 0.86
epoch 30  validation accuracy 0.84

Isi loop tidak lagi tahu bagaimana batch dibuat; loader yang menanganinya. Loop yang sama akan berfungsi tanpa diubah untuk dataset berisi jutaan gambar di disk.

Perhatikan detailnya: squeeze(1) mengubah output model (batch, 1) menjadi (batch,) agar cocok dengan target, dan statistik penskalaan berasal dari baris training saja.

Ide utama

Dataset menjawab "ada berapa?" dan "berikan nomor i"; DataLoader mengubahnya menjadi batch yang diacak. Training loop melakukan iterasi atas loader: for X_b, y_b in train_loader:.

Mengapa ini penting untuk kode yang bersih

Dataset memisahkan akses data dari logika training. Kamu bisa mengetes Dataset secara terpisah (apakah dataset[0] mengembalikan shape yang benar?), menukar Dataset berbasis CSV dengan yang berbasis database tanpa menyentuh loop, dan memakai ulang loop yang sama di berbagai proyek. Ini pemisahan yang sama seperti load_data() vs analyse() di modul pipeline pandas.

Rangkuman

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.