Image2Image Example
In this tutorial, we will walk through the process of building a deep learning model using the Modlee package and PyTorch to denoise images from the CIFAR-10 dataset.
The objective is to train a model that can learn to remove noise from images, which is a common task in image processing.
First, we will import the the necessary libraries and set up the environment.
import torch
import os
import modlee
import lightning.pytorch as pl
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torch import nn
from utils import check_artifacts
Now, we will set up the modlee
API key and initialize the modlee
package. You can access your modlee
API key from the
dashboard.
Replace replace-with-your-api-key
with your API key.
modlee.init(api_key="replace-with-your-api-key")
To train our denoising model, we need to simulate noisy images. This is
done using a function called add_noise
, which takes an image and a
noise level as inputs.
We generate random noise and add it to the original image, ensuring that
the pixel values remain within the valid range of [0, 1]
.
def add_noise(img, noise_level=0.1):
# Generate random noise with the specified noise level
noise = torch.randn_like(img) * noise_level
# Add noise to the original image and clamp the values to stay in range [0, 1]
return torch.clamp(img + noise, 0., 1.)
We define a custom dataset class called NoisyImageDataset
, which
inherits from torch.utils.data.Dataset
. This class will help us
create a dataset that contains noisy images along with their clean
counterparts.
class NoisyImageDataset(torch.utils.data.Dataset):
def __init__(self, dataset, noise_level=0.1, img_size=(1, 32, 32)):
self.dataset = dataset # Store the original dataset
self.noise_level = noise_level # Store the noise level
self.img_size = img_size # Store the target image size
def __len__(self):
return len(self.dataset) # Return the size of the dataset
def __getitem__(self, idx):
img, _ = self.dataset[idx] # Retrieve the image and ignore the label
# Resize the image if necessary
if img.size(0) != self.img_size[0]:
if img.size(0) < self.img_size[0]:
img = img.repeat(self.img_size[0] // img.size(0), 1, 1) # Repeat channels to match size
else:
img = img[:self.img_size[0], :, :] # Crop channels to match size
# Resize the image to the target size
img = transforms.Resize((self.img_size[1], self.img_size[2]))(img)
noisy_img = add_noise(img, self.noise_level) # Create a noisy version of the image
return noisy_img, img # Return the noisy image and the clean image
Next, we create a model class called ModleeDenoisingModel
, which
extends modlee.model.ImageImageToImageModleeModel
. This class
defines the architecture of our neural network, which consists of
convolutional layers for feature extraction.
class ModleeDenoisingModel(modlee.model.ImageImageToImageModleeModel):
def __init__(self, img_size=(1, 32, 32)):
super().__init__() # Initialize the parent class
self.img_size = img_size # Store the image size
in_channels = img_size[0] # Get the number of input channels
# Define the model architecture
self.model = nn.Sequential(
nn.Conv2d(in_channels, 16, kernel_size=3, stride=1, padding=1), # First convolutional layer
nn.ReLU(), # Activation function
nn.Conv2d(16, in_channels, kernel_size=3, stride=1, padding=1) # Second convolutional layer
)
self.loss_fn = nn.MSELoss() # Define the loss function as Mean Squared Error
def forward(self, x):
return self.model(x) # Define the forward pass
def training_step(self, batch, batch_idx):
x, y = batch # Get the noisy images and their clean counterparts
y_pred = self.forward(x) # Get the model predictions
loss = self.loss_fn(y_pred, y) # Calculate the loss
return {'loss': loss} # Return the loss value
def validation_step(self, val_batch, batch_idx):
x, y_target = val_batch # Get the validation batch
y_pred = self.forward(x) # Get the model predictions
val_loss = self.loss_fn(y_pred, y_target) # Calculate validation loss
return {'val_loss': val_loss} # Return the validation loss
def configure_optimizers(self):
return torch.optim.Adam(self.model.parameters(), lr=1e-3) # Set up the optimizer
Now we need to create our datasets. We will use the CIFAR-10
dataset, which consists of 60,000 32x32 color images in 10 different
classes.
To make our dataset suitable for training, we first define the
transformations to be applied to the images, which includes resizing and
converting them to tensors. We create both training and testing
datasets, applying our NoisyImageDataset
class to introduce noise.
noise_level = 0.1 # Define the level of noise to add
img_size = (3, 32, 32) # Define the target image size (channels, height, width)
# Define the transformations to be applied to the images
transform = transforms.Compose([
transforms.Resize((img_size[1], img_size[2])), # Resize images to the target size
transforms.ToTensor() # Convert images to tensor format
])
# Download and load the CIFAR-10 dataset
train_dataset = datasets.CIFAR10(root="data", train=True, download=True, transform=transform)
test_dataset = datasets.CIFAR10(root="data", train=False, download=True, transform=transform)
# Create noisy datasets for training and testing
train_noisy_dataset = NoisyImageDataset(train_dataset, noise_level=noise_level, img_size=img_size)
test_noisy_dataset = NoisyImageDataset(test_dataset, noise_level=noise_level, img_size=img_size)
We then create DataLoader
objects for both training and testing
datasets to enable batch processing during training.
# Create DataLoader for training and testing datasets
train_dataloader = DataLoader(train_noisy_dataset, batch_size=2, shuffle=True) # Shuffle training data
test_dataloader = DataLoader(test_noisy_dataset, batch_size=2, shuffle=False) # Do not shuffle test data
Now that we have our model and data prepared, we can begin training. We
instantiate the ModleeDenoisingModel
. We start a training run using
modlee.start_run()
, which automatically logs the experiment details.
model = ModleeDenoisingModel(img_size=img_size) # Instantiate the model
with modlee.start_run() as run: # Start a training run
trainer = pl.Trainer(max_epochs=1) # Set up the trainer
trainer.fit( # Start training the model
model=model,
train_dataloaders=train_dataloader,
val_dataloaders=test_dataloader # Use test data for validation
)
After training, we inspect the artifacts saved by Modlee, including the model graph and various statistics. With Modlee, your training assets are automatically saved, preserving valuable insights for future reference and collaboration.
last_run_path = modlee.last_run_path()
print(f"Run path: {last_run_path}")
artifacts_path = os.path.join(last_run_path, 'artifacts')
artifacts = sorted(os.listdir(artifacts_path))
print(f"Saved artifacts: {artifacts}")