CoolFace
Apppublic

crashedice/signify

sourceHugging Faceupdated 3y agoView on Hugging Face
1likes
siamese.py533 linesDownload Raw Back to signify
1# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_siamese.ipynb.2 3# %% auto 04__all__ = ['def_device', 'compare', 'with_cbs', 'run_cbs', 'to_cpu', 'to_device', 'CancelFitException', 'CancelBatchException',5           'CancelEpochException', 'Callback', 'MetricsCB', 'TrainCB', 'TrainContrastiveCB', 'TrainBceCB', 'DeviceCB',6           'ProgressCB', 'SaveModelCallback', 'LRFinderCB', 'HooksCallback', 'append_stats', 'ActivationStats',7           'LoadModelCallback', 'TwoDLVCallback', 'BatchTransformCB', 'BaseSchedCB', 'BatchSchedCB', 'rand_erase',8           'RandErase', 'SquareReflectPad', 'Learner', 'show_image', 'subplots', 'get_grid', 'show_images',9           'reshape_alternating']10 11# %% ../nbs/00_siamese.ipynb 412import pathlib, os, shutil, sys, cv2, torch, random, glob13 14import pandas as pd15import numpy as np16import matplotlib.pyplot as plt17import math18import statistics19 20from PIL import Image21from tqdm import tqdm22from pathlib import Path23from itertools import zip_longest24from copy import copy25from operator import attrgetter26from functools import partial27from collections.abc import Mapping28 29import torch.nn as nn30import torch.nn.functional as F31import torchvision.models as models32import torch.optim as optim33import torchvision.transforms as transforms34import torchvision35from torch.utils.data import DataLoader, Dataset36from torcheval.metrics import MulticlassAccuracy,Mean, BinaryAccuracy37from torch.nn import init38from fastprogress import progress_bar,master_bar39from torch.optim.lr_scheduler import ExponentialLR40 41# %% ../nbs/00_siamese.ipynb 842def compare(pic1, pic2): 43    return random.random()44 45# %% ../nbs/00_siamese.ipynb 1646class with_cbs:47    def __init__(self, nm): self.nm = nm48    def __call__(self, f):49        def _f(o, *args, **kwargs):50            try:51                o.callback(f'before_{self.nm}')52                f(o, *args, **kwargs)53                o.callback(f'after_{self.nm}')54            except globals()[f'Cancel{self.nm.title()}Exception']: pass55            finally: o.callback(f'cleanup_{self.nm}')56        return _f57 58    59def run_cbs(cbs, method_nm, learn=None):60    for cb in sorted(cbs, key=attrgetter('order')):61        method = getattr(cb, method_nm, None)62        if method is not None: method(learn)63 64# %% ../nbs/00_siamese.ipynb 1765def to_cpu(x):66    if isinstance(x, Mapping): return {k:to_cpu(v) for k,v in x.items()}67    if isinstance(x, list): return [to_cpu(o) for o in x]68    if isinstance(x, tuple): return tuple(to_cpu(list(x)))69    res = x.detach().cpu()70    return res.float() if res.dtype==torch.float16 else res71 72def_device = 'mps' if torch.backends.mps.is_available() else 'cuda' if torch.cuda.is_available() else 'cpu'73 74def to_device(x, device=def_device):75    if isinstance(x, list): return [to_device(o) for o in x]76    if isinstance(x, tuple): return tuple(to_device(list(x)))77    if isinstance(x, torch.Tensor): return x.to(device)78 79# %% ../nbs/00_siamese.ipynb 1880class CancelFitException(Exception): pass81class CancelBatchException(Exception): pass82class CancelEpochException(Exception): pass83 84# %% ../nbs/00_siamese.ipynb 1985class Callback(): order = 086 87class MetricsCB(Callback):88    def __init__(self, wandb, *ms, **metrics):89        for o in ms: metrics[type(o).__name__] = o90        self.metrics = metrics91        self.all_metrics = copy(metrics)92        self.all_metrics['loss'] = self.loss = Mean()93        self.wandb = wandb94 95    def _log(self, d): print(d)96    def before_fit(self, learn): learn.metrics = self97    def before_epoch(self, learn): [o.reset() for o in self.all_metrics.values()]98 99    def after_epoch(self, learn):100        log = {k:f'{v.compute():.3f}' for k,v in self.all_metrics.items()}101        log['epoch'] = learn.epoch102        log['train'] = 'train' if learn.model.training else 'eval'103        self._log(log)104        105        log_wandb = {f'{k}_{log["train"]}':round(float(v.compute()), 3) for k,v in self.all_metrics.items()}106        self.wandb.log(log_wandb)107        108 109    def after_batch(self, learn):110        x,x2, y = to_cpu(learn.batch)111        for m in self.metrics.values():112            m.update(to_cpu(learn.preds), y)113        114        loss = to_cpu(learn.loss)115        self.loss.update(loss, weight=len(x))116        self.wandb.log({"batch loss": loss.item()})117 118class TrainCB(Callback):119    def __init__(self, n_inp=1): self.n_inp = n_inp120    def predict(self, learn): 121        learn.x, learn.y = learn.batch[:self.n_inp], learn.batch[self.n_inp]122        learn.preds = learn.model(*learn.x)123    def get_loss(self, learn): 124        learn.loss = learn.loss_func(learn.preds, *learn.batch[self.n_inp:])125    def backward(self, learn): learn.loss.backward()126    def step(self, learn): learn.opt.step()127    def zero_grad(self, learn): learn.opt.zero_grad()128    129class TrainContrastiveCB(Callback):130    def __init__(self, n_inp=1): 131        self.n_inp = n_inp132        self.loss_func133    def predict(self, learn): 134        learn.preds = learn.model(*learn.batch[:self.n_inp])135    def get_loss(self, learn): 136        pred1, pred2 = learn.preds137        label = learn.batch[self.n_inp]138        learn.loss = learn.loss_func(pred1, pred2, label)139    def backward(self, learn): learn.loss.backward()140    def step(self, learn): learn.opt.step()141    def zero_grad(self, learn): learn.opt.zero_grad()142 143class TrainBceCB(Callback):144    def __init__(self, n_inp=1): 145        self.n_inp = n_inp146        self.loss_func = torch.nn.BCELoss()147    def predict(self, learn): 148        learn.x, learn.y = learn.batch[:self.n_inp], learn.batch[self.n_inp]149        learn.preds = learn.model(*learn.x)150    def get_loss(self, learn): 151        label = learn.batch[self.n_inp]152        learn.loss = self.loss_func(learn.preds, label)153    def backward(self, learn): learn.loss.backward()154    def step(self, learn): learn.opt.step()155    def zero_grad(self, learn): learn.opt.zero_grad()156    157class DeviceCB(Callback):158    order = 1159    def __init__(self, device): fc.store_attr()160    def before_fit(self, learn):161        if hasattr(learn.model, 'to'): learn.model.to(self.device)162    def before_batch(self, learn): 163        learn.batch = to_device(learn.batch, device=self.device)164    165class ProgressCB(Callback):166    order = MetricsCB.order+1167    def __init__(self, plot=False): self.plot = plot168    def before_fit(self, learn):169        learn.epochs = self.mbar = master_bar(learn.epochs)170        self.first = True171        if hasattr(learn, 'metrics'): learn.metrics._log = self._log172        self.losses = []173        self.val_losses = []174 175 176    def _log(self, d):177        if self.first:178            self.mbar.write(list(d), table=True)179            self.first = False180        self.mbar.write(list(d.values()), table=True)181 182    def before_epoch(self, learn): learn.dl = progress_bar(learn.dl, leave=False, parent=self.mbar)183    def after_batch(self, learn):184        learn.dl.comment = f'{learn.loss:.3f}'185        if self.plot and hasattr(learn, 'metrics') and learn.training:186            self.losses.append(learn.loss.item())187            if self.val_losses: self.mbar.update_graph([[fc.L.range(self.losses), self.losses],[fc.L.range(learn.epoch).map(lambda x: (x+1)*len(learn.dlt)), self.val_losses]])188    189    def after_epoch(self, learn): 190        if not learn.training:191            if self.plot and hasattr(learn, 'metrics'): 192                self.val_losses.append(learn.metrics.all_metrics['loss'].compute())193                self.mbar.update_graph([[fc.L.range(self.losses), self.losses],[fc.L.range(learn.epoch+1).map(lambda x: (x+1)*len(learn.dlt)), self.val_losses]])194 195                196class SaveModelCallback(Callback):197    "A `TrackerCallback` that saves the model's best during training and loads it at the end."198    order = ProgressCB.order + 1199    def __init__(self):200        201        try:202            old_loss = torch.load("model.pth")["loss"]203        except:204            old_loss = 1000205        206        self.valid_losses = [old_loss]207        self.valid_losses_batch = []208        209    def after_batch(self, learn):210        if not learn.training:211            self.valid_losses_batch.append(learn.loss.item())212 213    def after_epoch(self,learn):214        215        if not learn.training:216            217            current_valid_loss = statistics.mean(self.valid_losses_batch)218            prev_best = min(self.valid_losses)219            if current_valid_loss < prev_best:220                print(f"saving model in epoch {learn.epoch} with loss {current_valid_loss} (prev: {prev_best})")221                torch.save({222                    'epoch': learn.epoch,223                    'model_state_dict': learn.model.state_dict(),224                    'optimizer_state_dict': learn.opt.state_dict(),225                    'loss':current_valid_loss,226                    }, "model.pth")227            self.valid_losses.append(current_valid_loss)228            self.valid_losses_batch = []229 230                231class LRFinderCB(Callback):232    order = 1233    def __init__(self, gamma=1.3, max_mult=3): fc.store_attr()234    235    def before_fit(self, learn):236        self.sched = ExponentialLR(learn.opt, self.gamma)237        self.lrs,self.losses = [],[]238        self.min = math.inf239 240    def after_batch(self, learn):241        if not learn.training: raise CancelEpochException()242        self.lrs.append(learn.opt.param_groups[0]['lr'])243        loss = to_cpu(learn.loss)244        self.losses.append(loss)245        if loss < self.min: self.min = loss246        if math.isnan(loss) or (loss > self.min*self.max_mult):247            raise CancelFitException()248        self.sched.step()249 250    def cleanup_fit(self, learn):251        plt.plot(self.lrs, self.losses)252        plt.xscale('log')253        254#| export255class HooksCallback(Callback):256    def __init__(self, hookfunc, mod_filter=fc.noop, on_train=True, on_valid=False, mods=None):257        fc.store_attr()258        super().__init__()259    260    def before_fit(self, learn):261        if self.mods: mods=self.mods262        else: mods = fc.filter_ex(learn.model.modules(), self.mod_filter)263        self.hooks = Hooks(mods, partial(self._hookfunc, learn))264 265    def _hookfunc(self, learn, *args, **kwargs):266        if (self.on_train and learn.training) or (self.on_valid and not learn.training): self.hookfunc(*args, **kwargs)267 268    def after_fit(self, learn): self.hooks.remove()269    def __iter__(self): return iter(self.hooks)270    def __len__(self): return len(self.hooks)271 272#| export273def append_stats(hook, mod, inp, outp):274    if not hasattr(hook,'stats'): hook.stats = ([],[],[])275    acts = to_cpu(outp)276    hook.stats[0].append(acts.mean())277    hook.stats[1].append(acts.std())278    hook.stats[2].append(acts.abs().histc(40,0,10))279     280#|export281class ActivationStats(HooksCallback):282    def __init__(self, mod_filter=fc.noop): super().__init__(append_stats, mod_filter)283 284    def color_dim(self, figsize=(11,5)):285        fig,axes = get_grid(len(self), figsize=figsize)286        for ax,h in zip(axes.flat, self):287            show_image(get_hist(h), ax, origin='lower')288 289    def dead_chart(self, figsize=(11,5)):290        fig,axes = get_grid(len(self), figsize=figsize)291        for ax,h in zip(axes.flatten(), self):292            ax.plot(get_min(h))293            ax.set_ylim(0,1)294 295    def plot_stats(self, figsize=(10,4)):296        fig,axs = plt.subplots(1,2, figsize=figsize)297        for h in self:298            for i in 0,1: axs[i].plot(h.stats[i])299        axs[0].set_title('Means')300        axs[1].set_title('Stdevs')301        plt.legend(fc.L.range(self))302 303    304 305class LoadModelCallback(Callback):306    order = 0307    def __init__(self, path):308        self.path = path309        310    def before_fit(self, learn):311        learn.model.load_state_dict(torch.load(self.path)["model_state_dict"])312 313class TwoDLVCallback(Callback):314    order = 0315    def __init__(self, dlv):316        self.dlv = dlv317        318    def after_epoch(self, learn):319        print("2nd valid")320        storedlearner = deepcopy(learn) #.copy()321        storedlearner.dlv = self.dlv322        torch.no_grad()(storedlearner._one_epoch)()323 324        325#| export326class BatchTransformCB(Callback):327    def __init__(self, tfm, on_train=True, on_val=True): fc.store_attr()328 329    def before_batch(self, learn):330        if (self.on_train and learn.training) or (self.on_val and not learn.training):331            learn.batch = self.tfm(learn.batch)332     333 334# %% ../nbs/00_siamese.ipynb 21335class BaseSchedCB(Callback):336    def __init__(self, sched): self.sched = sched337    def before_fit(self, learn): self.schedo = self.sched(learn.opt)338    def _step(self, learn):339        if learn.training: self.schedo.step()340#|export341class BatchSchedCB(BaseSchedCB):342    def after_batch(self, learn): self._step(learn)343 344# %% ../nbs/00_siamese.ipynb 23345def _rand_erase1(x, pct, xm, xs, mn, mx):346    szx = int(pct*x.shape[-2])347    szy = int(pct*x.shape[-1])348    stx = int(random.random()*(1-pct)*x.shape[-2])349    sty = int(random.random()*(1-pct)*x.shape[-1])350    init.normal_(x[:,:,stx:stx+szx,sty:sty+szy], mean=xm, std=xs)351    x.clamp_(mn, mx)352 353#|export354def rand_erase(x, pct=0.2, max_num = 4):355    xm,xs,mn,mx = x.mean(),x.std(),x.min(),x.max()356    num = random.randint(0, max_num)357    for i in range(num): _rand_erase1(x, pct, xm, xs, mn, mx)358    return x359    360class RandErase(nn.Module):361    def __init__(self, pct=0.2, max_num=4):362        super().__init__()363        self.pct,self.max_num = pct,max_num364    def forward(self, x): return rand_erase(x, self.pct, self.max_num)365class SquareReflectPad:366    def __call__(self, image):367        image = image.squeeze()368        s = image.size()369        max_wh = np.min([s[-1], s[-2] * 2.9])370        hp = np.max(int((max_wh - s[-1]) / 2), 0)371        vp = np.max(int((max_wh - s[-2]) / 2), 0)372        padding = (hp, vp, hp, vp)373        new_img = torchvision.transforms.functional.pad(image, padding, padding_mode='reflect')374        new_img = new_img.unsqueeze(1).expand(new_img.shape[0],3, new_img.shape[1]).permute(1,0,2)375        return new_img376 377 378# %% ../nbs/00_siamese.ipynb 25379def _flops(x, h, w):380    if x.dim()<3: return x.numel()381    if x.dim()==4: return x.numel()*h*w382 383class Learner():384    def __init__(self, model, dlt, dlv, lr=0.1, cbs=None, opt_func=optim.SGD):385        cbs = fc.L(cbs)386        fc.store_attr()387 388    @with_cbs('batch')389    def _one_batch(self):390        self.predict()391        self.callback('after_predict')392        self.get_loss()393        self.callback('after_loss')394        if self.training:395            self.backward()396            self.callback('after_backward')397            self.step()398            self.callback('after_step')399            self.zero_grad()400 401    @with_cbs('epoch')402    def _one_epoch(self):403        for self.iter ,self.batch in enumerate(self.dl): 404            self._one_batch()405            if self.iter > 100:406                break407 408    def one_epoch(self, training):409        self.model.train(training)410        self.dl = self.dlt if training else self.dlv411        self._one_epoch()412 413    @with_cbs('fit')414    def _fit(self, train, valid):415        for self.epoch in self.epochs:416            if train: self.one_epoch(True)417            if valid: torch.no_grad()(self.one_epoch)(False)418 419    def fit(self, n_epochs=1, train=True, valid=True, cbs=None, lr=None):420        cbs = fc.L(cbs)421        # `add_cb` and `rm_cb` were added in lesson 18422        for cb in cbs: self.cbs.append(cb)423        try:424            self.n_epochs = n_epochs425            self.epochs = range(n_epochs)426            if lr is None: lr = self.lr427            if self.opt_func: self.opt = self.opt_func(self.model.parameters(), lr)428            self._fit(train, True)429        finally:430            for cb in cbs: self.cbs.remove(cb)431 432    def __getattr__(self, name):433        if name in ('predict','get_loss','backward','step','zero_grad'): return partial(self.callback, name)434        raise AttributeError(name)435 436    def callback(self, method_nm): run_cbs(self.cbs, method_nm, self)437    438    @property439    def training(self): return self.model.training440    441    def lr_find(self, gamma=1.3, max_mult=3, start_lr=1e-5, max_epochs=10):442        self.fit(max_epochs, lr=start_lr, cbs=LRFinderCB(gamma=gamma, max_mult=max_mult))443 444    def summary(self):445        res = '|Module|Input|Output|Num params|MFLOPS|\n|--|--|--|--|--|\n'446        totp,totf = 0,0447        def _f(hook, mod, inp, outp):448            nonlocal res,totp,totf449            nparms = sum(o.numel() for o in mod.parameters())450            totp += nparms451            *_,h,w = outp.shape452            flops = sum(_flops(o, h, w) for o in mod.parameters())/1e6453            totf += flops454            res += f'|{type(mod).__name__}|{tuple(inp[0].shape)}|{tuple(outp.shape)}|{nparms}|{flops:.1f}|\n'455        with Hooks(self.model, _f) as hooks: self.fit(1, lr=1, cbs=SingleBatchCB())456        print(f"Tot params: {totp}; MFLOPS: {totf:.1f}")457        if fc.IN_NOTEBOOK:458            from IPython.display import Markdown459            return Markdown(res)460        else: print(res)461 462 463 464# %% ../nbs/00_siamese.ipynb 40465@fc.delegates(plt.Axes.imshow)466def show_image(im, ax=None, figsize=None, title=None, noframe=True, **kwargs):467    "Show a PIL or PyTorch image on `ax`."468    if fc.hasattrs(im, ('cpu','permute','detach')):469        im = im.detach().cpu()470        if len(im.shape)==3 and im.shape[0]<5: im=im.permute(1,2,0)471    elif not isinstance(im,np.ndarray): im=np.array(im)472    if im.shape[-1]==1: im=im[...,0]473    if ax is None: _,ax = plt.subplots(figsize=figsize)474    ax.imshow(im, **kwargs, cmap='gray')475    if title is not None: ax.set_title(title, color="red")476    ax.set_xticks([]) 477    ax.set_yticks([]) 478    if noframe: ax.axis('off')479 480    return ax481 482@fc.delegates(plt.subplots, keep=True)483def subplots(484    nrows:int=1, # Number of rows in returned axes grid485    ncols:int=1, # Number of columns in returned axes grid486    figsize:tuple=None, # Width, height in inches of the returned figure487    imsize:int=3, # Size (in inches) of images that will be displayed in the returned figure488    suptitle:str=None, # Title to be set to returned figure489    **kwargs490): # fig and axs491    "A figure and set of subplots to display images of `imsize` inches"492    if figsize is None: figsize=(ncols*imsize, nrows*imsize)493    fig,ax = plt.subplots(nrows, ncols, figsize=figsize, **kwargs)494    if suptitle is not None: fig.suptitle(suptitle)495    if nrows*ncols==1: ax = np.array([ax])496 497    return fig,ax498 499@fc.delegates(subplots)500def get_grid(501    n:int, # Number of axes502    nrows:int=None, # Number of rows, defaulting to `int(math.sqrt(n))`503    ncols:int=None, # Number of columns, defaulting to `ceil(n/rows)`504    title:str=None, # If passed, title set to the figure505    weight:str='bold', # Title font weight506    size:int=14, # Title font size507    **kwargs,508): # fig and axs509    "Return a grid of `n` axes, `rows` by `cols`"510    if nrows: ncols = ncols or int(np.floor(n/nrows))511    elif ncols: nrows = nrows or int(np.ceil(n/ncols))512    else:513        nrows = int(math.sqrt(n))514        ncols = int(np.floor(n/nrows))515    fig,axs = subplots(nrows, ncols, **kwargs)516    for i in range(n, nrows*ncols): axs.flat[i].set_axis_off()517    if title is not None: fig.suptitle(title, weight=weight, size=size)518    return fig,axs519 520@fc.delegates(subplots)521def show_images(ims:list, # Images to show522                nrows:int|None=None, # Number of rows in grid523                ncols:int|None=None, # Number of columns in grid (auto-calculated if None)524                titles:list|None=None, # Optional list of titles for each image525                **kwargs):526    "Show all images `ims` as subplots with `rows` using `titles`"527    axs = get_grid(len(ims), nrows, ncols, **kwargs)[1].flat528    for im,t,ax in zip_longest(ims, titles or [], axs): show_image(im, ax=ax, title=t)529 530def reshape_alternating(tens1, tens2):531    new =  torch.stack((tens1, tens2), dim=0)532    return torch.transpose(new,0,1).flatten(start_dim=0, end_dim=1)533