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

1961 lines
92 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "1f23f34a",
"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": "275b39bb",
"metadata": {},
"outputs": [],
"source": [
"#| default_exp data.transforms"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d17d629d",
"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",
"\n",
"from sklearn.model_selection import train_test_split\n",
"\n",
"import posixpath"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e42832a3",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"from nbdev.showdoc import *"
]
},
{
"cell_type": "markdown",
"id": "8df0ebe9",
"metadata": {},
"source": [
"# Data transformations\n",
"\n",
"> Functions for getting, splitting, and labeling data, as well as generic transforms"
]
},
{
"cell_type": "markdown",
"id": "e360d100",
"metadata": {},
"source": [
"## Get, split, and label"
]
},
{
"cell_type": "markdown",
"id": "408c9ae8",
"metadata": {},
"source": [
"For most data source creation we need functions to get a list of items, split them in to train/valid sets, and label them. fastai provides functions to make each of these steps easy (especially when combined with `fastai.data.blocks`)."
]
},
{
"cell_type": "markdown",
"id": "3f5484ed",
"metadata": {},
"source": [
"### Get"
]
},
{
"cell_type": "markdown",
"id": "25e6d368",
"metadata": {},
"source": [
"First we'll look at functions that *get* a list of items (generally file names).\n",
"\n",
"We'll use *tiny MNIST* (a subset of MNIST with just two classes, `7`s and `3`s) for our examples/tests throughout this page."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b67b6707",
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"\n",
"<style>\n",
" /* Turns off some styling */\n",
" progress {\n",
" /* gets rid of default border in Firefox and Opera. */\n",
" border: none;\n",
" /* Needs to be in here for Safari polyfill so background images work as expected. */\n",
" background-size: auto;\n",
" }\n",
" progress:not([value]), progress:not([value])::-webkit-progress-bar {\n",
" background: repeating-linear-gradient(45deg, #7e7e7e, #7e7e7e 10px, #5c5c5c 10px, #5c5c5c 20px);\n",
" }\n",
" .progress-bar-interrupted, .progress-bar-interrupted::-webkit-progress-bar {\n",
" background: #F44336;\n",
" }\n",
"</style>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
"\n",
" <div>\n",
" <progress value='344064' class='' max='342207' style='width:300px; height:20px; vertical-align: middle;'></progress>\n",
" 100.54% [344064/342207 00:00&lt;00:00]\n",
" </div>\n",
" "
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"(#2) [Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/3')]"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"path = untar_data(URLs.MNIST_TINY)\n",
"(path/'train').ls()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "889ed2fe",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _get_files(p, fs, extensions=None):\n",
" p = Path(p)\n",
" res = [p/f for f in fs if not f.startswith('.')\n",
" and ((not extensions) or f'.{f.split(\".\")[-1].lower()}' in extensions)]\n",
" return res"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7da88226",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def get_files(path, extensions=None, recurse=True, folders=None, followlinks=True):\n",
" \"Get all the files in `path` with optional `extensions`, optionally with `recurse`, only in `folders`, if specified.\"\n",
" path = Path(path)\n",
" folders=L(folders)\n",
" extensions = setify(extensions)\n",
" extensions = {e.lower() for e in extensions}\n",
" if recurse:\n",
" res = []\n",
" for i,(p,d,f) in enumerate(os.walk(path, followlinks=followlinks)): # returns (dirpath, dirnames, filenames)\n",
" if len(folders) !=0 and i==0: d[:] = [o for o in d if o in folders]\n",
" else: d[:] = [o for o in d if not o.startswith('.')]\n",
" if len(folders) !=0 and i==0 and '.' not in folders: continue\n",
" res += _get_files(p, f, extensions)\n",
" else:\n",
" f = [o.name for o in os.scandir(path) if o.is_file()]\n",
" res = _get_files(path, f, extensions)\n",
" return L(res)"
]
},
{
"cell_type": "markdown",
"id": "e23cf75c",
"metadata": {},
"source": [
"This is the most general way to grab a bunch of file names from disk. If you pass `extensions` (including the `.`) then returned file names are filtered by that list. Only those files directly in `path` are included, unless you pass `recurse`, in which case all child folders are also searched recursively. `folders` is an optional list of directories to limit the search to."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e55b1e77",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(#709) [Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/9243.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/9519.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/7534.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/9082.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/8377.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/994.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/8559.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/8217.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/8571.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/8954.png')...]"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"t3 = get_files(path/'train'/'3', extensions='.png', recurse=False)\n",
"t7 = get_files(path/'train'/'7', extensions='.png', recurse=False)\n",
"t = get_files(path/'train', extensions='.png', recurse=True)\n",
"test_eq(len(t), len(t3)+len(t7))\n",
"test_eq(len(get_files(path/'train'/'3', extensions='.jpg', recurse=False)),0)\n",
"test_eq(len(t), len(get_files(path, extensions='.png', recurse=True, folders='train')))\n",
"t"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "79e038ed",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"test_eq(len(get_files(path/'train'/'3', recurse=False)),346)\n",
"test_eq(len(get_files(path, extensions='.png', recurse=True, folders=['train', 'test'])),729)\n",
"test_eq(len(get_files(path, extensions='.png', recurse=True, folders='train')),709)\n",
"test_eq(len(get_files(path, extensions='.png', recurse=True, folders='training')),0)"
]
},
{
"cell_type": "markdown",
"id": "45dee6f7",
"metadata": {},
"source": [
"It's often useful to be able to create functions with customized behavior. `fastai.data` generally uses functions named as CamelCase verbs ending in `er` to create these functions. `FileGetter` is a simple example of such a function creator."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7facd818",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def FileGetter(suf='', extensions=None, recurse=True, folders=None):\n",
" \"Create `get_files` partial function that searches path suffix `suf`, only in `folders`, if specified, and passes along args\"\n",
" def _inner(o, extensions=extensions, recurse=recurse, folders=folders):\n",
" return get_files(o/suf, extensions, recurse, folders)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "42a41187",
"metadata": {},
"outputs": [],
"source": [
"fpng = FileGetter(extensions='.png', recurse=False)\n",
"test_eq(len(t7), len(fpng(path/'train'/'7')))\n",
"test_eq(len(t), len(fpng(path/'train', recurse=True)))\n",
"fpng_r = FileGetter(extensions='.png', recurse=True)\n",
"test_eq(len(t), len(fpng_r(path/'train')))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2a760615",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"image_extensions = set(k for k,v in mimetypes.types_map.items() if v.startswith('image/'))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "37020562",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def get_image_files(path, recurse=True, folders=None):\n",
" \"Get image files in `path` recursively, only in `folders`, if specified.\"\n",
" return get_files(path, extensions=image_extensions, recurse=recurse, folders=folders)"
]
},
{
"cell_type": "markdown",
"id": "c74d474e",
"metadata": {},
"source": [
"This is simply `get_files` called with a list of standard image extensions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fde1d48a",
"metadata": {},
"outputs": [],
"source": [
"test_eq(len(t), len(get_image_files(path, recurse=True, folders='train')))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74e2ba93",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def ImageGetter(suf='', recurse=True, folders=None):\n",
" \"Create `get_image_files` partial that searches suffix `suf` and passes along `kwargs`, only in `folders`, if specified\"\n",
" def _inner(o, recurse=recurse, folders=folders): return get_image_files(o/suf, recurse, folders)\n",
" return _inner"
]
},
{
"cell_type": "markdown",
"id": "85b88ee0",
"metadata": {},
"source": [
"Same as `FileGetter`, but for image extensions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c2da2bf2",
"metadata": {},
"outputs": [],
"source": [
"test_eq(len(get_files(path/'train', extensions='.png', recurse=True, folders='3')),\n",
" len(ImageGetter( 'train', recurse=True, folders='3')(path)))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a73cf240",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def get_text_files(path, recurse=True, folders=None):\n",
" \"Get text files in `path` recursively, only in `folders`, if specified.\"\n",
" return get_files(path, extensions=['.txt'], recurse=recurse, folders=folders)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5ec82947",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class ItemGetter(ItemTransform):\n",
" \"Creates a proper transform that applies `itemgetter(i)` (even on a tuple)\"\n",
" _retain = False\n",
" def __init__(self, i): self.i = i\n",
" def encodes(self, x): return x[self.i]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "93b027bf",
"metadata": {},
"outputs": [],
"source": [
"test_eq(ItemGetter(1)((1,2,3)), 2)\n",
"test_eq(ItemGetter(1)(L(1,2,3)), 2)\n",
"test_eq(ItemGetter(1)([1,2,3]), 2)\n",
"test_eq(ItemGetter(1)(np.array([1,2,3])), 2)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "de0e1d17",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class AttrGetter(ItemTransform):\n",
" \"Creates a proper transform that applies `attrgetter(nm)` (even on a tuple)\"\n",
" _retain = False\n",
" def __init__(self, nm, default=None): store_attr()\n",
" def encodes(self, x): return getattr(x, self.nm, self.default)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2d84b059",
"metadata": {},
"outputs": [],
"source": [
"test_eq(AttrGetter('shape')(torch.randn([4,5])), [4,5])\n",
"test_eq(AttrGetter('shape', [0])([4,5]), [0])"
]
},
{
"cell_type": "markdown",
"id": "ba85dea2",
"metadata": {},
"source": [
"### Split"
]
},
{
"cell_type": "markdown",
"id": "1214aa3d",
"metadata": {},
"source": [
"The next set of functions are used to *split* data into training and validation sets. The functions return two lists - a list of indices or masks for each of training and validation sets."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "197fe4a6",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def RandomSplitter(valid_pct=0.2, seed=None):\n",
" \"Create function that splits `items` between train/val with `valid_pct` randomly.\"\n",
" def _inner(o):\n",
" if seed is not None: torch.manual_seed(seed)\n",
" rand_idx = L(list(torch.randperm(len(o)).numpy()))\n",
" cut = int(valid_pct * len(o))\n",
" return rand_idx[cut:],rand_idx[:cut]\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4398004c",
"metadata": {},
"outputs": [],
"source": [
"def _test_splitter(f, items=None):\n",
" \"A basic set of condition a splitter must pass\"\n",
" items = ifnone(items, range_of(30))\n",
" trn,val = f(items)\n",
" assert 0<len(trn)<len(items)\n",
" assert all(o not in val for o in trn)\n",
" test_eq(len(trn), len(items)-len(val))\n",
" # test random seed consistency\n",
" test_eq(f(items)[0], trn)\n",
" return trn, val"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "499086ec",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((#24) [10,18,16,23,28,26,20,7,21,22...], (#6) [12,0,6,25,8,15])"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"_test_splitter(RandomSplitter(seed=42))"
]
},
{
"cell_type": "markdown",
"id": "734fdb43",
"metadata": {},
"source": [
"Use scikit-learn train_test_split. This allow to *split* items in a stratified fashion (uniformely according to the labels distribution)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8012804e",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def TrainTestSplitter(test_size=0.2, random_state=None, stratify=None, train_size=None, shuffle=True):\n",
" \"Split `items` into random train and test subsets using sklearn train_test_split utility.\"\n",
" def _inner(o, **kwargs):\n",
" train,valid = train_test_split(range_of(o), test_size=test_size, random_state=random_state,\n",
" stratify=stratify, train_size=train_size, shuffle=shuffle)\n",
" return L(train), L(valid)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b84be2f3",
"metadata": {},
"outputs": [],
"source": [
"src = list(range(30))\n",
"labels = [0] * 20 + [1] * 10\n",
"test_size = 0.2\n",
"\n",
"f = TrainTestSplitter(test_size=test_size, random_state=42, stratify=labels)\n",
"trn,val = _test_splitter(f, items=src)\n",
"\n",
"# test labels distribution consistency\n",
"# there should be test_size % of zeroes and ones respectively in the validation set\n",
"test_eq(len([t for t in val if t < 20]) / 20, test_size)\n",
"test_eq(len([t for t in val if t > 20]) / 10, test_size)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "479a3a7d",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def IndexSplitter(valid_idx):\n",
" \"Split `items` so that `val_idx` are in the validation set and the others in the training set\"\n",
" def _inner(o):\n",
" train_idx = np.setdiff1d(np.array(range_of(o)), np.array(valid_idx))\n",
" return L(train_idx, use_list=True), L(valid_idx, use_list=True)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9014fdc8",
"metadata": {},
"outputs": [],
"source": [
"items = 'a,b,c,d,e,f,g,h,i,j'.split(',') #to make obvious that splits indexes and not items.\n",
"splitter = IndexSplitter([3,7,9])\n",
"\n",
"_test_splitter(splitter, items)\n",
"test_eq(splitter(items),[[0,1,2,4,5,6,8],[3,7,9]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4881d8de",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def EndSplitter(valid_pct=0.2, valid_last=True):\n",
" \"Create function that splits `items` between train/val with `valid_pct` at the end if `valid_last` else at the start. Useful for ordered data.\"\n",
" assert 0<valid_pct<1, \"valid_pct must be in (0,1)\"\n",
" def _inner(o):\n",
" idxs = range_of(o)\n",
" cut = int(valid_pct * len(o))\n",
" return (idxs[:-cut], idxs[-cut:]) if valid_last else (idxs[cut:],idxs[:cut])\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2b50670d",
"metadata": {},
"outputs": [],
"source": [
"items = range_of(10)\n",
"\n",
"splitter_last = EndSplitter(valid_last=True)\n",
"_test_splitter(splitter_last)\n",
"test_eq(splitter_last(items), ([0,1,2,3,4,5,6,7], [8,9]))\n",
"\n",
"splitter_start = EndSplitter(valid_last=False)\n",
"_test_splitter(splitter_start)\n",
"test_eq(splitter_start(items), ([2,3,4,5,6,7,8,9], [0,1]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fa3c0f5c",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def _grandparent_idxs(items, name):\n",
" def _inner(items, name): return mask2idxs(Path(o).parent.parent.name == name for o in items)\n",
" return [i for n in L(name) for i in _inner(items,n)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5dee203d",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def GrandparentSplitter(train_name='train', valid_name='valid'):\n",
" \"Split `items` from the grand parent folder names (`train_name` and `valid_name`).\"\n",
" def _inner(o):\n",
" return _grandparent_idxs(o, train_name),_grandparent_idxs(o, valid_name)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1da055ca",
"metadata": {},
"outputs": [],
"source": [
"fnames = [path/'train/3/9932.png', path/'valid/7/7189.png',\n",
" path/'valid/7/7320.png', path/'train/7/9833.png',\n",
" path/'train/3/7666.png', path/'valid/3/925.png',\n",
" path/'train/7/724.png', path/'valid/3/93055.png']\n",
"splitter = GrandparentSplitter()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1263979c",
"metadata": {},
"outputs": [],
"source": [
"_test_splitter(splitter, items=fnames)\n",
"test_eq(splitter(fnames),[[0,3,4,6],[1,2,5,7]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "676e5d66",
"metadata": {},
"outputs": [],
"source": [
"fnames2 = fnames + [path/'test/3/4256.png', path/'test/7/2345.png', path/'valid/7/6467.png']\n",
"splitter = GrandparentSplitter(train_name=('train', 'valid'), valid_name='test')\n",
"_test_splitter(splitter, items=fnames2)\n",
"test_eq(splitter(fnames2),[[0,3,4,6,1,2,5,7,10],[8,9]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cb6d848f",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def FuncSplitter(func):\n",
" \"Split `items` by result of `func` (`True` for validation, `False` for training set).\"\n",
" def _inner(o):\n",
" val_idx = mask2idxs(func(o_) for o_ in o)\n",
" return IndexSplitter(val_idx)(o)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d5678a65",
"metadata": {},
"outputs": [],
"source": [
"splitter = FuncSplitter(lambda o: Path(o).parent.parent.name == 'valid')\n",
"_test_splitter(splitter, fnames)\n",
"test_eq(splitter(fnames),[[0,3,4,6],[1,2,5,7]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a43bc38a",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def MaskSplitter(mask):\n",
" \"Split `items` depending on the value of `mask`.\"\n",
" def _inner(o): return IndexSplitter(mask2idxs(mask))(o)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "97aa191c",
"metadata": {},
"outputs": [],
"source": [
"items = list(range(6))\n",
"splitter = MaskSplitter([True,False,False,True,False,True])\n",
"_test_splitter(splitter, items)\n",
"test_eq(splitter(items),[[1,2,4],[0,3,5]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "780bf843",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def FileSplitter(fname):\n",
" \"Split `items` by providing file `fname` (contains names of valid items separated by newline).\"\n",
" valid = Path(fname).read_text().split('\\n')\n",
" def _func(x): return x.name in valid\n",
" def _inner(o): return FuncSplitter(_func)(o)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "352f46e8",
"metadata": {},
"outputs": [],
"source": [
"with tempfile.TemporaryDirectory() as d:\n",
" fname = Path(d)/'valid.txt'\n",
" fname.write_text('\\n'.join([Path(fnames[i]).name for i in [1,3,4]]))\n",
" splitter = FileSplitter(fname)\n",
" _test_splitter(splitter, fnames)\n",
" test_eq(splitter(fnames),[[0,2,5,6,7],[1,3,4]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "beddd169",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def ColSplitter(col='is_valid', on=None):\n",
" \"Split `items` (supposed to be a dataframe) by value in `col`\"\n",
" def _inner(o):\n",
" assert isinstance(o, pd.DataFrame), \"ColSplitter only works when your items are a pandas DataFrame\"\n",
" c = o.iloc[:,col] if isinstance(col, int) else o[col]\n",
" if on is None: valid_idx = c.values.astype('bool')\n",
" elif is_listy(on): valid_idx = c.isin(on)\n",
" else: valid_idx = c == on\n",
" return IndexSplitter(mask2idxs(valid_idx))(o)\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fdfa7a05",
"metadata": {},
"outputs": [],
"source": [
"df = pd.DataFrame({'a': [0,1,2,3,4], 'b': [True,False,True,True,False]})\n",
"splits = ColSplitter('b')(df)\n",
"test_eq(splits, [[1,4], [0,2,3]])\n",
"# Works with strings or index\n",
"splits = ColSplitter(1)(df)\n",
"test_eq(splits, [[1,4], [0,2,3]])\n",
"# does not get confused if the type of 'is_valid' is integer, but it meant to be a yes/no\n",
"df = pd.DataFrame({'a': [0,1,2,3,4], 'is_valid': [1,0,1,1,0]})\n",
"splits_by_int = ColSplitter('is_valid')(df)\n",
"test_eq(splits_by_int, [[1,4], [0,2,3]])\n",
"# optionally pass a specific value to split on\n",
"df = pd.DataFrame({'a': [0,1,2,3,4,5], 'b': [1,2,3,1,2,3]})\n",
"splits_on_val = ColSplitter('b', 3)(df)\n",
"test_eq(splits_on_val, [[0,1,3,4], [2,5]])\n",
"# or multiple values\n",
"splits_on_val = ColSplitter('b', [2,3])(df)\n",
"test_eq(splits_on_val, [[0,3], [1,2,4,5]])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "852f4963",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def RandomSubsetSplitter(train_sz, valid_sz, seed=None):\n",
" \"Take randoms subsets of `splits` with `train_sz` and `valid_sz`\"\n",
" assert 0 < train_sz < 1\n",
" assert 0 < valid_sz < 1\n",
" assert train_sz + valid_sz <= 1.\n",
"\n",
" def _inner(o):\n",
" if seed is not None: torch.manual_seed(seed)\n",
" train_len,valid_len = int(len(o)*train_sz),int(len(o)*valid_sz)\n",
" idxs = L(list(torch.randperm(len(o)).numpy()))\n",
" return idxs[:train_len],idxs[train_len:train_len+valid_len]\n",
" return _inner"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2cf0c537",
"metadata": {},
"outputs": [],
"source": [
"items = list(range(100))\n",
"valid_idx = list(np.arange(70,100))\n",
"splitter = RandomSubsetSplitter(0.3, 0.1)\n",
"splits = RandomSubsetSplitter(0.3, 0.1)(items)\n",
"test_eq(len(splits[0]), 30)\n",
"test_eq(len(splits[1]), 10)"
]
},
{
"cell_type": "markdown",
"id": "086c63a1",
"metadata": {},
"source": [
"### Label"
]
},
{
"cell_type": "markdown",
"id": "23b262dd",
"metadata": {},
"source": [
"The final set of functions is used to *label* a single item of data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a6837f91",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def parent_label(o):\n",
" \"Label `item` with the parent folder name.\"\n",
" return Path(o).parent.name"
]
},
{
"cell_type": "markdown",
"id": "40636d9e",
"metadata": {},
"source": [
"Note that `parent_label` doesn't have anything customize, so it doesn't return a function - you can just use it directly."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0d69f958",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['3', '7', '7', '7', '3', '3', '7', '3']"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"test_eq(parent_label(fnames[0]), '3')\n",
"test_eq(parent_label(\"fastai_dev/dev/data/mnist_tiny/train/3/9932.png\"), '3')\n",
"[parent_label(o) for o in fnames]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f64d1ed4",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"#test for MS Windows when os.path.sep is '\\\\' instead of '/'\n",
"test_eq(parent_label(os.path.join(\"fastai_dev\",\"dev\",\"data\",\"mnist_tiny\",\"train\", \"3\", \"9932.png\") ), '3')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f4a4142e",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class RegexLabeller():\n",
" \"Label `item` with regex `pat`.\"\n",
" def __init__(self, pat, match=False):\n",
" self.pat = re.compile(pat)\n",
" self.matcher = self.pat.match if match else self.pat.search\n",
"\n",
" def __call__(self, o):\n",
" o = str(o).replace(os.sep, posixpath.sep)\n",
" res = self.matcher(o)\n",
" assert res,f'Failed to find \"{self.pat}\" in \"{o}\"'\n",
" return res.group(1)"
]
},
{
"cell_type": "markdown",
"id": "f1633a0c",
"metadata": {},
"source": [
"`RegexLabeller` is a very flexible function since it handles any regex search of the stringified item. Pass `match=True` to use `re.match` (i.e. check only start of string), or `re.search` otherwise (default).\n",
"\n",
"For instance, here's an example the replicates the previous `parent_label` results."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7701964d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['3', '7', '7', '7', '3', '3', '7', '3']"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = RegexLabeller(fr'{posixpath.sep}(\\d){posixpath.sep}')\n",
"test_eq(f(fnames[0]), '3')\n",
"[f(o) for o in fnames]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fee7375a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['3', '7', '7', '7', '3', '3', '7', '3']"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"f = RegexLabeller(fr'{posixpath.sep}(\\d){posixpath.sep}')\n",
"a1 = Path(fnames[0]).as_posix()\n",
"test_eq(f(a1), '3')\n",
"[f(o) for o in fnames]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f8d4f642",
"metadata": {},
"outputs": [],
"source": [
"f = RegexLabeller(r'(\\d*)', match=True)\n",
"test_eq(f(fnames[0].name), '9932')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1aa9a787",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class ColReader(DisplayedTransform):\n",
" \"Read `cols` in `row` with potential `pref` and `suff`\"\n",
" def __init__(self, cols, pref='', suff='', label_delim=None):\n",
" store_attr()\n",
" self.pref = str(pref) + os.path.sep if isinstance(pref, Path) else pref\n",
" self.cols = L(cols)\n",
"\n",
" def _do_one(self, r, c):\n",
" o = (r.iloc[c] if hasattr(r, 'iloc') else r[c]) if isinstance(c, int) else getattr(r, c) if c in getattr(r, '_fields', []) else r[c]\n",
" if len(self.pref)==0 and len(self.suff)==0 and self.label_delim is None: return o\n",
" if self.label_delim is None: return f'{self.pref}{o}{self.suff}'\n",
" else: return o.split(self.label_delim) if len(o)>0 else []\n",
"\n",
" def __call__(self, o, **kwargs):\n",
" if len(self.cols) == 1: return self._do_one(o, self.cols[0])\n",
" return L(self._do_one(o, c) for c in self.cols)"
]
},
{
"cell_type": "markdown",
"id": "0edfccf5",
"metadata": {},
"source": [
"`cols` can be a list of column names or a list of indices (or a mix of both). If `label_delim` is passed, the result is split using it."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6fb06de1",
"metadata": {},
"outputs": [],
"source": [
"df = pd.DataFrame({'a': 'a b c d'.split(), 'b': ['1 2', '0', '', '1 2 3']})\n",
"f = ColReader('a', pref='0', suff='1')\n",
"test_eq([f(o) for o in df.itertuples()], '0a1 0b1 0c1 0d1'.split())\n",
"\n",
"f = ColReader('b', label_delim=' ')\n",
"test_eq([f(o) for o in df.itertuples()], [['1', '2'], ['0'], [], ['1', '2', '3']])\n",
"\n",
"df['a1'] = df['a']\n",
"f = ColReader(['a', 'a1'], pref='0', suff='1')\n",
"test_eq([f(o) for o in df.itertuples()], [L('0a1', '0a1'), L('0b1', '0b1'), L('0c1', '0c1'), L('0d1', '0d1')])\n",
"\n",
"df = pd.DataFrame({'a': [L(0,1), L(2,3,4), L(5,6,7)]})\n",
"f = ColReader('a')\n",
"test_eq([f(o) for o in df.itertuples()], [L(0,1), L(2,3,4), L(5,6,7)])\n",
"\n",
"df['name'] = df['a']\n",
"f = ColReader('name')\n",
"test_eq([f(df.iloc[0,:])], [L(0,1)])\n",
"\n",
"df['mask'] = df['a']\n",
"f = ColReader('mask')\n",
"test_eq([f(o) for o in df.itertuples()], [L(0,1), L(2,3,4), L(5,6,7)])\n",
"test_eq([f(df.iloc[0,:])], [L(0,1)])"
]
},
{
"cell_type": "markdown",
"id": "87a542b3",
"metadata": {},
"source": [
"## Categorize -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fd27d2a0",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class CategoryMap(CollBase):\n",
" \"Collection of categories with the reverse mapping in `o2i`\"\n",
" def __init__(self, col, sort=True, add_na=False, strict=False):\n",
" if hasattr(col, 'dtype') and isinstance(col.dtype, CategoricalDtype):\n",
" items = L(col.cat.categories, use_list=True)\n",
" #Remove non-used categories while keeping order\n",
" if strict: items = L(o for o in items if o in col.unique())\n",
" else:\n",
" if not hasattr(col,'unique'): col = L(col, use_list=True)\n",
" # `o==o` is the generalized definition of non-NaN used by Pandas\n",
" items = L(o for o in col.unique() if o==o)\n",
" if sort: items = items.sorted()\n",
" self.items = '#na#' + items if add_na else items\n",
" self.o2i = defaultdict(int, self.items.val2idx()) if add_na else dict(self.items.val2idx())\n",
"\n",
" def map_objs(self,objs):\n",
" \"Map `objs` to IDs\"\n",
" return L(self.o2i[o] for o in objs)\n",
"\n",
" def map_ids(self,ids):\n",
" \"Map `ids` to objects in vocab\"\n",
" return L(self.items[o] for o in ids)\n",
"\n",
" def __eq__(self,b): return all_equal(b,self)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e3ee284c",
"metadata": {},
"outputs": [],
"source": [
"t = CategoryMap([4,2,3,4])\n",
"test_eq(t, [2,3,4])\n",
"test_eq(t.o2i, {2:0,3:1,4:2})\n",
"test_eq(t.map_objs([2,3]), [0,1])\n",
"test_eq(t.map_ids([0,1]), [2,3])\n",
"with expect_fail(): t.o2i['unseen label']"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4383c69b",
"metadata": {},
"outputs": [],
"source": [
"t = CategoryMap([4,2,3,4], add_na=True)\n",
"test_eq(t, ['#na#',2,3,4])\n",
"test_eq(t.o2i, {'#na#':0,2:1,3:2,4:3})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0f36dc7c",
"metadata": {},
"outputs": [],
"source": [
"t = CategoryMap(pd.Series([4,2,3,4]), sort=False)\n",
"test_eq(t, [4,2,3])\n",
"test_eq(t.o2i, {4:0,2:1,3:2})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a2dd76c",
"metadata": {},
"outputs": [],
"source": [
"col = pd.Series(pd.Categorical(['M','H','L','M'], categories=['H','M','L'], ordered=True))\n",
"t = CategoryMap(col)\n",
"test_eq(t, ['H','M','L'])\n",
"test_eq(t.o2i, {'H':0,'M':1,'L':2})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f59e4f7e",
"metadata": {},
"outputs": [],
"source": [
"col = pd.Series(pd.Categorical(['M','H','M'], categories=['H','M','L'], ordered=True))\n",
"t = CategoryMap(col, strict=True)\n",
"test_eq(t, ['H','M'])\n",
"test_eq(t.o2i, {'H':0,'M':1})"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3805c226",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class Categorize(DisplayedTransform):\n",
" \"Reversible transform of category string to `vocab` id\"\n",
" loss_func,order=CrossEntropyLossFlat(),1\n",
" def __init__(self, vocab=None, sort=True, add_na=False):\n",
" if vocab is not None: vocab = CategoryMap(vocab, sort=sort, add_na=add_na)\n",
" store_attr()\n",
"\n",
" def setups(self, dsets):\n",
" if self.vocab is None and dsets is not None: self.vocab = CategoryMap(dsets, sort=self.sort, add_na=self.add_na)\n",
" self.c = len(self.vocab)\n",
"\n",
" def encodes(self, o):\n",
" try:\n",
" return TensorCategory(self.vocab.o2i[o])\n",
" except KeyError as e:\n",
" raise KeyError(f\"Label '{o}' was not included in the training dataset\") from e\n",
" def decodes(self, o): return Category (self.vocab [o])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1c417d2d",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class Category(str, ShowTitle): _show_args = {'label': 'category'}"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9cb3ad29",
"metadata": {},
"outputs": [],
"source": [
"cat = Categorize()\n",
"tds = Datasets(['cat', 'dog', 'cat'], tfms=[cat])\n",
"test_eq(cat.vocab, ['cat', 'dog'])\n",
"test_eq(cat('cat'), 0)\n",
"test_eq(cat.decode(1), 'dog')\n",
"test_stdout(lambda: show_at(tds,2), 'cat')\n",
"with expect_fail(): cat('bird')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "98082733",
"metadata": {},
"outputs": [],
"source": [
"cat = Categorize(add_na=True)\n",
"tds = Datasets(['cat', 'dog', 'cat'], tfms=[cat])\n",
"test_eq(cat.vocab, ['#na#', 'cat', 'dog'])\n",
"test_eq(cat('cat'), 1)\n",
"test_eq(cat.decode(2), 'dog')\n",
"test_stdout(lambda: show_at(tds,2), 'cat')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f586f42d",
"metadata": {},
"outputs": [],
"source": [
"cat = Categorize(vocab=['dog', 'cat'], sort=False, add_na=True)\n",
"tds = Datasets(['cat', 'dog', 'cat'], tfms=[cat])\n",
"test_eq(cat.vocab, ['#na#', 'dog', 'cat'])\n",
"test_eq(cat('dog'), 1)\n",
"test_eq(cat.decode(2), 'cat')\n",
"test_stdout(lambda: show_at(tds,2), 'cat')"
]
},
{
"cell_type": "markdown",
"id": "75d22a20",
"metadata": {},
"source": [
"## Multicategorize -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "06a11d28",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class MultiCategorize(Categorize):\n",
" \"Reversible transform of multi-category strings to `vocab` id\"\n",
" loss_func,order=BCEWithLogitsLossFlat(),1\n",
" def __init__(self, vocab=None, add_na=False): super().__init__(vocab=vocab,add_na=add_na,sort=vocab==None)\n",
"\n",
" def setups(self, dsets):\n",
" if not dsets: return\n",
" if self.vocab is None:\n",
" vals = set()\n",
" for b in dsets: vals = vals.union(set(b))\n",
" self.vocab = CategoryMap(list(vals), add_na=self.add_na)\n",
"\n",
" def encodes(self, o):\n",
" if not all(elem in self.vocab.o2i.keys() for elem in o):\n",
" diff = [elem for elem in o if elem not in self.vocab.o2i.keys()]\n",
" diff_str = \"', '\".join(diff)\n",
" raise KeyError(f\"Labels '{diff_str}' were not included in the training dataset\")\n",
" return TensorMultiCategory([self.vocab.o2i[o_] for o_ in o])\n",
" def decodes(self, o): return MultiCategory ([self.vocab [o_] for o_ in o])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1e368de1",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class MultiCategory(L):\n",
" def show(self, ctx=None, sep=';', color='black', **kwargs):\n",
" return show_title(sep.join(self.map(str)), ctx=ctx, color=color, **kwargs)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "669391e1",
"metadata": {},
"outputs": [],
"source": [
"cat = MultiCategorize()\n",
"tds = Datasets([['b', 'c'], ['a'], ['a', 'c'], []], tfms=[cat])\n",
"test_eq(tds[3][0], TensorMultiCategory([]))\n",
"test_eq(cat.vocab, ['a', 'b', 'c'])\n",
"test_eq(cat(['a', 'c']), tensor([0,2]))\n",
"test_eq(cat([]), tensor([]))\n",
"test_eq(cat.decode([1]), ['b'])\n",
"test_eq(cat.decode([0,2]), ['a', 'c'])\n",
"test_stdout(lambda: show_at(tds,2), 'a;c')\n",
"\n",
"# if vocab supplied, ensure it maintains its order (i.e., it doesn't sort)\n",
"cat = MultiCategorize(vocab=['z', 'y', 'x'])\n",
"test_eq(cat.vocab, ['z','y','x'])\n",
"\n",
"with expect_fail(): cat('bird')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "02cc0b52",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class OneHotEncode(DisplayedTransform):\n",
" \"One-hot encodes targets\"\n",
" order=2\n",
" def __init__(self, c=None): store_attr()\n",
"\n",
" def setups(self, dsets):\n",
" if self.c is None: self.c = len(L(getattr(dsets, 'vocab', None)))\n",
" if not self.c: warn(\"Couldn't infer the number of classes, please pass a value for `c` at init\")\n",
"\n",
" def encodes(self, o): return TensorMultiCategory(one_hot(o, self.c).float())\n",
" def decodes(self, o): return one_hot_decode(o, None)"
]
},
{
"cell_type": "markdown",
"id": "e4de7c4c",
"metadata": {},
"source": [
"Works in conjunction with ` MultiCategorize` or on its own if you have one-hot encoded targets (pass a `vocab` for decoding and `do_encode=False` in this case)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a192f0c0",
"metadata": {},
"outputs": [],
"source": [
"_tfm = OneHotEncode(c=3)\n",
"test_eq(_tfm([0,2]), tensor([1.,0,1]))\n",
"test_eq(_tfm.decode(tensor([0,1,1])), [1,2])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c7ea8d86",
"metadata": {},
"outputs": [],
"source": [
"tds = Datasets([['b', 'c'], ['a'], ['a', 'c'], []], [[MultiCategorize(), OneHotEncode()]])\n",
"test_eq(tds[1], [tensor([1.,0,0])])\n",
"test_eq(tds[3], [tensor([0.,0,0])])\n",
"test_eq(tds.decode([tensor([False, True, True])]), [['b','c']])\n",
"test_eq(type(tds[1][0]), TensorMultiCategory)\n",
"test_stdout(lambda: show_at(tds,2), 'a;c')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30e95ba3",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"#test with passing the vocab\n",
"tds = Datasets([['b', 'c'], ['a'], ['a', 'c'], []], [[MultiCategorize(vocab=['a', 'b', 'c']), OneHotEncode()]])\n",
"test_eq(tds[1], [tensor([1.,0,0])])\n",
"test_eq(tds[3], [tensor([0.,0,0])])\n",
"test_eq(tds.decode([tensor([False, True, True])]), [['b','c']])\n",
"test_eq(type(tds[1][0]), TensorMultiCategory)\n",
"test_stdout(lambda: show_at(tds,2), 'a;c')"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0bab07b7",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class EncodedMultiCategorize(Categorize):\n",
" \"Transform of one-hot encoded multi-category that decodes with `vocab`\"\n",
" loss_func,order=BCEWithLogitsLossFlat(),1\n",
" def __init__(self, vocab):\n",
" super().__init__(vocab, sort=vocab==None)\n",
" self.c = len(vocab)\n",
" def encodes(self, o): return TensorMultiCategory(tensor(o).float())\n",
" def decodes(self, o): return MultiCategory (one_hot_decode(o, self.vocab))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "305d25c6",
"metadata": {},
"outputs": [],
"source": [
"_tfm = EncodedMultiCategorize(vocab=['a', 'b', 'c'])\n",
"test_eq(_tfm([1,0,1]), tensor([1., 0., 1.]))\n",
"test_eq(type(_tfm([1,0,1])), TensorMultiCategory)\n",
"test_eq(_tfm.decode(tensor([False, True, True])), ['b','c'])\n",
"\n",
"_tfm2 = EncodedMultiCategorize(vocab=['c', 'b', 'a'])\n",
"test_eq(_tfm2.vocab, ['c', 'b', 'a'])"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b7893be0",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class RegressionSetup(DisplayedTransform):\n",
" \"Transform that floatifies targets\"\n",
" loss_func=MSELossFlat()\n",
" def __init__(self, c=None): store_attr()\n",
"\n",
" def encodes(self, o): return tensor(o).float()\n",
" def decodes(self, o): return TitledFloat(o) if o.ndim==0 else TitledTuple(o_.item() for o_ in o)\n",
" def setups(self, dsets):\n",
" if self.c is not None: return\n",
" try: self.c = len(dsets[0]) if hasattr(dsets[0], '__len__') else 1\n",
" except: self.c = 0"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c195357f",
"metadata": {},
"outputs": [],
"source": [
"_tfm = RegressionSetup()\n",
"dsets = Datasets([0, 1, 2], RegressionSetup)\n",
"test_eq(dsets.c, 1)\n",
"test_eq_type(dsets[0], (tensor(0.),))\n",
"\n",
"dsets = Datasets([[0, 1, 2], [3,4,5]], RegressionSetup)\n",
"test_eq(dsets.c, 3)\n",
"test_eq_type(dsets[0], (tensor([0.,1.,2.]),))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b724db30",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def get_c(dls):\n",
" if getattr(dls, 'c', False): return dls.c\n",
" if nested_attr(dls, 'train.after_item.c', False): return dls.train.after_item.c\n",
" if nested_attr(dls, 'train.after_batch.c', False): return dls.train.after_batch.c\n",
" vocab = getattr(dls, 'vocab', [])\n",
" if len(vocab) > 0 and is_listy(vocab[-1]): vocab = vocab[-1]\n",
" return len(vocab)"
]
},
{
"cell_type": "markdown",
"id": "26014402",
"metadata": {},
"source": [
"## End-to-end dataset example with MNIST"
]
},
{
"cell_type": "markdown",
"id": "ec41fd09",
"metadata": {},
"source": [
"Let's show how to use those functions to grab the mnist dataset in a `Datasets`. First we grab all the images."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d02bc371",
"metadata": {},
"outputs": [],
"source": [
"path = untar_data(URLs.MNIST_TINY)\n",
"items = get_image_files(path)"
]
},
{
"cell_type": "markdown",
"id": "934cacf1",
"metadata": {},
"source": [
"Then we split between train and validation depending on the folder."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d979a449",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"((#3) [Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/9243.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/9519.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/train/7/7534.png')],\n",
" (#3) [Path('/Users/jhoward/.fastai/data/mnist_tiny/valid/7/9294.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/valid/7/9257.png'),Path('/Users/jhoward/.fastai/data/mnist_tiny/valid/7/8175.png')])"
]
},
"execution_count": null,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"splitter = GrandparentSplitter()\n",
"splits = splitter(items)\n",
"train,valid = (items[i] for i in splits)\n",
"train[:3],valid[:3]"
]
},
{
"cell_type": "markdown",
"id": "6be33cf3",
"metadata": {},
"source": [
"Our inputs are images that we open and convert to tensors, our targets are labeled depending on the parent directory and are categories."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fe451f60",
"metadata": {},
"outputs": [],
"source": [
"from PIL import Image"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c0dfaf25",
"metadata": {},
"outputs": [],
"source": [
"def open_img(fn:Path): return Image.open(fn).copy()\n",
"def img2tensor(im:Image.Image): return TensorImage(array(im)[None])\n",
"\n",
"tfms = [[open_img, img2tensor],\n",
" [parent_label, Categorize()]]\n",
"train_ds = Datasets(train, tfms)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "170ae4c3",
"metadata": {},
"outputs": [],
"source": [
"x,y = train_ds[3]\n",
"xd,yd = decode_at(train_ds,3)\n",
"test_eq(parent_label(train[3]),yd)\n",
"test_eq(array(Image.open(train[3])),xd[0].numpy())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fff0c0d8",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 100x100 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"ax = show_at(train_ds, 3, cmap=\"Greys\", figsize=(1,1))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "aec732da",
"metadata": {},
"outputs": [],
"source": [
"assert ax.title.get_text() in ('3','7')\n",
"test_fig_exists(ax)"
]
},
{
"cell_type": "markdown",
"id": "313f1696",
"metadata": {},
"source": [
"## ToTensor -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3767ffe5",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class ToTensor(Transform):\n",
" \"Convert item to appropriate tensor class\"\n",
" order = 5"
]
},
{
"cell_type": "markdown",
"id": "e60a1aeb",
"metadata": {},
"source": [
"## IntToFloatTensor -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ef22c9f0",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"class IntToFloatTensor(DisplayedTransform):\n",
" \"Transform image to float tensor, optionally dividing by 255 (e.g. for images).\"\n",
" order = 10 #Need to run after PIL transforms on the GPU\n",
" def __init__(self, div=255., div_mask=1): store_attr()\n",
" def encodes(self, o:TensorImage): return o.float().div_(self.div)\n",
" def encodes(self, o:TensorMask ): return (o.long() / self.div_mask).long()\n",
" def decodes(self, o:TensorImage): return ((o.clamp(0., 1.) * self.div).long()) if self.div else o"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "148cfb2d",
"metadata": {},
"outputs": [],
"source": [
"t = (TensorImage(tensor(1)),tensor(2).long(),TensorMask(tensor(3)))\n",
"tfm = IntToFloatTensor()\n",
"ft = tfm(t)\n",
"test_eq(ft, [1./255, 2, 3])\n",
"test_eq(type(ft[0]), TensorImage)\n",
"test_eq(type(ft[2]), TensorMask)\n",
"test_eq(ft[0].type(),'torch.FloatTensor')\n",
"test_eq(ft[1].type(),'torch.LongTensor')\n",
"test_eq(ft[2].type(),'torch.LongTensor')"
]
},
{
"cell_type": "markdown",
"id": "fd8513cb",
"metadata": {},
"source": [
"## Normalization -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e50efa08",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"def broadcast_vec(dim, ndim, *t, cuda=True):\n",
" \"Make a vector broadcastable over `dim` (out of `ndim` total) by prepending and appending unit axes\"\n",
" v = [1]*ndim\n",
" v[dim] = -1\n",
" f = to_device if cuda else noop\n",
" return [f(tensor(o).view(*v)) for o in t]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d365cc25",
"metadata": {},
"outputs": [],
"source": [
"#| export\n",
"@docs\n",
"class Normalize(DisplayedTransform):\n",
" \"Normalize/denorm batch of `TensorImage`\"\n",
" parameters,order = L('mean', 'std'),99\n",
" def __init__(self, mean=None, std=None, axes=(0,2,3)): store_attr()\n",
"\n",
" @classmethod\n",
" def from_stats(cls, mean, std, dim=1, ndim=4, cuda=True): return cls(*broadcast_vec(dim, ndim, mean, std, cuda=cuda))\n",
"\n",
" def setups(self, dl:DataLoader):\n",
" if self.mean is None or self.std is None:\n",
" x,*_ = dl.one_batch()\n",
" self.mean,self.std = x.mean(self.axes, keepdim=True),x.std(self.axes, keepdim=True)+1e-7\n",
" def encodes(self, x:TensorImage): return (x-self.mean) / self.std\n",
" def decodes(self, x:TensorImage):\n",
" f = to_cpu if x.device.type=='cpu' else noop\n",
" return (x*f(self.std) + f(self.mean))\n",
"\n",
" _docs=dict(encodes=\"Normalize batch\", decodes=\"Denormalize batch\", setups=\"Calculate mean/std statistics from DataLoader if not provided\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4235d415",
"metadata": {},
"outputs": [],
"source": [
"mean,std = [0.5]*3,[0.5]*3\n",
"mean,std = broadcast_vec(1, 4, mean, std)\n",
"batch_tfms = [IntToFloatTensor(), Normalize.from_stats(mean,std)]\n",
"tdl = TfmdDL(train_ds, after_batch=batch_tfms, bs=4, device=default_device())"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ede70d80",
"metadata": {},
"outputs": [],
"source": [
"x,y = tdl.one_batch()\n",
"xd,yd = tdl.decode((x,y))\n",
"\n",
"assert x.type().endswith('.FloatTensor')\n",
"test_eq(xd.type(), 'torch.LongTensor')\n",
"test_eq(type(x), TensorImage)\n",
"test_eq(type(y), TensorCategory)\n",
"assert x.mean()<0.0\n",
"assert x.std()>0.3\n",
"assert 0<xd.float().mean()/255.<1\n",
"assert 0<xd.float().std()/255.<0.7"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "24ea87a6",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"nrm = Normalize()\n",
"batch_tfms = [IntToFloatTensor(), nrm]\n",
"tdl = TfmdDL(train_ds, after_batch=batch_tfms, bs=4)\n",
"x,y = tdl.one_batch()\n",
"test_close(x.mean(), 0.0, 1e-4)\n",
"assert x.std()>0.9, x.std()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "197fbe3e",
"metadata": {},
"outputs": [],
"source": [
"#Just for visuals\n",
"from fastai.vision.core import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eeec27cc",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"tdl.show_batch((x,y))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1f77bce2",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png": "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",
"text/plain": [
"<Figure size 72x72 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"#| hide\n",
"x,y = cast(x,Tensor),cast(y,Tensor) #Lose type of tensors (to emulate predictions)\n",
"test_ne(type(x), TensorImage)\n",
"tdl.show_batch((x,y), figsize=(1,1)) #Check that types are put back by dl."
]
},
{
"cell_type": "markdown",
"id": "390f5150",
"metadata": {},
"source": [
"## Export -"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d9487e03",
"metadata": {},
"outputs": [],
"source": [
"#| hide\n",
"from nbdev import nbdev_export\n",
"nbdev_export()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e6a9169d",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}