prototorch_models/docs/source/tutorial.ipynb

399 lines
12 KiB
Plaintext
Raw Permalink Normal View History

2021-05-18 17:41:58 +00:00
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# A short tutorial for the `prototorch.models` plugin"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"## Introduction"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"This is a short tutorial for the [models](https://github.com/si-cim/prototorch_models) plugin of the [ProtoTorch](https://github.com/si-cim/prototorch) framework.\n",
"\n",
"[ProtoTorch](https://github.com/si-cim/prototorch) provides [torch.nn](https://pytorch.org/docs/stable/nn.html) modules and utilities to implement prototype-based models. However, it is up to the user to put these modules together into models and handle the training of these models. Expert machine-learning practioners and researchers sometimes prefer this level of control. However, this leads to a lot of boilerplate code that is essentially same across many projects. Needless to say, this is a source of a lot of frustration. [PyTorch-Lightning](https://pytorch-lightning.readthedocs.io/en/latest/) is a framework that helps avoid a lot of this frustration by handling the boilerplate code for you so you don't have to reinvent the wheel every time you need to implement a new model.\n",
"\n",
"With the [prototorch.models](https://github.com/si-cim/prototorch_models) plugin, we've gone one step further and pre-packaged commonly used prototype-models like GMLVQ as [Lightning-Modules](https://pytorch-lightning.readthedocs.io/en/latest/api/pytorch_lightning.core.lightning.html?highlight=lightning%20module#pytorch_lightning.core.lightning.LightningModule). With only a few lines to code, it is now possible to build and train prototype-models. It quite simply cannot get any simpler than this."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"## Basics"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"First things first. When working with the models plugin, you'll probably need `torch`, `prototorch` and `pytorch_lightning`. So, we recommend that you import all three like so:"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"import prototorch as pt\n",
"import pytorch_lightning as pl\n",
"import torch"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### Building Models"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"Let's start by building a `GLVQ` model. It is one of the simplest models to build. The only requirements are a prototype distribution and an initializer."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"model = pt.models.GLVQ(\n",
" hparams=dict(distribution=[1, 1, 1]),\n",
2021-07-12 19:21:29 +00:00
" prototypes_initializer=pt.initializers.ZerosCompInitializer(2),\n",
2021-05-18 17:41:58 +00:00
")"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"print(model)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
2021-07-12 19:21:29 +00:00
"The key `distribution` in the `hparams` argument describes the prototype distribution. If it is a Python [list](https://docs.python.org/3/tutorial/datastructures.html), it is assumed that there are as many entries in this list as there are classes, and the number at each location of this list describes the number of prototypes to be used for that particular class. So, `[1, 1, 1]` implies that we have three classes with one prototype per class. If it is a Python [tuple](https://docs.python.org/3/tutorial/datastructures.html), a shorthand of `(num_classes, prototypes_per_class)` is assumed. If it is a Python [dictionary](https://docs.python.org/3/tutorial/datastructures.html), the key-value pairs describe the class label and the number of prototypes for that class respectively. So, `{0: 2, 1: 2, 2: 2}` implies that we have three classes with labels `{1, 2, 3}`, each equipped with two prototypes. If however, the dictionary contains the keys `\"num_classes\"` and `\"per_class\"`, they are parsed to use their values as one might expect.\n",
2021-05-25 18:54:07 +00:00
"\n",
2021-07-12 19:21:29 +00:00
"The `prototypes_initializer` argument describes how the prototypes are meant to be initialized. This argument has to be an instantiated object of some kind of [AbstractComponentsInitializer](https://github.com/si-cim/prototorch/blob/dev/prototorch/components/initializers.py#L18). If this is a [ShapeAwareCompInitializer](https://github.com/si-cim/prototorch/blob/dev/prototorch/components/initializers.py#L41), this only requires a `shape` arugment that describes the shape of the prototypes. So, `pt.initializers.ZerosCompInitializer(3)` creates 3d-vector prototypes all initialized to zeros."
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### Data"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"The preferred way to working with data in `torch` is to use the [Dataset and Dataloader API](https://pytorch.org/tutorials/beginner/basics/data_tutorial.html). There a few pre-packaged datasets available under `prototorch.datasets`. See [here](https://prototorch.readthedocs.io/en/latest/api.html#module-prototorch.datasets) for a full list of available datasets."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"train_ds = pt.datasets.Iris(dims=[0, 2])"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"type(train_ds)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"train_ds.data.shape, train_ds.targets.shape"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"Once we have such a dataset, we could wrap it in a `Dataloader` to load the data in batches, and possibly apply some transformations on the fly."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"train_loader = torch.utils.data.DataLoader(train_ds, batch_size=2)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"type(train_loader)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"x_batch, y_batch = next(iter(train_loader))\n",
"print(f\"{x_batch=}, {y_batch=}\")"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"This perhaps seems like a lot of work for a small dataset that fits completely in memory. However, this comes in very handy when dealing with huge datasets that can only be processed in batches."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### Training"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"If you're familiar with other deep learning frameworks, you might perhaps expect a `.fit(...)` or `.train(...)` method. However, in PyTorch-Lightning, this is done slightly differently. We first create a trainer and then pass the model and the Dataloader to `trainer.fit(...)` instead. So, it is more functional in style than object-oriented."
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"trainer = pl.Trainer(max_epochs=2, weights_summary=None)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"trainer.fit(model, train_loader)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### From data to a trained model - a very minimal example"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-18 17:41:58 +00:00
"source": [
"train_ds = pt.datasets.Iris(dims=[0, 2])\n",
"train_loader = torch.utils.data.DataLoader(train_ds, batch_size=32)\n",
"\n",
"model = pt.models.GLVQ(\n",
" dict(distribution=(3, 2), lr=0.1),\n",
2021-07-12 19:21:29 +00:00
" prototypes_initializer=pt.initializers.SMCI(train_ds),\n",
2021-05-18 17:41:58 +00:00
")\n",
"\n",
"trainer = pl.Trainer(max_epochs=50, weights_summary=None)\n",
"trainer.fit(model, train_loader)"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"## Advanced"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
2021-05-25 18:54:07 +00:00
"### Initializing prototypes with a subset of a dataset (along with transformations)"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"import prototorch as pt\n",
"import pytorch_lightning as pl\n",
"import torch\n",
"from torchvision import transforms\n",
"from torchvision.datasets import MNIST"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"from matplotlib import pyplot as plt"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"train_ds = MNIST(\n",
" \"~/datasets\",\n",
" train=True,\n",
" download=True,\n",
" transform=transforms.Compose([\n",
" transforms.RandomHorizontalFlip(p=1.0),\n",
" transforms.RandomVerticalFlip(p=1.0),\n",
" transforms.ToTensor(),\n",
" ]),\n",
")"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"s = int(0.05 * len(train_ds))\n",
"init_ds, rest_ds = torch.utils.data.random_split(train_ds, [s, len(train_ds) - s])"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"init_ds"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"model = pt.models.ImageGLVQ(\n",
" dict(distribution=(10, 5)),\n",
2021-07-12 19:21:29 +00:00
" prototypes_initializer=pt.initializers.SMCI(init_ds),\n",
2021-05-25 18:54:07 +00:00
")"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-25 18:54:07 +00:00
},
{
"cell_type": "code",
2021-07-12 19:21:29 +00:00
"execution_count": null,
2021-05-25 18:54:07 +00:00
"source": [
"plt.imshow(model.get_prototype_grid(num_columns=10))"
2021-07-12 19:21:29 +00:00
],
"outputs": [],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"## FAQs"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### How do I Retrieve the prototypes and their respective labels from the model?\n",
"\n",
"For prototype models, the prototypes can be retrieved (as `torch.tensor`) as `model.prototypes`. You can convert it to a NumPy Array by calling `.numpy()` on the tensor if required.\n",
"\n",
"```python\n",
">>> model.prototypes.numpy()\n",
"```\n",
"\n",
"Similarly, the labels of the prototypes can be retrieved via `model.prototype_labels`.\n",
"\n",
"```python\n",
">>> model.prototype_labels\n",
"```"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
},
{
"cell_type": "markdown",
"source": [
"### How do I make inferences/predictions/recall with my trained model?\n",
"\n",
2021-05-25 18:54:07 +00:00
"The models under [prototorch.models](https://github.com/si-cim/prototorch_models) provide a `.predict(x)` method for making predictions. This returns the predicted class labels. It is essential that the input to this method is a `torch.tensor` and not a NumPy array. Model instances are also callable. So, you could also just say `model(x)` as if `model` were just a function. However, this returns a (pseudo)-probability distribution over the classes.\n",
2021-05-18 17:41:58 +00:00
"\n",
"#### Example\n",
"\n",
"```python\n",
2021-05-25 18:54:07 +00:00
">>> y_pred = model.predict(torch.Tensor(x_train)) # returns class labels\n",
"```\n",
"or, simply\n",
"```python\n",
">>> y_pred = model(torch.Tensor(x_train)) # returns probabilities\n",
2021-05-18 17:41:58 +00:00
"```"
2021-07-12 19:21:29 +00:00
],
"metadata": {}
2021-05-18 17:41:58 +00:00
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"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.9.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
}