CoolFace
Apppublic

captchaboy/dfff4444

sourceHugging Faceupdated 4y agoView on Hugging Face
0likes
callbacks.py361 linesDownload Raw Back to root
1import logging2import shutil3import time4 5import editdistance as ed6import torchvision.utils as vutils7from fastai.callbacks.tensorboard import (LearnerTensorboardWriter,8                                          SummaryWriter, TBWriteRequest,9                                          asyncTBWriter)10from fastai.vision import *11from torch.nn.parallel import DistributedDataParallel12from torchvision import transforms13 14import dataset15from utils import CharsetMapper, Timer, blend_mask16 17 18class IterationCallback(LearnerTensorboardWriter):19    "A `TrackerCallback` that monitor in each iteration."20    def __init__(self, learn:Learner, name:str='model', checpoint_keep_num=5,21                 show_iters:int=50, eval_iters:int=1000, save_iters:int=20000,22                 start_iters:int=0, stats_iters=20000):23        #if self.learn.rank is not None: time.sleep(self.learn.rank)  # keep all event files24        super().__init__(learn, base_dir='.', name=learn.path, loss_iters=show_iters, 25                        stats_iters=stats_iters, hist_iters=stats_iters)26        self.name, self.bestname = Path(name).name, f'best-{Path(name).name}'27        self.show_iters = show_iters28        self.eval_iters = eval_iters29        self.save_iters = save_iters30        self.start_iters = start_iters31        self.checpoint_keep_num = checpoint_keep_num32        self.metrics_root = 'metrics/'  # rewrite33        self.timer = Timer()34        self.host = self.learn.rank is None or self.learn.rank == 035 36    def _write_metrics(self, iteration:int, names:List[str], last_metrics:MetricsList)->None:37        "Writes training metrics to Tensorboard."38        for i, name in enumerate(names):39            if last_metrics is None or len(last_metrics) < i+1: return40            scalar_value = last_metrics[i]41            self._write_scalar(name=name, scalar_value=scalar_value, iteration=iteration)42 43    def _write_sub_loss(self, iteration:int, last_losses:dict)->None:44        "Writes sub loss to Tensorboard."45        for name, loss in last_losses.items():46            scalar_value = to_np(loss)47            tag = self.metrics_root + name48            self.tbwriter.add_scalar(tag=tag, scalar_value=scalar_value, global_step=iteration)49 50    def _save(self, name):51        if isinstance(self.learn.model, DistributedDataParallel):52            tmp = self.learn.model53            self.learn.model = self.learn.model.module54            self.learn.save(name)55            self.learn.model = tmp56        else: self.learn.save(name)57 58    def _validate(self, dl=None, callbacks=None, metrics=None, keeped_items=False):59        "Validate on `dl` with potential `callbacks` and `metrics`."60        dl = ifnone(dl, self.learn.data.valid_dl)61        metrics = ifnone(metrics, self.learn.metrics)62        cb_handler = CallbackHandler(ifnone(callbacks, []), metrics)63        cb_handler.on_train_begin(1, None, metrics); cb_handler.on_epoch_begin()64        if keeped_items: cb_handler.state_dict.update(dict(keeped_items=[]))65        val_metrics = validate(self.learn.model, dl, self.loss_func, cb_handler)66        cb_handler.on_epoch_end(val_metrics)67        if keeped_items: return cb_handler.state_dict['keeped_items']68        else: return cb_handler.state_dict['last_metrics']69 70    def jump_to_epoch_iter(self, epoch:int, iteration:int)->None:71        try:72            self.learn.load(f'{self.name}_{epoch}_{iteration}', purge=False)73            logging.info(f'Loaded {self.name}_{epoch}_{iteration}')74        except: logging.info(f'Model {self.name}_{epoch}_{iteration} not found.')75 76    def on_train_begin(self, n_epochs, **kwargs):77        # TODO: can not write graph here78        # super().on_train_begin(**kwargs)79        self.best = -float('inf')80        self.timer.tic()81        if self.host:82            checkpoint_path = self.learn.path/'checkpoint.yaml'83            if checkpoint_path.exists():84                os.remove(checkpoint_path)85            open(checkpoint_path, 'w').close()86        return {'skip_validate': True, 'iteration':self.start_iters}  # disable default validate87 88    def on_batch_begin(self, **kwargs:Any)->None:89        self.timer.toc_data()90        super().on_batch_begin(**kwargs)91 92    def on_batch_end(self, iteration, epoch, last_loss, smooth_loss, train, **kwargs):93        super().on_batch_end(last_loss, iteration, train, **kwargs)94        if iteration == 0: return95 96        if iteration % self.loss_iters == 0:97            last_losses = self.learn.loss_func.last_losses98            self._write_sub_loss(iteration=iteration, last_losses=last_losses)99            self.tbwriter.add_scalar(tag=self.metrics_root + 'lr',100                scalar_value=self.opt.lr, global_step=iteration)101 102        if iteration % self.show_iters == 0:103            log_str = f'epoch {epoch} iter {iteration}: loss = {last_loss:6.4f},  ' \104                      f'smooth loss = {smooth_loss:6.4f}'105            logging.info(log_str)106            # log_str = f'data time = {self.timer.data_diff:.4f}s, runing time = {self.timer.running_diff:.4f}s'107            # logging.info(log_str)108 109        if iteration % self.eval_iters == 0:110            # TODO: or remove time to on_epoch_end111            # 1. Record time 112            log_str = f'average data time = {self.timer.average_data_time():.4f}s, ' \113                      f'average running time = {self.timer.average_running_time():.4f}s'114            logging.info(log_str)115 116            # 2. Call validate117            last_metrics = self._validate()118            self.learn.model.train()119            log_str = f'epoch {epoch} iter {iteration}: eval loss = {last_metrics[0]:6.4f},  ' \120                      f'ccr = {last_metrics[1]:6.4f},  cwr = {last_metrics[2]:6.4f},  ' \121                      f'ted = {last_metrics[3]:6.4f},  ned = {last_metrics[4]:6.4f},  ' \122                      f'ted/w = {last_metrics[5]:6.4f}, '123            logging.info(log_str)124            names = ['eval_loss', 'ccr', 'cwr', 'ted', 'ned', 'ted/w']125            self._write_metrics(iteration, names, last_metrics)126 127            # 3. Save best model128            current = last_metrics[2]129            if current is not None and current > self.best:130                logging.info(f'Better model found at epoch {epoch}, '\131                             f'iter {iteration} with accuracy value: {current:6.4f}.')132                self.best = current133                self._save(f'{self.bestname}')134 135        if iteration % self.save_iters == 0 and self.host:136            logging.info(f'Save model {self.name}_{epoch}_{iteration}')137            filename = f'{self.name}_{epoch}_{iteration}'138            self._save(filename)139 140            checkpoint_path = self.learn.path/'checkpoint.yaml'141            if not checkpoint_path.exists():142                open(checkpoint_path, 'w').close()143            with open(checkpoint_path, 'r') as file:144                checkpoints = yaml.load(file, Loader=yaml.FullLoader) or dict()145            checkpoints['all_checkpoints'] = (146                checkpoints.get('all_checkpoints') or list())147            checkpoints['all_checkpoints'].insert(0, filename)148            if len(checkpoints['all_checkpoints']) > self.checpoint_keep_num:149                removed_checkpoint = checkpoints['all_checkpoints'].pop()150                removed_checkpoint =  self.learn.path/self.learn.model_dir/f'{removed_checkpoint}.pth'151                os.remove(removed_checkpoint)152            checkpoints['current_checkpoint'] = filename153            with open(checkpoint_path, 'w') as file:154                yaml.dump(checkpoints, file)155 156 157        self.timer.toc_running()158 159    def on_train_end(self, **kwargs):160        #self.learn.load(f'{self.bestname}', purge=False)161        pass162    163    def on_epoch_end(self, last_metrics:MetricsList, iteration:int, **kwargs)->None:164        self._write_embedding(iteration=iteration)165 166 167class TextAccuracy(Callback):168    _names = ['ccr', 'cwr', 'ted', 'ned', 'ted/w']169    def __init__(self, charset_path, max_length, case_sensitive, model_eval):170        self.charset_path = charset_path171        self.max_length = max_length172        self.case_sensitive = case_sensitive173        self.charset = CharsetMapper(charset_path, self.max_length)174        self.names = self._names175 176        self.model_eval = model_eval or 'alignment'177        assert self.model_eval in ['vision', 'language', 'alignment']178 179    def on_epoch_begin(self, **kwargs):180        self.total_num_char = 0.181        self.total_num_word = 0.182        self.correct_num_char = 0.183        self.correct_num_word = 0.184        self.total_ed = 0.185        self.total_ned = 0.186 187    def _get_output(self, last_output):188        if isinstance(last_output, (tuple, list)): 189            for res in last_output:190                if res['name'] == self.model_eval: output = res191        else: output = last_output192        return output193    194    def _update_output(self, last_output, items):195        if isinstance(last_output, (tuple, list)): 196            for res in last_output:197                if res['name'] == self.model_eval: res.update(items)198        else: last_output.update(items)199        return last_output200 201    def on_batch_end(self, last_output, last_target, **kwargs):202        output = self._get_output(last_output)203        logits, pt_lengths = output['logits'], output['pt_lengths']204        pt_text, pt_scores, pt_lengths_ = self.decode(logits)205        assert (pt_lengths == pt_lengths_).all(), f'{pt_lengths} != {pt_lengths_} for {pt_text}'206        last_output = self._update_output(last_output, {'pt_text':pt_text, 'pt_scores':pt_scores})207 208        pt_text = [self.charset.trim(t) for t in pt_text]209        label = last_target[0]210        if label.dim() == 3: label = label.argmax(dim=-1)  # one-hot label211        gt_text = [self.charset.get_text(l, trim=True) for l in label]212        213        for i in range(len(gt_text)):214            if not self.case_sensitive:215                gt_text[i], pt_text[i] = gt_text[i].lower(), pt_text[i].lower()216            distance = ed.eval(gt_text[i], pt_text[i])217            self.total_ed += distance218            self.total_ned += float(distance) / max(len(gt_text[i]), 1)219 220            if gt_text[i] == pt_text[i]:221                self.correct_num_word += 1222            self.total_num_word += 1223            224            for j in range(min(len(gt_text[i]), len(pt_text[i]))):225                if gt_text[i][j] == pt_text[i][j]:226                    self.correct_num_char += 1227            self.total_num_char += len(gt_text[i])228 229        return {'last_output': last_output}230 231    def on_epoch_end(self, last_metrics, **kwargs):232        mets = [self.correct_num_char / self.total_num_char,233                self.correct_num_word / self.total_num_word,234                self.total_ed,235                self.total_ned,236                self.total_ed / self.total_num_word]237        return add_metrics(last_metrics, mets)238 239    def decode(self, logit):240        """ Greed decode """241        # TODO: test running time and decode on GPU242        out = F.softmax(logit, dim=2)243        pt_text, pt_scores, pt_lengths = [], [], []244        for o in out:245            text = self.charset.get_text(o.argmax(dim=1), padding=False, trim=False)246            text = text.split(self.charset.null_char)[0]  # end at end-token247            pt_text.append(text)248            pt_scores.append(o.max(dim=1)[0])249            pt_lengths.append(min(len(text) + 1, self.max_length))  # one for end-token250        pt_scores = torch.stack(pt_scores)251        pt_lengths = pt_scores.new_tensor(pt_lengths, dtype=torch.long)252        return pt_text, pt_scores, pt_lengths253 254 255class TopKTextAccuracy(TextAccuracy):256    _names = ['ccr', 'cwr']257    def __init__(self, k, charset_path, max_length, case_sensitive, model_eval):258        self.k = k259        self.charset_path = charset_path260        self.max_length = max_length261        self.case_sensitive = case_sensitive262        self.charset = CharsetMapper(charset_path, self.max_length)263        self.names = self._names264 265    def on_epoch_begin(self, **kwargs):266        self.total_num_char = 0.267        self.total_num_word = 0.268        self.correct_num_char = 0.269        self.correct_num_word = 0.270 271    def on_batch_end(self, last_output, last_target, **kwargs):272        logits, pt_lengths = last_output['logits'], last_output['pt_lengths']273        gt_labels, gt_lengths = last_target[:]274 275        for logit, pt_length, label, length in zip(logits, pt_lengths, gt_labels, gt_lengths):276            word_flag = True277            for i in range(length):278                char_logit = logit[i].topk(self.k)[1]279                char_label = label[i].argmax(-1)280                if char_label in char_logit: self.correct_num_char += 1281                else: word_flag = False282                self.total_num_char += 1283            if pt_length == length and word_flag:284                self.correct_num_word += 1285            self.total_num_word += 1286 287    def on_epoch_end(self, last_metrics, **kwargs):288        mets = [self.correct_num_char / self.total_num_char,289                self.correct_num_word / self.total_num_word,290                0., 0., 0.]291        return add_metrics(last_metrics, mets)292 293 294class DumpPrediction(LearnerCallback):295 296    def __init__(self, learn, dataset, charset_path, model_eval, image_only=False, debug=False):297        super().__init__(learn=learn)298        self.debug = debug299        self.model_eval = model_eval or 'alignment'300        self.image_only = image_only301        assert self.model_eval in ['vision', 'language', 'alignment']302 303        self.dataset, self.root = dataset, Path(self.learn.path)/f'{dataset}-{self.model_eval}'304        self.attn_root = self.root/'attn'305        self.charset = CharsetMapper(charset_path)306        if self.root.exists(): shutil.rmtree(self.root)307        self.root.mkdir(), self.attn_root.mkdir()308 309        self.pil = transforms.ToPILImage()310        self.tensor = transforms.ToTensor()311        size = self.learn.data.img_h, self.learn.data.img_w312        self.resize = transforms.Resize(size=size, interpolation=0)313        self.c = 0314 315    def on_batch_end(self, last_input, last_output, last_target, **kwargs):316        if isinstance(last_output, (tuple, list)):317            for res in last_output:318                if res['name'] == self.model_eval: pt_text = res['pt_text']319                if res['name'] == 'vision': attn_scores = res['attn_scores'].detach().cpu()320                if res['name'] == self.model_eval: logits = res['logits']321        else:322            pt_text = last_output['pt_text']323            attn_scores = last_output['attn_scores'].detach().cpu()324            logits = last_output['logits']325 326        images = last_input[0] if isinstance(last_input, (tuple, list)) else last_input327        images = images.detach().cpu()328        pt_text = [self.charset.trim(t) for t in pt_text]329        gt_label = last_target[0]330        if gt_label.dim() == 3: gt_label = gt_label.argmax(dim=-1)  # one-hot label331        gt_text = [self.charset.get_text(l, trim=True) for l in gt_label]332 333        prediction, false_prediction = [], []334        for gt, pt, image, attn, logit in zip(gt_text, pt_text, images, attn_scores, logits):335            prediction.append(f'{gt}\t{pt}\n')336            if gt != pt:337                if self.debug:338                    scores = torch.softmax(logit, dim=-1)[:max(len(pt), len(gt)) + 1]339                    logging.info(f'{self.c} gt {gt}, pt {pt}, logit {logit.shape}, scores {scores.topk(5, dim=-1)}')340                false_prediction.append(f'{gt}\t{pt}\n')341 342            image = self.learn.data.denorm(image)343            if not self.image_only:344                image_np = np.array(self.pil(image))345                attn_pil = [self.pil(a) for a in attn[:, None, :, :]]346                attn = [self.tensor(self.resize(a)).repeat(3, 1, 1) for a in attn_pil]347                attn_sum = np.array([np.array(a) for a in attn_pil[:len(pt)]]).sum(axis=0)348                blended_sum = self.tensor(blend_mask(image_np, attn_sum))349                blended = [self.tensor(blend_mask(image_np, np.array(a))) for a in attn_pil]350                save_image = torch.stack([image] + attn + [blended_sum] + blended)351                save_image = save_image.view(2, -1, *save_image.shape[1:])352                save_image = save_image.permute(1, 0, 2, 3, 4).flatten(0, 1)353                vutils.save_image(save_image, self.attn_root/f'{self.c}_{gt}_{pt}.jpg', 354                                nrow=2, normalize=True, scale_each=True)355            else:356                self.pil(image).save(self.attn_root/f'{self.c}_{gt}_{pt}.jpg')357            self.c += 1358 359        with open(self.root/f'{self.model_eval}.txt', 'a') as f: f.writelines(prediction)360        with open(self.root/f'{self.model_eval}-false.txt', 'a') as f: f.writelines(false_prediction)  361