{ "cells": [ { "cell_type": "code", "execution_count": null, "id": "d11da809", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "#| eval: false\n", "! [ -e /content ] && pip install -Uqq fastai # upgrade fastai on colab" ] }, { "cell_type": "code", "execution_count": null, "id": "a42cdf59", "metadata": {}, "outputs": [], "source": [ "#| default_exp interpret" ] }, { "cell_type": "code", "execution_count": null, "id": "e22b2ff8", "metadata": {}, "outputs": [], "source": [ "#| export\n", "from fastai.data.all import *\n", "from fastai.optimizer import *\n", "from fastai.learner import *\n", "from fastai.tabular.core import *\n", "import sklearn.metrics as skm" ] }, { "cell_type": "code", "execution_count": null, "id": "d3242afe", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "from fastai.test_utils import *\n", "from nbdev.showdoc import *" ] }, { "cell_type": "markdown", "id": "1016a587", "metadata": {}, "source": [ "# Interpretation of Predictions\n", "\n", "> Classes to build objects to better interpret predictions of a model" ] }, { "cell_type": "code", "execution_count": null, "id": "f8978c9a", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "from fastai.vision.all import *" ] }, { "cell_type": "code", "execution_count": null, "id": "057737c9", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "mnist = DataBlock(blocks=(ImageBlock(cls=PILImageBW), CategoryBlock), \n", " get_items=get_image_files, \n", " splitter=RandomSubsetSplitter(.1,.1, seed=42),\n", " get_y=parent_label)\n", "test_dls = mnist.dataloaders(untar_data(URLs.MNIST_SAMPLE), bs=8)\n", "test_learner = vision_learner(test_dls, resnet18)" ] }, { "cell_type": "code", "execution_count": null, "id": "86b1e0d5", "metadata": {}, "outputs": [], "source": [ "#| export\n", "@dispatch\n", "def plot_top_losses(x, y, *args, **kwargs):\n", " raise Exception(f\"plot_top_losses is not implemented for {type(x)},{type(y)}\")" ] }, { "cell_type": "code", "execution_count": null, "id": "a4f71a5c", "metadata": {}, "outputs": [], "source": [ "#| export\n", "_all_ = [\"plot_top_losses\"]" ] }, { "cell_type": "code", "execution_count": null, "id": "3457588c", "metadata": {}, "outputs": [], "source": [ "#| export\n", "class Interpretation():\n", " \"Interpretation base class, can be inherited for task specific Interpretation classes\"\n", " def __init__(self,\n", " learn:Learner,\n", " dl:DataLoader, # `DataLoader` to run inference over\n", " losses:TensorBase, # Losses calculated from `dl`\n", " act=None # Activation function for prediction\n", " ): \n", " store_attr()\n", "\n", " def __getitem__(self, idxs):\n", " \"Return inputs, preds, targs, decoded outputs, and losses at `idxs`\"\n", " if isinstance(idxs, Tensor): idxs = idxs.tolist()\n", " if not is_listy(idxs): idxs = [idxs]\n", " items = getattr(self.dl.items, 'iloc', L(self.dl.items))[idxs]\n", " tmp_dl = self.learn.dls.test_dl(items, with_labels=True, process=not isinstance(self.dl, TabDataLoader))\n", " inps,preds,targs,decoded = self.learn.get_preds(dl=tmp_dl, with_input=True, with_loss=False, \n", " with_decoded=True, act=self.act, reorder=False)\n", " return inps, preds, targs, decoded, self.losses[idxs]\n", "\n", " @classmethod\n", " def from_learner(cls,\n", " learn, # Model used to create interpretation\n", " ds_idx:int=1, # Index of `learn.dls` when `dl` is None\n", " dl:DataLoader=None, # `Dataloader` used to make predictions\n", " act=None # Override default or set prediction activation function\n", " ):\n", " \"Construct interpretation object from a learner\"\n", " if dl is None: dl = learn.dls[ds_idx].new(shuffle=False, drop_last=False)\n", " _,_,losses = learn.get_preds(dl=dl, with_input=False, with_loss=True, with_decoded=False,\n", " with_preds=False, with_targs=False, act=act)\n", " return cls(learn, dl, losses, act)\n", "\n", " def top_losses(self,\n", " k:int|None=None, # Return `k` losses, defaults to all\n", " largest:bool=True, # Sort losses by largest or smallest\n", " items:bool=False # Whether to return input items\n", " ):\n", " \"`k` largest(/smallest) losses and indexes, defaulting to all losses.\"\n", " losses, idx = self.losses.topk(ifnone(k, len(self.losses)), largest=largest)\n", " if items: return losses, idx, getattr(self.dl.items, 'iloc', L(self.dl.items))[idx]\n", " else: return losses, idx\n", "\n", " def plot_top_losses(self,\n", " k:int|MutableSequence, # Number of losses to plot\n", " largest:bool=True, # Sort losses by largest or smallest\n", " **kwargs\n", " ):\n", " \"Show `k` largest(/smallest) preds and losses. Implementation based on type dispatch\"\n", " if is_listy(k) or isinstance(k, range):\n", " losses, idx = (o[k] for o in self.top_losses(None, largest))\n", " else: \n", " losses, idx = self.top_losses(k, largest)\n", " inps, preds, targs, decoded, _ = self[idx]\n", " inps, targs, decoded = tuplify(inps), tuplify(targs), tuplify(decoded)\n", " x, y, its = self.dl._pre_show_batch(inps+targs, max_n=len(idx))\n", " x1, y1, outs = self.dl._pre_show_batch(inps+decoded, max_n=len(idx))\n", " if its is not None:\n", " plot_top_losses(x, y, its, outs.itemgot(slice(len(inps), None)), preds, losses, **kwargs)\n", " #TODO: figure out if this is needed\n", " #its None means that a batch knows how to show itself as a whole, so we pass x, x1\n", " #else: show_results(x, x1, its, ctxs=ctxs, max_n=max_n, **kwargs)\n", "\n", " def show_results(self,\n", " idxs:list, # Indices of predictions and targets\n", " **kwargs\n", " ):\n", " \"Show predictions and targets of `idxs`\"\n", " if isinstance(idxs, Tensor): idxs = idxs.tolist()\n", " if not is_listy(idxs): idxs = [idxs]\n", " inps, _, targs, decoded, _ = self[idxs]\n", " b = tuplify(inps)+tuplify(targs)\n", " self.dl.show_results(b, tuplify(decoded), max_n=len(idxs), **kwargs)" ] }, { "cell_type": "code", "execution_count": null, "id": "422f77c1", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

class Interpretation[source]

\n", "\n", "> Interpretation(**`learn`**:[`Learner`](/learner.html#Learner), **`dl`**:[`DataLoader`](/data.load.html#DataLoader), **`losses`**:[`TensorBase`](/torch_core.html#TensorBase), **`act`**=*`None`*)\n", "\n", "Interpretation base class, can be inherited for task specific Interpretation classes\n", "\n", "||Type|Default|Details|\n", "|---|---|---|---|\n", "|**`learn`**|[`Learner`](/learner.html#Learner)||*No Content*|\n", "|**`dl`**|[`DataLoader`](/data.load.html#DataLoader)||[`DataLoader`](/data.load.html#DataLoader) to run inference over|\n", "|**[`losses`](/losses.html)**|[`TensorBase`](/torch_core.html#TensorBase)||Losses calculated from `dl`|\n", "|**`act`**|`NoneType`|`None`|Activation function for prediction|\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Interpretation, title_level=3)" ] }, { "cell_type": "markdown", "id": "a4315355", "metadata": {}, "source": [ "`Interpretation` is a helper base class for exploring predictions from trained models. It can be inherited for task specific interpretation classes, such as `ClassificationInterpretation`. `Interpretation` is memory efficient and should be able to process any sized dataset, provided the hardware could train the same model.\n", "\n", ":::{.callout-note}\n", "\n", "`Interpretation` is memory efficient due to generating inputs, predictions, targets, decoded outputs, and losses for each item on the fly, using batch processing where possible.\n", "\n", ":::" ] }, { "cell_type": "code", "execution_count": null, "id": "1ab238b0", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

Interpretation.from_learner[source]

\n", "\n", "> Interpretation.from_learner(**`learn`**, **`ds_idx`**:`int`=*`1`*, **`dl`**:[`DataLoader`](/data.load.html#DataLoader)=*`None`*, **`act`**=*`None`*)\n", "\n", "Construct interpretation object from a learner\n", "\n", "||Type|Default|Details|\n", "|---|---|---|---|\n", "|**`learn`**|||Model used to create interpretation|\n", "|**`ds_idx`**|`int`|`1`|Index of `learn.dls` when `dl` is None|\n", "|**`dl`**|[`DataLoader`](/data.load.html#DataLoader)|`None`|`Dataloader` used to make predictions|\n", "|**`act`**|`NoneType`|`None`|Override default or set prediction activation function|\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Interpretation.from_learner, title_level=3)" ] }, { "cell_type": "code", "execution_count": null, "id": "83b2d80e", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

Interpretation.top_losses[source]

\n", "\n", "> Interpretation.top_losses(**`k`**:`(, None)`=*`None`*, **`largest`**:`bool`=*`True`*, **`items`**:`bool`=*`False`*)\n", "\n", "`k` largest(/smallest) losses and indexes, defaulting to all losses.\n", "\n", "||Type|Default|Details|\n", "|---|---|---|---|\n", "|**`k`**|`(int, None)`|`None`|Return `k` losses, defaults to all|\n", "|**`largest`**|`bool`|`True`|Sort losses by largest or smallest|\n", "|**`items`**|`bool`|`False`|Whether to return input items|\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Interpretation.top_losses, title_level=3)" ] }, { "cell_type": "markdown", "id": "23ec08ee", "metadata": {}, "source": [ "With the default of `k=None`, `top_losses` will return the entire dataset's losses. `top_losses` can optionally include the input items for each loss, which is usually a file path or Pandas `DataFrame`." ] }, { "cell_type": "code", "execution_count": null, "id": "4f379700", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

Interpretation.plot_top_losses[source]

\n", "\n", "> Interpretation.plot_top_losses(**`k`**:`(, )`, **`largest`**:`bool`=*`True`*, **\\*\\*`kwargs`**)\n", "\n", "Show `k` largest(/smallest) preds and losses. Implementation based on type dispatch\n", "\n", "||Type|Default|Details|\n", "|---|---|---|---|\n", "|**`k`**|`(int, list)`||Number of losses to plot|\n", "|**`largest`**|`bool`|`True`|Sort losses by largest or smallest|\n", "|**`kwargs`**|||*No Content*|\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Interpretation.plot_top_losses, title_level=3)" ] }, { "cell_type": "markdown", "id": "77d38f5b", "metadata": {}, "source": [ "To plot the first 9 top losses:\n", "```python\n", "interp = Interpretation.from_learner(learn)\n", "interp.plot_top_losses(9)\n", "```\n", "Then to plot the 7th through 16th top losses:\n", "```python\n", "interp.plot_top_losses(range(7,16))\n", "```" ] }, { "cell_type": "code", "execution_count": null, "id": "641f42b7", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

Interpretation.show_results[source]

\n", "\n", "> Interpretation.show_results(**`idxs`**:`list`, **\\*\\*`kwargs`**)\n", "\n", "Show predictions and targets of `idxs`\n", "\n", "||Type|Default|Details|\n", "|---|---|---|---|\n", "|**`idxs`**|`list`||Indices of predictions and targets|\n", "|**`kwargs`**|||*No Content*|\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Interpretation.show_results, title_level=3)" ] }, { "cell_type": "markdown", "id": "811d33d9", "metadata": {}, "source": [ "Like `Learner.show_results`, except can pass desired index or indicies for item(s) to show results from." ] }, { "cell_type": "code", "execution_count": null, "id": "98023a9e", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#| hide\n", "interp = Interpretation.from_learner(test_learner)\n", "x, y, out = [], [], []\n", "for batch in test_learner.dls.valid:\n", " x += batch[0]\n", " y += batch[1]\n", " out += test_learner.model(batch[0])\n", "x,y,out = torch.stack(x), torch.stack(y, dim=0), torch.stack(out, dim=0)\n", "inps, preds, targs, decoded, losses = interp[:]\n", "test_eq(inps, to_cpu(x))\n", "test_eq(targs, to_cpu(y))\n", "loss = torch.stack([test_learner.loss_func(p,t) for p,t in zip(out,y)], dim=0)\n", "test_close(losses, to_cpu(loss))" ] }, { "cell_type": "code", "execution_count": null, "id": "2a779c35", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#| hide\n", "# verify stored losses equal calculated losses for idx\n", "top_losses, idx = interp.top_losses(9)\n", "\n", "dl = test_learner.dls[1].new(shuffle=False, drop_last=False)\n", "items = getattr(dl.items, 'iloc', L(dl.items))[idx]\n", "tmp_dl = test_learner.dls.test_dl(items, with_labels=True, process=not isinstance(dl, TabDataLoader))\n", "_, _, _, _, losses = test_learner.get_preds(dl=tmp_dl, with_input=True, with_loss=True, \n", " with_decoded=True, act=None, reorder=False)\n", "\n", "test_close(top_losses, losses, 1e-2)" ] }, { "cell_type": "code", "execution_count": null, "id": "c4a82c57", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#| hide\n", "#dummy test to ensure we can run on the training set\n", "interp = Interpretation.from_learner(test_learner, ds_idx=0)\n", "x, y, out = [], [], []\n", "for batch in test_learner.dls.train.new(drop_last=False, shuffle=False):\n", " x += batch[0]\n", " y += batch[1]\n", " out += test_learner.model(batch[0])\n", "x,y,out = torch.stack(x), torch.stack(y, dim=0), torch.stack(out, dim=0)\n", "inps, preds, targs, decoded, losses = interp[:]\n", "test_eq(inps, to_cpu(x))\n", "test_eq(targs, to_cpu(y))\n", "loss = torch.stack([test_learner.loss_func(p,t) for p,t in zip(out,y)], dim=0)\n", "test_close(losses, to_cpu(loss))" ] }, { "cell_type": "code", "execution_count": null, "id": "e42d8bef", "metadata": {}, "outputs": [], "source": [ "#| export\n", "class ClassificationInterpretation(Interpretation):\n", " \"Interpretation methods for classification models.\"\n", "\n", " def __init__(self, \n", " learn:Learner, \n", " dl:DataLoader, # `DataLoader` to run inference over\n", " losses:TensorBase, # Losses calculated from `dl`\n", " act=None # Activation function for prediction\n", " ):\n", " super().__init__(learn, dl, losses, act)\n", " self.vocab = self.dl.vocab\n", " if is_listy(self.vocab): self.vocab = self.vocab[-1]\n", "\n", " def confusion_matrix(self):\n", " \"Confusion matrix as an `np.ndarray`.\"\n", " x = torch.arange(0, len(self.vocab))\n", " _,targs,decoded = self.learn.get_preds(dl=self.dl, with_decoded=True, with_preds=True, \n", " with_targs=True, act=self.act)\n", " d,t = flatten_check(decoded, targs)\n", " cm = ((d==x[:,None]) & (t==x[:,None,None])).long().sum(2)\n", " return to_np(cm)\n", "\n", " def plot_confusion_matrix(self, \n", " normalize:bool=False, # Whether to normalize occurrences\n", " title:str='Confusion matrix', # Title of plot\n", " cmap:str=\"Blues\", # Colormap from matplotlib\n", " norm_dec:int=2, # Decimal places for normalized occurrences\n", " plot_txt:bool=True, # Display occurrence in matrix\n", " **kwargs\n", " ):\n", " \"Plot the confusion matrix, with `title` and using `cmap`.\"\n", " # This function is mainly copied from the sklearn docs\n", " cm = self.confusion_matrix()\n", " if normalize: cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]\n", " fig = plt.figure(**kwargs)\n", " plt.imshow(cm, interpolation='nearest', cmap=cmap)\n", " plt.title(title)\n", " tick_marks = np.arange(len(self.vocab))\n", " plt.xticks(tick_marks, self.vocab, rotation=90)\n", " plt.yticks(tick_marks, self.vocab, rotation=0)\n", "\n", " if plot_txt:\n", " thresh = cm.max() / 2.\n", " for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n", " coeff = f'{cm[i, j]:.{norm_dec}f}' if normalize else f'{cm[i, j]}'\n", " plt.text(j, i, coeff, horizontalalignment=\"center\", verticalalignment=\"center\", color=\"white\"\n", " if cm[i, j] > thresh else \"black\")\n", "\n", " ax = fig.gca()\n", " ax.set_ylim(len(self.vocab)-.5,-.5)\n", "\n", " plt.tight_layout()\n", " plt.ylabel('Actual')\n", " plt.xlabel('Predicted')\n", " plt.grid(False)\n", "\n", " def most_confused(self, min_val=1):\n", " \"Sorted descending largest non-diagonal entries of confusion matrix (actual, predicted, # occurrences\"\n", " cm = self.confusion_matrix()\n", " np.fill_diagonal(cm, 0)\n", " res = [(self.vocab[i],self.vocab[j],cm[i,j]) for i,j in zip(*np.where(cm>=min_val))]\n", " return sorted(res, key=itemgetter(2), reverse=True)\n", "\n", " def print_classification_report(self):\n", " \"Print scikit-learn classification report\"\n", " _,targs,decoded = self.learn.get_preds(dl=self.dl, with_decoded=True, with_preds=True, \n", " with_targs=True, act=self.act)\n", " d,t = flatten_check(decoded, targs)\n", " names = [str(v) for v in self.vocab]\n", " print(skm.classification_report(t, d, labels=list(self.vocab.o2i.values()), target_names=names))" ] }, { "cell_type": "code", "execution_count": null, "id": "dd3e7a08", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

ClassificationInterpretation.confusion_matrix[source]

\n", "\n", "> ClassificationInterpretation.confusion_matrix()\n", "\n", "Confusion matrix as an `np.ndarray`." ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(ClassificationInterpretation.confusion_matrix, title_level=3)" ] }, { "cell_type": "code", "execution_count": null, "id": "5b52227f", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "---\n", "\n", "[source](https://github.com/fastai/fastai/blob/main/fastai/interpret.py#L122){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### ClassificationInterpretation.plot_confusion_matrix\n", "\n", "> ClassificationInterpretation.plot_confusion_matrix (normalize:bool=False,\n", "> title:str='Confusion\n", "> matrix',\n", "> cmap:str='Blues',\n", "> norm_dec:int=2,\n", "> plot_txt:bool=True,\n", "> **kwargs)\n", "\n", "*Plot the confusion matrix, with `title` and using `cmap`.*\n", "\n", "| | **Type** | **Default** | **Details** |\n", "| -- | -------- | ----------- | ----------- |\n", "| normalize | bool | False | Whether to normalize occurrences |\n", "| title | str | Confusion matrix | Title of plot |\n", "| cmap | str | Blues | Colormap from matplotlib |\n", "| norm_dec | int | 2 | Decimal places for normalized occurrences |\n", "| plot_txt | bool | True | Display occurrence in matrix |\n", "| kwargs | VAR_KEYWORD | | |" ], "text/plain": [ "---\n", "\n", "[source](https://github.com/fastai/fastai/blob/main/fastai/interpret.py#L122){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n", "\n", "### ClassificationInterpretation.plot_confusion_matrix\n", "\n", "> ClassificationInterpretation.plot_confusion_matrix (normalize:bool=False,\n", "> title:str='Confusion\n", "> matrix',\n", "> cmap:str='Blues',\n", "> norm_dec:int=2,\n", "> plot_txt:bool=True,\n", "> **kwargs)\n", "\n", "*Plot the confusion matrix, with `title` and using `cmap`.*\n", "\n", "| | **Type** | **Default** | **Details** |\n", "| -- | -------- | ----------- | ----------- |\n", "| normalize | bool | False | Whether to normalize occurrences |\n", "| title | str | Confusion matrix | Title of plot |\n", "| cmap | str | Blues | Colormap from matplotlib |\n", "| norm_dec | int | 2 | Decimal places for normalized occurrences |\n", "| plot_txt | bool | True | Display occurrence in matrix |\n", "| kwargs | VAR_KEYWORD | | |" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "show_doc(ClassificationInterpretation.plot_confusion_matrix, title_level=3)" ] }, { "cell_type": "code", "execution_count": null, "id": "575936f0", "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

ClassificationInterpretation.most_confused[source]

\n", "\n", "> ClassificationInterpretation.most_confused(**`min_val`**=*`1`*)\n", "\n", "Sorted descending largest non-diagonal entries of confusion matrix (actual, predicted, # occurrences" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(ClassificationInterpretation.most_confused, title_level=3)" ] }, { "cell_type": "code", "execution_count": null, "id": "6bbc6b45", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "\n", "\n" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#| hide\n", "# simple test to make sure ClassificationInterpretation works\n", "interp = ClassificationInterpretation.from_learner(test_learner)\n", "cm = interp.confusion_matrix()" ] }, { "cell_type": "code", "execution_count": null, "id": "197f7895", "metadata": {}, "outputs": [], "source": [ "#| export\n", "class SegmentationInterpretation(Interpretation):\n", " \"Interpretation methods for segmentation models.\"\n", " pass" ] }, { "cell_type": "markdown", "id": "78c32819", "metadata": {}, "source": [ "## Export -" ] }, { "cell_type": "code", "execution_count": null, "id": "48922e92", "metadata": {}, "outputs": [], "source": [ "#| hide\n", "from nbdev import nbdev_export\n", "nbdev_export()" ] }, { "cell_type": "code", "execution_count": null, "id": "9b878b89", "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "jupytext": { "split_at_heading": true }, "kernelspec": { "display_name": "python3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 5 }