| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| import pytorch_lightning as pl |
|
|
| class ConvCVAE(pl.LightningModule): |
| def __init__(self, latent_dim=20, lr=1e-3): |
| super().__init__() |
| self.save_hyperparameters() |
| self.latent_dim = latent_dim |
| self.lr = lr |
|
|
| |
| self.encoder_net = nn.Sequential( |
| nn.Conv2d(11, 32, kernel_size=3, stride=2, padding=1), |
| nn.ReLU(), |
| nn.Conv2d(32, 64, kernel_size=3, stride=2, padding=1), |
| nn.ReLU(), |
| nn.Flatten(), |
| ) |
| self.fc_mu = nn.Linear(3136, latent_dim) |
| self.fc_logvar = nn.Linear(3136, latent_dim) |
|
|
| |
| self.decoder_input = nn.Linear(latent_dim + 10, 3136) |
|
|
| |
| self.decoder_net = nn.Sequential( |
| nn.Unflatten(1, (64, 7, 7)), |
| nn.ConvTranspose2d(64, 64, kernel_size=3, stride=2, padding=1, output_padding=1), |
| nn.ReLU(), |
| nn.ConvTranspose2d(64, 32, kernel_size=3, stride=2, padding=1, output_padding=1), |
| nn.ReLU(), |
| nn.Conv2d(32, 1, kernel_size=3, padding=1), |
| nn.Sigmoid() |
| ) |
|
|
| def reparameterize(self, mu, logvar): |
| std = torch.exp(0.5 * logvar) |
| eps = torch.randn_like(std) |
| return mu + eps * std |
|
|
| def forward(self, x, y): |
| batch_size = x.size(0) |
|
|
| |
| y_oh = F.one_hot(y, num_classes=10).float() |
| y_map = y_oh.view(batch_size, 10, 1, 1).expand(batch_size, 10, 28, 28) |
|
|
| |
| x_cond = torch.cat([x, y_map], dim=1) |
| x_enc = self.encoder_net(x_cond) |
| mu, logvar = self.fc_mu(x_enc), self.fc_logvar(x_enc) |
|
|
| |
| z = self.reparameterize(mu, logvar) |
|
|
| |
| z_cond = torch.cat([z, y_oh], dim=1) |
|
|
| |
| d_in = self.decoder_input(z_cond) |
| decoded_x = self.decoder_net(d_in) |
|
|
| return decoded_x, mu, logvar |
|
|
| def training_step(self, batch, batch_idx): |
| x, y = batch |
| recon_x, mu, logvar = self.forward(x, y) |
|
|
| |
| recon_loss = F.binary_cross_entropy(recon_x, x, reduction='sum') |
|
|
| |
| kld_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp()) |
|
|
| |
| loss = (recon_loss + kld_loss) / x.size(0) |
|
|
| self.log("train_loss", loss, prog_bar=True, on_epoch=True) |
| return loss |
|
|
| def validation_step(self, batch, batch_idx): |
| x, y = batch |
| recon_x, mu, logvar = self.forward(x, y) |
| |
| |
| recon_loss = F.binary_cross_entropy(recon_x, x, reduction='sum') |
| kld_loss = -0.5 * torch.sum(1 + logvar - mu.pow(2) - logvar.exp()) |
|
|
| val_loss = (recon_loss + kld_loss) / x.size(0) |
| |
| self.log("val_loss", val_loss, prog_bar=True) |
| return val_loss |
|
|
| def configure_optimizers(self): |
| return torch.optim.Adam(self.parameters(), lr=self.lr) |
| |
| def generate_number(self, number): |
| label = torch.zeros(1, 10) |
| label[:, number] = 1 |
| z = torch.randn(1, self.latent_dim) |
| z_cond = torch.cat([z, label], dim=1) |
|
|
| with torch.no_grad(): |
| d_in = self.decoder_input(z_cond) |
| recon_x = self.decoder_net(d_in) |
| return recon_x.squeeze().cpu().numpy() |