aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--main.py36
-rw-r--r--models/autoencoder.py2
-rw-r--r--requirements.txt3
-rw-r--r--src/dataloader.py7
-rw-r--r--src/sample.py195
-rw-r--r--src/train_autoencoder.py83
-rw-r--r--src/train_diffusion.py35
-rw-r--r--src/utils.py20
9 files changed, 253 insertions, 129 deletions
diff --git a/.gitignore b/.gitignore
index c1eb923..a91e98f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ plots/
checkpoints/
# MLflow artifacts
+mlruns/
mlflow.db
# Python bytecode
diff --git a/main.py b/main.py
index 46c8a50..1c05580 100644
--- a/main.py
+++ b/main.py
@@ -27,9 +27,9 @@ def main():
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__
)
-
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
-
+
# Train diffusion model
train_parser = subparsers.add_parser("train", help="Train diffusion model")
train_parser.add_argument("--epochs", type=int, default=10, help="Number of epochs")
@@ -38,7 +38,7 @@ def main():
train_parser.add_argument("--attention", action="store_true", help="Use self-attention")
train_parser.add_argument("--checkpoint", type=str, default=None, help="Resume from checkpoint")
train_parser.add_argument("--name", type=str, default=None, help="Run name")
-
+
# Train autoencoder
ae_parser = subparsers.add_parser("train-ae", help="Train autoencoder")
ae_parser.add_argument("--epochs", type=int, default=10, help="Number of epochs")
@@ -46,14 +46,14 @@ def main():
ae_parser.add_argument("--batch-size", type=int, default=64, help="Batch size")
ae_parser.add_argument("--latent-channels", type=int, default=1, help="Latent channels")
ae_parser.add_argument("--checkpoint", type=str, default=None, help="Resume from checkpoint")
-
+
# Sample from model
sample_parser = subparsers.add_parser("sample", help="Generate samples")
sample_parser.add_argument("--checkpoint", type=str, default="checkpoints/diffusion_latest.pt",
help="Path to model checkpoint")
sample_parser.add_argument("--n-samples", type=int, default=10, help="Samples per class")
sample_parser.add_argument("--attention", action="store_true", help="Use attention in model")
-
+
# Visualize diffusion
viz_parser = subparsers.add_parser("visualize", help="Visualize diffusion process")
viz_parser.add_argument("--checkpoint", type=str, default="checkpoints/diffusion_latest.pt",
@@ -62,16 +62,16 @@ def main():
viz_parser.add_argument("--forward", action="store_true", help="Visualize forward diffusion")
viz_parser.add_argument("--backward", action="store_true", help="Visualize backward diffusion")
viz_parser.add_argument("--all", action="store_true", help="Run all visualizations")
-
+
args = parser.parse_args()
-
+
if args.command is None:
parser.print_help()
return
-
+
# Set multiprocessing start method
torch.multiprocessing.set_start_method("spawn", force=True)
-
+
if args.command == "train":
from src.train_diffusion import train_diffusion
train_diffusion(
@@ -82,7 +82,7 @@ def main():
checkpoint_path=args.checkpoint,
run_name=args.name,
)
-
+
elif args.command == "train-ae":
from src.train_autoencoder import train_autoencoder
train_autoencoder(
@@ -92,22 +92,22 @@ def main():
latent_channels=args.latent_channels,
checkpoint_path=args.checkpoint,
)
-
+
elif args.command == "sample":
import os
from src.config import DEVICE
from src.sample import generate_grid
from src.utils import load_checkpoint
from models import UNetMNIST
-
+
model = UNetMNIST(use_attention=args.attention).to(DEVICE)
if os.path.exists(args.checkpoint):
model = load_checkpoint(model, os.path.basename(args.checkpoint))
else:
print(f"Warning: Checkpoint {args.checkpoint} not found.")
-
+
generate_grid(model, n_per_class=args.n_samples)
-
+
elif args.command == "visualize":
import os
from src.config import DEVICE
@@ -118,17 +118,17 @@ def main():
)
from src.utils import load_checkpoint
from models import UNetMNIST
-
+
model = UNetMNIST(use_attention=args.attention).to(DEVICE)
if os.path.exists(args.checkpoint):
model = load_checkpoint(model, os.path.basename(args.checkpoint))
-
+
if args.forward or args.all:
visualize_forward_diffusion()
-
+
if args.backward or args.all:
visualize_backward_diffusion(model)
-
+
if args.all:
generate_grid(model)
diff --git a/models/autoencoder.py b/models/autoencoder.py
index 353ec89..abbc3be 100644
--- a/models/autoencoder.py
+++ b/models/autoencoder.py
@@ -60,7 +60,7 @@ class Autoencoder(AEModule):
nn.Conv2d(64, latent_channels, kernel_size=3, padding=1),
nn.ReLU(),
- # 7x7 -> 8x8 (no activation - latent space should be unconstrained)
+ # 7x7 -> 8x8
nn.Conv2d(latent_channels, latent_channels, kernel_size=2, padding=1),
)
diff --git a/requirements.txt b/requirements.txt
index 34490ec..75c0fba 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,5 +2,6 @@ torch
torchvision
mlflow
scipy
-matplotlib
+plotly
+kaleido
rich
diff --git a/src/dataloader.py b/src/dataloader.py
index a08ae27..d1e7fcb 100644
--- a/src/dataloader.py
+++ b/src/dataloader.py
@@ -38,7 +38,7 @@ class DiffusionDataset(Dataset):
autoencoder: Optional autoencoder for latent diffusion
"""
- def __init__(self, dataset: Dataset, autoencoder: torch.nn.Module = None):
+ def __init__(self, dataset: Dataset, autoencoder: torch.nn.Module | None = None):
super().__init__()
self.dataset = dataset
self.autoencoder = autoencoder
@@ -87,7 +87,7 @@ class DiffusionDatasetX0(Dataset):
autoencoder: Optional autoencoder for latent diffusion
"""
- def __init__(self, dataset: Dataset, autoencoder: torch.nn.Module = None):
+ def __init__(self, dataset: Dataset, autoencoder: torch.nn.Module | None = None):
super().__init__()
self.dataset = dataset
self.autoencoder = autoencoder
@@ -143,6 +143,7 @@ class AutoencoderDataset(Dataset):
def __getitem__(self, idx: int) -> tuple[torch.Tensor, torch.Tensor]:
data = self.dataset[idx][0].to(DEVICE)
+ # data = data * 2 - 1 # Normalize to [-1, 1]
return data, data
@@ -151,7 +152,7 @@ def get_diffusion_dataloader(
batch_size: int = 64,
shuffle: bool = True,
num_workers: int = 4,
- autoencoder: torch.nn.Module = None,
+ autoencoder: torch.nn.Module | None = None,
) -> DataLoader:
"""
Create a DataLoader for diffusion training.
diff --git a/src/sample.py b/src/sample.py
index 5b438ed..b2c570b 100644
--- a/src/sample.py
+++ b/src/sample.py
@@ -4,17 +4,18 @@ Generate samples from a trained model and visualize results.
"""
from models import UNetMNIST
-from src.utils import ensure_dirs, tensor_to_image, load_checkpoint
+from src.utils import ensure_dirs, save_plot, tensor_to_image, load_checkpoint
from src.dataloader import get_mnist_dataset
from src.diffusion import p_xt_1_xt_x0_pred, forward_diffusion, q_xt_x0
from src.config import (
DEVICE, DIFFU_STEPS, NB_CHANNEL, IMG_SIZE, NB_LABEL,
- CHECKPOINT_DIR, PLOTS_DIR
+ CHECKPOINT_DIR,
)
import os
import torch
import numpy as np
-import matplotlib.pyplot as plt
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
from rich.progress import track
import sys
@@ -65,12 +66,14 @@ def generate_samples(
return x
-def visualize_forward_diffusion(save_path: str | None = None):
+def visualize_forward_diffusion(save_path: str | None = None) -> go.Figure:
"""
Visualize the forward diffusion process on a real image.
Args:
save_path: Path to save the visualization
+ Returns:
+ Plotly figure object
"""
ensure_dirs()
@@ -90,42 +93,73 @@ def visualize_forward_diffusion(save_path: str | None = None):
n_plots = 10
timesteps = np.linspace(1, DIFFU_STEPS, n_plots, dtype=int)
- fig, axes = plt.subplots(2, n_plots + 1, figsize=(2 * n_plots, 5))
+ # Create plotly figure
+ fig = make_subplots(
+ rows=2, cols=n_plots + 1,
+ horizontal_spacing=0.01,
+ vertical_spacing=0.1
+ )
- # Row labels
- axes[0, 0].text(0.5, 0.5, 'Step-by-step', ha='center', va='center', fontsize=10)
- axes[0, 0].axis('off')
- axes[1, 0].text(0.5, 0.5, 'Direct', ha='center', va='center', fontsize=10)
- axes[1, 0].axis('off')
+ # Add row labels
+ fig.add_annotation(
+ text='Step-by-step', xref="x domain", yref="y domain",
+ x=0.5, y=0.5, showarrow=False, font=dict(size=10),
+ row=1, col=1
+ )
+ fig.add_annotation(
+ text='Direct', xref="x domain", yref="y domain",
+ x=0.5, y=0.5, showarrow=False, font=dict(size=10),
+ row=2, col=1
+ )
# Plot step-by-step diffusion
for i, t in enumerate(timesteps):
- axes[0, i + 1].imshow(tensor_to_image(xs[t]), cmap='gray')
- axes[0, i + 1].set_title(f't={t}')
- axes[0, i + 1].axis('off')
+ step_img = tensor_to_image(xs[t]).squeeze()[::-1]
+ fig.add_trace(
+ go.Heatmap(z=step_img, colorscale='gray', showscale=False),
+ row=1, col=i + 2
+ )
+ # Add title annotation for each column
+ fig.add_annotation(
+ text=f't={t}',
+ xref=f'x{i + 2} domain', yref=f'y{i + 2} domain',
+ x=0.5, y=1.15, showarrow=False, font=dict(size=10)
+ )
# Plot direct diffusion for comparison
xt, _ = q_xt_x0(img, t)
- axes[1, i + 1].imshow(tensor_to_image(xt), cmap='gray')
- axes[1, i + 1].axis('off')
+ direct_img = tensor_to_image(xt).squeeze()[::-1]
+ fig.add_trace(
+ go.Heatmap(z=direct_img, colorscale='gray', showscale=False),
+ row=2, col=i + 2
+ )
+
+ fig.update_layout(
+ title_text=f'Forward Diffusion Process (Label: {label})',
+ width=200 * n_plots,
+ height=500,
+ showlegend=False
+ )
+
+ # Hide axes for all subplots
+ fig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False)
+ fig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False)
- fig.suptitle(f'Forward Diffusion Process (Label: {label})')
- plt.tight_layout()
+ if save_path:
+ save_plot(fig, save_path)
- if save_path is None:
- save_path = os.path.join(PLOTS_DIR, 'forward_diffusion.png')
- fig.savefig(save_path)
- print(f"Saved forward diffusion visualization to {save_path}")
- plt.close(fig)
+ return fig
-def visualize_backward_diffusion(model: torch.nn.Module, save_path: str | None = None):
+def visualize_backward_diffusion(model: torch.nn.Module, save_path: str | None = None) -> go.Figure:
"""
Visualize the backward (reverse) diffusion process.
Args:
model: Trained UNet model
save_path: Path to save the visualization
+ Returns:
+ Plotly figure object
"""
ensure_dirs()
model.eval()
@@ -134,7 +168,8 @@ def visualize_backward_diffusion(model: torch.nn.Module, save_path: str | None =
n_timesteps = 10
timesteps = np.linspace(1, DIFFU_STEPS, n_timesteps, dtype=int)[::-1]
- fig, axes = plt.subplots(n_classes, n_timesteps, figsize=(2 * n_timesteps, 2 * n_classes))
+ # Store images to plot later
+ images_to_plot = {}
with torch.no_grad():
# Start from noise
@@ -151,25 +186,60 @@ def visualize_backward_diffusion(model: torch.nn.Module, save_path: str | None =
if t in timesteps:
t_idx = timesteps.tolist().index(t)
for class_idx in range(n_classes):
- axes[class_idx, t_idx].imshow(tensor_to_image(x[class_idx]), cmap='gray')
- if class_idx == 0:
- axes[class_idx, t_idx].set_title(f't={t}')
- if t_idx == 0:
- axes[class_idx, t_idx].set_ylabel(f'Class {class_idx}')
- axes[class_idx, t_idx].set_xticks([])
- axes[class_idx, t_idx].set_yticks([])
+ images_to_plot[(class_idx, t_idx)] = {
+ 'img': tensor_to_image(x[class_idx]).squeeze()[::-1],
+ 't': t
+ }
+
+ # Create plotly figure
+ fig = make_subplots(
+ rows=n_classes, cols=n_timesteps,
+ horizontal_spacing=0.01,
+ vertical_spacing=0.02
+ )
+
+ for class_idx in range(n_classes):
+ for t_idx in range(n_timesteps):
+ data = images_to_plot[(class_idx, t_idx)]
+ fig.add_trace(
+ go.Heatmap(z=data['img'], colorscale='gray', showscale=False),
+ row=class_idx + 1, col=t_idx + 1
+ )
+ # Add column titles for first row
+ if class_idx == 0:
+ fig.add_annotation(
+ text=f"t={data['t']}",
+ xref=f'x{t_idx + 1} domain', yref=f'y{t_idx + 1} domain',
+ x=0.5, y=1.15, showarrow=False, font=dict(size=10)
+ )
+ # Add row labels for first column
+ if t_idx == 0:
+ fig.add_annotation(
+ text=f'Class {class_idx}',
+ xref=f'x{class_idx * n_timesteps + 1} domain',
+ yref=f'y{class_idx * n_timesteps + 1} domain',
+ x=-0.2, y=0.5, showarrow=False, font=dict(size=10),
+ textangle=-90
+ )
- fig.suptitle('Backward Diffusion Process')
- plt.tight_layout()
+ fig.update_layout(
+ title_text='Backward Diffusion Process',
+ width=200 * n_timesteps,
+ height=200 * n_classes,
+ showlegend=False
+ )
- if save_path is None:
- save_path = os.path.join(PLOTS_DIR, 'backward_diffusion.png')
- fig.savefig(save_path)
- print(f"Saved backward diffusion visualization to {save_path}")
- plt.close(fig)
+ # Hide axes for all subplots
+ fig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False)
+ fig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False)
+ if save_path is not None:
+ save_plot(fig, save_path)
-def generate_grid(model: torch.nn.Module, n_per_class: int = 10, save_path: str | None = None):
+ return fig
+
+
+def generate_grid(model: torch.nn.Module, n_per_class: int = 10, save_path: str | None = None) -> go.Figure:
"""
Generate a grid of samples, organized by class.
@@ -177,6 +247,8 @@ def generate_grid(model: torch.nn.Module, n_per_class: int = 10, save_path: str
model: Trained UNet model
n_per_class: Number of samples per class
save_path: Path to save the grid
+ Returns:
+ Plotly figure object
"""
ensure_dirs()
@@ -188,26 +260,45 @@ def generate_grid(model: torch.nn.Module, n_per_class: int = 10, save_path: str
samples = generate_samples(model, labels=labels)
samples = samples.cpu().numpy()
- # Create grid
- fig, axes = plt.subplots(NB_LABEL, n_per_class, figsize=(n_per_class, NB_LABEL))
+ # Create plotly grid
+ fig = make_subplots(
+ rows=NB_LABEL, cols=n_per_class,
+ horizontal_spacing=0.01,
+ vertical_spacing=0.02
+ )
for class_idx in range(NB_LABEL):
for sample_idx in range(n_per_class):
idx = class_idx * n_per_class + sample_idx
- axes[class_idx, sample_idx].imshow(samples[idx].transpose(1, 2, 0).squeeze(), cmap='gray')
- axes[class_idx, sample_idx].axis('off')
-
+ img = samples[idx].transpose(1, 2, 0).squeeze()[::-1]
+ fig.add_trace(
+ go.Heatmap(z=img, colorscale='gray', showscale=False),
+ row=class_idx + 1, col=sample_idx + 1
+ )
+ # Add row labels for first column
if sample_idx == 0:
- axes[class_idx, sample_idx].set_ylabel(f'{class_idx}')
+ fig.add_annotation(
+ text=f'{class_idx}',
+ xref=f'x{class_idx * n_per_class + 1} domain',
+ yref=f'y{class_idx * n_per_class + 1} domain',
+ x=-0.2, y=0.5, showarrow=False, font=dict(size=10)
+ )
+
+ fig.update_layout(
+ title_text='Generated MNIST Digits',
+ width=100 * n_per_class,
+ height=100 * NB_LABEL,
+ showlegend=False
+ )
+
+ # Hide axes for all subplots
+ fig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False)
+ fig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False)
- fig.suptitle('Generated MNIST Digits')
- plt.tight_layout()
+ if save_path:
+ save_plot(fig, save_path)
- if save_path is None:
- save_path = os.path.join(PLOTS_DIR, 'generated_grid.png')
- fig.savefig(save_path)
- print(f"Saved generated grid to {save_path}")
- plt.close(fig)
+ return fig
if __name__ == "__main__":
diff --git a/src/train_autoencoder.py b/src/train_autoencoder.py
index 669ce47..6af1fc5 100644
--- a/src/train_autoencoder.py
+++ b/src/train_autoencoder.py
@@ -9,7 +9,8 @@ from src.config import DEVICE, BATCH_SIZE, NUM_WORKERS, CHECKPOINT_DIR, PLOTS_DI
import os
import torch
import torch.nn as nn
-import matplotlib.pyplot as plt
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
from rich.progress import track
import mlflow
@@ -56,8 +57,9 @@ def train_autoencoder(
# Setup training
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
- # Use MSE loss instead of BCE for better reconstruction of continuous values
- criterion = nn.MSELoss()
+ # criterion = nn.BCELoss()
+ # criterion = nn.MSELoss()
+ criterion = nn.functional.binary_cross_entropy
# Get dataloader
dataloader = get_autoencoder_dataloader(
@@ -80,9 +82,6 @@ def train_autoencoder(
# Backward pass
loss.backward()
- # Gradient clipping to prevent exploding gradients
- torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
-
optimizer.step()
epoch_loss += loss.item()
@@ -94,14 +93,16 @@ def train_autoencoder(
save_checkpoint(model, f"autoencoder_epoch_{epoch:03d}.pt")
save_checkpoint(model, "autoencoder_latest.pt")
- # Visualize results
- visualize_reconstructions(model, dataloader)
+ # Visualize results
+ fig = visualize_reconstructions(model, dataloader)
+
+ mlflow.log_figure(fig, f"reconstructions/epoch_{epoch:03d}.png")
mlflow.end_run()
return model
-def visualize_reconstructions(model: AEModule, dataloader, n_samples: int = 10):
+def visualize_reconstructions(model: AEModule, dataloader, n_samples: int = 10) -> go.Figure:
"""Visualize original, latent, and reconstructed images."""
model.eval()
ensure_dirs()
@@ -114,34 +115,56 @@ def visualize_reconstructions(model: AEModule, dataloader, n_samples: int = 10):
latent = model.encode(x_batch)
x_recon = model.decode(latent)
- # Create visualization
- fig, axes = plt.subplots(3, n_samples + 1, figsize=(2 * n_samples, 6))
+ # Create visualization with plotly
+ fig = make_subplots(
+ rows=3, cols=n_samples + 1,
+ subplot_titles=[''] * (3 * (n_samples + 1)),
+ horizontal_spacing=0.01,
+ vertical_spacing=0.05
+ )
- # Labels
- axes[0, 0].text(0.5, 0.5, 'Original', ha='center', va='center', fontsize=12)
- axes[0, 0].axis('off')
- axes[1, 0].text(0.5, 0.5, 'Latent', ha='center', va='center', fontsize=12)
- axes[1, 0].axis('off')
- axes[2, 0].text(0.5, 0.5, 'Reconstructed', ha='center', va='center', fontsize=12)
- axes[2, 0].axis('off')
+ # Add row labels as annotations
+ row_labels = ['Original', 'Latent', 'Reconstructed']
+ for row_idx, label in enumerate(row_labels):
+ fig.add_annotation(
+ text=label,
+ xref="x domain", yref="y domain",
+ x=0.5, y=0.5,
+ showarrow=False,
+ font=dict(size=12),
+ row=row_idx + 1, col=1
+ )
# Plot images
for i in range(n_samples):
- axes[0, i + 1].imshow(x_batch[i].cpu().squeeze().numpy(), cmap='gray')
- axes[0, i + 1].axis('off')
+ # Original
+ fig.add_trace(
+ go.Heatmap(z=x_batch[i].cpu().squeeze().numpy()[::-1], colorscale='gray', showscale=False),
+ row=1, col=i + 2
+ )
+ # Latent
+ fig.add_trace(
+ go.Heatmap(z=latent[i].cpu().squeeze().numpy()[::-1], colorscale='gray', showscale=False),
+ row=2, col=i + 2
+ )
+ # Reconstructed
+ fig.add_trace(
+ go.Heatmap(z=x_recon[i].cpu().squeeze().numpy()[::-1], colorscale='gray', showscale=False),
+ row=3, col=i + 2
+ )
- axes[1, i + 1].imshow(latent[i].cpu().squeeze().numpy(), cmap='gray')
- axes[1, i + 1].axis('off')
-
- axes[2, i + 1].imshow(x_recon[i].cpu().squeeze().numpy(), cmap='gray')
- axes[2, i + 1].axis('off')
+ fig.update_layout(
+ title_text='Autoencoder Results',
+ width=200 * n_samples,
+ height=600,
+ showlegend=False
+ )
- fig.suptitle('Autoencoder Results')
- plt.tight_layout()
+ # Hide axes for all subplots
+ fig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False)
+ fig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False)
- save_path = os.path.join(PLOTS_DIR, 'autoencoder_results.png')
- fig.savefig(save_path)
- plt.close(fig)
+ return fig
if __name__ == "__main__":
diff --git a/src/train_diffusion.py b/src/train_diffusion.py
index cb70650..2ec4d60 100644
--- a/src/train_diffusion.py
+++ b/src/train_diffusion.py
@@ -18,7 +18,8 @@ import os
import torch
import torch.nn as nn
import numpy as np
-import matplotlib.pyplot as plt
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
from rich.progress import track
import mlflow
@@ -156,20 +157,26 @@ def evaluate_and_log(model: nn.Module, epoch: int, predict_x0: bool = True):
mlflow.log_metric("KL Divergence", kl_score, step=epoch)
mlflow.log_metric("JSD", jsd_score, step=epoch)
- # Log sample images
- fig, axes = plt.subplots(4, 8, figsize=(16, 8))
- for i, ax in enumerate(axes.flat):
- if i < len(fakes):
- ax.imshow(fakes[i].transpose(1, 2, 0).squeeze(), cmap='gray')
- ax.axis('off')
- fig.suptitle(f"Generated Samples - Epoch {epoch}")
- plt.tight_layout()
+ # Log sample images using plotly
+ fig = make_subplots(rows=4, cols=8, horizontal_spacing=0.01, vertical_spacing=0.02)
+ for i in range(min(32, len(fakes))):
+ row = i // 8 + 1
+ col = i % 8 + 1
+ img = fakes[i].transpose(1, 2, 0).squeeze()[::-1]
+ fig.add_trace(
+ go.Heatmap(z=img, colorscale='gray', showscale=False),
+ row=row, col=col
+ )
+ fig.update_layout(
+ title_text=f"Generated Samples - Epoch {epoch}",
+ width=800,
+ height=400,
+ showlegend=False
+ )
+ fig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False)
+ fig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False)
- mlflow.log_figure(fig, f"samples_epoch_{epoch:03d}.png")
-
- # Save to plots folder
- fig.savefig(os.path.join(PLOTS_DIR, f"samples_epoch_{epoch:03d}.png"))
- plt.close(fig)
+ mlflow.log_figure(fig, f"samples/epoch_{epoch:03d}.png")
if __name__ == "__main__":
diff --git a/src/utils.py b/src/utils.py
index f727a68..f34ec33 100644
--- a/src/utils.py
+++ b/src/utils.py
@@ -6,9 +6,9 @@ import os
import io
import numpy as np
import torch
-import scipy.linalg
from PIL import Image
-import matplotlib.pyplot as plt
+import plotly.graph_objects as go
+from plotly.subplots import make_subplots
from .config import CHECKPOINT_DIR, PLOTS_DIR
@@ -51,18 +51,18 @@ def tensor_to_images(tensor: torch.Tensor) -> np.ndarray:
return img
-def figure_to_image(figure: plt.Figure) -> np.ndarray:
+def figure_to_image(figure: go.Figure) -> np.ndarray:
"""
- Convert a matplotlib figure to a numpy image array.
+ Convert a plotly figure to a numpy image array.
Args:
- figure: Matplotlib figure
+ figure: Plotly figure
Returns:
Numpy array of the figure image
"""
buf = io.BytesIO()
- figure.savefig(buf, format='png')
+ figure.write_image(buf, format='png')
buf.seek(0)
image = np.array(Image.open(buf))
return image
@@ -97,14 +97,14 @@ def load_checkpoint(model: torch.nn.Module, filename: str) -> torch.nn.Module:
return model
-def save_plot(figure: plt.Figure, filename: str):
+def save_plot(figure: go.Figure, filename: str):
"""
- Save a matplotlib figure to the plots directory.
+ Save a plotly figure to the plots directory.
Args:
- figure: Matplotlib figure to save
+ figure: Plotly figure to save
filename: Filename (will be saved in PLOTS_DIR)
"""
ensure_dirs()
path = os.path.join(PLOTS_DIR, filename)
- figure.savefig(path)
+ figure.write_image(path)