Files
fastai--fastai/nbs/06_data.block.ipynb
2026-07-13 13:21:43 +08:00

917 lines
35 KiB
Plaintext

{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "28e62357",
"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": "70c2ae80",
"metadata": {},
"outputs": [],
"source": [
"#| default_exp data.block"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9bded3a5",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"from fastai.torch_basics import *\n",
"from fastai.data.core import *\n",
"from fastai.data.load import *\n",
"from fastai.data.external import *\n",
"from fastai.data.transforms import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8cc7ac6",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"from nbdev.showdoc import *"
]
},
{
"cell_type": "markdown",
"id": "229e6992",
"metadata": {},
"source": [
"# Data block\n",
"\n",
"> High level API to quickly get your data in a `DataLoaders`"
]
},
{
"cell_type": "markdown",
"id": "fd54e27a",
"metadata": {},
"source": [
"## TransformBlock -"
]
},
{
"cell_type": "markdown",
"id": "3c34c75e",
"metadata": {},
"source": [
"> 📘 **Note**: Several domain-specific blocks such as `ImageBlock`, `BBoxBlock`, `PointBlock`, and `CategoryBlock` are implemented on top of `TransformBlock`. These blocks are designed to handle common tasks in computer vision, classification, and regression. See the [Vision Blocks](https://docs.fast.ai/data.block.html#Vision-blocks) section for more details.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f275736d",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class TransformBlock():\n",
" \"A basic wrapper that links defaults transforms for the data block API\"\n",
" def __init__(self, \n",
" type_tfms:list=None, # One or more `Transform`s\n",
" item_tfms:list=None, # `ItemTransform`s, applied on an item\n",
" batch_tfms:list=None, # `Transform`s or `RandTransform`s, applied by batch\n",
" dl_type:TfmdDL=None, # Task specific `TfmdDL`, defaults to `TfmdDL`\n",
" dls_kwargs:dict=None, # Additional arguments to be passed to `DataLoaders`\n",
" ):\n",
" self.type_tfms = L(type_tfms)\n",
" self.item_tfms = ToTensor + L(item_tfms)\n",
" self.batch_tfms = L(batch_tfms)\n",
" self.dl_type,self.dls_kwargs = dl_type,({} if dls_kwargs is None else dls_kwargs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "82b37b7b",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def CategoryBlock(\n",
" vocab:MutableSequence|pd.Series=None, # List of unique class names\n",
" sort:bool=True, # Sort the classes alphabetically\n",
" add_na:bool=False, # Add `#na#` to `vocab`\n",
"):\n",
" \"`TransformBlock` for single-label categorical targets\"\n",
" return TransformBlock(type_tfms=Categorize(vocab=vocab, sort=sort, add_na=add_na))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5467becf",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def MultiCategoryBlock(\n",
" encoded:bool=False, # Whether the data comes in one-hot encoded\n",
" vocab:MutableSequence|pd.Series=None, # List of unique class names \n",
" add_na:bool=False, # Add `#na#` to `vocab`\n",
"):\n",
" \"`TransformBlock` for multi-label categorical targets\"\n",
" tfm = EncodedMultiCategorize(vocab=vocab) if encoded else [MultiCategorize(vocab=vocab, add_na=add_na), OneHotEncode]\n",
" return TransformBlock(type_tfms=tfm)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8348b925",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def RegressionBlock(\n",
" n_out:int=None, # Number of output values\n",
"):\n",
" \"`TransformBlock` for float targets\"\n",
" return TransformBlock(type_tfms=RegressionSetup(c=n_out))"
]
},
{
"cell_type": "markdown",
"id": "ee28ab11",
"metadata": {},
"source": [
"## General API"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b0e452a6",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"from inspect import isfunction,ismethod"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "934d2604",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _merge_grouper(o):\n",
" if isinstance(o, LambdaType): return id(o)\n",
" elif isinstance(o, type): return o\n",
" elif (isfunction(o) or ismethod(o)): return o.__qualname__\n",
" return o.__class__"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "90c6377f",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _merge_tfms(*tfms):\n",
" \"Group the `tfms` in a single list, removing duplicates (from the same class) and instantiating\"\n",
" g = groupby(concat(*tfms), _merge_grouper)\n",
" return L(v[-1] for k,v in g.items()).map(instantiate)\n",
"\n",
"def _zip(x): return L(x).zip()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b39fcb8c",
"metadata": {},
"outputs": [],
"source": [
"#For example, so not exported\n",
"from fastai.vision.core import *\n",
"from fastai.vision.data import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b650c00",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"tfms = _merge_tfms([Categorize, MultiCategorize, Categorize(['dog', 'cat'])], Categorize(['a', 'b']))\n",
"#If there are several instantiated versions, the last one is kept.\n",
"test_eq(len(tfms), 2)\n",
"test_eq(tfms[1].__class__, MultiCategorize)\n",
"test_eq(tfms[0].__class__, Categorize)\n",
"test_eq(tfms[0].vocab, ['a', 'b'])\n",
"\n",
"tfms = _merge_tfms([PILImage.create, PILImage.show])\n",
"#Check methods are properly separated\n",
"test_eq(len(tfms), 2)\n",
"tfms = _merge_tfms([show_image, set_trace])\n",
"#Check functions are properly separated\n",
"test_eq(len(tfms), 2)\n",
"\n",
"_f = lambda x: 0\n",
"test_eq(len(_merge_tfms([_f,lambda x: 1])), 2)\n",
"test_eq(len(_merge_tfms([_f,_f])), 1)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2334afdb",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"@docs\n",
"@funcs_kwargs\n",
"class DataBlock():\n",
" \"Generic container to quickly build `Datasets` and `DataLoaders`.\"\n",
" get_x=get_items=splitter=get_y = None\n",
" blocks,dl_type = (TransformBlock,TransformBlock),TfmdDL\n",
" _methods = 'get_items splitter get_y get_x'.split()\n",
" _msg = \"If you wanted to compose several transforms in your getter don't forget to wrap them in a `Pipeline`.\"\n",
" def __init__(self, \n",
" blocks:list=None, # One or more `TransformBlock`s\n",
" dl_type:TfmdDL=None, # Task specific `TfmdDL`, defaults to `block`'s dl_type or`TfmdDL`\n",
" getters:list=None, # Getter functions applied to results of `get_items`\n",
" n_inp:int=None, # Number of inputs\n",
" item_tfms:list=None, # `ItemTransform`s, applied on an item \n",
" batch_tfms:list=None, # `Transform`s or `RandTransform`s, applied by batch\n",
" **kwargs, \n",
" ):\n",
" blocks = L(self.blocks if blocks is None else blocks)\n",
" blocks = L(b() if callable(b) else b for b in blocks)\n",
" self.type_tfms = blocks.attrgot('type_tfms', L())\n",
" self.default_item_tfms = _merge_tfms(*blocks.attrgot('item_tfms', L()))\n",
" self.default_batch_tfms = _merge_tfms(*blocks.attrgot('batch_tfms', L()))\n",
" for b in blocks:\n",
" if getattr(b, 'dl_type', None) is not None: self.dl_type = b.dl_type\n",
" if dl_type is not None: self.dl_type = dl_type\n",
" self.dataloaders = delegates(self.dl_type.__init__)(self.dataloaders)\n",
" self.dls_kwargs = merge(*blocks.attrgot('dls_kwargs', {}))\n",
"\n",
" self.n_inp = ifnone(n_inp, max(1, len(blocks)-1))\n",
" self.getters = ifnone(getters, [noop]*len(self.type_tfms))\n",
" if self.get_x:\n",
" if len(L(self.get_x)) != self.n_inp:\n",
" raise ValueError(f'get_x contains {len(L(self.get_x))} functions, but must contain {self.n_inp} (one for each input)\\n{self._msg}')\n",
" self.getters[:self.n_inp] = L(self.get_x)\n",
" if self.get_y:\n",
" n_targs = len(self.getters) - self.n_inp\n",
" if len(L(self.get_y)) != n_targs:\n",
" raise ValueError(f'get_y contains {len(L(self.get_y))} functions, but must contain {n_targs} (one for each target)\\n{self._msg}')\n",
" self.getters[self.n_inp:] = L(self.get_y)\n",
"\n",
" if kwargs: raise TypeError(f'invalid keyword arguments: {\", \".join(kwargs.keys())}')\n",
" self.new(item_tfms, batch_tfms)\n",
"\n",
" def _combine_type_tfms(self): return L([self.getters, self.type_tfms]).map_zip(\n",
" lambda g,tt: (g.fs if isinstance(g, Pipeline) else L(g)) + tt)\n",
"\n",
" def new(self, \n",
" item_tfms:list=None, # `ItemTransform`s, applied on an item\n",
" batch_tfms:list=None, # `Transform`s or `RandTransform`s, applied by batch \n",
" ):\n",
" self.item_tfms = _merge_tfms(self.default_item_tfms, item_tfms)\n",
" self.batch_tfms = _merge_tfms(self.default_batch_tfms, batch_tfms)\n",
" return self\n",
"\n",
" @classmethod\n",
" def from_columns(cls, \n",
" blocks:list =None, # One or more `TransformBlock`s\n",
" getters:list =None, # Getter functions applied to results of `get_items`\n",
" get_items:Callable=None, # A function to get items\n",
" **kwargs,\n",
" ):\n",
" if getters is None: getters = L(ItemGetter(i) for i in range(2 if blocks is None else len(L(blocks))))\n",
" get_items = _zip if get_items is None else compose(get_items, _zip)\n",
" return cls(blocks=blocks, getters=getters, get_items=get_items, **kwargs)\n",
"\n",
" def datasets(self, \n",
" source, # The data source\n",
" verbose:bool=False, # Show verbose messages\n",
" ) -> Datasets:\n",
" self.source = source ; pv(f\"Collecting items from {source}\", verbose)\n",
" items = (self.get_items or noop)(source) ; pv(f\"Found {len(items)} items\", verbose)\n",
" splits = (self.splitter or RandomSplitter())(items)\n",
" pv(f\"{len(splits)} datasets of sizes {','.join([str(len(s)) for s in splits])}\", verbose)\n",
" return Datasets(items, tfms=self._combine_type_tfms(), splits=splits, dl_type=self.dl_type, n_inp=self.n_inp, verbose=verbose)\n",
"\n",
" def dataloaders(self, \n",
" source, # The data source\n",
" path:str='.', # Data source and default `Learner` path \n",
" verbose:bool=False, # Show verbose messages\n",
" **kwargs\n",
" ) -> DataLoaders:\n",
" dsets = self.datasets(source, verbose=verbose)\n",
" kwargs = {**self.dls_kwargs, **kwargs, 'verbose': verbose}\n",
" return dsets.dataloaders(path=path, after_item=self.item_tfms, after_batch=self.batch_tfms, **kwargs)\n",
"\n",
" _docs = dict(new=\"Create a new `DataBlock` with other `item_tfms` and `batch_tfms`\",\n",
" datasets=\"Create a `Datasets` object from `source`\",\n",
" dataloaders=\"Create a `DataLoaders` object from `source`\")"
]
},
{
"cell_type": "markdown",
"id": "9f3ea4f9",
"metadata": {},
"source": [
"To build a `DataBlock` you need to give the library four things: the types of your input/labels, and at least two functions: `get_items` and `splitter`. You may also need to include `get_x` and `get_y` or a more generic list of `getters` that are applied to the results of `get_items`.\n",
"\n",
"splitter is a callable which, when called with `items`, returns a tuple of iterables representing the indices of the training and validation data.\n",
"\n",
"Once those are provided, you automatically get a `Datasets` or a `DataLoaders`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f96695f0",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L141){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.datasets\n",
"\n",
"> DataBlock.datasets (source, verbose:bool=False)\n",
"\n",
"*Create a `Datasets` object from `source`*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| verbose | bool | False | Show verbose messages |\n",
"| **Returns** | **Datasets** | | |"
],
"text/plain": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L141){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.datasets\n",
"\n",
"> DataBlock.datasets (source, verbose:bool=False)\n",
"\n",
"*Create a `Datasets` object from `source`*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| verbose | bool | False | Show verbose messages |\n",
"| **Returns** | **Datasets** | | |"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"show_doc(DataBlock.datasets)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3ad33bd8",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L151){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.dataloaders\n",
"\n",
"> DataBlock.dataloaders (source, path:str='.', verbose:bool=False,\n",
"> bs:int=64, shuffle:bool=False,\n",
"> num_workers:int=None, do_setup:bool=True,\n",
"> pin_memory=False, timeout=0, batch_size=None,\n",
"> drop_last=False, indexed=None, n=None,\n",
"> device=None, persistent_workers=False,\n",
"> pin_memory_device='', wif=None, before_iter=None,\n",
"> after_item=None, before_batch=None,\n",
"> after_batch=None, after_iter=None,\n",
"> create_batches=None, create_item=None,\n",
"> create_batch=None, retain=None, get_idxs=None,\n",
"> sample=None, shuffle_fn=None, do_batch=None)\n",
"\n",
"*Create a `DataLoaders` object from `source`*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| path | str | . | Data source and default `Learner` path |\n",
"| verbose | bool | False | Show verbose messages |\n",
"| bs | int | 64 | Size of batch |\n",
"| shuffle | bool | False | Whether to shuffle data |\n",
"| num_workers | int | None | Number of CPU cores to use in parallel (default: All available up to 16) |\n",
"| do_setup | bool | True | Whether to run `setup()` for batch transform(s) |\n",
"| pin_memory | bool | False | |\n",
"| timeout | int | 0 | |\n",
"| batch_size | NoneType | None | |\n",
"| drop_last | bool | False | |\n",
"| indexed | NoneType | None | |\n",
"| n | NoneType | None | |\n",
"| device | NoneType | None | |\n",
"| persistent_workers | bool | False | |\n",
"| pin_memory_device | str | | |\n",
"| wif | NoneType | None | |\n",
"| before_iter | NoneType | None | |\n",
"| after_item | NoneType | None | |\n",
"| before_batch | NoneType | None | |\n",
"| after_batch | NoneType | None | |\n",
"| after_iter | NoneType | None | |\n",
"| create_batches | NoneType | None | |\n",
"| create_item | NoneType | None | |\n",
"| create_batch | NoneType | None | |\n",
"| retain | NoneType | None | |\n",
"| get_idxs | NoneType | None | |\n",
"| sample | NoneType | None | |\n",
"| shuffle_fn | NoneType | None | |\n",
"| do_batch | NoneType | None | |\n",
"| **Returns** | **DataLoaders** | | |"
],
"text/plain": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L151){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.dataloaders\n",
"\n",
"> DataBlock.dataloaders (source, path:str='.', verbose:bool=False,\n",
"> bs:int=64, shuffle:bool=False,\n",
"> num_workers:int=None, do_setup:bool=True,\n",
"> pin_memory=False, timeout=0, batch_size=None,\n",
"> drop_last=False, indexed=None, n=None,\n",
"> device=None, persistent_workers=False,\n",
"> pin_memory_device='', wif=None, before_iter=None,\n",
"> after_item=None, before_batch=None,\n",
"> after_batch=None, after_iter=None,\n",
"> create_batches=None, create_item=None,\n",
"> create_batch=None, retain=None, get_idxs=None,\n",
"> sample=None, shuffle_fn=None, do_batch=None)\n",
"\n",
"*Create a `DataLoaders` object from `source`*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| path | str | . | Data source and default `Learner` path |\n",
"| verbose | bool | False | Show verbose messages |\n",
"| bs | int | 64 | Size of batch |\n",
"| shuffle | bool | False | Whether to shuffle data |\n",
"| num_workers | int | None | Number of CPU cores to use in parallel (default: All available up to 16) |\n",
"| do_setup | bool | True | Whether to run `setup()` for batch transform(s) |\n",
"| pin_memory | bool | False | |\n",
"| timeout | int | 0 | |\n",
"| batch_size | NoneType | None | |\n",
"| drop_last | bool | False | |\n",
"| indexed | NoneType | None | |\n",
"| n | NoneType | None | |\n",
"| device | NoneType | None | |\n",
"| persistent_workers | bool | False | |\n",
"| pin_memory_device | str | | |\n",
"| wif | NoneType | None | |\n",
"| before_iter | NoneType | None | |\n",
"| after_item | NoneType | None | |\n",
"| before_batch | NoneType | None | |\n",
"| after_batch | NoneType | None | |\n",
"| after_iter | NoneType | None | |\n",
"| create_batches | NoneType | None | |\n",
"| create_item | NoneType | None | |\n",
"| create_batch | NoneType | None | |\n",
"| retain | NoneType | None | |\n",
"| get_idxs | NoneType | None | |\n",
"| sample | NoneType | None | |\n",
"| shuffle_fn | NoneType | None | |\n",
"| do_batch | NoneType | None | |\n",
"| **Returns** | **DataLoaders** | | |"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"#| echo: false\n",
"dblock = DataBlock()\n",
"show_doc(dblock.dataloaders, name=\"DataBlock.dataloaders\")"
]
},
{
"cell_type": "markdown",
"id": "904d6b9e",
"metadata": {},
"source": [
"You can create a `DataBlock` by passing functions:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4298e91b",
"metadata": {},
"outputs": [],
"source": [
"mnist = DataBlock(blocks = (ImageBlock(cls=PILImageBW),CategoryBlock),\n",
" get_items = get_image_files,\n",
" splitter = GrandparentSplitter(),\n",
" get_y = parent_label)"
]
},
{
"cell_type": "markdown",
"id": "08bcd53d",
"metadata": {},
"source": [
"Each type comes with default transforms that will be applied:\n",
"\n",
"- at the base level to create items in a tuple (usually input,target) from the base elements (like filenames)\n",
"- at the item level of the datasets\n",
"- at the batch level\n",
"\n",
"They are called respectively type transforms, item transforms, batch transforms. In the case of MNIST, the type transforms are the method to create a `PILImageBW` (for the input) and the `Categorize` transform (for the target), the item transform is `ToTensor` and the batch transforms are `Cuda` and `IntToFloatTensor`. You can add any other transforms by passing them in `DataBlock.datasets` or `DataBlock.dataloaders`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9e06e19c",
"metadata": {},
"outputs": [],
"source": [
"test_eq(mnist.type_tfms[0], [PILImageBW.create])\n",
"test_eq(mnist.type_tfms[1].map(type), [Categorize])\n",
"test_eq(mnist.default_item_tfms.map(type), [ToTensor])\n",
"test_eq(mnist.default_batch_tfms.map(type), [IntToFloatTensor])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "abfbe8c3",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAHsAAACLCAYAAABBVeZmAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8/fFQqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAEbklEQVR4nO2dPUscURSGz1ULMSuKWAiBrYIgNloIEkRQQawl9aawsRARRBC0EOwF3c5GsggKQvAHqKDZRrFVtFHsTLRQEvBznRTBJecmrqvO1933fUCYlxl3z/p458zHzozxPE8IBmVRF0DCg7KBoGwgKBsIygaCsoGgbCCgZBtjflk/OWNMOuq6wqIi6gLCxPO8xOO0MeadiHwXkZXoKgoXqJFt8UlEfojIt6gLCQtk2Z9FJOMBHS82QJ81jzEmKSLHIvLB87zjqOsJC9SRnRKRLJJoEWzZX6IuImzgZBtjPorIewHaCn8ETrb82TD76nnez6gLCRvIDTRUEEc2LJQNBGUDQdlAUDYQz5314qa6e5inZnBkA0HZQFA2EJQNBGUDQdlAUDYQlA0EZQNB2UBQNhCUDQRlA0HZQFA2EJQNBGUDQdlAUDYQlA0EZQNB2UBQNhCUDQRlA0HZQDhz07v9/X2VM5mMysvLyypXVOiPNjw8nJ8eGRlR81paWlTu7e19ZZX/x3697u5uX1+/WDiygaBsICgbiOduoBObS3YHBgZUXlhYiKiSl9PU1KTy3t5ekG/HS3YJZUNB2UA407M3NzdV7uvrU/nm5ibMcl5EQ0ODyoeHhypXV1f7+Xbs2YSyoaBsIJzp2TaXl5cqr6+vq3xyclL0a9n7vc3NzSpfXFyoPD09rbL9NzRGt822tjaVt7e3i67tFbBnE8qGgrKBcOZ8tk1NTY3K/f39gb2X3aNt7B5tn79eXFz0vabXwJENBGUDQdlAONuzg+T+/l7lpaWlgsvX1taqnEqlVK6vr/elrrfCkQ0EZQNB2UCwZ8u/x7ZHR0dVPjg4KPj7Kyv6SY89PT3+FOYzHNlAUDYQkKtxe9fK/lpyOp0u+PszMzMqR3U5z0vhyAaCsoGgbCCc/VrSW8hmsyp3dnYWXD6RSKh8enqqclVVlT+F+QO/lkQoGwrKBgJmP/vo6Cg/3dXVVXBZu0dvbW2pHLMeXTQc2UBQNhCUDUTJ9uyrqyuVBwcH89O5XE7NKyvT//N2j7ZvneUqHNlAUDYQlA1Eyfbs8fFxldfW1p5cdmhoSOVS6dE2HNlAUDYQlA1EyZzP3t3dVbm9vV3lh4eH/HRHR4eat7GxobJ9+2rH4PlsQtlQUDYQzjanu7s7lcfGxlT+u0eLiCSTyfz07Oysmud4jy4ajmwgKBsIygbC2WZlP67JvkV1eXm5ynNzc/np1tbWwOqKMxzZQFA2EJQNhDPHxu1bStuPT7q+vlbZvgXlxMREMIXFDx4bJ5QNBWUDEduefXt7q7J9DfXOzo7Kjl1DHSTs2YSyoYjt4dL5+XmV7dW2zerqqspAq+2i4cgGgrKBoGwgYrPrdX5+rnJjY6PK9lPzJicnVZ6amlLZvgwXCO56EcqGgrKBiE3PPjs7U9nu2XV1dSrbT8atrKwMpjD3YM8mlA0FZQMRm55NfIM9m1A2FJQNxHPns59c/xP34MgGgrKBoGwgKBsIygaCsoH4Dd9p/n3ENZhPAAAAAElFTkSuQmCC",
"text/plain": [
"<Figure size 144x144 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"dsets = mnist.datasets(untar_data(URLs.MNIST_TINY))\n",
"test_eq(dsets.vocab, ['3', '7'])\n",
"x,y = dsets.train[0]\n",
"test_eq(x.size,(28,28))\n",
"show_at(dsets.train, 0, cmap='Greys', figsize=(2,2));"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1008d5d8",
"metadata": {},
"outputs": [],
"source": [
"with expect_fail(): DataBlock(wrong_kwarg=42, wrong_kwarg2='foo')"
]
},
{
"cell_type": "markdown",
"id": "ef2029df",
"metadata": {},
"source": [
"We can pass any number of blocks to `DataBlock`, we can then define what are the input and target blocks by changing `n_inp`. For example, defining `n_inp=2` will consider the first two blocks passed as inputs and the others as targets. "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e4e9fe6f",
"metadata": {},
"outputs": [],
"source": [
"mnist = DataBlock((ImageBlock, ImageBlock, CategoryBlock), get_items=get_image_files, splitter=GrandparentSplitter(),\n",
" get_y=parent_label)\n",
"dsets = mnist.datasets(untar_data(URLs.MNIST_TINY))\n",
"test_eq(mnist.n_inp, 2)\n",
"test_eq(len(dsets.train[0]), 3)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2ba917ba",
"metadata": {},
"outputs": [],
"source": [
"with expect_fail(msg='get_y contains 2 functions, but must contain 1 (one for each output)'): DataBlock((ImageBlock, ImageBlock, CategoryBlock), get_items=get_image_files, splitter=GrandparentSplitter(),\n",
" get_y=[parent_label, noop],\n",
" n_inp=2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42ca8668",
"metadata": {},
"outputs": [],
"source": [
"mnist = DataBlock((ImageBlock, ImageBlock, CategoryBlock), get_items=get_image_files, splitter=GrandparentSplitter(),\n",
" n_inp=1,\n",
" get_y=[noop, Pipeline([noop, parent_label])])\n",
"dsets = mnist.datasets(untar_data(URLs.MNIST_TINY))\n",
"test_eq(len(dsets.train[0]), 3)"
]
},
{
"cell_type": "markdown",
"id": "4aa13fbd",
"metadata": {},
"source": [
"## Debugging"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a129305",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _short_repr(x):\n",
" if isinstance(x, tuple): return f'({\", \".join([_short_repr(y) for y in x])})'\n",
" if isinstance(x, list): return f'[{\", \".join([_short_repr(y) for y in x])}]'\n",
" if not isinstance(x, Tensor): return str(x)\n",
" if x.numel() <= 20 and x.ndim <=1: return str(x)\n",
" return f'{x.__class__.__name__} of size {\"x\".join([str(d) for d in x.shape])}'"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "da2f605e",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"test_eq(_short_repr(TensorImage(torch.randn(40,56))), 'TensorImage of size 40x56')\n",
"test_eq(_short_repr(TensorCategory([1,2,3])), 'TensorCategory([1, 2, 3])')\n",
"test_eq(_short_repr((TensorImage(torch.randn(40,56)), TensorImage(torch.randn(32,20)))),\n",
" '(TensorImage of size 40x56, TensorImage of size 32x20)')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "599171f6",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _apply_pipeline(p, x):\n",
" print(f\" {p}\\n starting from\\n {_short_repr(x)}\")\n",
" for f in p.fs:\n",
" name = f.name\n",
" try:\n",
" x = f(x)\n",
" if name != \"noop\": print(f\" applying {name} gives\\n {_short_repr(x)}\")\n",
" except Exception as e:\n",
" print(f\" applying {name} failed.\")\n",
" raise e\n",
" return x"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f24c7b5d",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"from fastai.data.load import _collate_types\n",
"\n",
"def _find_fail_collate(s):\n",
" s = L(*s)\n",
" for x in s[0]:\n",
" if not isinstance(x, _collate_types): return f\"{type(x).__name__} is not collatable\"\n",
" for i in range_of(s[0]):\n",
" try: _ = default_collate(s.itemgot(i))\n",
" except:\n",
" shapes = [getattr(o[i], 'shape', None) for o in s]\n",
" return f\"Could not collate the {i}-th members of your tuples because got the following shapes\\n{','.join([str(s) for s in shapes])}\""
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b9f57e3",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"@patch\n",
"def summary(self:DataBlock,\n",
" source, # The data source \n",
" bs:int=4, # The batch size\n",
" show_batch:bool=False, # Call `show_batch` after the summary\n",
" **kwargs, # Additional keyword arguments to `show_batch`\n",
"):\n",
" \"Steps through the transform pipeline for one batch, and optionally calls `show_batch(**kwargs)` on the transient `Dataloaders`.\"\n",
" print(f\"Setting-up type transforms pipelines\")\n",
" dsets = self.datasets(source, verbose=True)\n",
" print(\"\\nBuilding one sample\")\n",
" for tl in dsets.train.tls:\n",
" _apply_pipeline(tl.tfms, get_first(dsets.train.items))\n",
" print(f\"\\nFinal sample: {dsets.train[0]}\\n\\n\")\n",
"\n",
" dls = self.dataloaders(source, bs=bs, verbose=True)\n",
" print(\"\\nBuilding one batch\")\n",
" if len([f for f in dls.train.after_item.fs if f.name != 'noop'])!=0:\n",
" print(\"Applying item_tfms to the first sample:\")\n",
" s = [_apply_pipeline(dls.train.after_item, dsets.train[0])]\n",
" print(f\"\\nAdding the next {bs-1} samples\")\n",
" s += [dls.train.after_item(dsets.train[i]) for i in range(1, bs)]\n",
" else:\n",
" print(\"No item_tfms to apply\")\n",
" s = [dls.train.after_item(dsets.train[i]) for i in range(bs)]\n",
"\n",
" if len([f for f in dls.train.before_batch.fs if f.name != 'noop'])!=0:\n",
" print(\"\\nApplying before_batch to the list of samples\")\n",
" s = _apply_pipeline(dls.train.before_batch, s)\n",
" else: print(\"\\nNo before_batch transform to apply\")\n",
"\n",
" print(\"\\nCollating items in a batch\")\n",
" try:\n",
" b = dls.train.create_batch(s)\n",
" b = retain_types(b, s[0] if is_listy(s) else s)\n",
" except Exception as e:\n",
" print(\"Error! It's not possible to collate your items in a batch\")\n",
" why = _find_fail_collate(s)\n",
" print(\"Make sure all parts of your samples are tensors of the same size\" if why is None else why)\n",
" raise e\n",
"\n",
" if len([f for f in dls.train.after_batch.fs if f.name != 'noop'])!=0:\n",
" print(\"\\nApplying batch_tfms to the batch built\")\n",
" b = to_device(b, dls.device)\n",
" b = _apply_pipeline(dls.train.after_batch, b)\n",
" else: print(\"\\nNo batch_tfms to apply\")\n",
"\n",
" if show_batch: dls.show_batch(**kwargs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bf0ed396",
"metadata": {},
"outputs": [
{
"data": {
"text/markdown": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L201){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.summary\n",
"\n",
"> DataBlock.summary (source, bs:int=4, show_batch:bool=False, **kwargs)\n",
"\n",
"*Steps through the transform pipeline for one batch, and optionally calls `show_batch(**kwargs)` on the transient `Dataloaders`.*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| bs | int | 4 | The batch size |\n",
"| show_batch | bool | False | Call `show_batch` after the summary |\n",
"| kwargs | VAR_KEYWORD | | |"
],
"text/plain": [
"---\n",
"\n",
"[source](https://github.com/fastai/fastai/blob/main/fastai/data/block.py#L201){target=\"_blank\" style=\"float:right; font-size:smaller\"}\n",
"\n",
"### DataBlock.summary\n",
"\n",
"> DataBlock.summary (source, bs:int=4, show_batch:bool=False, **kwargs)\n",
"\n",
"*Steps through the transform pipeline for one batch, and optionally calls `show_batch(**kwargs)` on the transient `Dataloaders`.*\n",
"\n",
"| | **Type** | **Default** | **Details** |\n",
"| -- | -------- | ----------- | ----------- |\n",
"| source | | | The data source |\n",
"| bs | int | 4 | The batch size |\n",
"| show_batch | bool | False | Call `show_batch` after the summary |\n",
"| kwargs | VAR_KEYWORD | | |"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"show_doc(DataBlock.summary)"
]
},
{
"cell_type": "markdown",
"id": "3ce10b33",
"metadata": {},
"source": [
"Besides stepping through the transformation, `summary()` provides a shortcut `dls.show_batch(...)`, to see the data. E.g.\n",
"\n",
"```\n",
"pets.summary(path/\"images\", bs=8, show_batch=True, unique=True,...)\n",
"```\n",
"\n",
"is a shortcut to:\n",
"```\n",
"pets.summary(path/\"images\", bs=8)\n",
"dls = pets.dataloaders(path/\"images\", bs=8)\n",
"dls.show_batch(unique=True,...) # See different tfms effect on the same image.\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "76fe8190",
"metadata": {},
"source": [
"## Export -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7a7cd4b4",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"from nbdev import nbdev_export\n",
"nbdev_export()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d15d40f2",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"jupytext": {
"split_at_heading": true
},
"kernelspec": {
"display_name": "python3",
"language": "python",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}