"""Functions for getting, splitting, and labeling data, as well as generic transforms Docs: https://docs.fast.ai/data.transforms.html.md""" # AUTOGENERATED! DO NOT EDIT! File to edit: ../../nbs/05_data.transforms.ipynb. # %% auto #0 __all__ = ['image_extensions', 'get_files', 'FileGetter', 'get_image_files', 'ImageGetter', 'get_text_files', 'ItemGetter', 'AttrGetter', 'RandomSplitter', 'TrainTestSplitter', 'IndexSplitter', 'EndSplitter', 'GrandparentSplitter', 'FuncSplitter', 'MaskSplitter', 'FileSplitter', 'ColSplitter', 'RandomSubsetSplitter', 'parent_label', 'RegexLabeller', 'ColReader', 'CategoryMap', 'Categorize', 'Category', 'MultiCategorize', 'MultiCategory', 'OneHotEncode', 'EncodedMultiCategorize', 'RegressionSetup', 'get_c', 'ToTensor', 'IntToFloatTensor', 'broadcast_vec', 'Normalize'] # %% ../../nbs/05_data.transforms.ipynb #d17d629d from ..torch_basics import * from .core import * from .load import * from .external import * from sklearn.model_selection import train_test_split import posixpath # %% ../../nbs/05_data.transforms.ipynb #889ed2fe def _get_files(p, fs, extensions=None): p = Path(p) res = [p/f for f in fs if not f.startswith('.') and ((not extensions) or f'.{f.split(".")[-1].lower()}' in extensions)] return res # %% ../../nbs/05_data.transforms.ipynb #7da88226 def get_files(path, extensions=None, recurse=True, folders=None, followlinks=True): "Get all the files in `path` with optional `extensions`, optionally with `recurse`, only in `folders`, if specified." path = Path(path) folders=L(folders) extensions = setify(extensions) extensions = {e.lower() for e in extensions} if recurse: res = [] for i,(p,d,f) in enumerate(os.walk(path, followlinks=followlinks)): # returns (dirpath, dirnames, filenames) if len(folders) !=0 and i==0: d[:] = [o for o in d if o in folders] else: d[:] = [o for o in d if not o.startswith('.')] if len(folders) !=0 and i==0 and '.' not in folders: continue res += _get_files(p, f, extensions) else: f = [o.name for o in os.scandir(path) if o.is_file()] res = _get_files(path, f, extensions) return L(res) # %% ../../nbs/05_data.transforms.ipynb #7facd818 def FileGetter(suf='', extensions=None, recurse=True, folders=None): "Create `get_files` partial function that searches path suffix `suf`, only in `folders`, if specified, and passes along args" def _inner(o, extensions=extensions, recurse=recurse, folders=folders): return get_files(o/suf, extensions, recurse, folders) return _inner # %% ../../nbs/05_data.transforms.ipynb #2a760615 image_extensions = set(k for k,v in mimetypes.types_map.items() if v.startswith('image/')) # %% ../../nbs/05_data.transforms.ipynb #37020562 def get_image_files(path, recurse=True, folders=None): "Get image files in `path` recursively, only in `folders`, if specified." return get_files(path, extensions=image_extensions, recurse=recurse, folders=folders) # %% ../../nbs/05_data.transforms.ipynb #74e2ba93 def ImageGetter(suf='', recurse=True, folders=None): "Create `get_image_files` partial that searches suffix `suf` and passes along `kwargs`, only in `folders`, if specified" def _inner(o, recurse=recurse, folders=folders): return get_image_files(o/suf, recurse, folders) return _inner # %% ../../nbs/05_data.transforms.ipynb #a73cf240 def get_text_files(path, recurse=True, folders=None): "Get text files in `path` recursively, only in `folders`, if specified." return get_files(path, extensions=['.txt'], recurse=recurse, folders=folders) # %% ../../nbs/05_data.transforms.ipynb #5ec82947 class ItemGetter(ItemTransform): "Creates a proper transform that applies `itemgetter(i)` (even on a tuple)" _retain = False def __init__(self, i): self.i = i def encodes(self, x): return x[self.i] # %% ../../nbs/05_data.transforms.ipynb #de0e1d17 class AttrGetter(ItemTransform): "Creates a proper transform that applies `attrgetter(nm)` (even on a tuple)" _retain = False def __init__(self, nm, default=None): store_attr() def encodes(self, x): return getattr(x, self.nm, self.default) # %% ../../nbs/05_data.transforms.ipynb #197fe4a6 def RandomSplitter(valid_pct=0.2, seed=None): "Create function that splits `items` between train/val with `valid_pct` randomly." def _inner(o): if seed is not None: torch.manual_seed(seed) rand_idx = L(list(torch.randperm(len(o)).numpy())) cut = int(valid_pct * len(o)) return rand_idx[cut:],rand_idx[:cut] return _inner # %% ../../nbs/05_data.transforms.ipynb #8012804e def TrainTestSplitter(test_size=0.2, random_state=None, stratify=None, train_size=None, shuffle=True): "Split `items` into random train and test subsets using sklearn train_test_split utility." def _inner(o, **kwargs): train,valid = train_test_split(range_of(o), test_size=test_size, random_state=random_state, stratify=stratify, train_size=train_size, shuffle=shuffle) return L(train), L(valid) return _inner # %% ../../nbs/05_data.transforms.ipynb #479a3a7d def IndexSplitter(valid_idx): "Split `items` so that `val_idx` are in the validation set and the others in the training set" def _inner(o): train_idx = np.setdiff1d(np.array(range_of(o)), np.array(valid_idx)) return L(train_idx, use_list=True), L(valid_idx, use_list=True) return _inner # %% ../../nbs/05_data.transforms.ipynb #4881d8de def EndSplitter(valid_pct=0.2, valid_last=True): "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." assert 00 else [] def __call__(self, o, **kwargs): if len(self.cols) == 1: return self._do_one(o, self.cols[0]) return L(self._do_one(o, c) for c in self.cols) # %% ../../nbs/05_data.transforms.ipynb #fd27d2a0 class CategoryMap(CollBase): "Collection of categories with the reverse mapping in `o2i`" def __init__(self, col, sort=True, add_na=False, strict=False): if hasattr(col, 'dtype') and isinstance(col.dtype, CategoricalDtype): items = L(col.cat.categories, use_list=True) #Remove non-used categories while keeping order if strict: items = L(o for o in items if o in col.unique()) else: if not hasattr(col,'unique'): col = L(col, use_list=True) # `o==o` is the generalized definition of non-NaN used by Pandas items = L(o for o in col.unique() if o==o) if sort: items = items.sorted() self.items = '#na#' + items if add_na else items self.o2i = defaultdict(int, self.items.val2idx()) if add_na else dict(self.items.val2idx()) def map_objs(self,objs): "Map `objs` to IDs" return L(self.o2i[o] for o in objs) def map_ids(self,ids): "Map `ids` to objects in vocab" return L(self.items[o] for o in ids) def __eq__(self,b): return all_equal(b,self) # %% ../../nbs/05_data.transforms.ipynb #3805c226 class Categorize(DisplayedTransform): "Reversible transform of category string to `vocab` id" loss_func,order=CrossEntropyLossFlat(),1 def __init__(self, vocab=None, sort=True, add_na=False): if vocab is not None: vocab = CategoryMap(vocab, sort=sort, add_na=add_na) store_attr() def setups(self, dsets): if self.vocab is None and dsets is not None: self.vocab = CategoryMap(dsets, sort=self.sort, add_na=self.add_na) self.c = len(self.vocab) def encodes(self, o): try: return TensorCategory(self.vocab.o2i[o]) except KeyError as e: raise KeyError(f"Label '{o}' was not included in the training dataset") from e def decodes(self, o): return Category (self.vocab [o]) # %% ../../nbs/05_data.transforms.ipynb #1c417d2d class Category(str, ShowTitle): _show_args = {'label': 'category'} # %% ../../nbs/05_data.transforms.ipynb #06a11d28 class MultiCategorize(Categorize): "Reversible transform of multi-category strings to `vocab` id" loss_func,order=BCEWithLogitsLossFlat(),1 def __init__(self, vocab=None, add_na=False): super().__init__(vocab=vocab,add_na=add_na,sort=vocab==None) def setups(self, dsets): if not dsets: return if self.vocab is None: vals = set() for b in dsets: vals = vals.union(set(b)) self.vocab = CategoryMap(list(vals), add_na=self.add_na) def encodes(self, o): if not all(elem in self.vocab.o2i.keys() for elem in o): diff = [elem for elem in o if elem not in self.vocab.o2i.keys()] diff_str = "', '".join(diff) raise KeyError(f"Labels '{diff_str}' were not included in the training dataset") return TensorMultiCategory([self.vocab.o2i[o_] for o_ in o]) def decodes(self, o): return MultiCategory ([self.vocab [o_] for o_ in o]) # %% ../../nbs/05_data.transforms.ipynb #1e368de1 class MultiCategory(L): def show(self, ctx=None, sep=';', color='black', **kwargs): return show_title(sep.join(self.map(str)), ctx=ctx, color=color, **kwargs) # %% ../../nbs/05_data.transforms.ipynb #02cc0b52 class OneHotEncode(DisplayedTransform): "One-hot encodes targets" order=2 def __init__(self, c=None): store_attr() def setups(self, dsets): if self.c is None: self.c = len(L(getattr(dsets, 'vocab', None))) if not self.c: warn("Couldn't infer the number of classes, please pass a value for `c` at init") def encodes(self, o): return TensorMultiCategory(one_hot(o, self.c).float()) def decodes(self, o): return one_hot_decode(o, None) # %% ../../nbs/05_data.transforms.ipynb #0bab07b7 class EncodedMultiCategorize(Categorize): "Transform of one-hot encoded multi-category that decodes with `vocab`" loss_func,order=BCEWithLogitsLossFlat(),1 def __init__(self, vocab): super().__init__(vocab, sort=vocab==None) self.c = len(vocab) def encodes(self, o): return TensorMultiCategory(tensor(o).float()) def decodes(self, o): return MultiCategory (one_hot_decode(o, self.vocab)) # %% ../../nbs/05_data.transforms.ipynb #b7893be0 class RegressionSetup(DisplayedTransform): "Transform that floatifies targets" loss_func=MSELossFlat() def __init__(self, c=None): store_attr() def encodes(self, o): return tensor(o).float() def decodes(self, o): return TitledFloat(o) if o.ndim==0 else TitledTuple(o_.item() for o_ in o) def setups(self, dsets): if self.c is not None: return try: self.c = len(dsets[0]) if hasattr(dsets[0], '__len__') else 1 except: self.c = 0 # %% ../../nbs/05_data.transforms.ipynb #b724db30 def get_c(dls): if getattr(dls, 'c', False): return dls.c if nested_attr(dls, 'train.after_item.c', False): return dls.train.after_item.c if nested_attr(dls, 'train.after_batch.c', False): return dls.train.after_batch.c vocab = getattr(dls, 'vocab', []) if len(vocab) > 0 and is_listy(vocab[-1]): vocab = vocab[-1] return len(vocab) # %% ../../nbs/05_data.transforms.ipynb #3767ffe5 class ToTensor(Transform): "Convert item to appropriate tensor class" order = 5 # %% ../../nbs/05_data.transforms.ipynb #ef22c9f0 class IntToFloatTensor(DisplayedTransform): "Transform image to float tensor, optionally dividing by 255 (e.g. for images)." order = 10 #Need to run after PIL transforms on the GPU def __init__(self, div=255., div_mask=1): store_attr() def encodes(self, o:TensorImage): return o.float().div_(self.div) def encodes(self, o:TensorMask ): return (o.long() / self.div_mask).long() def decodes(self, o:TensorImage): return ((o.clamp(0., 1.) * self.div).long()) if self.div else o # %% ../../nbs/05_data.transforms.ipynb #e50efa08 def broadcast_vec(dim, ndim, *t, cuda=True): "Make a vector broadcastable over `dim` (out of `ndim` total) by prepending and appending unit axes" v = [1]*ndim v[dim] = -1 f = to_device if cuda else noop return [f(tensor(o).view(*v)) for o in t] # %% ../../nbs/05_data.transforms.ipynb #d365cc25 @docs class Normalize(DisplayedTransform): "Normalize/denorm batch of `TensorImage`" parameters,order = L('mean', 'std'),99 def __init__(self, mean=None, std=None, axes=(0,2,3)): store_attr() @classmethod def from_stats(cls, mean, std, dim=1, ndim=4, cuda=True): return cls(*broadcast_vec(dim, ndim, mean, std, cuda=cuda)) def setups(self, dl:DataLoader): if self.mean is None or self.std is None: x,*_ = dl.one_batch() self.mean,self.std = x.mean(self.axes, keepdim=True),x.std(self.axes, keepdim=True)+1e-7 def encodes(self, x:TensorImage): return (x-self.mean) / self.std def decodes(self, x:TensorImage): f = to_cpu if x.device.type=='cpu' else noop return (x*f(self.std) + f(self.mean)) _docs=dict(encodes="Normalize batch", decodes="Denormalize batch", setups="Calculate mean/std statistics from DataLoader if not provided")