Model Evaluation#

In this lesson, we’ll evaluate the model experiments from our Hydra workflow. We want identify the model with the best relative performance.

Import required packages.

import os

import hydra
import torch
from omegaconf import OmegaConf

from ml_pipeline.datasets.datamodule import BurnScarsDataModule
from ml_pipeline.model.lightningmodule import BurnScarsSegmentationModel

Configure Hydra.

# load hydra configuration
with hydra.initialize(config_path="../../config", version_base="1.3.0"):
    cfg = hydra.compose(
        config_name="config",
        overrides=["seed=0", "author=devseed", "name=test-exp-nb-1"],
        return_hydra_config=True,
    )

Check the parameters of the datamodule.

print(OmegaConf.to_yaml(cfg.datamodule))
image_query:
  bbox:
  - -119.1
  - 36.2
  - -118.2
  - 36.9
  datetime:
  - '2021-08-15T00:00:00Z'
  - '2021-09-15T23:59:59Z'
  collections:
  - HLSS30.v2.0
vector_url: https://gist.githubusercontent.com/weiji14/286032ac2498d10e050ba585257dd50d/raw/c897c7c1b3b8354ec8c6e8327df38fcfee79b4ef/burn_scars.geojson
batch_size: 4

Check the static parameters of the model.

print(OmegaConf.to_yaml(cfg.model))
encoder_name: resnet18
encoder_depth: 5
encoder_weights: null
in_channels: 5
classes: 1
activation: null
lr: 0.001

Only the weights of the best performing model experiment were written to disk.

# use the best model ckpt for evaluation
!pwd
!ls logs/checkpoint/
/home/runner/work/ml-pipeline/ml-pipeline/jbook/docs
ls: cannot access 'logs/checkpoint/': No such file or directory
CKPT = os.path.join(cfg.callbacks.model_checkpoint.dirpath, "last.ckpt")
CKPT
'/home/runner/work/ml-pipeline/ml-pipeline/jbook/docs/logs/checkpoint/last.ckpt'

Load the data and model modules for prediction.

# Load the datamodule with predict setup
datamodule = BurnScarsDataModule(**cfg.datamodule)
datamodule.setup(stage="predict")
# Loads the model with the best trained checkpoint weights for evaluation
model = BurnScarsSegmentationModel.load_from_checkpoint(CKPT)
_ = model.eval()  # set the model to evaluation mode
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[8], line 2
      1 # Loads the model with the best trained checkpoint weights for evaluation
----> 2 model = BurnScarsSegmentationModel.load_from_checkpoint(CKPT)
      3 _ = model.eval()  # set the model to evaluation mode

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/lightning/pytorch/core/module.py:1537, in LightningModule.load_from_checkpoint(cls, checkpoint_path, map_location, hparams_file, strict, **kwargs)
   1457 @classmethod
   1458 def load_from_checkpoint(
   1459     cls,
   (...)
   1464     **kwargs: Any,
   1465 ) -> Self:
   1466     r"""
   1467     Primary way of loading a model from a checkpoint. When Lightning saves a checkpoint
   1468     it stores the arguments passed to ``__init__``  in the checkpoint under ``"hyper_parameters"``.
   (...)
   1535         y_hat = pretrained_model(x)
   1536     """
-> 1537     loaded = _load_from_checkpoint(
   1538         cls,
   1539         checkpoint_path,
   1540         map_location,
   1541         hparams_file,
   1542         strict,
   1543         **kwargs,
   1544     )
   1545     return cast(Self, loaded)

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/lightning/pytorch/core/saving.py:63, in _load_from_checkpoint(cls, checkpoint_path, map_location, hparams_file, strict, **kwargs)
     54 def _load_from_checkpoint(
     55     cls: Union[Type["pl.LightningModule"], Type["pl.LightningDataModule"]],
     56     checkpoint_path: Union[_PATH, IO],
   (...)
     60     **kwargs: Any,
     61 ) -> Union["pl.LightningModule", "pl.LightningDataModule"]:
     62     with pl_legacy_patch():
---> 63         checkpoint = pl_load(checkpoint_path, map_location=map_location)
     65     # convert legacy checkpoints to the new format
     66     checkpoint = _pl_migrate_checkpoint(
     67         checkpoint, checkpoint_path=(checkpoint_path if isinstance(checkpoint_path, (str, Path)) else None)
     68     )

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/lightning/fabric/utilities/cloud_io.py:51, in _load(path_or_url, map_location)
     46     return torch.hub.load_state_dict_from_url(
     47         str(path_or_url),
     48         map_location=map_location,  # type: ignore[arg-type]
     49     )
     50 fs = get_filesystem(path_or_url)
---> 51 with fs.open(path_or_url, "rb") as f:
     52     return torch.load(f, map_location=map_location)

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/fsspec/spec.py:1241, in AbstractFileSystem.open(self, path, mode, block_size, cache_options, compression, **kwargs)
   1239 else:
   1240     ac = kwargs.pop("autocommit", not self._intrans)
-> 1241     f = self._open(
   1242         path,
   1243         mode=mode,
   1244         block_size=block_size,
   1245         autocommit=ac,
   1246         cache_options=cache_options,
   1247         **kwargs,
   1248     )
   1249     if compression is not None:
   1250         from fsspec.compression import compr

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/fsspec/implementations/local.py:184, in LocalFileSystem._open(self, path, mode, block_size, **kwargs)
    182 if self.auto_mkdir and "w" in mode:
    183     self.makedirs(self._parent(path), exist_ok=True)
--> 184 return LocalFileOpener(path, mode, fs=self, **kwargs)

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/fsspec/implementations/local.py:315, in LocalFileOpener.__init__(self, path, mode, autocommit, fs, compression, **kwargs)
    313 self.compression = get_compression(path, compression)
    314 self.blocksize = io.DEFAULT_BUFFER_SIZE
--> 315 self._open()

File ~/micromamba-root/envs/mlpipeline/lib/python3.9/site-packages/fsspec/implementations/local.py:320, in LocalFileOpener._open(self)
    318 if self.f is None or self.f.closed:
    319     if self.autocommit or "w" not in self.mode:
--> 320         self.f = open(self.path, mode=self.mode)
    321         if self.compression:
    322             compress = compr[self.compression]

FileNotFoundError: [Errno 2] No such file or directory: '/home/runner/work/ml-pipeline/ml-pipeline/jbook/docs/logs/checkpoint/last.ckpt'

Load the validation partition.

# load the validation dataloader
val_dataloader = datamodule.val_dataloader()
def run_prediction(model, dataloader):
    """
    Loop through the dataloader & get model predictions.

    Args:
        model: model with trained weights
        dataloader: dataloader to run inference on

    Returns:
        preds: prediction results
        masks: ground truth labels
    """
    preds = []
    masks = []
    with torch.inference_mode():
        for batch in dataloader:
            image, mask = batch
            pred = model(image)
            preds.extend(pred.detach().numpy())
            masks.extend(mask.detach().numpy())
    return preds, masks

Get model predictions and their respective ground truths from the test partition.

preds, targets = run_prediction(model=model, dataloader=val_dataloader)
# We have the trained the model on a tiny subset of the burn scars dataset to
# show the capabilities of ml-pipeline.
# Hence, the model is overfitting on NON-FLOODED class.
# To properly display the f1,precision,recall score & the confusion matrix, adding here a couple of predictions for FLOODED class.
preds.extend([1, 1])
targets.extend([1, 0])

Generate confusion matrices, precision, recall and f1-scores.

from sklearn.metrics import confusion_matrix, classification_report
print(classification_report(preds, targets))
confusion_matrix(preds, targets, labels=[0, 1])