The Boring Model¶
Setup Env¶
In [ ]:
Copied!
%pip install torch lightning whos-there --upgrade
%pip install torch lightning whos-there --upgrade
Dependencies¶
In [ ]:
Copied!
from pathlib import Path
from typing import Optional
import lightning.pytorch as pl
import torch
from torch.nn import functional as F  # noqa: N812
from torch.utils.data import DataLoader, Dataset
from whos_there.callback import NotificationCallback
from whos_there.senders.debug import DebugSender
tmpdir = Path.cwd()
from pathlib import Path
from typing import Optional
import lightning.pytorch as pl
import torch
from torch.nn import functional as F  # noqa: N812
from torch.utils.data import DataLoader, Dataset
from whos_there.callback import NotificationCallback
from whos_there.senders.debug import DebugSender
tmpdir = Path.cwd()
Data¶
Random data is best for debugging. If you needs special tensor shapes or batch compositions or dataloaders, modify as needed
In [ ]:
Copied!
class RandomDataset(Dataset):
    def __init__(self, size, num_samples):
        self.len = num_samples
        self.data = torch.randn(num_samples, size)
    def __getitem__(self, index):
        return self.data[index]
    def __len__(self):
        return self.len
class RandomDataset(Dataset):
    def __init__(self, size, num_samples):
        self.len = num_samples
        self.data = torch.randn(num_samples, size)
    def __getitem__(self, index):
        return self.data[index]
    def __len__(self):
        return self.len
In [ ]:
Copied!
class RandomDataModule(pl.LightningDataModule):
    def __init__(self, size: int = 32, num_samples: int = 10000, batch_size: int = 32):
        super().__init__()
        self.size = size
        self.num_sampes = num_samples
        self.batch_size = batch_size
    def setup(self, stage: str | None = None):
        self.mnist_test = RandomDataset(self.size, self.num_sampes)
        self.mnist_train = RandomDataset(self.size, self.num_sampes)
        self.mnist_val = RandomDataset(self.size, self.num_sampes)
    def train_dataloader(self):
        return DataLoader(self.mnist_train, batch_size=self.batch_size)
    def val_dataloader(self):
        return DataLoader(self.mnist_val, batch_size=self.batch_size)
    def test_dataloader(self):
        return DataLoader(self.mnist_test, batch_size=self.batch_size)
class RandomDataModule(pl.LightningDataModule):
    def __init__(self, size: int = 32, num_samples: int = 10000, batch_size: int = 32):
        super().__init__()
        self.size = size
        self.num_sampes = num_samples
        self.batch_size = batch_size
    def setup(self, stage: str | None = None):
        self.mnist_test = RandomDataset(self.size, self.num_sampes)
        self.mnist_train = RandomDataset(self.size, self.num_sampes)
        self.mnist_val = RandomDataset(self.size, self.num_sampes)
    def train_dataloader(self):
        return DataLoader(self.mnist_train, batch_size=self.batch_size)
    def val_dataloader(self):
        return DataLoader(self.mnist_val, batch_size=self.batch_size)
    def test_dataloader(self):
        return DataLoader(self.mnist_test, batch_size=self.batch_size)
Model¶
Modify this as needed to replicate your bug
In [ ]:
Copied!
from typing import Any
from lightning.pytorch import LightningModule
from torch import Tensor
class BoringModel(LightningModule):
    def __init__(self) -> None:
        super().__init__()
        self.layer = torch.nn.Linear(32, 2)
    def forward(self, x: Tensor, *args, **kwargs) -> Tensor:
        return self.layer(x)
    def loss(self, batch, prediction):
        # An arbitrary loss to have a loss that updates the model weights during `Trainer.fit` calls
        return torch.nn.functional.mse_loss(prediction, torch.ones_like(prediction))
    def training_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        return {"loss": loss}
    def training_step_end(self, training_step_outputs) -> Any:
        return training_step_outputs
    def training_epoch_end(self, outputs) -> None:
        torch.stack([x["loss"] for x in outputs]).mean()
    def validation_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        return {"x": loss}
    def validation_epoch_end(self, outputs) -> None:
        torch.stack([x["x"] for x in outputs]).mean()
    def test_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        self.log("fake_test_acc", loss)
        return {"y": loss}
    def test_epoch_end(self, outputs) -> None:
        torch.stack([x["y"] for x in outputs]).mean()
    def configure_optimizers(self) -> tuple[list[Any], list[Any]]:
        optimizer = torch.optim.SGD(self.layer.parameters(), lr=0.1)
        lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=1)
        return [optimizer], [lr_scheduler]
from typing import Any
from lightning.pytorch import LightningModule
from torch import Tensor
class BoringModel(LightningModule):
    def __init__(self) -> None:
        super().__init__()
        self.layer = torch.nn.Linear(32, 2)
    def forward(self, x: Tensor, *args, **kwargs) -> Tensor:
        return self.layer(x)
    def loss(self, batch, prediction):
        # An arbitrary loss to have a loss that updates the model weights during `Trainer.fit` calls
        return torch.nn.functional.mse_loss(prediction, torch.ones_like(prediction))
    def training_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        return {"loss": loss}
    def training_step_end(self, training_step_outputs) -> Any:
        return training_step_outputs
    def training_epoch_end(self, outputs) -> None:
        torch.stack([x["loss"] for x in outputs]).mean()
    def validation_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        return {"x": loss}
    def validation_epoch_end(self, outputs) -> None:
        torch.stack([x["x"] for x in outputs]).mean()
    def test_step(self, batch, batch_idx) -> dict[str, Any]:
        output = self.layer(batch)
        loss = self.loss(batch, output)
        self.log("fake_test_acc", loss)
        return {"y": loss}
    def test_epoch_end(self, outputs) -> None:
        torch.stack([x["y"] for x in outputs]).mean()
    def configure_optimizers(self) -> tuple[list[Any], list[Any]]:
        optimizer = torch.optim.SGD(self.layer.parameters(), lr=0.1)
        lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=1)
        return [optimizer], [lr_scheduler]
Training¶
In [ ]:
Copied!
dm = RandomDataModule()
model = BoringModel()
# Initialize a trainer
trainer = pl.Trainer(
    max_epochs=1,
    callbacks=[
        NotificationCallback(
            senders=[
                DebugSender(print=True),
            ]
        )
    ],
)
# Train the model ⚡
trainer.fit(model, datamodule=dm)
trainer.test(datamodule=dm)
dm = RandomDataModule()
model = BoringModel()
# Initialize a trainer
trainer = pl.Trainer(
    max_epochs=1,
    callbacks=[
        NotificationCallback(
            senders=[
                DebugSender(print=True),
            ]
        )
    ],
)
# Train the model ⚡
trainer.fit(model, datamodule=dm)
trainer.test(datamodule=dm)