From af75ba7fd2813b9e13b649846314e18ca1a98790 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 16:14:22 +0700 Subject: [PATCH 01/18] rename segmentation module --- configs/classification/pipeline.yaml | 1 - configs/{segmentation => semantic}/eval.py | 2 +- configs/{segmentation => semantic}/infer.py | 8 ++++---- configs/{segmentation => semantic}/pipeline.yaml | 6 +++--- configs/{segmentation => semantic}/test.yaml | 0 configs/{segmentation => semantic}/train.py | 2 +- configs/{segmentation => semantic}/transform.yaml | 0 .../augmentations/__init__.py | 0 .../augmentations/mosaic.py | 0 .../datasets/__init__.py | 0 .../datasets/csv_dataset.py | 0 .../{segmentation => semantic}/datasets/dataset.py | 0 .../datasets/mosaic_dataset.py | 2 +- .../{segmentation => semantic}/losses/__init__.py | 0 .../{segmentation => semantic}/losses/ce_loss.py | 0 .../{segmentation => semantic}/losses/dice_loss.py | 0 .../losses/lovasz_loss.py | 0 .../losses/tversky_loss.py | 0 .../{segmentation => semantic}/metrics/__init__.py | 0 .../metrics/dicecoeff.py | 0 theseus/{segmentation => semantic}/metrics/miou.py | 0 .../metrics/pixel_accuracy.py | 0 .../{segmentation => semantic}/models/__init__.py | 0 .../{segmentation => semantic}/models/segmodels.py | 0 .../{segmentation => semantic}/models/wrapper.py | 0 theseus/{segmentation => semantic}/pipeline.py | 14 +++++++------- .../{segmentation => semantic}/trainer/__init__.py | 0 .../{segmentation => semantic}/trainer/trainer.py | 0 28 files changed, 17 insertions(+), 18 deletions(-) rename configs/{segmentation => semantic}/eval.py (78%) rename configs/{segmentation => semantic}/infer.py (94%) rename configs/{segmentation => semantic}/pipeline.yaml (90%) rename configs/{segmentation => semantic}/test.yaml (100%) rename configs/{segmentation => semantic}/train.py (78%) rename configs/{segmentation => semantic}/transform.yaml (100%) rename theseus/{segmentation => semantic}/augmentations/__init__.py (100%) rename theseus/{segmentation => semantic}/augmentations/mosaic.py (100%) rename theseus/{segmentation => semantic}/datasets/__init__.py (100%) rename theseus/{segmentation => semantic}/datasets/csv_dataset.py (100%) rename theseus/{segmentation => semantic}/datasets/dataset.py (100%) rename theseus/{segmentation => semantic}/datasets/mosaic_dataset.py (98%) rename theseus/{segmentation => semantic}/losses/__init__.py (100%) rename theseus/{segmentation => semantic}/losses/ce_loss.py (100%) rename theseus/{segmentation => semantic}/losses/dice_loss.py (100%) rename theseus/{segmentation => semantic}/losses/lovasz_loss.py (100%) rename theseus/{segmentation => semantic}/losses/tversky_loss.py (100%) rename theseus/{segmentation => semantic}/metrics/__init__.py (100%) rename theseus/{segmentation => semantic}/metrics/dicecoeff.py (100%) rename theseus/{segmentation => semantic}/metrics/miou.py (100%) rename theseus/{segmentation => semantic}/metrics/pixel_accuracy.py (100%) rename theseus/{segmentation => semantic}/models/__init__.py (100%) rename theseus/{segmentation => semantic}/models/segmodels.py (100%) rename theseus/{segmentation => semantic}/models/wrapper.py (100%) rename theseus/{segmentation => semantic}/pipeline.py (93%) rename theseus/{segmentation => semantic}/trainer/__init__.py (100%) rename theseus/{segmentation => semantic}/trainer/trainer.py (100%) diff --git a/configs/classification/pipeline.yaml b/configs/classification/pipeline.yaml index d5760c2..d15673e 100644 --- a/configs/classification/pipeline.yaml +++ b/configs/classification/pipeline.yaml @@ -12,7 +12,6 @@ trainer: name: ClassificationTrainer args: num_epochs: 100 - total_accumulate_steps: null clip_grad: 10.0 print_per_iter: 20 save_per_iter: 1000 diff --git a/configs/segmentation/eval.py b/configs/semantic/eval.py similarity index 78% rename from configs/segmentation/eval.py rename to configs/semantic/eval.py index 21859aa..837b019 100644 --- a/configs/segmentation/eval.py +++ b/configs/semantic/eval.py @@ -2,7 +2,7 @@ mpl.use("Agg") from theseus.opt import Opts -from theseus.segmentation.pipeline import Pipeline +from theseus.semantic.pipeline import Pipeline if __name__ == "__main__": opts = Opts().parse_args() diff --git a/configs/segmentation/infer.py b/configs/semantic/infer.py similarity index 94% rename from configs/segmentation/infer.py rename to configs/semantic/infer.py index b9cafef..c15c64f 100644 --- a/configs/segmentation/infer.py +++ b/configs/semantic/infer.py @@ -9,9 +9,9 @@ import torch from datetime import datetime from theseus.opt import Config -from theseus.segmentation.models import MODEL_REGISTRY -from theseus.segmentation.augmentations import TRANSFORM_REGISTRY -from theseus.segmentation.datasets import DATASET_REGISTRY, DATALOADER_REGISTRY +from theseus.semantic.models import MODEL_REGISTRY +from theseus.semantic.augmentations import TRANSFORM_REGISTRY +from theseus.semantic.datasets import DATASET_REGISTRY, DATALOADER_REGISTRY from theseus.utilities.loading import load_state_dict from theseus.utilities.loggers import LoggerObserver, StdoutLogger @@ -19,7 +19,7 @@ from theseus.utilities.getter import (get_instance, get_instance_recursively) from theseus.utilities.visualization.visualizer import Visualizer -from theseus.segmentation.datasets.csv_dataset import CSVDataset +from theseus.semantic.datasets.csv_dataset import CSVDataset @DATASET_REGISTRY.register() class TestCSVDataset(CSVDataset): diff --git a/configs/segmentation/pipeline.yaml b/configs/semantic/pipeline.yaml similarity index 90% rename from configs/segmentation/pipeline.yaml rename to configs/semantic/pipeline.yaml index 5f8c0e0..bc42764 100644 --- a/configs/segmentation/pipeline.yaml +++ b/configs/semantic/pipeline.yaml @@ -1,6 +1,6 @@ global: debug: true - cfg_transform: configs/segmentation/transform.yaml + cfg_transform: configs/semantic/transform.yaml save_dir: runs device: cuda:0 use_fp16: true @@ -61,7 +61,7 @@ data: image_dir: data/images/train mask_dir: data/masks/train csv_path: data/train.csv - txt_classnames: configs/segmentation/classes.txt + txt_classnames: configs/semantic/classes.txt mosaic_size: 960 mosaic_prob: 0.4 val: @@ -70,7 +70,7 @@ data: image_dir: data/images/val mask_dir: data/masks/val csv_path: data/val.csv - txt_classnames: configs/segmentation/classes.txt + txt_classnames: configs/semantic/classes.txt dataloader: train: name: DataLoaderWithCollator diff --git a/configs/segmentation/test.yaml b/configs/semantic/test.yaml similarity index 100% rename from configs/segmentation/test.yaml rename to configs/semantic/test.yaml diff --git a/configs/segmentation/train.py b/configs/semantic/train.py similarity index 78% rename from configs/segmentation/train.py rename to configs/semantic/train.py index d811d30..83fe83b 100644 --- a/configs/segmentation/train.py +++ b/configs/semantic/train.py @@ -2,7 +2,7 @@ mpl.use("Agg") from theseus.opt import Opts -from theseus.segmentation.pipeline import Pipeline +from theseus.semantic.pipeline import Pipeline if __name__ == "__main__": opts = Opts().parse_args() diff --git a/configs/segmentation/transform.yaml b/configs/semantic/transform.yaml similarity index 100% rename from configs/segmentation/transform.yaml rename to configs/semantic/transform.yaml diff --git a/theseus/segmentation/augmentations/__init__.py b/theseus/semantic/augmentations/__init__.py similarity index 100% rename from theseus/segmentation/augmentations/__init__.py rename to theseus/semantic/augmentations/__init__.py diff --git a/theseus/segmentation/augmentations/mosaic.py b/theseus/semantic/augmentations/mosaic.py similarity index 100% rename from theseus/segmentation/augmentations/mosaic.py rename to theseus/semantic/augmentations/mosaic.py diff --git a/theseus/segmentation/datasets/__init__.py b/theseus/semantic/datasets/__init__.py similarity index 100% rename from theseus/segmentation/datasets/__init__.py rename to theseus/semantic/datasets/__init__.py diff --git a/theseus/segmentation/datasets/csv_dataset.py b/theseus/semantic/datasets/csv_dataset.py similarity index 100% rename from theseus/segmentation/datasets/csv_dataset.py rename to theseus/semantic/datasets/csv_dataset.py diff --git a/theseus/segmentation/datasets/dataset.py b/theseus/semantic/datasets/dataset.py similarity index 100% rename from theseus/segmentation/datasets/dataset.py rename to theseus/semantic/datasets/dataset.py diff --git a/theseus/segmentation/datasets/mosaic_dataset.py b/theseus/semantic/datasets/mosaic_dataset.py similarity index 98% rename from theseus/segmentation/datasets/mosaic_dataset.py rename to theseus/semantic/datasets/mosaic_dataset.py index 7d8d074..ae58bd2 100644 --- a/theseus/segmentation/datasets/mosaic_dataset.py +++ b/theseus/semantic/datasets/mosaic_dataset.py @@ -6,7 +6,7 @@ import pandas as pd from PIL import Image from .dataset import SegmentationDataset -from theseus.segmentation.augmentations.mosaic import Mosaic +from theseus.semantic.augmentations.mosaic import Mosaic from theseus.utilities.loggers.observer import LoggerObserver LOGGER = LoggerObserver.getLogger('main') diff --git a/theseus/segmentation/losses/__init__.py b/theseus/semantic/losses/__init__.py similarity index 100% rename from theseus/segmentation/losses/__init__.py rename to theseus/semantic/losses/__init__.py diff --git a/theseus/segmentation/losses/ce_loss.py b/theseus/semantic/losses/ce_loss.py similarity index 100% rename from theseus/segmentation/losses/ce_loss.py rename to theseus/semantic/losses/ce_loss.py diff --git a/theseus/segmentation/losses/dice_loss.py b/theseus/semantic/losses/dice_loss.py similarity index 100% rename from theseus/segmentation/losses/dice_loss.py rename to theseus/semantic/losses/dice_loss.py diff --git a/theseus/segmentation/losses/lovasz_loss.py b/theseus/semantic/losses/lovasz_loss.py similarity index 100% rename from theseus/segmentation/losses/lovasz_loss.py rename to theseus/semantic/losses/lovasz_loss.py diff --git a/theseus/segmentation/losses/tversky_loss.py b/theseus/semantic/losses/tversky_loss.py similarity index 100% rename from theseus/segmentation/losses/tversky_loss.py rename to theseus/semantic/losses/tversky_loss.py diff --git a/theseus/segmentation/metrics/__init__.py b/theseus/semantic/metrics/__init__.py similarity index 100% rename from theseus/segmentation/metrics/__init__.py rename to theseus/semantic/metrics/__init__.py diff --git a/theseus/segmentation/metrics/dicecoeff.py b/theseus/semantic/metrics/dicecoeff.py similarity index 100% rename from theseus/segmentation/metrics/dicecoeff.py rename to theseus/semantic/metrics/dicecoeff.py diff --git a/theseus/segmentation/metrics/miou.py b/theseus/semantic/metrics/miou.py similarity index 100% rename from theseus/segmentation/metrics/miou.py rename to theseus/semantic/metrics/miou.py diff --git a/theseus/segmentation/metrics/pixel_accuracy.py b/theseus/semantic/metrics/pixel_accuracy.py similarity index 100% rename from theseus/segmentation/metrics/pixel_accuracy.py rename to theseus/semantic/metrics/pixel_accuracy.py diff --git a/theseus/segmentation/models/__init__.py b/theseus/semantic/models/__init__.py similarity index 100% rename from theseus/segmentation/models/__init__.py rename to theseus/semantic/models/__init__.py diff --git a/theseus/segmentation/models/segmodels.py b/theseus/semantic/models/segmodels.py similarity index 100% rename from theseus/segmentation/models/segmodels.py rename to theseus/semantic/models/segmodels.py diff --git a/theseus/segmentation/models/wrapper.py b/theseus/semantic/models/wrapper.py similarity index 100% rename from theseus/segmentation/models/wrapper.py rename to theseus/semantic/models/wrapper.py diff --git a/theseus/segmentation/pipeline.py b/theseus/semantic/pipeline.py similarity index 93% rename from theseus/segmentation/pipeline.py rename to theseus/semantic/pipeline.py index 666a116..c6460e6 100644 --- a/theseus/segmentation/pipeline.py +++ b/theseus/semantic/pipeline.py @@ -3,15 +3,15 @@ import os import torch -from theseus.segmentation.models.wrapper import ModelWithLoss +from theseus.semantic.models.wrapper import ModelWithLoss from theseus.opt import Config from theseus.base.optimizers import OPTIM_REGISTRY, SCHEDULER_REGISTRY -from theseus.segmentation.augmentations import TRANSFORM_REGISTRY -from theseus.segmentation.losses import LOSS_REGISTRY -from theseus.segmentation.datasets import DATASET_REGISTRY, DATALOADER_REGISTRY -from theseus.segmentation.trainer import TRAINER_REGISTRY -from theseus.segmentation.metrics import METRIC_REGISTRY -from theseus.segmentation.models import MODEL_REGISTRY +from theseus.semantic.augmentations import TRANSFORM_REGISTRY +from theseus.semantic.losses import LOSS_REGISTRY +from theseus.semantic.datasets import DATASET_REGISTRY, DATALOADER_REGISTRY +from theseus.semantic.trainer import TRAINER_REGISTRY +from theseus.semantic.metrics import METRIC_REGISTRY +from theseus.semantic.models import MODEL_REGISTRY from theseus.utilities.getter import (get_instance, get_instance_recursively) from theseus.utilities.loggers import LoggerObserver, TensorboardLogger, StdoutLogger, ImageWriter from theseus.utilities.loading import load_state_dict, find_old_tflog diff --git a/theseus/segmentation/trainer/__init__.py b/theseus/semantic/trainer/__init__.py similarity index 100% rename from theseus/segmentation/trainer/__init__.py rename to theseus/semantic/trainer/__init__.py diff --git a/theseus/segmentation/trainer/trainer.py b/theseus/semantic/trainer/trainer.py similarity index 100% rename from theseus/segmentation/trainer/trainer.py rename to theseus/semantic/trainer/trainer.py From 6603e1f0c0b59a3274e127a295108f2aa92dad05 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 16:14:45 +0700 Subject: [PATCH 02/18] generalize base trainer --- theseus/base/trainer/base_trainer.py | 54 +------------ theseus/base/trainer/supervised_trainer.py | 91 +++++++++++++++++----- 2 files changed, 74 insertions(+), 71 deletions(-) diff --git a/theseus/base/trainer/base_trainer.py b/theseus/base/trainer/base_trainer.py index ac35bdb..00762aa 100644 --- a/theseus/base/trainer/base_trainer.py +++ b/theseus/base/trainer/base_trainer.py @@ -10,18 +10,6 @@ class BaseTrainer(): """Base class for trainer - model : `torch.nn.Module` - Wrapper model with loss - trainloader : `torch.utils.DataLoader` - DataLoader for training - valloader : `torch.utils.DataLoader` - DataLoader for validation - metrics: `List[Metric]` - list of metrics for evaluation - optimizer: `torch.optim.Optimizer` - optimizer for parameters update - scheduler: `torch.optim.lr_scheduler.Scheduler` - learning rate schedulers save_dir: `str` Path to directory for saving stuffs use_fp16: `bool` @@ -46,16 +34,9 @@ class BaseTrainer(): Path to checkpoint for continue training """ def __init__(self, - model, - trainloader, - valloader, - metrics, - optimizer, - scheduler, save_dir: str = 'runs', use_fp16: bool = False, num_epochs: int = 100, - total_accumulate_steps: Optional[int] = None, clip_grad: float = 10.0, print_per_iter: int = 100, save_per_iter: int = 100, @@ -65,25 +46,11 @@ def __init__(self, resume: str = Optional[None], ): - - self.model = model - self.metrics = metrics - self.optimizer = optimizer - self.scheduler = scheduler - self.trainloader = trainloader - self.valloader = valloader - self.save_dir = save_dir self.checkpoint = Checkpoint(os.path.join(self.save_dir, 'checkpoints')) self.num_epochs = num_epochs - self.step_per_epoch = self.scheduler.step_per_epoch self.use_amp = True if use_fp16 else False self.scaler = NativeScaler() if use_fp16 else False - - if total_accumulate_steps is None: - self.accumulate_steps = 1 - else: - self.accumulate_steps = max(round(total_accumulate_steps / trainloader.batch_size), 1) self.clip_grad = clip_grad self.evaluate_per_epoch = evaluate_per_epoch self.print_per_iter = print_per_iter @@ -96,16 +63,10 @@ def __init__(self, self.start_iter = 0 def fit(self): - # Total number of training iterations - self.num_iters = (self.num_epochs+1) * len(self.trainloader) # On start callbacks self.on_start() - # Init scheduler params - if self.step_per_epoch: - self.scheduler.last_epoch = self.epoch - 1 - LOGGER.text(f'===========================START TRAINING=================================', level=LoggerObserver.INFO) for epoch in range(self.epoch, self.num_epochs): try: @@ -157,18 +118,7 @@ def on_evaluate_end(self): return def on_epoch_end(self): - if self.step_per_epoch: - self.scheduler.step() - lrl = [x['lr'] for x in self.optimizer.param_groups] - lr = sum(lrl) / len(lrl) - LOGGER.log([{ - 'tag': 'Training/Learning rate', - 'value': lr, - 'type': LoggerObserver.SCALAR, - 'kwargs': { - 'step': self.epoch - } - }]) + return def on_finish(self): - self.save_checkpoint() \ No newline at end of file + return \ No newline at end of file diff --git a/theseus/base/trainer/supervised_trainer.py b/theseus/base/trainer/supervised_trainer.py index cbfdf60..f89d1a5 100644 --- a/theseus/base/trainer/supervised_trainer.py +++ b/theseus/base/trainer/supervised_trainer.py @@ -12,11 +12,42 @@ class SupervisedTrainer(BaseTrainer): """Trainer for supervised tasks + model : `torch.nn.Module` + Wrapper model with loss + trainloader : `torch.utils.DataLoader` + DataLoader for training + valloader : `torch.utils.DataLoader` + DataLoader for validation + metrics: `List[Metric]` + list of metrics for evaluation + optimizer: `torch.optim.Optimizer` + optimizer for parameters update + scheduler: `torch.optim.lr_scheduler.Scheduler` + learning rate schedulers + """ - def __init__(self, **kwargs): + def __init__( + self, + model, + trainloader, + valloader, + metrics, + optimizer, + scheduler, + **kwargs): super().__init__(**kwargs) + self.model = model + self.metrics = metrics + self.optimizer = optimizer + self.scheduler = scheduler + self.trainloader = trainloader + self.valloader = valloader + + self.step_per_epoch = self.scheduler.step_per_epoch + + def training_epoch(self): """ Perform training one epoch @@ -37,30 +68,27 @@ def training_epoch(self): loss = outputs['loss'] loss_dict = outputs['loss_dict'] - loss /= self.accumulate_steps # Backward loss self.scaler(loss, self.optimizer) - if i % self.accumulate_steps == 0 or i == len(self.trainloader)-1: - self.scaler.step(self.optimizer, clip_grad=self.clip_grad, parameters=self.model.parameters()) - - if not self.step_per_epoch: - self.scheduler.step() - lrl = [x['lr'] for x in self.optimizer.param_groups] - lr = sum(lrl) / len(lrl) + self.scaler.step(self.optimizer, clip_grad=self.clip_grad, parameters=self.model.parameters()) - LOGGER.log([{ - 'tag': 'Training/Learning rate', - 'value': lr, - 'type': LoggerObserver.SCALAR, - 'kwargs': { - 'step': self.iters - } - }]) + if not self.step_per_epoch: + self.scheduler.step() + lrl = [x['lr'] for x in self.optimizer.param_groups] + lr = sum(lrl) / len(lrl) + LOGGER.log([{ + 'tag': 'Training/Learning rate', + 'value': lr, + 'type': LoggerObserver.SCALAR, + 'kwargs': { + 'step': self.iters + } + }]) - self.optimizer.zero_grad() + self.optimizer.zero_grad() torch.cuda.synchronize() end_time = time.time() @@ -182,4 +210,29 @@ def evaluate_epoch(self): self.check_best(metric_dict) def check_best(self, metric_dict): - return \ No newline at end of file + return + + def on_start(self): + # Total number of training iterations + self.num_iters = (self.num_epochs+1) * len(self.trainloader) + + # Init scheduler params + if self.step_per_epoch: + self.scheduler.last_epoch = self.epoch - 1 + + def on_epoch_end(self): + if self.step_per_epoch: + self.scheduler.step() + lrl = [x['lr'] for x in self.optimizer.param_groups] + lr = sum(lrl) / len(lrl) + LOGGER.log([{ + 'tag': 'Training/Learning rate', + 'value': lr, + 'type': LoggerObserver.SCALAR, + 'kwargs': { + 'step': self.epoch + } + }]) + + def on_finish(self): + self.save_checkpoint() \ No newline at end of file From b8a5fa7a3e84435847faa3617aff722b7f0c10ad Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 16:52:36 +0700 Subject: [PATCH 03/18] add semantic requirement --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 149950d..21ac724 100644 --- a/setup.py +++ b/setup.py @@ -18,5 +18,7 @@ "omegaconf", "gdown==3.13.0", "grad-cam", + "tabulate", + "segmentation-models-pytorch" ], ) \ No newline at end of file From 391e2cd8fedbd02513b752105e7541cb08593fcb Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 16:52:57 +0700 Subject: [PATCH 04/18] change epochs to global iterations --- README.md | 2 +- configs/classification/pipeline.yaml | 8 ++--- configs/semantic/pipeline.yaml | 8 ++--- theseus/base/trainer/base_trainer.py | 35 ++++++++++------------ theseus/base/trainer/supervised_trainer.py | 33 ++++++++++---------- theseus/classification/pipeline.py | 5 ++-- theseus/classification/trainer/trainer.py | 4 +-- theseus/semantic/pipeline.py | 5 ++-- theseus/semantic/trainer/trainer.py | 4 +-- 9 files changed, 47 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 815449c..f29fb72 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ python train.py -c pipeline.yaml ``` python train.py \ -c pipeline.yaml \ - -o trainer.args.num_epochs=50 \ + -o trainer.args.num_iterations=5000 \ global.resume=checkpoint.pth ``` **Notice: There are no spaces between keys and values in -o flag** diff --git a/configs/classification/pipeline.yaml b/configs/classification/pipeline.yaml index d15673e..4478dca 100644 --- a/configs/classification/pipeline.yaml +++ b/configs/classification/pipeline.yaml @@ -11,11 +11,11 @@ global: trainer: name: ClassificationTrainer args: - num_epochs: 100 + num_iterations: 10000 clip_grad: 10.0 - print_per_iter: 20 - save_per_iter: 1000 - evaluate_per_epoch : 1 + print_interval: 20 + save_interval: 1000 + evaluate_interval : 1 visualize_when_val : True diff --git a/configs/semantic/pipeline.yaml b/configs/semantic/pipeline.yaml index bc42764..64609f5 100644 --- a/configs/semantic/pipeline.yaml +++ b/configs/semantic/pipeline.yaml @@ -9,12 +9,12 @@ global: trainer: name: SegmentationTrainer args: - num_epochs: 50 + num_iterations: 50000 total_accumulate_steps: null clip_grad: 10.0 - print_per_iter: 20 - save_per_iter: 1000 - evaluate_per_epoch: 1 + print_interval: 20 + save_interval: 1000 + evaluate_interval: 1 visualize_when_val: true model: name: BaseSegModel diff --git a/theseus/base/trainer/base_trainer.py b/theseus/base/trainer/base_trainer.py index 00762aa..1aef9d2 100644 --- a/theseus/base/trainer/base_trainer.py +++ b/theseus/base/trainer/base_trainer.py @@ -14,17 +14,17 @@ class BaseTrainer(): Path to directory for saving stuffs use_fp16: `bool` whether to use 16bit floating-point precision - num_epochs: `int` + num_iterations: `int` total number of running epochs total_accumulate_steps: `int` gradient accumulation step. None means not use clip_grad: `float` Gradient clipping - print_per_iter: `int` + print_interval: `int` Logging cycle per iteration - save_per_iter: `int` + save_interval: `int` Save checkpoint per iteration - evaluate_per_epoch: `int` + evaluate_interval: `int` Number of epochs to perform validation visualize_when_val: `bool` whether to visualize predictions @@ -36,11 +36,11 @@ class BaseTrainer(): def __init__(self, save_dir: str = 'runs', use_fp16: bool = False, - num_epochs: int = 100, + num_iterations: int = 10000, clip_grad: float = 10.0, - print_per_iter: int = 100, - save_per_iter: int = 100, - evaluate_per_epoch: int = 1, + print_interval: int = 100, + save_interval: int = 100, + evaluate_interval: int = 1, visualize_when_val: bool = True, best_value: float = 0.0, resume: str = Optional[None], @@ -48,19 +48,17 @@ def __init__(self, self.save_dir = save_dir self.checkpoint = Checkpoint(os.path.join(self.save_dir, 'checkpoints')) - self.num_epochs = num_epochs + self.num_iterations = num_iterations self.use_amp = True if use_fp16 else False self.scaler = NativeScaler() if use_fp16 else False self.clip_grad = clip_grad - self.evaluate_per_epoch = evaluate_per_epoch - self.print_per_iter = print_per_iter - self.save_per_iter = save_per_iter + self.evaluate_interval = evaluate_interval + self.print_interval = print_interval + self.save_interval = save_interval self.visualize_when_val = visualize_when_val self.best_value = best_value self.resume = resume - self.epoch = 0 self.iters = 0 - self.start_iter = 0 def fit(self): @@ -68,18 +66,15 @@ def fit(self): self.on_start() LOGGER.text(f'===========================START TRAINING=================================', level=LoggerObserver.INFO) - for epoch in range(self.epoch, self.num_epochs): + while self.iters < self.num_iterations: try: - # Save current epoch - self.epoch = epoch - # Start training self.training_epoch() self.on_training_end() # Start evaluation - if self.evaluate_per_epoch != 0: - if epoch % self.evaluate_per_epoch == 0 and epoch+1 >= self.evaluate_per_epoch: + if self.evaluate_interval != 0: + if self.iters % self.evaluate_interval == 0 and self.iters>0: self.evaluate_epoch() self.on_evaluate_end() diff --git a/theseus/base/trainer/supervised_trainer.py b/theseus/base/trainer/supervised_trainer.py index f89d1a5..6b4ac49 100644 --- a/theseus/base/trainer/supervised_trainer.py +++ b/theseus/base/trainer/supervised_trainer.py @@ -65,13 +65,13 @@ def training_epoch(self): # Gradient scaler with amp.autocast(enabled=self.use_amp): outputs = self.model.training_step(batch) - loss = outputs['loss'] loss_dict = outputs['loss_dict'] # Backward loss self.scaler(loss, self.optimizer) + # Optmizer step self.scaler.step(self.optimizer, clip_grad=self.clip_grad, parameters=self.model.parameters()) if not self.step_per_epoch: @@ -102,24 +102,24 @@ def training_epoch(self): running_time += end_time-start_time # Calculate current iteration - self.iters = self.start_iter + len(self.trainloader)*self.epoch + i + 1 + self.iters = self.iters + 1 # Logging - if self.iters % self.print_per_iter == 0: + if self.iters % self.print_interval == 0: for key in running_loss.keys(): - running_loss[key] /= self.print_per_iter + running_loss[key] /= self.print_interval running_loss[key] = np.round(running_loss[key], 5) loss_string = '{}'.format(running_loss)[1:-1].replace("'",'').replace(",",' ||') LOGGER.text( - "[{}|{}] [{}|{}] || {} || Time: {:10.4f}s".format( - self.epoch, self.num_epochs, self.iters, - self.num_iters,loss_string, running_time), + "[{}|{}] || {} || Time: {:10.4f}s".format( + self.iters, self.num_iterations, + loss_string, running_time), LoggerObserver.INFO) log_dict = [{ 'tag': f"Training/{k} Loss", - 'value': v/self.print_per_iter, + 'value': v/self.print_interval, 'type': LoggerObserver.SCALAR, 'kwargs': { 'step': self.iters @@ -131,8 +131,8 @@ def training_epoch(self): running_time = 0 # Saving checkpoint - if (self.iters % self.save_per_iter == 0 or self.iters == self.num_iters - 1): - LOGGER.text(f'Save model at [{self.iters}|{self.num_iters}] to last.pth', LoggerObserver.INFO) + if (self.iters % self.save_interval == 0 or self.iters == self.num_iterations - 1): + LOGGER.text(f'Save model at [{self.iters}|{self.num_iterations}] to last.pth', LoggerObserver.INFO) self.save_checkpoint() @torch.no_grad() @@ -175,7 +175,7 @@ def evaluate_epoch(self): loss_string = '{}'.format(epoch_loss)[1:-1].replace("'",'').replace(",",' ||') LOGGER.text( "[{}|{}] || {} || Time: {:10.4f} s".format( - self.epoch, self.num_epochs, loss_string, running_time), + self.iters, self.num_iterations, loss_string, running_time), level=LoggerObserver.INFO) metric_string = "" @@ -192,7 +192,7 @@ def evaluate_epoch(self): 'value': v/len(self.valloader), 'type': LoggerObserver.SCALAR, 'kwargs': { - 'step': self.epoch + 'step': self.iters } } for k,v in epoch_loss.items()] @@ -200,7 +200,7 @@ def evaluate_epoch(self): 'tag': f"Validation/{k}", 'value': v, 'kwargs': { - 'step': self.epoch + 'step': self.iters } } for k,v in metric_dict.items()] @@ -213,12 +213,9 @@ def check_best(self, metric_dict): return def on_start(self): - # Total number of training iterations - self.num_iters = (self.num_epochs+1) * len(self.trainloader) - # Init scheduler params if self.step_per_epoch: - self.scheduler.last_epoch = self.epoch - 1 + self.scheduler.last_epoch = self.iters//len(self.trainloader) - 1 def on_epoch_end(self): if self.step_per_epoch: @@ -230,7 +227,7 @@ def on_epoch_end(self): 'value': lr, 'type': LoggerObserver.SCALAR, 'kwargs': { - 'step': self.epoch + 'step': self.iters } }]) diff --git a/theseus/classification/pipeline.py b/theseus/classification/pipeline.py index 39474ec..1ecc431 100644 --- a/theseus/classification/pipeline.py +++ b/theseus/classification/pipeline.py @@ -110,12 +110,13 @@ def __init__( state_dict = torch.load(self.resume) self.model.model = load_state_dict(self.model.model, state_dict, 'model') self.optimizer = load_state_dict(self.optimizer, state_dict, 'optimizer') - last_epoch = load_state_dict(last_epoch, state_dict, 'epoch') + iters = load_state_dict(iters, state_dict, 'iters') + last_epoch = iters//len(self.train_dataloader) - 1 self.scheduler = get_instance( self.opt["scheduler"], registry=SCHEDULER_REGISTRY, optimizer=self.optimizer, **{ - 'num_epochs': self.opt["trainer"]['args']['num_epochs'], + 'num_epochs': self.opt["trainer"]['args']['num_iterations'] // len(self.train_dataloader), 'trainset': self.train_dataset, 'batch_size': self.opt["data"]['dataloader']['val']['args']['batch_size'], 'last_epoch': last_epoch, diff --git a/theseus/classification/trainer/trainer.py b/theseus/classification/trainer/trainer.py index c542998..42f9241 100644 --- a/theseus/classification/trainer/trainer.py +++ b/theseus/classification/trainer/trainer.py @@ -40,7 +40,6 @@ def save_checkpoint(self, outname='last'): weights = { 'model': self.model.model.state_dict(), 'optimizer': self.optimizer.state_dict(), - 'epoch': self.epoch, 'iters': self.iters, 'best_value': self.best_value, } @@ -56,8 +55,7 @@ def load_checkpoint(self, path:str): """ LOGGER.text("Loading checkpoints...", level=LoggerObserver.INFO) state_dict = torch.load(path, map_location='cpu') - self.epoch = load_state_dict(self.epoch, state_dict, 'epoch') - self.start_iter = load_state_dict(self.start_iter, state_dict, 'iters') + self.iters = load_state_dict(self.iters, state_dict, 'iters') self.best_value = load_state_dict(self.best_value, state_dict, 'best_value') self.scaler = load_state_dict(self.scaler, state_dict, self.scaler.state_dict_key) diff --git a/theseus/semantic/pipeline.py b/theseus/semantic/pipeline.py index c6460e6..ffa6716 100644 --- a/theseus/semantic/pipeline.py +++ b/theseus/semantic/pipeline.py @@ -112,12 +112,13 @@ def __init__( state_dict = torch.load(self.resume) self.model.model = load_state_dict(self.model.model, state_dict, 'model') self.optimizer = load_state_dict(self.optimizer, state_dict, 'optimizer') - last_epoch = load_state_dict(last_epoch, state_dict, 'epoch') + iters = load_state_dict(iters, state_dict, 'iters') + last_epoch = iters//len(self.train_dataloader) - 1 self.scheduler = get_instance( self.opt["scheduler"], registry=SCHEDULER_REGISTRY, optimizer=self.optimizer, **{ - 'num_epochs': self.opt["trainer"]['args']['num_epochs'], + 'num_epochs': self.opt["trainer"]['args']['num_iterations'] // len(self.train_dataloader), 'trainset': self.train_dataset, 'batch_size': self.opt["data"]['dataloader']['val']['args']['batch_size'], 'last_epoch': last_epoch, diff --git a/theseus/semantic/trainer/trainer.py b/theseus/semantic/trainer/trainer.py index 2b1c31d..dfd6842 100644 --- a/theseus/semantic/trainer/trainer.py +++ b/theseus/semantic/trainer/trainer.py @@ -41,7 +41,6 @@ def save_checkpoint(self, outname='last'): weights = { 'model': self.model.model.state_dict(), 'optimizer': self.optimizer.state_dict(), - 'epoch': self.epoch, 'iters': self.iters, 'best_value': self.best_value, } @@ -57,8 +56,7 @@ def load_checkpoint(self, path:str): """ LOGGER.text("Loading checkpoints...", level=LoggerObserver.INFO) state_dict = torch.load(path, map_location='cpu') - self.epoch = load_state_dict(self.epoch, state_dict, 'epoch') - self.start_iter = load_state_dict(self.start_iter, state_dict, 'iters') + self.iters = load_state_dict(self.iters, state_dict, 'iters') self.best_value = load_state_dict(self.best_value, state_dict, 'best_value') self.scaler = load_state_dict(self.scaler, state_dict, self.scaler.state_dict_key) From 83678d5cb7a8249d0659698d13f0bfe8f13de74b Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 17:19:08 +0700 Subject: [PATCH 05/18] fix something --- theseus/classification/datasets/csv_dataset.py | 3 +-- theseus/classification/datasets/dataset.py | 4 +++- theseus/classification/datasets/folder_dataset.py | 8 +++++--- theseus/classification/pipeline.py | 2 +- theseus/semantic/pipeline.py | 2 +- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/theseus/classification/datasets/csv_dataset.py b/theseus/classification/datasets/csv_dataset.py index 4288868..5e3e460 100644 --- a/theseus/classification/datasets/csv_dataset.py +++ b/theseus/classification/datasets/csv_dataset.py @@ -72,8 +72,7 @@ def _load_data(self): df = pd.read_csv(self.csv_path) for _, row in df.iterrows(): image_name, label = row - image_path = os.path.join(self.image_dir, image_name) - self.fns.append([image_path, label]) + self.fns.append([image_name, label]) def _calculate_classes_dist(self): """ diff --git a/theseus/classification/datasets/dataset.py b/theseus/classification/datasets/dataset.py index a00791c..68f3755 100644 --- a/theseus/classification/datasets/dataset.py +++ b/theseus/classification/datasets/dataset.py @@ -27,7 +27,9 @@ def __getitem__(self, idx: int) -> Dict: """ Get one item """ - image_path, label_name = self.fns[idx] + image_name, label_name = self.fns[idx] + image_path = os.path.join(self.image_dir, image_name) + im = Image.open(image_path).convert('RGB') width, height = im.width, im.height class_idx = self.classes_idx[label_name] diff --git a/theseus/classification/datasets/folder_dataset.py b/theseus/classification/datasets/folder_dataset.py index a29bded..d9fe86d 100644 --- a/theseus/classification/datasets/folder_dataset.py +++ b/theseus/classification/datasets/folder_dataset.py @@ -72,8 +72,7 @@ def _load_data(self): folder_name = os.path.join(self.image_dir, label) image_names = os.listdir(folder_name) for image_name in image_names: - image_path = os.path.join(folder_name, image_name) - self.fns.append([image_path, label]) + self.fns.append([image_name, label]) def _calculate_classes_dist(self): """ @@ -84,6 +83,9 @@ def _calculate_classes_dist(self): classnames = os.listdir(self.image_dir) for label in classnames: - self.classes_dist.append(self.classes_idx[label]) + folder_name = os.path.join(self.image_dir, label) + image_names = os.listdir(folder_name) + for _ in image_names: + self.classes_dist.append(self.classes_idx[label]) return self.classes_dist \ No newline at end of file diff --git a/theseus/classification/pipeline.py b/theseus/classification/pipeline.py index 1ecc431..a217d9a 100644 --- a/theseus/classification/pipeline.py +++ b/theseus/classification/pipeline.py @@ -110,7 +110,7 @@ def __init__( state_dict = torch.load(self.resume) self.model.model = load_state_dict(self.model.model, state_dict, 'model') self.optimizer = load_state_dict(self.optimizer, state_dict, 'optimizer') - iters = load_state_dict(iters, state_dict, 'iters') + iters = load_state_dict(None, state_dict, 'iters') last_epoch = iters//len(self.train_dataloader) - 1 self.scheduler = get_instance( diff --git a/theseus/semantic/pipeline.py b/theseus/semantic/pipeline.py index ffa6716..c266648 100644 --- a/theseus/semantic/pipeline.py +++ b/theseus/semantic/pipeline.py @@ -112,7 +112,7 @@ def __init__( state_dict = torch.load(self.resume) self.model.model = load_state_dict(self.model.model, state_dict, 'model') self.optimizer = load_state_dict(self.optimizer, state_dict, 'optimizer') - iters = load_state_dict(iters, state_dict, 'iters') + iters = load_state_dict(None, state_dict, 'iters') last_epoch = iters//len(self.train_dataloader) - 1 self.scheduler = get_instance( From dc606a1c510e181f42301bd19c2bfb215a10b577 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 17:41:17 +0700 Subject: [PATCH 06/18] fix most bugs --- configs/semantic/transform.yaml | 8 ++++---- theseus/base/datasets/dataset.py | 12 +++++++----- theseus/classification/datasets/dataset.py | 10 ++++------ theseus/classification/datasets/folder_dataset.py | 2 +- theseus/classification/trainer/trainer.py | 2 +- theseus/utilities/visualization/visualizer.py | 4 ++-- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/configs/semantic/transform.yaml b/configs/semantic/transform.yaml index 2bfa30e..3ff9670 100644 --- a/configs/semantic/transform.yaml +++ b/configs/semantic/transform.yaml @@ -27,8 +27,8 @@ train: # value: 0 - name: AlbNormalize args: - mean: [0.229, 0.224, 0.225] - std: [0.485, 0.456, 0.406] + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] - name: AlbToTensorV2 val: name: AlbCompose @@ -40,6 +40,6 @@ val: height: 640 - name: AlbNormalize args: - mean: [0.229, 0.224, 0.225] - std: [0.485, 0.456, 0.406] + mean: [0.485, 0.456, 0.406] + std: [0.229, 0.224, 0.225] - name: AlbToTensorV2 \ No newline at end of file diff --git a/theseus/base/datasets/dataset.py b/theseus/base/datasets/dataset.py index 8056978..1bb18f4 100644 --- a/theseus/base/datasets/dataset.py +++ b/theseus/base/datasets/dataset.py @@ -60,14 +60,14 @@ def load_data(self): self.fns = [] image_names = os.listdir(self.image_dir) for image_name in image_names: - image_path = os.path.join(self.image_dir, image_name) - self.fns.append(image_path) + self.fns.append(image_name) def __getitem__(self, index: int): """ Get an item from memory """ - image_path = self.fns[index] + image_name = self.fns[index] + image_path = os.path.join(self.image_dir, image_name) im = Image.open(image_path).convert('RGB') width, height = im.width, im.height @@ -76,7 +76,7 @@ def __getitem__(self, index: int): return { "input": im, - 'img_name': os.path.basename(image_path), + 'img_name': image_name, 'ori_size': [width, height] } @@ -86,8 +86,10 @@ def __len__(self): def collate_fn(self, batch: List): imgs = torch.stack([s['input'] for s in batch]) img_names = [s['img_name'] for s in batch] + ori_sizes = [s['ori_size'] for s in batch] return { 'inputs': imgs, - 'img_names': img_names + 'img_names': img_names, + 'ori_sizes': ori_sizes } \ No newline at end of file diff --git a/theseus/classification/datasets/dataset.py b/theseus/classification/datasets/dataset.py index 68f3755..6c715e6 100644 --- a/theseus/classification/datasets/dataset.py +++ b/theseus/classification/datasets/dataset.py @@ -44,7 +44,7 @@ def __getitem__(self, idx: int) -> Dict: return { "input": im, 'target': target, - 'img_name': os.path.basename(image_path), + 'img_name': image_name, 'ori_size': [width, height] } @@ -58,13 +58,11 @@ def collate_fn(self, batch: List): imgs = torch.stack([s['input'] for s in batch]) targets = torch.stack([torch.LongTensor(s['target']['labels']) for s in batch]) img_names = [s['img_name'] for s in batch] - - # if self.mixupcutmix is not None: - # imgs, targets = self.mixupcutmix(imgs, targets.squeeze(1)) - # targets = targets.float() + ori_sizes = [s['ori_size'] for s in batch] return { 'inputs': imgs, 'targets': targets, - 'img_names': img_names + 'img_names': img_names, + 'ori_sizes': ori_sizes, } \ No newline at end of file diff --git a/theseus/classification/datasets/folder_dataset.py b/theseus/classification/datasets/folder_dataset.py index d9fe86d..c97756d 100644 --- a/theseus/classification/datasets/folder_dataset.py +++ b/theseus/classification/datasets/folder_dataset.py @@ -72,7 +72,7 @@ def _load_data(self): folder_name = os.path.join(self.image_dir, label) image_names = os.listdir(folder_name) for image_name in image_names: - self.fns.append([image_name, label]) + self.fns.append([os.path.join(folder_name, image_name), label]) def _calculate_classes_dist(self): """ diff --git a/theseus/classification/trainer/trainer.py b/theseus/classification/trainer/trainer.py index 42f9241..059622d 100644 --- a/theseus/classification/trainer/trainer.py +++ b/theseus/classification/trainer/trainer.py @@ -138,7 +138,7 @@ def visualize_pred(self): grad_cam = CAMWrapper.get_method( name='gradcam', model=self.model.model.get_model(), - model_name=model_name, use_cuda=False) + model_name=model_name, use_cuda=self.model.model.is_cuda) grayscale_cams, label_indices, scores = grad_cam(images, return_probs=True) diff --git a/theseus/utilities/visualization/visualizer.py b/theseus/utilities/visualization/visualizer.py index 2f47800..7697753 100644 --- a/theseus/utilities/visualization/visualizer.py +++ b/theseus/utilities/visualization/visualizer.py @@ -196,8 +196,8 @@ def make_grid(self, batch: List[torch.Tensor], nrow: Optional[int]=None, normali def denormalize(self, image: Union[torch.Tensor, np.ndarray], - std: List[float] = [0.485, 0.456, 0.406], - mean: List[float] = [0.229, 0.224, 0.225]) -> np.ndarray: + mean: List[float] = [0.485, 0.456, 0.406], + std: List[float] = [0.229, 0.224, 0.225]) -> np.ndarray: """ Denormalize an image and return image: `torch.Tensor` or `np.ndarray` From 48553ea730cbfac2ff252e70c6cfed389514f5e7 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 21:42:29 +0700 Subject: [PATCH 07/18] fix gradcam cuda --- theseus/classification/trainer/trainer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/theseus/classification/trainer/trainer.py b/theseus/classification/trainer/trainer.py index 059622d..791d670 100644 --- a/theseus/classification/trainer/trainer.py +++ b/theseus/classification/trainer/trainer.py @@ -138,7 +138,7 @@ def visualize_pred(self): grad_cam = CAMWrapper.get_method( name='gradcam', model=self.model.model.get_model(), - model_name=model_name, use_cuda=self.model.model.is_cuda) + model_name=model_name, use_cuda=next(self.model.parameters()).is_cuda) grayscale_cams, label_indices, scores = grad_cam(images, return_probs=True) From d5c4afb7bc0d98310d8bcbc738195189ae5b2152 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sat, 26 Feb 2022 21:45:59 +0700 Subject: [PATCH 08/18] rename classes --- configs/semantic/pipeline.yaml | 2 +- theseus/semantic/datasets/csv_dataset.py | 4 ++-- theseus/semantic/datasets/dataset.py | 2 +- theseus/semantic/datasets/mosaic_dataset.py | 4 ++-- theseus/semantic/trainer/__init__.py | 4 ++-- theseus/semantic/trainer/trainer.py | 8 ++++---- theseus/utilities/analysis/analyzer.py | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/configs/semantic/pipeline.yaml b/configs/semantic/pipeline.yaml index 64609f5..c2a2c46 100644 --- a/configs/semantic/pipeline.yaml +++ b/configs/semantic/pipeline.yaml @@ -7,7 +7,7 @@ global: pretrained: null resume: null trainer: - name: SegmentationTrainer + name: SemanticTrainer args: num_iterations: 50000 total_accumulate_steps: null diff --git a/theseus/semantic/datasets/csv_dataset.py b/theseus/semantic/datasets/csv_dataset.py index 4f29351..edb0677 100644 --- a/theseus/semantic/datasets/csv_dataset.py +++ b/theseus/semantic/datasets/csv_dataset.py @@ -4,13 +4,13 @@ import numpy as np import pandas as pd from PIL import Image -from .dataset import SegmentationDataset +from .dataset import SemanticDataset from theseus.utilities.loggers.observer import LoggerObserver LOGGER = LoggerObserver.getLogger('main') -class CSVDataset(SegmentationDataset): +class CSVDataset(SemanticDataset): r"""CSVDataset multi-labels segmentation dataset Reads in .csv file with structure below: diff --git a/theseus/semantic/datasets/dataset.py b/theseus/semantic/datasets/dataset.py index 448b9df..f47b566 100644 --- a/theseus/semantic/datasets/dataset.py +++ b/theseus/semantic/datasets/dataset.py @@ -4,7 +4,7 @@ import numpy as np from PIL import Image -class SegmentationDataset(torch.utils.data.Dataset): +class SemanticDataset(torch.utils.data.Dataset): r"""Base dataset for segmentation tasks """ def __init__(self, **kwawrgs): diff --git a/theseus/semantic/datasets/mosaic_dataset.py b/theseus/semantic/datasets/mosaic_dataset.py index ae58bd2..06f07e9 100644 --- a/theseus/semantic/datasets/mosaic_dataset.py +++ b/theseus/semantic/datasets/mosaic_dataset.py @@ -5,14 +5,14 @@ import numpy as np import pandas as pd from PIL import Image -from .dataset import SegmentationDataset +from .dataset import SemanticDataset from theseus.semantic.augmentations.mosaic import Mosaic from theseus.utilities.loggers.observer import LoggerObserver LOGGER = LoggerObserver.getLogger('main') -class CSVDatasetWithMosaic(SegmentationDataset): +class CSVDatasetWithMosaic(SemanticDataset): r"""CSVDataset multi-labels segmentation dataset Reads in .csv file with structure below: diff --git a/theseus/semantic/trainer/__init__.py b/theseus/semantic/trainer/__init__.py index bafc774..590146d 100644 --- a/theseus/semantic/trainer/__init__.py +++ b/theseus/semantic/trainer/__init__.py @@ -1,5 +1,5 @@ from theseus.base.trainer import TRAINER_REGISTRY -from .trainer import SegmentationTrainer +from .trainer import SemanticTrainer -TRAINER_REGISTRY.register(SegmentationTrainer) \ No newline at end of file +TRAINER_REGISTRY.register(SemanticTrainer) \ No newline at end of file diff --git a/theseus/semantic/trainer/trainer.py b/theseus/semantic/trainer/trainer.py index dfd6842..517a893 100644 --- a/theseus/semantic/trainer/trainer.py +++ b/theseus/semantic/trainer/trainer.py @@ -7,11 +7,11 @@ from theseus.utilities.loading import load_state_dict from theseus.utilities.visualization.visualizer import Visualizer from theseus.utilities.visualization.colors import color_list -from theseus.utilities.analysis.analyzer import SegmentationAnalyzer +from theseus.utilities.analysis.analyzer import SemanticAnalyzer from theseus.utilities.loggers.observer import LoggerObserver LOGGER = LoggerObserver.getLogger("main") -class SegmentationTrainer(SupervisedTrainer): +class SemanticTrainer(SupervisedTrainer): """Trainer for segmentation tasks """ @@ -210,7 +210,7 @@ def analyze_gt(self): Perform simple data analysis """ LOGGER.text("Analyzing datasets...", level=LoggerObserver.DEBUG) - analyzer = SegmentationAnalyzer() + analyzer = SemanticAnalyzer() analyzer.add_dataset(self.trainloader.dataset) fig = analyzer.analyze(figsize=(10,5)) LOGGER.log([{ @@ -222,7 +222,7 @@ def analyze_gt(self): } }]) - analyzer = SegmentationAnalyzer() + analyzer = SemanticAnalyzer() analyzer.add_dataset(self.valloader.dataset) fig = analyzer.analyze(figsize=(10,5)) LOGGER.log([{ diff --git a/theseus/utilities/analysis/analyzer.py b/theseus/utilities/analysis/analyzer.py index 1a678e8..a33314b 100644 --- a/theseus/utilities/analysis/analyzer.py +++ b/theseus/utilities/analysis/analyzer.py @@ -91,7 +91,7 @@ def analyze(self, figsize=(8,8)): self.class_dist(axs[1]) return fig -class SegmentationAnalyzer(ClassificationAnalyzer): +class SemanticAnalyzer(ClassificationAnalyzer): def __init__(self): super().__init__() From 7f88fb4e5d4df0b66e09cef15f44f86986e8389a Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 04:24:08 +0700 Subject: [PATCH 09/18] use template collator, sampler for dataloader --- theseus/base/datasets/__init__.py | 4 +- theseus/base/datasets/balance_sampler.py | 52 ------------------------ theseus/base/datasets/collator.py | 13 ++++++ theseus/base/datasets/dataloader.py | 16 ++++++-- theseus/base/datasets/sampler.py | 36 ++++++++++++++++ 5 files changed, 65 insertions(+), 56 deletions(-) delete mode 100644 theseus/base/datasets/balance_sampler.py create mode 100644 theseus/base/datasets/collator.py create mode 100644 theseus/base/datasets/sampler.py diff --git a/theseus/base/datasets/__init__.py b/theseus/base/datasets/__init__.py index 8ab4e7d..813b934 100644 --- a/theseus/base/datasets/__init__.py +++ b/theseus/base/datasets/__init__.py @@ -2,7 +2,8 @@ from torch.utils.data import DataLoader, Dataset from .dataloader import DataLoaderWithCollator from .dataset import ChainDataset, ConcatDataset, ImageDataset -from .balance_sampler import BalanceSampler +from .sampler import BalanceSampler +from .collator import ChainCollateWrapper DATASET_REGISTRY = Registry('DATASET') DATASET_REGISTRY.register(Dataset) @@ -13,4 +14,5 @@ DATALOADER_REGISTRY = Registry('DATALOADER') DATALOADER_REGISTRY.register(DataLoader) DATALOADER_REGISTRY.register(BalanceSampler) +DATALOADER_REGISTRY.register(ChainCollateWrapper) DATALOADER_REGISTRY.register(DataLoaderWithCollator) \ No newline at end of file diff --git a/theseus/base/datasets/balance_sampler.py b/theseus/base/datasets/balance_sampler.py deleted file mode 100644 index 3c8e347..0000000 --- a/theseus/base/datasets/balance_sampler.py +++ /dev/null @@ -1,52 +0,0 @@ -import numpy as np -import torch -from torch.utils.data.sampler import WeightedRandomSampler - -def class_imbalance_sampler(labels: torch.Tensor): - r""" Create balance sampler based on label distribution - - labels: `torch.Tensor` - labels distribution - """ - class_count = torch.bincount(labels.squeeze()) - class_weighting = 1. / class_count - sample_weights = np.array([class_weighting[t] for t in labels.squeeze()]) - sample_weights = torch.from_numpy(sample_weights) - sampler = WeightedRandomSampler(sample_weights, len(sample_weights)) - return sampler - -class BalanceSampler(torch.utils.data.DataLoader): - r"""Balance DataLoader, equally distribute labels in one batch - - dataset: `torch.utils.data.Dataset` - dataset, must have classes_dict and collate_fn attributes - batch_size: `int` - number of samples in one batch - train: `bool` - whether the dataloader is used for training or test - - **Note**: the dataset must have `_calculate_classes_dist()` method - that return `classes_dist` - """ - def __init__(self, - dataset: torch.utils.data.Dataset, - batch_size: int, - **kwargs): - - if hasattr(dataset, 'collate_fn'): - collate_fn = dataset.collate_fn - else: - collate_fn = None - - classes_dist = dataset._calculate_classes_dist() - labels = torch.LongTensor(classes_dist).unsqueeze(1) - sampler = class_imbalance_sampler(labels) - - - super(BalanceSampler, self).__init__( - dataset, - batch_size=batch_size, - collate_fn = collate_fn, - sampler=sampler, - **kwargs - ) \ No newline at end of file diff --git a/theseus/base/datasets/collator.py b/theseus/base/datasets/collator.py new file mode 100644 index 0000000..7a0fa15 --- /dev/null +++ b/theseus/base/datasets/collator.py @@ -0,0 +1,13 @@ +from typing import List + +class ChainCollateWrapper(object): + """Wrapper for list of collate functions + + """ + def __init__(self, pre_collate_fns: List, **kwargs): + self.pre_collate_fns = pre_collate_fns + + def __call__(self, batch): + for fn in self.pre_collate_fns: + batch = fn(batch) + return batch \ No newline at end of file diff --git a/theseus/base/datasets/dataloader.py b/theseus/base/datasets/dataloader.py index 11f24f5..2d7d919 100644 --- a/theseus/base/datasets/dataloader.py +++ b/theseus/base/datasets/dataloader.py @@ -1,9 +1,19 @@ from torch.utils.data import DataLoader +from .collator import ChainCollateWrapper class DataLoaderWithCollator(DataLoader): - def __init__(self, dataset, **kwargs) -> None: + def __init__(self, dataset, collate_fn=None, sampler=None, **kwargs) -> None: self.dataset = dataset + + if collate_fn is not None: + if isinstance(collate_fn, list): + collate_fn.insert(0, dataset.collate_fn) + collate_fn = ChainCollateWrapper(collate_fn) + else: + collate_fn = dataset.collate_fn + super().__init__( dataset=dataset, - collate_fn=dataset.collate_fn, - **kwargs) + collate_fn=collate_fn, + sampler=sampler, + **kwargs) \ No newline at end of file diff --git a/theseus/base/datasets/sampler.py b/theseus/base/datasets/sampler.py new file mode 100644 index 0000000..6cae831 --- /dev/null +++ b/theseus/base/datasets/sampler.py @@ -0,0 +1,36 @@ +import numpy as np +import torch +from torch.utils.data.sampler import WeightedRandomSampler +from theseus.utilities.loggers.observer import LoggerObserver + +LOGGER = LoggerObserver.getLogger('main') + +class BalanceSampler(WeightedRandomSampler): + def __init__(self, dataset: torch.utils.data.Dataset, **kwargs): + r""" Create balance sampler based on label distribution + equally distribute labels in one batch + + dataset: `torch.utils.data.Dataset` + dataset, must have classes_dict and collate_fn attributes + + **Note**: the dataset must have `_calculate_classes_dist()` method + that return `classes_dist` + """ + + labels = self._load_labels(dataset) + class_count = torch.bincount(labels.squeeze()) + class_weighting = 1. / class_count + sample_weights = np.array([class_weighting[t] for t in labels.squeeze()]) + sample_weights = torch.from_numpy(sample_weights) + super().__init__(sample_weights, len(sample_weights)) + + def _load_labels(self, dataset): + op = getattr(dataset, '_calculate_classes_dist', None) + if not callable(op): + LOGGER.text("""Using BalanceSampler but _calculate_classes_dist() + method is missing from the dataset""", LoggerObserver.ERROR) + raise ValueError + + classes_dist = dataset._calculate_classes_dist() + labels = torch.LongTensor(classes_dist).unsqueeze(1) + return labels \ No newline at end of file From 950d18031cf6a4587482af298080640481aad461 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 04:24:22 +0700 Subject: [PATCH 10/18] remove uncessary --- configs/semantic/pipeline.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/configs/semantic/pipeline.yaml b/configs/semantic/pipeline.yaml index c2a2c46..b537f6d 100644 --- a/configs/semantic/pipeline.yaml +++ b/configs/semantic/pipeline.yaml @@ -10,7 +10,6 @@ trainer: name: SemanticTrainer args: num_iterations: 50000 - total_accumulate_steps: null clip_grad: 10.0 print_interval: 20 save_interval: 1000 From fe3cb6151bcad0b2e2403d44e885ebc8191deed0 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 04:52:56 +0700 Subject: [PATCH 11/18] add base collator, change mixup cutmix to collator function --- theseus/base/datasets/__init__.py | 4 +-- theseus/base/datasets/collator.py | 12 +++++++-- theseus/base/datasets/dataloader.py | 2 ++ theseus/classification/datasets/__init__.py | 6 ++++- theseus/classification/datasets/collator.py | 26 +++++++++++++++++++ .../classification/datasets/csv_dataset.py | 13 ---------- theseus/classification/datasets/dataset.py | 2 -- 7 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 theseus/classification/datasets/collator.py diff --git a/theseus/base/datasets/__init__.py b/theseus/base/datasets/__init__.py index 813b934..b69faa1 100644 --- a/theseus/base/datasets/__init__.py +++ b/theseus/base/datasets/__init__.py @@ -3,7 +3,7 @@ from .dataloader import DataLoaderWithCollator from .dataset import ChainDataset, ConcatDataset, ImageDataset from .sampler import BalanceSampler -from .collator import ChainCollateWrapper +from .collator import ChainCollatorWrapper DATASET_REGISTRY = Registry('DATASET') DATASET_REGISTRY.register(Dataset) @@ -14,5 +14,5 @@ DATALOADER_REGISTRY = Registry('DATALOADER') DATALOADER_REGISTRY.register(DataLoader) DATALOADER_REGISTRY.register(BalanceSampler) -DATALOADER_REGISTRY.register(ChainCollateWrapper) +DATALOADER_REGISTRY.register(ChainCollatorWrapper) DATALOADER_REGISTRY.register(DataLoaderWithCollator) \ No newline at end of file diff --git a/theseus/base/datasets/collator.py b/theseus/base/datasets/collator.py index 7a0fa15..6699085 100644 --- a/theseus/base/datasets/collator.py +++ b/theseus/base/datasets/collator.py @@ -1,8 +1,16 @@ from typing import List -class ChainCollateWrapper(object): +class BaseCollator(object): + """Base collator function + """ + def __init__(self, **kwargs) -> None: + pass + + def __call__(self, batch): + return batch + +class ChainCollatorWrapper(BaseCollator): """Wrapper for list of collate functions - """ def __init__(self, pre_collate_fns: List, **kwargs): self.pre_collate_fns = pre_collate_fns diff --git a/theseus/base/datasets/dataloader.py b/theseus/base/datasets/dataloader.py index 2d7d919..8714ef3 100644 --- a/theseus/base/datasets/dataloader.py +++ b/theseus/base/datasets/dataloader.py @@ -9,6 +9,8 @@ def __init__(self, dataset, collate_fn=None, sampler=None, **kwargs) -> None: if isinstance(collate_fn, list): collate_fn.insert(0, dataset.collate_fn) collate_fn = ChainCollateWrapper(collate_fn) + else: + collate_fn = ChainCollateWrapper([dataset.collate_fn, collate_fn]) else: collate_fn = dataset.collate_fn diff --git a/theseus/classification/datasets/__init__.py b/theseus/classification/datasets/__init__.py index 10cc896..da4cc5f 100644 --- a/theseus/classification/datasets/__init__.py +++ b/theseus/classification/datasets/__init__.py @@ -4,4 +4,8 @@ from .folder_dataset import * DATASET_REGISTRY.register(CSVDataset) -DATASET_REGISTRY.register(ImageFolderDataset) \ No newline at end of file +DATASET_REGISTRY.register(ImageFolderDataset) + +from .collator import MixupCutmixCollator + +DATALOADER_REGISTRY.register(MixupCutmixCollator) \ No newline at end of file diff --git a/theseus/classification/datasets/collator.py b/theseus/classification/datasets/collator.py new file mode 100644 index 0000000..164a6b6 --- /dev/null +++ b/theseus/classification/datasets/collator.py @@ -0,0 +1,26 @@ +import torch +from torchvision.transforms import transforms as tf +from theseus.base.datasets.collator import BaseCollator +from theseus.classification.augmentations.custom import RandomMixup, RandomCutmix + + +class MixupCutmixCollator(BaseCollator): + """Apply mixup and cutmix to a batch, temporarily supports classification only + """ + def __init__( + self, + dataset: torch.utils.data.Dataset, + mixup_alpha=0.2, cutmix_alpha=1.0, + weight=[0.5, 0.5], **kwargs) -> None: + + mixup_transforms = [] + mixup_transforms.append(RandomMixup(dataset.num_classes, p=1.0, alpha=mixup_alpha)) + mixup_transforms.append(RandomCutmix(dataset.num_classes, p=1.0, alpha=cutmix_alpha)) + self.mixupcutmix = tf.RandomChoice(mixup_transforms, p=weight) + + def __call__(self, batch): + imgs, targets = self.mixupcutmix( + batch['inputs'], batch['targets'].squeeze(1)) + batch['inputs'] = imgs + batch['targets'] = targets + return batch \ No newline at end of file diff --git a/theseus/classification/datasets/csv_dataset.py b/theseus/classification/datasets/csv_dataset.py index 5e3e460..dbf0532 100644 --- a/theseus/classification/datasets/csv_dataset.py +++ b/theseus/classification/datasets/csv_dataset.py @@ -1,10 +1,6 @@ -import os -import numpy as np import pandas as pd from typing import List, Optional -from torchvision.transforms import transforms as tf -from theseus.classification.augmentations.custom import RandomMixup, RandomCutmix from theseus.utilities.loggers.observer import LoggerObserver from .dataset import ClassificationDataset @@ -46,15 +42,6 @@ def __init__( self.transform = transform self._load_data() - if self.train: - # MixUp and CutMix - mixup_transforms = [] - mixup_transforms.append(RandomMixup(self.num_classes, p=1.0, alpha=0.2)) - mixup_transforms.append(RandomCutmix(self.num_classes, p=1.0, alpha=1.0)) - self.mixupcutmix = tf.RandomChoice(mixup_transforms) - else: - self.mixupcutmix = None - def _load_data(self): """ Read data from csv and load into memory diff --git a/theseus/classification/datasets/dataset.py b/theseus/classification/datasets/dataset.py index 6c715e6..cf8e801 100644 --- a/theseus/classification/datasets/dataset.py +++ b/theseus/classification/datasets/dataset.py @@ -9,11 +9,9 @@ class ClassificationDataset(torch.utils.data.Dataset): def __init__( self, - test: bool = False, **kwargs ): super(ClassificationDataset, self).__init__(**kwargs) - self.train = not (test) self.classes_idx = {} self.classnames = None self.transform = None From d79192988126f2f391c61ae5514705ad97eb15af Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 05:19:49 +0700 Subject: [PATCH 12/18] clean --- theseus/classification/datasets/csv_dataset.py | 3 +-- theseus/classification/datasets/folder_dataset.py | 12 +----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/theseus/classification/datasets/csv_dataset.py b/theseus/classification/datasets/csv_dataset.py index dbf0532..788302e 100644 --- a/theseus/classification/datasets/csv_dataset.py +++ b/theseus/classification/datasets/csv_dataset.py @@ -32,10 +32,9 @@ def __init__( csv_path: str, txt_classnames: str, transform: Optional[List] = None, - test: bool = False, **kwargs ): - super(CSVDataset, self).__init__(test, **kwargs) + super(CSVDataset, self).__init__(**kwargs) self.image_dir = image_dir self.txt_classnames = txt_classnames self.csv_path = csv_path diff --git a/theseus/classification/datasets/folder_dataset.py b/theseus/classification/datasets/folder_dataset.py index c97756d..86d8d98 100644 --- a/theseus/classification/datasets/folder_dataset.py +++ b/theseus/classification/datasets/folder_dataset.py @@ -35,24 +35,14 @@ def __init__( image_dir: str, txt_classnames: str, transform: Optional[List] = None, - test: bool = False, **kwargs ): - super(ImageFolderDataset, self).__init__(test, **kwargs) + super(ImageFolderDataset, self).__init__(**kwargs) self.image_dir = image_dir self.txt_classnames = txt_classnames self.transform = transform self._load_data() - if self.train: - # MixUp and CutMix - mixup_transforms = [] - mixup_transforms.append(RandomMixup(self.num_classes, p=1.0, alpha=0.2)) - mixup_transforms.append(RandomCutmix(self.num_classes, p=1.0, alpha=1.0)) - self.mixupcutmix = tf.RandomChoice(mixup_transforms) - else: - self.mixupcutmix = None - def _load_data(self): """ Read data from csv and load into memory From f3aa2c3e1ad61e8fc3a89d3156c9a4ace6779507 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 05:20:13 +0700 Subject: [PATCH 13/18] fix some bug --- theseus/base/datasets/dataloader.py | 6 +++--- theseus/classification/pipeline.py | 11 ++++++----- theseus/semantic/pipeline.py | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/theseus/base/datasets/dataloader.py b/theseus/base/datasets/dataloader.py index 8714ef3..1069458 100644 --- a/theseus/base/datasets/dataloader.py +++ b/theseus/base/datasets/dataloader.py @@ -1,5 +1,5 @@ from torch.utils.data import DataLoader -from .collator import ChainCollateWrapper +from .collator import ChainCollatorWrapper class DataLoaderWithCollator(DataLoader): def __init__(self, dataset, collate_fn=None, sampler=None, **kwargs) -> None: @@ -8,9 +8,9 @@ def __init__(self, dataset, collate_fn=None, sampler=None, **kwargs) -> None: if collate_fn is not None: if isinstance(collate_fn, list): collate_fn.insert(0, dataset.collate_fn) - collate_fn = ChainCollateWrapper(collate_fn) + collate_fn = ChainCollatorWrapper(collate_fn) else: - collate_fn = ChainCollateWrapper([dataset.collate_fn, collate_fn]) + collate_fn = ChainCollatorWrapper([dataset.collate_fn, collate_fn]) else: collate_fn = dataset.collate_fn diff --git a/theseus/classification/pipeline.py b/theseus/classification/pipeline.py index a217d9a..afa7dfb 100644 --- a/theseus/classification/pipeline.py +++ b/theseus/classification/pipeline.py @@ -68,13 +68,13 @@ def __init__( CLASSNAMES = self.val_dataset.classnames - self.train_dataloader = get_instance( + self.train_dataloader = get_instance_recursively( opt['data']["dataloader"]['train'], registry=DATALOADER_REGISTRY, dataset=self.train_dataset, ) - self.val_dataloader = get_instance( + self.val_dataloader = get_instance_recursively( opt['data']["dataloader"]['val'], registry=DATALOADER_REGISTRY, dataset=self.val_dataset @@ -85,9 +85,10 @@ def __init__( registry=MODEL_REGISTRY, classnames=CLASSNAMES).to(self.device) - criterion = get_instance(self.opt["loss"], registry=LOSS_REGISTRY).to( - self.device - ) + criterion = get_instance_recursively( + self.opt["loss"], + registry=LOSS_REGISTRY).to(self.device) + self.model = ModelWithLoss(model, criterion, self.device) self.metrics = get_instance_recursively( diff --git a/theseus/semantic/pipeline.py b/theseus/semantic/pipeline.py index c266648..7656a3f 100644 --- a/theseus/semantic/pipeline.py +++ b/theseus/semantic/pipeline.py @@ -68,13 +68,13 @@ def __init__( CLASSNAMES = self.val_dataset.classnames - self.train_dataloader = get_instance( + self.train_dataloader = get_instance_recursively( opt['data']["dataloader"]['train'], registry=DATALOADER_REGISTRY, dataset=self.train_dataset, ) - self.val_dataloader = get_instance( + self.val_dataloader = get_instance_recursively( opt['data']["dataloader"]['val'], registry=DATALOADER_REGISTRY, dataset=self.val_dataset From bb63a17f0615684eb9ecbe3635b3b9c67f03aa7d Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 05:20:36 +0700 Subject: [PATCH 14/18] allow ce loss to work with prob --- theseus/classification/losses/ce_loss.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/theseus/classification/losses/ce_loss.py b/theseus/classification/losses/ce_loss.py index 91264da..123ef00 100644 --- a/theseus/classification/losses/ce_loss.py +++ b/theseus/classification/losses/ce_loss.py @@ -12,7 +12,10 @@ def __init__(self, **kwargs): def forward(self, pred: torch.Tensor, batch: Dict[str, Any], device: torch.device): target = batch["targets"].to(device) - loss = self.criterion(pred, target.view(-1).contiguous()) + if pred.shape == target.shape: + loss = self.criterion(pred, target) + else: + loss = self.criterion(pred, target.view(-1).contiguous()) loss_dict = {"CE": loss.item()} return loss, loss_dict From 44fbf7b93938213f7497501527b032e9f82c1f08 Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 05:34:50 +0700 Subject: [PATCH 15/18] fix mixup cutmix collator --- theseus/classification/datasets/collator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/theseus/classification/datasets/collator.py b/theseus/classification/datasets/collator.py index 164a6b6..b432f20 100644 --- a/theseus/classification/datasets/collator.py +++ b/theseus/classification/datasets/collator.py @@ -13,9 +13,12 @@ def __init__( mixup_alpha=0.2, cutmix_alpha=1.0, weight=[0.5, 0.5], **kwargs) -> None: + assert sum(weight) <= 1.0, "Weight should be sum of 1.0" mixup_transforms = [] mixup_transforms.append(RandomMixup(dataset.num_classes, p=1.0, alpha=mixup_alpha)) mixup_transforms.append(RandomCutmix(dataset.num_classes, p=1.0, alpha=cutmix_alpha)) + mixup_transforms.append(tf.Lambda(lambda x: x)) # Identity transform + weight.append(1-sum(weight)) self.mixupcutmix = tf.RandomChoice(mixup_transforms, p=weight) def __call__(self, batch): From 8aec68250d616fb1164b1d21942f0b4fa7f525ce Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 05:53:20 +0700 Subject: [PATCH 16/18] fix mixup cutmix collator --- theseus/classification/datasets/collator.py | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/theseus/classification/datasets/collator.py b/theseus/classification/datasets/collator.py index b432f20..8bec36e 100644 --- a/theseus/classification/datasets/collator.py +++ b/theseus/classification/datasets/collator.py @@ -1,8 +1,7 @@ import torch -from torchvision.transforms import transforms as tf from theseus.base.datasets.collator import BaseCollator from theseus.classification.augmentations.custom import RandomMixup, RandomCutmix - +import numpy as np class MixupCutmixCollator(BaseCollator): """Apply mixup and cutmix to a batch, temporarily supports classification only @@ -14,16 +13,19 @@ def __init__( weight=[0.5, 0.5], **kwargs) -> None: assert sum(weight) <= 1.0, "Weight should be sum of 1.0" - mixup_transforms = [] - mixup_transforms.append(RandomMixup(dataset.num_classes, p=1.0, alpha=mixup_alpha)) - mixup_transforms.append(RandomCutmix(dataset.num_classes, p=1.0, alpha=cutmix_alpha)) - mixup_transforms.append(tf.Lambda(lambda x: x)) # Identity transform - weight.append(1-sum(weight)) - self.mixupcutmix = tf.RandomChoice(mixup_transforms, p=weight) + self.mixup_transforms = [] + self.mixup_transforms.append(RandomMixup(dataset.num_classes, p=1.0, alpha=mixup_alpha)) + self.mixup_transforms.append(RandomCutmix(dataset.num_classes, p=1.0, alpha=cutmix_alpha)) + self.mixup_transforms.append(None) + self.weight = weight + self.weight.append(1.0-sum(weight)) def __call__(self, batch): - imgs, targets = self.mixupcutmix( - batch['inputs'], batch['targets'].squeeze(1)) - batch['inputs'] = imgs - batch['targets'] = targets + + transform = np.random.choice(self.mixup_transforms, p=self.weight) + if transform is not None: + imgs, targets = transform( + batch['inputs'], batch['targets'].squeeze(1)) + batch['inputs'] = imgs + batch['targets'] = targets return batch \ No newline at end of file From a13d4b0f5053b7b91515ba254edd4817fa0eb72b Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 09:05:35 +0700 Subject: [PATCH 17/18] update requirements --- requirements.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 217e148..44a16c7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,13 @@ -albumentations +albumentations>=1.1.0 pyyaml>=5.1 webcolors tensorboard tqdm -ensemble-boxes -timm +timm>=0.5.4 omegaconf pycocotools -gdown==4.3.0 +gdown>=4.4.0 grad-cam tabulate segmentation-models-pytorch +opencv-python-headless==4.1.2.30 \ No newline at end of file From a58bfb8944c41b8b1f3e99c68fa7017a0b1288ff Mon Sep 17 00:00:00 2001 From: kaylode Date: Sun, 27 Feb 2022 09:14:36 +0700 Subject: [PATCH 18/18] change default sample classification config --- configs/classification/pipeline.yaml | 86 +++++++++++++--------------- 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/configs/classification/pipeline.yaml b/configs/classification/pipeline.yaml index 4478dca..6ff9d39 100644 --- a/configs/classification/pipeline.yaml +++ b/configs/classification/pipeline.yaml @@ -1,13 +1,11 @@ global: - debug: True + debug: true cfg_transform: configs/classification/transform.yaml - save_dir: runs + save_dir: /content/main/runs device: cuda:0 - use_fp16: True + use_fp16: true pretrained: null resume: null - - trainer: name: ClassificationTrainer args: @@ -17,41 +15,34 @@ trainer: save_interval: 1000 evaluate_interval : 1 visualize_when_val : True - - model: name: BaseTimmModel args: name: convnext_small - from_pretrained: True + from_pretrained: true num_classes: 2 - - loss: - name: SmoothCELoss - args: - smoothing: 0.1 - + name: CELoss metrics: - - name: Accuracy - args: - - name: BalancedAccuracyMetric - args: - - name: F1ScoreMetric - args: - average: weighted - - name: ConfusionMatrix - args: - - name: ErrorCases - args: - +- name: Accuracy + args: null +- name: BalancedAccuracyMetric + args: null +- name: F1ScoreMetric + args: + average: weighted +- name: ConfusionMatrix + args: null +- name: ErrorCases + args: optimizer: name: AdamW args: - lr: 0.001 # [adam: 1e-3 | sgd: 1e-2] + lr: 0.001 weight_decay: 0.0005 - betas: [0.937, 0.999] - + betas: + - 0.937 + - 0.999 scheduler: name: SchedulerWrapper args: @@ -59,35 +50,38 @@ scheduler: t_initial: 7 t_mul: 0.9 eta_mul: 0.9 - eta_min: 0.000001 - - -data: + eta_min: 1.0e-06 +data: dataset: train: name: ImageFolderDataset args: - image_dir: data/dog-vs-cats/train + image_dir: /content/main/data/dog-vs-cats/train txt_classnames: configs/classification/classes.txt - test: False - val: name: ImageFolderDataset args: - image_dir: data/dog-vs-cats/val + image_dir: /content/main/data/dog-vs-cats/val txt_classnames: configs/classification/classes.txt - test: True - dataloader: train: - name: BalanceSampler + name: DataLoaderWithCollator args: - batch_size: 64 - drop_last: True - shuffle: False + batch_size: 32 + drop_last: true + shuffle: false + collate_fn: + name: MixupCutmixCollator + args: + mixup_alpha: 0.4 + cutmix_alpha: 1.0 + weight: [0.2, 0.2] + sampler: + name: BalanceSampler + args: val: name: DataLoaderWithCollator args: - batch_size: 64 - drop_last: False - shuffle: True \ No newline at end of file + batch_size: 32 + drop_last: false + shuffle: true \ No newline at end of file