Chapter sebelumnya memperlihatkan diffusion sebagai gagasan: data dideraui bertahap, lalu prosesnya dibalik oleh jaringan. Kini kita formalisasikan kedua arah tersebut dengan notasi Gaussian yang menjadi bahasa standar seluruh literatur diffusion — notasi beta, alpha, dan alpha_bar yang sama dipakai DDPM, Stable Diffusion, hingga implementasi di library modern. Hasil formalisasi ini adalah satu rumus tertutup yang memungkinkan kita melompat dari x_0 ke x_t mana pun tanpa menjalankan t langkah satu per satu, dan satu persamaan reverse yang menjadi target pembelajaran jaringan. Chapter ini menutup semuanya dengan implementasi NumPy yang membuktikan kesetaraan kedua bentuk secara numerik.
Notasi: β, α, dan ᾱ #
Tiga simbol ini muncul di setiap rumus diffusion, jadi kita tempatkan lebih dulu:
| Simbol | Definisi | Makna |
|---|---|---|
β_t | variance derau pada langkah t | seberapa banyak derau baru ditambahkan |
α_t | 1 − β_t | porsi sinyal yang bertahan satu langkah |
ᾱ_t | α_1 · α_2 · … · α_t | porsi sinyal yang bertahan dari x_0 sampai x_t |
ε | sampel N(0, I) | derau yang menjadi target prediksi jaringan |
ε_θ(x_t, t) | keluaran denoiser | tebakan jaringan atas derau di dalam x_t |
ᾱ_t adalah hasil kali beruntun sehingga selalu menurun ke arah nol — itulah meteran penyusutan sinyal yang kita pakai pada chapter sebelumnya (SNR = ᾱ_t / (1 − ᾱ_t)). Jadwal derau (noise schedule) adalah pilihan nilai β_1 … β_T; jadwal linear DDPM menaikkan β_t dari 1e-4 ke 0.02 selama T = 1000 langkah.
Forward Process: Rantai Gaussian dan Bentuk Tertutup #
Satu langkah forward didefinisikan sebagai distribusi kondisional Gaussian:
q(x_t | x_{t-1}) = N(sqrt(1 − β_t) · x_{t-1}, β_t I)
Artinya: kalikan x_{t-1} dengan sqrt(1 − β_t) (sinyal menyusut sedikit), lalu tambahkan derau Gaussian baru ber-variance β_t. Derivasinya memakai satu sifat Gaussian dua kali: jika x ~ N(μ, σ²), maka a·x + b·ε berdistribusi N(a·μ, a²σ² + b²). Dengan aturan itu, tiga langkah pertama dapat diuraikan menjadi:
x_3 = sqrt(ᾱ_3) · x_0 + sqrt(α_3α_2β_1) · ε_1 + sqrt(α_3β_2) · ε_2 + sqrt(β_3) · ε_3
Tiga suku derau adalah jumlah variabel Gaussian independen, sehingga digabung menjadi satu Gaussian dengan variance α_3α_2β_1 + α_3β_2 + β_3 = 1 − ᾱ_3 (aljabar sederhana: α(1−β) + β = 1 berlaku berulang). Generalisasi untuk langkah ke-t menghasilkan bentuk tertutup (closed-form):
x_t = sqrt(ᾱ_t) · x_0 + sqrt(1 − ᾱ_t) · ε, ε ~ N(0, I)
Intuisinya: pada langkah t, amplitudo sinyal asli tersisa sqrt(ᾱ_t), amplitudo derau total mencapai sqrt(1 − ᾱ_t), dan keduanya berbagi satu sumber derau ε. Bentuk ini juga adalah reparameterisasi — trik yang sama dengan VAE pada chapter Variational Autoencoder
: sampel acak dikeluarkan dari rumus sehingga gradien dapat mengalir ke parameter.
Membuktikan Kedua Bentuk Cocok Secara Numerik #
Berkas forward_proses.py membuktikan kesetaraan kedua bentuk dalam dua bagian. Bagian A membuktikan pada satu titik bahwa tiga langkah iteratif sama persis dengan satu rumus tertutup memakai derau gabungan. Bagian B menjalankan T = 1000 langkah pada dua cluster 2D (512 titik) dan membandingkan statistik jalur iteratif terhadap sampel closed-form yang dibangun langsung dari x_0:
"""Forward diffusion: bentuk iteratif vs bentuk tertutup (closed-form).
Bagian A membuktikan pada satu titik bahwa tiga langkah kecil sama
persis dengan satu rumus tertutup yang memakai derau gabungan, serta
memeriksa identitas varians derau yang mendasarinya.
Bagian B menjalankan T = 1000 langkah pada dua cluster 2D dan
membandingkan statistik jalur iteratif terhadap sampel closed-form.
"""
import numpy as np
rng = np.random.default_rng(7)
T = 1000
beta = np.linspace(1e-4, 0.02, T)
alpha = 1.0 - beta
alpha_bar = np.cumprod(alpha)
# ---------- A. Tiga langkah kecil == satu rumus tertutup ----------
x0 = np.array([2.0, -2.0])
e1, e2, e3 = rng.standard_normal((3, 2))
x1 = np.sqrt(alpha[0]) * x0 + np.sqrt(beta[0]) * e1
x2 = np.sqrt(alpha[1]) * x1 + np.sqrt(beta[1]) * e2
x3 = np.sqrt(alpha[2]) * x2 + np.sqrt(beta[2]) * e3
derau_gabungan = (np.sqrt(alpha[2] * alpha[1] * beta[0]) * e1
+ np.sqrt(alpha[2] * beta[1]) * e2
+ np.sqrt(beta[2]) * e3)
x3_tertutup = np.sqrt(alpha_bar[2]) * x0 + derau_gabungan
print("=== A. Tiga langkah iteratif vs satu rumus tertutup ===")
print("x_3 iteratif :", np.round(x3, 6))
print("x_3 tertutup :", np.round(x3_tertutup, 6))
print("selisih maksimum :",
f"{np.max(np.abs(x3 - x3_tertutup)):.1e}")
varians_gabungan = (alpha[2] * alpha[1] * beta[0]
+ alpha[2] * beta[1] + beta[2])
print(f"varians derau gabungan : {varians_gabungan:.12f}")
print(f"1 - alpha_bar_3 : {1.0 - alpha_bar[2]:.12f}")
# ---------- B. Dua cluster 2D, T = 1000 langkah ----------
n_per = 256
cluster_a = 1.5 + 0.3 * rng.standard_normal((n_per, 2))
cluster_b = -1.5 + 0.3 * rng.standard_normal((n_per, 2))
x0_semua = np.vstack([cluster_a, cluster_b])
jarak_awal = np.linalg.norm(cluster_a.mean(axis=0) - cluster_b.mean(axis=0))
std_awal = x0_semua.std()
def jarak_pusat(x):
a = x[:n_per].mean(axis=0)
b = x[n_per:].mean(axis=0)
return np.linalg.norm(a - b)
# Jalur iteratif: benar-benar menjalankan 1000 langkah
x = x0_semua.copy()
snapshot = {0: x.copy()}
for t in range(1, T + 1):
eps = rng.standard_normal(x.shape)
x = np.sqrt(1.0 - beta[t - 1]) * x + np.sqrt(beta[t - 1]) * eps
if t in (250, 500, 1000):
snapshot[t] = x.copy()
# Jalur closed-form: x_t dibangun langsung dari x_0 dengan derau segar
def closed_form(t):
if t == 0:
return x0_semua.copy()
ab = alpha_bar[t - 1]
eps = rng.standard_normal(x0_semua.shape)
return np.sqrt(ab) * x0_semua + np.sqrt(1.0 - ab) * eps
print()
print("=== B. Dua cluster 2D (512 titik), T = 1000 ===")
print(f"jarak pusat awal : {jarak_awal:.3f} | std awal : {std_awal:.3f}")
print()
print(" t | mean iter | mean clsd | std iter | std clsd | std teori | jarak iter")
print("-------+-----------+-----------+----------+----------+-----------+-----------")
for t in (0, 250, 500, 1000):
xi = snapshot[t]
xc = closed_form(t)
ab = 1.0 if t == 0 else alpha_bar[t - 1]
std_teori = np.sqrt(ab * std_awal**2 + (1.0 - ab))
print(f" {t:5d} | {xi.mean():9.3f} | {xc.mean():9.3f} | "
f"{xi.std():8.3f} | {xc.std():8.3f} | {std_teori:9.3f} | "
f"{jarak_pusat(xi):10.3f}")
print()
print(f"jarak pusat pada t=1000 (teori sqrt(alpha_bar)*{jarak_awal:.3f} "
f"= {np.sqrt(alpha_bar[-1]) * jarak_awal:.3f})")
print(f"alpha_bar_1000 = {alpha_bar[-1]:.2e} -> x_1000 hampir murni N(0, I)")Output yang Dihasilkan:
=== A. Tiga langkah iteratif vs satu rumus tertutup ===
x_3 iteratif : [ 1.991274 -2.018132]
x_3 tertutup : [ 1.991274 -2.018132]
selisih maksimum : 8.9e-16
varians derau gabungan : 0.000359717016
1 - alpha_bar_3 : 0.000359717016
=== B. Dua cluster 2D (512 titik), T = 1000 ===
jarak pusat awal : 4.201 | std awal : 1.512
t | mean iter | mean clsd | std iter | std clsd | std teori | jarak iter
-------+-----------+-----------+----------+----------+-----------+-----------
0 | -0.023 | -0.023 | 1.512 | 1.512 | 1.512 | 4.201
250 | 0.008 | -0.019 | 1.285 | 1.327 | 1.294 | 3.037
500 | 0.033 | -0.019 | 1.020 | 1.055 | 1.049 | 1.023
1000 | -0.031 | -0.014 | 1.012 | 0.976 | 1.000 | 0.038
jarak pusat pada t=1000 (teori sqrt(alpha_bar)*4.201 = 0.027)
alpha_bar_1000 = 4.04e-05 -> x_1000 hampir murni N(0, I)Bagian A menghasilkan pembuktian eksak: kedua jalur memberi x_3 yang sama hingga selisih 8.9e-16 (batas presisi float64), dan identitas varians derau gabungan cocok sampai 12 digit desimal. Bagian B memperlihatkan konvergensi menuju N(0, I) dari dua jalur berbeda: mean mengendap ke sekitar nol, std menyusut dari 1.512 menuju 1.012 (iteratif) dan 0.976 (closed-form) dengan prediksi teori tepat 1.000, sementara jarak dua pusat cluster runtuh dari 4.201 menjadi 0.038 — teori sqrt(ᾱ) · 4.201 = 0.027. Selisih kecil antar jalur wajar karena kedua jalur memakai realisasi derau berbeda; distribusinya yang identik. Mari kita telaah kode di atas baris per baris:
- Baris 12 (
default_rng(7)): seluruh derau ditarik dari satu generator ber-seed; urutan penarikan tetap sehingga keluaran reproducible. - Baris 14-17 (jadwal):
np.cumprodmenghitungᾱ_tsekaligus untuk semuat— hasil kali beruntunα_1 … α_t, diindeks dari nol. - Baris 20-25 (Bagian A): satu titik
x_0 = (2, −2)dideraui tiga langkah secara eksplisit, satu baris per langkah, persis mengikuti definisiq(x_t | x_{t-1}). - Baris 27-30 (derau gabungan): tiga suku derau dijumlahkan dengan bobot
sqrt(α_3α_2β_1),sqrt(α_3β_2),sqrt(β_3)— bobot yang muncul saat ketiga update disubstitusikan bertahap.x3_tertutuplalu dibangun dengan bentuksqrt(ᾱ_3)·x_0 + derau gabungan. - Baris 37-40 (identitas varians): jumlah kuadrat bobot derau dibandingkan langsung dengan
1 − ᾱ_3; keduanya0.000359717016— bukti angka bahwa derau terakumulasi tepat menjadi satu Gaussian ber-variance1 − ᾱ_t. - Baris 43-48 (data): dua cluster 2D masing-masing 256 titik di sekitar
(1.5, 1.5)dan(−1.5, −1.5); jarak antar pusat empirisnya4.201. - Baris 56-62 (jalur iteratif): loop
T = 1000mengimplementasikan definisi forward mentah-mentah — derau segar per langkah untuk seluruh 512 titik sekaligus lewat broadcasting. - Baris 65-70 (
closed_form): jalur pembanding melompat langsung darix_0kex_tmemakai bentuk tertutup dengan derau segar; tanpanp.cumproddan bentuk ini, kita harus menjalankantlangkah untuk setiaptyang ingin diperiksa. - Baris 78-85 (tabel):
std_teori = sqrt(ᾱ·var_data + (1−ᾱ))adalah prediksi deterministik dari bentuk tertutup; nilai tercetak pada barist = 500(1.049) jatuh di antara1.020dan1.055dari kedua jalur empiris.
Reverse Process yang Dipelajari Jaringan #
Membalik forward secara analitik mustahil: q(x_{t-1} | x_t) saja tidak menentukan jawaban unik, dan bentuk posterior q(x_{t-1} | x_t, x_0) yang memang tertutup membutuhkan x_0 — persis hal yang tidak kita ketahui saat sampling. Diffusion model karenanya mempelajari reverse sebagai rantai Gaussian bertparameter jaringan:
p_θ(x_{t-1} | x_t) = N(μ_θ(x_t, t), σ_t² I)
Jaringan tidak memprediksi μ_θ secara langsung; jaringan memprediksi derau ε_θ(x_t, t), lalu mean dihitung dari rumus:
μ_θ(x_t, t) = (1 / sqrt(α_t)) · (x_t − (β_t / sqrt(1 − ᾱ_t)) · ε_θ(x_t, t))
Dua pilihan σ_t yang lazim: sqrt(β_t) (mudah) atau variance posterior (lebih ketat). Sampling lengkapnya: tarik x_T ~ N(0, I), terapkan rumus di atas dari t = T turun ke t = 1, dan tambahkan derau kecil z ~ N(0, I) pada tiap langkah kecuali langkah terakhir.
Mengapa jaringan cukup memprediksi ε dengan loss MSE sederhana, dan bagaimana jadwal β memengaruhi kualitas sampel? Chapter berikutnya, Denoising dan Noise Schedule
, menjawab keduanya sekaligus melatih denoiser mini pertama kita dan menjalankan sampling reverse yang utuh.