Pengenalan generative model menutup dengan satu pertanyaan: bagaimana jaringan neural mempelajari representasi data yang cukup baik untuk dibangkitkan ulang? Langkah pertamanya bukan menghasilkan data, melainkan belajar menyimpannya secara hemat. Autoencoder adalah jaringan yang dilatih menyalin masukannya sendiri — tugas yang tampak sepele, tetapi menjadi bermakna karena salinan harus melewati ruang sempit bernama bottleneck. Agar rekonstruksi tetap akurat melalui jalur sempit itu, jaringan terpaksa menemukan struktur terpenting data. Chapter ini membangun autoencoder linear kecil dengan PyTorch, membuktikan loss rekonstruksinya turun sampai mendekati batas teoretis PCA, lalu memakai error rekonstruksi sebagai alat deteksi anomali.
Struktur Encoder, Bottleneck, dan Decoder #
Autoencoder terdiri dari dua jaringan yang berbagi satu titik temu. Encoder memetakan data x menjadi kode laten z berdimensi lebih kecil; decoder memetakan z kembali menjadi rekonstruksi x̂. Seluruh pasangan dilatih dengan satu loss — mean squared error antara x dan x̂, loss yang sudah kita bedah di chapter Loss Function
:
Tidak ada label yang perlu ditebak — sinyal latihannya adalah data itu sendiri, sehingga autoencoder tergolong self-supervised. Dimensi z yang lebih kecil memaksa jaringan membuang bagian data yang paling mudah direduksi. Pada kasus linear murni, teori mengatakan hasil optimalnya persis PCA: proyeksi ke subspace varians terbesar, dengan error minimum sama dengan varians yang dibuang.
Demo: Autoencoder Linear 2D ke 1D #
Kita memakai data 2D berkorelasi kuat — sebuah awan memanjang — lalu memampatkannya menjadi satu angka. Berkas autoencoder_mini.py melatih pasangan Linear(2,1) dan Linear(1,2) berisi total 7 parameter:
"""Autoencoder linear mini: kompresi 2D -> 1D -> 2D dengan PyTorch.
Encoder memampatkan data 2D menjadi 1 angka (kode z), decoder
merekonstruksi 2D dari z. Loss rekonstruksi MSE diturunkan dengan Adam
pada data berkorelasi kuat, lalu hasilnya dibandingkan dengan PCA
(subspace optimal untuk kasus linear). Sebagai aplikasi kedua, error
rekonstruksi dipakai untuk deteksi anomali: data uji normal vs titik
acak seragam. Deterministik (seed tetap), diuji di CPU, PyTorch 2.14.
"""
import numpy as np
import torch
import torch.nn as nn
torch.manual_seed(0)
rng = np.random.default_rng(7)
# ---------- 1. Data 2D berkorelasi kuat ----------
KOV = np.array([[2.5, 1.8], [1.8, 1.7]])
MU = np.array([3.0, 5.0])
X_latih = rng.multivariate_normal(MU, KOV, size=600)
X_uji = rng.multivariate_normal(MU, KOV, size=200)
eigval, eigvec = np.linalg.eigh(np.cov(X_latih.T))
pc1 = eigvec[:, int(np.argmax(eigval))] # arah komponen utama
var_sisa = float(eigval.min()) # varians di arah minor
print(f"data: 600 latih + 200 uji, 2D berkorelasi {np.corrcoef(X_latih.T)[0, 1]:.3f}")
print(f"PCA acuan: varians utama {eigval.max():.3f}, varians sisa {var_sisa:.3f}")
print()
# ---------- 2. Model: encoder Linear(2->1), decoder Linear(1->2) ----------
class Autoencoder(nn.Module):
def __init__(self):
super().__init__()
self.encoder = nn.Linear(2, 1)
self.decoder = nn.Linear(1, 2)
def forward(self, x):
return self.decoder(self.encoder(x))
ae = Autoencoder()
n_param = sum(p.numel() for p in ae.parameters())
print(f"arsitektur: 2 -> 1 -> 2 (linear), {n_param} parameter")
print()
Xt_latih = torch.tensor(X_latih, dtype=torch.float32)
Xt_uji = torch.tensor(X_uji, dtype=torch.float32)
# ---------- 3. Training: minimalkan error rekonstruksi ----------
opt = torch.optim.Adam(ae.parameters(), lr=0.05)
EPOCH = 300
print("=== Pelatihan (MSE per titik = ||x - x_rekon||^2) ===")
for ep in range(EPOCH):
rekon = ae(Xt_latih)
loss = ((rekon - Xt_latih) ** 2).sum(dim=1).mean()
opt.zero_grad()
loss.backward()
opt.step()
if ep % 100 == 0 or ep == EPOCH - 1:
print(f" epoch {ep:3d} loss = {loss.item():.4f}")
print()
# ---------- 4. Evaluasi rekonstruksi vs PCA ----------
with torch.no_grad():
rekon_uji = ae(Xt_uji)
mse_uji = ((rekon_uji - Xt_uji) ** 2).sum(dim=1).mean().item()
arah_decoder = ae.decoder.weight.detach().squeeze().numpy()
arah_decoder = arah_decoder / np.linalg.norm(arah_decoder)
cos = float(np.dot(arah_decoder, pc1))
print(f"MSE uji rekonstruksi : {mse_uji:.4f} (batas PCA: {var_sisa:.4f})")
print(f"arah decoder vs PC1 : cos = {cos:.4f}")
print()
with torch.no_grad():
z_contoh = ae.encoder(Xt_uji[:3]).squeeze(1)
rekon_contoh = ae.decoder(z_contoh[:, None])
print("=== Tiga contoh rekonstruksi (z = kode 1 dimensi) ===")
for i in range(3):
x_rekon = np.round(rekon_contoh[i].numpy().astype(np.float64), 2).tolist()
print(f" x = {X_uji[i].round(2).tolist()} -> z = {z_contoh[i].item():+.3f}"
f" -> x_rekon = {x_rekon}")
print()
# ---------- 5. Deteksi anomali lewat error rekonstruksi ----------
with torch.no_grad():
err_normal = ((ae(Xt_uji) - Xt_uji) ** 2).sum(dim=1).numpy()
X_acak = rng.uniform(MU - 6.0, MU + 6.0, size=(20, 2))
Xt_acak = torch.tensor(X_acak, dtype=torch.float32)
with torch.no_grad():
err_acak = ((ae(Xt_acak) - Xt_acak) ** 2).sum(dim=1).numpy()
ambang = float(np.quantile(err_normal, 0.95))
print("=== Deteksi anomali ===")
print(f"error data normal : rata-rata {err_normal.mean():.4f}, p95 {ambang:.4f}")
print(f"error data acak : rata-rata {err_acak.mean():.4f}")
print(f"ambang p95 normal -> anomali terdeteksi {(err_acak > ambang).sum()}/20,"
f" normal salah tandai {(err_normal > ambang).sum()}/200")Output yang Dihasilkan:
data: 600 latih + 200 uji, 2D berkorelasi 0.859
PCA acuan: varians utama 3.378, varians sisa 0.244
arsitektur: 2 -> 1 -> 2 (linear), 7 parameter
=== Pelatihan (MSE per titik = ||x - x_rekon||^2) ===
epoch 0 loss = 48.3007
epoch 100 loss = 0.6266
epoch 200 loss = 0.3452
epoch 299 loss = 0.2684
MSE uji rekonstruksi : 0.3503 (batas PCA: 0.2437)
arah decoder vs PC1 : cos = 0.9989
=== Tiga contoh rekonstruksi (z = kode 1 dimensi) ===
x = [5.05, 6.78] -> z = -8.351 -> x_rekon = [5.05, 6.63]
x = [3.94, 5.51] -> z = -6.728 -> x_rekon = [3.87, 5.61]
x = [5.98, 6.81] -> z = -9.231 -> x_rekon = [5.69, 7.19]
=== Deteksi anomali ===
error data normal : rata-rata 0.3503, p95 1.1635
error data acak : rata-rata 12.8063
ambang p95 normal -> anomali terdeteksi 14/20, normal salah tandai 10/200Mari kita telaah kode di atas baris per baris:
- Baris 15-16: dua seed tetap —
torch.manual_seed(0)untuk inisialisasi bobot,default_rng(7)untuk data, agar seluruh keluaran dapat direproduksi. - Baris 21-26: data 600 titik dari Gaussian 2D berkorelasi 0,859;
np.linalg.eighpada kovarian empiris menghitung acuan PCA — arah komponen utamapc1danvar_sisa, yaitu error terkecil yang mungkin dicapai pemampatan linear apa pun. - Baris 32-39: kelas
Autoencoder— encodernn.Linear(2, 1)dan decodernn.Linear(1, 2);forwardhanya merangkai keduanya, danzadalah keluaran encoder. - Baris 51-61: training loop standar dengan
torch.optim.Adam: hitung rekonstruksi, ukur||x - x̂||²rata-rata per titik, mundurkan gradien, perbarui bobot. Loss jatuh dari 48,30 ke 0,27 dalam 300 epoch. - Baris 65-72: evaluasi pada data uji — MSE 0,3503 berdiri dekat batas PCA 0,2437, dan arah garis decoder membentuk cos 0,9989 dengan komponen utama PCA: jaringan menemukan ulang PCA tanpa pernah diberi tahu rumusnya.
- Baris 75-82: tiga contoh nyata kompresi — titik
[5.05, 6.78]tersimpan sebagai satu angkaz = -8.351dan lahir kembali sebagai[5.05, 6.63]; selisih kecil itulah harga kompresi 2D menjadi 1D. - Baris 85-98: deteksi anomali — titik acak seragam pada kotak selebar data diumpankan ke model yang sama; kini error dibaca sebagai skor keanehan, dan ambang kuantil-95 data normal memisahkan keduanya.
Error Rekonstruksi sebagai Detektor Anomali #
Bagian keluaran terakhir memperlihatkan inti pemakaian praktis autoencoder. Data normal direkonstruksi dengan error rata-rata 0,3503 karena model mengenal bentuk awannya; titik acak rata-rata bererror 12,8063 — hampir 37 kali lebih besar — karena jatuh di wilayah yang tidak pernah dilihat encoder. Ambang 1,1635 (kuantil-95 error normal) menandai 14 dari 20 titik acak sebagai anomali dengan 10 dari 200 titik normal salah ditandai. Enam titik acak lolos bukan karena model gagal: titik-titik itu kebetulan jatuh dekat garis manifold yang dipelajari, sehingga memang tidak aneh menurut struktur data 2D ini.
| Kegunaan | Cara kerja | Catatan |
|---|---|---|
| Denoising | latih rekonstruksi dari masukan yang diberi derau | decoder belajar proyeksi ke manifold bersih |
| Deteksi anomali | error rekonstruksi sebagai skor keanehan | seperti demo di atas |
| Pra-pelatihan | encoder sebagai fitur awal tugas lain | populer sebelum era fine-tuning besar |
| Kompresi | kirim z, rekonstruksi di sisi penerima | lossy, mirip demo 2D ke 1D |
Keterbatasan: Ruang Laten yang Tak Terstruktur #
Autoencoder kita kini bisa mengompresi dan merekonstruksi, tetapi perhatikan ciri z: rentang nilainya (sekitar -10 sampai -4 pada data ini) ditentukan kebetulan pelatihan, bukan distribusi yang kita pilih. Kalau kita menarik z acak dari rentang itu, decoder menghasilkan titik-titik di sekitar garis manifold — untunglah. Tetapi pada data berdimensi tinggi, ruang laten autoencoder biasa penuh “lubang”: sebagian besar z acak menghasilkan keluaran yang tidak menyerupai data sama sekali. Autoencoder menghasilkan encoder yang berguna, tetapi belum menghasilkan model generatif yang bisa di-sampling. Menutup lubang itu — dengan memaksa z mengikuti distribusi yang kita kendalikan — adalah pekerjaan variational autoencoder, chapter berikutnya.