yolozyk's picture
update generate tools
c3ec853
Raw
History Blame Contribute Delete
11.3 kB
"""
MLP-based decoder for reconstructing point clouds from latent codes.
This module provides flexible MLP architectures for decoding latent representations
into point clouds, commonly used in generative models like VAE and cVAE.
"""
import torch
import torch.nn as nn
import torch.nn.functional as F
from typing import List, Optional
class PointCloudDecoder(nn.Module):
"""
MLP-based decoder for reconstructing point clouds from latent representations.
This decoder uses a series of fully connected layers with optional dropout
and normalization to transform a latent code into point cloud coordinates.
Args:
latent_dim: Dimensionality of input latent code
num_points: Number of output points in the point cloud
point_dim: Dimensionality of each point (default: 3 for XYZ coordinates)
hidden_dims: List of hidden layer dimensions (default: [1024, 2048])
dropout_rate: Dropout probability (default: 0.3)
use_batch_norm: Whether to use batch normalization (default: False)
activation: Activation function to use (default: 'relu')
Input:
Latent code of shape (B, latent_dim)
Output:
Point cloud of shape (B, num_points, point_dim)
"""
def __init__(
self,
latent_dim: int,
num_points: int,
point_dim: int = 3,
hidden_dims: Optional[List[int]] = None,
dropout_rate: float = 0.3,
use_batch_norm: bool = False,
activation: str = 'relu'
):
super(PointCloudDecoder, self).__init__()
self.latent_dim = latent_dim
self.num_points = num_points
self.point_dim = point_dim
self.dropout_rate = dropout_rate
self.use_batch_norm = use_batch_norm
# Default hidden dimensions
if hidden_dims is None:
hidden_dims = [1024, 2048]
self.hidden_dims = hidden_dims
# Select activation function
if activation == 'relu':
self.activation = nn.ReLU()
elif activation == 'leaky_relu':
self.activation = nn.LeakyReLU(0.2)
elif activation == 'elu':
self.activation = nn.ELU()
elif activation == 'gelu':
self.activation = nn.GELU()
else:
raise ValueError(f"Unsupported activation: {activation}")
# Build network layers
self.layers = nn.ModuleList()
self.batch_norms = nn.ModuleList() if use_batch_norm else None
self.dropouts = nn.ModuleList()
# Input layer
prev_dim = latent_dim
for hidden_dim in hidden_dims:
self.layers.append(nn.Linear(prev_dim, hidden_dim))
if use_batch_norm:
self.batch_norms.append(nn.BatchNorm1d(hidden_dim))
self.dropouts.append(nn.Dropout(dropout_rate))
prev_dim = hidden_dim
# Output layer
output_dim = num_points * point_dim
self.output_layer = nn.Linear(prev_dim, output_dim)
def forward(self, z: torch.Tensor) -> torch.Tensor:
"""
Decode latent code into point cloud.
Args:
z: Latent code of shape (B, latent_dim)
Returns:
Reconstructed point cloud of shape (B, num_points, point_dim)
"""
x = z
# Process through hidden layers
for i, layer in enumerate(self.layers):
x = layer(x)
if self.use_batch_norm:
x = self.batch_norms[i](x)
x = self.activation(x)
x = self.dropouts[i](x)
# Output layer
x = self.output_layer(x)
# Reshape to point cloud
x = x.view(-1, self.num_points, self.point_dim)
return x
def get_output_shape(self) -> tuple:
"""
Get the output point cloud shape (excluding batch dimension).
Returns:
Tuple of (num_points, point_dim)
"""
return (self.num_points, self.point_dim)
class ResidualMLPDecoder(nn.Module):
"""
MLP decoder with residual connections for improved gradient flow.
This decoder uses residual blocks to help with training deep networks.
Args:
latent_dim: Dimensionality of input latent code
num_points: Number of output points in the point cloud
point_dim: Dimensionality of each point (default: 3 for XYZ coordinates)
hidden_dim: Hidden layer dimension (default: 1024)
num_blocks: Number of residual blocks (default: 3)
dropout_rate: Dropout probability (default: 0.3)
use_batch_norm: Whether to use batch normalization (default: True)
Input:
Latent code of shape (B, latent_dim)
Output:
Point cloud of shape (B, num_points, point_dim)
"""
def __init__(
self,
latent_dim: int,
num_points: int,
point_dim: int = 3,
hidden_dim: int = 1024,
num_blocks: int = 3,
dropout_rate: float = 0.3,
use_batch_norm: bool = True
):
super(ResidualMLPDecoder, self).__init__()
self.latent_dim = latent_dim
self.num_points = num_points
self.point_dim = point_dim
self.hidden_dim = hidden_dim
# Initial projection
self.input_proj = nn.Linear(latent_dim, hidden_dim)
# Residual blocks
self.blocks = nn.ModuleList([
ResidualBlock(hidden_dim, dropout_rate, use_batch_norm)
for _ in range(num_blocks)
])
# Output projection
output_dim = num_points * point_dim
self.output_proj = nn.Linear(hidden_dim, output_dim)
def forward(self, z: torch.Tensor) -> torch.Tensor:
"""
Decode latent code into point cloud.
Args:
z: Latent code of shape (B, latent_dim)
Returns:
Reconstructed point cloud of shape (B, num_points, point_dim)
"""
# Initial projection
x = F.relu(self.input_proj(z))
# Residual blocks
for block in self.blocks:
x = block(x)
# Output projection
x = self.output_proj(x)
x = x.view(-1, self.num_points, self.point_dim)
return x
class ResidualBlock(nn.Module):
"""
Residual block with optional batch normalization and dropout.
Args:
hidden_dim: Dimension of the hidden layer
dropout_rate: Dropout probability
use_batch_norm: Whether to use batch normalization
"""
def __init__(
self,
hidden_dim: int,
dropout_rate: float = 0.3,
use_batch_norm: bool = True
):
super(ResidualBlock, self).__init__()
self.fc1 = nn.Linear(hidden_dim, hidden_dim)
self.fc2 = nn.Linear(hidden_dim, hidden_dim)
self.dropout = nn.Dropout(dropout_rate)
if use_batch_norm:
self.bn1 = nn.BatchNorm1d(hidden_dim)
self.bn2 = nn.BatchNorm1d(hidden_dim)
else:
self.bn1 = None
self.bn2 = None
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Forward pass through residual block.
Args:
x: Input tensor of shape (B, hidden_dim)
Returns:
Output tensor of shape (B, hidden_dim)
"""
residual = x
out = self.fc1(x)
if self.bn1 is not None:
out = self.bn1(out)
out = F.relu(out)
out = self.dropout(out)
out = self.fc2(out)
if self.bn2 is not None:
out = self.bn2(out)
out = out + residual
out = F.relu(out)
return out
def create_pointcloud_decoder(
latent_dim: int,
num_points: int = 6890,
point_dim: int = 3,
architecture: str = 'mlp',
**kwargs
) -> nn.Module:
"""
Factory function to create a point cloud decoder with specified architecture.
Args:
latent_dim: Dimensionality of input latent code
num_points: Number of output points (default: 6890 for SMPL mesh)
point_dim: Dimensionality of each point (default: 3 for XYZ)
architecture: Decoder architecture type ('mlp' or 'residual')
**kwargs: Additional arguments passed to the decoder constructor
Returns:
Configured decoder instance
Examples:
>>> # Create a simple MLP decoder
>>> decoder = create_pointcloud_decoder(
... latent_dim=256,
... num_points=6890,
... architecture='mlp',
... hidden_dims=[1024, 2048],
... dropout_rate=0.3
... )
>>> # Create a residual decoder
>>> decoder = create_pointcloud_decoder(
... latent_dim=256,
... num_points=6890,
... architecture='residual',
... hidden_dim=1024,
... num_blocks=3
... )
"""
if architecture == 'mlp':
return PointCloudDecoder(
latent_dim=latent_dim,
num_points=num_points,
point_dim=point_dim,
**kwargs
)
elif architecture == 'residual':
return ResidualMLPDecoder(
latent_dim=latent_dim,
num_points=num_points,
point_dim=point_dim,
**kwargs
)
else:
raise ValueError(f"Unsupported architecture: {architecture}")
if __name__ == '__main__':
print("Testing Point Cloud Decoders...\n")
# Test parameters
batch_size = 32
latent_dim = 256
num_points = 6890 # SMPL mesh vertices
point_dim = 3
# Test standard MLP decoder
print("1. Standard MLP Decoder:")
mlp_decoder = create_pointcloud_decoder(
latent_dim=latent_dim,
num_points=num_points,
point_dim=point_dim,
architecture='mlp',
hidden_dims=[1024, 2048],
dropout_rate=0.3,
use_batch_norm=False
)
z = torch.randn(batch_size, latent_dim)
output = mlp_decoder(z)
print(f" Input shape: {z.shape}")
print(f" Output shape: {output.shape}")
print(f" Output expected shape: {mlp_decoder.get_output_shape()}")
print()
# Test MLP decoder with batch norm
print("2. MLP Decoder with Batch Normalization:")
mlp_bn_decoder = create_pointcloud_decoder(
latent_dim=latent_dim,
num_points=num_points,
architecture='mlp',
use_batch_norm=True,
activation='leaky_relu'
)
output = mlp_bn_decoder(z)
print(f" Output shape: {output.shape}")
print()
# Test residual MLP decoder
print("3. Residual MLP Decoder:")
residual_decoder = create_pointcloud_decoder(
latent_dim=latent_dim,
num_points=num_points,
architecture='residual',
hidden_dim=1024,
num_blocks=3,
dropout_rate=0.3
)
output = residual_decoder(z)
print(f" Input shape: {z.shape}")
print(f" Output shape: {output.shape}")
print()
# Test with different point dimensions
print("4. Decoder with 6D points (XYZ + RGB):")
decoder_6d = create_pointcloud_decoder(
latent_dim=latent_dim,
num_points=1000,
point_dim=6,
architecture='mlp',
hidden_dims=[512, 1024]
)
output = decoder_6d(z)
print(f" Output shape: {output.shape}")
print()