kick-gen-v1 / models /autoencoder.py
zhinit's picture
Upload 16 files
50611ef verified
Raw
History Blame Contribute Delete
3.81 kB
"""
Variational Autoencoder for mel spectrogram compression.
Input: (batch, 1, 128, 173) log-mel spectrogram
Latent: (batch, 4, 8, 11)
Output: (batch, 1, 128, 173) reconstructed log-mel spectrogram
"""
import torch
import torch.nn as nn
class ResBlock(nn.Module):
"""Residual block with GroupNorm and SiLU activation."""
def __init__(self, channels: int) -> None:
super().__init__()
self.block = nn.Sequential(
nn.GroupNorm(8, channels),
nn.SiLU(),
nn.Conv2d(channels, channels, 3, padding=1),
nn.GroupNorm(8, channels),
nn.SiLU(),
nn.Conv2d(channels, channels, 3, padding=1),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return x + self.block(x)
class Encoder(nn.Module):
"""Encodes mel spectrograms to mu and logvar in latent space."""
def __init__(self, latent_dim: int = 4) -> None:
super().__init__()
self.net = nn.Sequential(
# (1, 128, 173) -> (32, 64, 87)
nn.Conv2d(1, 32, 3, stride=2, padding=1),
ResBlock(32),
# (32, 64, 87) -> (64, 32, 44)
nn.Conv2d(32, 64, 3, stride=2, padding=1),
ResBlock(64),
# (64, 32, 44) -> (128, 16, 22)
nn.Conv2d(64, 128, 3, stride=2, padding=1),
ResBlock(128),
# (128, 16, 22) -> (256, 8, 11)
nn.Conv2d(128, 256, 3, stride=2, padding=1),
ResBlock(256),
)
self.conv_mu = nn.Conv2d(256, latent_dim, 1)
self.conv_logvar = nn.Conv2d(256, latent_dim, 1)
def forward(
self, x: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor]:
h = self.net(x)
return self.conv_mu(h), self.conv_logvar(h)
class Decoder(nn.Module):
"""Decodes latent vectors back to mel spectrograms."""
def __init__(self, latent_dim: int = 4) -> None:
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(latent_dim, 256, 1),
ResBlock(256),
# (256, 8, 11) -> (128, 16, 22)
nn.ConvTranspose2d(256, 128, 4, stride=2, padding=1),
ResBlock(128),
# (128, 16, 22) -> (64, 32, 44)
nn.ConvTranspose2d(128, 64, 4, stride=2, padding=1),
ResBlock(64),
# (64, 32, 44) -> (32, 64, 88)
nn.ConvTranspose2d(64, 32, 4, stride=2, padding=1),
ResBlock(32),
# (32, 64, 88) -> (1, 128, 176)
nn.ConvTranspose2d(32, 1, 4, stride=2, padding=1),
)
def forward(self, z: torch.Tensor) -> torch.Tensor:
out = self.net(z)
# Crop to match input dimensions (128, 173)
return out[:, :, :128, :173]
class KickVAE(nn.Module):
"""Variational Autoencoder for kick drum mel spectrograms."""
def __init__(self, latent_dim: int = 4) -> None:
super().__init__()
self.encoder = Encoder(latent_dim)
self.decoder = Decoder(latent_dim)
def reparameterize(
self, mu: torch.Tensor, logvar: torch.Tensor
) -> torch.Tensor:
std = torch.exp(0.5 * logvar)
eps = torch.randn_like(std)
return mu + eps * std
def forward(
self, x: torch.Tensor
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Returns (reconstruction, mu, logvar)."""
mu, logvar = self.encoder(x)
z = self.reparameterize(mu, logvar)
recon = self.decoder(z)
return recon, mu, logvar
def encode(self, x: torch.Tensor) -> torch.Tensor:
"""Encode to latent space (uses mu, no sampling)."""
mu, _ = self.encoder(x)
return mu
def decode(self, z: torch.Tensor) -> torch.Tensor:
"""Decode from latent space."""
return self.decoder(z)