Kata “representation” sudah muncul berulang kali di section ini tanpa definisi operasional. Representation adalah bentuk data setelah diolah sejumlah layer — fitur per-tingkat yang baik membuat struktur data (klaster, manifold, kelas) makin mudah dipisahkan. Representation learning adalah seni melatih jaringan agar fitur-fitur itu menjadi baik, baik lewat label (supervised), lewat pretext task (self-supervised), atau keduanya. Pertanyaan praktisnya segera muncul: bagaimana mengukur keseberapa baik sebuah representation? Chapter ini memperkenalkan alat ukurnya — linear probe di atas backbone beku — lalu memakai alat itu untuk membandingkan fitur tiap layer jaringan terlatih melawan jaringan berbobot acak.
Fitur Berjenjang dan Cara Mengukurnya #
Sebuah jaringan saraf bisa dibaca sebagai rantai transformasi: masukan mentah → fitur layer-1 → fitur layer-2 → … → keluaran. Setiap tingkat punya klaim kualitas yang berbeda, dan klaim itu harus diukur dengan perlakuan yang setara supaya perbandingannya adil. Linear probe menjawabnya: bekukan seluruh jaringan (frozen backbone), ambil fitur pada tingkat tertentu, lalu latih hanya satu lapisan linear kecil — biasanya regresi logistik — di atasnya. Akurasi probe menjadi skor kualitas fitur: jika struktur kelas sudah terpisah linier pada fitur tersebut, probe kecil pun berhasil.
Mengapa harus linear? Karena probe yang kuat bisa menutupi kelemahan fitur: dengan kapasitas cukup, probe non-linear menyelesaikan banyak hal sendirian sehingga skor tidak lagi mencerminkan kualitas representation. Konvensi linear ini juga dipakai luas di literatur untuk menilai pretrained model — dari fitur ImageNet sampai encoder kontras seperti chapter Contrastive Learning. Sumber-sumber representation yang umum dirangkum berikut:
| Sumber representation | Cara diperoleh | Sifat khas |
|---|---|---|
| Fitur mentah (piksel, koordinat) | tanpa pelatihan | struktur asli belum terlipat |
| Fitur rancangan manual (SIFT, HOG) | dirancang manusia | domain-spesifik, tidak belajar |
| Layer-N jaringan terlatih | supervised pada tugas apa pun | makin dalam makin terpisah linier |
| Encoder hasil pretraining SSL | contrastive / masked modeling | label tak dibutuhkan |
Demo: Probe Berjenjang pada Spiral Tiga Lengan #
Data kita berupa spiral tiga lengan 2D — tiga kelas yang batasnya melingkar sehingga tak mungkin dipisah garis lurus di ruang mentah. Sebuah MLP 2→32→32→3 dilatih terawasi penuh; sebagai efek samping, jaringan itu juga membangun representation di tiap layernya. Probe 60 label kemudian mengukur fitur mentah, fitur layer-1, dan fitur layer-2 — masing-masing dibandingkan dengan jaringan identik berbobot acak:
"""Representation learning: fitur per layer dan pengukuran linear probe.
Data: spiral tiga lengan 2D (tiga kelas; batasnya melingkar sehingga
tidak bisa dipisahkan garis lurus di ruang mentah). Sebuah MLP
2->32->32->3 dilatih terawasi penuh; sebagai efek sampingnya jaringan
membangun representation berjenjang: layer pertama mulai melipat
ruang, layer kedua membuat kelas nyaris terpisah linier. Kualitas
tiap level diukur dengan linear probe: LogisticRegression memakai
60 label pada fitur yang DIBEKUKAN (frozen backbone). Pembanding:
jaringan berarsitektur sama dengan bobot acak. Diuji di CPU dengan
PyTorch 2.14, seed tetap.
"""
import torch
import torch.nn as nn
from sklearn.linear_model import LogisticRegression
torch.manual_seed(0)
# ---------- 1. Data: spiral tiga lengan tanpa garis pemisah lurus ----------
import math
N_PER, DERAU = 300, 0.12
PUTAR = 3 * math.pi
titik, kelas = [], []
for c in range(3):
t = torch.rand(N_PER) * PUTAR
r = 0.3 + (t / PUTAR) * 1.9 # jari-jari tumbuh seiring sudut
th = t + c * 2 * math.pi / 3 # offset antar lengan
x = r * torch.cos(th) + DERAU * torch.randn(N_PER)
y = r * torch.sin(th) + DERAU * torch.randn(N_PER)
titik.append(torch.stack([x, y], dim=1))
kelas.append(torch.full((N_PER,), c, dtype=torch.long))
X = torch.cat(titik)
y = torch.cat(kelas)
acak = torch.randperm(X.shape[0])
X, y = X[acak], y[acak]
X_latih, y_latih = X[:600], y[:600]
X_uji, y_uji = X[600:], y[600:]
print(f"data : {X_latih.shape[0]} latih + {X_uji.shape[0]} uji, 3 kelas spiral")
print(f"lengan berputar {PUTAR / math.pi:.1f}x pi, derau {DERAU}")
print()
# ---------- 2. MLP dengan pintu fitur per layer ----------
class MLPBertingkat(nn.Module):
"""MLP 2->32->32->3 yang bisa memuntaskan fitur tiap layer."""
def __init__(self):
super().__init__()
self.l1 = nn.Linear(2, 32)
self.l2 = nn.Linear(32, 32)
self.l3 = nn.Linear(32, 3)
def fitur(self, x):
"""Kembalikan fitur mentah, setelah layer-1, setelah layer-2."""
z1 = torch.relu(self.l1(x))
z2 = torch.relu(self.l2(z1))
return x, z1, z2
def forward(self, x):
_, _, z2 = self.fitur(x)
return self.l3(z2)
def latih_mlp(seed):
torch.manual_seed(seed)
model = MLPBertingkat()
opt = torch.optim.Adam(model.parameters(), lr=0.01)
for _ in range(1500):
loss = nn.CrossEntropyLoss()(model(X_latih), y_latih)
opt.zero_grad(); loss.backward(); opt.step()
return model
model_terlatih = latih_mlp(seed=7)
with torch.no_grad():
acc = (model_terlatih(X_uji).argmax(1) == y_uji).float().mean().item()
print(f"MLP terlatih penuh : akurasi uji {acc:.3f} (1500 epoch, 600 label)")
model_acak = MLPBertingkat()
torch.manual_seed(99)
for p in model_acak.parameters(): # bobot acak, tidak pernah dilatih
torch.nn.init.normal_(p, std=0.5)
print()
# ---------- 3. Linear probe: 60 label pada fitur yang dibekukan ----------
NAMA_LEVEL = ["fitur mentah (x, y) ", "setelah layer-1 (32 fitur)",
"setelah layer-2 (32 fitur)"]
def probe(fitur_latih, fitur_uji, n_label=60, ambil=5):
"""Rata-rata akurasi probe LogisticRegression dari `ambil` sampel label."""
total = 0.0
for s in range(ambil):
g = torch.Generator().manual_seed(300 + s)
idx = torch.randperm(fitur_latih.shape[0], generator=g)[:n_label]
clf = LogisticRegression(max_iter=2000)
clf.fit(fitur_latih[idx].numpy(), y_latih[idx].numpy())
total += clf.score(fitur_uji.numpy(), y_uji.numpy())
return total / ambil
print("=== Linear probe 60 label (fitur dibekukan, rata-rata 5 sampel) ===")
print(f"{'sumber fitur':28s} {'bobot acak':>11s} {'terlatih':>9s}")
with torch.no_grad():
f_acak = model_acak.fitur(X_latih), model_acak.fitur(X_uji)
f_trai = model_terlatih.fitur(X_latih), model_terlatih.fitur(X_uji)
for lv in range(3):
a = probe(f_acak[0][lv], f_acak[1][lv])
t = probe(f_trai[0][lv], f_trai[1][lv])
print(f"{NAMA_LEVEL[lv]:28s} {a:11.3f} {t:9.3f}")
print()
# ---------- 4. Jumlah bobot yang dilatih: probe vs jaringan penuh ----------
print("=== Biaya: berapa bobot yang ikut dilatih ===")
n_prosen = sum(p.numel() for p in model_terlatih.l3.parameters())
n_semua = sum(p.numel() for p in model_terlatih.parameters())
print(f" jaringan penuh (fine-tune) : {n_semua} bobot")
print(f" kepala probe saja (frozen) : {n_prosen} bobot "
f"({100 * n_prosen / n_semua:.1f}% dari total)")Output yang Dihasilkan:
data : 600 latih + 300 uji, 3 kelas spiral
lengan berputar 3.0x pi, derau 0.12
MLP terlatih penuh : akurasi uji 0.897 (1500 epoch, 600 label)
=== Linear probe 60 label (fitur dibekukan, rata-rata 5 sampel) ===
sumber fitur bobot acak terlatih
fitur mentah (x, y) 0.388 0.388
setelah layer-1 (32 fitur) 0.370 0.425
setelah layer-2 (32 fitur) 0.405 0.821
=== Biaya: berapa bobot yang ikut dilatih ===
jaringan penuh (fine-tune) : 1251 bobot
kepala probe saja (frozen) : 99 bobot (7.9% dari total)Mari kita telaah kode di atas baris per baris:
- Baris 23-41 (spiral): tiap lengan dibangun dari sudut acak
tdengan jari-jari yang tumbuh seiringtplus offset 120° antar kelas, lalu derau kecil — menghasilkan tiga kurva yang saling melingkar dan mustahil dipisah garis lurus. - Baris 46-63 (
MLPBertingkat): metodefiturmengembalikan tiga tingkat sekaligus — masukan mentah, keluaran ReLU layer-1, keluaran ReLU layer-2 — sementaraforwardmemakai jalur yang sama sampai kepalal3. Arsitektur identik memastikan perbandingan probe adil. - Baris 76-83 (dua jaringan): jaringan terlatih dilatih 1500 epoch penuh label; jaringan acak hanya diinisialisasi ulang dengan
torch.nn.init.normal_tanpa pernah belajar. - Baris 92-98 (
probe): lima sampel label berbeda (seed 300+s), masing-masing melatihLogisticRegressionpada 60 label dan diuji ke 300 titik uji — fitur jaringan tidak pernah diubah. - Baris 116-121 (biaya): probe hanya menyentuh bobot kepala
l3(99 dari 1251, kurang dari 8%), itulah arti praktis “backbone beku”.
Membaca Tabel Probe #
Tiga angka pada kolom terlatih membentuk tangga yang jelas: 0.388 pada fitur mentah, 0.425 setelah layer-1, 0.821 setelah layer-2. Kolom bobot acak nyaris datar di sekitar 0.4 — transformasi linear acak plus ReLU tidak melipat spiral secara berarti. Jadi bukan jumlah parameter yang menentukan kualitas fitur, melainkan pelatihannya: hanya jaringan yang belajar tugas yang menggeser struktur kelas menjadi terpisah linier, dan pergeseran itu baru tuntas di layer kedua. Menarik juga membandingkan probe 0.821 dengan jaringan penuh 0.897 — kepala l3 yang dilatih 600 label memang lebih tajam daripada probe 60 label, tetapi jaraknya kecil: hampir seluruh kemampuan jaringan ternyata tersimpan di representation-nya, bukan di kepalanya.
Dengan fitur yang sudah terbukti bagilah pretrained model modern dibagikan ke publik: pengguna membekukan backbone, melatih kepala kecil, atau membuka seluruh bobot untuk fine-tuning. Tiga level pemakaian itu — lengkap dengan biaya dan risikonya — menjadi topik chapter penutup section ini: Penggunaan Pretrained Representation.