变分自编码器(VAE)
把图像压缩成潜在空间中的点,再从随机点画出新图像。
自编码器由编码器(把图像压缩成小向量,即潜在表示)和解码器(还原图像)组成。但它的潜在空间有空隙,随机采样的效果很差。
VAE 的解决办法是让编码器输出一个分布(μ, σ),并用 KL 散度把它拉向 N(0, I)。训练后只需从 N(0, I) 采样 z 再解码,就能得到新图像。示例用字符画出一个新的 8×8 数字。
z = μ + σ · ε, ε ~ N(0, I) ← reparameterization trick
loss = reconstruction(x, x̂) + β · KL( N(μ, σ²) ‖ N(0, I) )
KL = −½ Σ (1 + log σ² − μ² − σ²)
重建损失让重建图像接近原图
KL 损失让潜在空间连续无空洞,从而可以采样
要点
- VAE = 自编码器 + 随机性 + KL
- 潜在向量可以做运算,例如 人脸 + “眼镜” = 戴眼镜的人脸
import torch, torch.nn as nn
from sklearn.datasets import load_digits
torch.manual_seed(0)
X = torch.tensor(load_digits().data / 16.0, dtype=torch.float32) # 8x8 digits
class VAE(nn.Module):
def __init__(self, z=2):
super().__init__()
self.enc = nn.Sequential(nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 2 * z))
self.dec = nn.Sequential(nn.Linear(z, 128), nn.ReLU(), nn.Linear(128, 64), nn.Sigmoid())
def forward(self, x):
mu, logvar = self.enc(x).chunk(2, dim=1)
z = mu + torch.randn_like(mu) * torch.exp(0.5 * logvar) # reparameterization trick
return self.dec(z), mu, logvar
vae = VAE()
opt = torch.optim.Adam(vae.parameters(), lr=1e-3)
for epoch in range(200):
recon, mu, logvar = vae(X)
rec = nn.functional.binary_cross_entropy(recon, X, reduction="sum") / len(X)
kl = -0.5 * torch.sum(1 + logvar - mu ** 2 - logvar.exp()) / len(X)
loss = rec + kl
opt.zero_grad(); loss.backward(); opt.step()
if epoch % 50 == 49:
print(f"epoch {epoch + 1} reconstruction {rec.item():.1f} KL {kl.item():.2f}")
# Generate: decode random points from N(0, I)
with torch.no_grad():
sample = vae.dec(torch.randn(1, 2)).view(8, 8)
for row in sample:
print("".join(" .:-=+*#%@"[int(v * 9.99)] for v in row))
输出结果
epoch 50 reconstruction 29.1 KL 1.31 epoch 100 reconstruction 27.7 KL 0.97 epoch 150 reconstruction 26.7 KL 1.09 epoch 200 reconstruction 25.5 KL 1.42 ..=%#+.. :%*+#:. .:#:-#.. ..+=+#: .: :#:. . *-. +-=#-. ..-%%+..
- #GenerativeAI
- #DeepLearning
- #PythonສຳລັບຄົນLaos
- #AI
- #ບອກຕໍ່ຄວາມຮູ້