Source code for tensormonk.detection.config

""" TensorMONK's :: detection :: CONFIG """

__all__ = ["CONFIG"]

import torch
from collections import namedtuple
from .. import layers, loss


[docs]class CONFIG: r"""CONFIG is used to configure all the options for object detection tasks. Example: Assume an object detection model that is trained on 320x320 images to detect dogs and cats. .. code-block:: python import tensormonk config = tensormonk.detection.CONFIG("mnas_bifpn_dogs_cats") # Define input size config.t_size = (1, 3, 320, 320) # Use pretrained MNAS model as base network config.base_network = "mnas_100" config.base_network_pretrained = True # Given the above config and input size of (4, 3, 320, 320), base # network will return a tuple of tensor's of shape # ((4, 24, 80, 80), (4, 40, 40, 40), (4, 96, 20, 20), (4, 320, 10, 10)) # By using base_network_forced_stride, base network will return a tuple # of tensor's of shape # ((4, 24, 40, 40), (4, 40, 20, 20), (4, 96, 10, 10), (4, 320, 5, 5)). config.base_network_forced_stride = True # All the ouputs from base network are encoded to have constant depth # (96) using a 1x1 convolution per level. # Essentially, the base network output with tensor shapes # ((4, 24, 40, 40), (4, 40, 20, 20), (4, 96, 10, 10) and (4, 320,5, 5)) # is converted to # ((4, 96, 40, 40), (4, 96, 20, 20), (4, 96, 10, 10) and (4, 96, 5, 5)) config.encoding_depth = 96 # Define a body network with 4 "bifpn" layers. config.body_network = "bifpn" config.body_network_depth = 4 # Define number of labels (labels to detect + background) config.n_label = 2 + 1 config.label_loss_fn = tensormonk.loss.LabelLoss config.label_loss_kwargs = { "method": "ce_with_negative_mining", "pos_to_neg_ratio": 1 / 3., "reduction": "mean"} # Define loss function and encoding for bounding box config.is_boxes = True config.boxes_loss_fn = tensormonk.loss.BoxesLoss config.boxes_loss_kwargs = { "method": "smooth_l1", "reduction": "mean"} config.boxes_encode_format = "normalized_offset" # Enable objectness and disable centerness config.is_point = False config.is_objectness = True config.is_centerness = False # Define encode_iou # minimum iou required for a prior to set a location as non background config.encode_iou = 0.5 # Define detect_iou - iou_threshold for non-maximal suppression config.detect_iou = 0.2 # Define score_threshold - minimum score required to label an anchor as # non background during inference. config.score_threshold = 0.46 # Define ignore_base - As a pretrained base network is used in this # example, disable the gradients to reach base_network for 5000 # iterations. config.ignore_base = 5000 # Define anchors config.anchors_per_layer = ( # anchors at 40x40 (config.an_anchor(32, 32), config.an_anchor(46, 46)), # anchors at 20x20 (config.an_anchor(64, 64), config.an_anchor(90, 90)), # anchors at 10x10 (config.an_anchor(128, 128), config.an_anchor(180, 180)), # anchors at 5x5 (config.an_anchor(256, 256), config.an_anchor(320, 320)) print(config) """ def __init__(self, name: str): self.name = name # ------------------------------------------------------------------- # # network options self._base_network_pretrained = False self._base_network = "mnas_050" self._base_network_options = ("mnas_050", "mnas_100", "mobilev2") self._base_network_forced_stride = False self._base_extension = 0 # not enabled self._body_network = "bifpn" self._body_network_options = ("nofpn", "fpn", "pafpn", "bifpn") self._body_network_depth = 2 # Refer tensormonk.layers.FeatureFusion.METHODS for fusion options self._body_fpn_fusion = "softmax" self._anchors_per_layer = None self._body_network_return_responses = False # ------------------------------------------------------------------- # # input size and feature encoding size self._t_size = None # BCHW - Ex: (1, 3, 256, 256) self._encoding_depth = None # ------------------------------------------------------------------- # self._single_classifier_head = False # ------------------------------------------------------------------- # # info on labels self._n_label = None # loss function - refer tensormonk.loss.LabelLoss self._label_loss_fn = loss.LabelLoss self._label_loss_kwargs = {} # ------------------------------------------------------------------- # # info on boxes self._is_boxes = True # loss function - refer tensormonk.loss.BoxesLoss self._boxes_loss_fn = loss.BoxesLoss self._boxes_loss_kwargs = {} # target boxes transformation format and prediction boxes format self._boxes_encode_format = "normalized_gcxcywh" self._boxes_encode_format_options = ( "normalized_offset", "normalized_gcxcywh") # ------------------------------------------------------------------- # self._is_point = False self._n_point = None # loss function - refer tensormonk.loss.PointLoss self._point_loss_fn = loss.PointLoss self._point_loss_kwargs = {} # target points transformation format and prediction points format self._point_encode_format = "normalized_xy_offsets" self._point_encode_format_options = ( "normalized_xy_offsets") # ------------------------------------------------------------------- # # FCOS self._is_centerness = False # YoloV3 - Intersection (of pixel and any box) over area of the pixel self._is_objectness = False self._hard_encode = False self._encode_iou = 0.5 self._encode_iou_max_background = self.encode_iou - 0.1 self._detect_iou = 0.2 self._score_threshold = 0.1 # ------------------------------------------------------------------- # self._boxes_encode_var1 = 0.1 self._boxes_encode_var2 = 0.2 self._point_encode_var = 0.5 self._is_pad = True self._anchors_per_layer = None self._an_anchor = namedtuple("anchor", ("w", "h", "offset")) self._ignore_base = 0 @property def base_network(self): r"""Base network for anchor detector (str/nn.Module). Args: value (str, optional): Current options are :obj:`"mnas_050"`, :obj:`"mnas_100"`, and :obj:`"mobilev2"`. See :class:`tensormonk.architectures.MNAS`, and :class:`tensormonk.architectures.MobileNetV2` for more information. Also accept a custom network. default = :obj:`"mnas_050"`. Example custom network: .. code-block:: python import torch import torch.nn as nn import torch.nn.functional as F import tensormonk class Tiny(torch.nn.Module): def __init__(self, **kwargs): super(Tiny, self).__init__() self._layer_0 = torch.nn.Sequential( nn.Conv2d(3, 16, 3, stride=2, padding=1), nn.PReLU(), nn.Conv2d(16, 16, 3, stride=2, padding=1), nn.PReLU()) self._layer_1 = torch.nn.Sequential( nn.Conv2d(16, 24, 3, stride=2, padding=1), nn.PReLU(), nn.Conv2d(24, 24, 3, stride=1, padding=1), nn.PReLU()) self._layer_2 = torch.nn.Sequential( nn.Conv2d(24, 32, 3, stride=2, padding=1), nn.PReLU(), nn.Conv2d(32, 32, 3, stride=1, padding=1), nn.PReLU()) self._layer_3 = torch.nn.Sequential( nn.Conv2d(32, 48, 3, stride=2, padding=1), nn.PReLU(), nn.Conv2d(48, 48, 3, stride=1, padding=1), nn.PReLU()) def forward(self, tensor: torch.Tensor): x0 = self._layer_0(tensor) x1 = self._layer_1(x0) x2 = self._layer_2(x1) x3 = self._layer_3(x2) return (x1, x2, x3) config = tensormonk.detection.CONFIG("tiny") config.base_network = Tiny """ return self._base_network @base_network.setter def base_network(self, value): assert value in self._base_network_options or \ value.__base__ == torch.nn.Module value = value.lower() if isinstance(value, str) else value self._base_network = value @property def base_network_pretrained(self): r"""Used when base_network is :obj:`"mnas_050"`, :obj:`"mnas_100"`, or :obj:`"mobilev2"` to load pretrained weights. Args: value (bool, optional): default = :obj:`True` """ return self._base_network_pretrained @base_network_pretrained.setter def base_network_pretrained(self, value): assert isinstance(value, bool) self._base_network_pretrained = value @property def base_network_forced_stride(self): r"""Used when base_network is :obj:`"mnas_050"`, :obj:`"mnas_100"`, or :obj:`"mobilev2"` to add an additional stride in the second or third convolution layer. Args: value (bool, optional): default = :obj:`False` """ return self._base_network_forced_stride @base_network_forced_stride.setter def base_network_forced_stride(self, value): assert isinstance(value, bool) self._base_network_forced_stride = value @property def base_extension(self): return self._base_extension @base_extension.setter def base_extension(self, value): assert isinstance(value, int) and value >= 0 self._base_extension = value @property def body_network(self): r"""Body network options are Args: value (str, optional): :obj:`"bifpn"`, :obj:`"fpn"`, :obj:`"nofpn"`, and :obj:`"pafpn"`. default = :obj:`"bifpn"`. :obj:`"bifpn"` = :class:`tensormonk.detection.BiFPNLayer` :obj:`"fpn"` = :class:`tensormonk.detection.FPNLayer` :obj:`"nofpn"` = :class:`tensormonk.detection.NoFPNLayer` :obj:`"pafpn"` = :class:`tensormonk.detection.PAFPNLayer` """ return self._body_network @body_network.setter def body_network(self, value): if isinstance(value, str) and value.startswith("anchor_"): value = value.split("_")[-1] assert value in self._body_network_options self._body_network = value.lower() @property def body_network_depth(self): r"""Number of FPN or NoFPN layers to stack. Below is an example config of body network that has 6 :obj:`"bifpn"` layers: .. code-block:: python import tensormonk config = tensormonk.detection.CONFIG("mnas_bifpn") config.base_network = "mnas_050" config.encoding_depth = 96 config.body_network = "bifpn" config.body_network_depth = 6 Args: value (int, optional): default = :obj:`2`. """ return self._body_network_depth @body_network_depth.setter def body_network_depth(self, value): assert isinstance(self._body_network_depth, int) assert self._body_network_depth >= 1 self._body_network_depth = value @property def body_fpn_fusion(self): r"""Fusion scheme used by FPN and NoFPN. See :class:`tensormonk.layers.FeatureFusion` and :class:`tensormonk.detection.Block` for more information. Args: value (str, optional): default = :obj:`"softmax"`. See :class:`tensormonk.layers.FeatureFusion` for all available options. """ return self._body_fpn_fusion @body_fpn_fusion.setter def body_fpn_fusion(self, value): assert isinstance(self._body_fpn_fusion, str) assert self._body_fpn_fusion in layers.FeatureFusion.METHODS self._body_fpn_fusion = value @property def body_network_return_responses(self): r"""When True, compute_loss in :class:`tensormonk.detection.AnchorDetector` also return's the responses from body network. Args: value (bool, optional): default = :obj:`False`. """ return self._body_network_return_responses @body_network_return_responses.setter def body_network_return_responses(self, value): assert isinstance(self._body_network_return_responses, bool) self._body_network_return_responses = value @property def t_size(self): r"""Input tensor size in BCHW. Also, used to precompute centers, anchor_wh and pix2pix_delta. Args: value (tuple, required): Input tensor shape in BCHW (None/any integer >0, channels, height, width). """ return self._t_size @t_size.setter def t_size(self, value): assert isinstance(value, (list, tuple)) and len(value) == 4 value = list(value) value[0] = 1 self._t_size = tuple(value) @property def encoding_depth(self): r"""Encoding depth to convert all the base network outputs to a constant depth in order to enable FPN and NoFPN layers. Args: value (int, required): See the example in :class:`tensormonk.detection.AnchorDetector` for more information. """ return self._encoding_depth @encoding_depth.setter def encoding_depth(self, value): assert isinstance(value, int) and value >= 8 self._encoding_depth = value @property def single_classifier_head(self): r"""Flag to enable single classifier head in :class:`tensormonk.detection.Classifier`. Args: value (bool, optional): default = :obj:`False`. See :class:`tensormonk.detection.Classifier` for more information. """ return self._single_classifier_head @single_classifier_head.setter def single_classifier_head(self, value): assert isinstance(value, bool) self._single_classifier_head = value @property def n_label(self): r"""Number of labels (including background) to predict. Args: value (int, required): Must be >= 2. """ return self._n_label @n_label.setter def n_label(self, value): assert isinstance(value, int) and value > 1 self._n_label = value @property def label_loss_fn(self): r"""Loss function to compute loss given p_label and t_label. This function is initialized in :class:`tensormonk.detection.AnchorDetector`. A custom loss function can be initialized as long as it is a nn.Module and all the label_loss_kwargs are set. Args: value (nn.Module, optional): default = :class:`tensormonk.loss.LabelLoss` """ return self._label_loss_fn @label_loss_fn.setter def label_loss_fn(self, value): assert value.__base__ == torch.nn.Module self._label_loss_fn = value @property def label_loss_kwargs(self): r"""Dictonary of parameters required to initialize config.label_loss_fn function. Args: value (dict, required): See :class:`tensormonk.loss.LabelLoss` for more information if config.label_loss_fn is :class:`tensormonk.loss.LabelLoss`. """ return self._label_loss_kwargs @label_loss_kwargs.setter def label_loss_kwargs(self, value): assert isinstance(value, dict) if self.label_loss_fn == loss.LabelLoss: assert all([x in loss.LabelLoss.KWARGS for x in value.keys()]) assert value["method"] in loss.LabelLoss.METHODS self._label_loss_kwargs = value @property def is_boxes(self): r"""Flag to enable bounding box detection. Not used in current implementation (default = :obj:`"True"`), will get updated with inclusion of segmentation task. Args: value (bool, optional): default = :obj:`"True"` """ return self._is_boxes @is_boxes.setter def is_boxes(self, value): assert isinstance(value, bool) self._is_boxes = value @property def boxes_loss_fn(self): r"""Loss function to compute loss given p_boxes and t_boxes. This function is initialized in :class:`tensormonk.detection.AnchorDetector`. A custom loss function can be initialized as long as it is a nn.Module and all the boxes_loss_kwargs are set. Args: value (nn.Module, optional): default = :class:`tensormonk.loss.BoxesLoss` """ return self._boxes_loss_fn @boxes_loss_fn.setter def boxes_loss_fn(self, value): assert value.__base__ == torch.nn.Module self._boxes_loss_fn = value @property def boxes_loss_kwargs(self): r"""Dictonary of parameters required to initialize config.boxes_loss_fn function. Args: value (dict, required): See :class:`tensormonk.loss.BoxesLoss` for more information if config.boxes_loss_fn is :class:`tensormonk.loss.BoxesLoss`. """ return self._boxes_loss_kwargs @boxes_loss_kwargs.setter def boxes_loss_kwargs(self, value): assert isinstance(value, dict) assert all([x in loss.BoxesLoss.KWARGS for x in value.keys()]) assert value["method"] in loss.BoxesLoss.METHODS self._boxes_loss_kwargs = value @property def boxes_encode_format(self): r"""Boxes encoding format. See :class:`tensormonk.detection.ObjectUtils` for more options. Note: IOU based loss functions require "normalized_offset" Args: value (str, optional): Options "normalized_gcxcywh" or "normalized_offset". default = :obj:`"normalized_gcxcywh"`. """ return self._boxes_encode_format @boxes_encode_format.setter def boxes_encode_format(self, value): assert value in self._boxes_encode_format_options assert self.boxes_loss_kwargs is not None, \ "boxes_loss_kwargs must be set before boxes_encode_format" if "iou" in self.boxes_loss_kwargs["method"]: assert "normalized_offset" in value, \ "iou based loss requires boxes_encode_format=normalized_offset" self._boxes_encode_format = value @property def is_point(self): r"""Flag to enable point localization within a bounding box. Args: value (bool, optional): default = :obj:`"False"` """ return self._is_point @is_point.setter def is_point(self, value): assert isinstance(value, bool) self._is_point = value @property def n_point(self): r"""Number of points to detect in an object. This is relavent to tasks like identifying body parts/joints in person detection, facial landmarks in face detection, etc. Args: value (int, optional): Must be >= 1 and set when config.is_point is True. """ return self._n_point @n_point.setter def n_point(self, value): assert isinstance(value, int) self._n_point = value @property def point_loss_fn(self): r"""Loss function to compute loss given p_point and t_point. This function is initialized in :class:`tensormonk.detection.AnchorDetector`. A custom loss function can be initialized as long as it is a nn.Module and all the point_loss_kwargs are set. Args: value (nn.Module, optional): default = :class:`tensormonk.loss.PointLoss` """ return self._point_loss_fn @point_loss_fn.setter def point_loss_fn(self, value): assert value.__base__ == torch.nn.Module self._point_loss_fn = value @property def point_loss_kwargs(self): r"""Dictonary of parameters required to initialize config.point_loss_fn function. Args: value (dict, required): See :class:`tensormonk.loss.PointLoss` for more information if config.point_loss_fn is :class:`tensormonk.loss.PointLoss`. """ return self._point_loss_kwargs @point_loss_kwargs.setter def point_loss_kwargs(self, value): assert isinstance(value, dict) assert all([x in loss.PointLoss.KWARGS for x in value.keys()]) assert value["method"] in loss.PointLoss.METHODS self._point_loss_kwargs = value @property def point_encode_format(self): r"""Point encoding format. See :class:`tensormonk.detection.ObjectUtils` for more information. Args: value (str, optional): default = :obj:`"normalized_xy_offsets"`. """ return self._point_encode_format @point_encode_format.setter def point_encode_format(self, value): assert value in self._point_encode_format_options self._point_encode_format = value @property def is_centerness(self): r"""Enables centerness as defined in `FCOS: Fully Convolutional One-Stage Object Detection <https://arxiv.org/pdf/1904.01355.pdf>`_ Args: value (bool, optional): default = :obj:`False`. """ return self._is_centerness @is_centerness.setter def is_centerness(self, value): assert isinstance(value, bool) self._is_centerness = value @property def is_objectness(self): r"""Enables centerness as defined in `YOLOv3: An Incremental Improvement <https://pjreddie.com/media/files/papers/YOLOv3.pdf>`_. Args: value (bool, optional): default = :obj:`False`. """ return self._is_objectness @is_objectness.setter def is_objectness(self, value): assert isinstance(value, bool) self._is_objectness = value @property def hard_encode(self): r"""Eliminates boxes with centers that are not within pix2pix_delta. Args: value (bool, optional): default = :obj:`False`. """ return self._hard_encode @hard_encode.setter def hard_encode(self, value): assert isinstance(value, bool) self._hard_encode = value @property def encode_iou(self): r"""IOU required by a box to map it to an anchor. Args: value (float, optional): default = :obj:`0.5`. """ return self._encode_iou @encode_iou.setter def encode_iou(self, value): assert isinstance(value, float) self._encode_iou = value @property def encode_iou_max_background(self): r"""IOU below which is considered as background. Args: value (float, optional): default = :obj:`0.5`. """ return self._encode_iou_max_background @encode_iou_max_background.setter def encode_iou_max_background(self, value): assert isinstance(value, float) self._encode_iou_max_background = value @property def detect_iou(self): r"""IOU used to filter boxes during detection. Args: value (float, optional): default = :obj:`0.5`. """ return self._detect_iou @detect_iou.setter def detect_iou(self, value): assert isinstance(value, float) self._detect_iou = value @property def score_threshold(self): r"""Score threshold used to filter boxes during detection. Args: value (float, optional): default = :obj:`0.5`. """ return self._score_threshold @score_threshold.setter def score_threshold(self, value): assert isinstance(value, float) self._score_threshold = value @property def boxes_encode_var1(self): r"""Variance used to encode boxes - `SSD: Single Shot MultiBox Detector <https://arxiv.org/pdf/1512.02325.pdf>`_. Args: value (float, optional): default = :obj:`0.1`. """ return self._boxes_encode_var1 @boxes_encode_var1.setter def boxes_encode_var1(self, value): assert isinstance(value, float) self._boxes_encode_var1 = value @property def boxes_encode_var2(self): r"""Variance used to encode boxes - `SSD: Single Shot MultiBox Detector <https://arxiv.org/pdf/1512.02325.pdf>`_. Args: value (float, optional): default = :obj:`0.2`. """ return self._boxes_encode_var2 @boxes_encode_var2.setter def boxes_encode_var2(self, value): assert isinstance(value, float) self._boxes_encode_var2 = value @property def point_encode_var(self): r"""SSD normalization variance is used for points. Args: value (float, optional): default = :obj:`0.5`. """ return self._point_encode_var @point_encode_var.setter def point_encode_var(self, value): assert isinstance(value, float) self._point_encode_var = value @property def is_pad(self): r"""Used for computing centers. Args: value (bool, optional): default = :obj:`True`. """ return self._is_pad @is_pad.setter def is_pad(self, value): assert isinstance(value, bool) self._is_pad = value @property def ignore_base(self): r"""Gradients are not propagated to base network for ignore_base iterations. Used when a pretrained network is used to tune parameters. Args: value (int, optional): default = :obj:`0`. """ return self._ignore_base @ignore_base.setter def ignore_base(self, value): assert isinstance(value, int) self._ignore_base = value @property def anchors_per_layer(self): r"""All anchors per layer. A list/tuple of list/tuple of config.an_anchor's. Args: value (int, optional): default = :obj:`0`. """ return self._anchors_per_layer @anchors_per_layer.setter def anchors_per_layer(self, value): assert isinstance(value, (list, tuple)) for x in value: assert isinstance(x, (list, tuple)) for y in x: assert isinstance(y, self._an_anchor) self._anchors_per_layer = value
[docs] def an_anchor(self, w: int, h: int, offset: int = 0): r"""A namedtuple with w and h of anchor.""" return self._an_anchor(w, h, offset)
def __repr__(self): msg = ["CONFIG :: {}".format(self.name)] msg += ["Base = {}".format( self.base_network if isinstance(self.base_network, str) else self.base_network.__name__)] msg += ["Body = {}".format(self.body_network)] msg += ["t_size = {}".format("x".join(map(str, self.t_size)))] msg += ["n_label = {}".format(self.n_label)] msg += ["LabelLoss = {}".format(self.label_loss_kwargs["method"])] if self.is_boxes: msg += ["BoxesLoss = {}".format(self.boxes_loss_kwargs["method"])] if self.is_point: msg += ["PointLoss = {}".format(self.point_loss_kwargs["method"])] msg += ["Objectness is " + (" ON" if self.is_objectness else "OFF") + " & Centerness is " + (" ON" if self.is_centerness else "OFF")] return "\n\t".join(msg)