Dataset kustom

Membungkus citra atau teks milik sendiri menjadi sebuah Dataset.

Daftar isi

Data nyata jarang berupa tensor siap pakai: data milik sendiri biasanya berupa kumpulan file citra, teks, atau tabel. Topik ini membahas satu pola lengkap, yaitu menyiapkan folder citra contoh, membungkusnya menjadi kelas Dataset dengan transformasi, lalu menyerahkannya ke DataLoader. Setelah topik ini, data apa pun dapat diperlakukan sama seperti dataset bawaan.

Dataset citra dari folder#

File berikut berdiri sendiri dan dapat dijalankan apa adanya. Bagian pertama membuat folder contoh-citra berisi delapan PNG keabuan 28x28 berupa kotak dan lingkaran, dan bagian itu hanya berjalan bila folder belum ada. Bagian kedua mendefinisikan DatasetBentuk yang mewarisi Dataset: __init__ menyimpan daftar file yang diurutkan, __getitem__ membuka citra dengan Pillow dan menerapkan transformasi, sedangkan label diambil dari awal nama file.

python dataset_kustom.py
import torch
from pathlib import Path
from PIL import Image, ImageDraw
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

FOLDER = Path("contoh-citra")


def buat_citra_contoh():
    """Membuat delapan PNG keabuan 28x28 berisi kotak dan lingkaran."""
    if FOLDER.exists():
        return
    FOLDER.mkdir()
    for i in range(8):
        geser = 2 + i  # posisi bentuk bergeser deterministik tiap berkas
        if i % 2 == 0:
            nama, jenis = f"kotak-{i}.png", "kotak"
        else:
            nama, jenis = f"lingkaran-{i}.png", "lingkaran"
        citra = Image.new("L", (28, 28), color=0)
        kanvas = ImageDraw.Draw(citra)
        if jenis == "kotak":
            kanvas.rectangle([geser, geser, geser + 12, geser + 12], fill=255)
        else:
            kanvas.ellipse([geser, geser, geser + 12, geser + 12], fill=255)
        citra.save(FOLDER / nama)


buat_citra_contoh()


class DatasetBentuk(Dataset):
    """Membungkus folder PNG menjadi Dataset citra dengan label bentuk."""

    def __init__(self, folder, transformasi=None):
        self.berkas = sorted(folder.glob("*.png"))  # urutan diurutkan agar deterministik
        self.transformasi = transformasi

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

    def __getitem__(self, idx):
        path = self.berkas[idx]
        citra = Image.open(path)
        if self.transformasi is not None:
            citra = self.transformasi(citra)
        label = 0 if path.name.startswith("kotak") else 1
        return citra, label


torch.manual_seed(0)

transformasi = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,)),
])

dataset = DatasetBentuk(FOLDER, transformasi=transformasi)
loader = DataLoader(dataset, batch_size=4, shuffle=True, num_workers=0)

print("Jumlah citra:", len(dataset))
print("Nama berkas pertama:", dataset.berkas[0].name)
citra, label = dataset[0]
print("Shape satu citra:", tuple(citra.shape))
print("Label citra pertama:", label)
x, y = next(iter(loader))
print("Shape batch pertama:", tuple(x.shape))
print("Label batch pertama:", y.tolist())
text Keluaran
Jumlah citra: 8
Nama berkas pertama: kotak-0.png
Shape satu citra: (1, 28, 28)
Label citra pertama: 0
Shape batch pertama: (4, 1, 28, 28)
Label batch pertama: [0, 0, 1, 0]

Keluaran di atas merangkum alur lengkap: folder file citra menjadi dataset dengan kontrak __len__ dan __getitem__, transformasi merapikan satu item menjadi tensor (1,28,28)(1, 28, 28), dan DataLoader mengemasnya menjadi batch (4,1,28,28)(4, 1, 28, 28). Struktur inilah yang dibutuhkan perulangan pelatihan, tanpa satu pun penyesuaian khusus.

Membaca data saat diminta#

