{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#default_exp torch_core" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from local.test import *\n", "from local.core.all import *\n", "from local.torch_imports import *\n", "from fastprogress import progress_bar,master_bar" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from local.notebook.showdoc import *\n", "from PIL import Image" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "_all_ = ['progress_bar','master_bar']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "if torch.cuda.is_available():\n", " if torch.cuda.current_device()==0:\n", " torch.cuda.set_device(int(os.environ.get('DEFAULT_GPU') or 0))\n", " torch.backends.cudnn.benchmark = True" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Torch Core\n", "\n", "> Basic pytorch functions used in the fastai library" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Basics" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def __array_eq__(self:Tensor,b):\n", " return torch.equal(self,b) if self.dim() else self==b" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _array2tensor(x):\n", " if x.dtype==np.uint16: x = x.astype(np.float32) \n", " return torch.from_numpy(x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def tensor(x, *rest, **kwargs):\n", " \"Like `torch.as_tensor`, but handle lists too, and can pass multiple vector elements directly.\"\n", " if len(rest): x = (x,)+rest\n", " # There was a Pytorch bug in dataloader using num_workers>0. Haven't confirmed if fixed\n", " # if isinstance(x, (tuple,list)) and len(x)==0: return tensor(0)\n", " res = (x if isinstance(x, Tensor)\n", " else torch.tensor(x, **kwargs) if isinstance(x, (tuple,list))\n", " else _array2tensor(x) if isinstance(x, ndarray)\n", " else as_tensor(x.values, **kwargs) if isinstance(x, (pd.Series, pd.DataFrame))\n", " else as_tensor(x, **kwargs) if hasattr(x, '__array__') or is_iter(x)\n", " else _array2tensor(array(x), **kwargs))\n", " if res.dtype is torch.float64: return res.float()\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(tensor(torch.tensor([1,2,3])), torch.tensor([1,2,3]))\n", "test_eq(tensor(array([1,2,3])), torch.tensor([1,2,3]))\n", "test_eq(tensor(1,2,3), torch.tensor([1,2,3]))\n", "test_eq_type(tensor(1.0), torch.tensor(1.0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def set_seed(s):\n", " \"Set random seed for `random`, `torch`, and `numpy` (where available)\"\n", " try: torch.manual_seed(s)\n", " except NameError: pass\n", " try: np.random.seed(s%(2**32-1))\n", " except NameError: pass\n", " random.seed(s)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "set_seed(2*33)\n", "a1 = np.random.random()\n", "a2 = torch.rand(())\n", "a3 = random.random()\n", "set_seed(2*33)\n", "b1 = np.random.random()\n", "b2 = torch.rand(())\n", "b3 = random.random()\n", "test_eq(a1,b1)\n", "test_eq(a2,b2)\n", "test_eq(a3,b3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def unsqueeze(x, dim=-1, n=1):\n", " \"Same as `torch.unsqueeze` but can add `n` dims\"\n", " for _ in range(n): x = x.unsqueeze(dim)\n", " return x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = tensor([1])\n", "t2 = unsqueeze(t, n=2)\n", "test_eq(t2,t[:,None,None])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def unsqueeze_(x, dim=-1, n=1):\n", " \"Same as `torch.unsqueeze_` but can add `n` dims\"\n", " for _ in range(n): x.unsqueeze_(dim)\n", " return x" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = tensor([1])\n", "unsqueeze_(t, n=2)\n", "test_eq(t, tensor([1]).view(1,1,1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _fa_rebuild_tensor (cls, *args, **kwargs): return cls(torch._utils._rebuild_tensor_v2(*args, **kwargs))\n", "def _fa_rebuild_qtensor(cls, *args, **kwargs): return cls(torch._utils._rebuild_qtensor (*args, **kwargs))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def apply(func, x, *args, **kwargs):\n", " \"Apply `func` recursively to `x`, passing on args\"\n", " if is_listy(x): return type(x)([apply(func, o, *args, **kwargs) for o in x])\n", " if isinstance(x,dict): return {k: apply(func, v, *args, **kwargs) for k,v in x.items()}\n", " res = func(x, *args, **kwargs)\n", " return res if x is None else retain_type(res, x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_detach(b, cpu=True):\n", " \"Recursively detach lists of tensors in `b `; put them on the CPU if `cpu=True`.\"\n", " def _inner(x, cpu=True):\n", " if not isinstance(x,Tensor): return x\n", " x = x.detach()\n", " return x.cpu() if cpu else x\n", " return apply(_inner, b, cpu=cpu)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_half(b):\n", " \"Recursively map lists of tensors in `b ` to FP16.\"\n", " return apply(lambda x: x.half() if torch.is_floating_point(x) else x, b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_float(b):\n", " \"Recursively map lists of int tensors in `b ` to float.\"\n", " return apply(lambda x: x.float() if torch.is_floating_point(x) else x, b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "# None: True if available; True: error if not availabe; False: use CPU\n", "defaults.use_cuda = None" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def default_device(use_cuda=-1):\n", " \"Return or set default device; `use_cuda`: None - CUDA if available; True - error if not availabe; False - CPU\"\n", " if use_cuda != -1: defaults.use_cuda=use_cuda\n", " use = defaults.use_cuda or (torch.cuda.is_available() and defaults.use_cuda is None)\n", " assert torch.cuda.is_available() or not use\n", " return torch.device(torch.cuda.current_device()) if use else torch.device('cpu')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#cuda\n", "_td = torch.device(torch.cuda.current_device())\n", "test_eq(default_device(None), _td)\n", "test_eq(default_device(True), _td)\n", "test_eq(default_device(False), torch.device('cpu'))\n", "default_device(None);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_device(b, device=None):\n", " \"Recursively put `b` on `device`.\"\n", " if device is None: device=default_device()\n", " def _inner(o): return o.to(device, non_blocking=True) if isinstance(o,Tensor) else o\n", " return apply(_inner, b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = to_device((3,(tensor(3),tensor(2))))\n", "t1,(t2,t3) = t\n", "test_eq_type(t,(3,(tensor(3).cuda(),tensor(2).cuda())))\n", "test_eq(t2.type(), \"torch.cuda.LongTensor\")\n", "test_eq(t3.type(), \"torch.cuda.LongTensor\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_cpu(b):\n", " \"Recursively map lists of tensors in `b ` to the cpu.\"\n", " return to_device(b,'cpu')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t3 = to_cpu(t3)\n", "test_eq(t3.type(), \"torch.LongTensor\")\n", "test_eq(t3, 2)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def to_np(x):\n", " \"Convert a tensor to a numpy array.\"\n", " return apply(lambda o: o.data.cpu().numpy(), x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t3 = to_np(t3)\n", "test_eq(type(t3), np.ndarray)\n", "test_eq(t3, 2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Tensor subtypes" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class TensorBase(Tensor):\n", " def __new__(cls, x, **kwargs): \n", " res = torch.Tensor._make_subclass(cls, tensor(x))\n", " res._meta = kwargs\n", " return res\n", "\n", " def __reduce_ex__(self,proto):\n", " torch.utils.hooks.warn_if_has_hooks(self)\n", " args = (type(self), self.storage(), self.storage_offset(), tuple(self.size()), self.stride())\n", " if self.is_quantized: args = args + (self.q_scale(), self.q_zero_point())\n", " f = _fa_rebuild_qtensor if self.is_quantized else _fa_rebuild_tensor\n", " return (f, args + (self.requires_grad, OrderedDict()))\n", "\n", " def gi(self, i):\n", " res = self[i]\n", " return type(self)(res) if isinstance(res,Tensor) else res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _patch_tb():\n", " if getattr(TensorBase,'_patched',False): return\n", " TensorBase._patched = True\n", "\n", " def get_f(fn):\n", " def _f(self, *args, **kwargs):\n", " cls = self.__class__\n", " res = getattr(super(TensorBase, self), fn)(*args, **kwargs)\n", " return cls(res) if isinstance(res,Tensor) else res\n", " return _f\n", "\n", " t = tensor([1])\n", " skips = '__getitem__ __class__ __deepcopy__ __delattr__ __dir__ __doc__ __getattribute__ __hash__ __init__ \\\n", " __init_subclass__ __new__ __reduce__ __reduce_ex__ __module__ __setstate__'.split()\n", "\n", " for fn in dir(t):\n", " if fn in skips: continue\n", " f = getattr(t, fn)\n", " if isinstance(f, (MethodWrapperType, BuiltinFunctionType, BuiltinMethodType, MethodType, FunctionType)):\n", " setattr(TensorBase, fn, get_f(fn))\n", "\n", "_patch_tb()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "class TensorCategory(TensorBase): pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "class TensorMultiCategory(TensorCategory): pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class _T(TensorBase): pass\n", "\n", "t = _T(range(5))\n", "test_eq(t[0], 0)\n", "test_eq_type(t.gi(0), _T(0))\n", "test_eq_type(t.gi(slice(2)), _T([0,1]))\n", "test_eq_type(t+1, _T(range(1,6)))\n", "\n", "test_eq(type(pickle.loads(pickle.dumps(t))), _T)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = tensor([1,2,3])\n", "m = TensorBase([False,True,True])\n", "test_eq(t[m], tensor([2,3]))\n", "\n", "t = tensor([[1,2,3],[1,2,3]])\n", "m = TensorBase([[False,True,True],\n", " [False,True,True]])\n", "test_eq(t[m], tensor([2,3,2,3]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = TensorBase([[1,2,3],[1,2,3]], a=1)\n", "test_eq(t._meta, {'a': 1})\n", "x = retain_type(tensor([4,5,6]), t)\n", "test_eq(x._meta, {'a': 1})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class TensorImageBase(TensorBase):\n", " _show_args = ArrayImageBase._show_args\n", " def show(self, ctx=None, **kwargs):\n", " return show_image(self, ctx=ctx, **{**self._show_args, **kwargs})" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class TensorImage(TensorImageBase): pass" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class TensorImageBW(TensorImage): _show_args = ArrayImageBW._show_args" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class TensorMask(TensorImageBase): _show_args = ArrayMask._show_args" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "im = Image.open(TEST_IMAGE)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "im_t = TensorImage(array(im))\n", "test_eq(type(im_t), TensorImage)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "im_t2 = TensorMask(tensor(1))\n", "test_eq(type(im_t2), TensorMask)\n", "test_eq(im_t2, tensor(1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "ax = im_t.show(figsize=(2,2))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_fig_exists(ax)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## L -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def tensored(self:L):\n", " \"`mapped(tensor)`\"\n", " return self.map(tensor)\n", "@patch\n", "def stack(self:L, dim=0):\n", " \"Same as `torch.stack`\"\n", " return torch.stack(list(self.tensored()), dim=dim)\n", "@patch\n", "def cat (self:L, dim=0):\n", " \"Same as `torch.cat`\"\n", " return torch.cat (list(self.tensored()), dim=dim)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

L.tensored[source]

\n", "\n", "> L.tensored()\n", "\n", "`mapped(tensor)`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(L.tensored)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are shortcuts for `torch.stack` and `torch.cat` if your `L` contains tensors or something convertible. You can manually convert with `tensored`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = L(([1,2],[3,4]))\n", "test_eq(t.tensored(), [tensor(1,2),tensor(3,4)])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

L.stack[source]

\n", "\n", "> L.stack(**`dim`**=*`0`*)\n", "\n", "Same as `torch.stack`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(L.stack)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(t.stack(), tensor([[1,2],[3,4]]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/markdown": [ "

L.cat[source]

\n", "\n", "> L.cat(**`dim`**=*`0`*)\n", "\n", "Same as `torch.cat`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(L.cat)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(t.cat(), tensor([1,2,3,4]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Chunks" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def concat(*ls):\n", " \"Concatenate tensors, arrays, lists, or tuples\"\n", " if not len(ls): return []\n", " it = ls[0]\n", " if isinstance(it,torch.Tensor): res = torch.cat(ls)\n", " elif isinstance(it,ndarray): res = np.concatenate(ls)\n", " else:\n", " res = itertools.chain.from_iterable(map(L,ls))\n", " if isinstance(it,(tuple,list)): res = type(it)(res)\n", " else: res = L(res)\n", " return retain_type(res, it)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "a,b,c = [1],[1,2],[1,1,2]\n", "test_eq(concat(a,b), c)\n", "test_eq_type(concat(tuple (a),tuple (b)), tuple (c))\n", "test_eq_type(concat(array (a),array (b)), array (c))\n", "test_eq_type(concat(tensor(a),tensor(b)), tensor(c))\n", "test_eq_type(concat(TensorBase(a),TensorBase(b)), TensorBase(c))\n", "test_eq_type(concat([1,1],1), [1,1,1])\n", "test_eq_type(concat(1,1,1), L(1,1,1))\n", "test_eq_type(concat(L(1,2),1), L(1,2,1))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "class Chunks:\n", " \"Slice and int indexing into a list of lists\"\n", " def __init__(self, chunks, lens=None):\n", " self.chunks = chunks\n", " self.lens = L(map(len,self.chunks) if lens is None else lens)\n", " self.cumlens = np.cumsum(0+self.lens)\n", " self.totlen = self.cumlens[-1]\n", "\n", " def __getitem__(self,i):\n", " if isinstance(i,slice): return retain_type(self.getslice(i), old=self.chunks[0])\n", " di,idx = self.doc_idx(i)\n", " return retain_type(self.chunks[di][idx], old=self.chunks[0])\n", "\n", " def getslice(self, i):\n", " st_d,st_i = self.doc_idx(ifnone(i.start,0))\n", " en_d,en_i = self.doc_idx(ifnone(i.stop,self.totlen+1))\n", " res = [self.chunks[st_d][st_i:(en_i if st_d==en_d else sys.maxsize)]]\n", " for b in range(st_d+1,en_d): res.append(self.chunks[b])\n", " if st_d!=en_d and en_dclass Module[source]\n", "\n", "> Module() :: [`Module`](/torchcore.html#Module)\n", "\n", "Same as `nn.Module`, but no need for subclasses to call `super().__init__`" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "show_doc(Module, title_level=3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "tensor([-1.0893], grad_fn=)" ] }, "execution_count": null, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class _T(Module):\n", " def __init__(self): self.f = nn.Linear(1,1)\n", " def forward(self,x): return self.f(x)\n", "\n", "t = _T()\n", "t(tensor([1.]))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# export\n", "from torch.nn.parallel import DistributedDataParallel\n", "\n", "def get_model(model):\n", " \"Return the model maybe wrapped inside `model`.\"\n", " return model.module if isinstance(model, (DistributedDataParallel, nn.DataParallel)) else model" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# export\n", "def one_hot(x, c):\n", " \"One-hot encode `x` with `c` classes.\"\n", " res = torch.zeros(c, dtype=torch.uint8)\n", " if isinstance(x, Tensor) and x.numel()>0: res[x] = 1.\n", " else: res[list(L(x, use_list=None))] = 1.\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(one_hot([1,4], 5), tensor(0,1,0,0,1).byte())\n", "test_eq(one_hot(torch.tensor([]), 5), tensor(0,0,0,0,0).byte())\n", "test_eq(one_hot(2, 5), tensor(0,0,1,0,0).byte())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def one_hot_decode(x, vocab=None):\n", " return L(vocab[i] if vocab else i for i,x_ in enumerate(x) if x_==1)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "test_eq(one_hot_decode(tensor(0,1,0,0,1)), [1,4])\n", "test_eq(one_hot_decode(tensor(0,0,0,0,0)), [ ])\n", "test_eq(one_hot_decode(tensor(0,0,1,0,0)), [2 ])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def params(m):\n", " \"Return all parameters of `m`\"\n", " return [p for p in m.parameters()]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def trainable_params(m):\n", " \"Return all trainable parameters of `m`\"\n", " return [p for p in m.parameters() if p.requires_grad]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "m = nn.Linear(4,5)\n", "test_eq(trainable_params(m), [m.weight, m.bias])\n", "m.weight.requires_grad_(False)\n", "test_eq(trainable_params(m), [m.bias])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "bn_types = (nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def bn_bias_params(m, with_bias=True):\n", " \"Return all bias and BatchNorm parameters\"\n", " if isinstance(m, bn_types): return L(m.parameters()) if with_bias else L(m.weight)\n", " res = L(m.children()).map(bn_bias_params, with_bias=with_bias).concat()\n", " #if with_bias and hasattr(m, 'bias'): res.append(m.bias)\n", " return res" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "model = nn.Sequential(nn.Linear(10,20), nn.BatchNorm1d(20), nn.Conv1d(3,4, 3))\n", "test_eq(bn_bias_params(model), [model[1].weight, model[1].bias])\n", "model = nn.ModuleList([nn.Linear(10,20), nn.Sequential(nn.BatchNorm1d(20), nn.Conv1d(3,4, 3))])\n", "test_eq(bn_bias_params(model), [model[1][0].weight, model[1][0].bias])\n", "model = nn.ModuleList([nn.Linear(10,20), nn.Sequential(nn.BatchNorm1d(20), nn.Conv1d(3,4, 3))])\n", "test_eq(bn_bias_params(model, with_bias=False), [model[1][0].weight])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def batch_to_samples(b, max_n=10):\n", " \"'Transposes' a batch to (at most `max_n`) samples\"\n", " if isinstance(b, Tensor): return retain_types(list(b[:max_n]), [b])\n", " else:\n", " res = L(b).map(partial(batch_to_samples,max_n=max_n))\n", " return retain_types(res.zip(), [b])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "t = tensor([1,2,3])\n", "test_eq(batch_to_samples([t,t+1], max_n=2), ([1,2],[2,3]))\n", "test_eq(batch_to_samples(tensor([1,2,3]), 10), [1, 2, 3])\n", "test_eq(batch_to_samples([tensor([1,2,3]), tensor([4,5,6])], 10), [(1, 4), (2, 5), (3, 6)])\n", "test_eq(batch_to_samples([tensor([1,2,3]), tensor([4,5,6])], 2), [(1, 4), (2, 5)])\n", "test_eq(batch_to_samples([tensor([1,2,3]), [tensor([4,5,6]),tensor([7,8,9])]], 10), \n", " [(1, (4, 7)), (2, (5, 8)), (3, (6, 9))])\n", "test_eq(batch_to_samples([tensor([1,2,3]), [tensor([4,5,6]),tensor([7,8,9])]], 2), [(1, (4, 7)), (2, (5, 8))])\n", "\n", "t = Tuple(tensor([1,2,3]),TensorBase([2,3,4]))\n", "test_eq_type(batch_to_samples(t)[0][1], TensorBase(2))\n", "test_eq(batch_to_samples(t).map(type), [Tuple]*3)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@patch\n", "def interp_1d(x:Tensor, xp, fp):\n", " \"Same as `np.interp`\"\n", " slopes = (fp[1:]-fp[:-1])/(xp[1:]-xp[:-1])\n", " incx = fp[:-1] - (slopes*xp[:-1])\n", " locs = (x[:,None]>=xp[None,:]).long().sum(1)-1\n", " locs = locs.clamp(0,len(slopes)-1)\n", " return slopes[locs]*x + incx[locs]" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "brks = tensor(0,1,2,4,8,64).float()\n", "ys = tensor(range_of(brks)).float()\n", "ys /= ys[-1].item()\n", "pts = tensor(0.2,0.5,0.8,3,5,63)\n", "\n", "preds = pts.interp_1d(brks, ys)\n", "test_close(preds.numpy(), np.interp(pts.numpy(), brks.numpy(), ys.numpy()))\n", "\n", "plt.scatter(brks,ys)\n", "plt.scatter(pts,preds)\n", "plt.legend(['breaks','preds']);" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# export\n", "def logit(x):\n", " \"Logit of `x`, clamped to avoid inf.\"\n", " x = x.clamp(1e-7, 1-1e-7)\n", " return -(1/x-1).log()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def num_distrib():\n", " \"Return the number of processes in distributed training (if applicable).\"\n", " return int(os.environ.get('WORLD_SIZE', 0))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def rank_distrib():\n", " \"Return the distributed rank of this process (if applicable).\"\n", " return int(os.environ.get('RANK', 0))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Image helpers" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def make_cross_image(bw=True):\n", " \"Create a tensor containing a cross image, either `bw` (True) or color\"\n", " if bw:\n", " im = torch.zeros(5,5)\n", " im[2,:] = 1.\n", " im[:,2] = 1.\n", " else:\n", " im = torch.zeros(3,5,5)\n", " im[0,2,:] = 1.\n", " im[1,:,2] = 1.\n", " return im" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.imshow(make_cross_image(), cmap=\"Greys\");" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "
" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "plt.imshow(make_cross_image(False).permute(1,2,0));" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def show_image_batch(b, show=show_titled_image, items=9, cols=3, figsize=None, **kwargs):\n", " \"Display batch `b` in a grid of size `items` with `cols` width\"\n", " if items" ] }, "metadata": { "needs_background": "light" }, "output_type": "display_data" } ], "source": [ "show_image_batch(([Image.open(TEST_IMAGE_BW),Image.open(TEST_IMAGE)],['bw','color']), items=2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Model init" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def requires_grad(m):\n", " \"Check if the first parameter of `m` requires grad or not\"\n", " ps = list(m.parameters())\n", " return ps[0].requires_grad if len(ps)>0 else False" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = nn.Linear(4,5)\n", "assert requires_grad(tst)\n", "for p in tst.parameters(): p.requires_grad_(False)\n", "assert not requires_grad(tst)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def init_default(m, func=nn.init.kaiming_normal_):\n", " \"Initialize `m` weights with `func` and set `bias` to 0.\"\n", " if func:\n", " if hasattr(m, 'weight'): func(m.weight)\n", " if hasattr(m, 'bias') and hasattr(m.bias, 'data'): m.bias.data.fill_(0.)\n", " return m" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = nn.Linear(4,5)\n", "tst.weight.data.uniform_(-1,1)\n", "tst.bias.data.uniform_(-1,1)\n", "tst = init_default(tst, func = lambda x: x.data.fill_(1.))\n", "test_eq(tst.weight, torch.ones(5,4))\n", "test_eq(tst.bias, torch.zeros(5))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def cond_init(m, func):\n", " \"Apply `init_default` to `m` unless it's a batchnorm module\"\n", " if (not isinstance(m, bn_types)) and requires_grad(m): init_default(m, func)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = nn.Linear(4,5)\n", "tst.weight.data.uniform_(-1,1)\n", "tst.bias.data.uniform_(-1,1)\n", "cond_init(tst, func = lambda x: x.data.fill_(1.))\n", "test_eq(tst.weight, torch.ones(5,4))\n", "test_eq(tst.bias, torch.zeros(5))\n", "\n", "tst = nn.BatchNorm2d(5)\n", "init = [tst.weight.clone(), tst.bias.clone()]\n", "cond_init(tst, func = lambda x: x.data.fill_(1.))\n", "test_eq(tst.weight, init[0])\n", "test_eq(tst.bias, init[1])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def apply_leaf(m, f):\n", " \"Apply `f` to children of `m`.\"\n", " c = m.children()\n", " if isinstance(m, nn.Module): f(m)\n", " for l in c: apply_leaf(l,f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = nn.Sequential(nn.Linear(4,5), nn.Sequential(nn.Linear(4,5), nn.Linear(4,5)))\n", "apply_leaf(tst, partial(init_default, func=lambda x: x.data.fill_(1.)))\n", "for l in [tst[0], *tst[1]]: test_eq(l.weight, torch.ones(5,4))\n", "for l in [tst[0], *tst[1]]: test_eq(l.bias, torch.zeros(5))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def apply_init(m, func=nn.init.kaiming_normal_):\n", " \"Initialize all non-batchnorm layers of `m` with `func`.\"\n", " apply_leaf(m, partial(cond_init, func=func))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "tst = nn.Sequential(nn.Linear(4,5), nn.Sequential(nn.Linear(4,5), nn.BatchNorm1d(5)))\n", "init = [tst[1][1].weight.clone(), tst[1][1].bias.clone()]\n", "apply_init(tst, func=lambda x: x.data.fill_(1.))\n", "for l in [tst[0], tst[1][0]]: test_eq(l.weight, torch.ones(5,4))\n", "for l in [tst[0], tst[1][0]]: test_eq(l.bias, torch.zeros(5))\n", "test_eq(tst[1][1].weight, init[0])\n", "test_eq(tst[1][1].bias, init[1])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Multiprocessing" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from multiprocessing import Process, Queue" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def set_num_threads(nt):\n", " \"Get numpy (and others) to use `nt` threads\"\n", " try: import mkl; mkl.set_num_threads(nt)\n", " except: pass\n", " torch.set_num_threads(1)\n", " os.environ['IPC_ENABLE']='1'\n", " for o in ['OPENBLAS_NUM_THREADS','NUMEXPR_NUM_THREADS','OMP_NUM_THREADS','MKL_NUM_THREADS']:\n", " os.environ[o] = str(nt)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "@delegates(concurrent.futures.ProcessPoolExecutor)\n", "class ProcessPoolExecutor(concurrent.futures.ProcessPoolExecutor):\n", " def __init__(self, max_workers=None, on_exc=print, **kwargs):\n", " self.not_parallel = max_workers==0\n", " self.on_exc = on_exc\n", " if self.not_parallel: max_workers=1\n", " super().__init__(max_workers, **kwargs)\n", "\n", " def map(self, f, items, *args, **kwargs):\n", " g = partial(f, *args, **kwargs)\n", " if self.not_parallel: return map(g, items)\n", " try: return super().map(g, items)\n", " except Exception as e: self.on_exc(e)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "def parallel(f, items, *args, n_workers=defaults.cpus, total=None, progress=True, **kwargs):\n", " \"Applies `func` in parallel to `items`, using `n_workers`\"\n", " with ProcessPoolExecutor(n_workers) as ex:\n", " r = ex.map(f,items, *args, **kwargs)\n", " if progress:\n", " if total is None: total = len(items)\n", " r = progress_bar(r, total=total, leave=False)\n", " return L(r)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def add_one(x, a=1): \n", " time.sleep(random.random()/100)\n", " return x+a\n", "\n", "inp,exp = range(50),range(1,51)\n", "test_eq(parallel(add_one, inp, n_workers=2), exp)\n", "test_eq(parallel(add_one, inp, n_workers=0), exp)\n", "test_eq(parallel(add_one, inp, n_workers=1, a=2), range(2,52))\n", "test_eq(parallel(add_one, inp, n_workers=0, a=2), range(2,52))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def run_procs(f, f_done, args):\n", " \"Call `f` for each item in `args` in parallel, yielding `f_done`\"\n", " processes = L(args).map(Process, args=arg0, target=f)\n", " for o in processes: o.start()\n", " try: yield from f_done()\n", " except Exception as e: print(e)\n", " finally: processes.map(Self.join())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export \n", "def parallel_gen(cls, items, n_workers=defaults.cpus, as_gen=False, **kwargs):\n", " \"Instantiate `cls` in `n_workers` procs & call each on a subset of `items` in parallel.\"\n", " batches = np.array_split(items, n_workers)\n", " idx = np.cumsum(0 + L(batches).map(len))\n", " queue = Queue()\n", " def f(batch, start_idx):\n", " for i,b in enumerate(cls(**kwargs)(batch)): queue.put((start_idx+i,b))\n", " def done(): return (queue.get() for _ in progress_bar(items, leave=False))\n", " yield from run_procs(f, done, L(batches,idx).zip())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`cls` is any class with `__call__`. It will be passed `args` and `kwargs` when initialized. Note that `n_workers` instances of `cls` are created, one in each process. `items` are then split in `n_workers` batches and one is sent to each `cls`. The function then returns a list of all the results, matching the order of `items` (if not `as_gen`) or a generator of tuples of item indices and results (if `as_gen`)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class SleepyBatchFunc:\n", " def __init__(self): self.a=1\n", " def __call__(self, batch):\n", " for k in batch:\n", " time.sleep(random.random()/4)\n", " yield k+self.a\n", "\n", "x = np.linspace(0,0.99,20)\n", "res = L(parallel_gen(SleepyBatchFunc, x, n_workers=2))\n", "test_eq(res.sorted().itemgot(1), x+1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## autograd jit functions" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def script_use_ctx(f):\n", " \"Decorator: create jit script and pass everything in `ctx.saved_variables to `f`, after `*args`\"\n", " sf = torch.jit.script(f)\n", " def _f(ctx, *args, **kwargs): return sf(*args, *ctx.saved_variables, **kwargs)\n", " return update_wrapper(_f,f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def script_save_ctx(static, *argidx):\n", " \"Decorator: create jit script and save args with indices `argidx` using `ctx.save_for_backward`\"\n", " def _dec(f):\n", " sf = torch.jit.script(f)\n", " def _f(ctx, *args, **kwargs):\n", " if argidx:\n", " save = [args[o] for o in argidx]\n", " ctx.save_for_backward(*save)\n", " if not argidx: args = [ctx]+args\n", " return sf(*args, **kwargs)\n", " if static: _f = staticmethod(_f)\n", " return update_wrapper(_f,f)\n", " return _dec" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def script_fwd(*argidx):\n", " \"Decorator: create static jit script and save args with indices `argidx` using `ctx.save_for_backward`\"\n", " return script_save_ctx(True, *argidx)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def script_bwd(f):\n", " \"Decorator: create static jit script and pass everything in `ctx.saved_variables to `f`, after `*args`\"\n", " return staticmethod(script_use_ctx(f))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def grad_module(cls):\n", " \"Decorator: convert `cls` into an autograd function\"\n", " class _c(nn.Module):\n", " def forward(self, *args, **kwargs): return cls.apply(*args, **kwargs)\n", " return _c" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Export -" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Converted 00_test.ipynb.\n", "Converted 01_core_foundation.ipynb.\n", "Converted 01a_core_utils.ipynb.\n", "Converted 01b_core_dispatch.ipynb.\n", "Converted 01c_core_transform.ipynb.\n", "Converted 02_core_script.ipynb.\n", "Converted 03_torchcore.ipynb.\n", "Converted 03a_layers.ipynb.\n", "Converted 04_data_load.ipynb.\n", "Converted 05_data_core.ipynb.\n", "Converted 06_data_transforms.ipynb.\n", "Converted 07_data_block.ipynb.\n", "Converted 08_vision_core.ipynb.\n", "Converted 09_vision_augment.ipynb.\n", "Converted 09a_vision_data.ipynb.\n", "Converted 10_pets_tutorial.ipynb.\n", "Converted 11_vision_models_xresnet.ipynb.\n", "Converted 12_optimizer.ipynb.\n", "Converted 13_learner.ipynb.\n", "Converted 13a_metrics.ipynb.\n", "Converted 14_callback_schedule.ipynb.\n", "Converted 14a_callback_data.ipynb.\n", "Converted 15_callback_hook.ipynb.\n", "Converted 15a_vision_models_unet.ipynb.\n", "Converted 16_callback_progress.ipynb.\n", "Converted 17_callback_tracker.ipynb.\n", "Converted 18_callback_fp16.ipynb.\n", "Converted 19_callback_mixup.ipynb.\n", "Converted 20_interpret.ipynb.\n", "Converted 20a_distributed.ipynb.\n", "Converted 21_vision_learner.ipynb.\n", "Converted 22_tutorial_imagenette.ipynb.\n", "Converted 23_tutorial_transfer_learning.ipynb.\n", "Converted 30_text_core.ipynb.\n", "Converted 31_text_data.ipynb.\n", "Converted 32_text_models_awdlstm.ipynb.\n", "Converted 33_text_models_core.ipynb.\n", "Converted 34_callback_rnn.ipynb.\n", "Converted 35_tutorial_wikitext.ipynb.\n", "Converted 36_text_models_qrnn.ipynb.\n", "Converted 37_text_learner.ipynb.\n", "Converted 38_tutorial_ulmfit.ipynb.\n", "Converted 40_tabular_core.ipynb.\n", "Converted 41_tabular_model.ipynb.\n", "Converted 42_tabular_rapids.ipynb.\n", "Converted 50_data_block_examples.ipynb.\n", "Converted 60_medical_imaging.ipynb.\n", "Converted 65_medical_text.ipynb.\n", "Converted 70_callback_wandb.ipynb.\n", "Converted 71_callback_tensorboard.ipynb.\n", "Converted 90_notebook_core.ipynb.\n", "Converted 91_notebook_export.ipynb.\n", "Converted 92_notebook_showdoc.ipynb.\n", "Converted 93_notebook_export2html.ipynb.\n", "Converted 94_notebook_test.ipynb.\n", "Converted 95_index.ipynb.\n", "Converted 96_data_external.ipynb.\n", "Converted 97_utils_test.ipynb.\n", "Converted notebook2jekyll.ipynb.\n" ] } ], "source": [ "#hide\n", "from local.notebook.export import notebook2script\n", "notebook2script(all_fs=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 2 }