seamseg/utils/misc.py (89 lines of code) (raw):

# Copyright (c) Facebook, Inc. and its affiliates. import io from collections import OrderedDict from functools import partial import torch import torch.distributed as dist import torch.nn as nn from inplace_abn import InPlaceABN, InPlaceABNSync, ABN from seamseg.modules.misc import ActivatedAffine, ActivatedGroupNorm from . import scheduler as lr_scheduler NORM_LAYERS = [ABN, nn.BatchNorm1d, nn.BatchNorm2d, nn.BatchNorm3d, nn.GroupNorm] OTHER_LAYERS = [nn.Linear, nn.Conv1d, nn.Conv2d, nn.Conv3d, nn.ConvTranspose1d, nn.ConvTranspose2d, nn.ConvTranspose3d] class Empty(Exception): """Exception to facilitate handling of empty predictions, annotations etc.""" pass def try_index(scalar_or_list, i): try: return scalar_or_list[i] except TypeError: return scalar_or_list def config_to_string(config): with io.StringIO() as sio: config.write(sio) config_str = sio.getvalue() return config_str def scheduler_from_config(scheduler_config, optimizer, epoch_length): assert scheduler_config["type"] in ("linear", "step", "poly", "multistep") params = scheduler_config.getstruct("params") if scheduler_config["type"] == "linear": if scheduler_config["update_mode"] == "batch": count = epoch_length * scheduler_config.getint("epochs") else: count = scheduler_config.getint("epochs") beta = float(params["from"]) alpha = float(params["to"] - beta) / count scheduler = lr_scheduler.LambdaLR(optimizer, lambda it: it * alpha + beta) elif scheduler_config["type"] == "step": scheduler = lr_scheduler.StepLR(optimizer, params["step_size"], params["gamma"]) elif scheduler_config["type"] == "poly": if scheduler_config["update_mode"] == "batch": count = epoch_length * scheduler_config.getint("epochs") else: count = scheduler_config.getint("epochs") scheduler = lr_scheduler.LambdaLR(optimizer, lambda it: (1 - float(it) / count) ** params["gamma"]) elif scheduler_config["type"] == "multistep": scheduler = lr_scheduler.MultiStepLR(optimizer, params["milestones"], params["gamma"]) else: raise ValueError("Unrecognized scheduler type {}, valid options: 'linear', 'step', 'poly', 'multistep'" .format(scheduler_config["type"])) if scheduler_config.getint("burn_in_steps") != 0: scheduler = lr_scheduler.BurnInLR(scheduler, scheduler_config.getint("burn_in_steps"), scheduler_config.getfloat("burn_in_start")) return scheduler def norm_act_from_config(body_config): """Make normalization + activation function from configuration Available normalization modes are: - `bn`: Standard In-Place Batch Normalization - `syncbn`: Synchronized In-Place Batch Normalization - `syncbn+bn`: Synchronized In-Place Batch Normalization in the "static" part of the network, Standard In-Place Batch Normalization in the "dynamic" parts - `gn`: Group Normalization - `syncbn+gn`: Synchronized In-Place Batch Normalization in the "static" part of the network, Group Normalization in the "dynamic" parts - `off`: No normalization (preserve scale and bias parameters) The "static" part of the network includes the backbone, FPN and semantic segmentation components, while the "dynamic" part of the network includes the RPN, detection and instance segmentation components. Note that this distinction is due to historical reasons and for back-compatibility with the CVPR2019 pre-trained models. Parameters ---------- body_config Configuration object containing the following fields: `normalization_mode`, `activation`, `activation_slope` and `gn_groups` Returns ------- norm_act_static : callable Function that returns norm_act modules for the static parts of the network norm_act_dynamic : callable Function that returns norm_act modules for the dynamic parts of the network """ mode = body_config["normalization_mode"] activation = body_config["activation"] slope = body_config.getfloat("activation_slope") groups = body_config.getint("gn_groups") if mode == "bn": norm_act_static = norm_act_dynamic = partial(InPlaceABN, activation=activation, activation_param=slope) elif mode == "syncbn": norm_act_static = norm_act_dynamic = partial(InPlaceABNSync, activation=activation, activation_param=slope) elif mode == "syncbn+bn": norm_act_static = partial(InPlaceABNSync, activation=activation, activation_param=slope) norm_act_dynamic = partial(InPlaceABN, activation=activation, activation_param=slope) elif mode == "gn": norm_act_static = norm_act_dynamic = partial( ActivatedGroupNorm, num_groups=groups, activation=activation, activation_param=slope) elif mode == "syncbn+gn": norm_act_static = partial(InPlaceABNSync, activation=activation, activation_param=slope) norm_act_dynamic = partial(ActivatedGroupNorm, num_groups=groups, activation=activation, activation_param=slope) elif mode == "off": norm_act_static = norm_act_dynamic = partial(ActivatedAffine, activation=activation, activation_param=slope) else: raise ValueError("Unrecognized normalization_mode {}, valid options: 'bn', 'syncbn', 'syncbn+bn', 'gn', " "'syncbn+gn', 'off'".format(mode)) return norm_act_static, norm_act_dynamic def freeze_params(module): """Freeze all parameters of the given module""" for p in module.parameters(): p.requires_grad_(False) def all_reduce_losses(losses): """Coalesced mean all reduce over a dictionary of 0-dimensional tensors""" names, values = [], [] for k, v in losses.items(): names.append(k) values.append(v) # Peform the actual coalesced all_reduce values = torch.cat([v.view(1) for v in values], dim=0) dist.all_reduce(values, dist.ReduceOp.SUM) values.div_(dist.get_world_size()) values = torch.chunk(values, values.size(0), dim=0) # Reconstruct the dictionary return OrderedDict((k, v.view(())) for k, v in zip(names, values))