From 3710a1e9987ec47d88c0944fc0399a067d1b6228 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Mon, 4 Dec 2023 16:25:05 -0500 Subject: [PATCH 1/7] Update Image_Classification_using_PyTorch_Lightning.ipynb Updates links and pip installs --- ...lassification_using_PyTorch_Lightning.ipynb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb b/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb index 0b98bdfe..f8ddb4df 100644 --- a/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb @@ -27,7 +27,7 @@ "\n", "# Image Classification using PyTorch Lightning ⚡️\n", "\n", - "We will build an image classification pipeline using PyTorch Lightning. We will follow this [style guide](https://pytorch-lightning.readthedocs.io/en/stable/starter/style_guide.html) to increase the readability and reproducibility of our code. A cool explanation of this available [here](https://wandb.ai/wandb/wandb-lightning/reports/Image-Classification-using-PyTorch-Lightning--VmlldzoyODk1NzY)." + "We will build an image classification pipeline using PyTorch Lightning. We will follow this [style guide](https://lightning.ai/docs/pytorch/stable/starter/style_guide.html) to increase the readability and reproducibility of our code. A cool explanation of this available [here](https://wandb.ai/wandb/wandb-lightning/reports/Image-Classification-using-PyTorch-Lightning--VmlldzoyODk1NzY)." ] }, { @@ -46,7 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install pytorch-lightning -q\n", + "!pip install lightning -q\n", "# install weights and biases\n", "!pip install wandb -qU" ] @@ -65,9 +65,9 @@ "metadata": {}, "outputs": [], "source": [ - "import pytorch_lightning as pl\n", + "import lightning.pytorch as pl\n", "# your favorite machine learning tracking tool\n", - "from pytorch_lightning.loggers import WandbLogger\n", + "from lightning.pytorch.loggers import WandbLogger\n", "\n", "import torch\n", "from torch import nn\n", @@ -115,7 +115,7 @@ "- Apply transforms (rotate, tokenize, etc…).\n", "- Wrap inside a DataLoader.\n", "\n", - "Learn more about datamodules [here](https://pytorch-lightning.readthedocs.io/en/stable/extensions/datamodules.html). Let's build a datamodule for the Cifar-10 dataset. " + "Learn more about datamodules [here](https://lightning.ai/docs/pytorch/stable/data/datamodule.html). Let's build a datamodule for the Cifar-10 dataset. " ] }, { @@ -168,8 +168,8 @@ "source": [ "## 📱 Callbacks\n", "\n", - "A callback is a self-contained program that can be reused across projects. PyTorch Lightning comes with few [built-in callbacks](https://pytorch-lightning.readthedocs.io/en/latest/extensions/callbacks.html#built-in-callbacks) which are regularly used. \n", - "Learn more about callbacks in PyTorch Lightning [here](https://pytorch-lightning.readthedocs.io/en/latest/extensions/callbacks.html)." + "A callback is a self-contained program that can be reused across projects. PyTorch Lightning comes with few [built-in callbacks](https://lightning.ai/docs/pytorch/latest/extensions/callbacks.html#built-in-callbacks) which are regularly used. \n", + "Learn more about callbacks in PyTorch Lightning [here](https://lightning.ai/docs/pytorch/latest/extensions/callbacks.html)." ] }, { @@ -179,7 +179,7 @@ "source": [ "### Built-in Callbacks\n", "\n", - "In this tutorial, we will use [Early Stopping](https://pytorch-lightning.readthedocs.io/en/latest/api/pytorch_lightning.callbacks.EarlyStopping.html#pytorch_lightning.callbacks.EarlyStopping) and [Model Checkpoint](https://pytorch-lightning.readthedocs.io/en/latest/api/pytorch_lightning.callbacks.ModelCheckpoint.html#pytorch_lightning.callbacks.ModelCheckpoint) built-in callbacks. They can be passed to the `Trainer`.\n" + "In this tutorial, we will use [Early Stopping](https://lightning.ai/docs/pytorch/latest/api/lightning.pytorch.callbacks.EarlyStopping.html#lightning.callbacks.EarlyStopping) and [Model Checkpoint](https://lightning.ai/docs/pytorch/latest/api/lightning.pytorch.callbacks.ModelCheckpoint.html#pytorch_lightning.callbacks.ModelCheckpoint) built-in callbacks. They can be passed to the `Trainer`.\n" ] }, { @@ -437,7 +437,7 @@ "I hope you find this report helpful. I will encourage to play with the code and train an image classifier with a dataset of your choice. \n", "\n", "Here are some resources to learn more about PyTorch Lightning:\n", - "- [Step-by-step walk-through](https://pytorch-lightning.readthedocs.io/en/latest/starter/introduction.html) - This is one of the official tutorials. Their documentation is really well written and I highly encourage it as a good learning resource.\n", + "- [Step-by-step walk-through](https://lightning.ai/docs/pytorch/latest/starter/introduction.html) - This is one of the official tutorials. Their documentation is really well written and I highly encourage it as a good learning resource.\n", "- [Use Pytorch Lightning with Weights & Biases](https://wandb.me/lightning) - This is a quick colab that you can run through to learn more about how to use W&B with PyTorch Lightning." ] } From c63bb1b3e394133c08e2087a5db3c6753609a0d1 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Mon, 4 Dec 2023 17:03:46 -0500 Subject: [PATCH 2/7] Update fine tuning ptl notebook --- ..._a_Transformer_with_Pytorch_Lightning.ipynb | 18 +++++++++++++++--- ...lassification_using_PyTorch_Lightning.ipynb | 14 +++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb b/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb index 9a2a1f10..0f91a0c9 100644 --- a/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb @@ -64,7 +64,7 @@ "outputs": [], "source": [ "# Install some dependencies\n", - "!pip install pandas torch pytorch-lightning transformers==4.1.1 -q\n", + "!pip install pandas torch lightning transformers\n", "!pip install -Uq wandb" ] }, @@ -81,7 +81,7 @@ "import transformers\n", "import numpy as np\n", "import pandas as pd\n", - "import pytorch_lightning as pl" + "import lightning.pytorch as pl" ] }, { @@ -426,7 +426,7 @@ " gpus = -1 if torch.cuda.is_available() else 0\n", " \n", " # Construct a Trainer object with the W&B logger we created and epoch set by the config object\n", - " trainer = pl.Trainer(max_epochs=config.epochs, gpus=gpus, logger=logger)\n", + " trainer = pl.Trainer(max_epochs=config.epochs, logger=logger)\n", " \n", " # Build data loaders for our datasets, using the batch_size from our config object\n", " train_data_loader = torch.utils.data.DataLoader(train_dataset, batch_size=config.batch_size)\n", @@ -536,6 +536,18 @@ "kernelspec": { "display_name": "Python 3", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" } }, "nbformat": 4, diff --git a/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb b/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb index f8ddb4df..82c9402b 100644 --- a/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Image_Classification_using_PyTorch_Lightning.ipynb @@ -46,7 +46,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install lightning -q\n", + "!pip install lightning torchvision -q\n", "# install weights and biases\n", "!pip install wandb -qU" ] @@ -452,6 +452,18 @@ "kernelspec": { "display_name": "Python 3", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.2" } }, "nbformat": 4, From d57448e7965b306a4bce3864176022f5d86262cc Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Mon, 4 Dec 2023 17:19:19 -0500 Subject: [PATCH 3/7] update lightning imports --- ...ghtning_models_with_Weights_&_Biases.ipynb | 34 ++++++++++++------- .../Profile_PyTorch_Code.ipynb | 4 +-- ...rch_Lightning_and_Weights_and_Biases.ipynb | 6 ++-- ...fer_Learning_Using_PyTorch_Lightning.ipynb | 6 ++-- ...db_End_to_End_with_PyTorch_Lightning.ipynb | 14 ++++---- 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb b/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb index a4746eaf..4e5a3fc3 100644 --- a/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb +++ b/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb @@ -28,8 +28,8 @@ "Coupled with the [Weights & Biases integration](https://docs.wandb.com/library/integrations/lightning), you can quickly train and monitor models for full traceability and reproducibility with only 2 extra lines of code:\n", "\n", "```python\n", - "from pytorch_lightning.loggers import WandbLogger\n", - "from pytorch_lightning import Trainer\n", + "from lightning.pytorch.loggers import WandbLogger\n", + "from lightning.pytorch import Trainer\n", "\n", "wandb_logger = WandbLogger()\n", "trainer = Trainer(logger=wandb_logger)\n", @@ -64,7 +64,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -q pytorch-lightning wandb" + "!pip install -q lightning wandb torchvision" ] }, { @@ -150,6 +150,15 @@ "* Call self.log in `training_step` and `validation_step` to log the metrics" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import lightning.pytorch as pl" + ] + }, { "cell_type": "code", "execution_count": null, @@ -160,9 +169,8 @@ "from torch.nn import Linear, CrossEntropyLoss, functional as F\n", "from torch.optim import Adam\n", "from torchmetrics.functional import accuracy\n", - "from pytorch_lightning import LightningModule\n", "\n", - "class MNIST_LitModule(LightningModule):\n", + "class MNIST_LitModule(pl.LightningModule):\n", "\n", " def __init__(self, n_classes=10, n_layer_1=128, n_layer_2=256, lr=1e-3):\n", " '''method used to define our model parameters'''\n", @@ -273,7 +281,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pytorch_lightning.callbacks import ModelCheckpoint\n", + "from lightning.pytorch.callbacks import ModelCheckpoint\n", "\n", "checkpoint_callback = ModelCheckpoint(monitor='val_accuracy', mode='max')" ] @@ -284,9 +292,9 @@ "source": [ "## 💡 Tracking Experiments with WandbLogger\n", "\n", - "PyTorch Lightning has a `WandbLogger` to easily log your experiments with Wights & Biases. Just pass it to your `Trainer` to log to W&B. See the [WandbLogger docs](https://pytorch-lightning.readthedocs.io/en/stable/extensions/generated/pytorch_lightning.loggers.WandbLogger.html#pytorch_lightning.loggers.WandbLogger) for all parameters. Note, to log the metrics to a specific W&B Team, pass your Team name to the `entity` argument in `WandbLogger`\n", + "PyTorch Lightning has a `WandbLogger` to easily log your experiments with Wights & Biases. Just pass it to your `Trainer` to log to W&B. See the [WandbLogger docs](https://lightning.ai/docs/pytorch/stable/extensions/generated/pytorch_lightning.loggers.WandbLogger.html#pytorch_lightning.loggers.WandbLogger) for all parameters. Note, to log the metrics to a specific W&B Team, pass your Team name to the `entity` argument in `WandbLogger`\n", "\n", - "#### `pytorch_lightning.loggers.WandbLogger()`\n", + "#### `lightning.pytorch.loggers.WandbLogger()`\n", "\n", "| Functionality | Argument/Function | PS |\n", "| ------ | ------ | ------ |\n", @@ -295,9 +303,9 @@ "| Organize runs by project | `WandbLogger(... ,project='my_project')` | |\n", "| Log histograms of gradients and parameters | `WandbLogger.watch(model)` | `WandbLogger.watch(model, log='all')` to log parameter histograms |\n", "| Log hyperparameters | Call `self.save_hyperparameters()` within `LightningModule.__init__()` |\n", - "| Log custom objects (images, audio, video, molecules…) | Use `WandbLogger.log_text`, `WandbLogger.log_image` and `WandbLogger.log_table` |\n", + "| Log custom objects (images, audio, video, molecules…) | Use `WandbLogger.log_text`, `WandbLogger.log_image` and `WandbLogger.log_table`, etc. |\n", "\n", - "See the [WandbLogger docs](https://pytorch-lightning.readthedocs.io/en/stable/extensions/generated/pytorch_lightning.loggers.WandbLogger.html#pytorch_lightning.loggers.WandbLogger) here for all parameters. " + "See the [WandbLogger docs](https://lightning.ai/docs/pytorch/stable/extensions/generated/pytorch_lightning.loggers.WandbLogger.html#pytorch_lightning.loggers.WandbLogger) here for all parameters. " ] }, { @@ -306,8 +314,8 @@ "metadata": {}, "outputs": [], "source": [ - "from pytorch_lightning.loggers import WandbLogger\n", - "from pytorch_lightning import Trainer\n", + "from lightning.pytorch.loggers import WandbLogger\n", + "from lightning.pytorch import Trainer\n", "\n", "wandb_logger = WandbLogger(project='MNIST', # group runs in \"MNIST\" project\n", " log_model='all') # log all new checkpoints during training" @@ -334,7 +342,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pytorch_lightning.callbacks import Callback\n", + "from lightning.pytorch.callbacks import Callback\n", " \n", "class LogPredictionsCallback(Callback):\n", " \n", diff --git a/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb b/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb index b7968866..00b56a76 100644 --- a/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb +++ b/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb @@ -88,7 +88,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -q wandb pytorch_lightning torch_tb_profiler" + "!pip install -q wandb lightning torch_tb_profiler torchvision" ] }, { @@ -99,7 +99,7 @@ "source": [ "import glob\n", "\n", - "import pytorch_lightning as pl\n", + "import lightning.pytorch as pl\n", "import torch\n", "import torch.nn as nn\n", "import torch.nn.functional as F\n", diff --git a/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb b/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb index b150820c..1b3aa0c3 100644 --- a/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb +++ b/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb @@ -81,7 +81,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -qqq wandb pytorch-lightning torchmetrics" + "!pip install -qqq wandb lightning torchmetrics" ] }, { @@ -142,7 +142,7 @@ "outputs": [], "source": [ "# ⚡ PyTorch Lightning\n", - "import pytorch_lightning as pl\n", + "import lightning.pytorch as pl\n", "import torchmetrics\n", "pl.seed_everything(hash(\"setting random seeds\") % 2**32 - 1)\n", "\n", @@ -150,7 +150,7 @@ "import wandb\n", "\n", "# ⚡ 🤝 🏋️‍♀️\n", - "from pytorch_lightning.loggers import WandbLogger\n" + "from lightning.pytorch.loggers import WandbLogger\n" ] }, { diff --git a/colabs/pytorch-lightning/Transfer_Learning_Using_PyTorch_Lightning.ipynb b/colabs/pytorch-lightning/Transfer_Learning_Using_PyTorch_Lightning.ipynb index 3a4a744b..776bc3b1 100644 --- a/colabs/pytorch-lightning/Transfer_Learning_Using_PyTorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Transfer_Learning_Using_PyTorch_Lightning.ipynb @@ -38,7 +38,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install wandb pytorch-lightning -qqq" + "!pip install wandb lightning torchvision -qqq" ] }, { @@ -56,9 +56,9 @@ "source": [ "import os\n", "\n", - "import pytorch_lightning as pl\n", + "import lightning.pytorch as pl\n", "# your favorite machine learning tracking tool\n", - "from pytorch_lightning.loggers import WandbLogger\n", + "from lightning.pytorch.loggers import WandbLogger\n", "\n", "import torch\n", "from torch import nn\n", diff --git a/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb b/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb index 7a3b7edd..16d7a3b6 100644 --- a/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb @@ -32,7 +32,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -q pytorch-lightning wandb" + "!pip install -q lightning wandb torchvision" ] }, { @@ -184,7 +184,7 @@ "outputs": [], "source": [ "from torchvision import transforms\n", - "import pytorch_lightning as pl\n", + "import lightning.pytorch as pl\n", "import torch\n", "from torch.utils.data import Dataset, DataLoader, random_split\n", "from skimage import io, transform\n", @@ -368,7 +368,7 @@ "from torch.nn import Linear, CrossEntropyLoss, functional as F\n", "from torch.optim import Adam\n", "from torchmetrics.functional import accuracy\n", - "from pytorch_lightning import LightningModule\n", + "from lightning.pytorch import LightningModule\n", "from torchvision import models\n", "\n", "class NatureLitModule(LightningModule):\n", @@ -468,7 +468,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pytorch_lightning.callbacks import Callback\n", + "from lightning.pytorch.callbacks import Callback\n", "\n", "class LogPredictionsCallback(Callback):\n", "\n", @@ -525,9 +525,9 @@ "metadata": {}, "outputs": [], "source": [ - "from pytorch_lightning.callbacks import ModelCheckpoint\n", - "from pytorch_lightning.loggers import WandbLogger\n", - "from pytorch_lightning import Trainer\n", + "from lightning.pytorch.callbacks import ModelCheckpoint\n", + "from lightning.pytorch.loggers import WandbLogger\n", + "from lightning.pytorch import Trainer\n", "\n", "wandb.init(project=PROJECT_NAME,\n", " entity=ENTITY,\n", From 126f1349f124b39bbb94356f775481082665f306 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Wed, 6 Dec 2023 07:42:11 -0500 Subject: [PATCH 4/7] Update Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb remove broken arg dataloader_idx --- ...ptimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb b/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb index 4e5a3fc3..3cf40163 100644 --- a/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb +++ b/colabs/pytorch-lightning/Optimize_Pytorch_Lightning_models_with_Weights_&_Biases.ipynb @@ -347,7 +347,7 @@ "class LogPredictionsCallback(Callback):\n", " \n", " def on_validation_batch_end(\n", - " self, trainer, pl_module, outputs, batch, batch_idx, dataloader_idx):\n", + " self, trainer, pl_module, outputs, batch, batch_idx):\n", " \"\"\"Called when the validation batch ends.\"\"\"\n", " \n", " # `outputs` comes from `LightningModule.validation_step`\n", From 213e6a0c1bb585550c9e05c3488e558181918981 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Wed, 6 Dec 2023 08:03:53 -0500 Subject: [PATCH 5/7] fix broken flags and deprecated functions --- ...Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb | 8 ++++---- colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb b/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb index 0f91a0c9..3ddb4919 100644 --- a/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Fine_tuning_a_Transformer_with_Pytorch_Lightning.ipynb @@ -245,7 +245,7 @@ " \n", " # Download the raw cola data from the 'zipfile' reference we added to the cola-raw artifact.\n", " raw_data_artifact = run.use_artifact(\"cola-raw:latest\")\n", - " zip_path = raw_data_artifact.get_path(\"zipfile\").download()\n", + " zip_path = raw_data_artifact.get_entry(\"zipfile\").download()\n", " !unzip -o $zip_path # jupyter hack to unzip data :P\n", " \n", " # Read in the raw data, log it to W&B as a wandb.Table\n", @@ -298,7 +298,7 @@ "\n", " # Download the preprocessed data\n", " pp_data_artifact = run.use_artifact(\"preprocessed-data:latest\")\n", - " data_path = pp_data_artifact.get_path(\"dataset\").download()\n", + " data_path = pp_data_artifact.get_entry(\"dataset\").download()\n", " dataset = torch.load(data_path)\n", "\n", " # Calculate the number of samples to include in each set.\n", @@ -410,8 +410,8 @@ "\n", " # Load the datasets from the split-dataset artifact\n", " data = run.use_artifact(\"split-dataset:latest\")\n", - " train_dataset = torch.load(data.get_path(\"train-data\").download())\n", - " val_dataset = torch.load(data.get_path(\"validation-data\").download())\n", + " train_dataset = torch.load(data.get_entry(\"train-data\").download())\n", + " val_dataset = torch.load(data.get_entry(\"validation-data\").download())\n", "\n", " # Extract the config object associated with the run\n", " config = run.config\n", diff --git a/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb b/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb index 00b56a76..35040872 100644 --- a/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb +++ b/colabs/pytorch-lightning/Profile_PyTorch_Code.ipynb @@ -357,7 +357,7 @@ " with profiler:\n", " profiler_callback = TorchTensorboardProfilerCallback(profiler)\n", "\n", - " trainer = pl.Trainer(gpus=1, max_epochs=1, max_steps=total_steps,\n", + " trainer = pl.Trainer(max_epochs=1, max_steps=total_steps,\n", " logger=pl.loggers.WandbLogger(log_model=True, save_code=True),\n", " callbacks=[profiler_callback], precision=wandb.config.precision)\n", "\n", From 13fc7f51293eb6a1be804adf00dfe1f992d2f20a Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Wed, 6 Dec 2023 08:30:47 -0500 Subject: [PATCH 6/7] Update Wandb_End_to_End_with_PyTorch_Lightning.ipynb --- ...db_End_to_End_with_PyTorch_Lightning.ipynb | 1414 +++++++++-------- 1 file changed, 735 insertions(+), 679 deletions(-) diff --git a/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb b/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb index 16d7a3b6..32a5df77 100644 --- a/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb +++ b/colabs/pytorch-lightning/Wandb_End_to_End_with_PyTorch_Lightning.ipynb @@ -1,681 +1,737 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Open\n", - "" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "Ws0nlkuGOpDy" + }, + "source": [ + "\"Open\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "aU1q92uCOpD1" + }, + "source": [ + "\"Weights\n", + "\n", + "\n", + "\n", + "# W&B Tutorial with Pytorch Lightning" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mHCKzEzSOpD1" + }, + "source": [ + "## 🛠️ Install `wandb` and `pytorch-lightning`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "RnyGWvwDOpD1" + }, + "outputs": [], + "source": [ + "!pip install -q lightning wandb torchvision" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "G5wpAEAoOpD2" + }, + "source": [ + "## Login to W&B either through Python or CLI\n", + "If you are using the public W&B cloud, you don't need to specify the `WANDB_HOST`.\n", + "\n", + "You can set environment variables `WANDB_API_KEY` and `WANDB_HOST` and pass them in as:\n", + "```\n", + "import os\n", + "import wandb\n", + "\n", + "wandb.login(host=os.getenv(\"WANDB_HOST\"), key=os.getenv(\"WANDB_API_KEY\"))\n", + "```\n", + "You can also login via the CLI with:\n", + "```\n", + "wandb login --host \n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YjXGiBLQOpD2" + }, + "outputs": [], + "source": [ + "import wandb" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "r5HflvclOpD2" + }, + "outputs": [], + "source": [ + "wandb.login()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zNuxn8BjOpD2" + }, + "source": [ + "## ⚱ Logging the Raw Training Data as an Artifact" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "X9mkr942OpD2" + }, + "outputs": [], + "source": [ + "#@title Enter your W&B project and entity\n", + "\n", + "# FORM VARIABLES\n", + "PROJECT_NAME = \"pytorch-lightning-e2e\" #@param {type:\"string\"}\n", + "ENTITY = \"wandb\"#@param {type:\"string\"}\n", + "\n", + "# set SIZE to \"TINY\", \"SMALL\", \"MEDIUM\", or \"LARGE\"\n", + "# to select one of these three datasets\n", + "# TINY dataset: 100 images, 30MB\n", + "# SMALL dataset: 1000 images, 312MB\n", + "# MEDIUM dataset: 5000 images, 1.5GB\n", + "# LARGE dataset: 12,000 images, 3.6GB\n", + "\n", + "SIZE = \"TINY\"\n", + "\n", + "if SIZE == \"TINY\":\n", + " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_100.zip\"\n", + " src_zip = \"nature_100.zip\"\n", + " DATA_SRC = \"nature_100\"\n", + " IMAGES_PER_LABEL = 10\n", + " BALANCED_SPLITS = {\"train\" : 8, \"val\" : 1, \"test\": 1}\n", + "elif SIZE == \"SMALL\":\n", + " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_1K.zip\"\n", + " src_zip = \"nature_1K.zip\"\n", + " DATA_SRC = \"nature_1K\"\n", + " IMAGES_PER_LABEL = 100\n", + " BALANCED_SPLITS = {\"train\" : 80, \"val\" : 10, \"test\": 10}\n", + "elif SIZE == \"MEDIUM\":\n", + " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_12K.zip\"\n", + " src_zip = \"nature_12K.zip\"\n", + " DATA_SRC = \"inaturalist_12K/train\" # (technically a subset of only 10K images)\n", + " IMAGES_PER_LABEL = 500\n", + " BALANCED_SPLITS = {\"train\" : 400, \"val\" : 50, \"test\": 50}\n", + "elif SIZE == \"LARGE\":\n", + " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_12K.zip\"\n", + " src_zip = \"nature_12K.zip\"\n", + " DATA_SRC = \"inaturalist_12K/train\" # (technically a subset of only 10K images)\n", + " IMAGES_PER_LABEL = 1000\n", + " BALANCED_SPLITS = {\"train\" : 800, \"val\" : 100, \"test\": 100}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "o8nApIhdOpD3" + }, + "outputs": [], + "source": [ + "%%capture\n", + "!curl -SL $src_url > $src_zip\n", + "!unzip $src_zip" + ] + }, + { + "cell_type": "code", + "source": [ + "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")" + ], + "metadata": { + "id": "ALmdQ7wISLaA" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "XQ_Kwsg9OpD3" + }, + "outputs": [], + "source": [ + "import wandb\n", + "import pandas as pd\n", + "import os\n", + "\n", + "with wandb.init(project=PROJECT_NAME, entity=ENTITY, job_type='log_datasets') as run:\n", + " img_paths = []\n", + " for root, dirs, files in os.walk('nature_100', topdown=False):\n", + " for name in files:\n", + " img_path = os.path.join(root, name)\n", + " label = img_path.split('/')[1]\n", + " img_paths.append([img_path, label])\n", + "\n", + " index_df = pd.DataFrame(columns=['image_path', 'label'], data=img_paths)\n", + " index_df.to_csv('index.csv', index=False)\n", + "\n", + " train_art = wandb.Artifact(name='Nature_100', type='raw_images', description='nature image dataset with 10 classes, 10 images per class')\n", + " train_art.add_dir('nature_100')\n", + "\n", + " # Also adding a csv indicating the labels of each image\n", + " train_art.add_file('index.csv')\n", + " wandb.log_artifact(train_art)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "1eJpOk_VOpD3" + }, + "source": [ + "## Using Artifacts in Pytorch Lightning `DataModule`'s and Pytorch `Dataset`'s\n", + "- Makes it easy to interopt your DataLoaders with new versions of datasets\n", + "- Just indicate the `name:alias` as an argument to your `Dataset` or `DataModule`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "Z2g9JRrwOpD3" + }, + "outputs": [], + "source": [ + "from torchvision import transforms\n", + "import lightning.pytorch as pl\n", + "import torch\n", + "from torch.utils.data import Dataset, DataLoader, random_split\n", + "from skimage import io, transform\n", + "from torchvision import transforms, utils, models\n", + "import math\n", + "\n", + "class NatureDataset(Dataset):\n", + " def __init__(self,\n", + " wandb_run,\n", + " artifact_name_alias=\"Nature_100:latest\",\n", + " local_target_dir=\"Nature_100:latest\",\n", + " transform=None):\n", + " self.local_target_dir = local_target_dir\n", + " self.transform = transform\n", + "\n", + " # Pull down the artifact locally to load it into memory\n", + " art = wandb_run.use_artifact(artifact_name_alias)\n", + " path_at = art.download(root=self.local_target_dir)\n", + "\n", + " self.ref_df = pd.read_csv(os.path.join(self.local_target_dir, 'index.csv'))\n", + " self.class_names = self.ref_df.iloc[:, 1].unique().tolist()\n", + " self.idx_to_class = {k: v for k, v in enumerate(self.class_names)}\n", + " self.class_to_idx = {v: k for k, v in enumerate(self.class_names)}\n", + "\n", + " def __len__(self):\n", + " return len(self.ref_df)\n", + "\n", + " def __getitem__(self, idx):\n", + " if torch.is_tensor(idx):\n", + " idx = idx.tolist()\n", + "\n", + " img_path = self.ref_df.iloc[idx, 0]\n", + "\n", + " image = io.imread(img_path)\n", + " label = self.ref_df.iloc[idx, 1]\n", + " label = torch.tensor(self.class_to_idx[label], dtype=torch.long)\n", + "\n", + " if self.transform:\n", + " image = self.transform(image)\n", + "\n", + " return image, label\n", + "\n", + "\n", + "class NatureDatasetModule(pl.LightningDataModule):\n", + " def __init__(self,\n", + " wandb_run,\n", + " artifact_name_alias: str = \"Nature_100:latest\",\n", + " local_target_dir: str = \"Nature_100:latest\",\n", + " batch_size: int = 16,\n", + " input_size: int = 224,\n", + " seed: int = 42):\n", + " super().__init__()\n", + " self.wandb_run = wandb_run\n", + " self.artifact_name_alias = artifact_name_alias\n", + " self.local_target_dir = local_target_dir\n", + " self.batch_size = batch_size\n", + " self.input_size = input_size\n", + " self.seed = seed\n", + "\n", + " def setup(self, stage=None):\n", + " self.nature_dataset = NatureDataset(wandb_run=self.wandb_run,\n", + " artifact_name_alias=self.artifact_name_alias,\n", + " local_target_dir=self.local_target_dir,\n", + " transform=transforms.Compose([transforms.ToTensor(),\n", + " transforms.CenterCrop(self.input_size),\n", + " transforms.Normalize((0.485, 0.456, 0.406),\n", + " (0.229, 0.224, 0.225))]))\n", + "\n", + " nature_length = len(self.nature_dataset)\n", + " train_size = math.floor(0.8 * nature_length)\n", + " val_size = math.floor(0.2 * nature_length)\n", + " self.nature_train, self.nature_val = random_split(self.nature_dataset,\n", + " [train_size, val_size],\n", + " generator=torch.Generator().manual_seed(self.seed))\n", + " return self\n", + "\n", + " def train_dataloader(self):\n", + " return DataLoader(self.nature_train, batch_size=self.batch_size)\n", + "\n", + " def val_dataloader(self):\n", + " return DataLoader(self.nature_val, batch_size=self.batch_size)\n", + "\n", + " def predict_dataloader(self):\n", + " pass\n", + "\n", + " def teardown(self, stage: str):\n", + " pass" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NLhjdvF-OpD3" + }, + "source": [ + "##How Logging in your Pytorch `LightningModule`works:\n", + "When you train the model using `Trainer`, ensure you have a `WandbLogger` instantiated and passed in as a `logger`.\n", + "\n", + "```\n", + "wandb_logger = WandbLogger(project=\"my_project\", entity=\"machine-learning\")\n", + "trainer = Trainer(logger=wandb_logger)\n", + "```\n", + "\n", + "\n", + "You can always use `wandb.log` as normal throughout the module. When the `WandbLogger` is used, `self.log` will also log metrics to W&B.\n", + "- To access the current run from within the `LightningModule`, you can access `Trainer.logger.experiment`, which is a `wandb.Run` object" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Oi1NpQs7OpD4" + }, + "source": [ + "### Some helper functions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "HLgYji2oOpD4" + }, + "outputs": [], + "source": [ + "# Some helper functions\n", + "\n", + "def set_parameter_requires_grad(model, feature_extracting):\n", + " if feature_extracting:\n", + " for param in model.parameters():\n", + " param.requires_grad = False\n", + "\n", + "def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):\n", + " # Initialize these variables which will be set in this if statement. Each of these\n", + " # variables is model specific.\n", + " model_ft = None\n", + " input_size = 0\n", + "\n", + " if model_name == \"resnet\":\n", + " \"\"\" Resnet18\n", + " \"\"\"\n", + " model_ft = models.resnet18(pretrained=use_pretrained)\n", + " set_parameter_requires_grad(model_ft, feature_extract)\n", + " num_ftrs = model_ft.fc.in_features\n", + " model_ft.fc = torch.nn.Linear(num_ftrs, num_classes)\n", + " input_size = 224\n", + "\n", + " elif model_name == \"squeezenet\":\n", + " \"\"\" Squeezenet\n", + " \"\"\"\n", + " model_ft = models.squeezenet1_0(pretrained=use_pretrained)\n", + " set_parameter_requires_grad(model_ft, feature_extract)\n", + " model_ft.classifier[1] = torch.nn.Conv2d(512, num_classes, kernel_size=(1, 1), stride=(1, 1))\n", + " model_ft.num_classes = num_classes\n", + " input_size = 224\n", + "\n", + " elif model_name == \"densenet\":\n", + " \"\"\" Densenet\n", + " \"\"\"\n", + " model_ft = models.densenet121(pretrained=use_pretrained)\n", + " set_parameter_requires_grad(model_ft, feature_extract)\n", + " num_ftrs = model_ft.classifier.in_features\n", + " model_ft.classifier = torch.nn.Linear(num_ftrs, num_classes)\n", + " input_size = 224\n", + "\n", + " else:\n", + " print(\"Invalid model name, exiting...\")\n", + " exit()\n", + "\n", + " return model_ft, input_size" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LLRK0S6oOpD4" + }, + "source": [ + "### Writing the `LightningModule`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "k0tTtK5zOpD4" + }, + "outputs": [], + "source": [ + "import torch\n", + "from torch.nn import Linear, CrossEntropyLoss, functional as F\n", + "from torch.optim import Adam\n", + "from torchmetrics.functional import accuracy\n", + "from lightning.pytorch import LightningModule\n", + "from torchvision import models\n", + "\n", + "class NatureLitModule(LightningModule):\n", + " def __init__(self,\n", + " model_name,\n", + " num_classes=10,\n", + " feature_extract=True,\n", + " lr=0.01):\n", + " '''method used to define our model parameters'''\n", + " super().__init__()\n", + "\n", + " self.model_name = model_name\n", + " self.num_classes = num_classes\n", + " self.feature_extract = feature_extract\n", + " self.model, self.input_size = initialize_model(model_name=self.model_name,\n", + " num_classes=self.num_classes,\n", + " feature_extract=True)\n", + "\n", + " # loss\n", + " self.loss = CrossEntropyLoss()\n", + "\n", + " # optimizer parameters\n", + " self.lr = lr\n", + "\n", + " # save hyper-parameters to self.hparams (auto-logged by W&B)\n", + " self.save_hyperparameters()\n", + "\n", + " # Record the gradients of all the layers\n", + " wandb.watch(self.model)\n", + "\n", + " def forward(self, x):\n", + " '''method used for inference input -> output'''\n", + " x = self.model(x)\n", + "\n", + " return x\n", + "\n", + " def training_step(self, batch, batch_idx):\n", + " '''needs to return a loss from a single batch'''\n", + " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", + "\n", + " # Log loss and metric\n", + " self.log('train/loss', loss)\n", + " self.log('train/accuracy', acc)\n", + "\n", + " return loss\n", + "\n", + " def validation_step(self, batch, batch_idx):\n", + " '''used for logging metrics'''\n", + " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", + "\n", + " # Log loss and metric\n", + " self.log('validation/loss', loss)\n", + " self.log('validation/accuracy', acc)\n", + "\n", + " # Let's return preds to use it in a custom callback\n", + " return preds, y\n", + "\n", + " def test_step(self, batch, batch_idx):\n", + " '''used for logging metrics'''\n", + " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", + "\n", + " # Log loss and metric\n", + " self.log('test/loss', loss)\n", + " self.log('test/accuracy', acc)\n", + "\n", + " def configure_optimizers(self):\n", + " '''defines model optimizer'''\n", + " return Adam(self.parameters(), lr=self.lr)\n", + "\n", + "\n", + " def _get_preds_loss_accuracy(self, batch):\n", + " '''convenience function since train/valid/test steps are similar'''\n", + " x, y = batch\n", + " logits = self(x)\n", + " preds = torch.argmax(logits, dim=1)\n", + " loss = self.loss(logits, y)\n", + " acc = accuracy(preds, y, task=\"multiclass\", num_classes=10)\n", + " return preds, y, loss, acc" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bvZHZuZzOpD4" + }, + "source": [ + "### Instrument Callbacks to log additional things at certain points in your code" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "j9pHqaw1OpD5" + }, + "outputs": [], + "source": [ + "from lightning.pytorch.callbacks import Callback\n", + "\n", + "class LogPredictionsCallback(Callback):\n", + "\n", + " def __init__(self):\n", + " super().__init__()\n", + "\n", + "\n", + " def on_validation_epoch_start(self, trainer, pl_module):\n", + " self.batch_dfs = []\n", + " self.image_list = []\n", + " self.val_table = wandb.Table(columns=['image', 'ground_truth', 'prediction'])\n", + "\n", + "\n", + " def on_validation_batch_end(\n", + " self, trainer, pl_module, outputs, batch, batch_idx):\n", + " \"\"\"Called when the validation batch ends.\"\"\"\n", + "\n", + " # Append validation predictions and ground truth to log in confusion matrix\n", + " x, y = batch\n", + " preds, y = outputs\n", + " self.batch_dfs.append(pd.DataFrame({\"Ground Truth\": y.cpu().numpy(), \"Predictions\": preds.cpu().numpy()}))\n", + "\n", + " # Add wandb.Image to a table to log at the end of validation\n", + " x = x.cpu().numpy().transpose(0, 2, 3, 1)\n", + " for x_i, y_i, y_pred in list(zip(x, y, preds)):\n", + " self.image_list.append(wandb.Image(x_i, caption=f'Ground Truth: {y_i} - Prediction: {y_pred}'))\n", + " self.val_table.add_data(wandb.Image(x_i), y_i, y_pred)\n", + "\n", + "\n", + " def on_validation_epoch_end(self, trainer, pl_module):\n", + " # Collect statistics for whole validation set and log\n", + " class_names = trainer.datamodule.nature_dataset.class_names\n", + " val_df = pd.concat(self.batch_dfs)\n", + " wandb.log({\"validation_table\": self.val_table,\n", + " \"images_over_time\": self.image_list,\n", + " \"validation_conf_matrix\": wandb.plot.confusion_matrix(y_true = val_df[\"Ground Truth\"].tolist(),\n", + " preds=val_df[\"Predictions\"].tolist(),\n", + " class_names=class_names)}, step=trainer.global_step)\n", + "\n", + " del self.batch_dfs\n", + " del self.val_table\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "jAiH-kb1OpD5" + }, + "source": [ + "## 🏋️‍ Main Training Loop" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "FrYTQ7uaOpD5" + }, + "outputs": [], + "source": [ + "from lightning.pytorch.callbacks import ModelCheckpoint\n", + "from lightning.pytorch.loggers import WandbLogger\n", + "from lightning.pytorch import Trainer\n", + "\n", + "wandb.init(project=PROJECT_NAME,\n", + " entity=ENTITY,\n", + " job_type='training',\n", + " config={\n", + " \"model_name\": \"squeezenet\",\n", + " \"batch_size\": 16\n", + " })\n", + "\n", + "wandb_logger = WandbLogger(log_model='all', checkpoint_name=f'nature-{wandb.run.id}')\n", + "\n", + "log_predictions_callback = LogPredictionsCallback()\n", + "checkpoint_callback = ModelCheckpoint(every_n_epochs=1)\n", + "\n", + "model = NatureLitModule(model_name=wandb.config['model_name']) # Access hyperparameters downstream to instantiate models/datasets\n", + "\n", + "nature_module = NatureDatasetModule(wandb_run = wandb_logger.experiment,\n", + " artifact_name_alias = \"Nature_100:latest\",\n", + " local_target_dir = \"Nature_100:latest\",\n", + " batch_size=wandb.config['batch_size'],\n", + " input_size=model.input_size)\n", + "nature_module.setup()\n", + "\n", + "trainer = Trainer(logger=wandb_logger, # W&B integration\n", + " callbacks=[log_predictions_callback, checkpoint_callback],\n", + " max_epochs=5,\n", + " log_every_n_steps=5)\n", + "trainer.fit(model, datamodule=nature_module)\n", + "\n", + "wandb.finish()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "uEII8J7UOpD5" + }, + "source": [ + "### Syncing with W&B Offline\n", + "If for some reason, network communication is lost during the course of training, you can always sync progress with `wandb sync`\n", + "\n", + "The W&B sdk caches all logged data in a local directory `wandb` and when you call `wandb sync`, this syncs the your local state with the web app." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xi-EhHsxOpD5" + }, + "source": [ + "## Retrieve a model checkpoint artifact and resume training\n", + "- Artifacts make it easy to track state of your training remotely and then resume training from a checkpoint" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jiE1Bk7fOpD5" + }, + "outputs": [], + "source": [ + "#@title Enter which checkpoint you want to resume training from:\n", + "\n", + "# FORM VARIABLES\n", + "ARTIFACT_NAME_ALIAS = \"nature-oyxk79m1:v4\" #@param {type:\"string\"}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "jZZXWRatOpD5" + }, + "outputs": [], + "source": [ + "wandb.init(project=PROJECT_NAME,\n", + " entity=ENTITY,\n", + " job_type='resume_training')\n", + "\n", + "# Retrieve model checkpoint artifact and restore previous hyperparameters\n", + "model_chkpt_art = wandb.use_artifact(f'{ENTITY}/{PROJECT_NAME}/{ARTIFACT_NAME_ALIAS}')\n", + "model_chkpt_art.download() # Can change download directory by adding `root`, defaults to \"./artifacts\"\n", + "logging_run = model_chkpt_art.logged_by()\n", + "wandb.config = logging_run.config\n", + "\n", + "# Can create a new artifact name or continue logging to the old one\n", + "artifact_name = ARTIFACT_NAME_ALIAS.split(\":\")[0]\n", + "wandb_logger = WandbLogger(log_model='all', checkpoint_name=artifact_name)\n", + "\n", + "log_predictions_callback = LogPredictionsCallback()\n", + "checkpoint_callback = ModelCheckpoint(every_n_epochs=1)\n", + "\n", + "model = NatureLitModule.load_from_checkpoint(f'./artifacts/{ARTIFACT_NAME_ALIAS}/model.ckpt')\n", + "\n", + "nature_module = NatureDatasetModule(wandb_run = wandb_logger.experiment,\n", + " artifact_name_alias = \"Nature_100:latest\",\n", + " local_target_dir = \"Nature_100:latest\",\n", + " batch_size=wandb.config['batch_size'],\n", + " input_size=model.input_size)\n", + "nature_module.setup()\n", + "\n", + "\n", + "\n", + "trainer = Trainer(logger=wandb_logger, # W&B integration\n", + " callbacks=[log_predictions_callback, checkpoint_callback],\n", + " max_epochs=10,\n", + " log_every_n_steps=5)\n", + "trainer.fit(model, datamodule=nature_module)\n", + "\n", + "wandb.finish()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KTforR6EOpD5" + }, + "source": [ + "## Model Registry\n", + "After logging a bunch of checkpoints across multiple runs during experimentation, now comes time to hand-off the best checkpoint to the next stage of the workflow (e.g. testing, deployment).\n", + "\n", + "The model registry offers a centralized place to house the best checkpoints for all your model tasks. Any `model` artifact you log can be \"linked\" to a Registered Model. Here are the steps to start using the model registry for more organized model management:\n", + "1. Access your team's model registry by going the team page and selecting `Model Registry`\n", + "![model registry](https://drive.google.com/uc?export=view&id=1ZtJwBsFWPTm4Sg5w8vHhRpvDSeQPwsKw)\n", + "\n", + "2. Create a new Registered Model.\n", + "![model registry](https://drive.google.com/uc?export=view&id=1RuayTZHNE0LJCxt1t0l6-2zjwiV4aDXe)\n", + "\n", + "3. Go to the artifacts tab of the project that holds all your model checkpoints\n", + "![model registry](https://drive.google.com/uc?export=view&id=1r_jlhhtcU3as8VwQ-4oAntd8YtTwElFB)\n", + "\n", + "4. Click \"Link to Registry\" for the model artifact version you want. (Alternatively you can [link a model via api](https://docs.wandb.ai/guides/models) with `wandb.run.link_artifact`)\n", + "\n", + "**A note on linking:** The process of linking a model checkpoint is akin to \"bookmarking\" it. Each time you link a new model artifact to a Registered Model, this increments the version of the Registered Model. This helps delineate the model development side of the workflow from the model deployment/consumption side. The globally understood version/alias of a model should be unpolluted from all the experimental versions being generated in R&D and thus the versioning of a Registered Model increments according to new \"bookmarked\" models as opposed to model checkpoint logging.\n", + "\n", + "\n", + "### Create a Centralized Hub for all your models\n", + "- Add a model card, tags, slack notifactions to your Registered Model\n", + "- Change aliases to reflect when models move through different phases\n", + "- Embed the model registry in reports for model documentation and regression reports. See this report as an [example](https://api.wandb.ai/links/wandb-smle/r82bj9at)\n", + "![model registry](https://drive.google.com/uc?export=view&id=1lKPgaw-Ak4WK_91aBMcLvUMJL6pDQpgO)\n" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Weights\n", - "\n", - "\n", - "\n", - "# W&B Tutorial with Pytorch Lightning" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 🛠️ Install `wandb` and `pytorch-lightning`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -q lightning wandb torchvision" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Login to W&B either through Python or CLI\n", - "If you are using the public W&B cloud, you don't need to specify the `WANDB_HOST`.\n", - "\n", - "You can set environment variables `WANDB_API_KEY` and `WANDB_HOST` and pass them in as:\n", - "```\n", - "import os\n", - "import wandb \n", - "\n", - "wandb.login(host=os.getenv(\"WANDB_HOST\"), key=os.getenv(\"WANDB_API_KEY\"))\n", - "```\n", - "You can also login via the CLI with: \n", - "```\n", - "wandb login --host \n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import wandb" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wandb.login()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ⚱ Logging the Raw Training Data as an Artifact" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#@title Enter your W&B project and entity\n", - "\n", - "# FORM VARIABLES\n", - "PROJECT_NAME = \"pytorch-lightning-e2e\" #@param {type:\"string\"}\n", - "ENTITY = \"wandb\"#@param {type:\"string\"}\n", - "\n", - "# set SIZE to \"TINY\", \"SMALL\", \"MEDIUM\", or \"LARGE\"\n", - "# to select one of these three datasets\n", - "# TINY dataset: 100 images, 30MB\n", - "# SMALL dataset: 1000 images, 312MB\n", - "# MEDIUM dataset: 5000 images, 1.5GB\n", - "# LARGE dataset: 12,000 images, 3.6GB\n", - "\n", - "SIZE = \"TINY\"\n", - "\n", - "if SIZE == \"TINY\":\n", - " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_100.zip\"\n", - " src_zip = \"nature_100.zip\"\n", - " DATA_SRC = \"nature_100\"\n", - " IMAGES_PER_LABEL = 10\n", - " BALANCED_SPLITS = {\"train\" : 8, \"val\" : 1, \"test\": 1}\n", - "elif SIZE == \"SMALL\":\n", - " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_1K.zip\"\n", - " src_zip = \"nature_1K.zip\"\n", - " DATA_SRC = \"nature_1K\"\n", - " IMAGES_PER_LABEL = 100\n", - " BALANCED_SPLITS = {\"train\" : 80, \"val\" : 10, \"test\": 10}\n", - "elif SIZE == \"MEDIUM\":\n", - " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_12K.zip\"\n", - " src_zip = \"nature_12K.zip\"\n", - " DATA_SRC = \"inaturalist_12K/train\" # (technically a subset of only 10K images)\n", - " IMAGES_PER_LABEL = 500\n", - " BALANCED_SPLITS = {\"train\" : 400, \"val\" : 50, \"test\": 50}\n", - "elif SIZE == \"LARGE\":\n", - " src_url = \"https://storage.googleapis.com/wandb_datasets/nature_12K.zip\"\n", - " src_zip = \"nature_12K.zip\"\n", - " DATA_SRC = \"inaturalist_12K/train\" # (technically a subset of only 10K images)\n", - " IMAGES_PER_LABEL = 1000\n", - " BALANCED_SPLITS = {\"train\" : 800, \"val\" : 100, \"test\": 100}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture\n", - "!curl -SL $src_url > $src_zip\n", - "!unzip $src_zip" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import wandb\n", - "import pandas as pd\n", - "import os\n", - "\n", - "with wandb.init(project=PROJECT_NAME, entity=ENTITY, job_type='log_datasets') as run:\n", - " img_paths = []\n", - " for root, dirs, files in os.walk('nature_100', topdown=False):\n", - " for name in files:\n", - " img_path = os.path.join(root, name)\n", - " label = img_path.split('/')[1]\n", - " img_paths.append([img_path, label])\n", - "\n", - " index_df = pd.DataFrame(columns=['image_path', 'label'], data=img_paths)\n", - " index_df.to_csv('index.csv', index=False)\n", - "\n", - " train_art = wandb.Artifact(name='Nature_100', type='raw_images', description='nature image dataset with 10 classes, 10 images per class')\n", - " train_art.add_dir('nature_100')\n", - "\n", - " # Also adding a csv indicating the labels of each image\n", - " train_art.add_file('index.csv')\n", - " wandb.log_artifact(train_art)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Using Artifacts in Pytorch Lightning `DataModule`'s and Pytorch `Dataset`'s\n", - "- Makes it easy to interopt your DataLoaders with new versions of datasets\n", - "- Just indicate the `name:alias` as an argument to your `Dataset` or `DataModule`\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from torchvision import transforms\n", - "import lightning.pytorch as pl\n", - "import torch\n", - "from torch.utils.data import Dataset, DataLoader, random_split\n", - "from skimage import io, transform\n", - "from torchvision import transforms, utils, models\n", - "import math\n", - "\n", - "class NatureDataset(Dataset):\n", - " def __init__(self, \n", - " wandb_run, \n", - " artifact_name_alias=\"Nature_100:latest\", \n", - " local_target_dir=\"Nature_100:latest\", \n", - " transform=None):\n", - " self.local_target_dir = local_target_dir\n", - " self.transform = transform\n", - "\n", - " # Pull down the artifact locally to load it into memory\n", - " art = wandb_run.use_artifact(artifact_name_alias)\n", - " path_at = art.download(root=self.local_target_dir)\n", - "\n", - " self.ref_df = pd.read_csv(os.path.join(self.local_target_dir, 'index.csv'))\n", - " self.class_names = self.ref_df.iloc[:, 1].unique().tolist()\n", - " self.idx_to_class = {k: v for k, v in enumerate(self.class_names)}\n", - " self.class_to_idx = {v: k for k, v in enumerate(self.class_names)}\n", - "\n", - " def __len__(self):\n", - " return len(self.ref_df)\n", - "\n", - " def __getitem__(self, idx):\n", - " if torch.is_tensor(idx):\n", - " idx = idx.tolist()\n", - "\n", - " img_path = self.ref_df.iloc[idx, 0]\n", - "\n", - " image = io.imread(img_path)\n", - " label = self.ref_df.iloc[idx, 1]\n", - " label = torch.tensor(self.class_to_idx[label], dtype=torch.long)\n", - "\n", - " if self.transform:\n", - " image = self.transform(image)\n", - "\n", - " return image, label\n", - "\n", - "\n", - "class NatureDatasetModule(pl.LightningDataModule):\n", - " def __init__(self,\n", - " wandb_run,\n", - " artifact_name_alias: str = \"Nature_100:latest\",\n", - " local_target_dir: str = \"Nature_100:latest\",\n", - " batch_size: int = 16,\n", - " input_size: int = 224,\n", - " seed: int = 42):\n", - " super().__init__()\n", - " self.wandb_run = wandb_run\n", - " self.artifact_name_alias = artifact_name_alias\n", - " self.local_target_dir = local_target_dir\n", - " self.batch_size = batch_size\n", - " self.input_size = input_size\n", - " self.seed = seed\n", - "\n", - " def setup(self, stage=None):\n", - " self.nature_dataset = NatureDataset(wandb_run=self.wandb_run,\n", - " artifact_name_alias=self.artifact_name_alias,\n", - " local_target_dir=self.local_target_dir,\n", - " transform=transforms.Compose([transforms.ToTensor(),\n", - " transforms.CenterCrop(self.input_size),\n", - " transforms.Normalize((0.485, 0.456, 0.406),\n", - " (0.229, 0.224, 0.225))]))\n", - "\n", - " nature_length = len(self.nature_dataset)\n", - " train_size = math.floor(0.8 * nature_length)\n", - " val_size = math.floor(0.2 * nature_length)\n", - " self.nature_train, self.nature_val = random_split(self.nature_dataset,\n", - " [train_size, val_size],\n", - " generator=torch.Generator().manual_seed(self.seed))\n", - " return self\n", - "\n", - " def train_dataloader(self):\n", - " return DataLoader(self.nature_train, batch_size=self.batch_size)\n", - "\n", - " def val_dataloader(self):\n", - " return DataLoader(self.nature_val, batch_size=self.batch_size)\n", - "\n", - " def predict_dataloader(self):\n", - " pass\n", - "\n", - " def teardown(self, stage: str):\n", - " pass" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "##How Logging in your Pytorch `LightningModule`works:\n", - "When you train the model using `Trainer`, ensure you have a `WandbLogger` instantiated and passed in as a `logger`. \n", - " \n", - "```\n", - "wandb_logger = WandbLogger(project=\"my_project\", entity=\"machine-learning\") \n", - "trainer = Trainer(logger=wandb_logger) \n", - "```\n", - "\n", - "\n", - "You can always use `wandb.log` as normal throughout the module. When the `WandbLogger` is used, `self.log` will also log metrics to W&B. \n", - "- To access the current run from within the `LightningModule`, you can access `Trainer.logger.experiment`, which is a `wandb.Run` object" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Some helper functions" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Some helper functions\n", - "\n", - "def set_parameter_requires_grad(model, feature_extracting):\n", - " if feature_extracting:\n", - " for param in model.parameters():\n", - " param.requires_grad = False\n", - "\n", - "def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):\n", - " # Initialize these variables which will be set in this if statement. Each of these\n", - " # variables is model specific.\n", - " model_ft = None\n", - " input_size = 0\n", - "\n", - " if model_name == \"resnet\":\n", - " \"\"\" Resnet18\n", - " \"\"\"\n", - " model_ft = models.resnet18(pretrained=use_pretrained)\n", - " set_parameter_requires_grad(model_ft, feature_extract)\n", - " num_ftrs = model_ft.fc.in_features\n", - " model_ft.fc = torch.nn.Linear(num_ftrs, num_classes)\n", - " input_size = 224\n", - "\n", - " elif model_name == \"squeezenet\":\n", - " \"\"\" Squeezenet\n", - " \"\"\"\n", - " model_ft = models.squeezenet1_0(pretrained=use_pretrained)\n", - " set_parameter_requires_grad(model_ft, feature_extract)\n", - " model_ft.classifier[1] = torch.nn.Conv2d(512, num_classes, kernel_size=(1, 1), stride=(1, 1))\n", - " model_ft.num_classes = num_classes\n", - " input_size = 224\n", - "\n", - " elif model_name == \"densenet\":\n", - " \"\"\" Densenet\n", - " \"\"\"\n", - " model_ft = models.densenet121(pretrained=use_pretrained)\n", - " set_parameter_requires_grad(model_ft, feature_extract)\n", - " num_ftrs = model_ft.classifier.in_features\n", - " model_ft.classifier = torch.nn.Linear(num_ftrs, num_classes)\n", - " input_size = 224\n", - "\n", - " else:\n", - " print(\"Invalid model name, exiting...\")\n", - " exit()\n", - "\n", - " return model_ft, input_size" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Writing the `LightningModule`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import torch\n", - "from torch.nn import Linear, CrossEntropyLoss, functional as F\n", - "from torch.optim import Adam\n", - "from torchmetrics.functional import accuracy\n", - "from lightning.pytorch import LightningModule\n", - "from torchvision import models\n", - "\n", - "class NatureLitModule(LightningModule):\n", - " def __init__(self,\n", - " model_name,\n", - " num_classes=10,\n", - " feature_extract=True,\n", - " lr=0.01):\n", - " '''method used to define our model parameters'''\n", - " super().__init__()\n", - "\n", - " self.model_name = model_name\n", - " self.num_classes = num_classes\n", - " self.feature_extract = feature_extract\n", - " self.model, self.input_size = initialize_model(model_name=self.model_name,\n", - " num_classes=self.num_classes,\n", - " feature_extract=True)\n", - "\n", - " # loss\n", - " self.loss = CrossEntropyLoss()\n", - "\n", - " # optimizer parameters\n", - " self.lr = lr\n", - "\n", - " # save hyper-parameters to self.hparams (auto-logged by W&B)\n", - " self.save_hyperparameters()\n", - "\n", - " # Record the gradients of all the layers\n", - " wandb.watch(self.model)\n", - "\n", - " def forward(self, x):\n", - " '''method used for inference input -> output'''\n", - " x = self.model(x)\n", - "\n", - " return x\n", - "\n", - " def training_step(self, batch, batch_idx):\n", - " '''needs to return a loss from a single batch'''\n", - " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", - "\n", - " # Log loss and metric\n", - " self.log('train/loss', loss)\n", - " self.log('train/accuracy', acc)\n", - "\n", - " return loss\n", - "\n", - " def validation_step(self, batch, batch_idx):\n", - " '''used for logging metrics'''\n", - " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", - "\n", - " # Log loss and metric\n", - " self.log('validation/loss', loss)\n", - " self.log('validation/accuracy', acc)\n", - "\n", - " # Let's return preds to use it in a custom callback\n", - " return preds, y\n", - "\n", - " def validation_epoch_end(self, validation_step_outputs):\n", - " \"\"\"Called when the validation ends.\"\"\"\n", - " preds, y = validation_step_outputs\n", - " all_preds = torch.stack(preds)\n", - " all_y = torch.stack(y)\n", - "\n", - " def test_step(self, batch, batch_idx):\n", - " '''used for logging metrics'''\n", - " preds, y, loss, acc = self._get_preds_loss_accuracy(batch)\n", - "\n", - " # Log loss and metric\n", - " self.log('test/loss', loss)\n", - " self.log('test/accuracy', acc)\n", - "\n", - " def configure_optimizers(self):\n", - " '''defines model optimizer'''\n", - " return Adam(self.parameters(), lr=self.lr)\n", - "\n", - "\n", - " def _get_preds_loss_accuracy(self, batch):\n", - " '''convenience function since train/valid/test steps are similar'''\n", - " x, y = batch\n", - " logits = self(x)\n", - " preds = torch.argmax(logits, dim=1)\n", - " loss = self.loss(logits, y)\n", - " acc = accuracy(preds, y, task=\"multiclass\", num_classes=10)\n", - " return preds, y, loss, acc" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Instrument Callbacks to log additional things at certain points in your code" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from lightning.pytorch.callbacks import Callback\n", - "\n", - "class LogPredictionsCallback(Callback):\n", - "\n", - " def __init__(self):\n", - " super().__init__()\n", - "\n", - " \n", - " def on_validation_epoch_start(self, trainer, pl_module):\n", - " self.batch_dfs = []\n", - " self.image_list = []\n", - " self.val_table = wandb.Table(columns=['image', 'ground_truth', 'prediction'])\n", - "\n", - " \n", - " def on_validation_batch_end(\n", - " self, trainer, pl_module, outputs, batch, batch_idx, dataloader_idx):\n", - " \"\"\"Called when the validation batch ends.\"\"\"\n", - "\n", - " # Append validation predictions and ground truth to log in confusion matrix\n", - " x, y = batch\n", - " preds, y = outputs\n", - " self.batch_dfs.append(pd.DataFrame({\"Ground Truth\": y.numpy(), \"Predictions\": preds.numpy()}))\n", - "\n", - " # Add wandb.Image to a table to log at the end of validation\n", - " x = x.numpy().transpose(0, 2, 3, 1)\n", - " for x_i, y_i, y_pred in list(zip(x, y, preds)):\n", - " self.image_list.append(wandb.Image(x_i, caption=f'Ground Truth: {y_i} - Prediction: {y_pred}'))\n", - " self.val_table.add_data(wandb.Image(x_i), y_i, y_pred)\n", - " \n", - " \n", - " def on_validation_epoch_end(self, trainer, pl_module):\n", - " # Collect statistics for whole validation set and log\n", - " class_names = trainer.datamodule.nature_dataset.class_names\n", - " val_df = pd.concat(self.batch_dfs)\n", - " wandb.log({\"validation_table\": self.val_table,\n", - " \"images_over_time\": self.image_list,\n", - " \"validation_conf_matrix\": wandb.plot.confusion_matrix(y_true = val_df[\"Ground Truth\"].tolist(), \n", - " preds=val_df[\"Predictions\"].tolist(), \n", - " class_names=class_names)}, step=trainer.global_step)\n", - "\n", - " del self.batch_dfs\n", - " del self.val_table\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 🏋️‍ Main Training Loop" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from lightning.pytorch.callbacks import ModelCheckpoint\n", - "from lightning.pytorch.loggers import WandbLogger\n", - "from lightning.pytorch import Trainer\n", - "\n", - "wandb.init(project=PROJECT_NAME,\n", - " entity=ENTITY,\n", - " job_type='training',\n", - " config={\n", - " \"model_name\": \"squeezenet\",\n", - " \"batch_size\": 16\n", - " })\n", - "\n", - "wandb_logger = WandbLogger(log_model='all', checkpoint_name=f'nature-{wandb.run.id}') \n", - "\n", - "log_predictions_callback = LogPredictionsCallback()\n", - "checkpoint_callback = ModelCheckpoint(every_n_epochs=1)\n", - "\n", - "model = NatureLitModule(model_name=wandb.config['model_name']) # Access hyperparameters downstream to instantiate models/datasets\n", - "\n", - "nature_module = NatureDatasetModule(wandb_run = wandb_logger.experiment,\n", - " artifact_name_alias = \"Nature_100:latest\",\n", - " local_target_dir = \"Nature_100:latest\",\n", - " batch_size=wandb.config['batch_size'],\n", - " input_size=model.input_size)\n", - "nature_module.setup()\n", - "\n", - "trainer = Trainer(logger=wandb_logger, # W&B integration\n", - " callbacks=[log_predictions_callback, checkpoint_callback],\n", - " max_epochs=5,\n", - " log_every_n_steps=5) \n", - "trainer.fit(model, datamodule=nature_module)\n", - "\n", - "wandb.finish()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Syncing with W&B Offline\n", - "If for some reason, network communication is lost during the course of training, you can always sync progress with `wandb sync`\n", - "\n", - "The W&B sdk caches all logged data in a local directory `wandb` and when you call `wandb sync`, this syncs the your local state with the web app. " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Retrieve a model checkpoint artifact and resume training\n", - "- Artifacts make it easy to track state of your training remotely and then resume training from a checkpoint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#@title Enter which checkpoint you want to resume training from:\n", - "\n", - "# FORM VARIABLES\n", - "ARTIFACT_NAME_ALIAS = \"nature-zb4swpn6:v4\" #@param {type:\"string\"}" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wandb.init(project=PROJECT_NAME,\n", - " entity=ENTITY,\n", - " job_type='resume_training')\n", - "\n", - "# Retrieve model checkpoint artifact and restore previous hyperparameters\n", - "model_chkpt_art = wandb.use_artifact(f'{ENTITY}/{PROJECT_NAME}/{ARTIFACT_NAME_ALIAS}')\n", - "model_chkpt_art.download() # Can change download directory by adding `root`, defaults to \"./artifacts\"\n", - "logging_run = model_chkpt_art.logged_by()\n", - "wandb.config = logging_run.config\n", - "\n", - "# Can create a new artifact name or continue logging to the old one\n", - "artifact_name = ARTIFACT_NAME_ALIAS.split(\":\")[0]\n", - "wandb_logger = WandbLogger(log_model='all', checkpoint_name=artifact_name) \n", - "\n", - "log_predictions_callback = LogPredictionsCallback()\n", - "checkpoint_callback = ModelCheckpoint(every_n_epochs=1)\n", - "\n", - "model = NatureLitModule(model_name=wandb.config['model_name']) # Access hyperparameters downstream to instantiate models/datasets\n", - "\n", - "nature_module = NatureDatasetModule(wandb_run = wandb_logger.experiment,\n", - " artifact_name_alias = \"Nature_100:latest\",\n", - " local_target_dir = \"Nature_100:latest\",\n", - " batch_size=wandb.config['batch_size'],\n", - " input_size=model.input_size)\n", - "nature_module.setup()\n", - "\n", - "\n", - "\n", - "trainer = Trainer(logger=wandb_logger, # W&B integration\n", - " resume_from_checkpoint = f'./artifacts/{ARTIFACT_NAME_ALIAS}/model.ckpt',\n", - " callbacks=[log_predictions_callback, checkpoint_callback],\n", - " max_epochs=10,\n", - " log_every_n_steps=5) \n", - "trainer.fit(model, datamodule=nature_module)\n", - "\n", - "wandb.finish()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Model Registry \n", - "After logging a bunch of checkpoints across multiple runs during experimentation, now comes time to hand-off the best checkpoint to the next stage of the workflow (e.g. testing, deployment).\n", - "\n", - "The model registry offers a centralized place to house the best checkpoints for all your model tasks. Any `model` artifact you log can be \"linked\" to a Registered Model. Here are the steps to start using the model registry for more organized model management:\n", - "1. Access your team's model registry by going the team page and selecting `Model Registry`\n", - "![model registry](https://drive.google.com/uc?export=view&id=1ZtJwBsFWPTm4Sg5w8vHhRpvDSeQPwsKw)\n", - "\n", - "2. Create a new Registered Model. \n", - "![model registry](https://drive.google.com/uc?export=view&id=1RuayTZHNE0LJCxt1t0l6-2zjwiV4aDXe)\n", - "\n", - "3. Go to the artifacts tab of the project that holds all your model checkpoints\n", - "![model registry](https://drive.google.com/uc?export=view&id=1r_jlhhtcU3as8VwQ-4oAntd8YtTwElFB)\n", - "\n", - "4. Click \"Link to Registry\" for the model artifact version you want. (Alternatively you can [link a model via api](https://docs.wandb.ai/guides/models) with `wandb.run.link_artifact`)\n", - "\n", - "**A note on linking:** The process of linking a model checkpoint is akin to \"bookmarking\" it. Each time you link a new model artifact to a Registered Model, this increments the version of the Registered Model. This helps delineate the model development side of the workflow from the model deployment/consumption side. The globally understood version/alias of a model should be unpolluted from all the experimental versions being generated in R&D and thus the versioning of a Registered Model increments according to new \"bookmarked\" models as opposed to model checkpoint logging. \n", - "\n", - "\n", - "### Create a Centralized Hub for all your models\n", - "- Add a model card, tags, slack notifactions to your Registered Model\n", - "- Change aliases to reflect when models move through different phases\n", - "- Embed the model registry in reports for model documentation and regression reports. See this report as an [example](https://api.wandb.ai/links/wandb-smle/r82bj9at)\n", - "![model registry](https://drive.google.com/uc?export=view&id=1lKPgaw-Ak4WK_91aBMcLvUMJL6pDQpgO)\n" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "include_colab_link": true, - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file From 03020a3fb1462712e3fd785d5951a7c69c470353 Mon Sep 17 00:00:00 2001 From: Anish Shah Date: Wed, 6 Dec 2023 08:58:11 -0500 Subject: [PATCH 7/7] Update Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb --- ...rch_Lightning_and_Weights_and_Biases.ipynb | 1824 +++++++++-------- 1 file changed, 968 insertions(+), 856 deletions(-) diff --git a/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb b/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb index 1b3aa0c3..018a1ad6 100644 --- a/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb +++ b/colabs/pytorch-lightning/Supercharge_your_Training_with_Pytorch_Lightning_and_Weights_and_Biases.ipynb @@ -1,857 +1,969 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Open\n", - "" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Weights\n", - "\n", - "\n", - "\n", - "# ⚡ 💘 🏋️‍♀️ Supercharge your Training with PyTorch Lightning + Weights & Biases" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\"Weights" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "At Weights & Biases, we love anything\n", - "that makes training deep learning models easier.\n", - "That's why we worked with the folks at PyTorch Lightning to\n", - "[integrate our experiment tracking tool](https://docs.wandb.com/library/integrations/lightning)\n", - "directly into\n", - "[the Lightning library](https://pytorch-lightning.readthedocs.io/en/latest/common/loggers.html#weights-and-biases).\n", - "\n", - "[PyTorch Lightning](https://pytorch-lightning.readthedocs.io/en/stable/) is a lightweight wrapper for organizing your PyTorch code and easily adding advanced features such as distributed training and 16-bit precision.\n", - "It retains all the flexibility of PyTorch,\n", - "in case you need it,\n", - "but adds some useful abstractions\n", - "and builds in some best practices.\n", - "\n", - "## What this notebook covers:\n", - "\n", - "1. Differences between PyTorch and PyTorch Lightning, including how to set up `LightningModules` and `LightningDataModules`\n", - "2. How to get basic metric logging with the [`WandbLogger`](https://pytorch-lightning.readthedocs.io/en/latest/common/loggers.html#weights-and-biases)\n", - "3. How to log media with W&B and fully customize logging with Lightning `Callbacks`\n", - "\n", - "## The interactive dashboard in W&B will look like this:\n", - "\n", - "![](https://i.imgur.com/lIbMyFR.png)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Follow along with a [video tutorial](http://wandb.me/lit-video)!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 🚀 Installing and importing" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`wandb` and `pytorch-lightning` are both easily installable via [`pip`](https://pip.pypa.io/en/stable/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install -qqq wandb lightning torchmetrics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "PyTorch Lightning is built on top of PyTorch,\n", - "so we still need to import vanilla PyTorch." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# numpy for non-GPU array math\n", - "import numpy as np\n", - "\n", - "# 🍦 Vanilla PyTorch\n", - "import torch\n", - "from torch.nn import functional as F\n", - "from torch import nn\n", - "from torch.utils.data import DataLoader, random_split\n", - "\n", - "# 👀 Torchvision for CV\n", - "from torchvision.datasets import MNIST\n", - "from torchvision import transforms\n", - "\n", - "# remove slow mirror from list of MNIST mirrors\n", - "MNIST.mirrors = [mirror for mirror in MNIST.mirrors\n", - " if not mirror.startswith(\"http://yann.lecun.com\")]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Much of Lightning is built on the [Modules](https://pytorch.org/docs/stable/generated/torch.nn.Module.html)\n", - "API from PyTorch,\n", - "but adds extra features\n", - "(like data loading and logging)\n", - "that are common to lots of PyTorch projects.\n", - "\n", - "Let's bring those in,\n", - "plus W&B and the integration.\n", - "\n", - "Lastly, we log in to the [Weights & Biases web service](https://wandb.ai).\n", - "If you've never used W&B,\n", - "you'll need to sign up first.\n", - "Accounts are free forever for academic and public projects." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# ⚡ PyTorch Lightning\n", - "import lightning.pytorch as pl\n", - "import torchmetrics\n", - "pl.seed_everything(hash(\"setting random seeds\") % 2**32 - 1)\n", - "\n", - "# 🏋️‍♀️ Weights & Biases\n", - "import wandb\n", - "\n", - "# ⚡ 🤝 🏋️‍♀️\n", - "from lightning.pytorch.loggers import WandbLogger\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wandb.login()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> _Note_: If you're executing your training in a terminal, rather than a notebook, you don't need to include `wandb.login()` in your script.\n", - "Instead, call `wandb login` in the terminal and we'll keep you logged in for future runs." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 🏗️ Building a Model with Lightning\n", - "\n", - "In PyTorch Lightning, models are built with `LightningModule` ([docs here](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html)), which has all the functionality of a vanilla `torch.nn.Module` (🍦) but with a few delicious cherries of added functionality on top (🍨).\n", - "These cherries are there to cut down on boilerplate and\n", - "help separate out the ML engineering code\n", - "from the actual machine learning.\n", - "\n", - "For example, the mechanics of iterating over batches\n", - "as part of an epoch are extracted away,\n", - "so long as you define what happens on the `training_step`.\n", - "\n", - "To make a working model out of a `LightningModule`,\n", - "we need to define a new `class` and add a few methods on top.\n", - "\n", - "We'll demonstrate this process with `LitMLP`,\n", - "which applies a two-layer perceptron\n", - "(aka two fully-connected layers and\n", - "a fully-connected softmax readout layer)\n", - "to input `Tensors`.\n", - "\n", - "> _Note_: It is common in the Lightning community to shorten \"Lightning\" to \"[Lit](https://www.urbandictionary.com/define.php?term=it%27s%20lit)\".\n", - "This sometimes it sound like\n", - "[your code was written by Travis Scott](https://www.youtube.com/watch?v=y3FCXV8oEZU).\n", - "We consider this a good thing." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 🍦 `__init__` and `forward`\n", - "\n", - "First, we need to add two methods that\n", - "are part of any vanilla PyTorch model." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Those methods are:\n", - "* `__init__` to do any setup, just like any Python class\n", - "* `forward` for inference, just like a PyTorch Module\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The `forward` pass method is standard,\n", - "and it'll be different for every project,\n", - "so we won't comment on it.\n", - "\n", - "The `__init__` method,\n", - "which `init`ializes new instances of the class,\n", - "is a good place to log hyperparameter information to `wandb`.\n", - "\n", - "This is done with the `save_hyperparameters` method,\n", - "which captures all of the arguments to the initializer\n", - "and adds them to a dictionary at `self.hparams` --\n", - "that all comes for free as part of the `LightningModule`.\n", - "\n", - "> _Note_: `hparams` is logged to `wandb` as the `config`,\n", - "so you'll never lose track of the arguments you used to run a model again!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class LitMLP(pl.LightningModule):\n", - "\n", - " def __init__(self, in_dims, n_classes=10,\n", - " n_layer_1=128, n_layer_2=256, lr=1e-4):\n", - " super().__init__()\n", - "\n", - " # we flatten the input Tensors and pass them through an MLP\n", - " self.layer_1 = nn.Linear(np.prod(in_dims), n_layer_1)\n", - " self.layer_2 = nn.Linear(n_layer_1, n_layer_2)\n", - " self.layer_3 = nn.Linear(n_layer_2, n_classes)\n", - "\n", - " # log hyperparameters\n", - " self.save_hyperparameters()\n", - "\n", - " # compute the accuracy -- no need to roll your own!\n", - " self.train_acc = torchmetrics.Accuracy()\n", - " self.valid_acc = torchmetrics.Accuracy()\n", - " self.test_acc = torchmetrics.Accuracy()\n", - "\n", - " def forward(self, x):\n", - " \"\"\"\n", - " Defines a forward pass using the Stem-Learner-Task\n", - " design pattern from Deep Learning Design Patterns:\n", - " https://www.manning.com/books/deep-learning-design-patterns\n", - " \"\"\"\n", - " batch_size, *dims = x.size()\n", - "\n", - " # stem: flatten\n", - " x = x.view(batch_size, -1)\n", - "\n", - " # learner: two fully-connected layers\n", - " x = F.relu(self.layer_1(x))\n", - " x = F.relu(self.layer_2(x))\n", - " \n", - " # task: compute class logits\n", - " x = self.layer_3(x)\n", - " x = F.log_softmax(x, dim=1)\n", - "\n", - " return x\n", - "\n", - " # convenient method to get the loss on a batch\n", - " def loss(self, xs, ys):\n", - " logits = self(xs) # this calls self.forward\n", - " loss = F.nll_loss(logits, ys)\n", - " return logits, loss" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> _Note_: for pedagogical purposes, we're splitting out\n", - "each stage of building the `LitMLP` into a different cell.\n", - "In a more typical workflow,\n", - "this would all happen in the `class` definition.\n", - "\n", - "> _Note_: if you're familiar with PyTorch,\n", - "you might be surprised to see we aren't taking care with `.device`s:\n", - "no `to_cuda` etc. PyTorch Lightning handles all that for you! 😎" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 🍨 `training_step` and `configure_optimizers`\n", - "Now, we add some special methods so that our `LitMLP` can be trained\n", - "using PyTorch Lightning's training API.\n", - "\n", - "> _Note_: if you've used Keras, this might be familiar.\n", - "It's very similar to the `.fit` API in that library." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Those methods are\n", - "\n", - "* `training_step`, which takes a batch and computes the loss; backprop goes through it\n", - "* `configure_optimizers`, which returns the `torch.optim.Optimizer` to apply after the `training_step`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> _Note_: `training_step` is part of a rich system of callbacks in PyTorch Lightning.\n", - "These callbacks are methods that get called\n", - "at specific points during training\n", - "(e.g. when a validation epoch ends),\n", - "and they are a major part of what makes\n", - "PyTorch Lightning both useful and extensible." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here's where we add some more serious logging code.\n", - "`self.log` takes a name and value for a metric.\n", - "Under the hood, this will get passed to `wandb.log` if you're using W&B.\n", - "\n", - "The logging behavior of PyTorch Lightning is both intelligent and configurable.\n", - "For example, by passing the `on_epoch`\n", - "keyword argument here,\n", - "we'll get `_epoch`-wise averages\n", - "of the metrics logged on each `_step`,\n", - "and those metrics will be named differently\n", - "in the W&B interface.\n", - "When training in a distributed setting,\n", - "these averages will be automatically computed across nodes.\n", - "\n", - "Read more about the `log` method [in the docs](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html#log)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def training_step(self, batch, batch_idx):\n", - " xs, ys = batch\n", - " logits, loss = self.loss(xs, ys)\n", - " preds = torch.argmax(logits, 1)\n", - "\n", - " # logging metrics we calculated by hand\n", - " self.log('train/loss', loss, on_epoch=True)\n", - " # logging a pl.Metric\n", - " self.train_acc(preds, ys)\n", - " self.log('train/acc', self.train_acc, on_epoch=True)\n", - " \n", - " return loss\n", - "\n", - "def configure_optimizers(self):\n", - " return torch.optim.Adam(self.parameters(), lr=self.hparams[\"lr\"])\n", - "\n", - "LitMLP.training_step = training_step\n", - "LitMLP.configure_optimizers = configure_optimizers" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## ➕ Optional methods for even better logging\n", - "\n", - "The code above will log our model's performance,\n", - "system metrics, and more to W&B.\n", - "\n", - "If we want to take our logging to the next level,\n", - "we need to make use of PyTorch Lightning's callback system.\n", - "\n", - "> _Note_: thanks to the clean design of PyTorch Lightning,\n", - "the training code below will run with or without any\n", - "of this extra logging code. Nice!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The other callbacks we'll make use of fall into two categories:\n", - "* methods that trigger on each batch for a dataset: `validation_step` and `test_step`\n", - "* methods that trigger at the end of an epoch,\n", - "or a full pass over a given dataset: `{training, validation, test}_epoch_end`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 💾 `test`ing and saving the model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We use the test set to evaluate the performance of the final model,\n", - "so the `test` callbacks will be called at the end of the training pipeline.\n", - "\n", - "For performance on the `test` and `validation` sets,\n", - "we're typically less concerned about how\n", - "we do on intermediate steps and more\n", - "with how we did overall.\n", - "That's why below, we pass in\n", - "`on_step=False` and `on_epoch=True`\n", - "so that we log only `epoch`-wise metrics.\n", - "\n", - "> _Note_: That's actually the default behavior for `.log` when it's called inside of a `validation` or a `test` loop -- but not when it's called inside a `training` loop! Check out the table of default behaviors for `.log` [in the docs](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html#log)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def test_step(self, batch, batch_idx):\n", - " xs, ys = batch\n", - " logits, loss = self.loss(xs, ys)\n", - " preds = torch.argmax(logits, 1)\n", - "\n", - " self.test_acc(preds, ys)\n", - " self.log(\"test/loss_epoch\", loss, on_step=False, on_epoch=True)\n", - " self.log(\"test/acc_epoch\", self.test_acc, on_step=False, on_epoch=True)\n", - "\n", - "LitMLP.test_step = test_step" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We'll also take the opportunity to save the model in the\n", - "[portable `ONNX` format](https://onnx.ai/).\n", - "\n", - "\n", - "Later,\n", - "we'll see that this allows us to use the\n", - "[Netron model viewer](https://github.com/lutzroeder/netron) in W&B." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def test_epoch_end(self, test_step_outputs): # args are defined as part of pl API\n", - " dummy_input = torch.zeros(self.hparams[\"in_dims\"], device=self.device)\n", - " model_filename = \"model_final.onnx\"\n", - " self.to_onnx(model_filename, dummy_input, export_params=True)\n", - " artifact = wandb.Artifact(name=\"model.ckpt\", type=\"model\")\n", - " artifact.add_file(model_filename)\n", - " wandb.log_artifact(artifact)\n", - "\n", - "LitMLP.test_epoch_end = test_epoch_end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 📊 Logging `Histograms`" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "For the `validation_data`,\n", - "let's track not only the `acc`uracy and `loss`,\n", - "but also the `logits`:\n", - "the un-normalized class probabilities.\n", - "That way, we can track if our network\n", - "is becoming more or less confident over time.\n", - "\n", - "There's a problem though:\n", - "`.log` wants to average,\n", - "but we'd rather look at a distribution.\n", - "\n", - "So instead, on every `validation_step`,\n", - "we'll `return` the `logits`,\n", - "rather than `log`ging them.\n", - "\n", - "Then, when we reach the `end`\n", - "of the `validation_epoch`,\n", - "the `logits` are available as the\n", - "`validation_step_outputs` -- a list.\n", - "\n", - "So to log we'll take those `logits`,\n", - "concatenate them together,\n", - "and turn them into a histogram with [`wandb.Histogram`](https://docs.wandb.com/library/log#histograms).\n", - "\n", - "Because we're no longer using Lightning's `.log` interface and are instead using `wandb`,\n", - "we need to drop down a level and use\n", - "`self.experiment.logger.log`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def validation_step(self, batch, batch_idx):\n", - " xs, ys = batch\n", - " logits, loss = self.loss(xs, ys)\n", - " preds = torch.argmax(logits, 1)\n", - " self.valid_acc(preds, ys)\n", - "\n", - " self.log(\"valid/loss_epoch\", loss) # default on val/test is on_epoch only\n", - " self.log('valid/acc_epoch', self.valid_acc)\n", - " \n", - " return logits\n", - "\n", - "def validation_epoch_end(self, validation_step_outputs):\n", - " dummy_input = torch.zeros(self.hparams[\"in_dims\"], device=self.device)\n", - " model_filename = f\"model_{str(self.global_step).zfill(5)}.onnx\"\n", - " torch.onnx.export(self, dummy_input, model_filename, opset_version=11)\n", - " artifact = wandb.Artifact(name=\"model.ckpt\", type=\"model\")\n", - " artifact.add_file(model_filename)\n", - " self.logger.experiment.log_artifact(artifact)\n", - "\n", - " flattened_logits = torch.flatten(torch.cat(validation_step_outputs))\n", - " self.logger.experiment.log(\n", - " {\"valid/logits\": wandb.Histogram(flattened_logits.to(\"cpu\")),\n", - " \"global_step\": self.global_step})\n", - "\n", - "LitMLP.validation_step = validation_step\n", - "LitMLP.validation_epoch_end = validation_epoch_end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Note that we're once again saving\n", - "the model in ONNX format.\n", - "That way, we can roll back our model to any given epoch --\n", - "useful in case the evaluation on the test set reveals we've overfit." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### 📲 `Callback`s for extra-fancy logging" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What we've done so far\n", - "will tell us how well our model\n", - "is using our system resources,\n", - "how well our model is training and generalizing,\n", - "and how confident it is.\n", - "\n", - "But DNNs often fail in pernicious and silent ways.\n", - "Often, the only way to notice these failures\n", - "is to look at how the model is doing\n", - "on specific examples.\n", - "\n", - "So let's additionally log some detailed information on some specific examples:\n", - "the inputs, outputs,\n", - "and `pred`ictions.\n", - "\n", - "We'll do this by writing our own `Callback` --\n", - "one that, after every `validation_epoch` ends,\n", - "logs input images and output predictions\n", - "using W&B's `Image` logger.\n", - "\n", - "> _Note_:\n", - "For more on the W&B media toolkit, read the [docs](https://docs.wandb.com/library/log#media)\n", - "or check out\n", - "[this Colab](http://wandb.me/media-colab)\n", - "to see everything it's capable of." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class ImagePredictionLogger(pl.Callback):\n", - " def __init__(self, val_samples, num_samples=32):\n", - " super().__init__()\n", - " self.val_imgs, self.val_labels = val_samples\n", - " self.val_imgs = self.val_imgs[:num_samples]\n", - " self.val_labels = self.val_labels[:num_samples]\n", - " \n", - " def on_validation_epoch_end(self, trainer, pl_module):\n", - " val_imgs = self.val_imgs.to(device=pl_module.device)\n", - "\n", - " logits = pl_module(val_imgs)\n", - " preds = torch.argmax(logits, 1)\n", - "\n", - " trainer.logger.experiment.log({\n", - " \"examples\": [wandb.Image(x, caption=f\"Pred:{pred}, Label:{y}\") \n", - " for x, pred, y in zip(val_imgs, preds, self.val_labels)],\n", - " \"global_step\": trainer.global_step\n", - " })" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 🛒 Loading data\n", - "\n", - "Data pipelines can be created with:\n", - "* 🍦 Vanilla Pytorch `DataLoaders`\n", - "* ⚡ Pytorch Lightning `DataModules`\n", - "\n", - "`DataModules` are more structured definition, which allows for additional optimizations such as automated distribution of workload between CPU & GPU.\n", - "Using `DataModules` is recommended whenever possible!\n", - "\n", - "A `DataModule` is also defined by an interface:\n", - "* `prepare_data` (optional) which is called only once and on 1 GPU -- typically something like the data download step we have below\n", - "* `setup`, which is called on each GPU separately and accepts `stage` to define if we are at `fit` or `test` step\n", - "* `train_dataloader`, `val_dataloader` and `test_dataloader` to load each dataset" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "class MNISTDataModule(pl.LightningDataModule):\n", - "\n", - " def __init__(self, data_dir='./', batch_size=128):\n", - " super().__init__()\n", - " self.data_dir = data_dir\n", - " self.batch_size = batch_size\n", - " self.transform = transforms.Compose([\n", - " transforms.ToTensor(),\n", - " transforms.Normalize((0.1307,), (0.3081,))])\n", - "\n", - " def prepare_data(self):\n", - " # download data, train then test\n", - " MNIST(self.data_dir, train=True, download=True)\n", - " MNIST(self.data_dir, train=False, download=True)\n", - "\n", - " def setup(self, stage=None):\n", - "\n", - " # we set up only relevant datasets when stage is specified\n", - " if stage == 'fit' or stage is None:\n", - " mnist = MNIST(self.data_dir, train=True, transform=self.transform)\n", - " self.mnist_train, self.mnist_val = random_split(mnist, [55000, 5000])\n", - " if stage == 'test' or stage is None:\n", - " self.mnist_test = MNIST(self.data_dir, train=False, transform=self.transform)\n", - "\n", - " # we define a separate DataLoader for each of train/val/test\n", - " def train_dataloader(self):\n", - " mnist_train = DataLoader(self.mnist_train, batch_size=self.batch_size)\n", - " return mnist_train\n", - "\n", - " def val_dataloader(self):\n", - " mnist_val = DataLoader(self.mnist_val, batch_size=10 * self.batch_size)\n", - " return mnist_val\n", - "\n", - " def test_dataloader(self):\n", - " mnist_test = DataLoader(self.mnist_test, batch_size=10 * self.batch_size)\n", - " return mnist_test\n", - "\n", - "# setup data\n", - "mnist = MNISTDataModule()\n", - "mnist.prepare_data()\n", - "mnist.setup()\n", - "\n", - "# grab samples to log predictions on\n", - "samples = next(iter(mnist.val_dataloader()))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 👟 Making a `Trainer`\n", - "\n", - "The `DataLoader` and the `LightningModule`\n", - "are brought together by a `Trainer`,\n", - "which orchestrates data loading,\n", - "gradient calculation,\n", - "optimizer logic,\n", - "and logging. \n", - "\n", - "Luckily, we don't need to sub-class the `Trainer`,\n", - "we just need to configure it with keyword arguments." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And that is where we'll use the `pytorch_lightning.loggers.WandbLogger` to connect our logging to W&B." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "wandb_logger = WandbLogger(project=\"lit-wandb\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> _Note_: Check out [the documentation](https://docs.wandb.com/library/integrations/lightning) for customization options. I like `group`s and `tag`s!.\n", - "\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can then set up our `Trainer` and customize several options, such as gradient accumulation, half precision training and distributed computing.\n", - "\n", - "We'll stick to the basics for this example,\n", - "but half-precision training and easy scaling to distributed settings are two of the major reasons why folks like PyTorch Lightning!" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "trainer = pl.Trainer(\n", - " logger=wandb_logger, # W&B integration\n", - " log_every_n_steps=50, # set the logging frequency\n", - " gpus=-1, # use all GPUs\n", - " max_epochs=5, # number of epochs\n", - " deterministic=True, # keep it deterministic\n", - " callbacks=[ImagePredictionLogger(samples)] # see Callbacks section\n", - " )" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# 🏃‍♀️ Running our Model" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now, let's make it all happen:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# setup model\n", - "model = LitMLP(in_dims=(1, 28, 28))\n", - "\n", - "# fit the model\n", - "trainer.fit(model, mnist)\n", - "\n", - "# evaluate the model on a test set\n", - "trainer.test(datamodule=mnist,\n", - " ckpt_path=None) # uses last-saved model\n", - "\n", - "wandb.finish()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "> _Note_: In notebooks, we need to call `wandb.finish()` to indicate when we've finished our run. This isn't necessary in scripts." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Viewing the results on wandb.ai\n", - "\n", - "Among the outputs from W&B,\n", - "you will have noticed a few URLs.\n", - "One of these is the\n", - "[run page](https://docs.wandb.ai/ref/app/pages/run-page),\n", - "which has a dashboard with all of the information logged in this run, complete with smart default charts\n", - "and more.\n", - "The run page is printed both at the start and end of training, and ends with `lit-wandb/runs/{run_id}`.\n", - "\n", - ">_Note_: When visiting your run page, it is recommended to use `global_step` as x-axis to correctly superimpose metrics logged in different stages.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "![image.png](data:image/png;base64,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)" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "include_colab_link": true, - "provenance": [], - "toc_visible": true - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "z5w0NbyRVCKD" + }, + "source": [ + "\"Open\n", + "" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "7PI6YWwrVCKF" + }, + "source": [ + "\"Weights\n", + "\n", + "\n", + "\n", + "# ⚡ 💘 🏋️‍♀️ Supercharge your Training with PyTorch Lightning + Weights & Biases" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mPqv_lacVCKF" + }, + "source": [ + "\"Weights" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "zJwmNjhlVCKF" + }, + "source": [ + "At Weights & Biases, we love anything\n", + "that makes training deep learning models easier.\n", + "That's why we worked with the folks at PyTorch Lightning to\n", + "[integrate our experiment tracking tool](https://docs.wandb.com/library/integrations/lightning)\n", + "directly into\n", + "[the Lightning library](https://pytorch-lightning.readthedocs.io/en/latest/common/loggers.html#weights-and-biases).\n", + "\n", + "[PyTorch Lightning](https://pytorch-lightning.readthedocs.io/en/stable/) is a lightweight wrapper for organizing your PyTorch code and easily adding advanced features such as distributed training and 16-bit precision.\n", + "It retains all the flexibility of PyTorch,\n", + "in case you need it,\n", + "but adds some useful abstractions\n", + "and builds in some best practices.\n", + "\n", + "## What this notebook covers:\n", + "\n", + "1. Differences between PyTorch and PyTorch Lightning, including how to set up `LightningModules` and `LightningDataModules`\n", + "2. How to get basic metric logging with the [`WandbLogger`](https://pytorch-lightning.readthedocs.io/en/latest/common/loggers.html#weights-and-biases)\n", + "3. How to log media with W&B and fully customize logging with Lightning `Callbacks`\n", + "\n", + "## The interactive dashboard in W&B will look like this:\n", + "\n", + "![](https://i.imgur.com/lIbMyFR.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "3-dKMdt4VCKG" + }, + "source": [ + "## Follow along with a [video tutorial](http://wandb.me/lit-video)!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Y-QLq_y4VCKG" + }, + "source": [ + "# 🚀 Installing and importing" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LgFEVQPiVCKG" + }, + "source": [ + "`wandb` and `pytorch-lightning` are both easily installable via [`pip`](https://pip.pypa.io/en/stable/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "YKrH3BHxVCKG" + }, + "outputs": [], + "source": [ + "!pip install -qqq wandb lightning torchmetrics onnx" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "HyRgs61mVCKG" + }, + "source": [ + "PyTorch Lightning is built on top of PyTorch,\n", + "so we still need to import vanilla PyTorch." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "UksNGoWZVCKH" + }, + "outputs": [], + "source": [ + "# numpy for non-GPU array math\n", + "import numpy as np\n", + "\n", + "# 🍦 Vanilla PyTorch\n", + "import torch\n", + "from torch.nn import functional as F\n", + "from torch import nn\n", + "from torch.utils.data import DataLoader, random_split\n", + "\n", + "# 👀 Torchvision for CV\n", + "from torchvision.datasets import MNIST\n", + "from torchvision import transforms\n", + "\n", + "# remove slow mirror from list of MNIST mirrors\n", + "MNIST.mirrors = [mirror for mirror in MNIST.mirrors\n", + " if not mirror.startswith(\"http://yann.lecun.com\")]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kOwGIep1VCKH" + }, + "source": [ + "Much of Lightning is built on the [Modules](https://pytorch.org/docs/stable/generated/torch.nn.Module.html)\n", + "API from PyTorch,\n", + "but adds extra features\n", + "(like data loading and logging)\n", + "that are common to lots of PyTorch projects.\n", + "\n", + "Let's bring those in,\n", + "plus W&B and the integration.\n", + "\n", + "Lastly, we log in to the [Weights & Biases web service](https://wandb.ai).\n", + "If you've never used W&B,\n", + "you'll need to sign up first.\n", + "Accounts are free forever for academic and public projects." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "6SpApyF_VCKH" + }, + "outputs": [], + "source": [ + "# ⚡ PyTorch Lightning\n", + "import lightning.pytorch as pl\n", + "import torchmetrics\n", + "pl.seed_everything(hash(\"setting random seeds\") % 2**32 - 1)\n", + "\n", + "# 🏋️‍♀️ Weights & Biases\n", + "import wandb\n", + "\n", + "# ⚡ 🤝 🏋️‍♀️\n", + "from lightning.pytorch.loggers import WandbLogger\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "WLJp3GnxVCKH" + }, + "outputs": [], + "source": [ + "wandb.login()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "bw6unCdQVCKH" + }, + "source": [ + "> _Note_: If you're executing your training in a terminal, rather than a notebook, you don't need to include `wandb.login()` in your script.\n", + "Instead, call `wandb login` in the terminal and we'll keep you logged in for future runs." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "VHlNYD9iVCKH" + }, + "source": [ + "# 🏗️ Building a Model with Lightning\n", + "\n", + "In PyTorch Lightning, models are built with `LightningModule` ([docs here](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html)), which has all the functionality of a vanilla `torch.nn.Module` (🍦) but with a few delicious cherries of added functionality on top (🍨).\n", + "These cherries are there to cut down on boilerplate and\n", + "help separate out the ML engineering code\n", + "from the actual machine learning.\n", + "\n", + "For example, the mechanics of iterating over batches\n", + "as part of an epoch are extracted away,\n", + "so long as you define what happens on the `training_step`.\n", + "\n", + "To make a working model out of a `LightningModule`,\n", + "we need to define a new `class` and add a few methods on top.\n", + "\n", + "We'll demonstrate this process with `LitMLP`,\n", + "which applies a two-layer perceptron\n", + "(aka two fully-connected layers and\n", + "a fully-connected softmax readout layer)\n", + "to input `Tensors`.\n", + "\n", + "> _Note_: It is common in the Lightning community to shorten \"Lightning\" to \"[Lit](https://www.urbandictionary.com/define.php?term=it%27s%20lit)\".\n", + "This sometimes it sound like\n", + "[your code was written by Travis Scott](https://www.youtube.com/watch?v=y3FCXV8oEZU).\n", + "We consider this a good thing." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "RkIVeyM9VCKI" + }, + "source": [ + "## 🍦 `__init__` and `forward`\n", + "\n", + "First, we need to add two methods that\n", + "are part of any vanilla PyTorch model." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "coQH1peAVCKI" + }, + "source": [ + "Those methods are:\n", + "* `__init__` to do any setup, just like any Python class\n", + "* `forward` for inference, just like a PyTorch Module\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "xr46KZ1ZVCKI" + }, + "source": [ + "The `forward` pass method is standard,\n", + "and it'll be different for every project,\n", + "so we won't comment on it.\n", + "\n", + "The `__init__` method,\n", + "which `init`ializes new instances of the class,\n", + "is a good place to log hyperparameter information to `wandb`.\n", + "\n", + "This is done with the `save_hyperparameters` method,\n", + "which captures all of the arguments to the initializer\n", + "and adds them to a dictionary at `self.hparams` --\n", + "that all comes for free as part of the `LightningModule`.\n", + "\n", + "> _Note_: `hparams` is logged to `wandb` as the `config`,\n", + "so you'll never lose track of the arguments you used to run a model again!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "-16a4P3nVCKI" + }, + "outputs": [], + "source": [ + "class LitMLP(pl.LightningModule):\n", + "\n", + " def __init__(self, in_dims, n_classes=10,\n", + " n_layer_1=128, n_layer_2=256, lr=1e-4):\n", + " super().__init__()\n", + "\n", + " # we flatten the input Tensors and pass them through an MLP\n", + " self.layer_1 = nn.Linear(np.prod(in_dims), n_layer_1)\n", + " self.layer_2 = nn.Linear(n_layer_1, n_layer_2)\n", + " self.layer_3 = nn.Linear(n_layer_2, n_classes)\n", + "\n", + " # log hyperparameters\n", + " self.save_hyperparameters()\n", + "\n", + " # compute the accuracy -- no need to roll your own!\n", + " self.train_acc = torchmetrics.Accuracy(task=\"multiclass\", num_classes=n_classes)\n", + " self.valid_acc = torchmetrics.Accuracy(task=\"multiclass\", num_classes=n_classes)\n", + " self.test_acc = torchmetrics.Accuracy(task=\"multiclass\", num_classes=n_classes)\n", + "\n", + " def forward(self, x):\n", + " \"\"\"\n", + " Defines a forward pass using the Stem-Learner-Task\n", + " design pattern from Deep Learning Design Patterns:\n", + " https://www.manning.com/books/deep-learning-design-patterns\n", + " \"\"\"\n", + " batch_size, *dims = x.size()\n", + "\n", + " # stem: flatten\n", + " x = x.view(batch_size, -1)\n", + "\n", + " # learner: two fully-connected layers\n", + " x = F.relu(self.layer_1(x))\n", + " x = F.relu(self.layer_2(x))\n", + "\n", + " # task: compute class logits\n", + " x = self.layer_3(x)\n", + " x = F.log_softmax(x, dim=1)\n", + "\n", + " return x\n", + "\n", + " # convenient method to get the loss on a batch\n", + " def loss(self, xs, ys):\n", + " logits = self(xs) # this calls self.forward\n", + " loss = F.nll_loss(logits, ys)\n", + " return logits, loss" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "XHIwTEqwVCKI" + }, + "source": [ + "> _Note_: for pedagogical purposes, we're splitting out\n", + "each stage of building the `LitMLP` into a different cell.\n", + "In a more typical workflow,\n", + "this would all happen in the `class` definition.\n", + "\n", + "> _Note_: if you're familiar with PyTorch,\n", + "you might be surprised to see we aren't taking care with `.device`s:\n", + "no `to_cuda` etc. PyTorch Lightning handles all that for you! 😎" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "q9n788xQVCKI" + }, + "source": [ + "## 🍨 `training_step` and `configure_optimizers`\n", + "Now, we add some special methods so that our `LitMLP` can be trained\n", + "using PyTorch Lightning's training API.\n", + "\n", + "> _Note_: if you've used Keras, this might be familiar.\n", + "It's very similar to the `.fit` API in that library." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "0PTKSV_4VCKI" + }, + "source": [ + "Those methods are\n", + "\n", + "* `training_step`, which takes a batch and computes the loss; backprop goes through it\n", + "* `configure_optimizers`, which returns the `torch.optim.Optimizer` to apply after the `training_step`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "KL-Yuk-DVCKI" + }, + "source": [ + "> _Note_: `training_step` is part of a rich system of callbacks in PyTorch Lightning.\n", + "These callbacks are methods that get called\n", + "at specific points during training\n", + "(e.g. when a validation epoch ends),\n", + "and they are a major part of what makes\n", + "PyTorch Lightning both useful and extensible." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "S_xhIcNbVCKI" + }, + "source": [ + "Here's where we add some more serious logging code.\n", + "`self.log` takes a name and value for a metric.\n", + "Under the hood, this will get passed to `wandb.log` if you're using W&B.\n", + "\n", + "The logging behavior of PyTorch Lightning is both intelligent and configurable.\n", + "For example, by passing the `on_epoch`\n", + "keyword argument here,\n", + "we'll get `_epoch`-wise averages\n", + "of the metrics logged on each `_step`,\n", + "and those metrics will be named differently\n", + "in the W&B interface.\n", + "When training in a distributed setting,\n", + "these averages will be automatically computed across nodes.\n", + "\n", + "Read more about the `log` method [in the docs](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html#log)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "IpbUYwBDVCKJ" + }, + "outputs": [], + "source": [ + "def training_step(self, batch, batch_idx):\n", + " xs, ys = batch\n", + " logits, loss = self.loss(xs, ys)\n", + " preds = torch.argmax(logits, 1)\n", + "\n", + " # logging metrics we calculated by hand\n", + " self.log('train/loss', loss, on_epoch=True)\n", + " # logging a pl.Metric\n", + " self.train_acc(preds, ys)\n", + " self.log('train/acc', self.train_acc, on_epoch=True)\n", + "\n", + " return loss\n", + "\n", + "def configure_optimizers(self):\n", + " return torch.optim.Adam(self.parameters(), lr=self.hparams[\"lr\"])\n", + "\n", + "LitMLP.training_step = training_step\n", + "LitMLP.configure_optimizers = configure_optimizers" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JXquZkggVCKJ" + }, + "source": [ + "## ➕ Optional methods for even better logging\n", + "\n", + "The code above will log our model's performance,\n", + "system metrics, and more to W&B.\n", + "\n", + "If we want to take our logging to the next level,\n", + "we need to make use of PyTorch Lightning's callback system.\n", + "\n", + "> _Note_: thanks to the clean design of PyTorch Lightning,\n", + "the training code below will run with or without any\n", + "of this extra logging code. Nice!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TE8qYwtOVCKJ" + }, + "source": [ + "The other callbacks we'll make use of fall into two categories:\n", + "* methods that trigger on each batch for a dataset: `validation_step` and `test_step`\n", + "* methods that trigger at the end of an epoch,\n", + "or a full pass over a given dataset: `{training, validation, test}_epoch_end`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Z8JylsGXVCKJ" + }, + "source": [ + "### 💾 `test`ing and saving the model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "AHy-SYLDVCKJ" + }, + "source": [ + "We use the test set to evaluate the performance of the final model,\n", + "so the `test` callbacks will be called at the end of the training pipeline.\n", + "\n", + "For performance on the `test` and `validation` sets,\n", + "we're typically less concerned about how\n", + "we do on intermediate steps and more\n", + "with how we did overall.\n", + "That's why below, we pass in\n", + "`on_step=False` and `on_epoch=True`\n", + "so that we log only `epoch`-wise metrics.\n", + "\n", + "> _Note_: That's actually the default behavior for `.log` when it's called inside of a `validation` or a `test` loop -- but not when it's called inside a `training` loop! Check out the table of default behaviors for `.log` [in the docs](https://pytorch-lightning.readthedocs.io/en/latest/lightning_module.html#log)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "yeVmedTTVCKJ" + }, + "outputs": [], + "source": [ + "def test_step(self, batch, batch_idx):\n", + " xs, ys = batch\n", + " logits, loss = self.loss(xs, ys)\n", + " preds = torch.argmax(logits, 1)\n", + "\n", + " self.test_acc(preds, ys)\n", + " self.log(\"test/loss_epoch\", loss, on_step=False, on_epoch=True)\n", + " self.log(\"test/acc_epoch\", self.test_acc, on_step=False, on_epoch=True)\n", + "\n", + "LitMLP.test_step = test_step" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qk50ovviVCKJ" + }, + "source": [ + "We'll also take the opportunity to save the model in the\n", + "[portable `ONNX` format](https://onnx.ai/).\n", + "\n", + "\n", + "Later,\n", + "we'll see that this allows us to use the\n", + "[Netron model viewer](https://github.com/lutzroeder/netron) in W&B." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "QNr-9bEpVCKJ" + }, + "outputs": [], + "source": [ + "def on_test_epoch_end(self): # args are defined as part of pl API\n", + " dummy_input = torch.zeros(self.hparams[\"in_dims\"], device=self.device)\n", + " model_filename = \"model_final.onnx\"\n", + " self.to_onnx(model_filename, dummy_input, export_params=True)\n", + " artifact = wandb.Artifact(name=\"model.ckpt\", type=\"model\")\n", + " artifact.add_file(model_filename)\n", + " wandb.log_artifact(artifact)\n", + "\n", + "LitMLP.on_test_epoch_end = on_test_epoch_end" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "JosFHArGVCKK" + }, + "source": [ + "### 📊 Logging `Histograms`" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "FxViSjO4VCKK" + }, + "source": [ + "For the `validation_data`,\n", + "let's track not only the `acc`uracy and `loss`,\n", + "but also the `logits`:\n", + "the un-normalized class probabilities.\n", + "That way, we can track if our network\n", + "is becoming more or less confident over time.\n", + "\n", + "There's a problem though:\n", + "`.log` wants to average,\n", + "but we'd rather look at a distribution.\n", + "\n", + "So instead, on every `validation_step`,\n", + "we'll `return` the `logits`,\n", + "rather than `log`ging them.\n", + "\n", + "Then, when we reach the `end`\n", + "of the `validation_epoch`,\n", + "the `logits` are available as the\n", + "`validation_step_outputs` -- a list.\n", + "\n", + "So to log we'll take those `logits`,\n", + "concatenate them together,\n", + "and turn them into a histogram with [`wandb.Histogram`](https://docs.wandb.com/library/log#histograms).\n", + "\n", + "Because we're no longer using Lightning's `.log` interface and are instead using `wandb`,\n", + "we need to drop down a level and use\n", + "`self.experiment.logger.log`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VrPs4tAJVCKK" + }, + "outputs": [], + "source": [ + "def on_validation_epoch_start(self):\n", + " self.validation_step_outputs = []\n", + "\n", + "def validation_step(self, batch, batch_idx):\n", + " xs, ys = batch\n", + " logits, loss = self.loss(xs, ys)\n", + " preds = torch.argmax(logits, 1)\n", + " self.valid_acc(preds, ys)\n", + "\n", + " self.log(\"valid/loss_epoch\", loss) # default on val/test is on_epoch only\n", + " self.log('valid/acc_epoch', self.valid_acc)\n", + "\n", + " self.validation_step_outputs.append(logits)\n", + "\n", + " return logits\n", + "\n", + "def on_validation_epoch_end(self):\n", + "\n", + " validation_step_outputs = self.validation_step_outputs\n", + "\n", + " dummy_input = torch.zeros(self.hparams[\"in_dims\"], device=self.device)\n", + " model_filename = f\"model_{str(self.global_step).zfill(5)}.onnx\"\n", + " torch.onnx.export(self, dummy_input, model_filename, opset_version=11)\n", + " artifact = wandb.Artifact(name=\"model.ckpt\", type=\"model\")\n", + " artifact.add_file(model_filename)\n", + " self.logger.experiment.log_artifact(artifact)\n", + "\n", + " flattened_logits = torch.flatten(torch.cat(validation_step_outputs))\n", + " self.logger.experiment.log(\n", + " {\"valid/logits\": wandb.Histogram(flattened_logits.to(\"cpu\")),\n", + " \"global_step\": self.global_step})\n", + "\n", + "LitMLP.on_validation_epoch_start = on_validation_epoch_start\n", + "LitMLP.validation_step = validation_step\n", + "LitMLP.on_validation_epoch_end = on_validation_epoch_end" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "617Vro8jVCKK" + }, + "source": [ + "Note that we're once again saving\n", + "the model in ONNX format.\n", + "That way, we can roll back our model to any given epoch --\n", + "useful in case the evaluation on the test set reveals we've overfit." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "yMe2e8FEVCKK" + }, + "source": [ + "### 📲 `Callback`s for extra-fancy logging" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "X-DfYBQrVCKL" + }, + "source": [ + "What we've done so far\n", + "will tell us how well our model\n", + "is using our system resources,\n", + "how well our model is training and generalizing,\n", + "and how confident it is.\n", + "\n", + "But DNNs often fail in pernicious and silent ways.\n", + "Often, the only way to notice these failures\n", + "is to look at how the model is doing\n", + "on specific examples.\n", + "\n", + "So let's additionally log some detailed information on some specific examples:\n", + "the inputs, outputs,\n", + "and `pred`ictions.\n", + "\n", + "We'll do this by writing our own `Callback` --\n", + "one that, after every `validation_epoch` ends,\n", + "logs input images and output predictions\n", + "using W&B's `Image` logger.\n", + "\n", + "> _Note_:\n", + "For more on the W&B media toolkit, read the [docs](https://docs.wandb.com/library/log#media)\n", + "or check out\n", + "[this Colab](http://wandb.me/media-colab)\n", + "to see everything it's capable of." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "VM9hxnNtVCKL" + }, + "outputs": [], + "source": [ + "class ImagePredictionLogger(pl.Callback):\n", + " def __init__(self, val_samples, num_samples=32):\n", + " super().__init__()\n", + " self.val_imgs, self.val_labels = val_samples\n", + " self.val_imgs = self.val_imgs[:num_samples]\n", + " self.val_labels = self.val_labels[:num_samples]\n", + "\n", + " def on_validation_epoch_end(self, trainer, pl_module):\n", + " val_imgs = self.val_imgs.to(device=pl_module.device)\n", + "\n", + " logits = pl_module(val_imgs)\n", + " preds = torch.argmax(logits, 1)\n", + "\n", + " trainer.logger.experiment.log({\n", + " \"examples\": [wandb.Image(x, caption=f\"Pred:{pred}, Label:{y}\")\n", + " for x, pred, y in zip(val_imgs, preds, self.val_labels)],\n", + " \"global_step\": trainer.global_step\n", + " })" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6z2bcdwZVCKL" + }, + "source": [ + "# 🛒 Loading data\n", + "\n", + "Data pipelines can be created with:\n", + "* 🍦 Vanilla Pytorch `DataLoaders`\n", + "* ⚡ Pytorch Lightning `DataModules`\n", + "\n", + "`DataModules` are more structured definition, which allows for additional optimizations such as automated distribution of workload between CPU & GPU.\n", + "Using `DataModules` is recommended whenever possible!\n", + "\n", + "A `DataModule` is also defined by an interface:\n", + "* `prepare_data` (optional) which is called only once and on 1 GPU -- typically something like the data download step we have below\n", + "* `setup`, which is called on each GPU separately and accepts `stage` to define if we are at `fit` or `test` step\n", + "* `train_dataloader`, `val_dataloader` and `test_dataloader` to load each dataset" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "pO5M1XEnVCKL" + }, + "outputs": [], + "source": [ + "class MNISTDataModule(pl.LightningDataModule):\n", + "\n", + " def __init__(self, data_dir='./', batch_size=128):\n", + " super().__init__()\n", + " self.data_dir = data_dir\n", + " self.batch_size = batch_size\n", + " self.transform = transforms.Compose([\n", + " transforms.ToTensor(),\n", + " transforms.Normalize((0.1307,), (0.3081,))])\n", + "\n", + " def prepare_data(self):\n", + " # download data, train then test\n", + " MNIST(self.data_dir, train=True, download=True)\n", + " MNIST(self.data_dir, train=False, download=True)\n", + "\n", + " def setup(self, stage=None):\n", + "\n", + " # we set up only relevant datasets when stage is specified\n", + " if stage == 'fit' or stage is None:\n", + " mnist = MNIST(self.data_dir, train=True, transform=self.transform)\n", + " self.mnist_train, self.mnist_val = random_split(mnist, [55000, 5000])\n", + " if stage == 'test' or stage is None:\n", + " self.mnist_test = MNIST(self.data_dir, train=False, transform=self.transform)\n", + "\n", + " # we define a separate DataLoader for each of train/val/test\n", + " def train_dataloader(self):\n", + " mnist_train = DataLoader(self.mnist_train, batch_size=self.batch_size)\n", + " return mnist_train\n", + "\n", + " def val_dataloader(self):\n", + " mnist_val = DataLoader(self.mnist_val, batch_size=10 * self.batch_size)\n", + " return mnist_val\n", + "\n", + " def test_dataloader(self):\n", + " mnist_test = DataLoader(self.mnist_test, batch_size=10 * self.batch_size)\n", + " return mnist_test\n", + "\n", + "# setup data\n", + "mnist = MNISTDataModule()\n", + "mnist.prepare_data()\n", + "mnist.setup()\n", + "\n", + "# grab samples to log predictions on\n", + "samples = next(iter(mnist.val_dataloader()))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "6Ord5Pu3VCKM" + }, + "source": [ + "# 👟 Making a `Trainer`\n", + "\n", + "The `DataLoader` and the `LightningModule`\n", + "are brought together by a `Trainer`,\n", + "which orchestrates data loading,\n", + "gradient calculation,\n", + "optimizer logic,\n", + "and logging.\n", + "\n", + "Luckily, we don't need to sub-class the `Trainer`,\n", + "we just need to configure it with keyword arguments." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "MDDjVOSEVCKM" + }, + "source": [ + "And that is where we'll use the `pytorch_lightning.loggers.WandbLogger` to connect our logging to W&B." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "3nibeocmVCKM" + }, + "outputs": [], + "source": [ + "wandb_logger = WandbLogger(project=\"lit-wandb\")" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "YDEryNsoVCKM" + }, + "source": [ + "> _Note_: Check out [the documentation](https://docs.wandb.com/library/integrations/lightning) for customization options. I like `group`s and `tag`s!.\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "NLfJc41aVCKM" + }, + "source": [ + "We can then set up our `Trainer` and customize several options, such as gradient accumulation, half precision training and distributed computing.\n", + "\n", + "We'll stick to the basics for this example,\n", + "but half-precision training and easy scaling to distributed settings are two of the major reasons why folks like PyTorch Lightning!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "0dciVNKHVCKN" + }, + "outputs": [], + "source": [ + "trainer = pl.Trainer(\n", + " logger=wandb_logger, # W&B integration\n", + " log_every_n_steps=50, # set the logging frequency\n", + " max_epochs=5, # number of epochs\n", + " deterministic=True, # keep it deterministic\n", + " callbacks=[ImagePredictionLogger(samples)] # see Callbacks section\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Fs6hyyU2VCKN" + }, + "source": [ + "# 🏃‍♀️ Running our Model" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "LzVnPc6_VCKN" + }, + "source": [ + "Now, let's make it all happen:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "id": "q7VxL_o6VCKN" + }, + "outputs": [], + "source": [ + "# setup model\n", + "model = LitMLP(in_dims=(1, 28, 28))\n", + "\n", + "# fit the model\n", + "trainer.fit(model, mnist)\n", + "\n", + "# evaluate the model on a test set\n", + "trainer.test(datamodule=mnist,\n", + " ckpt_path=None) # uses last-saved model\n", + "\n", + "wandb.finish()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "cryJvmKOVCKN" + }, + "source": [ + "> _Note_: In notebooks, we need to call `wandb.finish()` to indicate when we've finished our run. This isn't necessary in scripts." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "t9jsRs-oVCKN" + }, + "source": [ + "## Viewing the results on wandb.ai\n", + "\n", + "Among the outputs from W&B,\n", + "you will have noticed a few URLs.\n", + "One of these is the\n", + "[run page](https://docs.wandb.ai/ref/app/pages/run-page),\n", + "which has a dashboard with all of the information logged in this run, complete with smart default charts\n", + "and more.\n", + "The run page is printed both at the start and end of training, and ends with `lit-wandb/runs/{run_id}`.\n", + "\n", + ">_Note_: When visiting your run page, it is recommended to use `global_step` as x-axis to correctly superimpose metrics logged in different stages.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "fgPkQGamVCKN" + }, + "source": [ + "\n", + "![image.png](data:image/png;base64,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)" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file