dl-from-scratch / gen /vae /model.py
yusiwen's picture
refactor: restructure into domain-grounded directories (ml/ cv/ gen/ graph/ rl/ nlp/)
73526b1 unverified
Raw
History Blame Contribute Delete
3.46 kB
import torch
import torch.nn as nn
class Encoder(nn.Module):
"""Image → μ, logσ² in latent space."""
def __init__(self, latent_dim=100, feature_dim=32):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(3, feature_dim, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_dim, feature_dim * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim * 2),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_dim * 2, feature_dim * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim * 4),
nn.LeakyReLU(0.2, inplace=True),
nn.Conv2d(feature_dim * 4, feature_dim * 8, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim * 8),
nn.LeakyReLU(0.2, inplace=True),
)
self.fc_mu = nn.Linear(feature_dim * 8 * 4 * 4, latent_dim)
self.fc_logvar = nn.Linear(feature_dim * 8 * 4 * 4, latent_dim)
def forward(self, x):
x = self.net(x)
x = x.view(x.size(0), -1)
return self.fc_mu(x), self.fc_logvar(x)
class Decoder(nn.Module):
"""Latent z → image."""
def __init__(self, latent_dim=100, feature_dim=32):
super().__init__()
self.fc = nn.Linear(latent_dim, feature_dim * 8 * 4 * 4)
self.net = nn.Sequential(
nn.ConvTranspose2d(feature_dim * 8, feature_dim * 4, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim * 4),
nn.ReLU(True),
nn.ConvTranspose2d(feature_dim * 4, feature_dim * 2, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim * 2),
nn.ReLU(True),
nn.ConvTranspose2d(feature_dim * 2, feature_dim, 4, 2, 1, bias=False),
nn.BatchNorm2d(feature_dim),
nn.ReLU(True),
nn.ConvTranspose2d(feature_dim, 3, 4, 2, 1, bias=False),
nn.Sigmoid(),
)
def forward(self, z):
x = self.fc(z)
x = x.view(-1, 256, 4, 4)
return self.net(x)
class VAE(nn.Module):
"""Variational Autoencoder with reparameterization trick."""
def __init__(self, latent_dim=100):
super().__init__()
self.latent_dim = latent_dim
self.encoder = Encoder(latent_dim)
self.decoder = Decoder(latent_dim)
self._init_weights()
def _init_weights(self):
for p in self.parameters():
if p.dim() > 1:
nn.init.normal_(p, mean=0.0, std=0.02)
def reparameterize(self, mu, logvar):
"""z = μ + ε * σ, where ε ~ N(0,1)."""
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def forward(self, x):
mu, logvar = self.encoder(x)
z = self.reparameterize(mu, logvar)
recon = self.decoder(z)
return recon, mu, logvar
def generate(self, num_samples, device):
with torch.no_grad():
z = torch.randn(num_samples, self.latent_dim, device=device)
return self.decoder(z)
def interpolate(self, z1, z2, steps=8):
"""Linear interpolation between two latent codes."""
alphas = torch.linspace(0, 1, steps, device=z1.device)
interp = torch.stack([(1 - a) * z1 + a * z2 for a in alphas])
return self.decoder(interp)
def num_params(self):
return sum(p.numel() for p in self.parameters())