Variational Autoencoder
Bộ tự mã hóa biến phân
Autoencoder nén ảnh mèo thành 1 điểm trong latent space, ảnh chó thành điểm khác. Nếu bạn lấy điểm GIỮA hai điểm đó và decode, kết quả sẽ là gì?
Hãy tưởng tượng bản đồ Google Maps. Autoencoder thường giống bản đồ chỉ có vài chấm thành phố — giữa chúng là biển trắng. VAE giống bản đồ liên tục — mỗi nơi đều có địa hình, bạn đi từ Sài Gòn ra Huế thấy cảnh thay đổi dần.
Hình minh họa
Nhấn vào bất kỳ đâu trên latent space để "lấy mẫu". Quan sát các điểm phân bố liên tục — không có vùng trống.
Mỗi "đám mây" quanh điểm = phân bố Gaussian mà VAE học được. Các đám mây overlap → latent space liên tục → sinh dữ liệu mới bằng cách lấy mẫu bất kỳ đâu!
Bạn vừa thấy sự khác biệt cốt lõi: autoencoder → điểm cô lập, VAE → vùng mờ overlap. Nhưng bằng cách nào VAE tạo ra sự "mờ" này?
VAE = Autoencoder + Xác suất! Encoder không xuất z cố định mà xuất μ (trung tâm) và σ (mức độ "mờ"). Rồi lấy mẫu: z = μ + σ × ε. KL divergence ép các đám mây gần nhau → latent space liên tục.
Giống bạn vẽ bản đồ: thay vì đặt 1 ghim cho mỗi quán phở, bạn tô 1 vùng tròn mờ quanh nó. Các vùng overlap → bạn đi giữa 2 quán vẫn thấy quán khác gần đó!
Vấn đề: Backprop không đi qua phép sampling
Cách sai (không gradient)
z ~ N(μ, σ²) — phép sampling ngẫu nhiên, gradient không chảy qua được.
Reparameterization trick (gradient ok!)
z = μ + σ × ε, ε ~ N(0,1). Random tách ra ε, gradient chảy qua μ và σ.
z = μ + σ × ε toán học tương đương z ~ N(μ, σ²), nhưng đặt phần random (ε) sang bên ngoài. ε là input cố định (constant) cho mỗi forward pass → gradient chảy qua μ và σ bình thường qua phép cộng và nhân!
VAE loss = Reconstruction + β × KL. Nếu β quá lớn, điều gì xảy ra?
Giải thích
VAE (Variational Autoencoder) (Kingma & Welling, 2013) biến autoencoder thành mô hình sinh bằng cách thêm cấu trúc xác suất vào latent space.
ELBO (Evidence Lower Bound):
Reconstruction: Ảnh giải mã phải giống ảnh gốc (giống autoencoder thường).
KL Divergence: Ép (phân bố encoder) gần (phân bố chuẩn).
Reparameterization Trick:
KL divergence (closed form cho Gaussian):
VAE: Latent space có cấu trúc tốt (nội suy mượt), nhưng ảnh sinh thường mờ do MSE loss trung bình hóa. GAN: Ảnh sinh sắc nét, nhưng latent space không có cấu trúc rõ ràng, dễ bị mode collapse. Diffusion: Kết hợp ưu điểm cả hai — latent tốt + ảnh sắc nét.
import torch
import torch.nn as nn
import torch.nn.functional as F
class VAE(nn.Module):
def __init__(self, input_dim=784, latent_dim=20):
super().__init__()
# Encoder → μ và log(σ²)
self.fc1 = nn.Linear(input_dim, 256)
self.fc_mu = nn.Linear(256, latent_dim)
self.fc_logvar = nn.Linear(256, latent_dim)
# Decoder
self.fc3 = nn.Linear(latent_dim, 256)
self.fc4 = nn.Linear(256, input_dim)
def encode(self, x):
h = F.relu(self.fc1(x))
return self.fc_mu(h), self.fc_logvar(h)
def reparameterize(self, mu, logvar):
std = torch.exp(0.5 * logvar) # σ = exp(½ log σ²)
eps = torch.randn_like(std) # ε ~ N(0,1)
return mu + std * eps # z = μ + σε
def decode(self, z):
h = F.relu(self.fc3(z))
return torch.sigmoid(self.fc4(h))
def forward(self, x):
mu, logvar = self.encode(x)
z = self.reparameterize(mu, logvar)
return self.decode(z), mu, logvar
def vae_loss(x_hat, x, mu, logvar):
# Reconstruction loss
recon = F.binary_cross_entropy(x_hat, x, reduction='sum')
# KL divergence (closed-form cho Gaussian)
kl = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp())
return recon + kl
# Sinh ảnh mới: lấy mẫu z ~ N(0,1) rồi decode!
z_new = torch.randn(1, latent_dim)
new_image = model.decode(z_new)- VAE = Autoencoder + Xác suất. Encoder xuất μ, σ thay vì z cố định → latent space liên tục.
- Loss = Reconstruction (tái tạo giống) + KL Divergence (ép phân bố gần N(0,1)).
- Reparameterization trick: z = μ + σε cho phép backprop qua phép sampling.
- Ưu điểm: latent space có cấu trúc tốt (nội suy, sinh dữ liệu). Nhược điểm: ảnh sinh thường mờ.
- Là nền tảng cho Stable Diffusion (VAE encoder/decoder) và nhiều mô hình sinh hiện đại.
Kiểm tra hiểu biết
VAE encoder xuất ra μ và σ thay vì z cố định. Tại sao?