Eksperimen Dasar GANs (Generative Adversarial Networks) dengan Python
Apa itu GANs?
GANs (Generative Adversarial Networks) adalah arsitektur machine learning yang melibatkan dua jaringan saraf "bermusuhan":
- Generator: menghasilkan data palsu dari input random noise
- Discriminator: menilai apakah data real atau palsu
Keduanya dilatih bersamaan sampai generatormampu menghasilkan data yang tidak dapat dibedakan dari data asli oleh diskriminator.
Analoginya: generator adalah pemalsu uang, diskriminator adalah polisi. Keduanya saling meningkatkan skill: polisi lebih pintar mendeteksi, pemalsu lebih pintar menyamarkan. Akhirnya? Uang palsu nyaris tidak terbedakan.
1. Setup Environment
# Buat virtual environment
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
# Install packages
pip install torch torchvision torchaudio
pip install matplotlib numpy pillow
# Cek GPU (PyTorch)
python -c "import torch; print(torch.cuda.is_available())"
2. Dataset: MNIST Sederhana
MNIST berisi 70.000 gambar digit tulisan tangan (28x28 grayscale). Sempurna untuk eksperimen pertama.
import torch
import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader
# Transform: normalize ke range [-1, 1]
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# Load dataset
dataset = torchvision.datasets.MNIST(
root='./data',
train=True,
download=True,
transform=transform
)
dataloader = DataLoader(
dataset,
batch_size=64,
shuffle=True,
num_workers=2
)
3. Arsitektur Generator
Generator menerima noise random (latent vector) dan menghasilkan gambar 28x28.
import torch.nn as nn
class Generator(nn.Module):
def __init__(self, latent_dim=100):
super(Generator, self).__init__()
self.latent_dim = latent_dim
self.model = nn.Sequential(
# Input: (batch, latent_dim, 1, 1)
nn.ConvTranspose2d(latent_dim, 256, kernel_size=4, stride=1, padding=0, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(True),
nn.ConvTranspose2d(256, 128, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(128),
nn.ReLU(True),
nn.ConvTranspose2d(128, 64, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(True),
# Output: (batch, 1, 28, 28) — single channel grayscale
nn.ConvTranspose2d(64, 1, kernel_size=4, stride=2, padding=1, bias=False),
nn.Tanh() # Output range [-1, 1]
)
def forward(self, x):
# x: (batch, latent_dim, 1, 1)
return self.model(x)
4. Arsitektur Diskriminator
Diskriminator menerima gambar (real atau palsu) dan output probabilitas (0 = palsu, 1 = real).
class Discriminator(nn.Module):
def __init__(self):
super(Discriminator, self).__init__()
self.model = nn.Sequential(
# Input: (batch, 1, 28, 28)
nn.Conv2d(1, 64, kernel_size=4, stride=2, padding=1, bias=False),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(64, 128, kernel_size=4, stride=2, padding=1, bias=False),
nn.BatchNorm2d(128),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(128, 256, kernel_size=3, stride=2, padding=1, bias=False),
nn.BatchNorm2d(256),
nn.LeakyReLU(0.2, inplace=True),
# Output: 1 value (probability)
nn.Conv2d(256, 1, kernel_size=4, stride=1, padding=0, bias=False),
nn.Sigmoid() # Output range [0, 1]
)
def forward(self, x):
return self.model(x).view(-1, 1).squeeze(1)
5. Loss Function dan Optimizer
GAN menggunakan BCELoss (Binary Cross Entropy) untuk kedua jaringan.
import torch.optim as optim
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
latent_dim = 100
G = Generator(latent_dim).to(device)
D = Discriminator().to(device)
criterion = nn.BCELoss() # Binary Cross Entropy
# Optimizer — Adam dengan learning rate rendah dan beta1=0.5 (GAN best practice)
optimizerG = optim.Adam(G.parameters(), lr=0.0002, betas=(0.5, 0.999))
optimizerD = optim.Adam(D.parameters(), lr=0.0002, betas=(0.5, 0.999))
# Labels
real_label = 1.0
fake_label = 0.0
6. Training Loop
Inilah inti GAN training — alternating antara train diskriminator dan generator.
import matplotlib.pyplot as plt
import numpy as np
num_epochs = 50
fixed_noise = torch.randn(64, latent_dim, 1, 1, device=device) # Untuk visualisasi
img_list = []
for epoch in range(num_epochs):
for i, (real_images, _) in enumerate(dataloader):
batch_size = real_images.size(0)
real_images = real_images.to(device)
# ============ Train Discriminator ============
# max log(D(real)) + log(1 - D(G(z)))
D.zero_grad()
# Real images
labels_real = torch.full((batch_size,), real_label, device=device)
output_real = D(real_images)
lossD_real = criterion(output_real, labels_real)
lossD_real.backward()
# Fake images
noise = torch.randn(batch_size, latent_dim, 1, 1, device=device)
fake_images = G(noise)
labels_fake = torch.full((batch_size,), fake_label, device=device)
output_fake = D(fake_images.detach()) # detach: tidak train G di sini
lossD_fake = criterion(output_fake, labels_fake)
lossD_fake.backward()
lossD = lossD_real + lossD_fake
optimizerD.step()
# ============ Train Generator ============
# min log(1 - D(G(z))) atau max log(D(G(z)))
G.zero_grad()
labels_g = torch.full((batch_size,), real_label, device=device) # G ingin D(prediksi) = real
output_g = D(fake_images) # jangan detach — kembali gradient ke G
lossG = criterion(output_g, labels_g)
lossG.backward()
optimizerG.step()
# ============ Log progress ============
if i % 100 == 0:
print(f"Epoch [{epoch}/{num_epochs}] Batch [{i}] "
f"Loss_D: {lossD.item():.4f}, Loss_G: {lossG.item():.4f}")
# Save image sample tiap epoch
if epoch % 5 == 0:
with torch.no_grad():
fake = G(fixed_noise).cpu()
img_list.append(torchvision.utils.make_grid(fake, padding=2, normalize=True))
7. Visualisasi Hasil
Animasi dari noise → digit yang jelas.
fig = plt.figure(figsize=(8,8))
plt.axis("off")
ims = [[plt.imshow(np.transpose(i,(1,2,0)), animated=True)] for i in img_list]
import matplotlib.animation as animation
import IPython.display as ipd
ani = animation.ArtistAnimation(fig, ims, interval=200, repeat_delay=1000, blit=True)
ipd.HTML(ani.to_jshtml())
Image pertama muncul random noise. Setelah 50 epoch, gambar menyerupai digit tulisan tangan.
8. Mode Collapse: Masalah GANs
Mode collapse terjadi saat generator hanya menghasilkan variasi terbatas. Misal, generator hanya menghasilkan digit "1" padahal dataset punya 0-9.
Deteksi mode collapse:
- Loss D dan G mendekati 0 (over-converged)
- Generated samples terlihat identic
Solusi yang bisa dipraktikkan:
Wasserstein GAN (WGAN): gunakan loss berbeda (EM distance) - lebih stabil
# Ganti criterion dengan WLoss # Loss_D: E[D(real)] - E[D(fake)] (negative) # Loss_G: -E[D(fake)] # Ganti sigmoid output D: tidak ada activation (output langsung) # Optimizer: RMSprop lr=5e-5Label smoothing: real_label = 0.9 (bukan 1.0) — D tidak over-confident
Add noise to inputs: input D ditambah noise → konvergen lebih lambat dan stabil
Conditional GAN (cGAN): kondisikan G/D dengan class label — generator tahu digit apa yang harus dibuat
class CondGenerator(nn.Module):
def __init__(self, latent_dim=100, num_classes=10):
super().__init__()
# Embed label ke latent space
self.label_embedding = nn.Embedding(num_classes, num_classes)
# Latent + label → input
self.model = nn.Sequential(
nn.Linear(latent_dim + num_classes, 256),
# ...
)
def forward(self, noise, labels):
emb = self.label_embedding(labels)
x = torch.cat([noise, emb], dim=1)
return self.model(x)
9. Evaluasi: FID dan IS
Untuk mengukur kualitas generasi:
- FID (Fréchet Inception Distance): semakin rendah semakin baik. Mengukur jarak distribusi antara real dan fake.
- IS (Inception Score): semakin tinggi semakin baik. Mengukur kualitas dan diversity.
# Pakai library pytorch-fid
pip install pytorch-fid
python -m pytorch_fid real_images/ fake_images/
10. Tips Practical
- Batch size: 64-128 optimal. Terlalu kecil: tidak stabil. Terlalu besar: konvergen lambat
- Learning rate: 0.0002 untuk Adam adalah sweet spot. Tingkatkan perlahan ke 0.001 untuk eksperimen
- LeakyReLU: gunakan leak=0.2 di discriminator (mencegah sparse gradients)
- Stride convolutions: selalu gunakan stride 2 (atau 4) untuk downsampling — lebih baik dari pooling
- Batch normalization: wajib di generator dan diskriminator, kecuali layer output G dan input D
Kesimpulan
GANs adalah fundamental generatif AI yang powerful. Eksperimen pertama dengan MNIST adalah cara terbaik untuk memahami fundamental: musuh vs musuh, generator maju dengan feedback dari diskriminator, dan akhirnya menghasilkan data sintesis yang realistis.
Setelah menguasai DCGAN ini, eksplorasi lebih lanjut:
- Progressive GAN: training bertahap dari 4x4 hingga 1024x1024
- StyleGAN: kontrol style konten gambar
- CycleGAN: image-to-image translation tanpa paired data (horse ↔ zebra)
- BigGAN: high-fidelity class-conditional generation
Kode lengkap artikel ini tersedia di repo GitHub saya. Selamat berexperiment!