Supervised learning menuntut pasangan masukan-label untuk setiap contoh pelatihan, dan label itulah yang mahal: foto bisa diambil jutaan per hari, tetapi memberi keterangan “kucing” atau “bukan kucing” pada tiap foto tetap membutuhkan manusia. Self-supervised learning membalik logika tersebut dengan memakai data mentah untuk membuat targetnya sendiri — sebuah bagian data disembunyikan atau diubah, lalu model diminta memulihkannya. Strategi inilah yang membuat model bahasa dan model visi modern dilatih pada miliaran contoh tanpa satu pun anotasi manusia. Chapter ini membandingkan kedua paradigma dengan satu dataset yang sama, lalu menunjukkan bahwa sinyal supervisi bisa diproduksi otomatis dari struktur data itu sendiri.
Label Mahal, Data Mentah Melimpah #
Perbedaan paling mendasar antara kedua paradigma terletak pada sumber sinyal supervisi. Tabel berikut merangkum kontrasnya:
| Aspek | Supervised | Self-Supervised |
|---|---|---|
| Sumber label | anotasi manusia | dibuat otomatis dari data itu sendiri |
| Skala data | terbatas anggaran pelabelan | sebesar data mentah yang tersedia |
| Biaya per contoh | tinggi (menit-jam per item kompleks) | nyaris nol setelah pipeline berjalan |
| Contoh tugas | klasifikasi foto berlabel | menebak bagian yang disembunyikan |
Skala inilah alasan utamanya. Model seperti BERT dan varian vision transformer modern dilatih pada ratusan juta hingga miliaran contoh; angka tersebut mustahil dicapai lewat anotasi manual. Konsepnya kita sudah sambut di chapter Transfer Learning : model besar yang sudah belajar dari data melimpah bisa dipakai ulang untuk tugas berlabel sedikit.
Tugas Pretext: Target dari Data Sendiri #
Cara paling sederhana memahami mekanisme ini adalah membayangkan setiap vektor data dibelah dua: setengah kiri menjadi masukan, setengah kanan menjadi target. Tidak ada satu pun angka baru yang perlu dianotasi — target diambil langsung dari kolom data yang sudah ada. Pola ini disebut tugas pretext (pretext task): tugas buatan yang targetnya gratis, tetapi menuntut model memahami struktur data untuk menyelesaikannya. Alur lengkap dari pretext ke pemakaian nyata tampak seperti ini:
Diagram di atas memperlihatkan dua tahap yang terpisah: pretraining memakai data mentah tanpa label, sedangkan tugas downstream (klasifikasi, pencarian, dan sebagainya) baru menyentuh label dalam jumlah kecil. Encoder hasil tahap pertama membawa pengetahuan struktur data ke tahap kedua.
Demo: Sinyal Gratis pada Dataset yang Sama #
Kita pakai 300 titik 2D dari tiga klaster. Bagian supervised melatih klasifikasi tiga kelas dengan anggaran label 6, 30, dan 60 — lalu bagian self-supervised membelah setiap titik [x, y] menjadi masukan x dan target y tanpa memakai label kelas sama sekali:
"""Supervised vs self-supervised pada dataset titik 2D yang sama.
Bagian A (supervised): klasifikasi 3 klaster memakai label kelas.
Tiga anggaran label diuji (6/30/60 label); setiap anggaran dirata-rata
atas 10 pengambilan sampel label agar tidak menipu karena kebetulan.
Bagian B (self-supervised): label kelas SAMA SEKALI tidak dipakai.
Setiap vektor [x, y] dibelah: setengah kiri (x) jadi masukan,
setengah kanan (y) jadi target prediksi -- target dibuat dari data
sendiri lewat masking, tanpa satu pun anotasi manusia.
Baseline pembanding: prediksi rata-rata y.
Diuji di CPU dengan PyTorch 2.14, seed tetap, deterministik.
"""
import torch
import torch.nn as nn
torch.manual_seed(7)
# ---------- 1. Data: 300 titik dari 3 klaster (belum berlabel) ----------
# tiap klaster: x ~ N(cx, 0.5); y = cy + 0.5*(x - cx) + derau 0.3
PUSAT = torch.tensor([[-1.5, -1.2], [0.0, 0.35], [1.5, 1.9]])
N_KLASTER, PER_KLASTER = 3, 100
titik = []
for c in range(N_KLASTER):
dx = 0.5 * torch.randn(PER_KLASTER)
dy = 0.5 * dx + 0.3 * torch.randn(PER_KLASTER)
titik.append(torch.stack([PUSAT[c, 0] + dx, PUSAT[c, 1] + dy], dim=1))
data = torch.cat(titik) # (300, 2)
label = torch.repeat_interleave(torch.arange(3), PER_KLASTER)
acak = torch.randperm(data.shape[0])
data, label = data[acak], label[acak]
latih, uji = data[:250], data[250:]
label_latih, label_uji = label[:250], label[250:]
print(f"data mentah (tanpa label) : {tuple(data.shape)} titik, {N_KLASTER} klaster")
print(f" latih: {tuple(latih.shape)} uji: {tuple(uji.shape)}")
print()
# ---------- 2. Supervised: anggaran label mahal ----------
print("=== Supervised: klasifikasi klaster, butuh label beranotasi ===")
def akurasi_anggaran(n_label, ambil=10):
"""Rata-rata akurasi uji dari `ambil` sampel label berukuran n_label."""
total = 0.0
for s in range(ambil):
g = torch.Generator().manual_seed(100 + s)
idx = torch.randperm(latih.shape[0], generator=g)[:n_label]
X, y = latih[idx], label_latih[idx]
model = nn.Linear(2, 3)
opt = torch.optim.Adam(model.parameters(), lr=0.05)
for _ in range(300):
loss = nn.CrossEntropyLoss()(model(X), y)
opt.zero_grad(); loss.backward(); opt.step()
with torch.no_grad():
total += (model(uji).argmax(1) == label_uji).float().mean().item()
return total / ambil
for n_label in (6, 30, 60):
print(f" {n_label:2d} label -> akurasi uji rata-rata "
f"{akurasi_anggaran(n_label):.3f}")
print()
# ---------- 3. Self-supervised: belah vektor, prediksi setengah kanan ----------
print("=== Self-supervised: prediksi y dari x, 0 label ===")
kiri, kanan = latih[:, :1], latih[:, 1:] # setengah kiri / kanan
kiri_uji, kanan_uji = uji[:, :1], uji[:, 1:]
print(f" masukan : setengah kiri (kolom x) {tuple(kiri.shape)}")
print(f" target : setengah kanan (kolom y) {tuple(kanan.shape)}")
print(f" baris-1 : kiri={kiri[0,0]:+.3f} -> target={kanan[0,0]:+.3f}")
print(f" baris-2 : kiri={kiri[1,0]:+.3f} -> target={kanan[1,0]:+.3f}")
print(" (target diproduksi otomatis lewat masking, bukan anotasi)")
print()
prediktor = nn.Sequential(nn.Linear(1, 32), nn.Tanh(), nn.Linear(32, 1))
opt = torch.optim.Adam(prediktor.parameters(), lr=0.01)
for _ in range(400):
loss = nn.MSELoss()(prediktor(kiri), kanan)
opt.zero_grad(); loss.backward(); opt.step()
with torch.no_grad():
tebakan_rata = kanan.mean().expand_as(kanan_uji)
mse_baseline = nn.MSELoss()(tebakan_rata, kanan_uji).item()
mse_model = nn.MSELoss()(prediktor(kiri_uji), kanan_uji).item()
print(f" MSE baseline (tebak rata-rata y) : {mse_baseline:.4f}")
print(f" MSE model (x -> y) : {mse_model:.4f}")
print(f" rasio penurunan galat : {mse_baseline / mse_model:.1f}x")
print()
print("RINGKASAN: supervised memakai 6/30/60 label beranotasi;")
print("self-supervised memakai 0 label pada 250 titik yang sama.")Output yang Dihasilkan:
data mentah (tanpa label) : (300, 2) titik, 3 klaster
latih: (250, 2) uji: (50, 2)
=== Supervised: klasifikasi klaster, butuh label beranotasi ===
6 label -> akurasi uji rata-rata 0.832
30 label -> akurasi uji rata-rata 0.954
60 label -> akurasi uji rata-rata 0.954
=== Self-supervised: prediksi y dari x, 0 label ===
masukan : setengah kiri (kolom x) (250, 1)
target : setengah kanan (kolom y) (250, 1)
baris-1 : kiri=+1.553 -> target=+1.973
baris-2 : kiri=-0.117 -> target=+0.382
(target diproduksi otomatis lewat masking, bukan anotasi)
MSE baseline (tebak rata-rata y) : 1.8557
MSE model (x -> y) : 0.1596
rasio penurunan galat : 11.6x
RINGKASAN: supervised memakai 6/30/60 label beranotasi;
self-supervised memakai 0 label pada 250 titik yang sama.Mari kita telaah kode di atas baris per baris:
- Baris 21-35 (data): tiga klaster dibangun dengan korelasi internal
y = cy + 0.5·(x − cx)plus derau, sehingga hubungan kiri-kanan benar-benar ada untuk dipelajari.torch.randpermmengacak urutan sebelum dibagi 250 latih / 50 uji. - Baris 44-58 (
akurasi_anggaran): tiap anggaran label diuji 10 kali dengan sampel label berbeda (generator berbeda seed), lalu dirata-rata — mencegah kesimpulan menipu dari satu kebetulan sampel. - Baris 61-63: tiga anggaran label dibandingkan. Dengan 6 label akurasi baru 0.832; label 30 dan 60 sudah saturasi di 0.954 — tambahan label tidak lagi menolong pada dataset mainan ini.
- Baris 68-75 (pembelahan):
latih[:, :1]mengambil kolom x sebagai masukan danlatih[:, 1:]kolom y sebagai target. Dua baris contoh dicetak supaya pembaca melihat pasangan masukan-target yang terbentuk tanpa anotasi. - Baris 77-89 (prediktor dan evaluasi): MLP kecil 1→32→1 dilatih meminimalkan MSE, lalu diuji terhadap baseline menebak rata-rata y. Galat turun 11.6x — bukti bahwa sinyal supervisi dari data sendiri sungguh informatif.
Membaca Angka Kedua Paradigma #
Bagian supervised memperlihatkan kurva klasik: akurasi naik tajam dari 6 ke 30 label, lalu mendatar. Bagian self-supervised menunjukkan hal yang berbeda — tanpa satu label pun, galat prediksi turun lebih dari sepuluh kali lipat dibanding menebak rata-rata. Model yang belajar memprediksi y dari x otomatis menangkap struktur tiga klaster, karena memang hanya dengan memahami struktur itu prediksinya bisa akurat.
Prediksi setengah kanan dari setengah kiri hanyalah satu dari banyak pretext task yang mungkin. Varian yang jauh lebih berdampak menyamakan dua tampilan dari objek yang sama dan mendorong tampilan objek lain menjauh — pola tarik-dorong itulah yang kita bahas di chapter berikutnya, Contrastive Learning.