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())