Perhatikan bahwa __init__ hanya menyimpan daftar path file, sedangkan isi citra dibaca di __getitem__ setiap kali item diminta. Pola lazy (menunda pekerjaan sampai benar-benar dibutuhkan) ini membuat penyiapan dataset nyaris tanpa biaya, karena tidak ada citra yang dibuka di awal. Untuk dataset berisi puluhan ribu file, memuat semuanya ke memori sejak awal justru tidak mungkin, sehingga pola ini menjadi pilihan bawaan yang sehat.

Pillow mendukung pola ini secara alami. Pemanggilan Image.open hanya membaca kepala file dan menunda pembacaan isi piksel sampai data benar-benar digunakan, dan pemanggilan transformasi di dalam __getitem__ justru memaksa pembacaan itu terjadi pada saat datanya dibutuhkan. Dataset kecil boleh saja memuat semua citra di __init__ agar aksesnya lebih cepat, tetapi keputusan itu sebaiknya ditunda sampai profil menunjukkan pembacaan file sebagai hambatan.

Dataset teks dari daftar#

Kontrak yang sama berlaku untuk teks: __getitem__ cukup mengembalikan pasangan teks yang sudah diproses dan labelnya. Contoh berikut menggunakan daftar ulasan film kecil yang tertanam di kode, dengan tokenisasi paling sederhana berupa pemisahan kata pada spasi.

python Contoh: Dataset teks dari daftar pasangan
from torch.utils.data import Dataset


class DatasetUlasan(Dataset):
    """Dataset teks kecil di atas daftar pasangan (teks, label)."""

    def __init__(self):
        self.data = [
            ("film ini menyenangkan untuk ditonton", 1),
            ("animasinya indah dan ceritanya rapi", 1),
            ("durasi film ini terlalu panjang", 0),
            ("ceritanya sulit diikuti dari awal", 0),
        ]

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

    def __getitem__(self, idx):
        teks, label = self.data[idx]
        kata = teks.split()  # tokenisasi paling sederhana: dipisah spasi
        return kata, label


dataset = DatasetUlasan()
print("Jumlah ulasan:", len(dataset))
kata, label = dataset[1]
print("Kata:", kata)
print("Label:", label)
text Keluaran
Jumlah ulasan: 4
Kata: ['animasinya', 'indah', 'dan', 'ceritanya', 'rapi']
Label: 1

Item teks berupa daftar kata, bukan tensor, dan itu sah menurut kontrak dataset. Untuk masuk ke model, daftar kata itu masih perlu dipetakan menjadi indeks kosakata dan dipadatkan panjangnya, yang dibahas pada modul Sekuens dan Attention. Bagian yang berubah hanyalah isi __getitem__, sedangkan kontraknya tetap.

Kesalahan umum#

Daftar file tidak diurutkan. Tanpa sorted(), urutan item bergantung pada urutan yang diberikan sistem file, sehingga pembagian data latih dan uji serta hasil pengacakan berubah antara mesin. Perbaikannya dengan mengurutkan daftar di __init__, atau mengurutkan menurut atribut file lain yang stabil seperti tanggal perekaman.

Seluruh citra dimuat di __init__. Pola eager pada dataset besar menghabiskan memori dan memperlambat waktu mulai program sebelum pelatihan berjalan. Perbaikannya dengan menyimpan daftar path dan membaca isinya di __getitem__, sebagaimana pola lazy pada bagian sebelumnya.

Transformasi diterapkan dua kali. Normalisasi yang dipasang di __getitem__ dan dipanggil lagi di luar dataset menggeser rentang nilai dua kali, dan hasilnya keliru tanpa pesan error. Perbaikannya dengan menetapkan satu tempat pemanggilan transformasi, sebaiknya di dalam dataset melalui parameter transform.

Ringkasan#

  • Dataset kustom mengikuti kontrak yang sama: __init__ menyiapkan daftar item yang diurutkan, __len__ menghitung jumlah item, dan __getitem__ membaca serta mentransformasi satu item.
  • Pola lazy menyimpan daftar path di __init__ dan menunda pembacaan file sampai item diminta, sehingga dataset besar tetap ringan.
  • Kontrak yang sama mencakup teks: __getitem__ mengembalikan pasangan teks terproses dan label, dan pemetaan lanjutannya menjadi indeks kosakata dikerjakan pada modul Sekuens dan Attention.

Referensi#