From 18b3db859a493d7b425b9388c9f95ce2ca2a4ee8 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 29 Apr 2020 20:30:16 +0300 Subject: [PATCH 01/54] upd version --- pytorch_tools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_tools/__init__.py b/pytorch_tools/__init__.py index 85f6dc8..b12314a 100644 --- a/pytorch_tools/__init__.py +++ b/pytorch_tools/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.1.4" +__version__ = "0.1.5" from . import fit_wrapper from . import losses From 1ed4dafddd42818b1ebd272904719e4e112d6854 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 6 May 2020 18:15:59 +0300 Subject: [PATCH 02/54] ... --- pytorch_tools/__init__.py | 1 + pytorch_tools/detection_models/__init__.py | 1 + pytorch_tools/detection_models/retinanet.py | 26 ++- pytorch_tools/utils/box.py | 168 ++++++++++++++++++++ 4 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 pytorch_tools/utils/box.py diff --git a/pytorch_tools/__init__.py b/pytorch_tools/__init__.py index b12314a..651659b 100644 --- a/pytorch_tools/__init__.py +++ b/pytorch_tools/__init__.py @@ -9,3 +9,4 @@ from . import segmentation_models from . import tta_wrapper from . import utils +from . import detection_models diff --git a/pytorch_tools/detection_models/__init__.py b/pytorch_tools/detection_models/__init__.py index e69de29..7c7fc59 100644 --- a/pytorch_tools/detection_models/__init__.py +++ b/pytorch_tools/detection_models/__init__.py @@ -0,0 +1 @@ +from .retinanet import RetinaNet \ No newline at end of file diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index 5ebc360..e64a12c 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -1,6 +1,6 @@ -# import torch +import torch import torch.nn as nn -import torch.nn.functional as F +# import torch.nn.functional as F from pytorch_tools.modules.fpn import FPN # from pytorch_tools.modules.bifpn import BiFPN from pytorch_tools.modules import bn_from_name @@ -43,7 +43,8 @@ def make_head(out_size): for _ in range(4): # some implementations don't use BN here but I think it's needed # TODO: test how it affects results - layers += [nn.Conv2d(256, 256, 3, padding=1), norm_layer(256, activation=norm_act)] + # upd. removed norm_layer. maybe change to group_norm later + layers += [nn.Conv2d(256, 256, 3, padding=1)] # norm_layer(256, activation=norm_act) # layers += [nn.Conv2d(256, 256, 3, padding=1), nn.ReLU()] layers += [nn.Conv2d(256, out_size, 3, padding=1)] @@ -55,19 +56,28 @@ def make_head(out_size): self.cls_head = make_head(num_classes * anchors) self.box_head = make_head(4 * anchors) + self.num_classes = num_classes def forward(self, x): # don't use p2 and p1 p5, p4, p3, _, _ = self.encoder(x) # enhance features p5, p4, p3 = self.fpn([p5, p4, p3]) - # coarsers FPN levels + # coarser FPN levels p6 = self.pyramid6(p5) - p7 = self.pyramid7(F.relu(p6)) - features = [p7, p6, p5, p4, p3] + p7 = self.pyramid7(p6.relu()) + # want features from lowest OS to highest to align with `generate_anchors_boxes` function + features = [p3, p4, p5, p6, p7] # TODO: (18.03.20) TF implementation has additional BN here before class/box outputs - class_outputs = [self.cls_head(f) for f in features] - box_outputs = [self.box_head(f) for f in features] + class_outputs = [] + box_outputs = [] + for f in features: + cls_anchor = self.cls_head(f).transpose(1, 3).contiguous().view(x.shape[0], -1, self.num_classes) + box_anchor = self.box_head(f).transpose(1, 3).contiguous().view(x.shape[0], -1, 4) + class_outputs.append(cls_anchor) + box_outputs.append(box_anchor) + class_outputs = torch.cat(class_outputs, 1) + box_outputs = torch.cat(box_outputs, 1) return class_outputs, box_outputs diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py new file mode 100644 index 0000000..172fcf1 --- /dev/null +++ b/pytorch_tools/utils/box.py @@ -0,0 +1,168 @@ +"""Various functions to help with bboxes""" +import torch +import numpy as np + +def box2delta(boxes, anchors): + """Convert boxes to deltas from anchors. Boxes are expected in 'ltrb' format + Args: + boxes (torch.Tensor): shape [N, 4] + anchors (torch.Tensor): shape [N, 4] + Returns: + deltas (torch.Tensor): shape [N, 4]. offset_x, offset_y, scale_x, scale_y + """ + + anchors_wh = anchors[:, 2:] - anchors[:, :2] + 1 + anchors_ctr = anchors[:, :2] + 0.5 * anchors_wh + boxes_wh = boxes[:, 2:] - boxes[:, :2] + 1 + boxes_ctr = boxes[:, :2] + 0.5 * boxes_wh + offset_delta = (boxes_ctr - anchors_ctr) / anchors_wh + scale_delta = torch.log(boxes_wh / anchors_wh) + return torch.cat([offset_delta, scale_delta], 1) + + +def delta2box(deltas, anchors): + """Convert anchors to boxes using deltas. Boxes are expected in 'ltrb' format + Args: + deltas (torch.Tensor): shape [N, 4]. + anchors (torch.Tensor): shape [N, 4] + Returns: + bboxes (torch.Tensor): bboxes obtained from anchors by regression [N, 4] + """ + + anchors_wh = anchors[:, 2:] - anchors[:, :2] + 1 + ctr = anchors[:, :2] + 0.5 * anchors_wh + pred_ctr = deltas[:, :2] * anchors_wh + ctr + pred_wh = torch.exp(deltas[:, 2:]) * anchors_wh + + return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh - 1], 1) + +# implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py +# with slight modifications +def box_iou(boxes1, boxes2): + """ + Return intersection-over-union (Jaccard index) of boxes. + Both sets of boxes are expected to be in `ltrb`: (x1, y1, x2, y2) format. + Arguments: + boxes1 (Tensor[N, 4]) + boxes2 (Tensor[M, 4]) + Returns: + iou (Tensor[N, M]): the NxM matrix containing the pairwise + IoU values for every element in boxes1 and boxes2 + """ + area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1]) + area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1]) + + lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] + rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] + + wh = (rb - lt).clamp(min=0) # [N,M,2] + inter = wh[:, :, 0] * wh[:, :, 1] # [N,M] + + iou = inter / (area1[:, None] + area2 - inter) + return iou + + +# based on https://github.com/NVIDIA/retinanet-examples/ +# and on https://github.com/google/automl/ +def generate_anchors_boxes( + image_size, + num_scales=3, + aspect_ratios=(1.0, 2.0, 0.5), + pyramid_levels=[3, 4, 5, 6, 7], + anchor_scale=4, +): + """Generates multiscale anchor boxes + Minimum object size which could be detected is anchor_scale * 2**pyramid_levels[0]. By default it's 32px + Maximum object size which could be detected is anchor_scale * 2**pyramid_levels[-1]. By default it's 512px + + Args: + image_size (int or (int, int)): shape of the image + num_scales (int): integer number representing intermediate scales added on each level. For instances, + num_scales=3 adds three additional anchor scales [2^0, 2^0.33, 2^0.66] on each level. + aspect_ratios (List[int]): Aspect ratios of anchor boxes + pyramid_levels (List[int]): Levels from which features are taken. Needed to calculate stride + anchor_scale (float): scale of size of the base anchor. Lower values allows detection of smaller objects. + + Returns: + anchor_boxes (torch.Tensor): stacked anchor boxes on all feature levels. shape [N, 4]. + boxes are in 'ltrb' format + """ + + if isinstance(image_size, int): + image_size = (image_size, image_size) + scale_vals = [anchor_scale * 2 ** (i / num_scales) for i in range(num_scales)] + # from lowest stride to largest. Anchors from models should be in the same order! + strides = [2**i for i in pyramid_levels] + + # get offsets for anchor boxes for one pixel + # can rewrite in pure Torch but using np is more convenient. This function usually should only be called once + num_anchors = len(scale_vals) * len(aspect_ratios) + ratio_vals_sq = np.sqrt(np.repeat(aspect_ratios, len(scale_vals))) + scale_vals_tiled = np.tile(scale_vals, len(aspect_ratios))[:, np.newaxis] + wh = np.stack([np.ones(num_anchors) * ratio_vals_sq, np.ones(num_anchors) / ratio_vals_sq], axis=1) + lt = - 0.5 * wh * scale_vals_tiled + rb = 0.5 * wh * scale_vals_tiled + base_offsets = torch.from_numpy(np.hstack([lt, rb])) # [num_anchors, 4] + base_offsets = base_offsets.view(-1, 1, 1, 4) # [num_anchors, 1, 1, 4] + + # generate anchor boxes for all given strides + all_anchors = [] + for stride in strides: + x, y = torch.meshgrid([torch.arange(stride / 2, image_size[i], stride) for i in range(2)]) + xyxy = torch.stack((x, y, x, y), 2).unsqueeze(0) + anchors = (xyxy + base_offsets * stride).view(-1, 4).contiguous() + all_anchors.append(anchors) + all_anchors = torch.cat(all_anchors) + # clip boxes to image. Not sure if we really need to clip them + # all_anchors[:, 0::2] = all_anchors[:, 0::2].clamp(0, image_size[0]) + # all_anchors[:, 1::2] = all_anchors[:, 1::2].clamp(0, image_size[1]) + return all_anchors + +def generate_targets(anchors, gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): + """Generate targets for regression and classification for SINGLE image + + Based on IoU between anchor and true bounding box there are three types of anchor boxes + 1) IoU >= matched_iou: Highest similarity. Matched/Positive. Mask value is 1 + 2) matched_iou > IoU >= unmatched_iou: Medium similarity. Ignored. Mask value is -1 + 3) unmatched_iou > IoU: Lowest similarity. Unmatched/Negative. Mask value is 0 + + Args: + anchors (torch.Tensor): all anchors on a single image. shape [N, 4] + gt_boxes (torch.Tesor): all groud truth bounding boxes and classes on the image. shape [N, 5] + classes are expected to be in the last column. + bboxes are in `ltrb` format! + num_classes (int): number of classes. needed for one-hot encoding labels + matched_iou (float): + unmatched_iou (float): + + Returns: + box_target, cls_target, matches_mask + + """ + + gt_boxes, gt_classes = gt_boxes.split(4, dim=1) + overlap = box_iou(anchors, gt_boxes) + + # Keep best box per anchor + overlap, indices = overlap.max(1) + box_target = box2delta(gt_boxes[indices], anchors) # I've double checked that it's corrects + # TODO: add test that anchor + box target gives gt_bbox + + # There are three types of anchors. + # matched (with objects), unmatched (with background), and in between (which should be ignored) + IGNORED_VALUE = -1 + UNMATCHED_VALUE = 0 + matches_mask = torch.ones_like(overlap) * IGNORED_VALUE + matches_mask[overlap < unmatched_iou] = UNMATCHED_VALUE # background + matches_mask[overlap >= matched_iou] = 1 + + # Generate one-hot-encoded target classes + cls_target = torch.zeros( + (anchors.size(0), num_classes + 1), device=gt_classes.device, dtype=gt_classes.dtype + ) + gt_classes = gt_classes[indices].long() + gt_classes[overlap < unmatched_iou] = num_classes # background has no class + cls_target.scatter_(1, gt_classes, 1) + cls_target = cls_target[:, :num_classes] # remove background class from one-hot + + return cls_target, box_target, matches_mask \ No newline at end of file From c283581154cdaafa826a2e2b10237506375eb6a6 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 6 May 2020 19:23:06 +0300 Subject: [PATCH 03/54] add smooth l1 loss --- pytorch_tools/losses/__init__.py | 1 + pytorch_tools/losses/huber.py | 33 ++++++++++++++++++++++++++++++++ tests/losses/test_losses.py | 6 ++++++ 3 files changed, 40 insertions(+) create mode 100644 pytorch_tools/losses/huber.py diff --git a/pytorch_tools/losses/__init__.py b/pytorch_tools/losses/__init__.py index d2c302f..962fa1c 100644 --- a/pytorch_tools/losses/__init__.py +++ b/pytorch_tools/losses/__init__.py @@ -10,6 +10,7 @@ from .vgg_loss import ContentLoss, StyleLoss from .smooth import CrossEntropyLoss from .hinge import BinaryHinge +from .huber import SmoothL1Loss from .functional import focal_loss_with_logits from .functional import soft_dice_score diff --git a/pytorch_tools/losses/huber.py b/pytorch_tools/losses/huber.py new file mode 100644 index 0000000..99d4337 --- /dev/null +++ b/pytorch_tools/losses/huber.py @@ -0,0 +1,33 @@ +from .base import Loss +from .base import Reduction + +class SmoothL1Loss(Loss): + """Huber loss aka Smooth L1 Loss + + loss = 0.5 * x^2 if |x| <= d + loss = 0.5 * d^2 + d * (|x| - d) if |x| > d + + Args: + delta (float): point where the Huber loss function changes from a quadratic to linear + reduction (str): The reduction type to apply to the output. {'none', 'mean', 'sum'}. + 'none' - no reduction will be applied + 'sum' - the output will be summed + 'mean' - the sum of the output will be divided by the number of elements in the output + """ + + def __init__(self, delta=0.1, reduction="none"): + super().__init__() + self.delta = delta + self.reduction = Reduction(reduction) + + def forward(self, pred, target): + x = (pred - target).abs() + l1 = self.delta * (x - 0.5 * self.delta) + l2 = 0.5 * x.pow(2) + + loss = l1.where(x >= self.delta, l2) + if self.reduction == Reduction.MEAN: + loss = loss.mean() + elif self.reduction == Reduction.SUM: + loss = loss.sum() + return loss \ No newline at end of file diff --git a/tests/losses/test_losses.py b/tests/losses/test_losses.py index 74df11b..cf3090e 100644 --- a/tests/losses/test_losses.py +++ b/tests/losses/test_losses.py @@ -386,3 +386,9 @@ def test_multiclass_multilabel_lovasz(): def test_binary_hinge(): assert losses.BinaryHinge()(INP_IMG_BINARY, TARGET_IMG_BINARY) + + +@pytest.mark.parametrize("reduction", ["sum", "mean", "none"]) +def test_smoothl1(reduction): + loss_my = losses.SmoothL1Loss(delta=1, reduction=reduction)(INP, TARGET_MULTILABEL) + loss_torch = F.smooth_l1_loss(INP, TARGET_MULTILABEL, reduction=reduction) \ No newline at end of file From 8efe866b5ec9971ccccb8bda84397c5069856688 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 6 May 2020 19:56:02 +0300 Subject: [PATCH 04/54] support generate targets for batches --- pytorch_tools/utils/box.py | 103 +++++++++++++++++++++++++++---------- 1 file changed, 75 insertions(+), 28 deletions(-) diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index 172fcf1..e446d63 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -118,8 +118,8 @@ def generate_anchors_boxes( # all_anchors[:, 1::2] = all_anchors[:, 1::2].clamp(0, image_size[1]) return all_anchors -def generate_targets(anchors, gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): - """Generate targets for regression and classification for SINGLE image +def generate_targets(anchors, batch_gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): + """Generate targets for regression and classification Based on IoU between anchor and true bounding box there are three types of anchor boxes 1) IoU >= matched_iou: Highest similarity. Matched/Positive. Mask value is 1 @@ -128,7 +128,7 @@ def generate_targets(anchors, gt_boxes, num_classes, matched_iou=0.5, unmatched_ Args: anchors (torch.Tensor): all anchors on a single image. shape [N, 4] - gt_boxes (torch.Tesor): all groud truth bounding boxes and classes on the image. shape [N, 5] + batch_gt_boxes (torch.Tesor): all groud truth bounding boxes and classes for the batch. shape [BS, N, 5] classes are expected to be in the last column. bboxes are in `ltrb` format! num_classes (int): number of classes. needed for one-hot encoding labels @@ -139,30 +139,77 @@ def generate_targets(anchors, gt_boxes, num_classes, matched_iou=0.5, unmatched_ box_target, cls_target, matches_mask """ - - gt_boxes, gt_classes = gt_boxes.split(4, dim=1) - overlap = box_iou(anchors, gt_boxes) + def _generate_single_targets(gt_boxes): + gt_boxes, gt_classes = gt_boxes.split(4, dim=1) + overlap = box_iou(anchors, gt_boxes) + + # Keep best box per anchor + overlap, indices = overlap.max(1) + box_target = box2delta(gt_boxes[indices], anchors) # I've double checked that it's corrects + # TODO: add test that anchor + box target gives gt_bbox + + # There are three types of anchors. + # matched (with objects), unmatched (with background), and in between (which should be ignored) + IGNORED_VALUE = -1 + UNMATCHED_VALUE = 0 + matches_mask = torch.ones_like(overlap) * IGNORED_VALUE + matches_mask[overlap < unmatched_iou] = UNMATCHED_VALUE # background + matches_mask[overlap >= matched_iou] = 1 + + # Generate one-hot-encoded target classes + cls_target = torch.zeros( + (anchors.size(0), num_classes + 1), device=gt_classes.device, dtype=gt_classes.dtype + ) + gt_classes = gt_classes[indices].long() + gt_classes[overlap < unmatched_iou] = num_classes # background has no class + cls_target.scatter_(1, gt_classes, 1) + cls_target = cls_target[:, :num_classes] # remove background class from one-hot + + return cls_target, box_target, matches_mask - # Keep best box per anchor - overlap, indices = overlap.max(1) - box_target = box2delta(gt_boxes[indices], anchors) # I've double checked that it's corrects - # TODO: add test that anchor + box target gives gt_bbox + anchors = anchors.to(batch_gt_boxes) # change device & type if needed + batch_results = ([], [], []) + for single_gt_boxes in batch_gt_boxes: + single_target_results = _generate_single_targets(single_gt_boxes) + for batch_res, single_res in zip(batch_results, single_target_results): + batch_res.append(single_res) + b_cls_target, b_box_target, b_matches_mask = [torch.stack(targets) for targets in batch_results] + return b_cls_target, b_box_target, b_matches_mask - # There are three types of anchors. - # matched (with objects), unmatched (with background), and in between (which should be ignored) - IGNORED_VALUE = -1 - UNMATCHED_VALUE = 0 - matches_mask = torch.ones_like(overlap) * IGNORED_VALUE - matches_mask[overlap < unmatched_iou] = UNMATCHED_VALUE # background - matches_mask[overlap >= matched_iou] = 1 - - # Generate one-hot-encoded target classes - cls_target = torch.zeros( - (anchors.size(0), num_classes + 1), device=gt_classes.device, dtype=gt_classes.dtype - ) - gt_classes = gt_classes[indices].long() - gt_classes[overlap < unmatched_iou] = num_classes # background has no class - cls_target.scatter_(1, gt_classes, 1) - cls_target = cls_target[:, :num_classes] # remove background class from one-hot - - return cls_target, box_target, matches_mask \ No newline at end of file +# copied from torchvision +def batched_nms(boxes, scores, idxs, iou_threshold): + # type: (Tensor, Tensor, Tensor, float) + """ + Performs non-maximum suppression in a batched fashion. + Each index value correspond to a category, and NMS + will not be applied between elements of different categories. + Parameters + ---------- + boxes : Tensor[N, 4] + boxes where NMS will be performed. They + are expected to be in (x1, y1, x2, y2) format + scores : Tensor[N] + scores for each one of the boxes + idxs : Tensor[N] + indices of the categories for each one of the boxes. + iou_threshold : float + discards all overlapping boxes + with IoU > iou_threshold + Returns + ------- + keep : Tensor + int64 tensor with the indices of + the elements that have been kept by NMS, sorted + in decreasing order of scores + """ + if boxes.numel() == 0: + return torch.empty((0,), dtype=torch.int64, device=boxes.device) + # strategy: in order to perform NMS independently per class. + # we add an offset to all the boxes. The offset is dependent + # only on the class idx, and is large enough so that boxes + # from different classes do not overlap + max_coordinate = boxes.max() + offsets = idxs.to(boxes) * (max_coordinate + 1) + boxes_for_nms = boxes + offsets[:, None] + keep = torch.ops.torchvision.nms(boxes_for_nms, scores, iou_threshold) + return keep \ No newline at end of file From 2ae009db21c410aa9e4a3775ace1534d0fc645ee Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 6 May 2020 21:45:00 +0300 Subject: [PATCH 05/54] move timer inside callback --- pytorch_tools/fit_wrapper/callbacks.py | 13 +++++++++---- pytorch_tools/fit_wrapper/state.py | 2 -- pytorch_tools/fit_wrapper/wrapper.py | 6 +++--- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index 1c782cd..52b2fca 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -10,6 +10,7 @@ from torch.utils.tensorboard import SummaryWriter from .state import RunnerState from pytorch_tools.utils.misc import listify +from pytorch_tools.utils.misc import TimeMeter from pytorch_tools.utils.visualization import plot_confusion_matrix from pytorch_tools.utils.visualization import render_figure_to_tensor @@ -113,18 +114,22 @@ class Timer(Callback): def __init__(self): super().__init__() self.has_printed = False + self.timer = TimeMeter() def on_batch_begin(self): - self.state.timer.batch_start() + self.timer.batch_start() def on_batch_end(self): - self.state.timer.batch_end() + self.timer.batch_end() + + def on_loader_begin(self): + self.timer.reset() def on_loader_end(self): if not self.has_printed: self.has_printed = True - d_time = self.state.timer.data_time.avg_smooth - b_time = self.state.timer.batch_time.avg_smooth + d_time = self.timer.data_time.avg_smooth + b_time = self.timer.batch_time.avg_smooth print(f"\nTimeMeter profiling. Data time: {d_time:.2E}s. Model time: {b_time:.2E}s \n") diff --git a/pytorch_tools/fit_wrapper/state.py b/pytorch_tools/fit_wrapper/state.py index 95ca30c..336ffcd 100644 --- a/pytorch_tools/fit_wrapper/state.py +++ b/pytorch_tools/fit_wrapper/state.py @@ -1,6 +1,5 @@ from ..utils.misc import listify from ..utils.misc import AverageMeter -from ..utils.misc import TimeMeter class RunnerState: @@ -39,7 +38,6 @@ def __init__( self.loss_meter = AverageMeter("loss") # for timer callback - self.timer = TimeMeter() self.__is_frozen = True def __setattr__(self, key, value): diff --git a/pytorch_tools/fit_wrapper/wrapper.py b/pytorch_tools/fit_wrapper/wrapper.py index 97506e1..cb57c77 100644 --- a/pytorch_tools/fit_wrapper/wrapper.py +++ b/pytorch_tools/fit_wrapper/wrapper.py @@ -88,12 +88,12 @@ def _make_step(self): # update metrics self.state.loss_meter.update(to_numpy(loss)) - for metric, meter in zip(self.state.metrics, self.state.metric_meters): - meter.update(to_numpy(metric(output, target).squeeze())) + with torch.no_grad(): + for metric, meter in zip(self.state.metrics, self.state.metric_meters): + meter.update(to_numpy(metric(output, target).squeeze())) def _run_loader(self, loader, steps=None): self.state.loss_meter.reset() - self.state.timer.reset() for metric in self.state.metric_meters: metric.reset() self.state.epoch_size = steps or len(loader) # steps overwrites len From e6036374bde74db35121ee0b6ca58a9f7e4af918 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Thu, 7 May 2020 18:43:25 +0300 Subject: [PATCH 06/54] add function to convert to weight standartization conv --- .../modules/weight_standartization.py | 46 +++++++++++-------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index 50e8631..5b9221a 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -1,3 +1,4 @@ +import torch from torch import nn import torch.nn.functional as F @@ -15,23 +16,30 @@ def forward(self, x): weight = weight.div(std.expand_as(weight)) return F.conv2d(x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups) -# code from random issue on github. -def convertConv2WeightStand(module, nextChild=None): - mod = module - norm_list = [torch.nn.modules.batchnorm.BatchNorm1d, torch.nn.modules.batchnorm.BatchNorm2d, torch.nn.modules.batchnorm.BatchNorm3d, torch.nn.GroupNorm, torch.nn.LayerNorm] - conv_list = [torch.nn.Conv1d, torch.nn.Conv2d, torch.nn.Conv3d, torch.nn.ConvTranspose1d, torch.nn.ConvTranspose2d, torch.nn.ConvTranspose3d] - for norm in norm_list: - for conv in conv_list: - if isinstance(mod, conv) and isinstance(nextChild, norm): - mod = Conv2d(mod.in_channels, mod.out_channels, mod.kernel_size, mod.stride, - mod.padding, mod.dilation, mod.groups, mod.bias!=None) - - moduleChildList = list(module.named_children()) - for index, [name, child] in enumerate(moduleChildList): - nextChild = None - if index < len(moduleChildList) -1: - nextChild = moduleChildList[index+1][1] - mod.add_module(name, convertConv2WeightStand(child, nextChild)) - - return mod +# code from SyncBatchNorm in pytorch +@torch.no_grad() # not sure if torch.no_grad is needed. but just in case +def conv_to_ws_conv(module): + module_output = module + if isinstance(module, torch.nn.Conv2d): + module_output = WS_Conv2d( + in_channels=module.in_channels, + out_channels=module.out_channels, + kernel_size=module.kernel_size, + stride=module.stride, + padding=module.padding, + dilation=module.dilation, + # groups are also present in DepthWiseConvs which we don't want to patch + # TODO: fix this + groups=module.groups, + bias=module.bias, + ) + module_output.weight.copy_(module.weight) + module_output.weight.requires_grad = module.weight.requires_grad + if module.bias: + module_output.bias.copy_(module.bias) + module_output.bias.requires_grad = module.bias.requires_grad + for name, child in module.named_children(): + module_output.add_module(name, conv_to_ws_conv(child)) + del module + return module_output \ No newline at end of file From 01c3b1fe946feade56f408947710815fedc1e3a4 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Thu, 7 May 2020 18:44:20 +0300 Subject: [PATCH 07/54] add box_area function --- pytorch_tools/utils/box.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index e446d63..92affe9 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -36,6 +36,12 @@ def delta2box(deltas, anchors): return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh - 1], 1) +def box_area(box): + """Args: + box (torch.Tensor): shape [N, 4] in 'ltrb' format + """ + return (box[:, 2] - box[:, 0]) * (box[:, 3] - box[:, 1]) + # implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py # with slight modifications def box_iou(boxes1, boxes2): @@ -49,8 +55,8 @@ def box_iou(boxes1, boxes2): iou (Tensor[N, M]): the NxM matrix containing the pairwise IoU values for every element in boxes1 and boxes2 """ - area1 = (boxes1[:, 2] - boxes1[:, 0]) * (boxes1[:, 3] - boxes1[:, 1]) - area2 = (boxes2[:, 2] - boxes2[:, 0]) * (boxes2[:, 3] - boxes2[:, 1]) + area1 = box_area(boxes1) + area2 = box_area(boxes2) lt = torch.max(boxes1[:, None, :2], boxes2[:, :2]) # [N,M,2] rb = torch.min(boxes1[:, None, 2:], boxes2[:, 2:]) # [N,M,2] @@ -86,6 +92,7 @@ def generate_anchors_boxes( Returns: anchor_boxes (torch.Tensor): stacked anchor boxes on all feature levels. shape [N, 4]. boxes are in 'ltrb' format + num_anchors (int): number of anchors per location """ if isinstance(image_size, int): @@ -116,7 +123,7 @@ def generate_anchors_boxes( # clip boxes to image. Not sure if we really need to clip them # all_anchors[:, 0::2] = all_anchors[:, 0::2].clamp(0, image_size[0]) # all_anchors[:, 1::2] = all_anchors[:, 1::2].clamp(0, image_size[1]) - return all_anchors + return all_anchors, num_anchors def generate_targets(anchors, batch_gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): """Generate targets for regression and classification From a5c267f120125aa5292ce67b2ba1f1f2f71c1aba Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Thu, 7 May 2020 19:26:56 +0300 Subject: [PATCH 08/54] fix ws --- pytorch_tools/modules/__init__.py | 1 + pytorch_tools/modules/weight_standartization.py | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/pytorch_tools/modules/__init__.py b/pytorch_tools/modules/__init__.py index 86a776f..ee20f3c 100644 --- a/pytorch_tools/modules/__init__.py +++ b/pytorch_tools/modules/__init__.py @@ -13,6 +13,7 @@ from .residual import SEModule # from .residual import Transition, DenseLayer +from .weight_standartization import conv_to_ws_conv from .activations import ACT_DICT from .activations import ACT_FUNC_DICT diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index 5b9221a..5f8e926 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -17,7 +17,6 @@ def forward(self, x): return F.conv2d(x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups) # code from SyncBatchNorm in pytorch -@torch.no_grad() # not sure if torch.no_grad is needed. but just in case def conv_to_ws_conv(module): module_output = module if isinstance(module, torch.nn.Conv2d): @@ -33,11 +32,12 @@ def conv_to_ws_conv(module): groups=module.groups, bias=module.bias, ) - module_output.weight.copy_(module.weight) - module_output.weight.requires_grad = module.weight.requires_grad - if module.bias: - module_output.bias.copy_(module.bias) - module_output.bias.requires_grad = module.bias.requires_grad + with torch.no_grad(): # not sure if torch.no_grad is needed. but just in case + module_output.weight.copy_(module.weight) + module_output.weight.requires_grad = module.weight.requires_grad + if module.bias: + module_output.bias.copy_(module.bias) + module_output.bias.requires_grad = module.bias.requires_grad for name, child in module.named_children(): module_output.add_module(name, conv_to_ws_conv(child)) From 4a48f977cfaa46e53d21fdf400082e14c31d54f5 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 8 May 2020 01:39:30 +0300 Subject: [PATCH 09/54] add metrics reduction in FileLogger. Check `mode` in PhasesScheduler --- pytorch_tools/fit_wrapper/callbacks.py | 31 +++++++++++++++++++------- tests/fit_wrapper/test_runner.py | 12 +++++++++- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index 52b2fca..57eacc8 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -9,8 +9,7 @@ import torch from torch.utils.tensorboard import SummaryWriter from .state import RunnerState -from pytorch_tools.utils.misc import listify -from pytorch_tools.utils.misc import TimeMeter +import pytorch_tools.utils.misc as utils from pytorch_tools.utils.visualization import plot_confusion_matrix from pytorch_tools.utils.visualization import render_figure_to_tensor @@ -66,7 +65,7 @@ class Callbacks(Callback): def __init__(self, callbacks): super().__init__() - self.callbacks = listify(callbacks) + self.callbacks = utils.listify(callbacks) def set_state(self, state): for callback in self.callbacks: @@ -114,7 +113,7 @@ class Timer(Callback): def __init__(self): super().__init__() self.has_printed = False - self.timer = TimeMeter() + self.timer = utils.TimeMeter() def on_batch_begin(self): self.timer.batch_start() @@ -136,6 +135,7 @@ def on_loader_end(self): class PhasesScheduler(Callback): """ Scheduler that uses `phases` to process updates. + Supported `mode`'s are {`linear`, `cos`} Args: phases (List[Dict]): phases @@ -160,9 +160,9 @@ def __init__(self, phases, change_every=50): super(PhasesScheduler, self).__init__() def _format_phase(self, phase): - phase["ep"] = listify(phase["ep"]) - phase["lr"] = listify(phase["lr"]) - phase["mom"] = listify(phase.get("mom", None)) # optional + phase["ep"] = utils.listify(phase["ep"]) + phase["lr"] = utils.listify(phase["lr"]) + phase["mom"] = utils.listify(phase.get("mom", None)) # optional if len(phase["lr"]) == 2 or len(phase["mom"]) == 2: phase["mode"] = phase.get("mode", "linear") assert len(phase["ep"]) == 2, "Linear learning rates must contain end epoch" @@ -175,6 +175,8 @@ def _schedule(start, end, pct, mode): return start + (end - start) * pct elif mode == "cos": return end + (start - end) / 2 * (math.cos(math.pi * pct) + 1) + else: + raise ValueError(f"Mode: `{mode}` is not supported in PhasesScheduler") def _get_lr_mom(self, batch_curr): phase = self.phase @@ -478,7 +480,8 @@ def on_batch_end(self): class FileLogger(Callback): - """Logs loss and metrics every epoch into file + """Logs loss and metrics every epoch into file. + If launched in distributed mode - reduces metrics before logging Args: log_dir (str): path where to store the logs logger (logging.Logger): external logger. Default None @@ -493,6 +496,8 @@ def on_epoch_begin(self): self.logger.info(f"Epoch {self.state.epoch_log} | lr {self.current_lr:.3f}") def on_epoch_end(self): + if utils.env_world_size() > 1: + self.reduce_metrics() loss, metrics = self.state.train_loss, self.state.train_metrics self.logger.info("Train " + self._format_meters(loss, metrics)) if self.state.val_loss is not None: @@ -518,6 +523,16 @@ def current_lr(self): def _format_meters(loss, metrics): return f"loss: {loss.avg:.4f} | " + " | ".join(f"{m.name}: {m.avg:.4f}" for m in metrics) + def reduce_metrics(self): + # can't reduce AverageMeter so need to reduce every attribute separately + meters = self.state.train_metrics + [self.state.train_loss,] + if self.state.val_loss is not None: + meters = meters + self.state.val_metrics + [self.state.val_loss,] + reduce_attributes = ["val", "avg", "avg_smooth", "sum", "count"] + for meter in meters: + for attr in reduce_attributes: + old_value = utils.to_tensor([getattr(meter, attr)]).float().cuda() + setattr(meter, attr, utils.reduce_tensor(old_value).cpu().numpy()[0]) class Mixup(Callback): """Performs mixup on input. Only for classification. diff --git a/tests/fit_wrapper/test_runner.py b/tests/fit_wrapper/test_runner.py index 6f16881..b600d37 100644 --- a/tests/fit_wrapper/test_runner.py +++ b/tests/fit_wrapper/test_runner.py @@ -156,4 +156,14 @@ def test_segm_callback(callback): criterion=TEST_CRITERION, callbacks=callback, ) - runner.fit(TEST_SEGM_LOADER, epochs=2) \ No newline at end of file + runner.fit(TEST_SEGM_LOADER, epochs=2) + +def test_invalid_phases_scheduler_mode(): + runner = Runner( + model=TEST_MODEL, + optimizer=TEST_OPTIMIZER, + criterion=TEST_CRITERION, + callbacks=pt_clb.PhasesScheduler([{"ep":[0,1], "lr":[0,1], "mode":"new_mode" },]) + ) + with pytest.raises(ValueError): + runner.fit(TEST_LOADER, epochs=2) \ No newline at end of file From ee199e25e3ca9b1fb4b879cbb8ecebe7bedfc082 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 8 May 2020 14:31:16 +0300 Subject: [PATCH 10/54] add group norm and GN + WS model weights --- pytorch_tools/models/resnet.py | 41 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index 7fbb4f8..0e396d7 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -307,10 +307,32 @@ def keep_prob(self): "resnet50": { "default": {"params": {"block": Bottleneck, "layers": [3, 4, 6, 3]}, **DEFAULT_IMAGENET_SETTINGS,}, "imagenet": {"url": "https://download.pytorch.org/models/resnet50-19c8e357.pth"}, + # I couldn't validate this weights because they give Acc@1 0.1 maybe a bug somewhere. Still leaving them just in case + # it works better that starting from scratch + "imagenet_gn": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/R-101-GN-abf6008e.pth", + "params": {"norm_layer": "agn"} + }, + # Acc@1: 76.33. Acc@5: 93.34. This weights only work with weight standardization! + "imagenet_gn_ws": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/R-50-GN-WS-fd84efb6.pth", + "params": {"norm_layer": "agn"} + }, }, "resnet101": { "default": {"params": {"block": Bottleneck, "layers": [3, 4, 23, 3]}, **DEFAULT_IMAGENET_SETTINGS,}, "imagenet": {"url": "https://download.pytorch.org/models/resnet101-5d3b4d8f.pth"}, + # I couldn't validate this weights because they give Acc@1 0.1 maybe a bug somewhere. Still leaving them just in case + # it works better that starting from scratch + "imagenet_gn": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/R-101-GN-abf6008e.pth", + "params": {"norm_layer": "agn"} + }, + # Acc@1: 77.85. Acc@5: 93.90. This weights only work with weight standardization! + "imagenet_gn_ws": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/R-101-GN-WS-c067a7de.pth", + "params": {"norm_layer": "agn"} + }, }, "resnet152": { "default": {"params": {"block": Bottleneck, "layers": [3, 8, 36, 3]}, **DEFAULT_IMAGENET_SETTINGS,}, @@ -337,25 +359,36 @@ def keep_prob(self): "params": {"block": Bottleneck, "layers": [3, 4, 6, 3], "base_width": 4, "groups": 32,}, **DEFAULT_IMAGENET_SETTINGS, }, - "imagenet": { # Acc@1: 75.80. Acc@5: 92.71. - "url": "https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth" - }, + # Acc@1: 75.80. Acc@5: 92.71. + "imagenet": {"url": "https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth"}, # weights from rwightman "imagenet2": { "url": "https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-weights/resnext50d_32x4d-103e99f8.pth" }, + # Acc@1: 77.28. Acc@5: 93.61. This weights only work with weight standardization! + "imagenet_gn_ws": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/X-50-GN-WS-2dea43a8.pth", + "params": {"norm_layer": "agn"} + }, }, "resnext101_32x4d": { "default": { "params": {"block": Bottleneck, "layers": [3, 4, 23, 3], "base_width": 4, "groups": 32,}, **DEFAULT_IMAGENET_SETTINGS, - }, # No pretrained + }, # No imagenet pretrained + # 78.19. Acc@5: 93.98 This weights only work with weight standardization! + "imagenet_gn_ws": { + "url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.2/X-101-GN-WS-eb1224cd.pth", + "params": {"norm_layer": "agn"}, + } + }, "resnext101_32x8d": { "default": { "params": {"block": Bottleneck, "layers": [3, 4, 23, 3], "base_width": 8, "groups": 32,}, **DEFAULT_IMAGENET_SETTINGS, }, + # on 8.05.20 this link was broken. maybe need to fix in the future "imagenet": {"url": "https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth"}, # pretrained on weakly labeled instagram and then tuned on Imagenet "imagenet_ig": {"url": "https://download.pytorch.org/models/ig_resnext101_32x8-c38310e5.pth"}, From 53e3a53ed7b5e960f353128ace467d1f32086146 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 8 May 2020 15:48:52 +0300 Subject: [PATCH 11/54] minor update to README and docs --- pytorch_tools/fit_wrapper/README.md | 11 +++++++++- pytorch_tools/models/README.md | 12 ++++++++++- pytorch_tools/models/efficientnet.py | 6 +++--- pytorch_tools/optim/README.md | 2 +- pytorch_tools/segmentation_models/README.md | 23 ++++++++++++++++++++- 5 files changed, 47 insertions(+), 7 deletions(-) diff --git a/pytorch_tools/fit_wrapper/README.md b/pytorch_tools/fit_wrapper/README.md index fe2e97b..96e00da 100644 --- a/pytorch_tools/fit_wrapper/README.md +++ b/pytorch_tools/fit_wrapper/README.md @@ -3,6 +3,8 @@ This module contains model runner (very close to `model.fit` in Keras) for **sup `Runner` is used to actually run the train loop calling `Callbacks` at appropriate times. Mixed precision (powered by apex) is supported implicitly. Users are expected to initialize their models before creating runner using `apex.amp.initialize`. +Main idea of this runner is to be as simple as possible. All core functionality is ~100 lines of code. + ## Minimal example This code will run training for 5 epochs. ```python @@ -40,4 +42,11 @@ runner = pt.fit_wrapper.Runner( ] ) runner.fit(train_loader, epochs=5, val_loader=val_loader) -``` \ No newline at end of file +``` + +## How to +### Add custom step logic +Monkey patch `Runner._make_step` function with yours + +### Process multiple inputs/outputs +Instead of modifying Runner move this logic inside Loss function. \ No newline at end of file diff --git a/pytorch_tools/models/README.md b/pytorch_tools/models/README.md index 5d8f823..44994c1 100644 --- a/pytorch_tools/models/README.md +++ b/pytorch_tools/models/README.md @@ -9,9 +9,19 @@ All models have `pretrained_settings` attribute with training size, mean, std an ## Encoders All models from this repo could be used as feature extractors for both object detection and semantic segmentation. Passing `encoder=True` arg will overwrite `forward` method of the model to return features at 5 different resolutions starting from 1/32 to 1/2. +## Features +* Unified API. Create `resnet, efficientnet, hrnet` models using the same code +* Low memory footprint dy to heavy use of inplace operations. Could be reduced even more by using `norm_layer='inplaceabn'` +* Fast models. As of `04.20` Efficient net's in this repo are the fastest available on GitHub (afaik) +* Support for custom number of input channels in pretrained models. Try with `resnet34(pretrained='imagenet', in_channels=7)` +* All core functionality covered with tests + + ## Repositories used * [Torch Vision Main Repo](https://github.com/pytorch/vision) * [Cadene pretrained models](https://github.com/Cadene/pretrained-models.pytorch/) * [Ross Wightman models](https://github.com/rwightman/pytorch-image-models/) * [Inplace ABN](https://github.com/mapillary/inplace_abn) -* [Efficient Densenet](https://github.com/gpleiss/efficient_densenet_pytorch) \ No newline at end of file +* [Efficient Densenet](https://github.com/gpleiss/efficient_densenet_pytorch) +* [Official Efficient Net](https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet) +* [Original HRNet for Classification](https://github.com/HRNet/HRNet-Image-Classification) \ No newline at end of file diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index 8f52ccc..f4dc720 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -47,12 +47,12 @@ class EfficientNet(nn.Module): width_multiplier (float): Multiplyer for number of channels in each block. Don't need to be passed manually depth_multiplier (float): - Multiplyer for number of InvertedResiduals in each block + Multiplyer for number of InvertedResiduals in each block. Don't need to be passed manually pretrained (str, optional): - If not, returns a model pre-trained on 'str' dataset. `imagenet` is available for every model. + If not None, returns a model pre-trained on 'str' dataset. `imagenet` is available for every model. NOTE: weights which are loaded into this model were ported from TF. There is a drop in accuracy for Imagenet (~1-2% top1) but they work well for finetuning. - NOTE 2: models were pretrained on very different resolution. take it into account during finetuning + NOTE 2: models were pretrained on very different resolutions. take it into account during finetuning num_classes (int): Number of classification classes. Defaults to 1000. in_channels (int): diff --git a/pytorch_tools/optim/README.md b/pytorch_tools/optim/README.md index ca95eee..c1bd7ca 100644 --- a/pytorch_tools/optim/README.md +++ b/pytorch_tools/optim/README.md @@ -1,2 +1,2 @@ -# Custom optimizers and utils +# PyTorch Optimizers and Utils Self-explanatory \ No newline at end of file diff --git a/pytorch_tools/segmentation_models/README.md b/pytorch_tools/segmentation_models/README.md index 30404ce..e31e395 100644 --- a/pytorch_tools/segmentation_models/README.md +++ b/pytorch_tools/segmentation_models/README.md @@ -1 +1,22 @@ -TODO \ No newline at end of file +# PyTorch Segmentation Models Zoo +All models here were either written from scratch or refactored from open-source implementations. +All models here use `Activated Normalization` layers instead of traditional `Normalization` followed by `Activation`. It makes changing activation function and normalization layer easy and convenient. It also allows using [Inplace Activated Batch Norm](https://github.com/mapillary/inplace_abn) from the box, which is essential for reducing memory footprint in segmentation tasks. + + +## Encoders +All [models](./pytorch_tools/models/) could be used as feature extractors (aka backbones) for segmentation architectures. Almost all combinations of backbones and segm.model are supported. + + +## Features +* Unified API. Create `Unet, SegmentaionFPN, HRnet` models using the same code +* Support for custom number of input channels in pretrained encoders +* All core functionality covered with tests + + +## Repositories used +* [Torch Vision Main Repo](https://github.com/pytorch/vision) +* [Cadene pretrained models](https://github.com/Cadene/pretrained-models.pytorch/) +* [Ross Wightman models](https://github.com/rwightman/pytorch-image-models/) +* [Pytorch Toolbelt by @BloodAxe](https://github.com/BloodAxe/pytorch-toolbelt) +* [Segmentation Models py @qubvel](https://github.com/qubvel/segmentation_models.pytorch) +* [Original HRNet for Segmentation](https://github.com/HRNet/HRNet-Semantic-Segmentation) \ No newline at end of file From ee7e1a71927ba1fedd0b9118b72db487a2220078 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 8 May 2020 16:01:46 +0300 Subject: [PATCH 12/54] change assert to warning --- pytorch_tools/models/densenet.py | 9 ++++----- pytorch_tools/models/efficientnet.py | 10 ++++------ pytorch_tools/models/hrnet.py | 9 ++++----- pytorch_tools/models/resnet.py | 9 ++++----- pytorch_tools/models/tresnet.py | 9 ++++----- pytorch_tools/models/vgg.py | 9 ++++----- 6 files changed, 24 insertions(+), 31 deletions(-) diff --git a/pytorch_tools/models/densenet.py b/pytorch_tools/models/densenet.py index 05993b5..bc27ab3 100644 --- a/pytorch_tools/models/densenet.py +++ b/pytorch_tools/models/densenet.py @@ -327,11 +327,10 @@ def _densenet(arch, pretrained=None, **kwargs): cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = DenseNet(**kwargs) diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index f4dc720..d2761c7 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -402,12 +402,10 @@ def _efficientnet(arch, pretrained=None, **kwargs): cfg_settings.update(pretrained_settings) cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = EfficientNet(**kwargs) if pretrained: diff --git a/pytorch_tools/models/hrnet.py b/pytorch_tools/models/hrnet.py index 1f7bde6..0a65182 100644 --- a/pytorch_tools/models/hrnet.py +++ b/pytorch_tools/models/hrnet.py @@ -381,11 +381,10 @@ def _hrnet(arch, pretrained=None, **kwargs): cfg_settings.update(pretrained_settings) cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = HighResolutionNet(**kwargs) if pretrained: diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index 0e396d7..ef4e030 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -486,11 +486,10 @@ def _resnet(arch, pretrained=None, **kwargs): cfg_settings.update(pretrained_settings) cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = ResNet(**kwargs) if pretrained: diff --git a/pytorch_tools/models/tresnet.py b/pytorch_tools/models/tresnet.py index 35063c9..7640e42 100644 --- a/pytorch_tools/models/tresnet.py +++ b/pytorch_tools/models/tresnet.py @@ -195,11 +195,10 @@ def _resnet(arch, pretrained=None, **kwargs): cfg_settings.update(pretrained_settings) cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = TResNet(**kwargs) if pretrained: diff --git a/pytorch_tools/models/vgg.py b/pytorch_tools/models/vgg.py index 41004cf..b2037ce 100644 --- a/pytorch_tools/models/vgg.py +++ b/pytorch_tools/models/vgg.py @@ -170,11 +170,10 @@ def _vgg(arch, pretrained=None, **kwargs): cfg_settings.update(pretrained_settings) cfg_params.update(pretrained_params) common_args = set(cfg_params.keys()).intersection(set(kwargs.keys())) - assert ( - common_args == set() - ), "Args {} are going to be overwritten by default params for {} weights".format( - common_args, pretrained or "default" - ) + if common_args: + logging.warning( + f"Args {common_args} are going to be overwritten by default params for {pretrained} weights" + ) kwargs.update(cfg_params) model = VGG(**kwargs) if pretrained: From ee41bdcbb8c0c7b001c4e8bc507a12ff0e6e45a0 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Sat, 9 May 2020 01:59:39 +0300 Subject: [PATCH 13/54] fix bug for RMSprop --- pytorch_tools/optim/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_tools/optim/__init__.py b/pytorch_tools/optim/__init__.py index fc06bbe..bcd9e6d 100644 --- a/pytorch_tools/optim/__init__.py +++ b/pytorch_tools/optim/__init__.py @@ -25,7 +25,7 @@ def optimizer_from_name(optim_name): # in this implementation eps in inside sqrt so it can be smaller return partial(AdamW_my, center=True, eps=1e-7) elif optim_name == "rmsprop": - return partial(optim.RMSprop, 2e-5) + return partial(optim.RMSprop, eps=2e-5) elif optim_name == "radam": return partial(RAdam, eps=2e-5) elif optim_name in ["fused_sgd", "fusedsgd"]: From c906cf2da2b312e5ba1ba02c88f610f58c98a8c0 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Mon, 11 May 2020 18:51:07 +0300 Subject: [PATCH 14/54] support output stride in decoder. remove `initialize` for decoders --- pytorch_tools/modules/decoder.py | 5 +++-- pytorch_tools/segmentation_models/hrnet.py | 3 ++- pytorch_tools/segmentation_models/linknet.py | 3 ++- pytorch_tools/segmentation_models/segm_fpn.py | 4 ---- pytorch_tools/segmentation_models/unet.py | 17 +++++++++++------ 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/pytorch_tools/modules/decoder.py b/pytorch_tools/modules/decoder.py index 731cdb5..af9412b 100644 --- a/pytorch_tools/modules/decoder.py +++ b/pytorch_tools/modules/decoder.py @@ -7,7 +7,7 @@ class UnetDecoderBlock(nn.Module): - def __init__(self, in_channels, out_channels, norm_layer=ABN, norm_act="relu"): + def __init__(self, in_channels, out_channels, norm_layer=ABN, norm_act="relu", upsample=True): super(UnetDecoderBlock, self).__init__() conv1 = conv3x3(in_channels, out_channels) @@ -15,10 +15,11 @@ def __init__(self, in_channels, out_channels, norm_layer=ABN, norm_act="relu"): abn1 = norm_layer(out_channels, activation=norm_act) abn2 = norm_layer(out_channels, activation=norm_act) self.block = nn.Sequential(conv1, abn1, conv2, abn2) + self.upsample = nn.Upsample(scale_factor=2, mode="bilinear") if upsample else nn.Identity() def forward(self, x): x, skip = x - x = F.interpolate(x, scale_factor=2, mode="nearest") + x = self.upsample(x) if skip is not None: x = torch.cat([x, skip], dim=1) x = self.block(x) diff --git a/pytorch_tools/segmentation_models/hrnet.py b/pytorch_tools/segmentation_models/hrnet.py index a763849..9690ea9 100644 --- a/pytorch_tools/segmentation_models/hrnet.py +++ b/pytorch_tools/segmentation_models/hrnet.py @@ -112,7 +112,8 @@ def __init__( self.name = f"segm-{encoder_name}" # use lower momemntum patch_bn_mom(self) - self._init_weights() + # works better without init (for some reason) + # self._init_weights() def forward(self, x): """Sequentially pass `x` trough model`s `encoder` and `head` (return logits!)""" diff --git a/pytorch_tools/segmentation_models/linknet.py b/pytorch_tools/segmentation_models/linknet.py index 53a8e99..e755c41 100644 --- a/pytorch_tools/segmentation_models/linknet.py +++ b/pytorch_tools/segmentation_models/linknet.py @@ -22,7 +22,8 @@ def __init__( self.dropout = nn.Dropout2d(drop_rate, inplace=True) self.final_conv = conv1x1(prefinal_channels, final_channels) - initialize(self) + # it works much better without initializing decoder. maybe need to investigate into this issue + # initialize(self) def forward(self, x): encoder_head = x[0] diff --git a/pytorch_tools/segmentation_models/segm_fpn.py b/pytorch_tools/segmentation_models/segm_fpn.py index 46cd152..bc73518 100644 --- a/pytorch_tools/segmentation_models/segm_fpn.py +++ b/pytorch_tools/segmentation_models/segm_fpn.py @@ -6,7 +6,6 @@ from pytorch_tools.modules.residual import conv1x1 from pytorch_tools.modules.residual import conv3x3 from pytorch_tools.modules.decoder import SegmentationUpsample -from pytorch_tools.utils.misc import initialize from .encoders import get_encoder @@ -114,9 +113,6 @@ def __init__( self.segm_head = conv1x1(segmentation_channels, num_classes) self.upsample = nn.Upsample(scale_factor=4, mode="bilinear") if last_upsample else nn.Identity() self.name = f"segm-fpn-{encoder_name}" - initialize(self.fpn) - initialize(self.decoder) - initialize(self.segm_head) def forward(self, x): x = self.encoder(x) # returns 5 features maps diff --git a/pytorch_tools/segmentation_models/unet.py b/pytorch_tools/segmentation_models/unet.py index d784e37..42eae40 100644 --- a/pytorch_tools/segmentation_models/unet.py +++ b/pytorch_tools/segmentation_models/unet.py @@ -1,6 +1,7 @@ import torch.nn as nn from pytorch_tools.modules import bn_from_name from pytorch_tools.modules.residual import conv1x1 +from pytorch_tools.modules.residual import conv3x3 from pytorch_tools.modules.decoder import UnetDecoderBlock from pytorch_tools.utils.misc import initialize from .base import EncoderDecoder @@ -20,6 +21,7 @@ def __init__( final_channels=1, center=False, drop_rate=0, + output_stride=32, **bn_params, # norm layer, norm_act ): @@ -32,16 +34,15 @@ def __init__( in_channels = self.compute_channels(encoder_channels, decoder_channels) out_channels = decoder_channels - - self.layer1 = UnetDecoderBlock(in_channels[0], out_channels[0], **bn_params) - self.layer2 = UnetDecoderBlock(in_channels[1], out_channels[1], **bn_params) + self.layer1 = UnetDecoderBlock(in_channels[0], out_channels[0], upsample=output_stride == 32, **bn_params) + self.layer2 = UnetDecoderBlock(in_channels[1], out_channels[1], upsample=not output_stride == 8, **bn_params) self.layer3 = UnetDecoderBlock(in_channels[2], out_channels[2], **bn_params) self.layer4 = UnetDecoderBlock(in_channels[3], out_channels[3], **bn_params) self.layer5 = UnetDecoderBlock(in_channels[4], out_channels[4], **bn_params) self.dropout = nn.Dropout2d(drop_rate, inplace=False) # inplace=True raises a backprop error self.final_conv = conv1x1(out_channels[4], final_channels) - - initialize(self) + # it works much better without initializing decoder. maybe need to investigate into this issue + # initialize(self) def compute_channels(self, encoder_channels, decoder_channels): channels = [ @@ -97,11 +98,14 @@ def __init__( decoder_channels=(256, 128, 64, 32, 16), num_classes=1, center=False, # usefull for VGG models + output_stride=32, drop_rate=0, norm_layer="abn", norm_act="relu", **encoder_params, - ): + ): + if output_stride != 32: + encoder_params["output_stride"] = output_stride encoder = get_encoder( encoder_name, norm_layer=norm_layer, @@ -115,6 +119,7 @@ def __init__( final_channels=num_classes, center=center, drop_rate=drop_rate, + output_stride=output_stride, norm_layer=bn_from_name(norm_layer), norm_act=norm_act, ) From 5f37b9928fabbb3ac732b385b59bc0d2babbfe39 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Tue, 12 May 2020 20:44:58 +0300 Subject: [PATCH 15/54] fix bug with biad in WS --- pytorch_tools/modules/weight_standartization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index 5f8e926..0ad0f9f 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -35,7 +35,7 @@ def conv_to_ws_conv(module): with torch.no_grad(): # not sure if torch.no_grad is needed. but just in case module_output.weight.copy_(module.weight) module_output.weight.requires_grad = module.weight.requires_grad - if module.bias: + if module.bias is not None: module_output.bias.copy_(module.bias) module_output.bias.requires_grad = module.bias.requires_grad From 128f07482ca9e57de47d6035be80b52ec1ce32ef Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 13 May 2020 11:25:37 +0300 Subject: [PATCH 16/54] add tests for WS --- .../modules/weight_standartization.py | 2 +- tests/modules/test_activations.py | 19 ------------ tests/modules/test_modules.py | 29 +++++++++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) delete mode 100644 tests/modules/test_activations.py create mode 100644 tests/modules/test_modules.py diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index 0ad0f9f..a250524 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -30,7 +30,7 @@ def conv_to_ws_conv(module): # groups are also present in DepthWiseConvs which we don't want to patch # TODO: fix this groups=module.groups, - bias=module.bias, + bias=module.bias is not None, ) with torch.no_grad(): # not sure if torch.no_grad is needed. but just in case module_output.weight.copy_(module.weight) diff --git a/tests/modules/test_activations.py b/tests/modules/test_activations.py deleted file mode 100644 index 1f2a9a9..0000000 --- a/tests/modules/test_activations.py +++ /dev/null @@ -1,19 +0,0 @@ -import torch -import pytest -import pytorch_tools.modules as modules - -activations_name = ["Swish", "Swish_Naive", "Mish", "Mish_naive"] - - -@pytest.mark.parametrize("activation", activations_name) -def test_activations_init(activation): - inp = torch.ones(10) - act = modules.activation_from_name(activation) - res = act(inp) - assert res.mean() - -def test_frozen_abn(): - l = modules.bn_from_name("frozen_abn")(10) - assert list(l.parameters()) == [] - l = modules.ABN(10, frozen=True) - assert list(l.parameters()) == [] \ No newline at end of file diff --git a/tests/modules/test_modules.py b/tests/modules/test_modules.py new file mode 100644 index 0000000..969adc0 --- /dev/null +++ b/tests/modules/test_modules.py @@ -0,0 +1,29 @@ +import torch +import pytest +import pytorch_tools as pt +import pytorch_tools.modules as modules + +activations_name = ["Swish", "Swish_Naive", "Mish", "Mish_naive"] + + +@pytest.mark.parametrize("activation", activations_name) +def test_activations_init(activation): + inp = torch.ones(10) + act = modules.activation_from_name(activation) + res = act(inp) + assert res.mean() + +def test_frozen_abn(): + l = modules.bn_from_name("frozen_abn")(10) + assert list(l.parameters()) == [] + l = modules.ABN(10, frozen=True) + assert list(l.parameters()) == [] + +# need to test and resnet and vgg because in resnet there are no Convs with bias +# and in VGG there are no Convs without bias +@pytest.mark.parametrize("norm_layer", ["abn", "agn"]) +@pytest.mark.parametrize("arch", ["resnet18", "vgg11_bn"]) +def test_weight_standardization(norm_layer, arch): + m = pt.models.__dict__[arch](norm_layer=norm_layer) + ws_m = modules.weight_standartization.conv_to_ws_conv(m) + out = ws_m(torch.ones(2, 3, 224, 224)) From 7cf13d763e4b80ad56a80b85ec7aeba1965d6c1b Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 13 May 2020 19:09:58 +0300 Subject: [PATCH 17/54] remove pool type selection (makes it ~1% faster) --- pytorch_tools/models/resnet.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index ef4e030..ed9c4b7 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -17,6 +17,7 @@ from pytorch_tools.modules import BasicBlock, Bottleneck from pytorch_tools.modules import GlobalPool2d, BlurPool from pytorch_tools.modules.residual import conv1x1, conv3x3 +from pytorch_tools.modules.pooling import FastGlobalAvgPool2d from pytorch_tools.modules import bn_from_name from pytorch_tools.utils.misc import add_docs_for from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS @@ -74,8 +75,6 @@ class ResNet(nn.Module): drop_connect_rate (float): Drop rate for StochasticDepth. Randomly removes samples each block. Used as regularization during training. keep prob will be linearly decreased from 1 to 1 - drop_connect_rate each block. Ref: https://arxiv.org/abs/1603.09382 - global_pool (str): - Global pooling type. One of 'avg', 'max', 'avgmax', 'catavgmax'. Defaults to 'avg'. init_bn0 (bool): Zero-initialize the last BN in each residual branch, so that the residual branch starts with zeros, and each residual block behaves like an identity. @@ -100,7 +99,6 @@ def __init__( encoder=False, drop_rate=0.0, drop_connect_rate=0.0, - global_pool="avg", init_bn0=True, ): @@ -145,12 +143,12 @@ def __init__( self.layer2 = self._make_layer(128, layers[1], stride=2, **largs) self.layer3 = self._make_layer(256, layers[2], stride=stride_3, dilation=dilation_3, **largs) self.layer4 = self._make_layer(512, layers[3], stride=stride_4, dilation=dilation_4, **largs) - self.global_pool = GlobalPool2d(global_pool) + self.global_pool = FastGlobalAvgPool2d() self.num_features = 512 * self.expansion self.encoder = encoder if not encoder: self.dropout = nn.Dropout(p=drop_rate, inplace=True) - self.last_linear = nn.Linear(self.num_features * self.global_pool.feat_mult(), num_classes) + self.last_linear = nn.Linear(self.num_features, num_classes) else: self.forward = self.encoder_features From 247e725bd49145ed820e4cae1a721c67189b0e55 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Wed, 13 May 2020 20:48:40 +0300 Subject: [PATCH 18/54] add polunomial decay to PhasesScheduler --- pytorch_tools/fit_wrapper/callbacks.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index 57eacc8..37526eb 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -135,7 +135,7 @@ def on_loader_end(self): class PhasesScheduler(Callback): """ Scheduler that uses `phases` to process updates. - Supported `mode`'s are {`linear`, `cos`} + Supported `mode`'s are {`linear`, `cos`, `poly`} Args: phases (List[Dict]): phases @@ -175,6 +175,9 @@ def _schedule(start, end, pct, mode): return start + (end - start) * pct elif mode == "cos": return end + (start - end) / 2 * (math.cos(math.pi * pct) + 1) + elif mode == "poly": + gamma = (end / start) ** (1 / 100) + return start * gamma ** (pct * 100) else: raise ValueError(f"Mode: `{mode}` is not supported in PhasesScheduler") From 5b267cc1d509ad996d0cb7c19432344048a9ec0b Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Thu, 14 May 2020 19:16:06 +0300 Subject: [PATCH 19/54] add ModelEMA --- pytorch_tools/fit_wrapper/callbacks.py | 67 +++++++++++++++++++++++++- tests/fit_wrapper/test_runner.py | 1 + 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index 37526eb..d38e3bd 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -3,6 +3,7 @@ import logging from tqdm import tqdm from enum import Enum +from copy import deepcopy from collections import OrderedDict from collections import defaultdict import numpy as np @@ -707,4 +708,68 @@ def on_epoch_end(self): self.state.optimizer.state = defaultdict(dict) if self.verbose: - print("Reseting optimizer") \ No newline at end of file + print("Reseting optimizer") + +# docstring from https://github.com/rwightman/pytorch-image-models +class ModelEma(Callback): + """ Model Exponential Moving Average + Keeps a moving average of everything in the model state_dict (parameters and buffers). + This is intended to allow functionality like + https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage + + A smoothed version of the weights is necessary for some training schemes to perform well. + E.g. Google's hyper-params for training MNASNet, MobileNet-V3, EfficientNet, etc that use + RMSprop with a short 2.4-3 epoch decay period and slow LR decay rate of .96-.99 requires EMA + smoothing of weights to match results. + + Current implementation follows TensorFlow and uses the following formula: + ema -= (1 - decay) * (ema - model) + This is mathematically equivalent to the classic formula below but inplace is faster + ema = decay * ema + (1 - decay) * model + + NOTE: Pay attention to the decay constant you are using relative to your update count per epoch. + + NOTE: put this Callback AFTER Checkpoint saver! Otherwise you would validate EMA weights but save + model weights + + NOTE: Only need to be used in master process! otherwise it would take extra memory on all GPUs + + NOTE: Pass model to ModelEma after cuda() and AMP but before SyncBN and DDP wrapper + + Args: + model (nn.Module): model after cuda and AMP + decay (float): decay for EMA for every step + decay_every (int): how oftern to really decay weights. Decaying every step produced a + visible training slowdown. Real decay factor is adjusted to match every step update. + """ + def __init__(self, model, decay=0.9999, decay_every=10): + super().__init__() + self.ema = deepcopy(model).eval() + for p in self.ema.parameters(): + p.requires_grad_(False) + self.model_copy = None + self.decay_factor = 1 - decay ** decay_every # simulate every step decay + self.decay_every = decay_every + + def on_batch_end(self): + if not self.state.is_train or (self.state.step % self.decay_every != 0): + return + + with torch.no_grad(): + for (ema_v, m_v) in zip(self.ema.state_dict().values(), self.state.model.state_dict().values()): + if m_v.numel() == 1: # to prevent errors on `num_batches_tracked` in BN + continue + ema_v.sub_(ema_v.sub(m_v), alpha=self.decay_factor) + + def on_loader_begin(self): + if self.state.is_train: + return + # validate on ema model + self.model_copy = self.state.model + self.state.model = self.ema + + def on_epoch_end(self): + if self.state.is_train: + return + # return model back + self.state.model = self.model_copy \ No newline at end of file diff --git a/tests/fit_wrapper/test_runner.py b/tests/fit_wrapper/test_runner.py index b600d37..6dd56ca 100644 --- a/tests/fit_wrapper/test_runner.py +++ b/tests/fit_wrapper/test_runner.py @@ -133,6 +133,7 @@ def test_grad_clip_loader(): pt_clb.Mixup(0.2, NUM_CLASSES), pt_clb.Cutmix(1.0, NUM_CLASSES), pt_clb.ScheduledDropout(), + pt_clb.ModelEma(decay=0.9999) ], ) def test_callback(callback): From c4479d45e98e0a3be44b0781bb78cf0b0f3a2198 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 15 May 2020 14:22:55 +0300 Subject: [PATCH 20/54] make BlurPool use GaussBlur instead of AvgBlur by default --- pytorch_tools/models/tresnet.py | 8 -------- pytorch_tools/modules/pooling.py | 21 +++++++-------------- 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/pytorch_tools/models/tresnet.py b/pytorch_tools/models/tresnet.py index 7640e42..532cab5 100644 --- a/pytorch_tools/models/tresnet.py +++ b/pytorch_tools/models/tresnet.py @@ -171,12 +171,6 @@ def load_state_dict(self, state_dict, **kwargs): }, } # fmt: on -def patch_blur_pool(module): - """changes `gauss` attribute in blur pool to True""" - if isinstance(module, BlurPool): - module.gauss = True - for m in module.children(): - patch_blur_pool(m) def patch_bn(module): """changes weight from InplaceABN to be compatible with usual ABN""" @@ -216,8 +210,6 @@ def _resnet(arch, pretrained=None, **kwargs): if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels state_dict["conv1.1.weight"] = repeat_channels(state_dict["conv1.1.weight"], kwargs["in_channels"] * 16, 3 * 16) model.load_state_dict(state_dict) - # need to adjust some parameters to be align with original model - patch_blur_pool(model) patch_bn(model) setattr(model, "pretrained_settings", cfg_settings) return model diff --git a/pytorch_tools/modules/pooling.py b/pytorch_tools/modules/pooling.py index 34ee24d..0e8c24d 100644 --- a/pytorch_tools/modules/pooling.py +++ b/pytorch_tools/modules/pooling.py @@ -81,30 +81,23 @@ def forward(self, x): class BlurPool(nn.Module): """ Idea from https://arxiv.org/abs/1904.11486 - Efficient implementation of Rect-3 using AvgPool + Efficient implementation of Rect-3 Args: - channels (int): numbers of channels. needed for gaussian blur - gauss (bool): flag to use Gaussian Blur instead of Average Blur. Uses more memory + channels (int): numbers of input channels. needed to construct gauss kernel """ - def __init__(self, channels=0, gauss=False): + def __init__(self, channels=0): super(BlurPool, self).__init__() - self.gauss = gauss self.channels = channels - # init both options to be able to switch - a = torch.tensor([1., 2., 1.]) - filt = (a[:, None] * a[None, :]).clone().detach() + filt = torch.tensor([1., 2., 1.]) + filt = filt[:, None] * filt[None, :] filt = filt / torch.sum(filt) filt = filt[None, None, :, :].repeat((self.channels, 1, 1, 1)) self.register_buffer("filt", filt) - self.pool = nn.AvgPool2d(3, stride=2, padding=1) def forward(self, inp): - if self.gauss: - inp_pad = F.pad(inp, (1, 1, 1, 1), 'reflect') - return F.conv2d(inp_pad, self.filt, stride=2, padding=0, groups=inp.shape[1]) - else: - return self.pool(inp) + inp_pad = F.pad(inp, (1, 1, 1, 1), 'reflect') + return F.conv2d(inp_pad, self.filt, stride=2, padding=0, groups=inp.shape[1]) # from https://github.com/mrT23/TResNet/ class SpaceToDepth(nn.Module): From c716834432e5b22a367a2daea994fe6b455fc179 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 15 May 2020 20:25:18 +0300 Subject: [PATCH 21/54] add Efficient Channel Attention --- pytorch_tools/models/efficientnet.py | 2 +- pytorch_tools/models/resnet.py | 32 ++++----- pytorch_tools/models/tresnet.py | 4 +- pytorch_tools/modules/residual.py | 97 +++++++++++++++++----------- 4 files changed, 79 insertions(+), 56 deletions(-) diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index d2761c7..7e01a35 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -234,7 +234,7 @@ def _decode_block_string(block_string): out_channels=int(options["o"]), dw_kernel_size=int(options["k"]), stride=tuple([options["s"], options["s"]]), - use_se=float(options["se"]) > 0 if "se" in options else False, + attn_type="se" if "se" in options else None, expand_ratio=int(options["e"]), noskip="noskip" in block_string, num_repeat=int(options["r"]), diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index ed9c4b7..88a562c 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -50,8 +50,10 @@ class ResNet(nn.Module): Number of classification classes. Defaults to 1000. in_channels (int): Number of input (color) channels. Defaults to 3. - use_se (bool): - Enable Squeeze-Excitation module in blocks. + attn_type (Union[str, None]): + If given, selects attention type to use in blocks. One of + `se` - Squeeze-Excitation + `eca` - Efficient Channel Attention groups (int): Number of convolution groups for 3x3 conv in Bottleneck. Defaults to 1. base_width (int): @@ -88,7 +90,7 @@ def __init__( pretrained=None, # not used. here for proper signature num_classes=1000, in_channels=3, - use_se=False, + attn_type=None, groups=1, base_width=64, deep_stem=False, @@ -127,9 +129,7 @@ def __init__( else: self.conv1 = nn.Conv2d(in_channels, stem_width, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = norm_layer(stem_width, activation=norm_act) - self.maxpool = nn.MaxPool2d( - kernel_size=3, stride=2, padding=0 if use_se else 1, ceil_mode=True if use_se else False, - ) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) if output_stride not in [8, 16, 32]: raise ValueError("Output stride should be in [8, 16, 32]") if output_stride == 8: @@ -138,7 +138,7 @@ def __init__( stride_3, stride_4, dilation_3, dilation_4 = 2, 1, 1, 2 elif output_stride == 32: stride_3, stride_4, dilation_3, dilation_4 = 2, 2, 1, 1 - largs = dict(use_se=use_se, norm_layer=norm_layer, norm_act=norm_act, antialias=antialias) + largs = dict(attn_type=attn_type, norm_layer=norm_layer, norm_act=norm_act, antialias=antialias) self.layer1 = self._make_layer(64, layers[0], stride=1, **largs) self.layer2 = self._make_layer(128, layers[1], stride=2, **largs) self.layer3 = self._make_layer(256, layers[2], stride=stride_3, dilation=dilation_3, **largs) @@ -160,7 +160,7 @@ def _make_layer( blocks, stride=1, dilation=1, - use_se=None, + attn_type=None, norm_layer=None, norm_act=None, antialias=None, @@ -186,7 +186,7 @@ def _make_layer( downsample=downsample, groups=self.groups, base_width=self.base_width, - use_se=use_se, + attn_type=attn_type, dilation=first_dilation, norm_layer=norm_layer, norm_act=norm_act, @@ -203,7 +203,7 @@ def _make_layer( planes=planes, groups=self.groups, base_width=self.base_width, - use_se=use_se, + attn_type=attn_type, dilation=first_dilation, norm_layer=norm_layer, norm_act=norm_act, @@ -417,28 +417,28 @@ def keep_prob(self): # SE RESNET MODELS "se_resnet34": { "default": { - "params": {"block": BasicBlock, "layers": [3, 4, 6, 3], "use_se": True}, + "params": {"block": BasicBlock, "layers": [3, 4, 6, 3], "attn_type": "se"}, **DEFAULT_IMAGENET_SETTINGS, }, # NO WEIGHTS }, "se_resnet50": { "default": { - "params": {"block": Bottleneck, "layers": [3, 4, 6, 3], "use_se": True}, + "params": {"block": Bottleneck, "layers": [3, 4, 6, 3], "attn_type": "se"}, **DEFAULT_IMAGENET_SETTINGS, }, "imagenet": {"url": "http://data.lip6.fr/cadene/pretrainedmodels/se_resnet50-ce0d4300.pth"}, }, "se_resnet101": { "default": { - "params": {"block": Bottleneck, "layers": [3, 4, 23, 3], "use_se": True}, + "params": {"block": Bottleneck, "layers": [3, 4, 23, 3], "attn_type": "se"}, **DEFAULT_IMAGENET_SETTINGS, }, "imagenet": {"url": "http://data.lip6.fr/cadene/pretrainedmodels/se_resnet101-7e38fcc6.pth"}, }, "se_resnet152": { "default": { - "params": {"block": Bottleneck, "layers": [3, 4, 36, 3], "use_se": True}, + "params": {"block": Bottleneck, "layers": [3, 4, 36, 3], "attn_type": "se"}, **DEFAULT_IMAGENET_SETTINGS, }, "imagenet": {"url": "http://data.lip6.fr/cadene/pretrainedmodels/se_resnet152-d17c99b7.pth"}, @@ -451,7 +451,7 @@ def keep_prob(self): "layers": [3, 4, 6, 3], "base_width": 4, "groups": 32, - "use_se": True, + "attn_type": "se", }, **DEFAULT_IMAGENET_SETTINGS, }, @@ -464,7 +464,7 @@ def keep_prob(self): "layers": [3, 4, 23, 3], "base_width": 4, "groups": 32, - "use_se": True, + "attn_type": "se", }, **DEFAULT_IMAGENET_SETTINGS, }, diff --git a/pytorch_tools/models/tresnet.py b/pytorch_tools/models/tresnet.py index 532cab5..52849f7 100644 --- a/pytorch_tools/models/tresnet.py +++ b/pytorch_tools/models/tresnet.py @@ -98,7 +98,7 @@ def __init__( # elif output_stride == 32: stride_3, stride_4, dilation_3, dilation_4 = 2, 2, 1, 1 - largs = dict(use_se=True, norm_layer=norm_layer, norm_act=norm_act, antialias=True) + largs = dict(attn_type="se", norm_layer=norm_layer, norm_act=norm_act, antialias=True) self.block = TBasicBlock self.expansion = TBasicBlock.expansion self.layer1 = self._make_layer(stem_width, layers[0], stride=1, **largs) @@ -107,7 +107,7 @@ def __init__( self.block = TBottleneck # first 2 - Basic, last 2 - Bottleneck self.expansion = TBottleneck.expansion self.layer3 = self._make_layer(stem_width * 4, layers[2], stride=stride_3, dilation=dilation_3, **largs) - largs.update(use_se=False) # no se in last layer + largs.update(attn_type=None) # no se in last layer self.layer4 = self._make_layer(stem_width * 8, layers[3], stride=stride_4, dilation=dilation_4, **largs) self.global_pool = FastGlobalAvgPool2d(flatten=True) self.num_features = stem_width * 8 * self.expansion diff --git a/pytorch_tools/modules/residual.py b/pytorch_tools/modules/residual.py index c8da1f4..a614ebb 100644 --- a/pytorch_tools/modules/residual.py +++ b/pytorch_tools/modules/residual.py @@ -30,6 +30,55 @@ def conv1x1(in_planes, out_planes, stride=1, bias=False): return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=bias) +class SEModule(nn.Module): + def __init__(self, channels, reduction_channels, norm_act="relu"): + super(SEModule, self).__init__() + + self.pool = FastGlobalAvgPool2d() + # authors of original paper DO use bias + self.fc1 = nn.Conv2d(channels, reduction_channels, kernel_size=1, stride=1, bias=True) + self.act1 = activation_from_name(norm_act) + self.fc2 = nn.Conv2d(reduction_channels, channels, kernel_size=1, stride=1, bias=True) + + def forward(self, x): + x_se = self.pool(x) + x_se = self.fc1(x_se) + x_se = self.act1(x_se) + x_se = self.fc2(x_se) + return x * x_se.sigmoid() + +class ECAModule(nn.Module): + """Efficient Channel Attention + This implementation is different from the paper. I've removed all hyperparameters and + use fixed kernel size of 3. If you think it may be better to use different k_size - feel free to open an issue. + + Ref: ECA-Net: Efficient Channel Attention for Deep Convolutional Neural Networks + https://arxiv.org/abs/1910.03151 + + """ + def __init__(self, *args, **kwargs): + super().__init__() + self.pool = FastGlobalAvgPool2d() + self.conv = nn.Conv1d(1, 1, kernel_size=3, padding=1, bias=False) + + def forward(self, x): + x_s = self.pool(x) + x_s = self.conv(x_s.view(x.size(0), 1, -1)) + x_s = x_s.view(x.size(0), -1, 1, 1).sigmoid() + return x * x_s.expand_as(x) + +def get_attn(attn_type): + """Args: attn_type (Uniont[str, None]): Attention type. Supported: + `se` - Squeeze and Excitation + `eca` - Efficient Channel Attention + None - not attention + """ + ATT_TO_MODULE = {"se": SEModule, "eca": ECAModule} + if attn_type is None: + return nn.Identity + else: + return ATT_TO_MODULE[attn_type.lower()] + class DepthwiseSeparableConv(nn.Sequential): """Depthwise separable conv with BN after depthwise & pointwise.""" @@ -50,7 +99,7 @@ def __init__( dw_kernel_size=3, stride=1, dilation=1, - use_se=False, + attn_type=None, expand_ratio=1.0, # expansion keep_prob=1, # drop connect param noskip=False, @@ -77,7 +126,7 @@ def __init__( ) self.bn2 = norm_layer(mid_chs, activation=norm_act) # some models like MobileNet use mid_chs here instead of in_channels. But I don't care for now - self.se = SEModule(mid_chs, in_channels // 4, norm_act) if use_se else nn.Identity() + self.se = get_attn(attn_type)(mid_chs, in_channels // 4, norm_act) self.conv_pw1 = conv1x1(mid_chs, out_channels) self.bn3 = norm_layer(out_channels, activation="identity") self.drop_connect = DropConnect(keep_prob) if keep_prob < 1 else nn.Identity() @@ -116,25 +165,6 @@ def forward(self, x): output = x / self.keep_prob * binary_tensor return output - -class SEModule(nn.Module): - def __init__(self, channels, reduction_channels, norm_act="relu"): - super(SEModule, self).__init__() - - self.pool = FastGlobalAvgPool2d() - # authors of original paper DO use bias - self.fc1 = nn.Conv2d(channels, reduction_channels, kernel_size=1, stride=1, bias=True) - self.act1 = activation_from_name(norm_act) - self.fc2 = nn.Conv2d(reduction_channels, channels, kernel_size=1, stride=1, bias=True) - - def forward(self, x): - x_se = self.pool(x) - x_se = self.fc1(x_se) - x_se = self.act1(x_se) - x_se = self.fc2(x_se) - return x * x_se.sigmoid() - - class BasicBlock(nn.Module): expansion = 1 @@ -146,7 +176,7 @@ def __init__( downsample=None, groups=1, base_width=64, - use_se=False, + attn_type=None, dilation=1, norm_layer=ABN, norm_act="relu", @@ -163,7 +193,7 @@ def __init__( self.bn1 = norm_layer(planes, activation=norm_act) self.conv2 = conv3x3(planes, outplanes) self.bn2 = norm_layer(outplanes, activation="identity") - self.se_module = SEModule(outplanes, planes // 4) if use_se else None + self.se_module = get_attn(attn_type)(outplanes, planes // 4) self.final_act = activation_from_name(norm_act) self.downsample = downsample self.blurpool = BlurPool(channels=planes) if antialias else nn.Identity() @@ -183,10 +213,7 @@ def forward(self, x): out = self.blurpool(out) out = self.conv2(out) # avoid 2 inplace ops by chaining into one long op. Needed for inplaceabn - if self.se_module is not None: - out = self.drop_connect(self.se_module(self.bn2(out))) + residual - else: - out = self.drop_connect(self.bn2(out)) + residual + out = self.drop_connect(self.se_module(self.bn2(out))) + residual return self.final_act(out) @@ -201,7 +228,7 @@ def __init__( downsample=None, groups=1, base_width=64, - use_se=False, + attn_type=None, dilation=1, norm_layer=ABN, norm_act="relu", @@ -220,7 +247,7 @@ def __init__( self.bn2 = norm_layer(width, activation=norm_act) self.conv3 = conv1x1(width, outplanes) self.bn3 = norm_layer(outplanes, activation="identity") - self.se_module = SEModule(outplanes, planes // 4) if use_se else None + self.se_module = get_attn(attn_type)(outplanes, planes // 4) self.final_act = activation_from_name(norm_act) self.downsample = downsample self.blurpool = BlurPool(channels=width) if antialias else nn.Identity() @@ -244,10 +271,7 @@ def forward(self, x): out = self.conv3(out) # avoid 2 inplace ops by chaining into one long op - if self.se_module is not None: - out = self.drop_connect(self.se_module(self.bn3(out))) + residual - else: - out = self.drop_connect(self.bn3(out)) + residual + out = self.drop_connect(self.se_module(self.bn3(out))) + residual return self.final_act(out) # TResnet models use slightly modified versions of BasicBlock and Bottleneck @@ -258,7 +282,7 @@ def __init__(self, **kwargs): super().__init__(**kwargs) self.final_act = nn.ReLU(inplace=True) self.bn1.activation_param = 1e-3 # needed for loading weights - if not kwargs.get("use_se"): + if not kwargs.get("attn_type") == "se": return planes = kwargs["planes"] self.se_module = SEModule(planes, max(planes // 4, 64)) @@ -269,7 +293,7 @@ def __init__(self, **kwargs): self.final_act = nn.ReLU(inplace=True) self.bn1.activation_param = 1e-3 # needed for loading weights self.bn2.activation_param = 1e-3 - if not kwargs["use_se"]: + if not kwargs.get("attn_type") == "se": return planes = kwargs["planes"] reduce_planes = max(planes * self.expansion // 8, 64) @@ -291,8 +315,7 @@ def forward(self, x): if self.antialias: out = self.blurpool(out) - if self.se_module is not None: - out = self.se_module(out) + out = self.se_module(out) out = self.conv3(out) # avoid 2 inplace ops by chaining into one long op From 0df40e2ec881b346f7ce368cc2924c310b86c529 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Fri, 15 May 2020 20:31:18 +0300 Subject: [PATCH 22/54] use as criterion in benchmarking --- tests/benchmarking/README.md | 2 ++ tests/benchmarking/memory_test.py | 12 +++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/benchmarking/README.md b/tests/benchmarking/README.md index b5fe0be..37fd0a0 100644 --- a/tests/benchmarking/README.md +++ b/tests/benchmarking/README.md @@ -54,6 +54,8 @@ Mean of 10 runs 10 iters each BS=64: 54.97+-0.01 msecs Forward. 185.83+-5.13 mse **Resnet50 Baseline: 25.56M params** Mean of 10 runs 10 iters each BS=64: 59.65+-0.07 msecs Forward. 164.39+-2.58 msecs Backward. Max memory: 5935.15Mb +**Resnet50 AMP** 25.56M params +Mean of 10 runs 10 iters each BS=256: 111.44+-0.03 msecs Forward. 357.80+-1.61 msecs Backward. Max memory: 11226.14Mb. 545.56 imgs/sec **Resnet34 Leaky ReLU 21.82M params** Mean of 10 runs 10 iters each BS=64: 30.81+-0.18 msecs Forward. 103.95+-1.05 msecs Backward. Max memory: 2766.59Mb diff --git a/tests/benchmarking/memory_test.py b/tests/benchmarking/memory_test.py index d2c3f52..08c83be 100644 --- a/tests/benchmarking/memory_test.py +++ b/tests/benchmarking/memory_test.py @@ -44,7 +44,7 @@ def run_once(): if forward_only: torch.cuda.synchronize() return start.elapsed_time(f_end), start.elapsed_time(f_end) - loss = criterion(output, TARGET) + loss = output.mean() optimizer.zero_grad() loss.backward() optimizer.step() @@ -137,8 +137,6 @@ def run_once(): N_RUNS = 10 RUN_ITERS = 10 INP = torch.ones((BS, 3, 224, 224), requires_grad=not args.forward).cuda(0) - TARGET = torch.ones(BS).long().cuda(0) - criterion = torch.nn.CrossEntropyLoss().cuda(0) for name, model in models_dict.items(): print(f"{name} {count_parameters(model) / 1e6:.2f}M params") model = model.cuda(0) @@ -152,11 +150,15 @@ def run_once(): # now test segmentation models BS = 16 INP = torch.ones((BS, 3, 224, 224), requires_grad=True).cuda(0) - TARGET = torch.ones((BS, 1, 224, 224)).cuda(0) - criterion = pt.losses.JaccardLoss().cuda(0) for name, model in segm_models_dict.items(): enc_params = count_parameters(model.encoder) / 1e6 total_params = count_parameters(model) / 1e6 print(f"{name}. Encoder {enc_params:.2f}M. Decoder {total_params - enc_params:.2f}M. Total {total_params:.2f}M params") + model = model.cuda(0) + if args.amp: + model = amp.initialize(model, verbosity=0, opt_level="O1") + INP = INP.half() + if args.forward: + model.eval() test_model(model, forward_only=args.forward) From 3be207bb108c0ff51f8b6f9b3387f7c47e202c49 Mon Sep 17 00:00:00 2001 From: bonlime Date: Sat, 16 May 2020 12:44:43 +0300 Subject: [PATCH 23/54] change `deep_stem` to `stem_type` (#64) --- pytorch_tools/models/resnet.py | 46 ++++++++++++++++++++++----------- pytorch_tools/models/tresnet.py | 5 +--- tests/models/test_models.py | 7 ++++- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index 88a562c..8743dea 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -19,6 +19,7 @@ from pytorch_tools.modules.residual import conv1x1, conv3x3 from pytorch_tools.modules.pooling import FastGlobalAvgPool2d from pytorch_tools.modules import bn_from_name +from pytorch_tools.modules import SpaceToDepth from pytorch_tools.utils.misc import add_docs_for from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS from pytorch_tools.utils.misc import repeat_channels @@ -58,8 +59,11 @@ class ResNet(nn.Module): Number of convolution groups for 3x3 conv in Bottleneck. Defaults to 1. base_width (int): Factor determining bottleneck channels. `planes * base_width / 64 * groups`. Defaults to 64. - deep_stem (bool): - Whether to replace the 7x7 conv1 with 3 3x3 convolution layers. Defaults to False. + stem_type (str): + Type on input stem. Supported options are: + '' - default. One 7x7 conv with 64 channels + 'deep' - three 3x3 conv with 32, 32, 64, channels + 'space2depth' - Reshape followed by one convolution. Idea from TResNet paper output_stride (List[8, 16, 32]): Applying dilation strategy to pretrained ResNet. Typically used in Semantic Segmentation. Defaults to 32. NOTE: Don't use this arg with `antialias` and `pretrained` together. it may produce weird results @@ -93,7 +97,7 @@ def __init__( attn_type=None, groups=1, base_width=64, - deep_stem=False, + stem_type="", output_stride=32, norm_layer="abn", norm_act="relu", @@ -118,18 +122,9 @@ def __init__( self.drop_connect_rate = drop_connect_rate super(ResNet, self).__init__() - if deep_stem: - self.conv1 = nn.Sequential( - conv3x3(in_channels, stem_width // 2, 2), - norm_layer(stem_width // 2, activation=norm_act), - conv3x3(stem_width // 2, stem_width // 2), - norm_layer(stem_width // 2, activation=norm_act), - conv3x3(stem_width // 2, stem_width), - ) - else: - self.conv1 = nn.Conv2d(in_channels, stem_width, kernel_size=7, stride=2, padding=3, bias=False) - self.bn1 = norm_layer(stem_width, activation=norm_act) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + # move stem creation in separate function for simplicity + self._make_stem(stem_type, stem_width, in_channels, norm_layer, norm_act) + if output_stride not in [8, 16, 32]: raise ValueError("Output stride should be in [8, 16, 32]") if output_stride == 8: @@ -213,6 +208,27 @@ def _make_layer( ) return nn.Sequential(*layers) + def _make_stem(self, stem_type, stem_width, in_channels, norm_layer, norm_act): + assert stem_type in {"", "deep", "space2depth"}, f"Stem type {stem_type} is not supported" + if stem_type == "space2depth": + # in the paper they use conv1x1 but in code conv3x3 (which seems better) + self.conv1 = nn.Sequential(SpaceToDepth(), conv3x3(in_channels * 16, stem_width)) + self.bn1 = norm_layer(stem_width, activation=norm_act) + self.maxpool = nn.Identity() # not used but needed for code compatability + else: + if stem_type == "deep": + self.conv1 = nn.Sequential( + conv3x3(in_channels, stem_width // 2, 2), + norm_layer(stem_width // 2, activation=norm_act), + conv3x3(stem_width // 2, stem_width // 2), + norm_layer(stem_width // 2, activation=norm_act), + conv3x3(stem_width // 2, stem_width), + ) + else: + self.conv1 = nn.Conv2d(in_channels, stem_width, kernel_size=7, stride=2, padding=3, bias=False) + self.bn1 = norm_layer(stem_width, activation=norm_act) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + def _initialize_weights(self, init_bn0=False): for m in self.modules(): if isinstance(m, nn.Conv2d): diff --git a/pytorch_tools/models/tresnet.py b/pytorch_tools/models/tresnet.py index 52849f7..022c9b0 100644 --- a/pytorch_tools/models/tresnet.py +++ b/pytorch_tools/models/tresnet.py @@ -83,10 +83,7 @@ def __init__( self.num_blocks = sum(layers) self.drop_connect_rate = drop_connect_rate - # in the paper they use conv1x1 but in code conv3x3 (which seems better) - self.conv1 = nn.Sequential(SpaceToDepth(), conv3x3(in_channels * 16, stem_width)) - self.bn1 = norm_layer(stem_width, activation=norm_act) - self.maxpool = nn.Identity() # not used but needed for code compatability + self._make_stem("space2depth", stem_width, in_channels, norm_layer, norm_act) if output_stride not in [8, 16, 32]: raise ValueError("Output stride should be in [8, 16, 32]") diff --git a/tests/models/test_models.py b/tests/models/test_models.py index b251ba9..d5fb396 100644 --- a/tests/models/test_models.py +++ b/tests/models/test_models.py @@ -100,4 +100,9 @@ def test_drop_connect(arch): def test_num_parameters(name_num_params): name, num_params = name_num_params[0] m = models.__dict__[name]() - assert pt.utils.misc.count_parameters(m)[0] == num_params \ No newline at end of file + assert pt.utils.misc.count_parameters(m)[0] == num_params + +@pytest.mark.parametrize('stem_type', ["", "deep", "space2depth"]) +def test_resnet_stem_type(stem_type): + m = models.resnet50(stem_type=stem_type) + _test_forward(m) \ No newline at end of file From a991342da841fb54d763eba4b0a8fc16c39d84b8 Mon Sep 17 00:00:00 2001 From: Emil Zakirov Date: Tue, 19 May 2020 12:51:02 +0300 Subject: [PATCH 24/54] update doc for ModelEma Callback --- pytorch_tools/fit_wrapper/callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index d38e3bd..e8d04fb 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -732,7 +732,7 @@ class ModelEma(Callback): NOTE: put this Callback AFTER Checkpoint saver! Otherwise you would validate EMA weights but save model weights - NOTE: Only need to be used in master process! otherwise it would take extra memory on all GPUs + NOTE: Need to be used in all process (not only master)! otherwise you would save not the best model bur some random NOTE: Pass model to ModelEma after cuda() and AMP but before SyncBN and DDP wrapper From 7271b4a5a43cc7498cdbec1c33b00bc188303017 Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 20 May 2020 19:58:46 +0300 Subject: [PATCH 25/54] fix encoder_features for efficientnet --- pytorch_tools/models/efficientnet.py | 15 +++++++-------- pytorch_tools/segmentation_models/encoders.py | 16 ++++++++-------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index 7e01a35..d9ab577 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -127,18 +127,19 @@ def __init__( self.blocks.append(nn.Sequential(*block)) # Head - out_channels = block_arg["out_channels"] - num_features = make_divisible(1280 * width_multiplier) - self.conv_head = conv1x1(out_channels, num_features) - self.bn2 = norm_layer(num_features, activation=norm_act) if encoder: self.forward = self.encoder_features else: + out_channels = block_arg["out_channels"] + num_features = make_divisible(1280 * width_multiplier) + self.conv_head = conv1x1(out_channels, num_features) + self.bn2 = norm_layer(num_features, activation=norm_act) self.global_pool = nn.AdaptiveAvgPool2d(1) self.dropout = nn.Dropout(drop_rate, inplace=True) self.classifier = nn.Linear(num_features, num_classes) + # TODO: change to init from official repo. It may be the source of problem for training from scratch initialize(self) def encoder_features(self, x): @@ -148,11 +149,9 @@ def encoder_features(self, x): x1 = self.blocks[1](x0) x2 = self.blocks[2](x1) x3 = self.blocks[3](x2) - x4 = self.blocks[4](x3) - x4 = self.blocks[5](x4) + x3 = self.blocks[4](x3) + x4 = self.blocks[5](x3) x4 = self.blocks[6](x4) - x4 = self.conv_head(x4) - x4 = self.bn2(x4) return [x4, x3, x2, x1, x0] def features(self, x): diff --git a/pytorch_tools/segmentation_models/encoders.py b/pytorch_tools/segmentation_models/encoders.py index 4f11acc..088a48e 100644 --- a/pytorch_tools/segmentation_models/encoders.py +++ b/pytorch_tools/segmentation_models/encoders.py @@ -23,14 +23,14 @@ "densenet201": (1920, 1792, 512, 256, 64), #'densenet161': (2208, 2112, 768, 384, 96), - "efficientnet_b0": (1280, 80, 40, 24, 16), - "efficientnet_b1": (1280, 80, 40, 24, 16), - "efficientnet_b2": (1408, 88, 48, 24, 16), - "efficientnet_b3": (1536, 96, 48, 32, 24), - "efficientnet_b4": (1792, 112, 56, 32, 24), - "efficientnet_b5": (2048, 128, 64, 40, 24), - "efficientnet_b6": (2304, 144, 72, 40, 32), - "efficientnet_b7": (2560, 160, 80, 48, 32), + "efficientnet_b0": (320, 112, 40, 24, 16), + "efficientnet_b1": (320, 112, 40, 24, 16), + "efficientnet_b2": (352, 120, 48, 24, 16), + "efficientnet_b3": (384, 136, 48, 32, 24), + "efficientnet_b4": (448, 160, 56, 32, 24), + "efficientnet_b5": (512, 176, 64, 40, 24), + "efficientnet_b6": (576, 200, 72, 40, 32), + "efficientnet_b7": (640, 224, 80, 48, 32), # this models return feature maps at OS= 32, 16, 8, 4, 4 # they CAN'T be used as encoders in Unet and Linknet From 72a5af7ebbbabae0020a30a8a8d3137c0eb91064 Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 20 May 2020 20:07:34 +0300 Subject: [PATCH 26/54] add TF same Conv and MaxPool --- pytorch_tools/modules/residual.py | 6 +- pytorch_tools/modules/tf_same_ops.py | 68 +++++++++++++++++++ .../modules/weight_standartization.py | 4 -- 3 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 pytorch_tools/modules/tf_same_ops.py diff --git a/pytorch_tools/modules/residual.py b/pytorch_tools/modules/residual.py index a614ebb..2ea7816 100644 --- a/pytorch_tools/modules/residual.py +++ b/pytorch_tools/modules/residual.py @@ -82,11 +82,11 @@ def get_attn(attn_type): class DepthwiseSeparableConv(nn.Sequential): """Depthwise separable conv with BN after depthwise & pointwise.""" - def __init__(self, in_channels, out_channels, stride=1, dilation=1, norm_layer=ABN, norm_act="relu"): + def __init__(self, in_channels, out_channels, stride=1, dilation=1, norm_layer=ABN, norm_act="relu", use_norm=True): modules = [ conv3x3(in_channels, in_channels, stride=stride, groups=in_channels, dilation=dilation), - conv1x1(in_channels, out_channels), - norm_layer(out_channels, activation=norm_act), + conv1x1(in_channels, out_channels, bias=True), # in efficient det they for some reason add bias + norm_layer(out_channels, activation=norm_act) if use_norm else nn.Identity(), ] super().__init__(*modules) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py new file mode 100644 index 0000000..6a5a910 --- /dev/null +++ b/pytorch_tools/modules/tf_same_ops.py @@ -0,0 +1,68 @@ +"""Implementations of Conv2d and MaxPool which match Tensorflow `same` padding""" +import math +import torch +import torch.nn as nn +import torch.nn.functional as F + +class Conv2dSamePadding(nn.Conv2d): + """ + Assymetric padding matching TensorFlow `same` + """ + def forward(self, x): + h, w = x.shape[-2:] + pad_w = (math.ceil(w / self.stride[1]) - 1) * self.stride[1] - w + self.kernel_size[1] + pad_h = (math.ceil(h / self.stride[0]) - 1) * self.stride[0] - h + self.kernel_size[0] + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) + return super().forward(x) + + +class MaxPool2dSamePadding(nn.MaxPool2d): + """ + Assymetric padding matching TensorFlow `same` + """ + def forward(self, x): + h, w = x.shape[-2:] + pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size + pad_h = (math.ceil(h / self.stride) - 1) * self.stride - h + self.kernel_size + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) + return super().forward(x) + + +def conv_to_same_conv(module): + module_output = module + # skip 1x1 convs + if isinstance(module, nn.Conv2d) and module.kernel_size[0] != 1: + module_output = Conv2dSamePadding( + in_channels=module.in_channels, + out_channels=module.out_channels, + kernel_size=module.kernel_size, + stride=module.stride, + padding=0, # explicitly set to 0 + dilation=module.dilation, + groups=module.groups, + bias=module.bias is not None, + ) + with torch.no_grad(): + module_output.weight.copy_(module.weight) + module_output.weight.requires_grad = module.weight.requires_grad + if module.bias is not None: + module_output.bias.copy_(module.bias) + module_output.bias.requires_grad = module.bias.requires_grad + + for name, child in module.named_children(): + module_output.add_module(name, conv_to_same_conv(child)) + del module + return module_output + +def maxpool_to_same_maxpool(module): + module_output = module + if isinstance(module, nn.MaxPool2d): + module_output = MaxPool2dSamePadding( + kernel_size=module.kernel_size, + stride=module.stride, + padding=0, # explicitly set to 0 + ) + for name, child in module.named_children(): + module_output.add_module(name, maxpool_to_same_maxpool(child)) + del module + return module_output \ No newline at end of file diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index a250524..ebe614b 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -5,10 +5,6 @@ # implements idea from `Weight Standardization` paper https://arxiv.org/abs/1903.10520 # eps is inside sqrt to avoid overflow Idea from https://arxiv.org/abs/1911.05920 class WS_Conv2d(nn.Conv2d): - def __init__(self, in_channels, out_channels, kernel_size, stride=1, - padding=0, dilation=1, groups=1, bias=True): - super().__init__(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias) - def forward(self, x): weight = self.weight weight = weight.sub(weight.mean(dim=(1, 2, 3), keepdim=True)) From f46b3416f8a885422705c4558d4e4047c179e49c Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 20 May 2020 20:11:26 +0300 Subject: [PATCH 27/54] add working EffDet --- pytorch_tools/detection_models/__init__.py | 9 +- .../detection_models/efficientdet.py | 149 +++++++++++++ pytorch_tools/modules/bifpn.py | 211 ++++++++++-------- 3 files changed, 273 insertions(+), 96 deletions(-) create mode 100644 pytorch_tools/detection_models/efficientdet.py diff --git a/pytorch_tools/detection_models/__init__.py b/pytorch_tools/detection_models/__init__.py index 7c7fc59..8f2dd8e 100644 --- a/pytorch_tools/detection_models/__init__.py +++ b/pytorch_tools/detection_models/__init__.py @@ -1 +1,8 @@ -from .retinanet import RetinaNet \ No newline at end of file +from .retinanet import RetinaNet +from .efficientdet import efficientdet_b0 +from .efficientdet import efficientdet_b1 +from .efficientdet import efficientdet_b2 +from .efficientdet import efficientdet_b3 +from .efficientdet import efficientdet_b4 +from .efficientdet import efficientdet_b5 +from .efficientdet import efficientdet_b6 \ No newline at end of file diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py new file mode 100644 index 0000000..dbd617b --- /dev/null +++ b/pytorch_tools/detection_models/efficientdet.py @@ -0,0 +1,149 @@ +import torch +import torch.nn as nn +from pytorch_tools.modules import ABN +from pytorch_tools.modules.bifpn import BiFPN +from pytorch_tools.modules import bn_from_name +from pytorch_tools.modules.residual import conv1x1 +from pytorch_tools.modules.residual import conv3x3 +from pytorch_tools.modules.residual import DepthwiseSeparableConv +from pytorch_tools.modules.tf_same_ops import conv_to_same_conv +from pytorch_tools.modules.tf_same_ops import maxpool_to_same_maxpool +from pytorch_tools.segmentation_models.encoders import get_encoder + +def patch_bn(module): + """TF ported weights use slightly different eps in BN. Need to adjust for better performance""" + if isinstance(module, ABN): + module.eps = 1e-3 + module.momentum = 1e-2 + for m in module.children(): + patch_bn(m) + +class EfficientDet(nn.Module): + def __init__( + self, + encoder_name="efficientnet_b0", + encoder_weights="imagenet", + pyramid_channels=64, + num_fpn_layers=3, + num_head_repeats=3, + num_classes=90, + drop_connect_rate=0, + encoder_norm_layer="abn", # TODO: set to frozenabn when ready + encoder_norm_act="swish", + decoder_norm_layer="abn", + decoder_norm_act="swish", + match_tf_same_padding=False, + ): + super().__init__() + self.encoder = get_encoder( + encoder_name, + norm_layer=encoder_norm_layer, + norm_act=encoder_norm_act, + encoder_weights=encoder_weights, + ) + norm_layer = bn_from_name(decoder_norm_layer) + bn_args = dict(norm_layer=norm_layer, norm_act=decoder_norm_act) + self.pyramid6 = nn.Sequential( + conv1x1(self.encoder.out_shapes[0], pyramid_channels, bias=True), + norm_layer(pyramid_channels, activation="identity"), + nn.MaxPool2d(3, stride=2, padding=1) + ) + self.pyramid7 = nn.MaxPool2d(3, stride=2, padding=1) # in EffDet it's a simple maxpool + + self.bifpn = BiFPN( + self.encoder.out_shapes[:-2], + pyramid_channels=pyramid_channels, + num_layers=num_fpn_layers, + **bn_args, + ) + + def make_head(out_size): + layers = [] + for _ in range(num_head_repeats): + # TODO: add drop connect + layers += [DepthwiseSeparableConv(pyramid_channels, pyramid_channels, use_norm=False)] + layers += [DepthwiseSeparableConv(pyramid_channels, out_size, use_norm=False)] + return nn.ModuleList(layers) + + def make_head_norm(): + return nn.ModuleList([ + nn.ModuleList( + [ + norm_layer(pyramid_channels, activation=decoder_norm_act) + for _ in range(num_head_repeats) + ] + [nn.Identity()] # no bn after last depthwise conv + ) + for _ in range(5) + ]) + anchors_per_location = 9 # TODO: maybe allow to pass this arg? + self.cls_head_convs = make_head(num_classes * anchors_per_location) + self.cls_head_norms = make_head_norm() + self.box_head_convs = make_head(4 * anchors_per_location) + self.box_head_norms = make_head_norm() + self.num_classes = num_classes + + patch_bn(self) + if match_tf_same_padding: + conv_to_same_conv(self) + maxpool_to_same_maxpool(self) + + + def forward(self, x): + # don't use p2 and p1 + p5, p4, p3, _, _ = self.encoder(x) + # coarser FPN levels + p6 = self.pyramid6(p5) + p7 = self.pyramid7(p6) + features = [p7, p6, p5, p4, p3] + # enhance features + features = self.bifpn(features) + # want features from lowest OS to highest to align with `generate_anchors_boxes` function + features = list(reversed(features)) + class_outputs = [] + box_outputs = [] + for feat, (cls_bns, box_bns) in zip(features, zip(self.cls_head_norms, self.box_head_norms)): + cls_feat, box_feat = feat, feat + for cls_conv, cls_bn in zip(self.cls_head_convs, cls_bns): + cls_feat = cls_bn(cls_conv(cls_feat)) + for box_conv, box_bn in zip(self.box_head_convs, box_bns): + box_feat = box_bn(box_conv(box_feat)) + + box_feat = box_feat.permute(0, 2, 3, 1) + box_outputs.append(box_feat.contiguous().view(box_feat.shape[0], -1, 4)) + + cls_feat = cls_feat.permute(0, 2, 3, 1) + class_outputs.append(cls_feat.contiguous().view(cls_feat.shape[0], -1, self.num_classes)) + + # TODO: return back to simplier transpose operations + # class_outputs.append(cls_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, self.num_classes)) + # box_outputs.append(box_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, 4)) + + class_outputs = torch.cat(class_outputs , 1) + box_outputs = torch.cat(box_outputs, 1) + return class_outputs, box_outputs + + + +def efficientdet_b0(**kwargs): + return EfficientDet(**kwargs) + +def efficientdet_b1(**kwargs): + return EfficientDet( + encoder_name="efficientnet_b1", pyramid_channels=88, num_fpn_layers=4, num_head_repeats=3, **kwargs) + +def efficientdet_b2(**kwargs): + return EfficientDet(encoder_name="efficientnet_b2", pyramid_channels=112, num_fpn_layers=5, num_head_repeats=3, **kwargs) + +def efficientdet_b3(**kwargs): + return EfficientDet(encoder_name="efficientnet_b3", pyramid_channels=160, num_fpn_layers=6, num_head_repeats=4, **kwargs) + +def efficientdet_b4(**kwargs): + return EfficientDet(encoder_name="efficientnet_b4", pyramid_channels=224, num_fpn_layers=7, num_head_repeats=4, **kwargs) + +def efficientdet_b5(**kwargs): + return EfficientDet(encoder_name="efficientnet_b5", pyramid_channels=288, num_fpn_layers=7, num_head_repeats=4, **kwargs) + +def efficientdet_b6(*args, **kwargs): + return EfficientDet(encoder_name="efficientnet_b6", pyramid_channels=384, num_fpn_layers=8, num_head_repeats=5, **kwargs) + +# No B7 because it's the same model as B6 but with larger input \ No newline at end of file diff --git a/pytorch_tools/modules/bifpn.py b/pytorch_tools/modules/bifpn.py index 2f603c2..406df57 100644 --- a/pytorch_tools/modules/bifpn.py +++ b/pytorch_tools/modules/bifpn.py @@ -2,17 +2,21 @@ import torch.nn as nn import torch.nn.functional as F +from .activations import activation_from_name from .residual import DepthwiseSeparableConv +from .residual import conv1x1 +from . import ABN class FastNormalizedFusion(nn.Module): """Combines 2 or 3 feature maps into one with weights. Args: input_num (int): 2 for intermediate features, 3 for output features """ - def __init__(self, in_nodes): + def __init__(self, in_nodes, activation="relu"): super().__init__() self.weights = nn.Parameter(torch.ones(in_nodes, dtype=torch.float32)) - self.register_buffer("eps", torch.tensor(0.0001)) + self.eps = 1e-4 + self.act = activation_from_name(activation) def forward(self, *features): # Assure that weights are positive (see paper) @@ -20,103 +24,138 @@ def forward(self, *features): # Normalize weights weights /= weights.sum() + self.eps fused_features = sum([p * w for p, w in zip(features, weights)]) - return fused_features - + return self.act(fused_features) +# need to create weights to allow loading anyway. So inherit from FastNormalizedFusion for simplicity +class SumFusion(FastNormalizedFusion): + def forward(self, *features): + return self.act(sum(features)) -# close to one in the paper class BiFPNLayer(nn.Module): r"""Builds one layer of Bi-directional Feature Pyramid Network Args: channels (int): Number of channels in each feature map after BiFPN. Defaults to 64. - downsample_by_stride (bool): If True, use convolution layer with stride=2 instead of F.interpolate - upsample_mode (str): how to upsample low resolution features during top_down pathway. - See F.interpolate mode for details. - + Input: - features (List): 5 feature maps from encoder with resolution from 1/32 to 1/2 + features (List): 5 feature maps from encoder with resolution from 1/128 to 1/8 Returns: p_out: features processed by 1 layer of BiFPN """ - def __init__(self, channels=64, output_stride=32, upsample_mode="nearest", **bn_args): - super(BiFPNLayer, self).__init__() - - self.up = nn.Upsample(scale_factor=2, mode=upsample_mode) - self.first_up = self.up if output_stride == 32 else nn.Identity() - last_stride = 2 if output_stride == 32 else 1 - self.down_p2 = DepthwiseSeparableConv(channels, channels, stride=2, **bn_args) - self.down_p3 = DepthwiseSeparableConv(channels, channels, stride=2, **bn_args) - self.down_p4 = DepthwiseSeparableConv(channels, channels, stride=last_stride, **bn_args) - - ## TODO (jamil) 11.02.2020 Rewrite this using list comprehensions - self.fuse_p4_td = FastNormalizedFusion(in_nodes=2) - self.fuse_p3_td = FastNormalizedFusion(in_nodes=2) - self.fuse_p2_td = FastNormalizedFusion(in_nodes=2) - self.fuse_p1_td = FastNormalizedFusion(in_nodes=2) + def __init__(self, channels=64, norm_layer=ABN, norm_act="relu"): + super().__init__() - # Top-down pathway, no block for P1 layer - self.p4_td = DepthwiseSeparableConv(channels, channels, **bn_args) - self.p3_td = DepthwiseSeparableConv(channels, channels, **bn_args) - self.p2_td = DepthwiseSeparableConv(channels, channels, **bn_args) + self.up = nn.Upsample(scale_factor=2, mode="nearest") + self.down = nn.MaxPool2d(3, stride=2, padding=1) # padding=1 TODO: change back + + # disable attention for large models. This is very dirty way to check that it's B6 & B7. But i don't care + Fusion = FastNormalizedFusion if channels < 288 else SumFusion + + # There is no activation in SeparableConvs, instead activation is in fusion layer + self.fuse_p6_up = Fusion(in_nodes=2, activation=norm_act) + self.fuse_p5_up = Fusion(in_nodes=2, activation=norm_act) + self.fuse_p4_up = Fusion(in_nodes=2, activation=norm_act) + + self.fuse_p3_out = Fusion(in_nodes=2, activation=norm_act) + self.fuse_p4_out = Fusion(in_nodes=3, activation=norm_act) + self.fuse_p5_out = Fusion(in_nodes=3, activation=norm_act) + self.fuse_p6_out = Fusion(in_nodes=3, activation=norm_act) + self.fuse_p7_out = Fusion(in_nodes=2, activation=norm_act) + + bn_args = dict(norm_layer=norm_layer, norm_act="identity") + # Top-down pathway, no block for P7 layer + self.p6_up = DepthwiseSeparableConv(channels, channels, **bn_args) + self.p5_up = DepthwiseSeparableConv(channels, channels, **bn_args) + self.p4_up = DepthwiseSeparableConv(channels, channels, **bn_args) # Bottom-up pathway - self.fuse_p2_out = FastNormalizedFusion(in_nodes=3) - self.fuse_p3_out = FastNormalizedFusion(in_nodes=3) - self.fuse_p4_out = FastNormalizedFusion(in_nodes=3) - self.fuse_p5_out = FastNormalizedFusion(in_nodes=2) - - self.p5_out = DepthwiseSeparableConv(channels, channels, **bn_args) - self.p4_out = DepthwiseSeparableConv(channels, channels, **bn_args) self.p3_out = DepthwiseSeparableConv(channels, channels, **bn_args) - + self.p4_out = DepthwiseSeparableConv(channels, channels, **bn_args) + self.p5_out = DepthwiseSeparableConv(channels, channels, **bn_args) + self.p6_out = DepthwiseSeparableConv(channels, channels, **bn_args) + self.p7_out = DepthwiseSeparableConv(channels, channels, **bn_args) def forward(self, features): - p5_inp, p4_inp, p3_inp, p2_inp = features + + # p7, p6, p5, p4, p3 + p7_in, p6_in, p5_in, p4_in, p3_in = features - # Top-down pathway - p4_td = self.p4_td(self.fuse_p4_td(p4_inp, self.first_up(p5_inp))) - p3_td = self.p3_td(self.fuse_p3_td(p3_inp, self.up(p4_td))) - p2_out = self.p2_td(self.fuse_p2_td(p2_inp, self.up(p3_td))) + # Top-down pathway (from low res to high res) + p6_up = self.p6_up(self.fuse_p6_up(p6_in, self.up(p7_in))) + p5_up = self.p5_up(self.fuse_p5_up(p5_in, self.up(p6_up))) + p4_up = self.p4_up(self.fuse_p4_up(p4_in, self.up(p5_up))) + p3_out = self.p3_out(self.fuse_p3_out(p3_in, self.up(p4_up))) + + # Bottom-Up Pathway (from high res to low res) + p4_out = self.p4_out(self.fuse_p4_out(p4_in, p4_up, self.down(p3_out))) + p5_out = self.p5_out(self.fuse_p5_out(p5_in, p5_up, self.down(p4_out))) + p6_out = self.p6_out(self.fuse_p6_out(p6_in, p6_up, self.down(p5_out))) + p7_out = self.p7_out(self.fuse_p7_out(p7_in, self.down(p6_out))) + + return p7_out, p6_out, p5_out, p4_out, p3_out + +# additionally downsamples the input +class FirstBiFPNLayer(BiFPNLayer): + def __init__(self, encoder_channels, channels=64, norm_layer=ABN, norm_act="relu"): + super().__init__(channels=channels, norm_layer=norm_layer, norm_act=norm_act) + + # TODO: later remove bias from downsample + self.p5_downsample_1 = nn.Sequential( + conv1x1(encoder_channels[0], channels, bias=True), + norm_layer(channels, activation="identity") + ) + self.p4_downsample_1 = nn.Sequential( + conv1x1(encoder_channels[1], channels, bias=True), + norm_layer(channels, activation="identity") + ) + self.p3_downsample_1 = nn.Sequential( + conv1x1(encoder_channels[2], channels, bias=True), + norm_layer(channels, activation="identity") + ) + + # Devil is in the details. In original repo they use 2 different downsamples from encoder channels + # it makes sense to preseve more information, but most of implementations in the internet + # use output of the first downsample + self.p4_downsample_2 = nn.Sequential( + conv1x1(encoder_channels[1], channels, bias=True), + norm_layer(channels, activation="identity") + ) + self.p5_downsample_2 = nn.Sequential( + conv1x1(encoder_channels[0], channels, bias=True), + norm_layer(channels, activation="identity") + ) + # only one downsample for p3 - # Calculate Bottom-Up Pathway - p3_out = self.p3_out(self.fuse_p3_out(p3_inp, p3_td, self.down_p2(p2_out))) - p4_out = self.p4_out(self.fuse_p4_out(p4_inp, p4_td, self.down_p3(p3_out))) - p5_out = self.p5_out(self.fuse_p5_out(p5_inp, self.down_p4(p4_out))) - return p5_out, p4_out, p3_out, p2_out + def forward(self, features): -# very simplified -class SimpleBiFPNLayer(nn.Module): - def __init__(self, channels=64, **bn_args): - super(SimpleBiFPNLayer, self).__init__() + # p7, p6, p5, p4, p3 + p7_in, p6_in, p5_in, p4_in, p3_in = features - self.up = nn.Upsample(scale_factor=2, mode="nearest") - self.down_p2 = DepthwiseSeparableConv(channels, channels, stride=2) - self.down_p3 = DepthwiseSeparableConv(channels, channels, stride=2) - self.down_p4 = DepthwiseSeparableConv(channels, channels, stride=2) + # downsample input's convs + p5_in_down1 = self.p5_downsample_1(p5_in) + p5_in_down2 = self.p5_downsample_2(p5_in) + p4_in_down1 = self.p4_downsample_1(p4_in) + p4_in_down2 = self.p4_downsample_2(p4_in) + p3_in_down1 = self.p3_downsample_1(p3_in) - self.fuse = sum + # Top-down pathway (from low res to high res) + p6_up = self.p6_up(self.fuse_p6_up(p6_in, self.up(p7_in))) + p5_up = self.p5_up(self.fuse_p5_up(p5_in_down1, self.up(p6_up))) + p4_up = self.p4_up(self.fuse_p4_up(p4_in_down1, self.up(p5_up))) + p3_out = self.p3_out(self.fuse_p3_out(p3_in_down1, self.up(p4_up))) - def forward(self, features): - p5_inp, p4_inp, p3_inp, p2_inp = features - - # Top-down pathway - p4_td = self.fuse(p4_inp, self.up(p5_inp)) - p3_td = self.fuse(p3_inp, self.up(p4_td)) - p2_out = self.fuse(p2_inp, self.up(p3_td)) + # Bottom-Up Pathway (from high res to low res) + p4_out = self.p4_out(self.fuse_p4_out(p4_in_down2, p4_up, self.down(p3_out))) + p5_out = self.p5_out(self.fuse_p5_out(p5_in_down2, p5_up, self.down(p4_out))) + p6_out = self.p6_out(self.fuse_p6_out(p6_in, p6_up, self.down(p5_out))) + p7_out = self.p7_out(self.fuse_p7_out(p7_in, self.down(p6_out))) - # Calculate Bottom-Up Pathway - p3_out = self.fuse(p3_inp, p3_td, self.down_p2(p2_out)) - p4_out = self.fuse(p4_inp, p4_td, self.down_p3(p3_out)) - p5_out = self.fuse(p5_inp, self.down_p4(p4_out)) + return p7_out, p6_out, p5_out, p4_out, p3_out - return p5_out, p4_out, p3_out, p2_out - - -class BiFPN(nn.Module): +class BiFPN(nn.Sequential): """ Implementation of Bi-directional Feature Pyramid Network @@ -131,28 +170,10 @@ class BiFPN(nn.Module): https://arxiv.org/pdf/1911.09070.pdf """ - def __init__( - self, - encoder_channels, - pyramid_channels=64, - num_layers=1, - output_stride=32, - **bn_args, - ): - super(BiFPN, self).__init__() - - self.input_convs = nn.ModuleList([nn.Conv2d(in_ch, pyramid_channels, 1) for in_ch in encoder_channels]) - - bifpns = [] - for _ in range(num_layers): - bifpns.append(BiFPNLayer(pyramid_channels, output_stride, **bn_args)) - self.bifpn = nn.Sequential(*bifpns) - - def forward(self, features): - - # Preprocces raw encoder features - p5, p4, p3, p2 = [inp_conv(feature) for inp_conv, feature in zip(self.input_convs, features)] - + def __init__(self, encoder_channels, pyramid_channels=64, num_layers=1, **bn_args): + # First layer preprocesses raw encoder features + bifpns = [FirstBiFPNLayer(encoder_channels, pyramid_channels, **bn_args)] # Apply BiFPN block `num_layers` times - p5_out, p4_out, p3_out, p2_out = self.bifpn([p5, p4, p3, p2]) - return p5_out, p4_out, p3_out, p2_out + for _ in range(num_layers - 1): + bifpns.append(BiFPNLayer(pyramid_channels, **bn_args)) + super().__init__(*bifpns) \ No newline at end of file From d3500e6fea1b9a4f535053d5c33c207af62d97da Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 20 May 2020 20:12:35 +0300 Subject: [PATCH 28/54] minor --- README.md | 2 +- tests/benchmarking/memory_test.py | 16 +++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c29c172..cdfa53b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Tool box for PyTorch for fast prototyping. * [TTA wrapper](./pytorch_tools/tta_wrapper/) - wrapper for easy test-time augmentation # Installation -Requeres GPU drivers and CUDA already installed. +Requires GPU drivers and CUDA already installed. `pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" git+https://github.com/NVIDIA/apex.git` `pip install git+https://github.com/bonlime/pytorch-tools.git@master` diff --git a/tests/benchmarking/memory_test.py b/tests/benchmarking/memory_test.py index 08c83be..c355f3f 100644 --- a/tests/benchmarking/memory_test.py +++ b/tests/benchmarking/memory_test.py @@ -40,6 +40,8 @@ def test_model(model, forward_only=False): def run_once(): start.record() output = model(INP) + if isinstance(output, tuple) and len(output) > 1: + output = output[0] f_end.record() if forward_only: torch.cuda.synchronize() @@ -70,10 +72,11 @@ def run_once(): f_times = np.array(f_times) fb_times = np.array(fb_times) print( - "Mean of {} runs {} iters each BS={}:\n\t {:.2f}+-{:.2f} msecs Forward. {:.2f}+-{:.2f} msecs Backward. Max memory: {:.2f}Mb. {:.2f} imgs/sec".format( + "Mean of {} runs {} iters each BS={}, SZ={}:\n\t {:.2f}+-{:.2f} msecs Forward. {:.2f}+-{:.2f} msecs Backward. Max memory: {:.2f}Mb. {:.2f} imgs/sec".format( N_RUNS, RUN_ITERS, BS, + SZ, f_times.mean(), f_times.std(), (fb_times - f_times).mean(), @@ -95,7 +98,10 @@ def run_once(): "--amp", action="store_true", help="Measure speed using apex mixed precision" ) parser.add_argument( - "--bs", default=64, type=int, help="BS for classification", + "--bs", default=64, type=int, help="BS for benchmarking", + ) + parser.add_argument( + "--sz", default=224, type=int, help="Size of images for benchmarking", ) args = parser.parse_args() # all models are first init to cpu memory to find errors earlier @@ -134,9 +140,10 @@ def run_once(): print("Initialized models") BS = args.bs + SZ = args.sz N_RUNS = 10 RUN_ITERS = 10 - INP = torch.ones((BS, 3, 224, 224), requires_grad=not args.forward).cuda(0) + INP = torch.ones((BS, 3, SZ, SZ), requires_grad=not args.forward).cuda(0) for name, model in models_dict.items(): print(f"{name} {count_parameters(model) / 1e6:.2f}M params") model = model.cuda(0) @@ -148,8 +155,7 @@ def run_once(): test_model(model, forward_only=args.forward) # now test segmentation models - BS = 16 - INP = torch.ones((BS, 3, 224, 224), requires_grad=True).cuda(0) + INP = torch.ones((BS, 3, SZ, SZ), requires_grad=True).cuda(0) for name, model in segm_models_dict.items(): enc_params = count_parameters(model.encoder) / 1e6 From ae744fc4e34209571134da61f34e7725c09fc44a Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 21 May 2020 16:10:17 +0300 Subject: [PATCH 29/54] shorter weight standardization --- pytorch_tools/modules/weight_standartization.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index ebe614b..63d0b5d 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -5,11 +5,11 @@ # implements idea from `Weight Standardization` paper https://arxiv.org/abs/1903.10520 # eps is inside sqrt to avoid overflow Idea from https://arxiv.org/abs/1911.05920 class WS_Conv2d(nn.Conv2d): + def forward(self, x): weight = self.weight - weight = weight.sub(weight.mean(dim=(1, 2, 3), keepdim=True)) - std = weight.var(dim=(1, 2, 3), keepdim=True).add_(1e-7).sqrt_() - weight = weight.div(std.expand_as(weight)) + var, mean = torch.var_mean(weight, dim=[1, 2, 3], keepdim=True, unbiased=False) + weight = (weight - mean) / torch.sqrt(var + 1e-7) return F.conv2d(x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups) # code from SyncBatchNorm in pytorch From ed3ab31431b6822c64bfa336b7dbd914391f63cc Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 21 May 2020 16:10:53 +0300 Subject: [PATCH 30/54] add new test imgs --- tests/imgs/cityscapes_sample.jpg | Bin 0 -> 49918 bytes tests/imgs/cityscapes_sample2.jpg | Bin 0 -> 428014 bytes tests/{models => }/imgs/dog.jpg | Bin tests/{models => }/imgs/helmet.jpeg | Bin tests/models/test_weights.py | 6 +++--- 5 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 tests/imgs/cityscapes_sample.jpg create mode 100644 tests/imgs/cityscapes_sample2.jpg rename tests/{models => }/imgs/dog.jpg (100%) rename tests/{models => }/imgs/helmet.jpeg (100%) diff --git a/tests/imgs/cityscapes_sample.jpg b/tests/imgs/cityscapes_sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9e401d85aeeb9b3503f7b33fd12bde40b637707e GIT binary patch literal 49918 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 z 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�C%EWLnj=2K4k*!=>%`LsF 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 z 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

0LYOidyVC1#;lz^H%^-^EWOS5%1Eg!lVR~2WNbA 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%0DgV+51DM#_+QjxdKXt)FAQ;}J^ky+=a z+;_;ub3QiHAidM=9n^1SY?0LZS1jdu-E7aI!bWr{sLP?`HaE{{D3%u5!>aZ_g?mTC zZ-~MPB8p}$2_Q0_O8q^n#Zuag|8NmmrCl!C;{{Y2}OTiaOq4AEL9&U#mXd@k{< zns%ievEDo=*bIc^XWVwLrUj0d@drp~?;%CFWdOE0&teayDZA>OpGVW?cop#6x=CGs zUvblFzaD%wrJa)M3ZwvXyyW{4+wiX?@xR5LC&j)cv3)95y4a2+`A7q%I@cHDPXS$c zZb>a~Vri`h+?yFFJ7?OtUk2#X>3$~}8{lOnWa=^LPIVHgVWb<8j!Jm(MPk=O=yB$Sl^I5|$mb-!DzdTnh@ft7mm_Qt0*OXtx>vs6h zQPQTInvIh^8BvXO$t{n${2lS%OYzOruv#s|epD?fT#yJINx`gi{iA#@HlnuiLvUm- zv&?MZ9OI!rwc>xXd&&L3e;63uV>U8z*jE9jnSbFbT!!M~Cxh$P>CJB_u3O(%zQ>Ua z4L6CECf$-vYv_Gx;;)PrKMrEW<&$0+;=G$!(*=g3tXe{(!k}&U7|8x~p(hr+)yk!X zr7Ov|blO_>W}d6#**sMCbG(0MlGYzBb{y^=q<}w_Lu;)@5E-qOSx+WLW6$6y_;2A4 z?ZW9hm;xbek(%#E!s|>bMR2bBLk-GD9ly^N$vNx2?0ejhOODHs!SnJpeeX zQkjdPPNGe#uRr)Fs9gTgo-Mh^TcwMPe7PUY*9GGb5^A0~xk+_N{P_?Nr=|vT=~*jv zeIer5AEFwMSAzZRk)lRc##?HUMdaO^Dl{omYEgk7B3bj&NU47R70D^W}ma^9V8nk7qQy%BNYu`Z2s@y=av3=wyQIlOxe6UR5tmzhx{S%(^ii5MK7H_i?EB&iR4?UhcE)>RyNiF> z=I(SN?&>5`K4vTqE4;b!J?yE2I3D=H6^}Nzdn+CHEyo?JPh_3$jHObgM|R9@Gxm1S zH1^HK@`4WI@)9%Z2sr%(b@2Ej!pySf8CB2=<-*9>9!2W%zKb5&@ z*yr4TJbzl!P>zns$x2H1XF(r^yd`3!C8pE$1Nm2ve0A}*k>Tq*O+QGq32^d{HOD1W zxdeBv=4fJ9Al%@3F(>t|N5y^;ven@-LR0M^L%I8mO zdGzRR2phcVS10M5*QSpbX>lkCrrM(ppSn?j{LOiejiuOWRxw02hX8IWF_Tl=-CD*9 zug(0bmDasNaZT=eA@S|BSOzGOM?fRM{VFl>A|+-UBPvH607vCsUoG{z9C>5(sLr7P z{{WUt`3iSQ=q_C>+3J7rvS3BPSc7`vll~S=Sp~eOUrrD7uO>I}6U=M=e;SRiZQFqu zALr{slC`W-gYJ6h_>BVqJh(y6pgew6Jbw|Q!3HP}cmPJZC-UR3Vg=`MQ0&D4i+T-HA zw}AXLZ}9WOt>ncVDla^vf>48q@(%@%Cbb8FK_F+|xu1tIt(0?5AmQ+<$34mZV!M0D(nMzm_({iOU3Aun z%Cwg?j#E&-DG@H&u5N4ZGJG7A9ap*bt2)&CuGkJ0G4v*}Mr4ybp1*}}YZ-5;sdK5z zBq0} z%O}l_{cEDQM2g)}fF$y30_HFom=H#4letS&RAxEH2mJjiCt^4iD!2`kP&*NxhKF+P z+yFS_b6VOZtIV<}$jJQb6fq{d{{V*?{kD#mBI?~{-Uiaik`8G-f=#5_F~8v*y^#E{ zJ`c5PTj>|+Zir)$#_87c zSzV-(d67n^C9{q_D|=G$5?wYe(EO{k9*6X(JVE1YJyPAEh$@E0S!2P+)YV-lR=2iM z_H;Xk0gqbBR3j&MxYb6M#rBs`)hxcv_6`_eJ`eS-TJysfwodX3R~rw_^5hX+4cCrj zhbV>J)Z-w4zsn}8uZ}NI5&`A64U$hEon+w%T_Z&~OH+%~F4VZh9$SO+H%>h(TS@s- z&WRYQUQRLr_pL2uto3Vr%NoTg84{;M^{o8@ac$<=OyPG7#@&zOSk#V=$3;2cb3*FY zJz^sZa@z?Q>z>~A<9;*J%&{}$4;dl2=N0N+AJoF?xF>~jxgG1D@tumttX@P$Bao5+ z^dh<8Cl+?3nzwU>_-*0|G#?RJT&Gy11gYr5BO~z@_EgN08+())is~^c9^C;y&b}Q; zA57L22qO$Ie;WIvL9&xj_+6lC%DIj>(j5DNgP+8HqP=cqWYNnwwuq;*Gs59;Ln#Cv zg!HeKd|jeRr0ZImd0SB(f`Q2Eoc^`-O!|V^>61)8@{A0Q{Ri}~7V%x2R}xF+GKnQ$ zmv>ep9S`GK)_NvHE}JsG1o+Nf3g+g*`#)oucMy7xa0t&GPCp-@u5-orP}}&v^d@4o zoy(v+V1N4SF}&R>`DQH3yDH-yyN~Nwny#gDsa+~QUtu_q=OAaMYldyfah8)9-vgWid;8UU z-7bAG;(35ol<|TN4_eMN)NYxYS6Xa!P-~J1yvSrB<8a$ou^kBfYweHOr}mVSQ}E*H z8pZOZ&6)*CWGCl91_nvVJQI^&1Y7DTlg7w^05-lm*Hfpp-jq{Nv)o}`u7~)0it3?= zo*CXcr_q^8jB2!}b#BM$%lu!mo>;c(Q29!CF=y&}j^ECfUyPP{`E^@m_6I+xAI`rt zbsb;DdW_M@rrhEH0aruG`sckPYQ8%I4}ReH&OgGv{61k$Yxl6TTU%3-whcx8=67Cd z%>61~jJApLwyOf;rbz@3%9VaJ+XmX2mAsk%0B`>QtzViaSnQ03 z<~&|I@q7)2yi!M~AK_Oa_>Y9|Ao`&rj5i4RjVqt=f*El^t!nkK%g=FDHr`>fro(8Q1rB*E`v*>bG zKM}4p9a#;&;W9Q>P{eH`>Gb~qJ-f|Q_OJ0`T12+UC6Pc^4V}dHJazv7KZl1-p{KYK z3^8N%9RC1~X=vq|<_VOQl^BiK$j_l2!5;PD`$tYrN!aOhqSmPGZ$D{&h&MrsmWUrn z&0}2rdH9JjAYV%wp1&q5rnc8~>Fsw*2oiMIm^e7+C+q(J>aJGub2O#+F8#;7ugypLGfGS80mFz-%(7xuy}5~ zt=T8NS<~!fwV7slf}-=!Pvt{SDQY(~^|B-X0ENTj%Xaytmm{v_#cSAp(4Hl=LYKDY z;Yh(Dijnx{sq5N|Eo_JinM-Gkbo?uqxVSHnLXqB`<-PSY(p?WmwD_MFjPYr9UKain zA>35^8mVidPpHjz5R(^n=W+l}S$h$K&3TM+n+y1E6Mz_J>7IW|?k+WZoi1oE?H>{# z1a}O*!S$|s$;mf+vv@^CB#*AYVr@fI)x1%09Cm_x*y8{pAO;F~BXQ&LueN?D_>pu! zhdR7hwsI`;*+_yk0hDD(&PPFCmcIu+JZfJLJW*rf4JBe*sEJwPAe3;!2O~Mb2k;;s zafA9W+T>ps{2zUNE~K+*&7vEYiqwD-Qn@470P)W}R~%c7nqMtH;m+=LHSF)Y>({yY zbK)rNbuD7`+8G?@-V*o6GGw zRe8SF>R}@!j&p(B@t?-Bd^0w^s`y@8r?ZyDlpU;zkjELx^ck;0)9kHh@x{yBfiw{6 zEx11@fr(s^(;VWuj|i-$&s{dn=Nov#tZD&_<$wbRz7A{6lc=rA?!T{*ys6Wu)s>yT z{{TO{@cV1MUf)U=_c9%h<}_@5@-R<7iQ~2_&pdypTzHz)%Vxw#t+k^c#etrOzdxX_ ztKQNGJ^?|f>N+ydYh!-@0K6Dd$0XxDbCL(8aefu}P~Q`LJ+JF}HJP-M&ef)lDGn5n zNd$VHKb1!_P0CHHJ1(pG+{RQStfKl~<^E^NpAY1?vDR5ZJ3^J2SP%fu_<_ZHJX)5A zab*hK-auHe`A6{aj1kw0@+-LYKM(kVc<$_6EbXwU=*)e6&2IP)P?q1sm+)NJK;vi& z9LfPwxFCUy1CL7UbeyEyNvj;u_pN7dk=FRX!PZ***0Mo(miCas(KOO@&Ua%SxH$Bx zYjXY>v$wI1QE?D$tt5R42pc8_e7uD#Q3%ws|$vhD=M@bzEyuW zt#iW92Z^JwhCvW)?R@7al16JudpdN~T6bM-qa0~TO3P>RIV}T6xU|)!x07UVurOv^ zoOZ#cc%M_d(KRSMxwmH@c;x*E>?>o(Gf$&yUt_g>sj+^14D|J`Kg9NPA0k(fNJtC_ z1fNk@(}zF3H|T50X702)EfdA(W!=5zW)}!mPfe}c1Jb^!{g=KPYZ~>x_AI)2wKL{% zdgA~<4^*pHIAhWSvi5xzn%oT{}>YaLKYM zA0lXD00!DmPC)E1ag22w_=vR|C3~}~jN=(M2eQ!k{{Z&3@Tz4Q$c7qAPTFogpdDm`)d_4clp8%8mU-5S$SlxJ3|yG?#4nN6T-9})aVZufFL(yFX| z@TfD6r-9p#%B*<1;H|cZ1jz3-yD21*$RK2c$NBz#<)V0Y&qNR9Dvji^J;}~M&!sPj zEzsM&s;X}50vdA{`m*`*RxpocTv&) z9a!oT*~l%+mJ%?)+D-xd!S=5+@r8_X z+v9%}-s@KH;(JZH!(uCSjR1|nJe=n_!LONq9r)_+;lGP5wF^mnr7h*Z1cHsh1P;6$ z;=L>46KMV`zO}uz@m1`afO90Tau1fIoPdkibBtFU;MA_3`{}lZQj$$Hw)5ZQc6Xt3 z8{{kP8RwzyKRWZBM#|!Cc586anB|yE&d27$fN(uJ8fKTEYOzl(#nsfe_H4t-kTFoh zxxn|XiaYxY2SKkXA4^Ryz}z=iTKDoH@o$W-d~@SDCW?Lbj5nBLBqEP&bw5h4;XO-J)fV!~8Hh!= zuk>}YZ?w;MYaPpN#ubPM0QVK|emeL;rg+ohCZ~0&>JjMna4>h4=0y!C>Oup-AROTJ z>FP&;e$cYV;(r?5&!yVPG}|>>TReLTx$Q7MAb5v1jrRTSy-70jUPESVq9E=nSk%@+R)P^g# zfC%-*I`hSL(C3X;8)?wvlcy?ewPwGqK1TA&{#nLI^{zw3YQoBQ`>rrg;f(&3+}|hK zI9Yb6&J}q6m4m41sdlYzCg)aCxj%<`^yHgXM=V-RGtCc(FD$O@&86GK8-_$Y6W5X7 zuldQ(8L8`+udB}G@>$Dv!6!NVdHgG{O%`U>BZF!E+>ES!1`a=38AUC~ zHHX9pJOTdz)~cF?%H3K*>iCW&7{{&!Sk{{nuoZ~WJ6T6<2qsyZX~#f6{Z-iFq|+Ta z@}`l8d8u4n+!pg#24J|)QPZEox(^y@%l(UIF7dcyJY%>209v^_g2Pc*RY(Z?e_H9h zNYAP1@kO|~P&VU?8ttUiV$IURA|AJ)EU_;uoo z%`vaEDa50BCI`s`9&!gXcuwz8u#3hKmz7XjG?$>g(Sn#n#Rp5=;`Jo+40n=Gz3yJw*sOzmc_d+&&~W8yCpPkB6%ys|N1 z(V#$m1_uL=&#A6+!PtgLj+8^c!+TZqKAqkAIYG3lSJdA6#C_R-x> z$hiJ=LZXvS*15wfZD`9r9n?}=;5vW{5$#s)yhWu+F$Jf}o)sg=&$U0p{s+I)<@;Rb zA1I+XSyY@6#{lMr`)byJ^WWK{#{3E=E5g&3YGyehg~2`gEyp zdY8ZzT-4soo>??p@Cr#Spu0A)hAI>ugC66tuKMf5vfWw;o+wO(PSF+y(cd`lTHX@) zcYkN#r-7|fIPeFV4oQ^Ya&kHRFoaV0Lk>GT8t|U{Im0Ks+Q@mT?%hzbk zaDWR25)78dTmjeguIowC1R6X{wwSmI10OGzKIa@!QVCkiQzoNp?qvP8JtoE}E+T7* z7-5|AwD!+_m4gPhE-zNy8tPbNVYuLw$ET%lUuk#KTrnL4g)RH z8-FN(6KNSgPNuW=Jg-%$lwI{`gH(O`vq5YjiGqT~V+51wo`a8LQE8>V-RImG(Id!E z=Lb3MnxPi6r)ox7V3pctd=Q}LK7yTjscb}Ng`OxB=W!VW)bma;Y1xI`mC`k?@7{ad z#HtZM-1!}I*p9V+TlSgQvXZPqvtm@1`O<2`t&vDnoo+gEk{vvdwVRXBvZ7W z0MDTB?~MLJy$VkUYWn_=j|G&nv!Fk_LPt+(=Cr?qo+OUyPcuxqMRpwdN2Y$2`nE6X zzQQ)O+VZ}~g@%Pxbx zh8Ko3hzAbgb}mLq$-wR3k81M^FWKwF>*A!IC7rERgR`=g{5k7hzod9mU5`%HEatbC z6Cx^^Hk=R`5H?bhP*P>RhcAr5=< zjNl##1mm}Eo8#BOjVs{ByJ>87RJfLCLonZ#8?rJ+207;#%B9?wxuVT zW}0OUmB0r)5=hAF>t5=90cd_9x%*# ztNQ8qm&wg5MPFxri1KfT`Xz+gsu?azM;Xfmk2wS$hc)--!OsTh(0F*r13=3+W3C;?dJP5iy6d#a=p0suZ2D#c$GBWW@A0X zhC;;eVo77c>(ugd^)>ePr22P^^r;|&QIE;S>3pXIfI0@j;ACg;uZcW+;Vl;TSi7HC z*DbB=?Og5=$_$T#wBU8g#~$?UMz{M0m9B-j zvtQl)q?}FxKQ2imJ~$nT7|AR?ZbSB^_}MRsHA}4)DPv7G$cKrc1eXk;(R@_9Z zD~UPI&_+Qg_(&ai#~o@ZB?&1)_SLJszI|1jNjGh*v{vSgURL85bduAyn|;e~$5ZBy z6n@bDGSe?_H2oJ@yU_ID#a1g>3Ktps%t66FLyT99YkJMzw|zC`{mhq_Qnu+QnTs)SPbY zWgnBDS~m`$Vum?_-+$0@9~2UBv6vSQ`Cw{e?VY`rm_1iG9r$)?RxMGso7kuDPRk z)ABKPjMG|QL+hC)%Zzk9lT|fIiXl)lk%LmAGoi`p!1t;b;7X^CgPP%L>75SOJ}LdF z!^vf?DiXlY5j{xj>t7yRxGE51c6(RZf3*}}WwF-K}k-$<)591K7*Xpzi2a- zzyq4EG|YqmbH^M}zT+D<5<(*faXrpy?HdI7iRT}MR8Z2necX=KdMNj<8=)Lgvkf+7 zcU`DRagj(1kTQOpW}hrTHr(W|sN?z4#^4MUpX(j?U`lHE@% zLi1Hyg18x8#ER*>3%hQGHYb)bIwwdgrx4}{#P(OY4OKd@a~s2)c2z!1yBxH^T7WAIIo$t zKZsi2jP=R2T}I)8Dc!I!A$S-Ap12>6w*Y#-?KPv@$Ks{ZEiU4bEmSKiu=~X1=N)>A z@axD|Pn25j0yu`}anPIt%}gWhWosVyvW^CVs@>-$r;=r#5qwO(()6=;HPMG+kqmjp z-rRfO_5T1P=Wh^r!u!P9o6RimD-vXRBuXHy05|81ahtZ-ieN{4e2K2Da8bD!R0+p+hm?9P(?yY;M`p z<^cUgdI!UA3pa`NXuoxcVqjT^dL-LTN$6!brmnO;yYNQ4;eQfc#i-kOS{*4y7!Ah< z>MOGGf5$yrM7ILT0z()IHnG4Sxa(ejqT2reWZEQA;c%cFeL7USy`pOxbn!6x*-q2% z+PwLtbk@kq5KBodd!5~%$8APUG*3M4n^dzM#{-O>L9DM1e$hIHy>&Sg%8)A+LBF2d z_5Cyd0M_xNsWf^8&9gV}bLHUvMyu%2FNd$99THRz`1hyEuHDLX)solmK7#)Mh4ZRf zc#hW6+T_ZlhLpQ-ppv)*cO80k81IqvIQ^^i-5*s;3*|;nvk4L7IV{7s*VyByUU>K; z!*>GnSi8L)42K@$pYf+z>at1Vd&1;4(l9yzI(F)P&o!gHlvCM1yol4~layP#w^#Xo zXV703{{U%SS6|j5`#eW!$%+uz3ZRT+4!J)0;Pmap{6e~Ud|6>Nz|4|bg5CMa&2Spz zGTdFkbrP3gqBc>JfzD6TyN`-9w0fq2IbEhp6v!U91pfd!UhEWJ?cII|r5Srho6)b- zipvqpu{smGZ}YCJ!x!kcX>MGsZom?~L9a2FQga|!+mrH=e!W4fR+^OdA267+gOE>Z zu6VU+5jt(FrhUiY4+CjZStZTnk;fj;OFIGinz`b1@E)IR(SK}Oq}kzCKuAA;uZXoT zidw9Yc~?55q6Pi{_yl{3=a0pmEo2ePcL+Jb22qpx*FP$gcY1jOQjBbpXS4iK@b!z# z_j+a2QZr`^gb@7)Kj-;Z%^p1PC5_A%x`Wuo_V`qu01Dj(dV6;KYtnS@71`>Ex(U$d z`>mco3b8()Z>?I*6OS=jFu_g4S)q}M0QIjqZ6-@+{{TorgBii#*Hg2zT~66WCw0`W&ev!{ z;0394*cB$sx#}@f+v(-ij4j4{)UfH(8Bg(T$3aEb#64Dr-Cwb0qi=KJ9ZJqXasU|X z)SUkSO7b6%z8!)O6J3%DMv@j7?kmv0VOxlsQNPpQ@5qI*+~aTf)<^A8r8b}93&&ib z;kobYShzS>ZN1h107DElPiYy!bawqtZpTaeI{ceX2VxKM71OP-J{(|ImTqf|({-1T zau**_itM4+EgwoSxeJrHWc@4BjwT7&yPhnmyI948Pn2&l5Jm|of2aBX0PDcAiFoMi z!Bg1x&1qR`h_}94sWG=3kJFCcwV$fnq}MVS97ibh>HMfwBHM6(BDKAm(B!@#Xy0tp zyx7OjAIuGM+9mM4u=0rlSo(U`b9r|>R?&VCD#d#sU{>#@SF_I!J$GsAo zdi6T!tKK~~N2lq2C2I{irb}CUnG|68%bZs)s`#R9a`|PN-rfMY8z52#;xkljHHCui zSjo(CjDc0!9k&I5%AQ4Y&fU$F5_hq&HQaYr$`U1vhZ)*W^);#ENPf?y>2d*#gOF;z zi{Y!yRuUpZw2*Q~73t6Ls`FLR?d8-ahR)g*C0^!Gfc-LgtQ|`iI^$@?aJq(@ErjQCouKjWUr(oiw1^HRy;*Vp06do?^R9o#{sqylG+7?ao9!qv z3jxUd>8guOcQuDL={tHJ2d8Q;Wje})<2-@Idr!mPi1NoRtQQiowG1%Ic?Tn{c!lM> zntj6~4u=(YN7Wv}aKM#D?hiv--IXXtYh6#jH0#|r#u{&LeJjfR zW$^xg1?mFo1T*PQ+lY^<9{B$N5Arp6 z4{&`ATTj`BIbVC)xjyU%=~_22>pu%_roPZ5^YqD^bdcj9;2e?12OMX(9Q5O!*1u{! zLr-w#$=wb?E7>tiFY zwMlLJKHEVckXhSr4Z9{l4t-BG<`DkY5|+ufxI%l1nSW{6Cj>~cp={)`X9w4>_|}RL zYR%nxnl({3B$fM~!>Iftxz$P=xh-Yb*-%?JJaiSCfAEJ)vq>kqxV4Wf$si2-a%;-& zzi3-|5SXrf$Ly>CIsB`V);=iSYlQi-x0UI*9 z)vp&$u_JM54ha~~0R3x4N-Gu}vKyScPSJ^Y|Z{7CkocxKX0A5nJ4 zXrc@Jg>*9N9vISzOKV$=W>su6IoO%+&ws+PuRbKtXL4h+((Lr(klEYj5I(2;{{T%? zV)_uOwyhM&E&LN@J^~BLk7A(z0EKD8@Tp}S*`Bxxk{E8!6Q9*r+BNyj5O$NG`%wA?m-*nY=!sdJc_}&OS{`8lQKba0VP8oeT_m_ zPeC@Dv)LM2FT?4)Gps{@Xu@ZJ?g-$F{c-*^#{68-@AVx<-tH?$@?(d~Y#rD+C%0^S z*EQl_?Ym-bw9P{9-c>&|am>s0zy`5welYl}MwL?X;NM0L3SKVaN7t|GShnJvUd*_` z%1WenUC&bRTxLImekzE99hZ=Pgx7(Kwzjp~un@-?_pfX4$HmwziMXx^`Rwr57NNP(i_=wK%B9b#SEpok_}S*K6{z^W+>b z=aOkZ;AyHUf2z{&pr8o8_7q*`M^ZKpzu zwBUuoU#3M#p=t1H8m)?4&LKjINGBiGqDoGi=T@=J?LXQRk7Lq2UEugUQ{nuG$C#_; zL~t{lSApK>GQ%UpQ|APaeAm=a>RM&Bv~VrtI}t*RxEy{p<~})1I?q!3MYE*RNJ&`Z z!5Q_>YURUJvT=JF*1tD0INuIw!%npqI*;(5k~Z!=>c!8*N$q7VXncUYC{Qp5wQ{~A z@eABaP#mc0M?>r@n3D0Gk8JzbM>DHRTb?!EyN0CY-w{1d-`hn_WtDqq0ZRI&Y6G^|eJw_BjLPd;_1ade-}1 z(VXYfzH5T{(j_Rw6~PBMApL9DJQb`@sc11tJ7tuW1oa?sn(2i`-I;``xT~#>wp+r> zv>X9d-tdkyilZ&Z8LE<9AH-IzvklD$yd87atG(|8X0zqGC#lUz zT9G%ua(V4k6D(`F_<7*@JW;C078t}1Fb7kE!J%GKNg75C>~x<9z7yW~?^U)>Exd+s zF}LpynI6AN{TJ}3gY7;I_yOXuw^=nNkDa4FWSBcg(0`3(d;<7oZQ)H0EB1gS@~K$C z7}~rH~C{%DStLX#f!Ibl5l@2$;+LkeO2tguGdE_=&d8secns(*Zv9dzQ1%X zrO79NNymO`p0;5%>Z)Y1Y<(*K0Eq;aT7>X1;2aKlBN-p1RMJsllh1#{70qU}dxK3K z{*68?jAU)te;ihs)fyXi1n1={Ip`~r(e*O-o>3!fjxp<6_l$Oh91?4~oRf+;W}14L zI*=@-@$)gP_{Iq*=~}mOlqyL2RXI^g92_-ieuSjTmiE7Ix(IRw0C8EWB%O*N$vtapRJ~0= zsEC{nykONURa<5*L3Z!yNxLHAowYNju$d6KTmW!QQ)DFIjskXLiOI^dE*Ro*99<78Sjxc+K?dF$7lei3Wdo z!#}700A8k78`{Ym+SZeKWo>67BzvWZJ1AUjK9z$cv&Ek_-{t=D^HlG=OR8Ue`{vp@ zDgdB;Fe@5uPV;dMaU*B_tkAAPi%U~+?orp|KjT+)o0bybAD2AW9UbJROtHy5YeF4e ztr=XXIQ%O_+T2o(mPc8C3AJ1SkbYo#gIWG;vhXvzO9bRZVv!Eh_fC2mqj+{k`1yA7068P+SyFhOSBR&U2tEG* z$g1*qiP|3T%=lwn%Gf$EAd(ITOoA)Pd|I+gZqh2{qa=#6f%YB$T-NYi-B_m{ISB{JS3=o*z`4wjd^RVo2#@Nn1j=U>C(AkmF)I4wbhPC z#X4H)w_;eBk>77)S!YwY)M63JUSBWnlg)ZpjNr7?Y(%!{w73PZI0qcpS@8S8{tD7Q z8(nMqd64o5B>EcY9Gm3~>>^H?HRoCmyDqcg&+O?564~Y9ey14ZeLt0S+7`UG z`eLNSW;3ZX`KXGDsJ9n%zcX0Tcm}{zDSKTmOtTA*y>AeOl(1j z@(pKe@+74$tkS%JbF{I}I@gI9N|rO)r)0W%pK7Yd6PGFyTKDuZb(>&rrIZdE z1%UPMT7C_-g8KLDk~+obw%yIqTlf#tJbpD_RKbL~;1xMOhOnc!cqR;z6+hkwn)UHC z*E8((J~|Di(^{<#t5LMM(6y_J8?1>@wz22|9Ar~ppQUsjBh)Q?XW+{%M6epQx$-V? z_n3Bb>&;N`hlBNR6j(gA$dVYCqvfzc?Z?ud8lsYM>Wu2sYSLuB7PXG!R7hRI`GBZm za0Oe}+8+#RCJ!`DF}o^91MsOeAB3JKbp6Md3*&>le>%Z`f}SpEtq!Gd8a#$k-lf(jbEFX;I0HEAim_w+KKQkq6MG61==rX{N&TGsQ#5Rn z>M*oqV+FIH!xc7bro~w$d+vDbGPI6V=i01@*~neFK9%$yf%_)v@yZ@c>vBlO4% zJ7XRF{(~nqp|zfibkCnGEN8X2CK(EbB@TYR)%4%&!LMpl{{U@h5^GOlrpj3rMA+ZK zEVw6-anskD@H_1q?@hn*+9xXKfVsgvKhOUFTBup;dY^;*MINUQ?xuvailk&`1QCpS z5cBN#Z&2d_%=`wQJiT+kvHiRav?jol?Df)fOebDWI* zYq0QUmnNyCU+K+ncw@L#F9<>eqyd0VGI<`ju4&pbX>_;D=%Fh`x|(lixwS8i^h>YV z>rl{d?i61&@t!Eq=Wxz*^y^z+4}5WPq5Lh^C)1;bYeQkOI8*>sRoa5?03O*g_UL_Y6x_t87#imP$=lM!23=jb%XCIz@X(&^u z{nw&*ci5Lv-@0o<16;hk)$A{|tF#-Xxh-$TPUSwHnBuvAh1$c<<7qW5M%kOpX2ONY z2N>!2SE2k%@a^x1?=5tzSxv+SHeuu`AQE^P#&h^p4JSgo(<4Ln8NlIy5#;Bo&wA>w zCC#RZ_6l0kz3$cj0280_CxK_wejrP#!5%dEInFkb^5du0xhp*-^oO&a>H^m0?YiV- zbmzT&5ox{jg?a5ASScHtJRIQTsH}ZI;lfW5RAt71=zka{N(U?&(l9IX*%_mqGHr# zjiZ(c0%Mbn*&WBY_O8$4uYsgCc0Ne67P?)U#vXE2%PQc3&wOVCuk-WI1o#JA@vfM& z+1tq^X(&%LZ~zCBoOZ{4dU4M@^seb$=({h$e_Ne1ai=unE8lDSYwBY7z#Ussvy~Hc zo>F8Erv!p(t;v!Ck)1d?#sF55GQ^Kw4SMdi@ILoV(^pf|(&tdQh=f=YB63M2gMs{k zCnFyCLq_<9n+%>1y=76y`ZMSGdRJy1bmJRKN9J+Y=A5k^SCJ=zJS^JBg|)4IUCB3!bHuJf`2&`8*+%b=r(>Tru;D|g~ z;!hUX=o4uZXtUTo_7$U+5(BD`dybg{^d0NYZ+;c{hSn=>R_e#dfJhrDjE>`gb4kBy z=A`s{d1>F&xVmXam1?wpzwpPge%^j0m&Sh*bg5oJDoGlMokj-I5P}EepQ)<;74WUE zhjc^{I5Lx$I2mGcN&GMcdH2O{7JtH9b>VrYh0@~IG`5kLVO*$H1b$hr-&L0OQ4|p= zNZ5SRX8;fJt*L8GIjvG1X5}teyC-ICd9lf#oqk@~>FZVPuAseQ(8#j@#aM&TjtzOw zihNrZn{N7Dl#KH2+a&5o9>D(qhf2oqM~P(8H7%r*Zze-8@;_SSm1WJ!chkVl(sdun z9M{C{39fuyeSV~QL3#S%)*gjp`^Y~W*L&kj$PLBvGk{$<_pV>V0cM6b9XBw|dMj9~ z9!qZS57;S(?x|9)OH~b)f;nA^KK*8V2suJ0B2upHa2&orS;I zw(MYHxmhqe^kI%i;wrA0;``gk=ZaLbkfN4&wvaLE2t58&+s#RJ4FWqy3P2brS!ca{bf1!JXHF*iuYE&@>Axs3w0JoIT$2ku&!U@XNucd(DcA9%y^Ws zY~&sXPAe*QO)ZTYX;~c>hvPjfL5NyiUI8>fD`Xt!9qIlj@m+_AwB&-qe>y-*I)Hj( zAJ)8ISC~(#OCZkH!5+N@c7F`KzqiOGAyuMv=egqoy~?;qM-Hl8-pKReQL2tTE-!(# z+q>O&NwN>++r;HSQH=FG`t$tj-0UNJ$@MEj2GBOH?!d1H^Es~#@Q$DMPY~F;tA$&o zEJ*2+GyXO0jpA!-Z9{Ab9I}OCJ#aCf*YK|msHdVkQ?j`04F=~&x`O1&lWiqc2*}R| zpTiaCwqFu2d^zE(KxB#t)>h0g4Xr4~Npd~$*ML5?=h~l)(pyVNk_FxahvOJI~Oyoynlb<-ELV0%$`#y zP^Gv45;*kFU(3)}noRR93c8cK>-hDrX7TsJli^{b+3snzJDYJc1>`v-V0&?s#bQbD z7sZd629oQsH!`*X$549z04&v1l{G7?YySWYH5Vne#|v$z{j%P6b_PTN(2NdpGxe`? z_-kmoZk=^|b#5cMx>Rj}cMM>VN8)SEydmKWy;PP+QP$nc%Nbw^=b*{vo#Xqf#PXs^ zW=nAr4XWe?;{b8RYev$OPjqKi>M2C_9Ye)>b&xGB#kIZ%CP@@y@atH&J|=@xv|GCw zqna(|GU>?9GDd6WDPW%SQ%htgbc_@dd-UYyy~o2bMW^^S9XRoG6J@*Qv(77`Z7msx zD@``bGwH9WYtUXJEKQ%79*%MR>&pB^rOO5F#O8I_9Q=X#>x^UTUZgJB@frdo0F@)C zIIg3?-?PS#;~x*Esi=608!MZPuz|<~9QP#u07~=f%|YFrQM){J>pH61+bZhjJV)h( ziH8HQ>DS)0d_E^SCGfeJ%;I&!vlckP?rYK4;P-^IUotEGT3@!tP7*l$)M1_oDsX>F z&eZ%V4ws=_&1t#fX%&=Xl>>lJy<-WfG`95~j_q$_%XOtL89?t-+3E3G-9fy7QX<59 zamH!)$QtDV7zKuNT<*KB&;6%xEk19T%O@G*rUy0Yrjolk;+(WZ7xvNId2>Y=Cvy_s zywyqWnNWkCKT6lOgj?wA=MAg?IPcb@xznSX6XgP3v5a@EV)>V#b=2kMv0Ldk{{Ru> zn)m+z*)QS!myG@)+i905f)b^jnFAaFo-^LO!&C5G{{V#~iakQsSPaM{jLMsLFQ^^s zy7(z3w`t<2&Vcg8e!!}D84vCmk9-n(@AwFNO8(Lf1~Ru$IYW zG3I=OfHBzOywkw?6_@s=sjJS`;nXs$Zo^`qq3Aniw{^dSemwCF+4J7!;Tg9vm4F1E zNjaxmdQ@gI*3)NjuBRD&fxcwvjEs5^d+}bB zq@vP&Le1XBXMpXJ9bB2*4h%$#+(3ijQ&JdhIoqlJA05PmA1QH#5Z6xj+QFq#|Cl}Jm7i^`U=Ig@m-diB$n59GhEw(epQem zC+mY(F|d_v8C4cs-R`O=EL_GD?nrTIh0&U8If* zlIC`cK63EyinMF(LO5-7neFCaLcEji8OC~J`qy!zcn0%T(wk9D1t+!{CgJB`3Fjvm z>A|mQ(LZM20_juv?WxFH1Pz@J*Pqik{+~hFd_VC20EfN{PBge0=@-mjGDYXENFy9_ zanN!4DOHPl9Q5f*Qc>=BC+zv+QQ^HJ_R7~sw|N=&6C0N7Gm=Jo=Dlhkh>nP{-FSWn z&sM-0{0Of$_!S+lo#Lhvs20*Yt^nYie=aN9J}dao!rocTi?0qVepjZmX7VcR>;jyh%hh_{{UgC4CE*v4EtlHJ$!9ycQl$jY!5^%G7qWk z-l)ZOq)TCLWj<8lM(zkX$?sEH+Gx|?pptCEw{bhLGtalv^Q;|57qQsz6q?rPCGpLp zJ_m>5a87VoV?T%@r;o=_$aWtNs(>?}Fk|^u$uyk{&25)T0RVt;kN&+k!n$^oGR$r! z!)_|7anC2)o-s?>H+$Rf7u0Dbd%w7gP4To*$>p|%4(S_ceh+>JO8Fo5qtGsA@zu@Z zNU$uh?^qY2jB(SweUl%Ct*vi-*siyQ$pB-6jx)!-eBb*^>7e+EJK0%Q+**c_$2@c= zj;9`#(Mh)M(Z`6xQ>PZNTtB?XAVP%O~0h z$?8Dv4SGky4~GfyYI4@E1PN@RR(Xa<9E@Yvj-9L6F8=^xmcLjTb?7(7!ICraC+ARs_2*ZD-2eEtUC)5&T-$L zLDIiKwSU-*UaUkwf%x`rM52C^e2z!T{P*skWNoitKqe_dj03FPw^thcnM`DJ7I_a z0IHb35-mTD&O$q3hxpf%w5Mmkr?n&(0B#T8`BtuyTQW83YWzv@wWY*vV6t?49ORA< zuUhfRyi8e}_btim#dKaN9%lW#j(XRh&l?9k_w8E72??9g_^Fg(VIciLtC4u>?#~X9 zxGU(`{Y7#CSPr~ZsPyACu#`C6pS%A6*R3MCeiAm(v#^iimag!Ul1zby4kjRehnmK_ z@kPbdjPfC7Zl#|;(y^~CKFSqC4hCwEIrbFVZ*lH!uZk|M7&J;_An;YOimh?td#TB3 z6uCU*o4sa)LZ>f-Rpg0qe|h>EIdvw>dloLWi7u4J=%Gl#In8-~zi)+`0ea`=>DIgA zqs>6Ay0}evO1Jj`6gzGuLHsL3v}i8YsN%1!o)MM+Zs&kN^r{^XHPdTamZh#o6f;=I z7>6JtMjwqOfV3}%dqDsIPLX3SHu?(Kbk;76m;X7 z(u7)zX?6V#>q$7pU%%o|7eJDHhb%wYH4mL6j84Y`(-_5Q`EnpG5T4wMRf z6}66guBAk|mMH$@gVP{?Q%(C-$Q!(-_a%V+Gg8ct&KCzf8flJFJDhXP8ws??8&Q>C zXpKQU{LBIVRMxwlfe#9|U_UsZfJ@U_2%^oZiv1f&AZj&t9it#ZE#d<`CrV;u8G z`{w|Yj)J`<9ad7R0NadyLb&Hsnb~YjuI|k?lqUd#?NR-q*BAf;RYaqM(3)(QL?{D} zypC~Lf=2XohJX|CSR#E8h?{x!8dsGDebSLOS> zo+<;Ut8N}jeBF7e=67w1)>bV;;u}UPzGgQLGsSm*8SY|+%fmN&^BJ`YNcsXR&*0N- zSgK6|FjK}m8t*`_rxL*-Z4={zmt z?O(!|amfYLo>(BXWr+v#udaR+d`XAJ8gz1o2>$@Yr1A}XOoSc}AlIq<3fCmC@ffw- ze7Qz3+NMxeZ7oh}l+=CY^GCgWUDI^!Wnq?BkC<5H*RFaFwd1}n@RpSog1mB1CE3RX zGx?t1ulRe9i#$7fuiC*Kk{KKCaacd_s(Ye+KU6loWxtclj}o(x$lmpLN^An^>E8=ciB7 zt<5#775W>0vA&Hih2yQxcdf#eQ;zu0*Qgc0`&4*&Z~Q~#h{zIvF@i^2=khhH@F(F% zihLvE4LaiPGGJK-cQ6?3pVz13U6<_>qc{8{dUqQ#;X^RbP&)g6O1^hGk(KYIzpuQ; zi&2#9w^x7HqCPB-?GTL1a~$~RE0O%^_d4pbsL4{jK<`|AhmZ9Mqs{HHnSkpoOBwu? zjZXeKGlCY%-FpH-&-19{Yi&+mM)x%JPZ!9f#)kxb+;*yP>Qh`Ol1X38C#fV?Gd1kZ zF_eWYPJO8)*5yOCPzHJtR-!;Q)))K37N}ullaruhkyg-u)eD;0B^}xXW zDo55OF$DyWzS+$|adkcX47)N1w@TlhSJz`m$Cce8CAlo9+2woJTcF4_1W>Vg&YiQ2 z*9mH@@|eIRp4HR%F6wPRR7S>7HV8hH*}GR(JtE#sc&f*4v09+uFHg?4JXhi$75IZi zw9;>{Ez;Ho-mMCd&ADh`EQg&~1mC-db8wgp5VfRnvUdQn3#xq)x49;FMLX{_=`d5NM zxd=TOdK&EfKYsS|L2{gxBX_+_;-$IJ<8*yVr}&ml5tSv)-jOFJfC__8T9kY@ql+m;;){VR&L_|Cd!xp8Y} z47XN0$T9JPKhOE}&K%U+wyUY*xIbxVt3M;U)3i+kP_|j3wnC?*>GQ`@=1#c$((s>l-12 zy0=mI8r8`twzoPZbeyE@tp5Nb=Z}qF4|Goj_=sti@FU3@<=nLW>7TPS4`H;rJ zIXNDc+5AKCJNSdecNWhhE}LlnR95PKPnV42*Bn$IvhA{qy;^cJ<=MFh(Bl<^ zmZtvzSi8&M`L4(FuKxhC@3w|mguSA^$zSJk;hGW`%1yZAj+K?-TTRyKbb|#x_qeTg zw_AR{fumr#wRo6h5Jf`ULlpX>CqcdQt;|P~IT`e;)_2orcX>h$yp7IAcAs8)3g&Dj zI(`2DmVbq|pQmc;ObdA#L(zp*)wMK|S5Koln@w@%QHWzCjOPcR%A0c=?lQD{bLr4m zKjDj}hVe|WtK=tpYqiY%7pS$^>SY4$LZQF zCWnj2eq_@+NOXCOpB^r0x_Nr=XqX@?! zo~Ic+{(qM>q$@P7Emw1zr53E>^gS2BclMqqw0nD~V{5oZ)>a(1Z2fA#ioP5J`~5p| zusLXR+rB@-yrB5*>qzk$Umdp62EhtQ1da|t>MPp(H{;atMxN5!+cb?D4dyeI2OTr@ z9q4soD2BFZ(ujx2g)6HWfhv+84u^r^cmOCEN2 z6X~4OlP8QC^d-{m8Mulp{RLZtK#iOPw}|JsP=BRMzhbmFZ6i&eQM!&8Vu67Z&r}#(ap^%y#$x03L$8!{G13#nkmkTb3$`H4&(CEcn{ z>5!$fkPb70+o1e~WL*3+@XhOPmr03;=yHF?oEuK^+qe85_!@bcK6iCxZl8BwnfW2( zj|^Y7r1A?#BNzo#pXFTI>6(qQY&MdRJ!3!NU$B$>7Vv$;0{TQ}9XT~xUx3~Tm4R&? zGCCc`l#-OL*^{R$ACp=>jd;_nuWZo;InL5QrF2&c6cgmO;nRa(sAu3Of{Nhj3y=4y zhu{x_NWsz<9+|6!qO4~uJ8whsuHha=-0H)Q>VLwiOuJ41Z~Nr`0EK?9x9s`gcI%`s z=T+bKZ}23Ybb;&Ce}!K<62r89P(8Spsyh?tr;pO8OQuWa03wqI{gYppvG`wah}^ve!8F8%fKZ- z{>4lV+pCiJk^3}wK0gQQ_j*Q=92%S!(n$*+m=Xp^!SEZ7~gaA3d=*hzKF_@ zI>X)EVMS2YH60T{3@)z>(+~M&0LlD)D|1uRH5>1U_Wl^xbvMxTDJCrUl0;X_OcGR( zI631ypXFZpZKiwz@V=UsmfAi40EzEaaQ7>f4&4dddJOt!kHb9fq@^xctJ|@Qv?P?C zkFNg!Bjel8i1xWFX`{^|_Gvh*d+&*@YT!POsCx2i>s?WN9d-WzAfJcGk+Jz&>g}74 zQWSB=zkhCcKN);6wegGz6nYh=orVk=-*Ieu633-5PDLc+m&f$4W4Ha7QTCB-BL?a6lUtg1?5r9<4UDWo>5S&Ibh%aA zH0Oszc!VA!+IO{*NT>UfkMO44c%n9Qy2f>1_6Q(;mG(Y`@M35zt?i-kt&{m<8=eAL zj(VPPU51hSHEKH8L>?crgpH@H57jzjH&o1EzeU2j**}@c#hp zvGE5|xJ8RXxjA8n+mJaPmHPl9vVi=sM5KBk6pwcj41xgc2NKLf2UFQpVFlLoL2@gWDhDDcp3Gr zh}=k@DbKBC7%F#3vwCZtS-aTy5961ETF1m!z^PcJCAxIyBk`{ez0lqO0Z-k(-EPLd z+x?+*1i79`5fm#oE$T_>Um)umqiM}`XC!;n5~^E*ImT>6NC8kT$a7?;o(o;*oB7#L7PtG&-tCw2MrlAWZ>|S2b zK#@EBN2PPgF5fdt%VWnZ{3$$obK6U9lB)y2a7g-p`s<9k_-B6w*eJ5HDcn_*;C@x? z`o*Mn>AeZsa6@At9>5XOqSLh?LlFH3-R+ukS636dK7H{ug~DH~j}Z!(UY$i~==yqV zI%C@;YA)md09M`4diGsH&q>y|$8!n?KgI?Qexv;V09+ei1SOWDE_B&ch0pHf<2-c8 z70))(w^LY6J1HZ`G;w)heij@FOKjTR{kiE~FNUoe3;T%YkzjqLnj(7l7(YTQC&#*8 zr=;q$+%!TU@8+n-PIG{J^ImtY>(}?UZ8Vdxk+7tpE6@Njk6~IvnYAdJE?Fje=A+{K z9Wvy?*8sk96>?5KgP-&4aeBwZIySFr4bHC(s|LVX(Sba5$RFYU9vy0hVQDj#JQ8^| zAKIoz`Emd~E1}7~3{@K=-(JcIxwO5vy99sBC+DZ3=DDlL%olPg?c4LNH$&APX<<|Q zxiSEC;DcT6wP7{Ckt4SDm3I;;DK_lJpyIF4?EVmFHy$U|Z!E2%Gdo(y+efg-{CKRb zawBD^G!mhYYZV~z&S>~qVtFYk}5mOe9ZfE(klgyUMSFWCrQ9E9o-_`izCj+xzvW%kpN(yzTp2(d5Lb^+ zQ~ayu_2e!UN<^UsX_Hn#YadYa|3EOS0LV?96so=$l@`*z1l!q)y8 zMtn&eR?)S@CKAN%930~WpQU;Shdvw&jX5H{f(Y$XJAZnCiTC-l+PGaRsjEGAZ(WaK z7|azr^2bNg%TMcHyz>$nF8r{v+Mm2vJAlB)^8BlX)~#gJRV9*C1gRTat~&AQ z*1eC#mr!W7{{YzVL?g7zhVtJ#iNHA^;PkI3f(N!{S>Z1TV(F3hx#WE-(xL4m8%_Ei z4l-0K%bt?Ae^c6jX8!`vdEdTxv1tt-RU+br#)LKg+d z1)J(?nDeaT3+LW3C#my~G{@Amh=2s2%r=6CbMt; zCTS6Zsc`ZTGsMlw^c>>3tM7<*cZ_i?@hMU`mH0gK*RS|gsHd*Oa_5gA8$^Lnp z?HZat&1%CO;oN?;TK@o4`yQWhb9HSPjZ~7!f`36-x!GRI9OwDfMe!KAy4h?MOXryz82VO>5t;IDZp675 zRoqYNdQ|&&7k0e?!6l*cPsU#lO`(k%=H^J1n(PIRE=D?&o}F<`@z0MM+TARypXZT& zX#xAA1EqD}wd62ccza5CN{K$x*o@-`wS4%KJmNCWT?BYE8;Z zL|Y8Z*(W6Oed^t}$tWaNzl1z3F28ty0Eb|8n(|`A5cIv)uk0@n!aw z_k(@21Y+Fe;Bqm>KhLo5J_vr#sjc{hZD+ZO)FE%3#9$nba!x&XtD5eo4yodM+lzU8 z_*{Y?ISs(?^{#`#zAUlumH529mEwRY49W?|9B?b$tB9)TayQW&QKwJZIV0NJ_FvGA z-Ga$~r(6A0pyz%7U5>NXC z8fW}3Uk+yrb;#?S{{V$}(N$va`W=bF?cY<`<^7kw6DUS)cG(;favvc5ermN3z#oOS z7V<1wtB2K5N2efSp~j^!j^ z~J8tyIp5AhE7POwH6DpivwBmy}A`d2IBh*wAP<-=P}l8CO0 zEYb8lYkx@dS=knam@I38g9n_Rrm1NfjlpvFI+{ob+6R~k@8~|2 zAB|;6{4c6z^CfL$J41|~Gwbcrv5K_Pdj9~Kw0U2|d?&A=SCB6qK*e>xvxmdjykX-R zB)N_$ptn-*6x|yjf%WFOZE?AHy_>bjH-3=u3ZB3q!~5Ho;&qP}LZw`g@sc#j{v3;N=}M)eD8Yb_e|BxlPJ zFxIbr0Utz4S1);7}T-i8aY!(S=S_hAF+-mAWw za_m9WTxa{L1xk~dV^f3k4cPs0O!FM{M%$cckMYfTS|hnO(GuwsHbK>90CdI&=aF3h z0F1O9z8cm{nw`TBvPwwcf;OHw`eM2#nog^ZxWPQ0zV*a>WYz98pN87>(1sHIk<^@W zp@9VcS*>9U%~^ISZk6{xG}}*YL&SpKB`ng-CT0UW*s}Db!SlbP?L2_W&Uz zlk~6257~i5r^XE#oR9>YxH$YP_5anmgKKO?wOz_j-WxCh{&kiiB~w+Uj*J_A z)inB_n?DP5)=7LyfD^hG3(hh@IQ?;64e?sZg^jWSlGBz4@abPj{6C4tv#p4tb_O{H z#pl<9-n=`<);qNe2(Aue7ig?~yZ-<>sR>*WNmqvNju zu(0ucq-5j$F%gM84CE7@{rOa2WOF#?EyYf&HxzpuMU#f*m&0AS;Qtg zz(aa`#|QE?^%PFB*)m?;tg9&Cx}KQNVb5C9v|lnup@hULZMO8e3FYhWz32!V8+G8s;IVaVjytXO<6I<}WA)_Rq^urjyF-@ZpAW4~I!)P59d znq9hFE5~w(bF>g}GENRpO#4?0i;8|nuMAB&Xr{F>F7(Bi{iA#_6kLQuoSuLERgspjCAhH(yazfyJG7mK`hkhGe!8^-xK15=rMnP_;Bc7Qx z7IyVFjd=34&30nTqFkAtREeTf&~Q)VUQzo(=t1vm`Dc@! zMS8czJtjRrO1y>@^3i8j!011A2kL9d(3g;?1-T$&@&5n{_GrN+s7t#U<^a048g zj~QZ56aZu7=im8rRCoNXkY_366p ziybQEjDPCf0>hx|k^HO7?*U!J`kHnIRbmBdX-Jm{%FGaFsjQX<>(>=((qNFtq=A|U zdX~IF3PmE7aQR-{s}zy9n$*;xSB+#22{;4;$l|h!kUgrj4uniLB0;Broz21M= z{{Z3Cs&k~c1a%S-T+*q@CkB?DLWC7X&8j~?H6M&J-{f4!EZhK0k`^R;TyNVd6*$7m&0^Kvo{3D0g0anp~kenGgezCgX#rp zt0mBfM_b(W1@UKxWH~mg^eamKA@J0I;sgh<+g?#Fg>(nacyK+c2=q&*1pTRp;Z^O| zRKDRlEo{$MZAZgR$%zI}UPV>ZwI2<^VJwQFm?(B$5OS^ zourvc4!G+_w2YmshN($)8DXW^_>);_uVpJ~xZyHKA6!?dX*vgl>|qN8yLcG|$>;H} zDAII&MeO79(lV?v2|UxaSoJL(6{HD*a4`AitDZ7@5b4TJ>7LHoZjB-YTZ|6egZ(M- zXu4{HvgJWPO7Zw0*5r*O@(5Bhki)0avUM*NUFmZalX1sin-pFTM6Nzco2Glx_-fJd zi=a<#l>1K(3tOn>is25{3`eN12-AFZb$uq-7Dr*!DJHg{@kXnWg}5Ag8ZQL(Ruhd& zZhbT2&k`RHcv2l6DB1>%0vA~@4hA|`fa;$Eu5~NNnB68A=NZYWx4Qnfr7B5qw@@n$ z%3Vm}t4HxJ`ZgcwPI2lfL)j(nj`b{+^c{QqKir7VkusL_I3L!z`>%i+>g1$o*z|1I zR~N;*#Kvb+{_v}i{9(HT1)@Ix0OZ!Uk9HjO64>Km_(`wZLJ~_>IO&dSs`!2I{(CJ^ zWz=D4)eclJ;H6h z&urHOzB$pPT!_k=4~(={I3$mJkw>sr(SoY&W{%d^7VsJSm5nPIRpG21!xhw6s z)XS}C@^rl;>hzrl(V?o#YaJuc8Ru{Z*wjsSgZRZ^8@)19fv8}Ayg$N*hQ-E4q%eKO zFJUg|Ue$S_b3(S# zt}kOYw)~*R4sni7X{s_yrSJY^x~rW&n*Jvht?OuzOyz*Wo^xL__@42v^;?;;NGx&m zucoiO2?g9thUgV;yw@`)z{?5AB3zPloz+z7N-pf(SBh3Xc8wW9IO=~&kOJL#9`*0i zdZhU2zk zKLCHNX!u*J>Em*wl8~ zKgPVOwC6UGi_^)G8QHgWsp`kVa;YRi9uM$>Dpl~ryb`KDq#E-C{i3uEuWxpz-9P&E zBL2|+8El4Gm+FL8E`>|zpWJKhBDZI*X?_}(JBZ~C8tq`iE$`?n%Krdp%Tedsv0xn} z0693p!0FGuKTrLlJTVmUv6LvtB)XBe^sZaucg1fEc%sTPr{ARW2388W$T=DPD|%3x zl1)AP5gL$;o4)J(&w!28H$aKXl7HC?{DoDULTjfjYU<0>jsWJ+sbX9_aq#3&q}98 zO^qMmsT>dU$K|p<0N#)Qyoi(6ulQFv@Z;h| z$Aa%Bd35%JywmUD>HGkJ`5NVMuCAJPGL)&tSC3y) zi$4Ia;$Tg}H#N{F!n=`Vu?dh60NT0EDt%-2h0u~vEiLXOQb8q`82mHO`TQr-e`s$F zG@t1yG$FX%vm|~L?@}v7%;eQY?=8Jd!2n1>x4!Vodnn7Hg*{t5)(?pO7rw2hO%=rP zFe*UqexPQ(alBjM9~xM%*!9%5Vb3Hf`e4_e>)toA@WeCP*$D@)!tJxWq?Nj=WZ_493}PXQzng=(+Ee-D3Z-Ay}fAx=mq zn$6U)G}6X6&m-ww-@$(Xc!R_*d2yq{E+c>sHF5zN&OW^jHqwpRD)CLN>|l73HeG9A z;Z$H^v^+oYFT@@elkE1ojlhhIY%&r6^uaaPc#pvvt(lTrttM2JbIPw%{Of~~O*$L< zNTo>xA8unla{$H*Joq z_I~lDuZOi;n>)7h;=PE%AD~itXWKQuspvEKXIzq5aMrRC$ujZ?JdSI_?3>DQw;+ST z9jnrOJ@GE~%E#-u6O!1FOeZ1%o z4Us~4-^1;L{er;v9(M+(9x~B!l+b@WgZ>q2&wUJBTvd{}$oPUXnx~l^GVwSgZftkt?M^>q^m2%vti1hPJcXCsGk%WuuX-I5B)O# z0PE8qRa=aIq1g|lApZb5mrcvB;r{>xA?`h{d2361C8JxRV3Ke_9e+ws?9EF~MPx33 z^v5E-Sv4mgFOg_h`=$jy%v7s>jq*urZG-;+On}W#veMW54P5T@N6+3Rx0_SQx{q|@ zq=T+I3exy@@UvRh^#~=kA_NRJ%3B;~2l;=Vwd_~g1RDHr9i5f5mmQWkfjRfU&w84F z2Wwss(AxA_&1~ckD3u@%bC5AyHJoYtw>OO6D{n-5mXZ4j-1v{-)A+MjyNI+f2^Y(5 zW*NZg(*qgKInSpaV->8v1?k?tOWIm^Xz#I?2fBJyOD%B?5uKxfnbo>o{ zou9k6pX1xe`U303@DfetuW6j*gF2jk0M>M#H}LhT-1?jhNB2oJ^QFt>iRtbtjOcq3 ze;U;$o$gn&R<_5}3-O;o)6P=+aUHht{{Yvj*I%?&loty*W9kzF`B%(xE>Fxks?tJ5 zX|Hj*Y3k3UE5%PZuPf*#LfmzIJs?jkQ2`Q`ke*oe9Xi*Zc>3ng zQ`V(zHZ@pO?Ome@Dq<{{ZXLKjCm*XCg8O`{dW0?8QuwGXPW{tpUj!9)^A` zUlKg9-v0nG{{R|B__3)lz+y4}(XM68C04t|vh_Zx&$vD?M* zM^(;5ji2|cXG-|Btwku?bj$76`d6Dr1c2=;*YmE!!yXFMHD*_iA`?G&^rb0xV&soQ z@BaYUzgE?JW3K5DwZV~J63##v7&$*qEA(2v4*vjxbLdHOA?zVIg^ zwy8dAF2`C?T0~kZt=6Fxsup(&2V9!wZ@hgKt6^0!&n#;rH&zkRv#!s*C<46pe~K#@ z{oH8e44$|Zmv!-|K)?v1IrRpUX%p?DdbULw72`KQ9-1(AM*%&)R_FDq68_LPS@~g& zM`N=i`Ow{k?V@|HS}|Wc&HF-XfD#02j+l%b{Az^N5)yyXqEpxIf%*z-Qhu0{3()**Y6f+DEdU{vJ`X9${5nt*t zM{}zjQ;?i^V8`&N#TD3e`-&EJF$qlmN`yKbP6%*a5)*t?Zz98mDuA}Q@xko z&oubq`!)D?$Ehx%Yi|v{qn*qE06E4oJq>(~d*E~7y-r;|TW>B@eWP&3M<3%}*W&*G zi2B^OcQK3E;PTjyorotK9({QK06o03Uew=Ta`$q)lVgc9&*NIte2uFfWT4cpm6Pat zCxSi!TT^eFZgAgtWR9Y}%S8A^;B5*7Ng$5&uO!IB8R||4sptIj@}8gLJyzNU zid{})vw?>rike><>XIo|)F(dTLIyuNa&x`zG@`Y(r`eX;M};jw@^tG!6m;4SKLTmb zH-_zqD+Q!yoM8rQ;->L_pjQU|bAjGMf2~z-65dGUo4JO1fg#80XyudDvtEBA?X%)N z8Vqdm6p!~osO0hfn-DAZh|``vVVd}kPZ7>=4b;b<@1OCeoo3;A7Y+&FeBAycG;+2} zo3@DiUH&C$z~o&1s6#oEf%F+V!u_vkAX5#K$2oL9y2Uzco#%8zg9P)n->EC?7pXQv-Z zIbGJE=Iv#<_8srWtrZ)a#cXrO0D4aRuDw;^;i+c#I^znKx=-#txcHId)bScZ zgy$@vg**|S{{a4~`E$kqU1}4$;Y&!z1m^=it1jpEh|#XyL{iU?K@6*t`qo9)#62?a zWWHSF;F3JDMP{nSD>iE!HCaAxNB14ahrBR#>s7ccuv{nHhSw%sC&@z40xIes*1xFI8pACDicdr+fBnzC2*INYj9-JYK>f;2cX zQq%?<@K0aPxX%-5F;6^-PB1tb>0GqG81;tvaR~Z@{x!~C{72NUF6CG9hFc(o_V+d9 zW|Wkvr%73+dl_v=)2RKjx@hdIZC1!yKI{nQ8=KHp@5J8^M{2gWw*LS!N`aV;=b^7U z@V>SEr>I92%bz8iEWJ7HUZvxGW6tpHrJ5?Sj0P>-3=CJ7gyw^Bea^gm>D9EUtAD^a zt7~HRiFku~&evYJ9<@f}Njja&Yzkmd8MEHHPYJ~jk$fEIHpP{5+!3GCH3qYN4wpT< z#(}<7R{&rU-2H2=QnPVqm20b_=Y{+iZE^$_t`b5vI+A8wgP{5p8o)WACRuH*GltKd+U(ff_~$Zk^1BE{{ZzxJ#Lv*8j4Ln zQ%Wy|mudksMn0z%X+9U+5rcCO$NNIF7fZ8Dm+>qM>+^s0>ckd$P&jWC!VY?F&-JZb zT1H&S8jyT7xauyUB!9LmMh^hqGvnKaE%9RPKZa6O#B#GQ3+dLTQM*Xm4chF`@Q$}^my_7Z#12VTV}YK;*V*5) zuZt`#`~xVv)S`$*aOe_ALf~hp1lPdYdI4`4&RnP`*Ep^31-;R!_^hJVW6vRftazTP8R-x(Yt0HTmSwT(bk@5tHjy z;(bm&6=VzO9D(^(e^kD$i`#i0dtI-DZPU$v6Ig{D9ruC1A)3Oy)jS5}R!x4@k8DZ^ z{5h|RS?{Gecp5@Kyh)@nUAO$Y754!D0EH!kTeqmav+R$wTT%Fb413#Ev}GKU()0Z4 zyLg-7ra)~<*c^28Kl=6YyGM3L4Yxn;{{Z!>ys%y{{{WA0KAe9_e^XbxV5>>=KHs(Y zzwqxuj71KoX@$Y#2M6kCllG7BMYEG0rD|AlmR#}p3iwUzZyEmp)SRDsiS(<}(iYFw ztLlmM1y!vP_hi4dUxQ-|r&5iFPu;}8eF4Q$m-e9WT&O_3etLX|AE>W~f3dIoM&E&? zmd5hgGVzc-Ng|ij<89bF&i6joxc#BL5jvJP8;JE&lU_CP%l3fMX0VFaL7Gj@b1MKq z0~`Q8wcsVC!N?z(s<$y08F-ZExf!IebmH#VO01f-#!2y3{@!6Iyqri5QL;`xnX30+ z8MWJvx|QZTZEe5EQ)IG@zVuQ4D@_)W#@Oz1B;f*>wSgheY zN6jH(3m;R(V^W%vNi7lEhsC-vl3TOZG`$|%MY2n4b=s;hNgT2D%}pd_<0{;OIrOhB zy!f+y6h3FzVk007%76Hu^ZcH_617Wy9*Gc!DNuA*82rEb)uAr4Hm5wY-EA^C2MRdG0O|NuTL2Q~@drS{ zx?FiuMtsg~wnuyei3T_$kZT`G(5^Ltuv>4M;fC1bEODNg#%o^qly*=!ClsCyy*65` z5+V7ce1Z7aJ+)}n-q%E0&x8C|;p=$X!~Y&u5G^)+cUs27nC5u4|`` z%nplzQvH}9&J8q?Jj~}J(=YRq6lc`cgwifg-*m^eO>V4UAG&BM=%7-v9odilibwpl zAL07aK8$vfW&;5K0G_q0w2pAJt&OC7v|CWSJTqNBPP;5j*()yG{x#Nk{{TY^pu=<+ zv50_k>5P9m<@|7h4;fF6R5}xnpeOm)rg*9ayYQ9bo_}+_Kpf-@j8{aOZ7|hY9LK`) zF70k&GP3z>7-0JU0Q#%YuQ&KQ0c}Ca&Ni+;6JDdealz#KfA#AZ6?QAM z^cx(LMfYwc{{X(J*ZQ^R_HD#w?p8)Zq!ZL&(lmia21W;^P}S}PdUeZS=-y7IG8pFyWV6yz(Iu4c9N->SnR=JL^c6&F7elu#C-@}`Wc~J~gTWw?1=ehdk zypQ3Azb=d8D+}0=J3}C43^EDku359(cpg~5EU2Vs&<=4}9tn%h*C0@LZ6$(^{7|77 z#iX=hcG9`^>_4W$zKY@t>T4P*LVusy@h(GjjW`(@Xg?4Tm&un)k>6VNjYvc zHwu?Tf3kL^r`mj1vYP#T#YH7YP!D|m75iCdrr7waz}7K5VeMzMLZcl%QHuN}_yc-u zyn7ePxVn@F`@Z%2sqm&_aq!Q>Qg>}WB7g6ZS=Oa{&Gk)wr%fsHPSJYr`5zzn8^an$ zfW9PntjQOf9FWR^8AAm*B;(s0SH!;_W{L~raT2P3rU}W)%`>yv-eM{ngzX_1S zjTFr6IXJI_KWJN3X(wA{c37^6-Huz6&MVEQ1gf^W*z1(s(~P^HaQrvEv%m0GjVrWy zYh+`P4o*4kU2InNjmSvI>_8vkUkZ2w#v1G%7SqsPNgAYJZ6^c{)!o>9SMd$Dz}I_% z@sa^2^A)tP(rW1Qm0M|J>)TTlP6Drf-~C};>G0u%-W0uzbB)6t`C9z_(mpp|qaw=W zj`_jo=tuRhvp;4Z8J`&VLsiQMFtUyY4=R7cqA-;^xho?`O-f3~%$_ui$Ky>kHwSP*%aa}K!Xy+6fwuYIHF5}p_AXrt8e zSk&m=-|)v=SHEOvd>Zjo*7lmNnp4dYWq}7Ad*-n|DtPBoytIzpF$^m%)3+d>uRgVx z@ZVDY%DmKEsr~QEk>8rH<6v)W_{sg@Yx8NwtRB4a^Co@RF^WbYVI!V)6(waI(4~VvJ!yv>x`P#vzN~E zQDoSrHr*d$aD8hJ!X6$=%^K24@75=1!3d)#823GY3h3;#TPu0I)Vz{OLXZZ~0sM1Z z&DvVKoYuT-k@JVcjX-E$8nvW%G4ErH4{u8Pk4w~Ixm9*|nOp9Jd>sB&^2U*1kMa8c zDdaGuq_#lG#eEMBgJW%pE$$@TIN^o~t?JInS!-e>uQ^!keE5*@Whp+UU;f6GB@&Pp z#N_loV9(?QWa!$qp9G4RuF(*2lgP){wRYAiq}(W$JAgqSdwAr2Rg#iw_XTyKUD$B?ue=&mX05 z{vNfulKxA3X4ul9M$b*X^Z8e$T==v5cfhvL+)U4N1C`0-k%BYN*169QUuoJ*jWn^J zJd?;Hp1$2G=OmKqXB)JwqcZ$Q;R*D;F5=2#8yV5%QinT^FaZ3k!1XIb1g@?KAdqr8 zSI|B%@nULPjjV_aaYY!5>x|_804nki6~P>r_i`o~S&1J0zpW8(m0xnCt)=c@=sHw) z(!ZOXia%PfsOd(=^u)g~C*}7Yt5(Z>u9qZ{ovvFM6?DKaZf#|5aG;;9Qc0OzqLjWG zL1=|ZG8|^8>RuRoXgt8%!0G_TD@yNLf3=vG9$s)U*EL}^BXM~pqo~?cfP3_zSV^?? zFfDv7CCUa@10y`smPX9WKMO8SrXa*IQ` z@eY?~bWu#r<*K*wKR6$W82oEkLg~uQ+WL3XZEe5EZ?bxyDQaJ^ABiT?zqM@35W_nS zvoIf1pX3dE?eR0=X0hNKrMA>9)iT>es!r10oQ&t&8L#L~A&xjiArwXi(759smGBqs zkKo+_{6T%DNemLa#2F(&jjF&f;B+HA5miQtn|iw~eE$F=RTmpw>i+LD^7lgUo~x|t zQQKKU(#q!m4s%}lq<+XAJG!uj@wA4G%83-PBO}+RHIYAsX7G*uwyUK(I@`E%gz`s1 z3CE|gueHBtkK5x!@Xmm`KZwb^f-XkU&UsDU zH1vNz!D@O3>^r61_HcLPk*O+6boZt6cmR=(=7qR)CJF-OKN{!!WidKQ0Dl)X&?La&1~5ls zT;Gip$EN4|%rRFVi2^k|I_$Pkg1;zHSe`s#n`P%L;6RA?EmLs*5HVhT<1!xRVx22W z8?;T`ioXkjE~PMT>Tm~NO7@KsG#3#N6@suhZoO;8z8o}ZtVz5ak-_ve>^gYadl3Zk z!-LS8nydGg66()n_`MyZ-wpJTB8c4XrD=G$Tg@y}rQ)hDB~`Ld`4!I(7@*cH-c@O>ZRA#E?!X-Wb!)mA z>c0*D0A}A2__8&f?Hs_vf~No$) zoc%ej&rSV|-b+>U^??M~`IL%ZEk;zi#Js(E?iKu z)h}(t(9dseB*;PIDx`o%rbSA{tEFZ?WUaCC+c7;bj){92WV zD5jtP01Hdz%S6AOdqtP*XJe??#d8*|<%tw9Rs@ml#aFuhiQF8+ti;@OU_h={icxyK zfm4fDT~8*D;&e^-pi(}x0Q2~`yT3c#0d3UQ~v-0KjB>dxBDvik4=Ee zw-HLCk+r!$oiDCJ`-ibj*=};y^D~c|gZ_Q0!bocgd`D{hnOGn8*A-=T%R|qer|={C zSFox^+Ovm|n+qWr6)=#K!OdX}e$jvBvXfK6uG=^OO23^~wU2Rfvt=YW>x_2)04AQ2 zoDa{M&yP`sj(3dxYQ%T2k;AX&Po6!*;>)Ws2cAt#vuCebq_f6GLxcF!)>bDYX&+jA z=$=;hEqSdQ2FG5tZaISha1CcHgq}q{+GP^#BLMd`Ji*G*qP|m~nkd`YsQ&;8g8CZ? zA|%+tykwLA0M-=QrdUw1VSqE5@}C@d>Pxcrwo(<060F?w>IFpHR;&_~ouj$At@w{Z zv6RMRNf>olvsmBol;~16JiCZ<=yP6oETxG#$v(9Vj!y%M=H~8>=+Q}Cy-!rNeH&A4 zv1%l-hu0A1MpVMCH!F?@<6Q5DJ|*h^01maQkL-J+89yuSB!l@H!_<6Ttlhxy{ia_n z#~_k;^vz<}SxD@1rzHATb5N3TYerQl&MmpU4_2GwCyhK#=0CPDFV|?#^Q=ek_NRY% z*Eg(QW<0ZSYoPdXr8TsIbP=B~l4R&Rde@{}UP%IPo;yZQX2JbwQ>9KjXiZ5t%E=s$ zhIE5{N`C~mgpGx+R3wX-I#*iH|ShbBNWeJip4?)~<*ZlY8^i5XUZbWDY`y>%v zHj?^Lyh$DuSu#Fw03({|aFa^UV}_)Zo!U5Wj6Vb;&~2Mj(Ui#uQtpa>kjGpCdXMq( z6`i1Rt&m3tKDGC(A7R$Cc`cq*nGQx*8B#&%p8VIy9~S%{K7+2@%WD&hW;=;1fO?*X z-2Sws`=r|ZOQ9uxSv@)&qF*)vC$Av%r=58qVhfIX);*@A6^NP0Cjf8<^sSpVwmj@d z&p99;=}ADpv)t~9tE zfa=*f{cApLJ}V7LZW1eKkmm?u1P^1LaroA9_N_M1?@2V#&ku>WHg;%+%&RXM+YeOt~BX8 z8nqN{yFSwWiF`MsUTbN6uWf@-oR1_-aHynU<0Bn=b(HO^uNU~q;w@9e*S2qWG7%D( z)Wm^sJ@6~h!_`$JlhWtSVsh!>law9OJnmH>^Ht;k@@lo!t>f!eTxTyDXYzdG2`ygxpm(TBKYRy>@OiqbTtE7)zNxiPjz z8OW%bDDXz{#dZ(y!rI#286?zQ#GtZ-LP@I@pA4*SE##BWxZI;9S#WXr);CeriAg<~ z#f!&=9g86!?wo%r&UqbVB)8MOdW63WH0ymj8S@4=ioujT+s{l`ca# z9Dj{FqIs3{Jh4j>PH|2n{JxD>>?06xkw&>RVRLgIVn3cU;Y!_G`wVK=&A=lD9`f#l?r&vO&7*mE^neG1o47^XO_%_A$9T!mhJ50a4+mpz^Amnw(uIkhFtnq!l zz>0lec+S#x1B&AO4WQm@UJ7kgY4enxX&H)~DC7Yq{3=tM;SuBvQDJ=t&!LqDb&a_pT=Y z0QRibxLK`H@H(ude<4lykMQc>L6Sig&!2=0s<|gP$Q<|N53P8$){%1M$%V*MjNszB zVP31zyEDYEmEj=%6>n46?f(F3n}?B7T`e6!!6JMdej^$BpZ>RN;g5|PUx~)VFAdQ= z#VP0TIKdox{{T=vWwz4ewA(AjN{ki`haIUV)I=JrH#%_;NgmR>1_2<0$j>7<B7@qhNdnH;XB zDVTimHziNk9F8j2_OT$6*~eUP2kBnC5|mV}x$-lEgqnR0;%#Iw;~-;$*Z%<5RAkq` zdBX-hLF_g+J zJ5NqJ{#8nCS&J4A&M}%xe&@vbF`X8@gt0bZ7Dvx2+e7t)Jb$ZBdR+PRk{Js zy+m$_eZ{hKkH?DTm&8T#Jc*CsI3J0p4PJQ<7SoaHafABN5%T$+Fu!D(O12qJMm>7h z(q95TIrwYCIyR#ID8Bm?>Q!yoqIpXw2O)NXMoAriI{7*qi$2lcIeD#KK} zIJb%{w?%0e1pX$JmonvBmkBy=nDsx39zF2?0K_Z(R{Kh|p3(y=GfgsWjpH0{2eCNL z2*}Sq-YcW(4<)*WW1N*8PVSiPU9`Up{B5h*2ujA5kA^YC$^&zrPhZxNJ_&fDB?3Db z*a3n2(f&2GAk=L2W)5q1b1!}rfC6A5l=JC#7=# z0NP-krSXq`KMG{}n#Bt{N=O4Flac)CHj>%LNMcAt)#nsG(XStS7a^(L2I-#lBh-HNXvlq$dwkx;rcZ1u+ zTD%s|Fo7J9U6(lEo}Yzlc)#I{Rvsf;NN&TJq1?__0A!2|S0jZ;>h96(Ll=Zsib+2t zao!ufhR1Q5$XtdP2LzSt)Bga~{{Yv_*B!3!nc#%Q8!_5Fv&L)P{1f0G4r!1|b-h%; zLXC>BmMy^q&ZUP32d5nJE1q=cIi|K}RC5e9I&Lpkd548;A(QP6v8=vaK#Uv> z#DE9$s=D2s%U?!ig{{#}GZT}M*gjxoq5`Bx8rtLhh) z-bs|ac>@?3{Oi=iLQqzfwLU*EuL||KsovH!uOh#KHE3?+D1!>D^{A)$!m2K2ZF2+%hnB$Mexovj+Z!X|0SIa_Fl^Fj3_18pVn`xtlR2rHU~dYD+B%}_Seq{fAm(%W%{qM?mt?$rFhcj&g06M z38LxGQS`2+dpmy-S^1WXuOJyQo(*R7X)td~*y3-jrBw6f24F|Z(;&$)xr*H5*HK;d2}euDs-)KDEu;*)`SnRDsiunX8f+B+g4Wu{7%# zh`G<92l>=Wo#5jqbZZ$}S4Hg})9e?)v$tsnj(?qZl56qaXws=dW-GL@908C=HJf)FTArpN zbGip|tC5g8V1ZO(w!z#;{OX>ks+}_3?gJ2F`GFmeYUZ*{zU6PZ=x}^9uq-2**bY9o2BSBcacXPT)1p5Is?vs3iHMIvm~&unXBEgDl$G?eJWA$atRP6 z)z!`HU=XP+gJ|`@6_gZ{O@y|xM?s=%KVr0wERq8CJl5r}iGy*I_*b4s@f$=ANU=Zn z@PCC(AH@9`I%(_w0Dph_)uYznk4{}_a2P&+ohk7bmKzK}JXf2=@mEXF8gOClC;tFi zt;g{m&h2u#U&?#=pZ#jWa!^{ELY%6*MH{-c=)2rQ^PJ+4FG1|CdK>8XZH6?UZ=}MF3mgdAEVzmqjB~WC3 z71Vqq)K+~u`+JSTZlBBfR|j>en@u)ZM?k#s?ZtE-2I3cbbf0A zZ1vBzBA%j^Zq>Vwa%&m%`4r)An;r5o{Haq5ds{Ic}9lRXs2PRP&v2<-v+D6 z04s%+PreN+*^b_3m)eGY@ccjhRMxprKKT4WtTShZ&z^EVv`&UxFPKI;U{QX9v9c}! zJUa7DmQBYQ4Ol;8C#;Y7Rhw9n(f1P0PfYWQ0j|cUlzQ!2X@y82g+8@jZB8iDE_U=B zj`f>$cKON{^%S%VHdwyWBXVRU3=W%su5Iq#-HBnn6dWIaTD_>tJb;o(IOspa*0Ilr z^-XlFaU`zI(HEzu<;`{mTer&z;DUPKK`|tso0B%+)9IMamQNLoV}c`XyC16 zdF_KU&eAE`fPvHBiqx@XO-}U(`zEblXyQ9>F40c}6P~AkZ)&~7w4)>)Hku`HP6Vvh}L6L@;y%?o|y!B*&M=ZHK@H;GatxmEcN zRH){=l&zu3XLFY?f!F#{D|w%I)_|}=dhX9)Y2!{$r__pE)*Y^M`isYGM8XK=IRNfJ zPv%8*8YZ!89172DkrFzhjMWWt(PGoxw=V<^4oz6m?V^^(IT_;_9ORCf#U}34Z$a5w z$5&&hT3vz?Kp1rdVAaC$4zJVFxfEDoebKt^1E?mZTgQH~jy|<1^&^z&cfrpm^QPwC z-}A0ru3ej%IL}H+CvtvPBigR)e2D5i%WKiFbFl`Tigx|qH;@P)nB}N$N)!{XbgkwTBlv9Ppt;X(B2K=%)l%KPH(OV9aD<4t{4R`I@mT0(CLDETmwJ z)8>`+`4r-tDEvRtQ$d#F7ZJ*2QI$i}l54m7U?VwRE0MI(;IwVfjjTouT=Gx%jU_2= z^MQv}V%$tL;Qn&}6o|R3b*x5YizG}SsWxU6CmKdJwx&E{^ z`UfwW;`)W8jWZpml1*>?H7%z2mfhtBG+;*JIO$#c=+Kf#NciiQO@mn&@TvAOvP$nH4yuSfIlRd6N) z{sJkS%_%!!B+$jewAX(rOM7b-eu)T2-a@$Px7KNd|crJ`KhT4G6M8*#fB9nUn_bbGmR z{*NIi)lc|Vg};LJE5n4eBR%m;+FPkgCk621OK%Qp7gAk?Mr8`59G<+6E4qeVTGn3+wI&;yEs8pwr~E5_O!!y!gNA&O_l7!FZnRdnFOj!<9wXpA2Kz?w zg`MPbNAk=83fvG*4^F*n_Uqtn(@pSS!qdJ(9X$#4?_Zo+55fo`xs8kknOGm|U!*?? zj2b_}eGOz~juB>sd;zN500egn5%)514)^NC(6XRT$-oEst!9 z>-<5gt-po*KHnq{lQKTU)z+ z?SaSy1_1GafO}{8)Xk=nf7XXgAG4gA-o*_P`WTW~3EQ_nk3;;bYip>ri;f?1D{^}Q zRBn6+;u{@B;7eyPGaRylK;sAS_Q&Aq&x#}pl-cd7AKr#ZCv_qp3(VhQcXHSadJ5?mt~A9A4FcJqqz4;Xk@$@@cDSuCDe zoQ2#}p8VI-wjTp7-rsB&w?-yWyU9QS_UElR!ZNbzIZ3HI9%Ff{*}_84J1cq&RWsr! zV)=yRC(!5c#d`EVvs?v)lSO$nnaM8w{(#n>{4<@rk?j^|2dR=lkHBX&9>w0xXf9^f z#|3HQeK%S7aeJdA{%zYaWkPa5$5Ly+=hM8o8zA*LZr{qjf$?9$l<+5rEjC3Q5!^z` z0$}bwzgppZMWDo%=`?DpRN&xaKK1A?-6d_B*VgSA!tU{XAK-c1LF0&^;eMT}oRPao zKnFSKdgIp>(tVRT1Ymx3PHPaXbM+YS^~W`@J@f{xvB~Q<G?I8R?F3Tz&Kf_c0k` zUXQD3zc0_6V~lq9&2kr(J4+0HT+-&!*9%`$pIc(uW2QfgG+1vo^JHKm5Vg5wWF_4Y z8OS_Q7Md;Nxkngaq>9ok%2Bb!C9!LePxrsAX&Y9LRz?GYf5xvvUllhG*y9RG{{TN) zF7_JOz|{9ge(ny|2Xc|v)~28FAN~=``$sn|Wpf)Wk;#mxU~&&WtA>LQ1M(IGw{8pr2_^M*~rBA>7IWY_(uN#4RYWv4teihbqLz>^j~-UIq1%!l_;dV z{{XMJ&n!1^2_BU?I6UB=tw-cLIV*rYaZN@bV6e`5S4(y|_eF=43<5p)p<^!~V}ifZ zqlkjXJbKlI!i=vzqM^Qkt}uCO{A3(;u7AezhtuN&vk%9SThbv831PI3o$HeEjr0c9 zBNFBJ41#&aYf66-VRWx^qSAm%7+fzqK^Wv$J>uC6nvIAYlEVVG^gFm;PJ|?=aC&zB?RYz(zqWMfG3n!p)4T4FzcTD_OCqg zb{65e&!trdcERjW@D4w=$mx+@!)b7lPZRvdYXge$zXLLjc{g+-y@DAdE)?(^9QUHF z)b2{w=d$?YSBF;kai&>CHZD}-Yhaw5y@T$Mvsk8lBhj2v)kT}it;jU~|X+i%%}z}D;YS;*`T0|E!> zirBXJ3E+z*Qy!A9w}M&EKasBXIY8xx;C)R$?TDuXBz734=H#r{a-{B&%Ub*`@QtF6 zD@V3War1(InXNdy6QD&HShTx92c$sOkhxNKV;@RWbO6ork-aK6Kz&l#t7)zLhwVeE{ZRDoE&I*-lpcC?7Q zi&{vad+j2%?xzv~hCMoekyx7JXGyzOVgZH6=xIu%VHY>kQsEo4kIRo5MYmbG7{CsH zop*n;@GGueC_j1<59TY1@xo_Zx(of|{Ohgw4=*#~A*Q$ray1Pg6 z-8zm(9C1~r@kXkuzv&Y*fzQlG9XlSip4%ezdb9GZ_*hyEw>NOBf3V>H07}mmPM2Z? zVJJNrdRI52YMPF%A{n!T$pbwDmEpw76dZ<{+P~T{;0(`7VebQYy<0 zG`8SC`e*zpM3z8dW^8?GIXuIey^z~q53_RD!AGY0*|E^XL}v5MAKh87e_F?HvarCP z2BSLxz_}RuWKwkdNI9Qlxra=&hyyEL4|QcD^r&@xJ5Q3$yo<}2<^wC{0gV0?$VWWz z48?u`^xQ}3nu6z5)R_v4rD5;S^EIEdpYt$nHERyPu+G zWCUk8VUzh9neR1dFTT>Ny%!*Zz$1`xn!>&DB+Y*#c^*k88&s3+-nUI9X0`PcQ|8g= zSf9hGyst5zL&&I~!x$O!1#M|^msFl*F28uIbv#y;=Zhh@k7?5)TyP4KD?7XBZr=bV zLD{}8olZyl2_js(XYxPRpB|YKbZ&l?sV=E&Fm2YQN%S>M)))Dca*t2)Y3p#gBuX%I)d=8~5s&|RK`rFf%Au!2NbWI%dg zXLVrf9w)if&_svHiAl!Z2_3Uoj#rJ6f;|ru=+wXQFXxjf^Keaouy^mp+O)I zerZm#r)#nD#@A@)Y`!LI8YSlVZV3c(I~v|^jZte0cOU&~@9n%VsKuqc%Nv_foSbo*;_fxOy((e{F%o`L$i+gtT68L$ zc~>p&Rkzlsg@VXW9AJvrwAaJqi~-jMqFJpqn70^FB*#EUC+UjDnm@1^9gf|+0qa`A ztgmB9K|RjS-^MUm$CqrL%j;BiuZT99lxqi-v|xsA;QJ9?X?1Zm#EkooB=@R*V_!KrThTvf zj}=|$ao$=Rgk}Y@sz?L3Ij=79r+~amrCvJTnOvzOcW>p2`RQf%)=%(0nmk7;c2QRO zjn{R%Emu>yx|Na%C1T}Ml^(#?72_LwJN+(3hFF?Z`G?E_KTP$eTzGc;G0Q$`g#H@5 zkPyN{>N&2Ocy$Xj_B`sjqpN$xS)A3Ss$amYz#NQzHAh$QysrUhfN|H}x{GZJ?@5;r z6E(R#hu!}G>sHmhvP__373!(2lRkP%`lH3hn>25Rjg;pcgI-V-d_O|Ekmf~23SsRc}D-+Dq1W3uKLcvh0ah<&6;=KNE0$&`NH4xe4gC|q@ z)PL}S%BsNLjlO~Z0Q&TuyOf~QZ8N;S@!pdS>{I^$X`wJFK!FJ)cgY?2uOqqD$=0tf z;lWv5h|i&|!{8mf`Juj?)X{hoJQpfGLX)4;sx3#UagCkMKKALa4ocvYlaE>~bjhz} zRgN;uPZ$;7+Watkrv1=zz1fBb>s{A}JUHcwB(Rg9m^246>cF)53 zxr`MYkSOJjrqQiYwRV1sd<2)nk^arLX*J9bce%5Y17~-?Y<{N|AHzAcIJ`-1EXi!J zG@(h8U>Ixx`W~l&it&NrE58r;F2Sv?HmsK6@U4tz1E)jDs9=TWEzalmn!D4ld~0wt@duR~0lMBrD8~SU!NxGy z$G5e4Hl=w4T4WKSja^w}DoFGo_vB|ET-A+xL)EW$OEuGmU~mXIZk%WQ{{WAQ{6o|3 z^xNPXM`R-ajDSsRPZp;nlx=SP&KlVq=)rT;Z{&Iox#Bx{?0kviw2f4cnlJ+&uRPZa zesr(3$1GEYAo3R&{&nXKZF6|#jIGB25^9v%b<&K7IR~YBDyy}b;!iJE=5lAb+jxr7 z+91nvr*1NLcIo(5oSLG}p~eV2gWn$2;1-q_2&~TKv)CH%yfLdyE|eBhNr>&w@~-SP zPG;iIGaG_*V5&vk8eT8E@|0YcA!9!`J-tnH*1k7}7tD94cJ)%#rK-sup=~S@a;~Hr z^F0&8pYc9>Ndekh1Jf1TDDyj`#g3Ou^w>Tp+e02@nG}yh)|O9<8g%CkaEJ%Jd9ROr zEV?8CqvLx34`1nA&WGW@?HxiGWRR-6V3AtG4^mNWY=zAh^*z;oENF&B2H_PzmL{9V!o(=+N4EF*!8pVFm&jT%H!gAM(_QQRrba9X#6v`re`-R=eAytyF` z11R^cOOJ;0&~E;$X;JoDod6w=Eh zgd{%z^Y5H+dFjPJc{|*P7-<%dWVZdZ?v~ki>Jpi|fdI$niWm0WzJ!w3PW*5_%nv`E zc?%@IIAyr9*pZrp-D&ayR2?=tt>IN^5o}3UW=@ z^&7w2YgUkAJBCd4+W;SrO#ZcFLH)KQvbafalJ@flB>@Vj>&H*?_3{fJ3+NK*S68#x z$rN_yYZBSqeFv{RRQiX*{UN+9CZ!dl!7k0p8O}iLY8~ZkvGUVO);@*2{l8^4fF7bNl@yBjhpEUfzPdjl|{?Wj}t{cQU zW!>hfF_Cb@4ZDs$Ty+(eBB!&B&qD=4SE{1+H=E*>gK5ufZC`Tz-hoN&-`l71?aJQ# zLP_P^Ey&xPZNTG?@TiA}qlz*dZhUitRTsl5&Q1si+*g?zk5<|4ybM!GMc?Lieh>Jg z{grwyW3(?K=LMVF(;lDa)3DNfRd?eJLr~OO-^=qEW8Iz<07w}<`qz@al63gwbr?_y z=l=k$UFXAHV%J8nS)TIMBz6U%oeO?A#w(ioyj|zM?pT+w@ zy@oB_-LeN>Paej!JX!lX9}nq|dit%bvLV~$tBjL^ay_`m<68b3_|K(jwh;++Eg0Yc z78nR4spJvXt9ZxaPldJ79pTkMyT}g&Q4oY=li!p0SBWfDRTSE7?Q1ja{{XYH=+d%^ zy}z!l9zh?B=kQjzNJY4j7hqPw80pw^Ua6vdd+{CCh_IbH1hyP3rrn4ii8%iNKcABL z!7MfXU(eHovlbX_$$&kE2TJe!8*hDgro(T0w#!U`K`Oc3C$JgKbVbyw6{EjfqvtEr zz|iKDlJZ^4J}TF5H7zFEYqnD?j#z~l!00%tx)@7nzSk^5U~&~t13tAkiPQT)&hemJ zx`jdfBLD$X8)FrmXa+=T0pq`V`WkffN%TGjyra(L1!UQo;=lNrMfQNZO8^)S#<&@5 z-bvClZOZMzJ&k%RL~c?@hd5Hz$;f`mBQmGVgx2=5vN_Y%=7ioKw$pT@E!4%_;3!@O zYK^w73<4mqtdR_c1P*^Xqj{@Ms)X}S5e5Pdl~2ytpbo~AV`Z-8^EUL@v8~;~b8#HD z@OcGJ7yvMTD%$X!%3JFYnQq0bX9UQ{h409&SW0Gcq-Q3&J8unK>Nab004HzCK?eu% z{{TPYG_7PN+DB`1saWebQo|IemuPmY1Mt*ukik;b<+%2aQT-ALaeyt@!$MwIYq^3u_{-0W)`of8Ei8oeuNyi;)`+CuruZsr%- zG05QK)Nx(-YK=6XQYMns99UFd*r6Fx=RfR$P5TAwO|hXd5vmRKU zdGs0RE7eWQLK{m`fw~N!dRCpC#^~1^fKNf%pm~-_3lO7sxBmcIt=mWv*!rF;Im4B1 zH0~r9uNC^r3E_w3TiS&1w8TJ0(gR?2IOeddZC2p1Cm00Q2Zyx@HD3|x1xeTj&<9Kd zio+2!td`}%ExVnso)Iv_S1aQgNYbarM>}hvFc7(X;8!)`WfOgx+?)ve8}SWGdJN# zU55nY+PistFQzj5nxtX9!2bXm&zr&a?0D4RBl7;V!s(pcSCT|SD_{!IgI(AM&x78t zCxaj)Zq%R(edyHuC1Yi#%Q9*SEXbsUQNi}2*9Rnr+jyO0A2g+i>@!ZW)rO<2!e4~J zP^a;(O%7YyuW7T3d$^ePY}C7#WVJ8qo+r4zQ)~{{!Pd$j}$ zgU`KJU2Z2i4WGu9wE-t{`PQkUmfk}i&g`7|{d?rpe`dWwg}6VRSzDHM<3E_AbB;=! z{b>)>reeQ2%=r}2Z+4>sTDl$cki2y3#RZ!^0sLuPdlY`nb=1B|>(l%xKF@LvGBfla z;aVGy_ebYVb}BQs@y!W`nM)TcFhZZee}xMi-A*{ETMRMjQArpBtA2m|YIm?Da;~JO zWNPX%>;NLPZS9)$c~GNBcJZ)xytyOc5Lsof!QA>G;>E>E8_OGW)Z+kSCm{Zn-soueR}q0|fRWAzBv+kMwVA9bYjehB_-%D`Bx4)DP%sGu zeKGxOjPW#^3Tq?40nOEqCFv+TOBU>4_-n9Zw%z zXOF_X@5J5^hQzpz$}T-K*U%odtT761#Bx24pJvh(0B@UVtn0lYWmZm326?Ye*DYdL zN+qK#ndhFK+}9&B7+Vi+-&0$viRj63(&+QePs4NhZt)%18QYF)iH14t^%7lJ4c!Q@ zXT6zu9!VLjYv|ya33yK6IA8`vT4`!4I~cb2!^=`sY{xw0)5ZL3*j5YcpHIrU>%BzT zT}YVuM@)*M`s~GtR36_iO4&=yj#kdnTj-P=H#qB6E_EjgHvGi*9+k++uHBqv1`qo* zqg=c^V~*To`credwZoC4qrAB{w!T@;P7Nic+gU&t?-e)zp0&?5qjvZJHa?wyN{p_f zD}%OBTaogg@ThWDwa3Y(r&n*NZe}l)C9~`JRgr!e{ooJK=lRznmYS@tsuo?uZ~~BV zS{9xc)@~%hSr8uXNE9lGAmp*p{iL@U-HJ$WSz8UX1OC_l09vRI1nZnCmnZ$=MP9S` zW3Jt{ST+ZyO*c{498om)h=V?2jCJR&L^UXwWJsi+Lt8)ar}aC(En-WO`1!KA=hzNw zR>$^t)^31dYjoM*e1uecRl8xz@1e-0l!cYzpXkVwSSnIP@aJB*Pu*#km>t9< z{{Tw;Mvvglz1&57P*w@`xDxYRn%5wRIbw-dvv90RI3w{W?#8I#WgxZA$WG?h9~#GgYSjpL`hu z?z)y>-|bEl{PnAxr@r5CDouY({Nm9*5XpOU+UH=+NONdru-7qE?O}zuVom|E3lvB zKhN^(^ncm^0K+!Z0{Wy3{XR$zPvC3aE-UpJ#-C3cACfmGaxzFp(blz;+ zf7Flr@BaX;Sw1hBQ zV!cQ9EbxV|iTrh_MW|azByd{ED(qkgK41Wxj+iFAzV6{JJ}hdIgMjZRUJiNx02=lW z*r!mPJ|ycg$8{B~tpt&|mjqx)4hOz#mMMEjD|9+BH;o4u{LAz{&$sX{iDsu}j$|W| zzCd&MZpZ0VueD1(5R_jFKGFO|BLnk4=lad4Xn)$Me$(Pf0-xcAK!fRkYnZ+GN#VP< z&Bn0up1(YskMsAd*_>IZCCbbv5lWE@Co=<)8PWR#m#v5D9LiU z{67$CejI{lw?NH;eo_6XY;SLejCCq z{5%ss$D9xMMQulxR=%%a>tf|Ed2;Edro`SHv6Z)ZDu>lTs3q{tyAPKNcmS0L0=DR` zr%Df?`Q*BsnDAnxo4}8b*s6Mug(NGKi;VJDlm34S?kO>v&Cp#3&Us7tBxS^r0iKx{ z{*}XgVep$+_!Cg_(2x`{$RlV3XY|c|98p`S2kTsi#fv!ZJ|F8rlpU}Z#yXse%h+=3 zp;wE0So1I0-^BM99s;zshRPYdrgbtq;IYX#$I#cXNj9Yj;NAW&!_*dq>jbY;C z@tR+0cUMv9k<2$oZ8p-lp?|gB$pBddC`4sO1C)=9)j>l4(OGwM=_FIaB zxe#;5yMIdZ4-ou5@E?t@EpKn$HM~qZzCb;Xe%S-^724>(4m>(jbS$!M;YRbv9A}!i z)r(0*+4_!Gbvc&SNb9B6ZQ&T3T(?YrbRa*eHIu3MzeTffC-EA`j-eevtSw8x@)?-h z*uyMi?vmNh=6}!g@{b&RJMnGB{L|PXTd7^D8A$;2btILyEpBT3L)2}& zTjD)BZ7O@MMjK$>bj)(Gj@>xHBD}ik<#(DOyOoLH^IG2wd?(^R0P329>la4-ZqO-= zmckH8$Sip4{y8T#pERNkO66Hf>~aF*r?pgPDaV#;Rom2*l6=dq=QVKv3`iJ0gQZoD zz|S}VNp>Is=xcvj*|OacHj<@|KMwus#l6*?zLf+@yRX_%SdqvX>6+VHCW&S@#ElLe zHaH?hQ6iu(uw#-t*OJ}Zm2xr1J*(b)b*Gs$b-a)e#Dkpm+SrY$4)`Lk4Yokb=?67|T*^r~*wWGlC0n%3hC(UJY!ex|q&h%ggl z3y#E!^j%Ie$q~Wi8s@w~qRXs9Z*3Bi#rZvydYZKT?FvhxK62K4GkdA(h3)O$L4lo$ zPx7kQwi3ew#_B|ha6#%1eAns!0PLOcMr(~0@=>5z-iO-1ZoQ0SARLSkbJw8h*1li; ztiA$je+&G5s$JYg0k@iOw21BF3mPjN;11)S!;S~8H)=|wjjwMbC^sv%wsz~&t@b`p zPX}suk$LdjO3{<|f{-&&-FRqY^Ja&kUaVBu6tCRXyU#IvN#JW83U4Ld= zpi{eWL(}rE_V-Co33!?fO2I|7#j^<&lyFa>1b!9z9s4r;M$rB!_&N(+OHZ+VN)+1p ziAj(Q^$K6it`)AWxJD{ zbI8Sgt@}s#D@oCOQ>G0r!rH~|ZKhUeV+*+80i1nn;;Z%|`alOpKTfrcIYvuO&0HKJ zlX_|SvZT!I#t2jCRg452o_#9QN;84i(xKfcn+JhbPJ@yYu#nk4l`J@O$>Nw3uRVP# z)C;hM1a!>;8rw_b89ghA@n?aq@1$w1oI1B9Pf_%)@PHhX%O04iFxwB!_!??XF6ezM zc$4@;UemsGNr$=<{S8wWI=!93yuN94JQoC!`PaHLf`E~mxsNl#k^$VPt$m|U zR85msIBy5|rsGza!FdqC0}ciU`TqbP&WDX7*>=jfBN+$quR<0U&=O*SW8K|GGh82z zP^HNk;EHse@Vrb9Qh%LyLN*!Qn)6SCjVSQi4g3SZ9=H|h3oDcXn!K8csWkkJ zlUC4omJeRErX?qyzNVvh+N6Vn?@o=uT$7XQSv##z`I8}(x;GG(UiiWP0PEI$-mNXM zJ1rfTr$Rq2D@sXDG8}uZc&JRMMoR(s(~bIzPKIjuhUJ$6#s2^t{{V$JYS*(UWwYS) zIW>GdOmZ9bq>@(%{CCgUJW5_Ng&{KM2U6anA&ru+t6L zDszksikWU!AfzWEp*Gmw#TB*G!2bY`g#`A-YEfr?;44mm`has*#pgSSAp28=(E}sD zW9wI|)LQITi%*&&15Lbp0yFhBVYIc8a$A$oWB&lJNN$*P$26&NAw0LgrYSV6#Q4O1C%p;) z0zv>CF`7OUUdL(U3o`5#cVi^tzL_SeuTRkD#ao_BrT+j^^iRMyd;B+T)d*a5>x|b; z9r0Emy94#FIPf;PZEv8!vQCOr9G$=rGoEW|PZ?pk&sk~{mzF<1>nj`+sGH-i0na^@DRHBoE zj@2k3k3HxYTb&iG+HCQN&AO;OaoVjP3hDPrl;b@s0>H=S0K>nnZ=1xmkK|&w?Vc!2 ztqF1JVrpItFapsg_4eyn6IqLPD}(%yKE;G7{Rn$3M|+)1`*$35}Zj%V0$PpOR7 zmc(`lONXD#8~c0L3yN}WtqY&L(?wk;Sf0vUV912$ z2b$bXQvTY1w7g8V?}5skV!8c4N4xU@0F_h8&2$(3CR-gm*p>T6Qy3=!O*Gx?iD@N% zr*q(s7+73e-e|XMJgL;QF2m(k^flq%5cI$7?NV4rAxXgU%+Uq?FZH_sB~;3ca;3i^}wHhnpDYnkpDMas&GON@Y+ILG6-egie~tU%x#X1y=q z=DTyF>n_&pQcZ^$=tf3GK7K`e4lSp2e*SnQ*dGb$t*7iZzC!)b2RP?Hj%(*Hjruo+ zFK=#MEujusO2!E2GlTU!f1Lb#;LqCo#a<2Y{nnLjs@+?`EJikrB1i`Ta)rmrZ~(~Z zjysd)n(yreuU}ka&$xJ3Ay#?Wv(yd&z!*IM&U^ajImM^S+rP{F^*QJ5Dm9`@UpIc8 zx^L(B`|Zz#bdAhJKqx1lYO4Ml(GU!X{r+KJAl`V}Ri0L6)Gi#iB>9Jv-1V&KJWYPb zEqOU7{kH!A8s_EGQK-pS`sY>Bd_Npqv?nJYd>Z2QO%Fg>3xjUCJw$+hpx4c_>(?$w z`$VVe=lp7n`joCec_W{z8hnbyd)L(ZE=yesB|khQj>hPL{OXLiniM30)~Sx>=%4Ff zJ$bNBwjs`qe3Q&kn>$H@3ukX1;xE$3J<` zKj+e-d!j}+bf@glS@%AHhhEVpU8z00xIAr4=BrKO?GX_E`T>ro$_M&ag2$*y5z0u) zbJb2NDK6cQ-Y4;@_Gm2nGu|8G7J(4Jop*OD2>$>If=`M15*{RwpJ?%36j{&lQtV^V zh@;uWIa%#f{8Q1-1I<715I?OzzZJCXkclbBP6c@aa5*{6PaAC`nj8{ZB9iNK=}(3a z7sNZ+V2buTA1?)j?}EUba68sljkV+94O&Yw$#PYQ-5Z0)zoGW864UitizeR$&l3*l z#2?a?@804@c_c2)$t-e7`kKd;+_@;Ul2&VXef~$G%kfd-`~6q`w(bbLG1VfcaY<41#0 zWwN|jLaf0_1ZR$%{{Twtd_C}z!|m`qzAn>Z!2}7nD9@oKaaH^!;ERW3iU^h%VO^>= z-~rHJR@Slb=TK-^s=Ie5X~*f>yvKoyPDxtlsI$Ce7bQ7pwmGkeS{vEky|urKv`dI% z#tO;^&!%?cBNgHQ01@;R)-0{k?_Sd#AdsKjkXUDea0W>H>$>qz!+m*LLYA!KpOj}m zhDHzJUSY2IH&u!kGg!?S85t|b=~qiHrxcvEx5%02xazRvolVS7_*eQIKr(9)zTuKW zNC*CdP|x7K4HT2BDfS5D{;N=l9tZ+I44*0Wth=kC#DGn9eOi*;et+h3EYB3QQ(tob z03$-n!+sfx?S9*+%x%Mb!YHyx_9XH7Quwz~&?55fGzgt-)B@7U!DIBUc4=L>=bqI` z(aUfRZBC6ka^{NE6>UB>)OwfknDX}7C5#XCdzORw2#wqxu?-bE|~e=#^2=mlx`r%ImpPO&P4DmZ67 z@m>D_!(WY>Cxm=cZ+otNrs61}bYPK&JBR}~ILPbYrFt;0P81}Y*P{H6PDyjCEiQ2P zz8{v;!K$xrQg@huSO81zoIEDfwQtnlgt7}kd?;+ryXnB)8l`^t6gjR zNBc8bxsWk+h{Ye4g!Ld6;P&IF`keN<2gLsXhnLpZ7xo?(xRyKF3zIz1AkI#9jo&EG z7z5Yw`T1AmP0d@*{{YpFzF9Y?{5PNEeD~uED`^@5duk+gJ%)b|rAgvXDf}wYEF*M^ zB8o6Si-0lw>)iEg-vao6J1=D&<{^Z=jCbK9;@FLf`2roFaE;zTQ#NhH3E zs*!*(^5;1AKjY8+r9n2G@8p~Oid37rf8=?W!s}6CrCeCfn=c`d`Pu40=zl7&;0NP<0659(kF9zJthzRomp69rY348jMoX1)Q!evkZ%bY$e7mtJVW@n4AJy}Ml(O?aZ7P(16WV5u5o zIRy1Mr>&P}zeAo;dtTRM zP-*7(P1Ege<}vAJ^`i5Dz$l>dPvO+}tCyAn+d%Ooa8C1BK(VYPjz&l%;P>PKRGZ_1 zJ+j+Ai(%v_07uRM_8G-e)xIeI0B-3orQ2z++QT8i5QCC2(;X{R-!gWx8;i4F*2ZS5 za7To!ZPV{Mg&6#3)8H1i=!?b&KU!APr)aGbNXYWRI3lerq`2V^4XeDh#$?a0i z;^k3hMUW`MPD*i@J6oHa3mc-4EeXC54wC=&}As z{Ay*jkabh}Quz`o&eM;jPb61FYUOY1T!4!FH2p*ZH;Tv5dRatj`dBsNsq#t(9 z*?;YjKhCtRu1q*F#R`AB7$4HQQSjRLcF`M?>9m^FvG_}>-ebwMne*s}AD(LoB)3M2 zq@Q2J=PdQR*w_0^Sr&m-KCx8z_BPpx0Sv)OC!#z5~{x^|Lo96Nld27U8V zYL@;{+(yt(1FcjVi)M0e=NDlRI*e)5G1`5>;Qs(R=JgUS?(Xf%0uEJhInNjwuBFsl z>LJ&G69f2gf0blgXwqxigx7BC94)z(bMrSG5Dh6c8@35HaCR1RUR))#N?KN6pnK-M zH%jfU*T*SmPk{JwBMK%d0$ck+3#T zVYBAWN9kDm$zJ8Owmjd)J{^u=Tp&phe54Eldei0(4+ASRe5|jt6Zbyt>9)-KfFc< z*P8jSz?aASGgyhFDX@Dqo5|Y~GS)D94`L%e}!f-}=@+&LhE|?Qt)kNbWGqmT|CZ^LT zEj)2MXU%YfpQ%5ft)C9r{{Uuc+TrLLU;#duC;Hc)UU!pM=xq;mT-4UlZDEjv4IT~; zr&{v8dr}uV?W8Dhu;3r5IW_1$FV+&@O)&=sNe%!Vaoqm^D)IX{jsCiacP$_Q_Unqp zYPBjhy~@5KwYZmByDE>5FypD?Jbo3LEv^*ukNFz<8%ctBpbZ|G9P7A^#w?T1JmRiC zn{zh@M~D9W{{Z!=#oV52A2)rP;PhAgjW|I*dNHr028T0X9U3qD_5T3YsguKVu?PDS zB7JbTh~WV9@`L$tUAB|pSm4_8-bp4s6aoCIT}ZB^S+nMwZvpCQ9!w`6QC1hhdX#4# zQ)kdu)R%TPwy}>UCVUPVk6+XLD<0p$y0?rq2w=0!X}Aa4a6mc7PxHr4;yu5R+$^q{ z;1-?+)u(JnZzP2Ez$f}vlz$7nLdX6=^YiGYzes)({?9sBh4is5vjD!lkOC51{DU3( zbKkdIdk;*Hz&;*{eqAQq526168qzazSFv%V+Pl#Fl$YV2y8!|0W>R_vLO!)r_$yOP z?uCIJyZ%-B-+ACK4S6pl`dzq18D=2xKRWR56nF!}mp9W-6^p8tB~x);I|I#UI<6Mb z9VU;@zxYLOl>E^*9kKra*Q*iuI{s7yLUEsZ`-0El)|YvE9I;ugtS1|Qa!U@~Pxy~- zUq`d>PKRd#A=BX`U>0nWe-d+8M;PwYTMbdW-2C2O0%|TkTW2}{0A&9Fg+$&0)YP|` zYbM^r0tq$xTHYGa(lEnKw)Q45pXXHM&@_w^x^2F5pSUQNCCO-tR9bhr@K1q!8FH{@ z?j}`J@~VTA_2RuNOt7BmX|gnIu!XVU|xgeMgIT_YFk@GH!ZYk zPh2P;(z)GfHL-D|yPh+p>9+b*DrZ*nF`q3EoPL$rctc*bmrzA@ngkpxJOpI^SvA=) z!NSLZ^#1_sRA%tR_%QARo}K>yD&(7!cVCgRNh7q=rnZ(4>nh_J$4t|s*6eNzMDg-C zBYvKr=lRunH3xhYia@8PR~hgxp+6a`BzLMggkT>?fwSJTqSSU8%h7ExnPG zarwG_b<0oiH(S&lnQc6^?Uf1`^T0j7AU~(nQZ%G*jy3>}at~f=vrRknPN&fS0F7v; zQrZyJ){OZ}{u!IZmkx7p40-9kK~etz!z*isLLJ~tgZx>qu3<~IAOrv&xgY(0YS7Yd zE(iqR1I85d`qf5t1fvzBJ}sa2AJjEzjI-Xu6COYv$E{tk{el~K-F+N`*9N}Mi$<4j zS@xcIBR|%pZ8~U3+ac(Ap~|J~-HJ*pQ{>A}*gH)|4J`g>Bb6Lr{c&2BKd`=y5`OT4 z**yR}epU4>HiRe7G0&$@=T>C08+Rmxanr2{&wH@C=#QE$zhe&%+b|Nb0CgJ|`Q!Rl zv|qA!hAoca8FAYrar#%igb28iyX5^Ut=zC7z;bcuGy2kMP2IQmit5&}<52$1eh{~n zS*(Ld1~b<`rE1!I5AbwY52r+N{6%ZfX8^Io9(nev;x#0KPvO(~P~z=-+?)9v+@A^j zFKG~!bjUEjbnT)a3TSq$rY$<(k3<#GIy9qce6QB4E}u612@VP4Bc(1@v?}YODy4;# zBN5p|gZ;sZnO#tE4tsIHtq8Pv*n(Rt+qP=L=xxx;f!Gn=j!U4vni-1nOa87rpG=H@ zI#UhvAG*p3>_;E1bWm7%2volBsOR&kAh%ENh9u`0rlHs_?RImYWV>Yqu`QmI_qV>- zP&7IFRa>NcjDbR&ziFWyk>7Jj34mvx7Y~gNo z3!9QuLQ5XoNf`Q7FYNi7JCK9x{!Mxf&4dm(Y#i`8XuDe2 z71rinhpuaS1+vFwIwX0>1wlWkt=Rr2_`=#$Flj_7$6K5Yugs~wyl9{gb*7F|BlOb@)MMt@q?v-tO{Z69i}i5UL? zbNmf?(7KKjiGzd31a|uLs}fx4xCB2v%OCTae%^f;?6$i-8Gbk3+{hDXgp-aGALc); zdJw*o#GVDYmmXx2M#@6-oErJg-sevHNF$Sxj1DX5Plrzoz7n%As8#ua9{DxVLT;tg zdY7zZrEL$*>&*&HE8}IvGofW^*|N$<88`<$>r>#|Ut;*JcoQfx<$|0JyjN@cMC(we z#w(P7#wUrkkUJl5!n|wX$BL(aiTa6hyVHNn-kJCBN=ua|<agV~h{{U0dhr-VlXd}s-20xcI&{lVdV_CFI^lnw4a7W;3C-AYY&(FUEqWRj4McoLT z@UfQTJ;>mGb=li#*3wEc(vh6}(Gi|~$4ck?4QwOVwaA^B5oaWh{Z1>sgY1B)4D8*2 z$Lqy-wXe?CQc_7X%Krdp>#Lm`TJZJFjojA4Y)2rC$P6})qwud#@Xh7*uAgr9x~qdjsD1(s+Yx zNgTTnImQ7$k2JP=OoHMkYlmz&JbGrGt7>1^o+h5)Ad#6y-P!qg1Nv8G8z-wFwv~}< zSc=~A;c2@wM_v;qwx?-3x7D`VY`ofoj(w zE?BQXaHR9;{x#`3rlw{yxycT{-so|P^4(~Uv}6JS=k*nExh~d9#3t)-`MEx|b4j|x zu{`6TtWzcqD&391iY9LSRjXd)NhYjXyI_;#WCPprsp7mbtRN4)k&1@u7QBu(OdR#7 zuQO%#LAwmO&*{xlR(BTY(bH~J%|1>EAOYVMi+dn@mmuVBIL%*GV&&A}9!@*f727gh zNH*=sJPvSZmWX%PLVN!J`1Om}nr4{lu0i|97^;(95`j*BT81eld7=?xe(~3)JN}iW zDOzcP`bqmI>JZ<2D}^E|UMYxuzN7*x#y@TiF3(r-4UMhTK4MF3Rz-ih!13$&V!EH$ zk@Yj8>Z%@U^D9G-Bde2kxLxmN>9_@q$i&4{Y;aK=_}+)?O&^ z)#i(F8k7xjH;EB|z|TcF2dM(R z-@-o^F1%yn`zWD}A<}0;Q2`8C;E+kj=s5cJ;HsdiT1j2`ey1e~!VBC@ySw>n`}FN^ zl0Pv20BFAlH-$V+e`x9y#Vbau++g73bsf6aU+ly2Do+P|MS?FhEK)WO=D^27KPvW5 z+xpHuH{v#`6znoLnT&FGVDb2x_{+fht6O;UPIbs4U^?=7sA?|nrH0dGG z7ty`!#V~$j^C`%$Ck~z4H>mGU7v9VBWjER+$Z{BPb5kMncsb9tKuD02pQS!2*?1iX zy>+vf%(WW`@DFp0_o+Tm`&8%DlT`fw0H5aUGf#{$IYJ2cq&CZ;2{{J_rBonpE*l=T z1W}eC9=_FUVN}LOJNEr?N$AD6G2EEhoyVxAn7o+ILE@g%s-QdqDy;YD=t&&(Z3ymbC`SZcONz!_HrdWz@W=#BPQv00bq0QaVtOL5w* zoo`5oCTY<@?m#2?)jN+Ccz!XTx56==gbaOoq|@vdPpH>%2LqAcBcINj_Q4SORPF1} zujg5pzY=^uW-xE;QmIQZVM(r>NpkWa(s61vJ5ri0o;I2KOt3R{i3`fZV49+HacvKV6ab6d3`&K~138%@=s|wGX_N9@@^7O-= z#h;($R;bi_pyp3@eG4YNNP_6Pj)F2l{CZc<^M2G9#D4Eg zRX*|_e<4&S{iSurB&C(O2m8dDrH7j69P4dtdv({v@3WJ$O1yq~H~=5VKhm)EAC413 zGBj&}>Ro^)ynb)mE5xh_OM65IzQRxHaap&Y9sEmhq%zs4J#lWi{LN^nwYnENwXV-k z)PHEJ#bybng$J{@O2w1*kkzq>?O{!)C+0lYm0$cu)iqQ5;Q=`Q7!-dYSd!{f+&1D4 zPxn^?`Bv(TdZQ;N-1Tis>n(Z5Qt1|8c5|{@aI)R$u zR%zJatNn3LB66f`Yys5Pst}HiAtuwhXBpyY<-5O+a?E;@Sk@AluVQ5-NN%;K zuOlVX7VGI*R-u~d037F~bo4ZqsoeF84PIkxpXm-p2UAu>^GZL~vwHm1jizZgGFu~T z7$9JSnzY(P>--J*8pGXBLlm?wHQJtck@(Vmr2x)P=T%oknffaqpsQB?8orf+M-+@t zv8JT9VQ!}z6oKRUO+A#6!RPtYAtPw)Ju1TkTI#kI#7oOy^r_3FJOu~T)O&Dq(-iI< zLGCFq6iQ0+lzE_zgNllLlaNj-t7IISTZOxt0#M(=rjR(tDz!{x(uO$AC%DU-K|MaS zg+?+z8mt@gr{v&{N2LHs410?X-qk9`duJ6^9F5pL>dMGL$w|hCH|;w zB2cZcu;c-PE0NW_Lk*to$1Hirb5HReuy!+y7UfhATHr2iuC8TiBn8WJ>xyY5swy#Z z)tzOJj%`Hiz9OSNNXV|fJNr#pR}uyVNdO#vUX|j^hCZN=t!;R3SZh1T!#D1U#xq;S zZD{soI%(>+IzJk0`&3Sm>?JXhLCtfvNNz78PvK@AYoqast6IY_JAvRIZY!SB*bDdx zI2(q4I?1bSRV95NbJn~uptgr)EYBd3S;@#Fj)JwcZ2+#HBoO}d&0_MuY<0y@v9U|r zv_-pE;%qYl+*ffXp%aMott9{@L3se}(DHiME0bHZIbH3m*!i!;T4>YttC&Ybkb(5; zS@K1ax&G*`&*ILT{hQ(dfMB#?j(X>u*CikV7>-HG*Hm57WyRhpv4Pcw0`t%lT}Q)t zyq^+VE1WEcX|7Z+B;)B@9vGRR)-Bv;A;Ig6Ql6=%Zx4xip0VOc#jNZX0DD&_b8r!k z=I7qKzY|zmCAv>>cFQEH8w>yg@Xz`FTnC6FveR7x+(&TCGJLkq->q;)r!%rl)lo}F zG>E3Sj~Og0GV4)|IA34Fsb1bAt`SH+xvb{|_a?exn_8JpDlW;~zWRXA1N8p@>(noN z=e<)ep48IV6{UzQHPe&Hs7!?LO(uP5hqW{Wp^rg8Yzl7!r&>@unl4x=^!(~fWRFT< zAbu3N&lECjIN)>?1|U;+dUT}RXt6}W$3aT2Y7N9_r;a>z<=(cO-La#e~&5`$g ztD-g**ujD$^+8rBs3}D)5tVh}jVkbBYZzP)_Hq2H6I1wA;k{BPj$J(fVcWHK6jQjI zBKJq;2geVGHl7#p-m74)A&x27=WYhm$Ribt;jf3j57eYlsCcT{&~ z-^43i`=J9p@GGaX_%ZMfEEn9=)nn65-@W=D46eVq6_4k)}-u6D< zhv8T3^9w0&7FfPH;lR)2D^}aVpR=8?0Ca^0jf^u{2Y4W?>zw7ZZno^RIR=4%}oy1=U{2Gck z>-uhZ&&*9t{{RZZ!3qFABh#_yHxfAZ73bTVZgP31kaLe;{=I0Hnu%j<5 z{8XB7$3N=jKgnqaf_w^x=kYC%9X8s`f0|bSRk~AM3mjvmJ8N}sxqpz9Ud{f0=6W2S z6!0u-mDga9`&zy~QBb#sybgmu+Q{C^Y=5b)2WtcCNL1tBwHAFJ;D_pU^J!iV1&UpU z$4uLrp)Hq&6rH$n>LDXPn67vs#~jisIVQDDrE3u4o4RJh!Qth}SmZspj0&@+Ug#Qh zq5jD%gmx;U`5NcEradXbC+(U|ran}UM2p8h9(fQ=qi#`vc>vcj2dEZTvb^9-G`6%$LSGbcZRSz=P1aHOB~v z&!@dj97i>L&ejrwPWL)Fd`E8}{oa#qqui7HjZ&9TdxNno;7s} z0XPKUej}zny)o?<9{_Yuys@Y~f$3cb!wok~@Hd6@j|;_d5Y!SCd7%zBd4yz)dt)Bq zbL(8M#BYwUctcl{PC;td5t6R2c8XMtbp-MF`gZ3SD{ITY0MPWcQf%Vf268~fcm>DpM5J>if^tFs07*h~_%~nk^s#IEP1#Mj zPpH`@2L)W5`(PTuySp;t){QHlOSP7m7jKho_^7WV__yKT4PI%|;UeBh3UuS$jIM2^92_!}mFz|c>Bp!40I!JG=l$aXHnNNYPY1nCsd%X`X7b>d*C*J%| z`Tn>X-N^w`W_R2@dY^Ng)~QB1re_5<^+tI}RVSX}Me>zPF(-gA)SphZ^47EQk5HT9 zNYrg^T`Vo93k*tdyA+TPJ;y(nrF&1si=?pdP4HReNoN8z9%08McF#;^zC)5Ww!0y^ zt5S?qn{w3aj*rm&2=K>_bc@?2kuDM=ENdox$jSV3QOn{x4=|$Krbl)-{EjQcJP9ga zXj*wPEKJB0k>B3Cc)TxrIU)ynMn3YPBlN5}R9@k(wr6b)t7$9oaSeKqL|M#d(PEF0#f)M3gFW#Hj#&bm6aG=;2Ubn97V}c_001m%f0dZO>MPPJDleiNWMoqM8Ndthrfv*g^gxFtQqW=KPJc0Dj74LdTgT&tuH47I( zBof8t0NsxSAIw*d{7lkc!@eWGwsDQP2e-C=O6aGfvOu&+KBgtT$};};Y#&UToLt(d z&zwmfjBA={!+#Y5T<7`LvW%O*Gdg2cC2mA+PktT#R&9gS=B!)Ywg)GQ!HQ9yPCffo zmDCX%sW|jJQLU_E-!r|Ak4wDs^}Rkem&zVsC9}}s^^bo0H$rJUuDQ&U(U->=kG)>g9!s~>!PCbN$Fz}j@p z5|1%U4l~A0b|*}TlZ0Q$3gdnXIsX8Kd!&+g4=jK^Yr7Hy-v+sQlJsOLrbIx(C?nW& zQ!R?F?5dB$27z*@2_$FVAJ(LfPc+VVXkJh4D z;#0SH-Sz2CN#e=#CPf2}yg$xsHtvePx|Y;!VUwKj2AOOqI;~Zp~`n&dX@J*$*hZeajB=<6m#-ju$x%cB9)$+fG zJT@<;GEPizjNpz-o)6){uSnAMN%SbAOM8i6iB3du$W#;b6_n)S{oTzY-=aRyUAf61 znrycb!4pX%gpEd7nBW2HUmL%OZ|+>k*OL%?84m;6r8>3Dk(7J6T~F|rBvq_TlF=)d zx$k~2@%&d2wY9PR(w9WRl*Tn#xn0 zwPB*wv(xmu$Y6D5n4kC;AFXP(v1~^(ujj|}uO$0ZstmJnz!|2r+mop$(y~dRU35Kt zbz3A|%{l?@NBGcnT|L_ibr(La2lcNW^B4^A$Eh^rxOF)!N8?TNG2I@iJ?)-yXOC$A z0B9fNXd0!7T$y7EJxKom8uI11-nl0}^vAhoDhszC?SD!=?S;&>JyEqgB1;*N8m zG6fw5fBMztZFb~+;(5=~qZgS4x_eV_yBd8N>k4C(=gy3Q=x{UdSMBW=%LS%EyW9|d zhPYn==_kaRt-ZM*Z(zHQI3520O7}}IhB|Gc0Mbh)I%5MLPAh0qZ=hYvRB`JB5saA- zlflCZd~atF!DT%9bT!;3fHb5+F)7a^mKpw4&uV&)f;46+b90tcw1K&>bB?(FwWqS> zS9%89mFybTix5->IQk!Y>!9$wCIl+{Fb#}x>s(Af5xfH%?Lzz<50$a}>%Q>kiL}oV z+3(Y)-x>fmk%GRQR*{Tej7rSOztByh?#2`ncMRZvoYy&guz~)$206(%$L4F)u~^!^ zm!tVlE%HMOTks+81y=*a~BKj?ojFlve5Yhxpbn z#VNchk+SI*rsC`$nG+17@vkpw-?{$)3ggmF>F7<}?0ZXin@^T9eC2+}7_6&}Qp!)6 zXHdO1ax2YE z%)5Cw1EA|lx`U`-KqvnI)k|H0Ugx8KYuZFd?~s$wRfxP%rz_#4!w$PZBlH#K0`0eC zaz~{scg?&KG0s0xPR+eXea};W7+7R%kmn!{5BO6@jdb$COE!BPab8z0p&(#TNu?HUbu4DcdmZCQd44+0PABi=esF87M z;lLPDNh6Rtn&j4S-1ZdIZnYult2<-yI`xSS5Mp>aVlnT*=kfml^`iVqy-)y&0rkoH z@y$>jI%QICl3KJ};o9?WxGS)4KH zf!cypjGy!R(CMq)gOk}E!+b}LrOCEn4l#z#I0C0%iMH`37S>FTKJ|I=Wc)p8p_e4} z?NsTj4K|VK5qwd!SoWKFOL2^20RI4=YK~utc8~Ivkp^-}P)5a*G0rx_GrB0qU(|WYTXVniMrFmi@BPX>;A{og9sr3H<^-!T_ zqbr%S)noBxgMg`Yn{$)NAJU}%0ECjuIP=i@ZRWi9P0**iUE(5<@NVD`Ren^7LwdJAm0;Pmx>^U3f)X-OImJPH zIz$P#l(Y8W*cDmVbA9kg_ouGc29oG>&*BZ}R#!$`;5Y-X@~ruFu(A0;Ljlo`TFMr2N}x+H z3Egsf{{Sj+j?N4LJ230=u0Z@rp`_i**J~D6R=F*eeT7dL9M`gVOUGLNnefKw+sdY8 zk%B%xWzV7i0P9zbM?IP-$CyW`!Tz?RuMvHkZ^mj4|<<95e6P~|Xy?gNEOSsiP zw5HzIMYk5tIra6en|&@2q!oA1Le5X+&1~g+E{Peq>vW7e4+QC2H0?Z6&oqRSnPn_L zE`#Zc+HqzO18z5QlFi5DMNj>m3a-%#1K5$1Qp0GG;24zT@Iq(x#R^GkB^0(q5le5t z`#NL&nyKP#BTexZjd5dnZWb8_%IdtS>_!J#-2TqX3~s^c#wa>dxWV~-GBf=uZx-|_ z&vbbgguG9y{5`o%F8`Maw9C zgm4cT1D?E!`d8t9iEZ?M4#953NT6gie2>S{w~a`zQ=LDQC$KGt$!bxWpKW+u>B2LrbsfRdW=-dXK|z8 zO)3c&$C~}LQkd~6=sNOoT@Ih7-p6{+Ec*(cImtfwu2;o=B(m^##f^2Yr&ywAIcXRu zCnve3x%lnj7L>2{mAQO?HnBNBfUg?5a+fq$O-0P3CwJ6u?K5lt02FSSIddZ`s11yF zu8Y8P%DxZNmM3uwL>rOLez^X%$NYHLZS~)Twi<=AhlH>wM>!d;OT!CQ|0AK`kK_`_axs>)b~*w%t67zjz7*SDR&=~;4!ZW)<0@1St4by zwV4~SmNEYT#aA&O?GfS$rt=85X!G}W5yz)o*FwY8YV9MP)=ApO)S7z`=mrnWI&yl} ze~oRw;%20DDi7mcGvUA5H~k?}?@V4u`Js@H%n{bT;_mj>UDNFEN*qfNWnA-*%9Lr! z4K|1EBPQ38lc(B3bjL9%^Y;A2`s9B~<@GCRE||VJUb*dEEV9W2ZxSd_$Ee`aTty5v zWJo|Ic_*Bj*-0hpOIA3G=m5549Fi~(t$DwQVe+oZfq*@F*RIa7n|-1^_~=hs^S>Eb zIg~h0W4$?VW~q^m3Fe|=bI;{Zne0z0z0uO?(wS{ncc&GLs*;yZdh?pKrCq8i zR^)c}tnEtU?H3HdZ5)3fD(h?=ZX(x%c$r#)?2cM=YSy*ywI{Pna zF!fVOTJ~Ui55jNTYWLu@GZgBW@83pP{$ZOtGAiE z+fVEM0V?YJt)tTPuL+3V9xJv4FmFzCUwD4bA0GTK;PIi`>r+b$+n4ihTx2d(f$m0Y z@h?G- z!oEhd(zR_HUV`)Na&C@5z!fAOYq_7|M7nju!*JMTI3V*{dUwPKFG$2|Cw`SlB^x$$ zRH;%=Rps{$_@nVlRq>XQrCUj2_Mc%wtI0coR zumPWX^m+acXt!kjmhMQ&>_}l*H-7@I22Hi>QVzkgKdo1?U5Zs>+~e2cE!Y?^FW_oX z@f!Ppv7p6h-uxTZ?M7A=ln?pl;O4Ndyb-F|6XsaT0M1U*NAjiYqu8}H^h~$^01F>e zEs&@&>zYRRg{n4rjC}$B02;!VOSpl~_?b_*#X``s@y<;yNbh3PH>B~_t1btc20z)) z^Qb@JF4ajnpB|V{Kc#2Ir_U(?QJd2M)!4i}er-u_R4zgO#c8gOE>Ka<2#)H0K~*b8~dm~ z!mzDA0(h!c=TDds{p^AI*3GZ%^Wu3in?f_{)rILTRflHuv6*-Ad&O5ki8Y&o4`C?- z^370_$GYU~44S3Go`9JN{^?Nlz`Di89&}U)$Kp_Yw-!c5X!NvOn=q_2*=kr z{{TOlbEK})UvQd~RqW3*J}cEO4j%q?C;hT&GptW2!`w>WQfulB8}>@DHpp)EXqIec zg5kOR7=K^!(Ek92Y4GKt0j~`Hr3N$f=A-LSy^z&6YqRAAxns$4e;ROv%g6+p`tIN0 z2ZbZX=;o9C;y=Q*ZT<`RLM#(t&5nNcQ^iGW9?zlih|=CckrOs)zwnOSNOB=@{(hDB z6dw#cD+mE}wLJ(8&1gm7zYg0BCX)tz3g)x=(ze=%vuyZ69|&H^av+htg8u;Zs?=Ts z)MV*vDgOX~pYX4|*Fe#&_L%gmPI%@paruh74xeQi#CmP2=cy3@Kb1qpyX-$_rsu)7 zpAI!yjuOU3J#mAY*|hjWta&Dxi=*8uk3QAi2RkDSXF0UB44e)u`6v zX)TfD>H9d@tKi+J$DscJ>(ih1cGC(lYqkf}V;|1FMCWlMps4OkmB?<~;-*z4X4BM4 z&Do>#^Wx8kBk<>nuPl+bh+UICjtLnbm2#dNwM)Hf)+u;aaHrU2y#wRXE8}fQ`poC` zHRt{ua=Oj45ATEeiuR~CDo}5BmHy&uu2&?Fw7doI=S9?X{7V=fUKC(+&~kIfdh28O zXQ4ng`6>{H zMm@3tAE>Kvcuz{SVCiQcuI-?Hgnxx_0%@mFT$n6dY( zam&XenyP@iWxM)wQiclH&r0^5r#9r}K^$>N5T_#}=~m&l^V2K&RB}Sfa78a6Y(fbu z$oxekA|6g~eMkQQUZpAl$9fe3IX}va1^N;BD74=tJBqdCgFuLTRM`x!B2sv#n1(8G z7zeMtJ7D@y1tf?7d-Lg2M;PZLH6%FP-lMHewQ@lRRf@?vn z(P6;GaYpPeV%o5`Y!uwwVA*asB!UE_NndTXxYO7ow3)ki@U#(Dwx*3E)#fqCY$V;F8vHL+%) zAsM9Aif=;IyqH|#tLirxw_}_WPD7H|wYw##xe$5Lv2NjZzZ^qKx+wO9G^ROFpw zBo1mziFU=uQhBKDbm{MZ)yB{C8K(QQWuhtEqTR16f;t+=Wovmc(+0XZtRC3ybTN_F zJXU6*ZG;RtBAi(7ZH@N18m54VK4**hS3et+agLzYpN8E`b1QV(YbI9PGvlCRilw_u zEoSU{pTc`fO>V|Wu3!%Rq&-K{y4@R9w9>W5c#{w#7+-fKdFzwfyzk);h#|I%$j&AV z58+m9bs5uN)c3|r4=XnA!mylG*%ED3`50duZsET1UicU?7{&;$7{b#<7y$gez3ZUW zHLW?kM?BY%nc^QRE^sPShbElDUy%;mhlz@>|H`)G5Vp z>3#v#49NaTj|Vv?6&d)iIxZrC5PMZ=eky7%a4@v=?iZfvSoIGT_ithdu{7yGlGg|69ALZR1{9`@GHS`a{uaCbAygjQ#o+;8~ zbPPALMjR3NbN+um7bgbNS1sK>X7|+j5^n`+a6#s^l)*hp9Dge3Cto5$k_<37KDGLv z@t5|O{hlWHXMVmMvb2|2mMyR%+J$(}zdW9!(>+M9%RN@$-D+1?k)cJBMsz|x=p>Lo zD$<;}lWb~D$;Yt_iIpS+k6L^dl3i!ZQowcF+L*p1)Kz@1IJ9Sir9l|{tI@s}d`8eb z9cO(Ot)Tw^Xg$UVkqF9;IKk=l6>@DW*u8bJz&4pN+Zza(4AMDxsZ9k1W`!Z3~4n6C!x%jbQ z{$`&u>s z-B13#W?XAeInR|Ef4aD*bjPUQv8Vj>AD90CT9jBv!7hIzRsR6AB<;8Irwu}5z$5Y> z;Z>mQ%RS9`$rr@!8RWH&;3U}* z&Y_f?5t0wrIqXJy*XrkmemO3Urz9_708{`UFC#o-*ZltgFUNm?dX%g2Cq)S^3tYy& zx%q&vziz*_*4TZS?bs3X268^5r>%3c!ka+wZgXtsp7XNM#vT z9)wq`cz4Es40VXW@gA8rpKx#)qE9gospF^96;7jX-q_1j?6&+57}IRl`2)s5QaaYD z&`K{oeKB82X!bt<8XUH_Z2>>?);Uy$WIv9oOc%v zKbWSfM)tG&gzWZf?><|7C4fIK6;kI(ETe&IjlJ0N14c%wfP4^ow{);Jhu*j4h9{+%f(_ znJ0(-2&Z2cLP+QRWQX|*pK9L9;md8$o6ed~YE!11XBF@FRv!f-KQD?PasJB8N&GQX z-98I+8sKd7D;%H?%+RVy{gxi;TC>59o_#3(&KT)l@K3>oT>k)Ta;kds|7Gi1@GI z%pY>zX$F7i)sORPrBdsy{Q;zp5;h?)NeiD^kZI~yIT$|m?9zNl_(^I&i%Pqk4sy1r zzs5?elvVOh~_^G zO2i)F0sQe#o8#|;Hh~|780{DtC^@3@y_yeotA*%IVTDh zGD-RjSEft*Na)bF`Y(iS%7Mq2;D05kANH=)t?(=~4KmvtuKmrAzz`^Y()uk0?%K^V z9|Zgz@lT7dB8J08xdFP#BJCLbdH(=D;2)ucJ`nws{3h30jm56H@de{mf)&9Q?$`nG z&=5B_t_B8klk+CG`%ifLS%||Pul<*9+!(A7L@}I^kDwQOh?aG>V{=d)V)nPcwO*p$+{{W{=ufF?S`a{8=93=1# zhvI)7O?J1wDAgUU?Ou1~B#;1R3(yq+05AqHI2H5$tK+HlYpE_SZkk!7ZSL;Hi#Kvf z>P=uwt3M8RfzT6>DzUS+Kl02R*2*d^+3#;emdfAPPR8jaWR;THFURjTPl?x@xW{3h zl!@YmL;ksaxvWpI+Yk9R>GbL=XlU#s*1ivX+Gd*aAe~f? z-GhQga&miD(Zk|(?TiTqo2J@ZM60v{0Y8rw^S8m=E`ws_K-#A#*OSSwM4w-?gyF6u zQ`43itZKnIUFuggv^p(M$DSk9cO%UlakfEN@CG~ftO;zM>2oYFzi*rTaaGe%M+MQt zgU<&wVedS+!0u`kS3*&bdEr>+KQEUF{{Uo@{Ka9nho;jn)yWSS1nwO_{dLz1g6v8B z>yq*Bh(?*E-86EAkVbRrGmt-1S|;9?2d1d2@OxFTO@CU{uJ<&NDUvXR0(m1G{Y82I z0PO>IH7^oNc&ZRg3EDeyc+cZoKL|W8srZ8S=6eJ#v8EqA2tK@4XUDxI^qbpQWB^DJ z_hWz#c^`qN7yHW>B$cjsubANQd(dQjb*PwN;(^k;E!0;ghDLSjYGpifY6v)~vO%C} z=xgW_i1kY{dI<;fuJ7V6i!K%mjU-OrXLT7s*%|s*n%Ya{Tt>sQ16w+?z5U>s&RC3+ zDt^_)C3R+VA~rn%C+SmKTg=lg)IDo{-oWw3In8U@SpY!_1}e3=E?tjG@K=wuJs(1} zvx@daSl0nN56-$l@e@~d{{WDL%j~2N&ya^Bu)#YX-mykK*6G4|eToQ`67z#f$KV(dY{?^k8BBGxyT#1}`U#JTm}X@9ov zi2&TlJCB_I0F7isgn&aR0;KZOseWp8z$IW6i6^!xJ2hsCN%T7{7viS5r#i>yi@1gfNrp-MfBOFb z*FL%7ABq}v+D7_~w6PvD@|IG2@(xGS2mE~Z!Cwtt?ghBJMu~6;1RRlGfob6+vWb!m zDiM`l*~ePuojA>1XeB2Xq3O0C5^#~Y(_!Tb$#{Z%{Jn)_$e`lj1ZwwR}v zGB|HW2Q{r4h68V5`Pa`M3p{VEwy$qy=x=S^wzlDtqtp>!M8pax$iS|7!;#q5oSc=} zDh0ysPp!9<*(Zc~$tesmlX8F9OwybgyStz1`DLwDGbWoAD< zbHT+zZt~v!_`-<;-y)`@{l$J^In2MJ3$ypMz^5@ePq*oC!I{Va|V@eIs5?@W;Xt=~uSb z4R-^|s!x|2g4~1G2EAsxX*AW*aaKWI74eh6enD&aa3h(C`A6M8hmN0(d#zE`^tg44 z87*gAqjR4_*ws!dNOD(JJ}I8f<9srZM_h{R{v7!B>%jMn=$~kTl&eC`fPD^e>s@ce zUxsa@ME4fsa3>&vo;}VpUQFy_c2`_FoM4V?fw!wfB-O5ZzO(WBS-MiTcF@^_#z)M5 zGsbIz)%;DWYw{T{uV$Q{&g6mg9Y0FKHyciSR5IQ^;s7LKvu(>p?lzO$xv>d&1nB<(XJ*p>SgM-guOpTv; zJcHa-Ep=ggXjn3hnI}B+Mh4IidVm4BhfMHkTHp+TGQ;%XRW)^>o9s-uLxc6=n=FV& z$`1mn%PUC0DHt8c>rMUVAoTSVkej`mB}oP|+dU}_#xtH*)A@F&7!bv=pHWXSmr(e^ zn!PV_U9Vv!xEmRcSZ&4u#~csqT$lw(L%;-&ezn#4BL|Q_&#hwW+I#uUIvvW~k=!5f zs!crs=!zf{$l{RWJoOa#qbuL1T8-4SnFk}jD%$ACR_F#$Fgnq=z6Ln;r^9Sn@vE0qL5f4tr5 zXe{1L3=j`W)wI*1>dBYvPxw}}77X~{hYVUdT($7~b+HIFlkX`@#p)ssBUbd_evbA#Iy_;iUF6;)HZw8o*{utI z2;3C}S{4L)9M@{HA-GwC6=9AQFh5_dbXJ<`7un?%OmI(g&-A9Gx&_*HXPm>}Z9^fF z#e$xL82ozG=sXRpN&yxwpqvcgez~t((={JCfG9vw&lwfD56{UQ(aNQFaktp;Kj-Lj*=V#UR_@O^Plr&u5hT%!{yckB9hCZd` zJwE~df0T*gTW&s7KBsXf^~Ekn(?nBmW5w@0I~*>{<_ApttP~QWtDOQS00SW;;Nau> zde^)U3Rs5Bkj6m&0B8Bti2OgN#+X}J@z|9I`O|T_z4a2l&tvDipAI=v65eOW_etb_ zRhuLDhr>{B(=G0VjDAte0rdoazvTTmwif8?vLsv%FrZYArrTXE$>9#Y!wiZ}n`-Im z4JV^MdC~qg>pFN5YkDDI2Qu57AAG6D>7ViRuLk^O(sdURTIwPxiRRkm6<~b`Jl9LA zd@#|qNI(|B7@UHukUEZ-$NBvDy;IbFc(QJMAR%$2IDjcf}=|4be{(+A!*p0#9s=S5MYk(V8hI zvHAD=W?JXs9>PMw~TFyFW>&^VbB}8I zzTW5;86;!W*7W3>x)oTZE1y~F3^fngveldnqc9%BdB^ErHY-O9mD)3&we(hrDAYe@ z>DM4EPFFeW(EfQiubiT@xV)5@qh}dC25TBs6RSBbt`0BQ!v24mKb;#4kTdkDVz^O& z1y5z+jYj5GL2To910x*QL#O;V)+cfel!OC}suDjjT~4%bYn?UP`_7;7E0ln9!XB0NM}qXbs}BL`_SaD~6GS$YanF2y z7_NHNFJzfeg{5{$sVRzV1;FY({{TwGz5U?HIX~x$*_3P~I)>ozM|#SdJ;8_vzd_Qu zo7Y33=&o1O>>p6NNF!a$PafY|$oP}t*{rY(Sx_=aJ@H+KhGKQpSIZzBq@0fRi}5n* zXpxM$C+`IZw_{sH=$S4`?C`7h%!LYoRQBsx?G`ya;;vuK=3Fz8jl`2xS@Y7gdx@mc z3&J|pcZ;9tR|yA>Y8S?8m5CrgP~dl{WC!b3at}^wIdGuXgz9EXkPT9s?nn=HHK*l? zWeVLYmZNRz z)m>`v_6Ba5C)9uZ3K05K#J~F+ING+ZOjdtOmIXOR8GHR)uz1< zr9WZc+CRcS3j9T8d{B>X1(b6{9d;7M{$+3p9SJMR&vB4A?*9O6Kiiv5_@7}Wnc;hu zO(r4&ESL`G1T!!MjPrrd9+}O4d0bpQyv|gRO;Z;R`O7iM=~~U*-qz6!_*j(kPpx-y z=oSi~008Oo{{Z!?nyshqvSizObUh2i{w44w^hNEh-6TEHFmql@sp<#&MViJ}ir@Hm zl1*HbMzcZ0jh@3amU?xRw<{di9#Rv_o^eqa#-6I;dhSZEvz)tL2CKqK?6#^2z7h2Xk889uM0-+|}bMEUgq(pqS~6`pmI?2v~99i(u73X{bi zCGd8zAM9Qon$|58kPOp;AbT9$2=Os zy}H))rXuRX;w*LEgnvrrwS7@Q>R9pWTh8!)k&=yIS&NfjX%Y96Hl1q zS%yg*0i0Lf{t(x!=FlyCtC-trf%k`}rfcKtIP?pUp_Mm!@z%Rv4c^&lB}_5f&Y9t5 z&+`VIdUB}KMrx-kx<}K~YF9T4{*5iFk69b~15UHkbzAH|_Jk_E{It(MiLWiuJ}1~% zg!6O@b^{q`-z1NrAJ)3~zBy>t@hFPI;xnF5C;)yS*9@psOGuikv2N}DXL)U*T}H@d z)B`VZ<$!+*)Qa8+fekA*dSv3cjThoBhpxsC+L8%B=bVQGS4j+(_nLy+|Qi$V?eio`2FA?pYx<^7IA>s&w5DhB!q$IbUff^`P6f1WLK$r z+e?Xg3|YGZap}iexgCjOPS8m0k;m&)VRk`-AtZJ=Kh~~8HV_V1Jwt*#H^hAt2X*{>@xdSC{b)(PgLdf6caJB5&;J0fnjj;Q-?6Sz z@8YI~0m!?JxyAzz&av-4GigyB(M}E*84NMk(AJ7CWQlEB=b<&D?%aBJrSd}z_pdFN zr|r*p!8Xh?jY8sCG?Wo86%+?w-E0zjIjws^yj z<}0cArQ$#MNHv*mv5o9cuQkHKJ4HG=oE2b2dvueHD8=rTg0oYTaoL}se+gucT{=Z4 zJCzPU4!N&Fdz*hR@2D9AAmo0P@$Z6uD(gCSksMGm3@B2>WRcr7dVh)xn-)N`#Z#u4Qcz=Au6-ag00bUcDVB%k)VHBDoAp8$R}>Fc?Pa@O$MoQFS3 z)wj8h;~8PQzIxUfw-Jw-!u@H{h*O_D1NEa(G?~&H2+HJVCZTH}eZHczqQ4R27h?n0 zpXpOBueUk4Lr+J!I-=+Hc`87~3t-AH+Hh%N zsoTV?-N~ZpcD}~M7NMjZR-u6+kDkJ~@Ayfjh~)s!y)nE|cOb(R!SH50YJh!NqGsEH}&h*7=WOaaS%aY;7#TQ@-Hz+Q5bVDRXOJ_;)ZTU=SeeDY0aH z-7!|u-6jN*bDSD+upKgL)Y2$-Thg(-OE;M^rg4E&rN*_gGOTpzHEY;yVFj7n9CKa% zhO40Kv1u~I0bnx6sWlhDy9g!L&CSDv5&h76n&`eEXmSr1>G!s=vl$pE2no-B*RkJf28Vi+zyV!arLN!x_9o$(lfMnvCn80?=91!0!|HO+~qDL2m8jl zj|r(wY$}ZJ1PXSk;H&xd8Cn~5C|e@{;|PXm?=#zcL%tP{{V$`x-aaP;*CX-Q%RXY=vfHo#6c$SuyZ@5ZCUU=pM~yGgD7Pn@J4G_Qt<1hnAdKs zl0}WjC+6q$uh1C23H&9{fD>wWnv*CQ`!oXt`x1J7AN_AV_~qd*2_FL9+jwhAoi2Q` zT3d$%kU1CtccE2Nl1Xl2=F)BSJ|kUbR6wN_zM`RhNlr^f2hy2y2}zw#Rby2rLC!^O zWqX}VR803)!0?zpl;5>4-Vx+_{{WFx9ANIQqf7Bloo(TJjW&HPWx9Nnxm*GdY@bi3 z9^R>@tz~d}CeexgpvADfXVSV2E5Vx9wQRH7Y12&;bag^h{SGUh(yfi%^hgHc%m)LW zxUal^9_S0<{{RQ+R%Rtvju6d}-H9ik#+u4h^Omo*63wT=7}u1*8xPx=19PYh|p zY!5Z#kNM&bKQeRve=Zwc{i>&sk*8_F2RJh}e?k5{D~wUwG{nWjP8 zf&D9n_?PfT#=(sGHPRxEu*~2e!2bZB@#A%mkKQTO0SR)ps(VDfzgproPZH{y)0e!w zn&6-BF~{Z7slrXCdj~9~ptdrZR9(L-SrhJ%bkhLbI7kJf3M) zLh?^Fx=z}&TR!Kc{2bKd(0p~J+{GeGBF?zr;2e)pU!pSV4C4WgIlu>{eqZ=j{As$) zr2Ps=4u2Z`39{Elp`cjZMIrL#g&R2Kjs|~P%4*VDYS#Xr@Z+ePf@`5`#C{{Tv_=!j z<>9)K-nkt^#yV!!0_A#j&H$~uZCg&X)MB4fuw72vAD#wcq>oT*PsAQ7_-%h`q8|%f z+e$w05-A7z)^n)h)swf8%@t(Z-16I>73sx^7jg1^YcA)+wg))6Ps~?Us9$&poQ($T z1L-1<>sVKJo(LJ)pMd$kx{fhSYJT zPCbmk{{ZZ@0o1e**&17w9gKrN%CxqZOwCH~W>alqET7*4_|p!v4&U{?e!t;YTUOB6 zs0W-;u39?VS-u7evAL zxF`87FzdQj4m4=T)>bqrH_#MUb2#2c-CX`gnf9dT=4#RY-qVjwCL#X-Z5#Y*Gp_06 zU}Q5AXyyOjs zKU%79cjWnKzd=d%v*^E-RniriZ|$x}5ueVT_O}Fn?BIKfs&3C+*sA=>Gr;QF{HD51=36N0=p{J6y~C&(@qK zob*brH3@&dMJr#-P>|<7)Ld57vL57gk3XF$lI-=5`P2hcpI`-u_vua-^J4;L&-=!e z^&N>oy<@~<@;~E9ZSULuS&{TpP_^vDV9Ngh_tF0V8m`w;bMv?7{{YvdGTK<`5)+jp zJ;

m3~Rw>c3Jy#y!n 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%0Df_Nm6$Ln7(H0;Te zPq#fP7j_&1dH4R6KUG_{Qmf>BKPSX(7q8n9vBAN{X+Pm)XoqU8UNEd8+o=tf0+ zx@I7plg@wo)e*QVqyx#R`m*jG)$e2KczkoBl`OZ>FQ_DB)5ni=f%7f=c_XIaef?|Y zGjQh|;-0SE$>~DFM*D}gc0Q;cJJMZpZxCaEGCBQeM~(EH5N{$a>PWAaMcd~A%KkKq za$6^PCp=U7w05|?tdFQ;@y3+d-F*^(++~OI^r8G~r31h1(6Al10s2?VuPcn@kLO38 z+cCMZ^`-S>-L5xPpHZidv}s4peH#A&vRD!MibnDFlemL<7#%qyAED3ZUoQ!qbYqXN z{{UW^W^LIle-@|pWwpia9kxEB9y-zRad`=D$8g8^)Pu)bO98U-EcXPAAJ)EAYnBa= z$FHqOE+u1~=O0>23b(ml*{+Aw4db05T%Wc^2tRZhm3(`nvfy2(ZmcWigDZk_BY&U9 zluYFE=O0>Y44&*AS8b1~AIADQ8-Cvy&%HW79qB4It?W(0n;_|xVm-#f=T>o$#o|n zxyP}ju&FC1FKX|x^#q&dV@-@eHUigugWb`kt82{kYLB|2Pjk}sod061}vY@XHf$-8hq zRzBR*TJAtIy}9=_KUTi%uWIdNkEvJ1`eyR~0B?k#Uzi66@&x|?Dr@-PNdb@B5N+rK z0h;-D?T$w>5=XhDlIfU#tZ#m3`l{Wq_O{s{PyYbIYo#%ETgdWx>M0w>x@N!<@eVk~ zK*fB(w+=cv$JU$YNKQkyuUe&sSKK|T?0q<%J<}}}fV+)>{v7`RBigJ%@fSp3rSD-) zqo(o6^skoM;!F{|=hrn0GlBQ8eRD_ES5^w6zK7K^{7=x(0@rER0D=$GjDMfx$o?v5 zAOhElt~&9b%D!1wI0GkesR?2;oC=qPidMKPjQW+nE9ee$_P_}5gM;`};P|_tsmI#X zdK__|OjpXK&JIt@{(Y&Xh+YpA{;aKQhqQM-w113R3dW>gMo)DI0+0AyS`lF1Y)!s^ z{{W4AjPkxnT+#`oUZmseS^ZKwT(4@@r`Pd(Xz=(@8tHh>M(zmho`1rgemVGUm*$;8 z{_x2DroKJ2GDcP?F02pL+(dcuQ+?)n4b`{{Zl@^bVr>AN%MEsQ&;I zd^eJRwJpcC@tXYg)c(_8`7^OXep8++IO;PKk2#OFO-jP)t2OrCN9e5I6}&SS{KTOr z(|FJ4oPM;)J}h`{MIdT$ANUym02=)3I)uIDBz!rCm~77^cm_sjZ%M`An-@6c38nX zi1K`;f$csn!WQEvJ6XCfwK&98uzJ;Gd~G=7 zcNJSjFZ0_@;9Bgt=<>^>bAV$g%bIm86wBfiul(!|T z?s|Ti;|((Os>*`iL;c~wKU&%X2>@jB*dByqn(>i3GMu+gm15Ufn@*K{nb-l&+~rT< zMeIvYbb9z;4h9A~igCPg6(C&p>|x zPAqZI))amuhFRoPf0iNhao(kaU5@HdtH}1NPU3oQYBO#aBRKy6KD9z?XYe7P1SD+z?oaUc6q@W!$1JU!>~E>1GQN2O2CKHO2>^metwAQS5kGa0 z`TEir-9t{czd4dM$^Ie1tV^#8%NQ?fHt;#v{{Ya|h0dZJ5P+|_KmB^nnt9{|30VOh zscJhM&Rfz)y- zUEj#9HYeDG3?Is%muNf__*JOh0+@|PJt?k$^j!WGK({k)Es}6SO~8*z&A9O8yrYEL zeL=0`9tWrNsP>R}qTR|lYmW@N^BZJ(8lfhOJZ-|Cb6v9|4&L<{Kzi|-n*wa$B;JjJ zq+rrXI{}cSS8*} zEAtPpHJ>%4w&Rpxk6MQku9v&IS!mmW2J{Gg>dC6V5rP zO@xY03F3{|u0pn>I4x2OWCU~XN@JEhWphkv2|(0z#@lm^)t1Y%0N#;*0?*eeCEK#cbdx!i9;yyNq(7sCHwl(!>h%MX62FdGN4 z%~t-@k=x7NxEfP#21r1CsyWZup)w+XMn9fEtsaBlL&d=)d(w>K6u9RU1vuxeY|X}u z($j+RN)YEbrEnRH9Ey}mO6}S{vv{J-lrC5(o>5G&fVt4m;`2OQFE^d{)UOCUyZ zQ>BJ@#cidMxyCV8Qpm48x(Y&bv52~9;1FuedRSZxWC6u(t)K%VkLylbb#8;4{uI(B z$jgsSQH+CDlEa>u#cCz6>57ppFnSU_Y3NI31hT;UxT#~YW@nJ`|7u^!m`6&DiJY zX(lv@2nfBi^`@5Cxi}g3s2$lyUcG9o+;$|ITY@yc&;ZXBEEg~&fXuGz*J_-9O33p` zD}#YjJcDjaWcyLdGqJPrZk=QRTLv6*Ny+|Iem{veF!h1J9f1|+JIMzEpOjI`f_=p< zP&?bdijjbR&2GmdtcQ=%u&;h0>UX<o4xg1Zqh5q&jzmt6aGz9` z=0eWw=hcZd8{In*^5f}J6^N0LNvR_dsuPaC=hm$&Y&{C=F~(2VHDcO$gRbyf2c^SzPm?eWUl(Ek9V$b{q2eziFG-(ahsv%cYua6iJlRE68mG@%AN@!F&6@azh0 zbv>zmIaxU3IXB|~mC62e_4wgpjx!90jz%lQSRDTVDsqfuk+^z7;sru}H{77l z*+3n+;2BR2v85PKh~@N0K(YT$ihevbNnOv)(6j)UF3D5hK)+X;)LZf4Yvp zn5!j=bLrcqO%po~0MES^T~KS#rn;5dkP0?WxuNeaPb`di?oC`b483;a)aU$aNbfU{ zaKqQr^`gtMH}fb9`-9RvAAUt6>Ng{U9_;t46W&Iks8N&LR4H(!NIaifX)eWHD8G;uJlRTM#(z>jl|#DW z5BigX?SWU8L+156dgHh9t4Szj|pbfsixWh5(FXpY!cgEF|QUk7{z;fI%(KdMpJ{0r^NDjXaSU zBaGCNK%ivz=8!@g7|G32OK`a!ZKairr^C1owJRW<~ z_psy+2j^0)sBDvwQ=|$w0|51^^FruUjgMi^=Toew88|gyTLt-trAEj<2I@^GxLjx~ z+z09EXc_E(8hXXMfyD+Qod;TCxQ{N*zcohl?jGWvVl1cFRBLQ>S)-Jjc`v!&ouaV$k{nK=8o z{HU>BfQ{RBJ%vh&O7cmjAdvELN1AtS{%00>)ks5-5A&s5^Nzllr*7gLI3Qzz%`9XV=c%MwO7cfN zDbejMo;vl#7XZLK9)6WB%wPk-BZE!B&U5(+6rO~1^`oe|66^rvrxoY1*!0uDtkqPbvfEI9nBprXDwG{roT z+){2S)pAWMSS~+yUxCJHcX@dOkb2})J&7D=@&2^zhfcU7wKT?%q>jTl>-46xsp>EX zN=8f$ob~HS!~!#dPp?X1@){)^ZeD0Eq;uAgl^G-yoqITw|NqCwFz2&unNwTN`8d|* zOvN1MOsJf4PM91f43V_vTn@`2W)3+Op;9E~kVD9!B-vOJa!QWh-~Ib@*Z#S$`+8sR z`*nCeA5Yv-tw=J%2f$wjJT;bEFiV*1%kqand)4kxB}a`nta6k2b?hef%?eF9;TaDp z`t4GA6*B&18+PinJAMxfz~^I7RU19IE!Z`)6gZLGkCE8auO!67@Tx%sTcQ9ShKnf~2r#ql|Em`lFkTRjJ&9a) zNH#OH`FxtFMsvW%ccn}{X+;`wZe-T8lWcSrv$hA;fW=a_Wjnu_R)e8>ak?#pZQC-Z zM~Yl@fdjg+Ve`Wbl53^cE6>$ud@zpip^CDa%aCou_|2CIM(ZrJMJz?@JU>?JH4CEz z+lf%{R-4-?(?*=zW>U}@&0rL2SbY(F_+4AA#;(-n&6sX3gN24nEVb|gfm5biO_8gl zJmSY#Lkl<1j=eE>OKSi~{HS!%H?t6^q3i!_;+Si!H!UN?vOeir$+hyQAJ}=#0Z_&{ zhYERl{h%|Zfzf0-0P&JGF+H5~DGGl7yatjP7JIBDeCP^*RqbsA+@O(~>yc>%^pC0) z|6GewwOmK}1pju|Cu-qF<+9V(@s6{+y*JOud?@HO`NHom%b~A)dnQuv!2SYH$>g(F z0uHR!yv+1IgS>x{g|Y)iqKEvgP44pK44XGmqQ6sc$l19Iy!DwQPm8T%xASMr5;yDX zneuu9ZYC@%Blw9XlFg!c(qMa^j?ma7|AY2~+tb!Txr*!@d^bD-;@uDv&6T=^N>sF@ z?s&f?ctCVTHqKvZH6s9M(m1R|fw=DSSAK{XgiO>m*M=DXf29-i##v! zXTvtWmeC~ZiVwG!u$~^&1?R3@Rlz*qWH$!gULC%e85Ts-<^J|D!kBGGU)l7_s&lfT z;#p;w!Mnj0DL6qi4DU^DC;lKAJS|n?<8k9BPhY+}y~fkL6u$TRc5l=xmwOXf-6{X< z%m`f3f@5FegN)A;Z!Dv$vc!&l-uq){*ytwydThk*mLs04#l%>z6z3f41eRxQ+G#vB zd;O;A8Cc>gT4ooJ4)Aw1#JB(5-dMdz$EnGDhp~yM(rZD8YWGOt7a{%2sL zdJd3CX&V2~^iQ3j*`V2~jkpY5Xn@wL)NekTH>0)Ajt;TE_%Wz}soY&t?*29Lk;I-W z?eePg1vE^EWmjtgVDj1G{{)2;JSxgD3$Nx4edl+?Zt-~9F1K*YAird4q60aa^plBL6F3hv!k{NpT+kA+S~X zC36IC7awZ(1R*kAvTHS>*Yz4Rz*uaZ|Aw{MWqUNp0VT$biw)isL?|bjFG=-1TRiK* z0ba2yw2MKK<7_1{R{j$i09X&8L@7O9^L)vzCe>c4DIA8-;%(^e)U+K1H!WnZWRx7l z%490S5||moI^;^BRLJ+nUy4`x_|+`?{5%cverC}=4;=?pe7B$nB1#sV{I&%dm>D23 z=aoDE@Op5eNc*r$yDPF^7d!QWr{h;&F8e*bKPjG##|h zZhRDM&sCzbzyaE$j>h;BlUZ~7%z{c@L9!n-T2VMRJlL9ZV_;^W>2h-_rAJzO$gfZ; zDdb)%FV}|S{G)Ta2=4L!frbdIXtAg-{{S$|cyZ685iV2p+-Z2m=wqW2a0kccuhvsM zPrc{KV*+6LjH*jxM-7}5Vd?i}73Qk2Fo?`#X`sT~OJyC!QIF%xSLURP63PPIgEVA+ zci|@&|8ixHypHenDFgk%JLJ=@&A)9C@o~CYPR_~^=&QRL_)K#w)2*?ck=^~bxu(%QDgJUj;Z z#WLx(K@)?oB46M0z?FuZu(j%SnP3~GCZ7u17}aY%A7RlCifr!cf*vU~+={$$wnn~+ z-R99HGR~c=Y7Jb9f1<*u5=;_8pZvh;i!Td$BlGFZCA%3l7GkdbcO*Y;Dk z?T>x>;d{rUm;MPx3zI{EDNkB!_(~;l4wOPfw$0Sp7wYH4C1Y2|tK3bD`eN%ZHP3M* zKGycrm0aKzqup_6*6VYOGON-Ye`e;Z#_|25bM1_17i^hod8RTf_Am8DSm(!|A^ays z+Clmk(BAq7J7VRM)dIP_HccT&;>$P(WVu+1S+``+ho~4>7bvRk`h}}^eLD5QQWi=3 z%Y;|Ly1w9ye3@?fG71FDmO`4WRC6#AR+3pOvO04ol2Gxd+;w z(2978us4-W|CY2mUND}zV89U{=j{9V9KtuZv|h?npM_yJ=rn9-aQ2!j`2Z*x+>(|( zh^j&9+2n--$AL#|xkRTK>qorBnKqMCL%58Ga=c*zHa0gG-Ko39{@SDZ)%m&3k3$ znwP;-WaPKri&AsAqwV7$-xyn8#xZ|lJ+E)`4}kBba|vELUT#@!hvCFhl!9cW5dC5o z;z4h-_zz=`R(=-SaPTW7O_CB8V3Z5LJ9Q2-o*18&M=lUq=03gW?~vLpxDDx3pZHR7 z7)(dvzT_8Pt11@CQtSiDZMd`URjVHGYOl#PkI*53dS&&4;`gh;(TFj-vx8)*-SjpQ z&pWe6O5s}clJM^t#Xl4D=Y)L&0j^ouL(c1;lOsPI;R!+6&lY723oMw?BlkF{T}Qa zj~GeTJgj*og}LRXLc?9XAXVT87H&v)^dlp>&TJ z_1fH$G(tcSDC%I3$>N=lmQwmku3jVIDW%LSb&0YPQKTTJ&u2+$3!n&GaEcIkwD#$j%ZSDw z;|*VUKE~`9H_A-b5)?kg;c(~q z&?sxBEo^ziQ-?cNHcTUtJ4jBtBwp#30uw<|KYu|Y=G;If3cf4Kbm%jaYSGANwsnHi z($01L44ef+H+m(E0EdX8c;@Q?Q4bZgt^ab(c{cg+vI|J_392#Bs5QL=ZEToU$j^&Y zK@%T-xKJVQC=T{pksjL{m$HYU41tdTM43+`^ufb02{tLCZWg^UwYl)NRns5g1|FUw*_SXJTw5L?bBoGs@l#3|!>Sf(lX&W?_)M0)ps88dCBz`=J zoMG@xHy9b@>9Tf_!e^Xfd4_Wck^=~~i7proHfjMBI|C~=pyP!Ig&A!XR!st!+hj)X z@46y^AOR5_`#^{8?LDQduyT!Dmi1~U{|RdMG-q2AlVgD8|FAwZms5q<{15cR@FN&e zev*IvBP1FPa**urh9BA{_hzakt!`M13w75f`@GJjQ84SSaPa8L#r1CC<1&8GfTX~_ zmPAs!um4RK7hW`*r`>8W-7z9=)mE!3r-B$=xy>V}%PsENdSAl`;5$}?hT$^Zg63`_g|gSaR0NuuchiDg zB<~NAXcRSi+r?G82LwDpL3oqYlKdf~CmK>}fs=mBHYsfIo?mT#^1Eh~`Djq(nH%;o zYnD0Ux&2TY#pLYT-1v*+*cDPm`(8O=%jS!b=BUM`_zG~z&gTdmiBAHwlvoS{#?Wiy z=D82DB7sEy^YW94^X zsxjks)y62Kaf`ysorBNtYNCXxe+N|QW>)?Hf*9kPr2(jgBuJR<4I^RR)B!YWR>^{c zu^#&!TY8PV%r0Y@e$RUyMM@7Mm=(Qri zDtVjIafukUAVsT}TcFKM-b^9dgGmZvsfIF(Pi5*(ag=m1wogR+pn4=e@V3wm7;Rr$ z#*+gu@SpJ^FIEwb+L0ng!aDGsZ~yP0aWo5qM=w4jp|mogdLkCbUF9mu=bbM@44%Gd z(DEjM@l>5$oq`5hpn(p~N+nYUp3;*ks>Zf2YJN8Sh@34DG@f)942sBynX z+Rpup;r*OpXY`LNMyPKD%&|t|D19)Nc#juN(QF&Kc$F(qb0)IW{{6n=hkod76E(9R zCyBJHSJmGWAv!F$mL?(WR@wNdd*SFD{1PQAzDZs0(05H> z%{0!n++sEFVto#|f9~y+?72yLbjX2I@r+;5?3kuH0`4La{o|(1l|)>s2NNe{w(20a z7U{SCQlAg$VjH0rAg3L|boA&~PbcbIU2|P)jaNofKS~S+ zu3mM*sPl83c_Q|{jg2k_Tof;IxJ1V};uU`+LE$MDJ`l>8H0T3f@Kkx)@S?X>YFm_k z?SQTh&Il;^0@sbczews|nD~H(La1)n)!^J0Wh51Ug702|%5MU!foe0E)2UTxaO;_o z_mxp%YuV=S6Yp(Te*^>Neoo2GLm7RYWj&dw0m;B(kha%WI8mgicKrMzCw;pj zY#R#rP^ow-&PR0rEJwT09q) zmU=tC`-6QIKNash)lABWTIYK`H@uZNt5t;<3wQbGU-B7tuNg-56;~MYl>?#p{yFhH zWN}?W{jDEWi3V&LUi;X8p9Tx$M%?1%0wPS9D^3!3%%BLdS%Zmg*r;1_utJrzMoQ^> zYF>l?3$znCGw6mLd%G?`mSu25q_oa0uiGBeA7AAm@HAp}!4Rjn#F$ELU|G8W! zTk<<7T1ipW!8x28h?`3soZHcU(r7R&mw5Bd|3F=-SG`Rbk+sv|l%*FvC|$TGJy|!| zP^btajQ+P~<^}YB4tCX*^iL`NVRLG3HeR0 zBIsvh>(pjgl6^$S5k<>ttzxX9(Vp=_EA#vpI2j5Y9{ZpR4Z8eo;I{jp7Y`sfGM zZs8BmCPl=e{_a#4j~z~GScoTMe_Vv=k35s93E(wHo zY*~+6YzUS0Y>tVpt+L=8QhJq8j&P|8U&C)*5)S9qU7qr#D^5n*E)Z4 zy=$}ybUdY(OPc&GhSFDXzmgu1BFS% zfxR5$+I-$R)HnPjq2-x4JT-%HlJ_9H$jN2I%pzYFN4#DBnmz3W+~?M{{c@)zrlc+~ z^md*aMIo2#FSBw;kkrQ?Ye*6|gAoxdwDy{cri4mB$TjAV*&dZj&Ko*479IIZ)BI&u ztyXvUxs0s9Y8xJ$v)(^$2AsZnB03Sx$xwsVMBGeGCy}W~iZ0efVm@NCSMQgQs@kl4 zP328<^Y>Zm-|NNWFOFSr`T&_51%~dsD^KU8*8w8#@PfdjP+jJMV@-JIFO8W4;IA5w z`dt=LuNYf%>N8*i%k9-p@Z@4al#A9y+%tSLN|6s6iy@wRUj{s@{Tvf&_y1eNG_-iw zkn}YhrLiJykNpSPU#M3Jh-Tzco)ln|{j4$bBAFZ1nFbt$ht=IJH~KcP7MCW+czFn# zGo>f3*2OG2E1#;yO=|Rr;WlxRL;)*DjjJZHvClhpXsgy8oez_1F=}yuf>2j%jO2d( zyoL#f{%c1?DrTjfjV-G3vF)WZ>M|ijCW^HXV4Mt!xyl&a%e8bqeJ)M!vU%SpQU&YN zY~A$jRe-Gi-xpC4AQGWvRt}PLvI1X?KWu6?4?ov5bLw`HDDqM0$(?M|S~?G>l1 zOIaeXVJj!$)^h)8e9sLiL$h6?98|tu~9E8?RW1zYLruF%ldMKqyg0 z3ma3v*3nioIU#S!vx7Y%p(S|CrD@{@tqC5cb2wpA=8y{dTebi0J@#6Fi$s%YOc}>B zBa-m|5J<-}!meX30EO=DRQ4zUDh1#?M%d`R;8HUP9?8d=ni{-gV!Ug*brONocEJp3DZ&?)&GH<(8tyoOz1t1)Y0p} zFP2D?F=@lFIqF**v+cULtOl5|!P5fAKU@HI=81OhkRC8kj24t*_%wM+yG2qXL|Sh3 zX|5RUzaD4zSZH&=7JjuIbC24p@LX#@IE}e~WiO`~gW!HQm3H3@QDg#InXVUqyU?Pd zl^#L3X&+mC%>x7);nV(`x71wfcK2niFiR{!P8+^NMT zwDLSx1YNkc!=j9_6l8*MUDZ?8jO_Qo1!geMk1enI(6Bb3i{CW*ZN-Ti#TSaQOe6Lk zk!1@aS?U(`q|TqMgJ(fPYjcy;DcZsdqy_tyO5R53niXKhj9D3Ftq45ipBY6_wy*sN z1A!Kp&?*UA8hP`e$V9GfL_>$WsZbt)OiJ3fcmMjN7#phoRnM_m)&m&3xi)C$?e6tr z3c-*+6e~@T@oI8>XHejo^qcCM>g(K_wi+@NBS{QZ&<28{_S?d1`tS$wip3EZJz22* zZtJ>UUvS@#4n7S)*>+YebIB|dWfWl`YsB4E8xE-g^j^&z;N{U)t(Wddf35X&_pyoA zG=!q04QR!ZP$qq6EEbNN&%)q^$UDPqnTPj~w<*LkWHS7Ke|~3d=^uh*HlAE=*0Fk$ zcp6;&t(?3No;y7;UIH)k4vUYaWU@r@3dRnrzl)sldGcX_Wr`q1m>reuy%0K=X*b;} zJF=+85nK7i9QhJR=JnucX3A%HLG*1jXLhNlU{W;FPR|}`{+@KI{&{7)UWxc`?V zb&I=taArf`T?IP)Q%Xr#Kp)FUEX6Cm2LXH`u2sSV!*{_upnIpy=}+3)Uch~(wvF+^#D$jv@ck~X%tG#=6skOX5Wy3OIZ}_pTYTNJxkLcY>XNgzY z#y;&VeTuN;&9PlM0ulvLz-kpg{}5@HZB4QghG6j*QgS!UK?@hv2L2v6eia2-`Zr2- z*0S%emX1G00SNTZu5Q24#hDZY#^nFsCKFK=Q-+d##cZ3(q6tk;?*649(+*Osy1>X% ziuo9aG$~+M zc&H9;{IOrGJR#gl$$S_khzkAoljUPr7)lI+OR|wsUs%pM288=Tpy0kZ-xRy{l@bWW zx=Rv2fdNk!IiHs0z0$s%|26O=&nMLA=qEULKKGNTL{miIpz&y@>Ffa9oFp2q}R!AJlLuUDFYn8+m`hOkl117Xb-yVv@spp&Kk@^V$?PSamr z_-lYkMsm-3VLW7Fkvq%nBL{*yStMCALFH-_9S{7uuyUp+2_o5)+1B zruu2Os5%^V@HEW8W2-as|GV0jalKo&P_FCY;A0H|`q!fqgNTOyuo!B}vd@@Z6WQUj z)M#LJ&+R7-l~IWDx+`qq*Nm%vP3r__Dh`>E^Yr;{zw#$mJjs?;U?ipVAN>TNSo?0e zYCR~!Cv3?vuv)vTk3j@ZM5~QFjszL6GYPT&=YWx~?$UEiz?Dd_7LB8SO^#_8bJDa$ zU)IhdiIX9W2=igBemrYQa6+HnQoAuS_7&m5I0*6{iTlm8j_n^7$wRg6piK}H((gq)-&e4 zvcyKvX_j>iQn?FxfU^-MRTe$)qFLBe;k@)#*5=!1A9mgRX1zXg77!85<4|Z5XKoRT+^T-R@rVxh0*$l74g?1nGF7*G`-2XlxUQdYes7 z4DEEUZd|7MgnfIjb3W+8yUPpC7=!)4RDuLC!Lgy2YQOgvpalDs$1T!8PVsLRB98+> zGP}d#l4+yR0?*nL)|%0(I$7>;mjTquhF#OSwP9t}DDF2FE$g>0|MfFC#%z=eaFM}U`a(VJ7p z%nIMBQ~zcTTVSp_z27WREMrnb^Iqj#chAe+Xk5a66(QEps)91wX>xhOWedy2TRg(^ zCal7S7R$Rl_P=eE%R-)iwk)_PJM6~x+sDWSP!6A{%L-|Vo!NHLO#A6;Db{S;o+6;cU-;E_OSgRW8&04leH4`=S{+y zVmHBAW0fR0Dag0E3nmZyJA4H_0x`4*;*iYLuL+Q4<4(!W37j_ulLD9Je&J6dU-N60 zV1rZ)QrK+f4x*@>#d_iP#H3MruWrqPR8X|S!?LqBm@`?IGzkfkbFp+Z7*;)R^KU?YtNNl8 z!LAQCU*whoNBW`lxOpp3tC$$ZLY%##q{+6mYL66`e1oEfX7>T!K7Jj*z*loteD72f z(nP?0NV{?q-J7__?kY%B$4R%EcSBy*D=Rh>8g1h@BFUn zCPw^3%9>fuMpkvAQYTxe``H*8<;rEnNpEU_itllL*OImVO%L9+kvRTfr!4w1LaHVG z&ED#Ct8#PUO3)H6r4%0-Hds+3&0KZ6Y(XaNv`Zc<&i`mGX|lpzpGvtU8{(UTciMH5 z@yx~w5xy%7bTb;t{q2cHAY@BVNUNNB;SX)ONv=WW>z zlCGhi&&y`L2nRaD-STCQ>xp=mM@XY<)WUU+J8p@^BgbX8M*=e|5d+zB8x*(px7KB< z3y@I4#JNUrL<~^deVhMzZt~fIBF|RG=yvtAxubO4@S0B4w(RH;yHadsP z4_=e{4V$B7l$C(bCs$;Zv*$CT6`DtaQ}Bq_7};UU#V6J9p>p$Vs9&NF+S}Afzh6sI z$%w-sWNL@2&x05@dDpBZ4`$E>fD7(?R3SzF+{)6me|Znx#pXQW{v*P@bTHd|WDcH* zj}CwlU#X-vd9dpLy8at&ypY*7mHVN3f7H;8_Xi;58Gq0;H>iUwTlU!lZ0sX-Sy>HBurV8 zYx*Tuh?Rmy$f8nqGnCf%HxUq87gu2%3hE(A~bGNC4akG_DKu&6rn0wjj(yg_?s1$nI_M(de#}sB?!O$;-nGvsYcbuVGkbP&m+B4Tmf`9 zLmWJ+9$W;@-EFhO31fnkzZk8$d- zTm)(j8+=yVeW=15C#=2uWb?q{(6oVmG{&_~b%(_>6;< z3dXetJWjDnT@Wa$T|e7lFk6!Z;@w=EDtIOQieQXbv5xdf8jF%Xd3Bk+`O$*N8un`W z{XY7PeW2|9*!?1;*EK8xn3*JG-Yqi16l990(L z=fn3MKD5i9Bl6>p6(-%A4?o<=oyiFC)m%s3MsHY!lz@G2*YGG3L8iult;hRo_^3i( zJTO7DxkC8G*?m;|pH|%hetQM0`hi&{mDdj zwte9?Yo^IF^C!b(+=?+cRKNk_3r6Bkw2Fs#NpD+@?%w^<36Y;Fih=)$kF)3OLO$A< zpqw`~j2`p#PHpc!m!mP$Rds7W9dwmZIc4YiChc2VuC(7 z#w%8A+bNS<0Zp@LPn|4X2c9Q0aN=(D6;}pxBEvR{yi=~vW?;%UvAc$82%r8#m3;ji z3!!K=g%eOayHC3~tQLG&Vk$i?)1WkJCyVjQ%(it%n#}2|Mv*xnqduc5U-%}iUWSoa z(kj7AKv%kCd%5UM4F1RK8g9ES9iBOrb%%!B{5n-~H%Fu$9u}|oCg~BN6osd(TGQo` zhZDcRDR4sI(q|4M=6w0Uw`^0R)ywavkpnWq7(go{pJNl=RXb?0XNV0t#ms%Ji|56e zNcAQuG7<1_46+L((RzZjsicLa={n2tya6CG`Cxi(DL#VWWOvC1USnKxH#-ZSz=IMVh=tiu&_m# zQ~P@zQ?e4DNSgHY-2J6nbaA_;YxPS73Pp2slo7B|Or;4W<*3c9VmKYT;k&(n`$LER zu2VtAbavH<`~HADH7IUp0D55WPm%F7hx_c?@|xntxVhxn!gP!LOcyl?@qsuJ%fhEf zmMrQFDUgDfdh+5Tj%I9;(o7#41E+r_@SE*cDmih)qE}3RvvMt02#~4>aO_YPyNQjB zk@Q3JEE?(ZrC-Lq@(ge{YBgnUY&fZVbgIHxwL=d-am~aAVr(LL&j;NXyRJxOF_X^- z3-J++7*38^wKC#}yS-xU5l*yCu#MNXg;+65N-aRars}0`ys_Os{Yc`djA zbRgnr*X}1flXDi_NO8WcF0~-tmz!V~7Px%1nul+a`+Y1G+`x`jHHMSo1LrM<21Ryi zH((|>+BjkBLgc9Mx0RVMhhvEVBW`v@O6BFGkv-tnf$>ckGLH`S1G4(U?RkT9vcpf2x)LScDM=Qb`h2*xM*a-)WJGY| zByYUJD{*BV6UYDD?iWh}+Ja@qRMpLyJG^)Y`{O4Oe#pbqp&0a)2`<)TOWRvduknCA zdgdN`aQOV2WVg3Uwn=DdxHsfet|^kKWR_D8+yTCPxiAgu1kS>R24DOI?pX z$a;Iit_5;|A_Xj7H2uf4lJP7m&nBi=ybxTF zQn!#a?q>7Ldd}3PEO$5m%81+i_3+ps(}t1 zUG)TGYER;lCKG@!+rx(b0mCOO4NFhebH}p`^*&#}5GJqn=}gM@X|M8IA5CUfE((!? zia%CREh3}AFqy-mtE#8Y20YF3%8PS~4LJ#^5ZbM|^Z9p#B3p4Lgetnnm$AmIMqllj zs(#b>4(YS%Ht%Pno*^-W+g1q5OFkYnVWbX-cB9b4LGc;$PJN4aKU+RIb4lWkO(_@n(Qq&!z4|<)C8Z$ z+5H+Z$x$;!*Iuf>()Vlq&PjpTEsFwAkt+w41u{hm`4?pH4sjCh7hkj%b8^ZR0_3Gu zE8-D-|8zw#E+34d25Xcy_a%On?w^XsxR(&KJr_ty#?r&NSY9NAmD2X&)hk)=q+J%SusD-Ut_Ms^RV>`YIb!KZeqkEmmtL)%3_Q&jlYyfB zu+{JT%hYo+%|(HUhmOtJZR~;jnZ|A=bKArT%k^tEikNww1zT~*pT!3T$yT@QvdoVH zl(UH^d9TxfL}g>yIn9YyEp?8ig};@MFNmAg?OZ_3leiwY^n`M$i9Z zgQ0Ay))*+i$@ce*s@sm~%tyoOtT$`$WS$+%l12WyXTjS!AtcD&q2)7u0%JHd8 z2=?~ph$hh~D)8yRERAAce3fWmYOLot^z5tXz@6r`4D@wRY_l>}h2SZsudno=Ze4c( zY_Lqpbs>O>C#M(|mLOiR2-`F^yR=bo%NdVTv#!MX|mb`EO8ct3`fx)nAE zvDwj!Ps7=~@9?60DEXu8&z;9E0O^iis|ZBhCK0UYD7HhQ`}{hR*cZ>|GP_3Oap>3EXqtNI6Js1uom56NsWTHp;s~-zqV!P zVl@;z#2Asi>LeS7GU^zJU54miqWJ9pWeLkqa;MwycQKZmre){~p5#Q;(kVh&gusuL zgYk`M6e8pN0v)xd^-S{e*RRo9wD*79OD#-#ko?%j`U|j-Z0jZ|iI|@p3gCAXi6)ds z?!Ns3n1&qdGU;H11rGke6F2AcMUW}`6JcPV21SBzHVumxGp5O$hQ0LwR-xnJBTAsS zGO$cWKw=&Ny<0ta3I=(H_rn19M92jsfRBf*TB**!LR5A5mC!tl&8jEX@^`B$3-*#eSW?l<^_vd@+VoR!j2djBHyV(tC`bk2~Yp%|a z^f%fIyGfUa$3qj&m!oNttJUdd0z;=$*TaA%XE7)>wA0$TSU0RbRX^U^w7B#A)Yzk& zrrIH4X5)|7lC{}Dn*CQRs7sEaVmD__NK1(C)a6<@56O&zn*(Xq&FQ%D~M3uT-^XWs6YLQFK>Xpx9*p}|Eu9%j`fn`ID z!3Xv)1rHeZzH->>n-r+Zg$Nt$cmZoaE*Y0XoOm;C3K~`;aDmHU!%ABm91^92j}|}i z6Y050Nxg<={}_)p20e%}hcZrQCwS~h_Hrf!SigQ=fiSFBOwZ2!Qm{o9#zI;ReFV97 zmPzFdm@Z+&@O{ZI4FO!Q$&7?Knl-yPmK_hof16b|SSQQDpk0-7EGWH0M{C3u7QIu+ zUa+}W{hB9I83q!5A`m(+>?%bh_Wh0Ni5dm z%}zLg|&faf~})`AWopn$){ju7v86dXh*{D?gXY zmOG;O_0L5Try)hQ@LLKz+~TifgQaB3U>7e)A3t%nSzVatsB=VjT>E2jHy_dGXs3Fq zBkLtNe*GO&)I*4?%cr!6{1KY0gU64HEeH*(7vK6}%}#q2o?}#jA9_fz|+en<`Q(Y8%~&k#pUms952X-0t*?yTXNNYQUgnQ(_|CdfG! z^v2Ojxn+%fNlWF!lKmBoFHcu!N`_vS_)18ScIo4Anw@B0H zlzzhg;3lNHI-g{8EAK!mU(?f*l#-#t@pO9(ZXe2oP)t8Y3zf7}KZL27h6PN5$;>S( z17ytDt7=IphiZ)CY{Q~UDfj7LRjnV}rv4yU-=f0?;un2u;pdHd1HJ z=1~eVB4)W52KW7wc+p0cN6^M-`2ocESr^dVQ44wanUu@j2fMw>7@-KwQMg@Gs8%i+ zOa|IQ;-gG9B6qBA2ZZ{=D*Htea1h40jM<{Az$K9JN-H1acx(z(^j?I9jS=`)pvDK{ z_{IPz{dw#SULS!+`>vFrv@c zKYQXGq)YN`buUF=r3XuLSxxyBjLSV6y)t~jWTEi_E;h&eISMnJBbe$+qB_lIipn$h zKTt_tDtfudEn^Ax+b7?~jD988H9qS;uETghD!Gk23nleUNzqme?ZMotfjxW{bZT14 zy|=rIuMhp~&ylZl>U{F^myAL3&YI(LTX0C)nuF;03#ApJ!1q=@g+4_fs4CbWNFC>V zH2>pc7nt~Y44eZ$=DR`5M?9}r<7sw&0f1nwpC28}PMmddX+sbPU@oM%*}GM*2wNJu&%5`~A}6&*H0Q zIW8ovVL}(Y2s;7{lN^*!UwH4i$!igjlDZjuljb=r@+)?o2mZ3=f2m%<9fjxfu;1(} z_kIV}1#X#qe?z2CM%F+2EDgUrF_qc&f*}Sv9B*3xR47*cJ5ef)qLz1npUiDeIKQwv zI2n`G5WQ2|n)*xWJ@>ZpeV5!meZ}XeB+cdDD&DPu$`nCm_aC|&mIPk<^r-7h+1Yp{ z*;aQMyWo`n;>$%yB_(;E!ZfRpM2J9vAt22IXIAa{B=lP*H@~9WqP+%!qmCx) zkcB#!EQz7%NxMNmh$H_8dS+5`Tr?MEahdql5~qdG5kwa&YVy_TAKN#TqrRXA0>LgO+S4r zG_Nm@UXrg!#1SC%mo`Bu1CzQx9C|0 z3S5_x<)i>3A4FF88iq%4KkZkOBWaeSc{DLhf zFG(7svC6lwC+TyQq&K(g?)5|%OBdWVtlbm+YI)HGxpV1MZhn@4 zTU&KoVMtx+B81vp8+;Rab5QMIvcXaTI>H|m?fbG3w-R^rbl-rHP@UTP(dWiBo;dBL z3cG*Y+ri%jzhBMKd;j1kIia=S#J9K&^Pz;nENZyMROBs-$n_1G3FRUFmtLC`Ff@beo$q3tjb!rI+N8_>yA;LPWXOj_WWFdYd6y!NDt6wt3`^Z)z=pAdyH9t@R8yn zm7G}O9H(?IbzGP~dvL@<*3B)7_!f!-fk*D>sc0}5q`8{3`o5?7JzqB(3Z5OR>j7;K zziitLxV*mG9KUaqm?nfCJe2*;HxPMT=PU*|fSdCg1+-+5YVOSCJn__~Ugvc8}0>fe||v zwqO5_L$PA$du`f9==y9B&}poh;+UPF?I} zno4{1qOZjCY`gO})mjXyo+g0f%4(v3dv5P8mt!sjyDuX_TU_feo1q0gc+a2^lwfUc zIo#4QWYjf#X||9YAbZDTP#}DK)FtM z6n`ysL?O#3QtcTX!t9KxJ$Bnb8U&qs^2f6`L~GJV^SN$S++h@iHj$^x~=4>+)^9nWK5>UC+cKU*Jx z+6OQ=*TOUzNfoD6(J>uAz>m=$w#(&gxUc0gC(u;2(TtFyv*ckZ7h#P6?h)4|)k7th z0pKfTu5TC*2xX2J3>M)D%kxnt|7qxnV}4ktk`pxBZu^OP7#`I4jQ=y5PVUbhNeZnih@Y*^XOr%FIYbZJ1b05 z_A*3pb4DW?!aEUUPVcl=pv;$}oCkfKX$~(xNMFP+MV$yv*{E4!9ZrF+bAI|@(wZE6 zB|}&X16LjGfd1r9!vBt%o1eT08l_~_4(lU9ZxLjsva*BE7vansUFi{_Usm#6vn&1QnIR*Wc{R<@OW;co>VK$0nt1I}d>RqWdO1elt};qX%sopHN17 zS?KLy|4UZ1Wm0fO#tS^Kfv*tDZXk&ofiaZokBQ>skLJ`ZVMG!eA6PweSHBWi>U$xv8~w0JR0aDEyGlhEP}Ku!orD&h0Zi1uJmNQRr+Na1W~6N#3^TYqfmJ0kP$(Y}oG zIpM0&6n*hJVu$Xj1?<50aDnOe*PJoftBdapP2dwV3pp5e%)hkXv?{3EfxD3 zGy3t5nOZ{fbgU}k`bUi|;xALVYWs}TX{q?$b`!GI0{wV-2Uv&4tp4xsu)k^>f>}~S zq8xa=S`F&M7s@@UtI_zfht|y-ZxO>;TlJcURbqi*=8q$JOwdKOj9IFU<1a}S&Bl7r zewVg&fg3DDlxs5-YxZ|2Bg|D9$Xc&(ad)DhuJo$hXmN7=RNqaB&^A>h0 zBUQ<2D%B4U6rR-h5hzR``^0N_Q#6O%k2&p0#9(v_5hUF9Hi&?52d zS3f#=-~z`$_kN13*=BW$y=(ilM`1-8Zl^x~Sf%e$4@hNU*+tn!m;_y^KOsMA9#-<< zL?C2Sg-1UVll3mJ$%d^`s|OP?+xAS!aWxCCjfzB=CkV^2sx#bcD^Qroc3?YNF@^Mw zkx&D@J81M18t9UQ0OmU-Rbd~NkDIHRhMt9!~JhTU86`ApFp=ljsj;g7hI18q{=pDh@ciYbe7^}b9_O$3a9~tRl(F}Yswyguf5=8Ne)D6s)Ya4 zr>+adjezvmjv%g$GE6=_Bnz3DGCr1zLe=a)=SSAf;Hk`28wK6q4RKlt8asPs z(*!k!8;ZljWQ%4=^H-+i=5E=SkAbKMoUy7rn`2P|G@szY%l=#~A#K)iJn*Qx#V0Jw zi$N~_)Yz+eN$G5=Z!M%ou5Q8OiB%Li4EKgy42>n^aodNhZr<#kQS0)&{gt*ZaDG^G zQW7$!bZA1}s;(XHkl`9XSPdHDW_uJG7$hM!iFr(?wraGxhB4`p*TzGI4>_?D>QSPf zgNMSioYn8TS?j#%d(yLWL4Qd_d^!#zbM6^a_g*^I=Vw}^KQ^=v{k!~|tRkQ5X+0k& zJ8hPiy$F=EoD+sTdY&GmYYsZTxReCBCcP$@eCon6vA;yT@j*_PsXa0#HCAdUYe*K; zX;s;6$&szsp(qztjy!pr3aJIQuNx6_9-Z4uS{t6=#GGL-ceRJ|yhd9>t)ZHSb{`J&Hw4D4PeC=89sxr`XmN_Tt z7V~a2?G`Og#7<>amUnN3wDzUtyFe>3jc?WUh|v2L*t7|i^de|URc)vXAASmu%mlC@aId1Umf zZ6=ETc$huo_&|A@^fB&YJ^a2%o0O~>G%BAf-w5@*cx7#U_E9rdwqz$lKfFUslK^S@ zUOj-Gx5Wx&k3`d0$^NL|-V;j@a#76D9&@i$w|IxX=iu3hJ@ZbpivoJAStB7Y-^ctT zZ5Ynm*>+dmd+9B4wj|DM4Yy_6Q(+(HBBFEvLJwG@NLD_v##W*MBKAb_?3Aycq1_Av z0*xNbUgMX3sXrxr7*Pp392-;Dd6XfHzO+krPk2%L6dA|AVP$alP{w-C5Y)jZequxF zon_VL=OBSv;H}^FFOC%XO-rwN9>HpBSUq^3@YRuLbd9Nbv}QaibTX;gIbS$9~{0y!m8hb zEu(1Pu_+_IeYyE38(&WwKLRp5mf1Zh_*BawFd(T;FtCpb0UAJUC8@|F4V)ik$$ zZ|JSUFN2p>bpa_y20s&;4)Qa~Bq9^M_gkt zruBjTc3d@O{dFQgig)<^g#{}UR=9VqtF0UD2+msl6+puCo@$*Bu)tZo96`Pe&UBAo za6S-U4r@p6{FvD@I_dPvRdOeC1y`$O-)#()0hT_<5od4DChi)@4J&TN#2m=f6jE)} zjiC67RZ8!5TIQfJJXt0u_Ivvilf;0+s|}c#a4Dgl#4n&hY~W^3yi7Zxf? z7I)Fp?^Jy%2D=jy7=LQ$LQ|wW=nHm9NAgCb{*HP2W%+m-iTRaa73`0$4AXMN{z&dR ziBGr-3A}8SQmd}LO>;?c2smaS99a@ZQ|(`%(^J1!OvndbnK~zoL1BEARQR)%J(Kpo zm~R_&C1q58$l9OPap7kHdDZr_@^cW`H+T9GSz`Nj)4K0U#Ms%7WEl3=Jh$PmTdYsU z5m<}#^sKhWvYe@E{R4yz%wQBqdd)TMMV#3-zA;*}ew=iLJG@^{(t> z;%PsSB<~ybpcakZZTEL|Zy{wf9?wYfGSJX@=?qeKUcvG)u+itRE2I_Nlg2{Rgq?_% zJg6kTV1$kxr~aW|SY!3}^cx|&C3zE^vq3>!ZU4I(z_0R?YF7Qh4B~oix2q(m!O}wy zt@#AzDV>=S;hsFGys(XLGtZp7{Z;{}hMv7Mb|))^J?GN1{x6~}3a2OT)?l~7pA&*@ zjwZMH@npSJ_kMEaP;dbZ?yg|rQ5Y$j`^ut4bzqbo#N}RunXlKY-P-Ha*)6GNQS6^M zi)Pgrb;>~q?hcxULf@g-zDu2RPdYRLlfkbYGfT1u5iLie77HCjGPKU4C-i;RJ52ny ztm{}CKaoZaI;mU5N_%9h)&V~EBTY|we}s(}R#KNezcwR!9^U^GC4&LAE_SZI{@pu_0 zIJlN2d%?*c+mZnQ!xh%Mj!iM*#7&3OJ!4IU=eh{xXj*qvMsef61M79IrF@0XjYUs! z{S$wUIP-;jO;5ydS#cBzlt3t;rg`pdn=3TqqTh?^EZ%XGO>4u2LJLS1ltzxwNow4}u+ zkza&`?cs|e(81XtJ6C(fUxZF!p5tUPj47L33+iY`v9CsnA1M^qsWgvEuDLFnVc@Xr zWFiO3GUME}emo>h`kb|1Q}(UsMnewLC*A*-d}!!J43+t%f45iWF@}}DC$%e!+*mbm zgA=#CtTJNbdz7?H>O(f&JRHQ{t;yjTL_GZPZcJ`lmu~MWKXKPA31i8PEla(`P1=s(eGo3@LF@DqgqI0y45Eu?fDdKIe+2 z#t-jAj{3{De(i(^zf%{Lpq_4Fy6!eYQ|nIhnD}I|CO6LVN}Q$N9tv)GjH-r~C1kUs zygLPh#PpzYQB3>@qb=VG<8r7sOH4QJ-NDpC$TZbmUv~`(T7hM8sQ5g`L+_>WB!URRjO2*uUcsBWA&H!6=!qY2zRt%9anF=ATVVqF(2+IH$I z*0NHd#VWyWnjI(LztI1*+k6`rU;YgQVuQ+p0X4evvIJ%piKlu!-X%ri+&z@KW8zSD ztcpeRbWa-_Y&3GnIY&4cYLvlKnfLR*vHfCLR^EdUI@ZJj>==j)Y6IxGDIHTGnqcdm zuSzbEJd1TEm@=RZs4~EJ!&p96*3vWpG`vw$Bz3J&^8TjMBS%#k@;d+$1ZX=ufd{@R z6qa0Gir&Bo^x&`zWMc8$6GhHk1fu#lT|Xnmh=pYfvnu?Y&QA3PBYEKuHQnpLgaL8e zU4$ovn~(5jr$?Dke&H6d2r$aBz!>20G6x}-fFw2ylUm`9T`joMfAv`7v zY*w;4_a_~8e^rSt_XvJO`bMAAL-Pu{nQ~~O5zR;**rY*BfUf8QoKs zo}+_4y#23HpTDtvYCnl==HncE+G6ui-C4o+<9FQv>-DQ@KZDbT`$fz89=!~CjM=JF zLG$vAwS(ZK#9Hfqb(`T14N;O;h)&v%ABKKYo}rqA9*Y*Uar=Zg#{Tt3wk0QI#@p)e z|EJdo_~jNQ8P#=n@!)>6F!v9M)6=!Rq%;HuWaEAldiVbrd}YYFm0OR$zh0{?vpA*M z0CfUg7fnm9?sVn)V_#L@Ivv*9DKmTgGA3%mCv2MX!iT%B=Mn$j><_EzK;OdJva`h| z&)*n3I@?RjEx5|#e1rytW4b0|yErnu7uvDlx~IA%ZQ5#QK8J8C+N<#0+OlWbT{(2W zsU3`1fcMDNcq#`Tp+Wgg*vv+Izk7WJ=1&|S9|oZ?$lXLR2I(sq+cp6Hmcxeo6FJZe6W?f^zIsgT_H<5C$n)S@+;Q z3PN6gs2xKjZhH<@xt@o|<0LRq^=)IZw%YCP7R;8Dw3|&V#da`rLwu2~>tZlnw<91b zHHAn`C~SEe1-W{Wb&RhtN6Q_-;JiS}SUaLDLo zN2TsOWE(s9T?y1y{OLHcm6Kki*Xl7DmEBC(sNy}gOK(x6?tOdoKgu|gPqTgVt|8VV z*IAu-p)a>yrS7B#Zdb!`C`-Rpy!4U5r-okqwX1(58}@VcDq}l;3w-Y-29hP}(?7Pj zx(Uw+l>hF)KP46d5S)j-570kK>h?U0BW?(pQg=ODceY7|CR9<%=JIim&fhhZ+N=(< zkC|jKkv7%(lC+;R*VYu*ooC?%3mzen>kt1+4&R#^vOPQ$#-W&&bu*ua0}?D=@BSAA zn}!rrYr{h223j@@;3(1Hdxam;Qr+u|2U5eT$VXP)qD){>`LBF}$p=xZB%b(Su93iRi$h`cJO`P#D4cZ0@vIPF`737GEcY*29)k&Atj{0}L9F;y-i$XXqZ$z>qLG;Uj+w z0Cc1dPizT$+V+L4YVxext*SH>$-@;<| zSv|1?VE()7Isr&~(A+92tPMB&UXQqV68*e#5s>QX7MGs`SQ&fxUK<5t9K`j7 zABl50McD|=*ZKsJy5dQaX4<=cI%_RC9hg}2Om+u9;eF(Nd@FVHfhA@}d3v9-R24rk z&BZKGJ$6np+WqlqT(=%(tLD1o=703i8*=Em4xKvD*vL_wjYde$00#Npp%>@1O@#-4EAXJ9`?I^%a>{qH;9T>6jB#ul9MAg)I2epplIJ^fUv)N#o$mnd^r@ zY`3esEl2Y5#Qg=XpT)Hd3)vP$_cHkL*tZn3%T+P1U}z1qGwviKn>IW`>8X+@(r;C; zuM*`cJF9Nh2XE&2W55E3#vIWV$AEAEPm8|FxX1&&CZ+-^b@ZLr9Dj8XMoMB#$k^VR z6w-StHlVmS-OYGk3|n#JUgH8YV!-~jqjR~Zcq*q3)eAWowtNp%u10W^)+fapI*Zx& zg|wH8nM1jq6n2}r=Q?`XVF}p$3OA3c9>aFdO>rntS6i{0JoD6{;~G3Fg@~6N?@q4O z0B2m?UN=E6xmnq)DM!onVvsMaR1nJ56UoXfHiyEDC&_LZz08Q9{jH^S8NWLF`pO2h zXhtVD)1zm6N+>(Bw6=n20)+oGvTfMh>i~S5($mz$MS6bO@w`O&Uc;em=T1Z|Xrs2Y ztvdztAk`l|M+?@PMHH;%*%q_V-G)DOtA70-AXke!Fl*sQOvN`}l^)o%A2|X$K9~Q=)mq+OdA%N< za9KrJvFP#(=q;POZ5+CtmV+vjwc>Ix`KIi|llq4CFG_J%gH5}a`{xhNr;u*!p^Ov0 z6qKU{HcsWGg$BmAUc>$q?_2-MtR3<}B#939zCeK~GM7EC6&VglK)(5u&o(aSZ9QTI zCaCw(y|sS!?tiG#d-%qB)e%*OwD1aWi+>)$PTham-863}W2xE$?toL2SIWN6 zUeoD7Sq@M#@4jS7@M?`_v9b9VZUo#hEK)AP-uWAx49#k}7NuXQR}8$Cj)#FV*_p zqWFMm{oo_1$2Y10%^3>gtBN_n*Z~5Kv{|bQh_mNzbM%3Y4xQ?bq%0rNc)RSwX08DE zTzd`9n(~@RHynDxp->C4bL)x`nNoZPBv9(q)uR@Hs$XCKyL_;1(EK;R^F_GSTg4a= zq-bI4`d~Gh!2}aH^icJL|~p(GoTxKz%Nq>qj=Z>2_3glQM~uFFOLg1UX5!4m3R6`qmE+;2$Dr4^gB++de2_`ZxtW*CGz6e@)k$w--HdZ9!K%w`RlH9Px& z6;IRRfZN-6sq3zCtvu`Nd$Nb5Of$^y0^-@LRX#^qD9{MKO+A^ngZxV4$=?0Z(;+L>N3P!3^~$*kY+@5h3>rDNCek;gc);~Q zw#_sk?eFkY#n@Q;1?TeyNW=utbwokuKZ8O$+%(>!H=0Or&XFR?#u4!YR&c^N-LHH< z7S}l~yPi{|C9;Z1uHiQ#+AF+%$r)*kV&6Bf^&7Xtl4{#Pb-Y?dc?mE?ec;xf4;JBP zC%giS5XOffq0n0{K~#$2f;F)1_{S!4q&HSvSacaC;VH4`GNv5s#V~tn$j$X>jAuF#qwuj7XQ(~gC zY1=~$&GM$-X{$#JfWPaVrV0I4*-TmB&&Wzua>V|b^7I&@XHzFj;PFe`;X8FoN|pJ) zQHWuz8Qw{nKu=tnAuKq?e%0QSr;36$Pe7(Wk|66{GJ|JbM5%4Cz=Qei0OF@CfXeKB z0dxKNCqUOkws^G%mBfu1?c7+eNvS5TYIC3VfFR5wp+9&3MV@BfnJSX-H`(E!T$o%H zo!O>kB#D0fJd=yjq@%ewW6tFD>X(D_FGw@S-* zrl;B)UViR5l0Pr2oj@(p{qW%xDNfg71RT#KJVICHk-V<5xbMCXIGY}Cx7QZ9AD>r^VV*eE&~HZ+nI!M*T~ zb`y{I`?f)!dXCBxc6aqXDp~e!=zFlT&9QFvvKz@|5;p2SgdrY&>{nz5nfWHvg||{u zbZ{GrXUa9Ze}>}yG*vHo+Fa&UYskL*KY$$Pe+rwAge4c~{XPIC{m>)(*r(Fj)E_#x z>}eZHvLiXI$SyU>mgmNu;98Y>$=*jj$A44QYva9lzo^EwTjszGVkHFq$giV7_lD`q z1CxxE=3dx^)HH(FZ1_ulWl(9)QCYN#D! zfRxZwRyvWS)b~EYfpzodeb1$2Qs%Oco}Eyu;IZuXL4l#!7P6WFo^cE=%kT$xoKX=e zsfcHv^EU&3=+NKkGKx~}Ru*(0kwlT$W(@kfN*i^tju_e}1PO3E&^M%^+TCz*y4 z^G2e>3v~>t|JzjxIS;%-(jQu{yW72Fdhi*hDCUnQnCs;d)sxJZ0_3dPt05o0fI*A) z1E06ShM&r%m)5PSvGj#N_DIRVw6G3q9&~&pzltCJtLngWzRa4O&7z|wqQ0Kwm{OyV znK@RHk}q{2+f!qYaNXTATcG`W0o(Z*F7KMA%}@NERYeexhFKHht`Q7ld8YDzfJ~1#|Do&Jw;;s4yRdFl&qJh&5IZc^cprL@ zGYcp9L#r+?cpk3PL<#WPIw`dpZ^K)wb#Ln*0!;rOU|S$iM`I!vdZ*WZAm?8Y*Btp< z3X9+;ZjObhZr~w?m!)kN6c7BIcl&Qnt~^*-q{O}KKa}yIH|HMP>2MRLj>Ts>4IOMc z>8}r4jk=tB3@Fl2A|k67@7XDjAis9>Nz{G>`%xi`O5|d$>a=~Nd{G0(bBEp?Gl)&A z-+SZe;u{7OGan{dT3PzKzHX+}Yo*Xd$B?>OFBC~OONt-^w*OwP{xXm}w^DPxvzKR7 zN$cG~p5--}Csq%`r#&$@Fse)cW8wOJeZq*tjI1^=V93(?+0Sb-++XG!z1`pdX=!|D zt91K-eFMzh?(2lq87Z}<*1fs9^Qm4pa(pN!pbSr>jxcu0j6%aH5CwJ%?yh3yZENB- zUNVuNo!9sQHwRi6DNiwh$hHJEGDPAM=SJvt@M(oDK0WC+N6TNi7A~1h#{3Ys^H6#| zhhE5|_3r8MGXcc5Ar|oR);k}7Kf!+DutSI8t(XW0?=Z8H;+n&4w8FTk%Ng3;mIqDt zs`0^q4DdHxpx3#Qa7(vewYCz8-SK&)^0M{-cN427krnoHeV07>b6I=T{hBo${9(hl zxkB%Zn;%YMMA5sG{ms)(xa>?`uU1e(S@t~)aHci^;)FvyzSfy{K~6|WqLTC-$45W} zqWq^$_7s)N8$E^BD0H3^sfYQ~3*ruM*<0+koDG&0p;5O!g{IvF-mqvEM^x@jvvtpP z(i(1#ixS~W)2TslKC~9yzgHpyE!@oRHj8&S*4VT$Ta@23Q5G8pPx*YFnm7_5O&C^h zFv-YrO5~}837cX`-N4xN;*h8Z1@}a~Je>xtmn?RhBQW*`;k4CR%J_r$&6^ul4(8o& zn|A7bZwx|q6@AZUl7fiL?^Q>7?joKhfO5Z)iJs7gqnn`x zRNjgoy}Dih0gZVYc~eUg#7HoD*@}2ltv{h;H*FW+S&KG{tEG*4T{ZpX=6e5rp*341 zgPU@mXIX4LV3-%tEB$WG*@h+@_xs3MeK0WG!e`6t56L!{nGjsUd)d_otb%ZWaM8Ju z;!*9v`tOwk(?Ef6-D(HE^uJ8bN3O7`;gt-k6~@fEM^2uPY!F^+(v@R!C9Tj}1x9Gr zv}}}lHff_?N!CD|V9SA4-pzo?b<^N$)V#p=Cp0aEv!zqYp>sk@0Tyx&l%01XhDW!5 ztBuHVMV1E^iqB&)RWWmU`DM)Z-;2(LcA6OWH;-;7%l>ZDVHJIv6k3$|MSyz>EKEKm zj7|K~chPPC{Vcp}c&*Ch=6PDW|7wk9j-~#h8_bRYAUN=sVaWGQY)4?C+NC{Toke6F zguTAR1J{;L6_>W@GUY5CuV!C?{Ctz72?-4;bJgWUH{bYnQVct0>h*l`-Cbhhb(p5g zsk3v85w7<|WlvPEIkYmH`>6|?L2wHL0Q4LL)=&4Po3%+oOH_Q!f=_ z;Xp1^_s-{-8Qk-f67VlACjaM$@rO_Mg_-cO>78vI(n7ZcM)(0fuKus2u@{Z`_wbmV zj0PmrXEx8z;Hoj9ui%Mau8Qgn5Qmm%2ViYhIh$DZvOD(AgRbRkbh`q&06EGr=|yGQ z(IG3fb;_)>Y5%la1KSTheT|V&uDPfz`YUxkF*=ikmbY)wb^bT#7NH++Dcz-=34 zCyw@IkL|$)SKr*71;{Dmg`2wI4W-6hc!D7B(!I9n1O#fcW|bA*G@WUq2aNBQf$I+{ ztl_`~>9f~W7MbsU*DOt#Zfa2jYJ8Rn+aI~ckN_|O^1EjL5uoP(mfojxF|T!E94V%9 zbIzhPHCw|bKqzzf(;><`@?_CEup~pD>`*R#U7Ha~`#-?v8iU*B%)9l#IZSZB~d)CDC3cU%g`UkKYfo~!6M($=o`0+dS^SLl)q`}FU1CQle^l!(S^EKpvr2i+X`4%Ju7c4%(V&w^)i$9`JLyN5C1zcRRx!&9sUPXhX(HWC{ zsN@ScDJ0YR*xQ1$f2ySe0Cs>wg<3o-9b!1di~2e`-KYvwXm3=nqRZInz|}m;ntrPG zNRq`8p;!L7DcI&l<4_=h>ogCIXe-Bnqha-fXX9N}CfvTcoHk*EOwvEtq(w$+xMvW* zXI_JERJ|32r*}klW>F;jsQs$>q*zIo*|Tl08zMl)USS6di#k~tDW4GN%M?2(c2fil zR&htq7CQh$Oufts;z+USFk<>~NhF$F$c3-gr(f|9*`hzhK)eZrKmc zv^N6|04$63pJz#!ll8H0_00+V@DBJ=Ndl7(FNj8wDgng;^JJEJIcXDRxJ59y!o?M|rM8^2Uu1D%f2Z z@vkSK_6Zn0z0XyTw8C1%z01%`frQx20w8WeV^JPP$G@wkwu zzDoU4jw{?$&c0__Me&2_-*4sd@BMqWfDIs#<%qUc*#oI~8)k07+IqPHa-&PbV@c)& zY5E|(5R3Wyk-uMaAf7v{{0sXdE$Z#@fXj7#bLOiC5sd*gr+Lw`W!2hcN3*+5QysQH zIcUD!mv;u+1oc|4zm~C*R}Z=8s+w1d#Y9Y~-6Sxrs)FqsYZ#XmIZ4u+{ri=}%{`hA zMob(meStt%G#R|Gr?Hh(t;rHWe>NW;L-*9-LWVmI8X20-a_!=BlN#_|sLtt;;Ww@> zw#JRc#Go9lhDMbm!UI)JcJoT-!{e$*jkfhVN7>h2dr<%5O z{Gj5?tg+k0tz}vj6-y zoLy)2f)ZJ~c`LIEWj6uZ$}3m=2@`hnR_kfU=##j&nhBGoEvcK!_&TMqR@oOg@YW}v zX3Zsa)<}g%FJ#y{f5L;C)O71mU+pYJ_IphZh%`s_^E?R`su3Q4+zQd$IwjVV2rW&; zOT!tm8?&hvgoO7nBn49_^t?avHt_;^NeP3pEDu{ zz5=Gbj;kPGyNcptd&Rt1g94v~FV5+a);_2o1V-31Tu`S{;yXUdT%my7;MwwSB-bui z^GVFzE?;b3KY9dkP)aVs*t@d=_3LeWssVJP>mNSZ3vRdrWCI z;l$n8-ze-8UAtJZI8GW?mn|^=R(sygOV{h=V6C zo|e0+5yTWO{ZBl-xFtIkWUssbIv?j3zz-RFg!1UL9BFUn&e?YJ7W;8`o-Ou<3|<-f zzGQZ9MH}C3z0r%>Z3)aDReMN3uu29&(Z-11r9Ajo|5RD7xvP6 zW}hvORhoTs+ABv4zo)4MG{?_`E=7F3uy;vAqG$PsPIJ;Fv%TyUV<|hwX%c<^A^Qr1 zQX+dI#H>QPdtM$PYv1tfV*n{Da0t~$va0xxvBByg)lD0hd!2DZ@ zu$%)wLIE=J*;6h1z4uIca^s{axbJrxUqF*LHU#c6<$Oq)BW+R4M|7Hq2fB84!keb? z`(k)GX7%zFfIKq!g^a=pU&XK@x^YHp$y%N$d4 z?}li&y4cS_rUSj)kmHQi$Q$$LX5q5yZ)R=>J|xdIV9$##f)-qE+rqCQp#KDl7gf|E zS~`zaj#(+IYu9axB+tmCG4y=maF@XBNsF22c zjaarHg;dvX2gJHqO53^3Owxe05ME)br@|I#w#t_^*Iq$BAiC=n1I^y2V*oIQg?Gz2 z=JNqVp-0+(BK4kUm+cuI{#CPoB4yXBu20+_@k&Q4mQ_gHr~mOm{*1GmNy^O?Pk>73v zFy*2`v{}FBj1vdCmT_KUHwE1;Qavu!?Mf}rCfa<5o~?hmIzG4v3nUrg@wEuJ#u#ZzwRN&P&~E%Q+?0c5heN*%)HT& zpI9D3WusO!P!HXW3`rxa4!7t{#aT-`HD|Vkymq8&PN1SUg%sfS{DHgGan?KrKktg{ z>0fsG16DhAVvXJTt%3h|17asakbsq*S<>wig;m;MgZ6-{s`*@*ALxU}{uAfh<|E(2 zV`C+!@mY|Vn17Kuv34J6<736F!b6LnUpF|rABsT`G?mpl=9_}SjYg|o-5`KO7v|bW zIQj0_ff%$_`CtCQI2b}Yec7VDqH0_Pn6lWV3z~?V2DrhKswRE;ya~M0Ekqw`oo%Gy zzW6e65j(naQ&q#&YNvixS{~ckrS;pZCjclAxV-O6mB#O{*xXmp!Hn$NI>S~=9X{pw z9H(Mr5RIPZm$BUS_C>>>DO<0Kp1z+=n=D~{FSY zV`-Z21FKWPgp&zypn5#3^~ZAqa!f z466(zgXoDO@z3SuoC`)OCmeX@ct7m>+Z*3=JZRb0SYY03^E}Ck3DS4!FpPJ5+2bKk zMR|_lkv;=Q-@EF{9ql&U5u7R3&yq9$lrnosPv@8E&E}l1Pu9X~Q2kwk|+wl>~I$M>Ar29GuQ2#Dq|HHeR^?1SX$r4;&b$6blq3S47XWr zcOSC?IAPft6svJPb%>LdnVS6>Q?Gc;0k-BqCBIzDcH&0F7G2WBptgr^1--t*saRc2 zuD611uXF($*pE%i(wj-b4V`604TMK7wJ3s0-QI~^^$1VAs_n#eUkp+R-k zkwAB^w8$vHbdxZT`((1pM?!LA!M%ARGtd2920_Stp=w5dI1X{nIC0wop8=P#eP7K7rRU3);D}yB&2DW-Lb~1W;q&ZB4 z$T{O|d14+)Se6`h=_qjHFaB*_*bgi(&)-~IaoT-Pr5 zuHEnV>-Bs-9yJydNyBuZ?dNPO$9nNbQ`^YMNMzkYiqR;=<7$c#%UIWxx51Zx_uJuB z1T;!gm_&Oa6Zr*HQiVM63eV=c^VEcbnU?uEj6b1ZVIIFq(b-w*13uhcJs7rtvkqkP z)7s(8(8`x zK;sz53bDxNq4}GF!mI=~50@Y0n0Fqo*?o*=e2?+>5v!Umy08%U5EUYZT^Umbg zICOhm?0y|@O|){|w!y-j({SG4-M+-)=XBj zcC;4)oE={QQD^3_gZ?B&SK*}wKm9tebQ1o~6u4xgdjxB#*>)&{K&;)^auHN%#?5Dc zvo=C|Ex0*>rGrDyIX;JF+mB{A`8`lNPQ88lm;2eW}Wyym%G`_7;st_|MxDjS@4xQ25 zLu7X6mMK9B9P!%tWOF%G}jB29L{@W{@%i=-HlJe8eP=N1(Re+Tq>M(_{6<dxxuM*(1bc~Rvl(Z|v@xGP_Xy)!=dm(Akg6GCbrN~&# z%8ULQy1u1dR(b5O$IYPPw*&QO6c_vUgFFQD*S=T6xa##5{uG&ipZpa>7g?2Gfz=YO zQ6CKf1OC`vZT9&!ei>-PwKI7$wnAow;vVIKnqknGx0_E5HM`0zvSBq}d5i1(M@qMy zyYsFZ{}v7>vO&Z8r0jyoclg;&(nVmZ``Dx0b)&w(m7vIu#GmW92*QHxUs`h#!Ew9ZE{YU7L2SHSe)$F>Rt#s&q zAA4qpowT6pvv49ka=7wqaoLR*ub)UWC>i!iY=CRJ1R(KBoO8jaE<-@U2&;mtxiAL9 z{nFT@T`r%yNg!Mpbr-0I>3y?s_v-EG*k(_azdgy1i0{{#WsW#`e^o|p=!>o=?FeDPZodcb!!RnK>{k7kSPL4k~ z9U(z5aLJsl-BIn}9zohZD^nXO!OS`07VMIt2sA3Gwj`RbG3s=*ME<6cuKf!yeb#AV zXVUs!K3Y=!kU~Zf;>A8i3{EoQfydXsAlObwbAy9e|A9HXE>71rIxK8F_nX04y|F-w zWcAah|2zzRDgqHoIaqtsvP+~<&7QZKfBUd-m*W9RYrfCaZio}Dlf~1(0n?dTFsFP} zullg@`l+dcWAw58cZ^nL&D?3j#Exnq{_@VR8C%YW)1=P;w$bf#(n8F4M`)3{%6H!9 zoZn;r0l`AuprfR&y;GXqn%>8HF;)YW5r9acK~)>P>3*YKI$RpXpK_nE4zdtP7jbzT zhNC>ZUY^Zcp?`|wSiym^Q?(buvcP*u1(%5PhE%_Bex`8V9SdquwqC{!R)cAwzrb`G zZw8!SMgIEC9y`SFAftE$&_@BZJc!y5o`NXJY@;PxuAy+SEw3NAI0o-fxFy&DoE9CbpNr5kB3o=dLnwtOMQEQBayfXYF$J72y{ z@ZE~ma~?c0)6ZR6oMc}-7+ng8mp_4%~L`LNd9{eX;xUw^1a___1wpqQfVarkbcdr(q2=nn>& z?uv#eDCq~L86T;UmCF@SbN~F=9!{^%eN(hv=_Q%U41L4GuclI@y6GCqSf0C*#Tqm8 zK)wFa?y0CKrxRbgvD3N0&Cm>Yow4UZ)66~In~N;cSS11Hk<|l|EVD04A}pMj^eNYoWXN*YtQn9&i?Y%)7<3jrr%R4#C{|E5BCDg zFEyQ4VTm3NXdf7;geez zvjnv83JZJt{?wiBICAfZp$uEG%l!{K)!jK{#vkBkz^1&fU6s>hp)f9$PQybTpO*0V zeu%31a}`i(67V(r>uXh4!fEJvEKrhJE<5Qu^_r+Ib|6s_>cr9F3?RszqNZu-Cq=Dh8ZJdCLOKOW262aM3<%jN_O75wU;xPXQsTyD$`^t)7%#1~0{L7Dzf|ym&@6&}fz!B=7Ts;o zfeEF%&wYP!JnmbQgg|c6 zui#CX)N2<=n|`=Gp);F2rRv@C>boC<=LFY8Vh&ncikMOQh1`^G_+D+HGj_xBmd!+p) z(Sx3xiSZAd>l1~FA*iQ#aHd1mK~aHr&{Rf<8gq}>MZav2yn&P=B=)=`r6H?D5e{Sm+F=8Z__6hQsq>Wv^XQ%egRKnz ztQ#wE>JZbFn97=Yc<+1>pq~Sx3AP}%viUCZ1NpIa@4)OS_Y~63x%~KYAw40FdzfcX z`y2TOj>Ie5`-J3EQ_y`T5B&dDv7%mn=D}mSP`z#XI4RP2amy?UKNIkJU4R9JJ8I9?I{G72rfB&dr6!RZ2OR7Qeps}OwX__5THTY?43cMaQ$j&J^K9Sr)F9#Yrfh_w#{m%(?E8Id6l* z%*W=QXiJf<5SzA@*|M))`!6gW-mtj!Ek3Aj-6~t__ydG)B<9g)Jxeijb(6Mfb7;RQ zowt4!9Oi?vyw)1W78uUGjL5`HCm)DwM_8DVch z@sKU`0=KCxvd=Y{ipkY~^bd|4C@Y%P9@f#w)bvf~E$aln zRi3Ml%7{cOh&d-*lTY@J7&6*R3VV_peI$Dhd~IQH_~ZQMW_qHh-m|iSz>$jmQ#Cx| zsO9TS2g)0RFVEx5pegyi}F$tB?R``ptn)p1v_tNPmZ7sZ+is%Q_&-iMKuAtCE?D)sVVV$(cdvR{kY{t&%J&;Zz!oy?*CWV0Yg2HE&nRl9WC&f z;#WNu?-UmevkgmY@X1U|TZ05L+=YI;;x0H|NhS_}hjN~)|7E|eKVYl4L55;k%3&UD z5~hvpH(`2>i`Qkk@Ea^$7udY8Hd(2mBIS#6mLN}#9AOrG{!LK>K|A)KMg_rbW9gJpUzYEGeHX=(}A7k;GitTmvA$6}BQ(WZ7 z+P!!|Do8&sz54_r{c@GPUq)uk1kv`m;-bgFR4#DaJQ``nM1IVeNQwJfGj3@%H0b{o zAnRVR2)M7p?;Or>BoSz~Idr)ajwFGc^k5z;?A=i&HFwIp@Rs~3_J zmLB)6ociz6FXQc^MC`&)VNd+5;v+emPsg3LWU<_l!yimf5&4H}+ z(Nam-SI*`cS>>28)RXrmg~7^QK62WW6iI=ow=^3b318;|F$qK~&e1{2J$-^|Aw}8t zZar)_?_(w>AH;}%Z##eE1(Z8B93MfX3-%VnWB2JpmQ2l}O=8e@VBc~^T7f9jp)dGF z`aNaB=?0@#``MEmCuJ)-X_I>$1@YH^ushAotZYx;zAZ18H<~pIDL+hoVxL7&f{{8s2*7w3vk=(?m30a%H z-BQ>Ly3N<5dAK6PndI_O)YCQ+swpnNN^)5(j9%|TA5z=tv{>&u^sMQR_EWgrYpeL$k&2wpE&52saw-2Q6(kY1hE`HD~{|BScCh9OtM5#)B%fqmbQ# zSrfHZ`(K8_9!DB*$D#c0OY9G6%C{5l2b%A{$Vl(ttCVEnpC}r1yC@W1EYw%RvMts! z?(L6si0!g66@8O&J0*Uj!l0ZP6?XSOrzw%0|2(=^e9QWr=$wbWFC}|XUAP|B_3Gqd zWn|s&e*+vsul64!$~NJ9t+~N7jh0@a-B8aAT|YUurbP-V#~uHSTRSjPWYcSe9~0sZ z74z=(5EvNi%6o=vEUpnm6>^mc^;p}UE<2;G*{P79Fcwq7>qn($|+NnEAy#TQ? zPoR7MN)A8vu51`=Rcu3T3RFwR>v|RfhJfMepn}HUnc~MPsrPJdY)%oA31aEr8Yu}g z#T%|7j;l8vlgn3EC@CS55OX|31mI8q=uzxN%Ue!Gp&#wvo)G9r9&Ig65`QYc{mi3o zdq^n8U&J|a`h`-ip;3l?UcjH$!12sR8QXAcCw9%8_a_PMyzIo-x=(of{_lwm{A#B- zaf-~V@l3z)e7uY^YTZPp5Z$h>2yE|~<4(!tc}jl5K!)Wx5!&}>-^hUEuYZ4EGjWb_ zG27v{nTK9mx6ztP2R*8`ps3{)uNhL{3&jcOA2h>czrlXN*S!K4e3Fxauq;U?B0B84ZDcv=*M>ZNwl$`hC`&oI={MzNOOjo9V9z6 z#)1C!))aTjx*RP|zGqwZGaCp_8b+%g^O6JULw-1K+kePra;^#d?sB#sXl}D>c~1H_ ze7&<50v@6IYUC|U7Sjd&6L{RsVZ}dj7C>5TR)p@NSYFQpLVHr@s_riVcCgf75Xqh% zWq)Ck$#R6Rip`Cue0$z!q(;f(Oh2G<)*L%qrs8Mo%|k7?hjRrx5o9YLT(4Vr_-&@}|OUOT(v5hHKj$-PcH9li^RP=d#-`LQM2QhyYPQrpCo_zMt|gM*K)?E z>$6V6o=yJ6(ZC-h?rq*66tHzh^581!ljF)3L@?`Sv5vzkABm>WEBm zv)RLwt>~wNA~Z3Fa}EQ5Vf@($@1z**42-Lx8&?^8x$$fpbR;Qkuxe_%P!^z&%$k%) zcQ^hlM=K8aOI~_VNR>}Vj6y2?!;a7C^p`J5a1r$c?@wtZ1ZG)HQsqRoP5gk*M|*TH zBAc^$q?2*OqqBE`olrsWf>$Q%(I9@}F-kG3W2lM=ak9t(RY#|Z8VG8EE)AXh80jK! zrre20olFm4XQ#G-XIU13>*XHg+#X5`1ZGui%mtOaF9$_TMORmU??-_%{2i2J@#{5T+qP13>a*c?ZWD{eJ5kO_NBaSpWpTdcOHJHNM_5~kwjkXAD zf+o4my$pVrBi=#mW+AiVfo!0811x%@6169H4|v`}wJ8+>Y(MwoX^0?FGca1(*)iTnjk{oc?$x7e`zqTy1CVtXa!4_9JS*bnBu3(J6s0 z{YJ^*eOq*Qo^r|4JVb%ozig-exL}fOFr8d&a-3~e}r*1JuQe)V@R#W z5V9k=N3m)}6-(F98dIEA4l^MH{b-O1gA5jlUP7Q$_{`H5V0vIff2?S%HGE)EwBymw^;-cBAyBrXI({Oq(q8kug+Qo z&$(Hety?`^tqu&brUaGrcxcRQ65`ZaH1wFo4I?QNrtidNs9yx$N2EsSoySWG2?Huuv1b`${@kc; z(;qAHqq)r4%D!Glya1f$xkS9!EG8_aGx-o{@Y75LVvKZ3qX0JhLi0*nTo1ZpTe!Qy zABXNkG4zGjvL$h6sNLp-o-g6RlJ|AG<3O7=8-V-cPi^yS{zi1i!S_70CAc+a@##hzw?kKI zv&~9ueGjUn=!pbJ)wt-~EUlv&TQ6T|x{+RY_Fu}G2R$z~agpa6Dw9rp=37?(yD*(| z`yAxYmsgyhFVET>w$2*Xd!zO*Wn(so z{-U>C?fSa#*E$j!I_rcfa(yeQ+#(j*%F;t%z zW&AX`a)duue0NP9B5=F9?Sg~&+m99O!|-(56sh{qxxJ%T|E+VZBPVp&Wm}_Wnbj{l zUCw6IZ9A~h>!sHRd=B2r(UHl#!)cQ22k?;XhipXRx%81^4rjp{K{{WZ^jRNCOwE|1 z&aZPum`@zJW!%He4q3ym?^}A-E6x{AMZDXtJu3NG(l8DBg+fi3{QCX4ely^Y%qh|ZXn5-IoTIuT zqO3aoD_?%U4zLt*o(?ih&SP?l(Cv#hg6PB4t)Dk#tXclqh2{4wh#3maD)ug68X)B# zH3DCHdUrJYr2uQuQAM8nNs%afK{EU_m}y{pWgi(;p#|}YfLfPAo)`da0cK;}AM1g7 z+h({-PC_06KAjmRm?pF~7*h)Z`4io0C8={G6+Nu=sxA&-S`(-;B09!d5b3z~olrs8 zraG89hxZ7yZ2B3Iu;Se9z|E-#Yg1+c2Wk6zX6VPlIet~Hhs?;W-(U(h+y3VJa$*YP zlt(InBpE5r?G&Ng*b$%!*LIPjYQ!$z5D`OY*wL)~DZ+)QRt+Wzt8T&X!haYwk#Qz6 z^Ccp~`P127*p3mRT=_OhkU_&jpBs~tQ)I7D-ZVRY-=A%4oU)rFqSWcf`~d04For>~{p2Os_#v-{W}!w;H&EaRY3gf(xtMSXaZ?hVhtrnxy6pM$)3PN?zFv@nXA=O?D+W)fJS4N@;{KrJ;eMbCr<9^{cWJ(0qtbDXBG+#v|Q6r5q5aDpmycZw1U0^ zBZ4n&X6V<_^U~b@0cVH>ZHf8;6ANOuuMS|3@&PvogzE8fH{2J2GiX)(u>bW%G$LnLP1FY#$sGK69`G#WyTpX?ngC`6l-%@Xid=rq$XN}3w)#7!-EAI zC{M}{Wrtcd9qm6Z8@ie?i@Sacrg*6)GtEj{;J4(3q$giIc7ymtoF z3wQ`3U&XCuFnxvlQ%p~w)fJ~7H?7zd^~|)|CTgpk2M^_bZ?QVwHJ&QdzUfCR|D1hBrFnHmbL0mgtui=x~*gI={ zCcWAPJ|Q@vI_s3L4YIHIH+}3nCFskSUtUBj#&3PP1hKx87I~UDl4E8Zu4~oZ%B{h- zZEeWkjl+J>`$2bIv{7qObxn!t(+uc%Z6O%Y{(s}izv-L);MfX?sO`j*eYH!lM zT(!xD8hsymWK5YQ2KIS}KMyMDW>h49P(fDu>lg{X23F3Nf58-vq^2@T&v3jNN12sPdoN6^M>tz?MW6Cp5@`^n$YdNU4Kjrdui>R63J$gN?T z-M&;tqU&J}3GH_ezBMdS8ifn1W}GN;`%VtS<-Q>SV+5mfCg^9zx_za zDo`f%WSVoei4wl=dp2kHeYxa#aiQoiT=f+3)TC`BX=3Qr0oIhbjR!-3;nuRRcvd3{ zDSjZrMLxohl$;@yVf0-&^t44U)QHvnsSC%eVJ(l2@ zthkRtsiv8K2tfJY$bSiP$>*(x**Ku#06AS~o&OMLr7Pu3ba+L6s-3*1I+9wPe78Sd z@S&ncULH0VASppvsFMsPf2b=86K14l`8Lgn2zCBVGvBBH^CDkEKl{=2NqOUp$i2N( z6B$bF2i1-&@e{N>HAoc{RTZw?+aFppyL@dndCa|nI`$-X71YZGxP$cRBSi098ttxVX+t2zzK+tt9c1mow<86P%i zVV^(ux$JThQJ|xTrNUZqSSW4AyvANWc(a|h{tALFm0 zAIv+MU6nbj;OD$hVr!Fm_C(^)Hx0^-3!_YI7wY4yI}nSQr$RaJygLkQqo-68($CAz zpVrh3Vn2_m44&}$O8R;&$!D^|G(JK>A$}o`|3sCr(pS9lWzBM8`t@p(xz(j*GmNu)FF_wt1Z6lQ6L~5B^g}b(F{Zla6yw(fuQK> z%ZJ9hYF;RE6oOHRRrxG6v_-?;s&z$FcW9(DDFw7sM-xF>gAi}Icz29hY=w=LR$QRE zf?M)$Wa^Q-auE)5-N?KPN+9s}If6~Kgy>Xk%@-m-oA<4Wm((kmBH^E}qyYZ3hlWca z?iRuLkS}P4{1y9-CIHI8I_A8%i%Ia0d1U%mR7Asi_jalLm)T~4*G-)=!!JO#M^$s< zmXNIj&dxx|&o)St?Sk#8#>&Kso8?hHJ;k1HIiwlsa+lsr)VZbIoZxRr*JCuJHN`w ztQs{7B#joYzxJOsZqn-S?BIr2&?PDaf%7SC%G5M`Tid$2_AR1JPA_5KXpInZ4kAfovX79^s%paVg6yRmCq*83^q{9n|2o z&{yKryx!_EQ6Nk#(4}G%0B&=D7Kr3SyHJlcuT<|d#`D@6|?4Huq+|Lj>=7#t=F=ZAqe=J4`X#Av9mb| z;?MYVlNF+scT%^XQZO>NvWrHstB)>RH4L_zPv^&G2e+qY? zM3*`(l+jL?4Onv40T80u=a(lCsQ(wZ^NP#va5vo>{082J`!qwS8STwio|VBbq(vvR z?+ch>It!B=(zKF9%YG9$bNihQ1!nH|p}5TnFRrGFOQY0+bHWPkVK4wBK2mV^m63|g z*!prtc%#lrzIx5__={U^8v+gJ=?6dU$ksfDYFNy^>_2}q5KS+W4*$M-N=|A0NMfpI zaFJI4_WkFiu=X4MM{G_s=FZ#0W9zmqj=LB>%6ZeOdTyPblKJ1f@AaOSLEh@J!E@sU zCsV8;h_Xc=8DAU2@|Ec%<142zADfGi7?qI4HUK|Ti#6XOTw3Ej? z&qPb&vl?>%8(5`A{+gh5I|_5^~G{&1{0hP zTi~V8r&r2u(-!c2ENdALkO8w!y^L!Ii`&;L-HC`FsMl#0G8LGLdDtVm_x_2908Dv% zc>EJyh~c)8lb6?9dC%1&ux&q*x@nNUz^_;(TO1V6YMpmcpL4#_4p9eU!FFlHEZN+B z5&>oUwY}X*VO}w;?|XJyEw0wT5{5z#O$3}c9{6y$A{m52%sE5UN`6k>5*|A2NxzvyvAaSK9LS&(Fl!4L~w(<{IQNljgAF z6*4_Dxu^C4Isw~LYU0$Gzw%iNV}C2W;M#!@;C96u^V35c{||IXV4D6;J*%bXSLngO zZq#&U^pVU4Wc$vXIC5^2bFqJ^RbHLiWpV*(eIQ-ccERnnHKo?L@>zB2tIZ*MGSTt< z&-jc+GA2oL+!|IY`8v47htOGn(2r)~T>i8TNq}OvIN9E4Sk2_QUCaCkukwdV=}L63 zTZ?^%bIVb@PcsI~cAYgHKp{}PT3vpH_QiNwfCtSNe#d?TX*=Y%i#*K>isn~GY$mUGZKT*JZF1U2~aw_^&Z?TJ`Cmf|mN4c12s4@;2T z@a z$`6L>ZMy7L%|=|VXTQ)meoWOi87uP01_4$rusv3;wp~>sJg=bi21u7qrlwJ`%D?yu z*9avx8YLLrDFZI#6Eoe zLf?tJGDV10nl>IP%H6Tj66$^bD0%_!&H%f`m*rHVFS`9BjIbb4DlU%JKW|&?BF|Y* zaYE1Zhg1 zkk+aux7s<{O|9#rgF5}!t;hO1Q@_jq8@(zdjadjt0o}BlpvI#cYi5p$jdtqU;OS+HyZ* zX3={d-7#|u*gJ6Ln%D7n|K_Zr4Iz{3P(SU}_qBUW0 z6jpUWVk&SaBNj`GUkaT(Z6PRZk`JZ;(O-ec2?lY&elh|XBjU#wOg*dy5)|7FX?qEB z_4pHCWtcL-7><>Ve0xkan&11deXyVh4nvU6y21 zZdf*ji{9|a1pzS5Ui~c7(%$8aC%&Px&1==*eDr+6&;%^=P?Vv;ZkUTcH?8YL@*UJ7 zzE%AgYuCv&dXOYTZGItoQsrlkkALq*t+RBkssUex`Qm`Tp@a2yYtOQ)wxTp6SfsM} zwzYoWPMtg#joOe0k(EEXT$p?Sll%ama{s%`ZCt>DWI>BA91{fx3vL*bxh}`7Sf5+3 zT-&!T%*~*@@6^b)$DYK}x)pH_;*h5aLH@<>n7igoWW7NIem5iyAO*h=)bP6p%DF%E zB;v%cxB6TT$5pHP;};Vne31~eX@`~h5jgQx%IYhQ6A2Jyd*i|OUUqeYwOR`hHT)|- zoDQ5e9L7bvop1{?lCACYp%WqAt*|ljP_Qk@C?!toiQtGJmnp zMO;{;VF6$u>$d`tc{2NGj@!J!{je3rE~@IC$PcCy+JTh(y$zrFTti}Nn)y&UnQ@I@ zW^IM!TINgle^I{z#e0Fcib6nRp&|998R79Qz4!XaH|L~-pI&g5P=~=+?8xqKyx;=K z3&4+H=`2d<<;?<|;BHB=4^%06O8mi1buOh-eDhh-_%S_D@C=S!FFvd&bL)KU7>TQ$hnU$6&uiM$Ugg?X zDPx`&sXgGe3WIKE&R+d+`uNh<@j?!8<8J5*t__XO_*M)%y8N#B?>3 z%5DRE+HD?tlN6k_7h-O*c(+zHZxwm|9RKg2^RJdExbP2hyGj#FbZ$^3sn5-f3?aIg zBt(U2euAfN5?Xw1zW0q*ISU`k`v8aUnVguPPZbs$u+7MJa=^lDQ`wHXo|${0*H`!z zGWQUIb+&p%Jp=&&srdJ`3|#tKe1$}@11OafTXj#X6_Pq$dif^;g;LMRSuE8ZtQa2D z30W4+Fr%3#488t=XMG54b4?sNaM*}JbUILF_dk#qX|kaG;dpEAGNyLX^bdfN+pksI z8d+o@U7G!`>H|veMLhe|L0u)<2NA!U`^`AScNQ!u_=Cps{2-DYJC^fIoymSp#nF?3kYjaT-!LCk;r+K8dL^knq(*kfV zZUa7!op9-&SWF+%wG~1`N_^1lC5Un}o*>?l4H@ncynEn_&#@6viCgX}eo%;VZTb{a z5iW~*$elmaZjqeaCEGwS7;RH)nNT#%#e^Y)K*=fA%X@a-h1VLs^vgU@M8GEH$PUcmD#eDmdbQ~E^3tAZTj2>rC6y<=o< zuy6w=4ES{gw+PTjKR(zG7nAw?%wtqLq!}NKT&%iA z(mfK2FHJ+^8zoPOoOAgzceFJDM=iN!DwlY&Zy&J=lL{HFL4Ra$!}Cqh%zn5^QQI0X7g0`RtxD8+JLdGx119)-8(DMyCh zytO`4qP+xQcApEw`Xq`AUy+p^6sy%!09==rzBkZ7Y+6jdqeZmqv_{0AlTv^!8PIc6 z#K12=nnJUAwx8!kTPy2d-w(+vZp=%WEO9@px^bi6@=^7IbJ%`E?VO9d-~AFld%x73 zv0kD<<21bFZdEKX(~dr^!#I98moh0?DSZXjqkP$P+co{S!IT_pL!lkltR;E?ioxC7 z;?TzXGzZ3mcK-iF)7nOPnpmtA>7#M}&E3GFJN~5c8tU7!8p%{yBO|Mk$9G=A&hRmubfsWf3kOv)vUn3%95J?$18Mj4Rfyn`0JrKbj8 zp~J72@}{%##i(OG|FIAxmlT)$sK>ho*-1vSh`a^=kMLF*ZCS26A&Gi%l%jfR0rjOXK|s1;Y6N z=b}iSuEKk_GUq(6)f(}ya!vdXvMf(@^=2^=Yn=;qaumIh=vGaL@V>}BT-g3{rOQVq z*Tus9oCPza;j88=INTG>)$2Xiws59Q{(G6)J^w z!>21!t1xpuz)Jtrn=#{V^=BK8ZWNv&G z)Ptu|5T@PHBJ@YAkzjXXBFNR;@9WD}hx;j5{-b*}L8LW_Y<3&z)YROBv4->h}qNvQ=`AV zcBzrl9o06UNg*zL9m+9Gk94*NCZOGt2uL=+J51UyF9e*LQ;E?Sv%J7RTsCpqALvOK zAz_MwHRsYkZ>D1>FtuE2)5?~oC-vs-pO(d8Arbk17J!8$)wL_C!;TWb2$)OG4m}`Y zXr{t18@GH7U=h0S;Nb^jCiL3yG(F--I&c=MOtRtOS=SUT-|_|*UR7FMwaB&h+=(=bON8s zyyR?$uLN~M?U0TB0^hq%-P8Iv;T)pS6NAIqihupH(Wa7{1uy^D9CbPktd$THTr(vh zw`87byKSn=o2fjjYo#Nx_4n_?QJIlVyVI$g;ssYSYA>l2K60$9F_n0OMl@Fa4(vrt zR?2GZf43T^;BOp>a{yxuizd+9wFx{L`pU`4qa?Yh#|x`9cEs!m{kHhi!%q(h?QyrV+|w#O}bVrxh{NJVU&UwZ1EY=)?6QO%|N z`q8F`F+PlALUV<&RC=7wuYTq-Rb6c;e=Od=K$&PwjKlW3;=KiR$6%<$84b~ z<)G*VNAA^gr13)WAzD7tJ zq9qPXvai5cr8ZDhDHA*3{8{ZtOL-+ZEVqs{M1DkBB{?+Z4OoYETQ}y)ldPWrR9Yli zn8z%xA$ZdanE4d!@<{TQq)yP)Ip=nS^4kIvj^2$X-GslT!l(b53gde`g4Qxl>BF-V zr>UKAP38E%zN(;&IAj zH=>^a%)Onn1S~qm|Y~fZaZ77<8POloxPUyjiY6vU*8rumhvIg z(FI2qI#-jMHbG_?PDvPrmir^aIdT+1tQ z#Mt!U!v8>=tEh%?@VpVm3RwiLQdla8>cS7gRy#2mbJyQ6f8xpFa^?;pkhqY&9@sD_ z;KxNgU9AKP;D58wk!CA>>c`cUw+B_qzX(nRk^r-v(79?RcPNWR9Bws!K!(&?i?akM zJ1?X+@2ZzBI}!YNOw3l`0>R?W-3B23yDDgU-YBgqI>0DzaF|jle~UX(?#)7cUoV@$ zK;mteRleQqC=U?)VUMUap7RskROPxlMhd_$50vboO4p zpAFs=gzY=g8sYL|!D-DQJTRH?Xi65swf=p$*5aB;nC6BG1OSE7Gz74FTKOuWW432s zt}i>v0M2sDeh_J0(edpwi> z`^PuKkVAwv<~YSDIo5_b7Ms(Y6Dp?)AB4#i#DA*o3W<&Z-sk_>YQ zehue4^1%>I4Tdu)^Ck_*-8UiK; zbCuqjq>;eFi(bWFGXt&|8QD&}NQ|(Z4nJ^69l@qys@DaC2^`aYuC19&yC%`aW^8or z>0L?v_Ssn{uV>|J%XS;WXDN&>$;)7mQc^K%VFfG`fI62qVjSS^n#|=ohD%G3(G#F5 z;G%aJJgOQPP6LM6!pXetm?t1ybg%AHq)?f#xs9q&ovUDirKvI6AZOJ_2K@i4<} zX_#wIj6RXzi4$?VmtTM8j|bTDofA3CVym2Vs%Eef+NiwjJiwgwEt%N8t`bJ=k`%lTp1?+$w!f`0rB2Cgq!3hs3MA z;^`U=g_EATQSN`D*7C!9=hu2W2BB_Quc=Rx62T+rx{wfy@`KM)5MA}x$C1k9bQMJz}sZ@WQyaH@_#0|?6Ob%~Koh=ao2|SSYy=QE|*{<=) z=pMl85pB2l0!w1!{9UgltB;zJ*>WigvcX5P0O(W0Yps{;gAOt4eoc`E zhV@8=ejd#-Y1X56Y~e?seNYn@E)rb+25l182SeN_0oQvyHJ1hifDWZcQPsF1aO{EO=hr*3OaU_f6)2-ByfTg%o(`4|ohehP%IXpTt4Hih z-?^~3I(I*Mr?ThBINc;N1q6K?Bak|F;C}wrMO6kcGdQ@!)0wxU+~MMPabBKkTt9=L z`Tr7sZlWkTZ8D$9YIVqF$J@sH%w+46js^P^y0zA<#fdol{PxDxKp|3m_q)2+b)NE^ z?5<3#g`EB0xGNvu7(#!Sa($UGefBWyu|%nW$y%yReChmQ?1|VONcvi1_ zCw*z(87YwP_tskn@{PLpN$)S!S56oz-j;p-b33~$`Se1Qer~mP8+=$lee-RR5o%CE zm0hFw-2Ru|zoV2KoOp=ObtUjOc-4!Kj|}kO+?4bmJ6_#B>qf8d>Sc|&RdihZd;^8% zGC+5;>^-(MqW1!%0FpSOmJ#NYPXXw>mOVbhPr8k|HqOwb(b-8!fj(O64trogu74Y&NpBk&o*#Noz zv6JMR9dXfB@?U7~v$at|UdLZa(^*yy6Y(n1oXPQ8K@N7N^0Qmdnn%NVe+XE}-Fkkk zyP9hJRy|I8*>>;9F@rmxp`7LOTK7stHp$Kr`_n=!NUN5Mj|!^4k5}c0WOc~hJ}rwt zNALWgeM(eK-qW61^dhz2Xn{KN;lUS0b9}fpO$=)7atEhm$EIjr5`2@hDAfLg$Is29 z_Hu_|&(v69Bq}Ee;RBWNzidY}>E5o+AC6RE8%#97RQWat{ccA+gZs`w#&WaMAPl;` zA4@TxkI1?*A-`l4G5`=0isi2sgPr~X=>~n#p+)wxU<>a)>YTNtH&xhJ}V~`N>tM;ElV9TT9ySW}Yc{P(14U4M* zk-@?Qy5zB=S!?$5IjpMzE|+`I-8EF$aDsqAa#`)x4PYT{-#tgA#@wpnPEH>7FY``L zF1(!__A~lmbkLDoSkqd}=OrqDFKSrB#(b_co0A@R*BrcoJIaH#<-^v_=JctU?}x(dolxMH7dQM~$4#LS76g4DkoLKX< zdv?px{CMq<=F`cTvD=>qHjD=*`&YA71`PJf?E=xk#ZRrLV~OeNY65l?KRFd39^1KT z@hTv}o*ZgX!%~5gcj$}Sq<&p~pH5O{OmPU)mzJ)3?05o-4rr?FxERrkaN_Zan0D6jI)x5I~{89(tdpt<7vC-M9qrF5GYN3{Z;)WFwc z$5W%wy?jXyY`V@SpZLtoc7Z&n0i8g zgck3i26Rhz)oGR8XjKDE=H+_R@gv+J77Z7@D7n77i$#*D$pL{`n`LC8O_Fm*usgg? zL;)#N=XX@lC&|y=EGR$im%M4p!$zIT#h>_oO-67rbH@Bu-RHymyk%>KJ|=Shp)ttVKa%gVSPkvK8UZwO%~fmOLTNGU+jz$C8?OSY*!Gb1*kKujbS=hW_c`TSiICe z2Dfb7(izHJrly8Qf-1!RvGomr1I-`3Sf%BYY~SuUtl`Iv9eGO)`1g8YasGM2P}U}i;8$gb@3;T$)ETang&a14 zvuYVN@cqv4x|_E{n|uXsYaIo9ZCAX$_K@W`R>UDPhm8apPR}fMceA|y>0YsV5^qob zLsx_QSF+jV}(=t^D67t@n^JZMGScWOW> z->{BvsRDKA{OIwdOp9~x?dU{@8vD(y#rf4+wN=lKrsZsSqE)P$)$e_rsX&_!c2-%!HL#g{hHiDKu1HcIfd z{0^Gr1E)}P56+g(+IJJsKgAkQldX-aC@ly0g~abEaaJwCo&-Y7WnJ*5)&`SP`noVa zf;sD`LA01b8VGnA<)LL(%vCEPIpaEaAqe3Xki?%^JNa-0;49*MQkS#Ih)$VO5=0&P zW%bZNal;xs5Yf^jt0mi$)JD+ttR`NlyaP(WSSOla?6`Lr@LeI2_FkD-bw;m}?4F(T zl+BIiVLeG62pp@Y0L7+88znJ7q%)ZI*cK)HBPVqB26rfdqGcPWUwB8fBqq*(C!-0V1@}hz`J(D&DpaHWAuSa-kBeJWsHD^#f&np8*_{@bZxPJ11>;U(>;L_ z8O5krPXZ1{jHjn~z^)vn)>tR5lvsPC_4Rq4v3&HqPAy42y9r z5N)seDEtY*c4qqwVo&Fhpv#GS+*^mW1Bp?bgHdM?`+?4>`lH?OlUew1B?0gNLku<> z_kDbQw&=S%G5!~ z4;glDJ#T|Nz}XWdfm$Vi$#PG91~>mNsWSEvIlO!t%G>8uLU={^P}G@J7qw#J>@SSo zuo1M7&g6VZJRNeC=jDruFa(W~ocj}l6Q#o`UjVaF zmR(anOGiu>8Po-=v|S{puES_0bA>fdw}$l`2b4fYQW0R2sWFl=9VDzd#A5+SRu7p+ zZdYxO#NeKMwY3HI3x|1t_(|YC=+ty6AwKtN*|wy$!m{0vihasToS(my7$L?-MHw64 zRqJZ{rp_HcdrKE-)!yU*E_~c`Q+d1I<+Q-d{jkvYaeyz!WC=s9NV2%PfLBbpwxi29 zz0lbQA`~H9+;OhYV)yFWgK-E>a(bTCtu^~c?zT3TA(cQ{XA8{hW=gmk5CL@@mBspJ zb+~Z4EZ>2lAeA+9Qq$c>=5rx2b4ev$@-z`QB`PmiPnL0*?sN9%ak@0<(Te#fP})(5 zOCANZITB%d33`5T%+0}k!Qn-20JF;1J)niM{R0C^DcJ-nMJ1J)L|wq)82jQufQBO4s__LACwWWUV%=$h!bVcXc2U zCLtWcz=(s-8gp0UcGg_=zRvU?=3=~~vCIv(hi#?4t=^%n7FT2Dl?onT^C!3%h?w9z z#q)O5FZI38gQUnqk|}Ce*HlmMLbinSGPv)6j2bSR2a56P3%sko-vrGYFJpEy_{i5L zo5F&)SRz-H4oK25TdmTJ|-&c~{wYxm(i zFM&nKy$21)mHmxS3mIpvZr2|hr^zkYvGn_J&`22-Jehejs0R(H28izc!t>X<2WCrS zp5)ZE4W|i>zCR`Ww##^`L^z-$SBWObp(}^pY}$eqI?{m`LBNR&^}ILKoqdpUrtI)a7Y;0d@#it;Fjz9dk9po<4toG z#KLZfM*f_!()qhKW_EhO=(%wALiuk?NT;79A=nAUYWZl13Awr49)_F15R$#nj_sA=9q3*Y@@}6MkbPP4WbV-^i?aIf zrloGS8n(dxW?ja5i>?$6-oiZ(`?1gzuu_Zqo#5uljUVy!UrFlqs--ZIuGa7maanTH8co7jkmp__zH@P2K8UyyPMJ!mr|N5_ z8MH>rp>r}yuLpiO#~JcpzUM8{##N~oE;=h_8y7I9a-BoIRXte8LVlE>ud9hq0dW#l z!gjBojHKl4a{#Qq-X<&gBczpQ@0kX$$4>ep+3@0w%=^!>wh5hI>Q>^9rN6!g6mz{w zpj?k!zYTf+`5yKt=0kE+`hNq*h9L4j^0&&*#p;|aHFs?KSxg`E)eq55(%g5B{RaKy zZoaus?u?#$p8Tjng&%f}Ik4=}cz35a??she_YA-}CFQ6HWPBt#Y5bXk~oz z*x~AYdj{n0eyLm#KYQ*Ya5TA8a%{Zp3Dslpf1u@4Vc0Z!q_Sg51D)rkcN*kdO`S)> z=RnluR-bpe?K$|)>}fULVgB76iHxxRcN(7qw;#8$&dx;#H6D}pwS)cncw7gl{c*=R`U$g5WVJyhY8$#>h?D4mJd z8{|zWf$o40(*hD{aCm{4Or=b%lFj|9Af`cj!PhGY9}=@JEIS=@vRM4id45jLZ^zHP z$!`K4X+#@#)wn+Mq=bGb&{R9QFn<(Xm1J)5R{i*=_YAw(W3YGEo}9apZr-LsPaKXz zU0G7b60RhS=pLWq&x=4PteTf)3B42SiHNHC?0bHY3|@12@y46DnX@mIzHrav)1Mf7 z&rRhdc3A2LP1KQfR&UcN&xaMFNSttKv3%llGd`E^k{ox|?+LF|69{be{QU)*Y)c|< zU(^U)Kk3sYctM9jc=^*7AD{_t)+fNl&^>l`dap7L>TSITq3dA2m89!Cd7B#E0T2|& zU(^0M-9re&d_(-@jCy*JfX!hpQjTD6U7xW?s9CcWcKY>&TFijfEZdxJCt10ZT^`2A z%6%xr1I*iQyDZ=&HGB^Qfn;qn79n_Udy2MkpUELgT7dI>@~E_k&6{*TJH1RsgOSmh z!Ki;uWxS9?z7=N|eh>`HE#{|xB~Rv1y4i$1?WgvtF*E`jYX`dWSAU$?nPZ%?Il#smSsK49 zfaYx2oU*XZE#HjZu&7xVd_xY=#0^H?(8*SSVlSMr_8d)pV*yS5>;Juf^|vF=fM{&s zS?Hql0NGAt=`^&#hAy;w`sj4@coxR{tmw!0uyjxPsNxT#?tloq4@2!)j>doalae%S zg@9<7_&sH~Vax8rh|n(?pB|lyQp^wSRgo_ZaG9b&7mWRMNZ+=qOHC}}v{_?`;5pI} z+BhGEb6DuoKO0fCF05%Rl=Tc4ELM850Y~a$K?Xy9%UF0S`L|>Dr}pI`&8y0bi1+Ox zA+ZkaUkAX`U8vZdno6Yj&#$%Ryxk>ki6W*6QudVXIsH-nyCTp+4Rn>^L}ddP?9;~3 z`M1DGi$@!H-l}1nnybd}vCq61L_&uBgUNDbMI>X~vi0VU3s=JBUJOHZm~`D|9vW z0Cg8Sf75bg^BghV>}I>~EpdUb886gLhW-CU_0K1$zQI?eRfaCbIJfTz)(CB8TJldk zo8Lt^fCyOP27y~I?nmZL%j>RT`lH8rxJ%3}HW^2!A>*yOS2Oz_MiYm?K^9Fto}rHB zRei#MjKdu&Kl#?R(CU_F3}?SE7W}zu6zYeB>{R1JUxY<%KXUPbof^t9y`ss60fh&o znYqOUc~Yl@%cU7}wP7reJW*j_*3KQyvW!?ishMB4|9iw3m34G&^C!yJQCaC1fud;P zA!zrZkZT>k$b&eX7*IN$Gb(2(Ef6BjSheq;-IiaZwDE$f;-Et-v5sKjXrKLrotiHq zr#|;0Uz$%!18~*v&GItIJ~GRsXJWi~Abrm($0oQ}ek91F$jOlcJVpTwRgJg+C*u%2XD0Eym)nWyD7VarP2#wL)020P zqxKV5m+gt4{bX4CMHd@&B{$!cv~O7U?WKEY zxngNP2jakDjrE0#>oK90S}GZlx6s&HW+DswJkIZv&xAILd)5&q_nL0%;rp+v_Hp}c z3H~oiZQ7d5g+UaCy7XyoSUwgt9@hLglQV>t`8z__ObrR8#5hQNWisx?UHrvpAvuD4 zu>SI*5$9wo{@LZ?Gtc~N@gI+yahPa6*Vmk_d88u0oa+AhaQm|e!zBH5=fcOp1Po;cGi^b_t#(P z1hoXHlHIcul-2G9slW`#p5y%9@HET+N_E?p(B+#EjW@@;K=O_sBPVuiijC3Yi`-2t zlMfH<`?Sf)tlP1p+-DM#l^bscgyT794VQMRQ0?C>+_HQ=RVpuTSDNgT3y;F;io0V9 zktW%=enh;^re2{l8NlCxLyOvIkdNE-nr}d}|4!y-MaN;3>4c2RP?l{jw2Xw^7q@^% ztfnx^<%z!q(s%}x=897q9d3Z#L7WfC+5Ff#4b2U!)+cxaXRvDX1>mEi`r=7&aEx%N zn}2SzQuvw;vCp`STkt9{Gen4uGkY4g%6mz2Qdg)}c3PV~PW}|Hd*zbgZL-QvZMmJ&srdCMiK0f2{GZM2 zQz1}B*Gb)utVdU*2VPVK_R-opw(H&-130W+H}{|-oCoFi`hVsy@}J{gc6#1VgMW18 zrH6dNMF837Aa*<$V>lVV^68R$#e9Lt)qv{bsDW>q58m z*_zizxrms9F{Rt*;C;rm>6uFEF_DJ>ogQA!NVF2wZ(F!(%+}Z`IGN@sap#fnJN9~} zqeF_IHo11Tx#pdh`EgkAzucA;Y~S5?BBZ zc8Cm?#NVX<4|Gvr!DNRefmd6y9pr>oyslWVOAOlnZ6!LPa|aQRT@|sLP%iY>R`YBQ z=4ZLRiq>D&6W0u$B1ZK26K+Sy<$1Qu>2xn-di={>+{IbyP2=Hhh&D^IO(!xG*{H+0 z=9efJbuj6S=8n`Y@TK>Kmb?NU2V~dxAtK0|_X0$4%USwUhw-W4jSFZi55_#nHj=>^O;Q}7MmYe!dc+rRc zP~^#6^nAKCk08P0Pt5p$GVCxw7XTzuCYC*T_6a8`WysJh+@bfK9L_L$R*ITvnZsnP zaGQH4bN^cXwm&X8TlZ^3S38jfy0w8Tw>x?fU1pH7H8?L^tg5Z4EM9Itos&8+N9A%Q z{g!egDc->9IX*E48l8n1t`zB4G5H$u$pE zUiflumFR=li)g;i99X!VoG6{VAIXpt^uW!ZKVM+r`9ZoujBj=qnIv@i$>f0Q8~r-YZC#_X zxP$rF^6I@Ah4xHT$b&zrMiCmJ9t;1;d-EB% zl-d|WI8kP>ZMaYOBRSYn<5Y5FuZ(Yr?x$Y~G`Rb@|Lv__e9%eT4;z-**MwbiAOEzR zylk_pBBK5&5T z_xGtfgDdys8ZPWb$t)PpvwyRqkxOm4t)lU87ME<#A1dTWmGbVk$iaZMxh0f*>`a_c^!mEz7d+ss3l)ZX# zcFMh6xG8)))sJHFBW_Vyp-Gr3*`(0%50#-Lmc1=RiA}aMS@#o#`H(5az4G8p1BVci zt+FO4gw8{;bN-p2M^%uqF;CY9>Ycsmd;Mh(kQ%G2)?$dQPjj@P2mLkO9Bhr0@^shB z+|FUcV3d?@h=5F0MD>|a%XeAtW<5*%V9TpBmF#l5rwNE?-Mp4u% z$f9$ZZi{zt3xxC=R#2&!GIjOnRs2U*>>jtGs;;e%DQLTtD8B6 zE>g&A*jkWRdhXWvhr+ktx-|KCqggujfHwoLIz1*S^BkCQOFfmjcP?o4_jvN&h=gP) z%gx_gSj5c2ozj~p6NifHH3gR4nQjUB`~X0xVmf}q5|&M**@aSskO?$91$uk$Nq<|w zC6Ghm^#k*9$2uKz=jVk%HeAFJJ5|bnOO*Mhqe(MqU`u6n&3QV{Bdh;$Vofk#9ntmP z@yr)CR;r7tP2M4>OR-K*zT^YgpL}G4U_<{!8SL^9u=ylmQQFBY@t(4ungnxIz*d!_ zv`BlWS?D6-Foy2qo88{270(}bnwP5SY{IHuMK|iC1ljQ20DbS+)m7M>UrTn93_8sE zou0?#&kax97q7oO;^IFhq$`GTFx1_#+-=LW%inLtKr7&$lhC z?tgSIFDt^6`uOxa{s$sS*>t5`=OYa$!?)}C`F6zIaK5tp6RYNY0nS92m=k1GKk0_A_CX6K1z7 zIG961K=APA{;KRHAJ7e6(#vaaH8K)Lx*(rS_xrg@Q&J)|_cH@hrY-ht;8v|&ryl<$)c1V|yYkWE4Zbtvto#nzyN@oU^ zzf=Nmt1h*Z?m5gg=OA-E1JSHPKqp)KmgXBSPGO291W$lc&B(%7KLQm|qqJ z6sTG68F|@@6Q0SI;yajK@d@-AYZd45U|35}z!RH$Mrmsq7lz}$G4|jTEn6>U+?6^l z+qGN~j83tA(MH#8BYtueUi-YdLFh(8YuA8-1LDSGlq)Sg?6C&^P$f|A^@BtmwR6}+ z#z~NTshf{g2^8cAnvUU&<%{t`^kZ)+0htqHqv5Oy_DZu-NtfjOOzC#R7P z4&s-<%Y>j1jgVH&dsrJCb!q83eeKV*(O&YN~I)_Y(u5yE`j22Y)2qH}1@qxKPjD1tEtFS}}+OP%O zEg$BeTy3NN15n<+?)miz=+OCK3Gox8CVn8F&(k+Uq&ZBSvRrMBUUe?j>*m3~%>hBW9_ zO@|mRRgW+A(tAy453qUyyufS5pTxv24~FnVOL8A;P^sS-n+!GI5X`!bMK(@o+0s`b zM6dNY!9{Wjhnt%EHd90cS%92wlf~lge-j;fxP1jPO$hza7bGX}L^_!aUWu-@kr)4I z4#p@qsHGLaU6eEb2f9>yTx|f(++Oit>Ir>4%kEP0QC^%Ckdl&aqFB;O-5dwH4Gx^z zM4Hk7wRW7U@DWZmVd267UkD6?#rm&>0_6UpoHhY1RU~}aL|5LH!a&jrk8;pT>2T9B zWbK;$W*LpGYh}ZvC2H_9R|3w;ugm``^vr4IV3(P`7^CnIkPZ`HEUEa04Wy~O=$%)G zhu_R1NK2|iJ0r#NBoX+2W?MjWpg}bs6q+%!BNKSh2++J8t zhHvU&0VtygCt+ftlRZj+_q}bw*$}X|%8FWZQ~!|ipx(B`l;457_9y?u+5X$hnH7}( zN=5@e92|;iWF5YX13i9<%fElx!uii5*1(+u;ol8nUvt!Z<;1?;FSdV{Z6{NQ1wG;z z2$D(-^%8G?Ka++cDd=|aIzp*5!@p{MrbJcI`ZioW1y|-v!Rr)D8a0SAH4px zC+CoDm*}DlEzwN2@jjk2tRmad$76xKQM+L*0vu$^t2LLfHR^^Q zC5Zx37Y62Iox#ZL-e_h&`motnA|dvDfdNIZ6>$&6G!kOO)P< zu&&l+7oOssx?DjC(TmyJq{0c41)&aRchq#kxdG*RSOSg>Af`9e)q{TW8o_ z1@qm~S^<&fT}_ct7DU&(b7jVudk`-)lm$C~(lX|LuVfwVK_>p5>}r?Mwow~tl}<@l zkgMh1T6QUTD-U~Jq7gIEshNx?7wW?8=e)Jh@&Fw?kRItHLI6VSR(;;WcB^%thw(_?_inA^ zwM)y^cTUR{ZQ0#0WtJxkx4gV?d|h${WgH``pMED>ZebXXe%h|eCp0>rb!vRX5){nT4nd#Qd59J8+iB(9JiGG z!`NA4StnElO5(I}gHFBz##-8BDTB0$(6_!hWTHJKVW!qH+pv{?gECR_{BCF;c(JH2 z4C>~<6qGVcfg?t-7D8p)OQ=oItmiN5Ono;xcR`XQse&o;pN!3g}o z(F(${Gzqz_!Ui;0BUt}oW~QZ`2~=B) zlE3=;;Ot)6`9NkmxmHF9cN9{RTcrYDHm^jwU}hbHl*<<%zPzA*_$S6rHd%`}gv$V4 z?m!;WG_+T+B92Lx5+_Zi-$i4ibQZit2(Rx`4LHq1id<31LQ^e~#(*${=!%NeC%a1Y z_F^tsF2HO{u<@!TJTB;(eScpQ9$YX!4_?80JmE1Yzb{Q{CELJMb zU#PqG<(^%Dp*ayRX+$;m$1|0?bhgF{r4azL4%+q=3LnzJQ=o39`*I($CfMVJdX$eR zwxEq10v_BKTG;rMwo`VJK$kUby?=sa&QIEdOGUjXHh0Hv>fshIwW0d5A z%F@wORDaEZsw2jN*}8clJ-Wr%q+0LR>Wh??u{$NXKlG;@OI4g^)6F5TuBds>^(w&; zXa74Jdp&Uzz4IgB3c2e2#b_vu0UdF3G3GHhS=)Dd zGi7&h)OcSscI8^Zv#sBb;yV<8{c4G5T>e`R?U0YB}re2wlE&#iGz_IEwMa zlh|Ez`L2QBpHWQ~0Cq^3lQAB$yn~fk90gmbyy*)uXJxW^IPiAG({U%Xv;W(yyNc#(b=reDx3&1KKj^s#$8 zBHm>Ht4){(m>f}El@b$1XyCbrM&wM7Uz~>Zv0DY*Z`$Um)zxiA1ES8yLtV|8!2buR zc%V;Z@d|H<%4WZO7K^SO`!oRnlnL6mVLX0qS4YQocxo;?6nq^3v1R;g zaA>UmRQqA?k=C#f=vYS2OTcxG?q|_WsKQ2W>in6r%~M;li+OaN=I;~{`B=vzQM^Lv#wds27Km{{L9(M%!@qg?Nx5rx7a&a0cN z)>vqP6&XC$dZDExO&rRnfzkQ?dR;l^Uz~po*D*+&@&}p*n05NFHu32=(-}dt_qrR6 z*d&gXGTDq)&HmtIczJW8p5rw4mhei%-!~=s<>oOWwN>(sSN@_pK|evu`1Yrs;lOMH zkGMgzYe-=MTOc|UJ!CX=CAA!hR0aUl`QhTce=@`L#r&65WGosD^JMWHgg{t?a?P@_ zM{Iu-=vH}tEyUoD!v>OZ^{VBaSMM-~{?WW|26@LzTKeQs64IK7HYgVXJbSE)KMXii zD}N}W>V?Jmr)y8-l(aD5eF1svkZurri++m#U(36r!rE3j;|u`%=^FJA5$AV(gJN^` z1c)X$o=)=xoQM7sP%}*jbFk8_>i$JM7PM?V{H>3wxg;bF+5?+M+G&B5SJS!OQwdil zP6fV;1#c8mO*i&QMk#pCI$#BzKpSuXHd*+eF-UOZW(9MDY*Ag(6d^5(w?Q(mEd8d-{_N?bsN81B0!8>r(Kck+KLE--65o9wq*V0{O}XWE+$*5qrDkcYR9! zC8aoM5xDZ!x%&74GbKMHDOyKOE13+of51QDXd{$404Y&bWXou$6Rl{zk$?SJg2fs< z3P(4-jVJ2aY0`4CZ6GpQdK0g8l{0NyqCrCo&85#(Z>79w)F`zUAn~ckK?9yVS@$R@ z$(5IGPtu-AtPBn%eJSLFKL6tKe?B&bhBgLnkz zZ}}WJJR!3Y0>_a`J4S;v#*tD-vm?{@dGdn|Vj^l~lE}jUO8-4S&r{H~&rtO_r5~TW zR>t3Kx^B?sX=5V=wFB`y|BI^WTyO?5)t|Elt`vjlp6z#nvJx@>14+e{UmHe?e_|q) zHluZci%)npw>!i)tHl3o@LSnDdAI}ND`;!OMko=C2F7!|l^w1m17OhpqWrm_=Pmj* zrIeGoiu1`6@0%buuKRoN?n6E7hG;5se`Ac_NFtN}KY?9y^oJ^D&H4q6<>wmOt*Z@< z+$}JP3ICk&mBG?-r)wqiyW1c{01OK<5x48?%^o z(YjuU`4s?DV*>~;<_}1YLOPNIfq%TK-LYk}zm|;yo&MlcP5OwAy4|WFVUr5Y(3~N> zOyQXiY129=Q!RJpEEP$Uoe){}^+>87mV6*L}_9%bbDh_K0799xu%f9sb%x6_a9;%Q;`94V+z@;)CP z=?G|ZwR+YVm$wBtHKLlspo0mj7vUCufDiZN*O|$LKU#$K=3Nlb z*2X=@zRO6tSM*co@q-_NqYRmt_iZ8Utp@tfohkCZ(7sdiZ?~NT_qK&}#K+JR6HHZ; zfieXHQg8U`?CRpOq!8{U-%E@%=%BD9YEPzwg=&K~fC<(gZ^$LC|H{6&4E&geEMJ0Y zo0p9b9Cn-6^|2I044X*a|2xl3(dwJpGyBjf3-EnSk|tnMh`APKP$qro)iFK(FmdiNz<7fXJTv_t?!q5p9++%|F>!G6LB*s{B;JzHH zF(8O{@Jjq}sfVL<&siR=pmF6drO(z$<2TadibuRrhQt3-yB_`4xAn>4yK0$oC|a#C za5vK$0O|-l{~>ij>@cMmrVI6lh0@h8*=>b;#zv z4|&-Vy;}0ZK4j;3a4tBU)C#dJh=qrW-u-|s5yl8KpBLZd2b!# z_T^-aWvzG$gCgouL3biTm-r&C|Nl#+`5bp&}bH%P?rS!t?kfNr3`=P4oEM@u^ zPJ>o>rn=Iz;nVn4d7oDOc@`aRs)5@D{@#4H{37|BTa>fGnC}_0!pI|ns@lw|FKF?< z?B=Rz5lorey(B;nQi)ll86!U~TeYyoMZBGM5b5S2uwV8_am7-_RKU(Q)`6@H9T#8w zyo9Mi?h|yCVg0Mtffq13RqVz9?+ z*jYh^sZ2vfbFwUjQ3Z#~k;2>P8%JA@OkN{)~_pF4%KNB z(YpL5eBMDHJdCX~pv@MBKn&R8*(GxhhCNn;sP(YhBz^1l8?re_?JdE#%jeqi?~ z%$V-&O2-*Ai7${#3n-v!E;E2Zfm%~gBaL)P9y#)&gV`^Gt+hmjZ*?VIX7keXS%(z zW#4|egP7m`KLAicufFUlrHE_}edqwG=aO^H10RwbAaP5A+;kqMo91JHGm1iU!8}kh zVCbC&NbY)6a!48f0GU1i01AoUBzgluB$@=0xb~1^0dh~ZG_l>l?avg_N)9^rrh*A1 z@_#x=WSxHZ0+A!mN#oNLfShKR9_F5_QEopVo+;R1a&gB>JiV#cliR&Q%*=2OD80r? za!*`mwJ_ru9R(*K@+tWMamoHvt^x)Jr_zUzz5zV?Qxgig z$NvCcfC{!ilD&*7SgxM$9I z6iWVQKmZ@DH)%LOpS1(?6f&2{?fB9hpP}k$yaS$;<}D!updGzw*kTq7fH|ir-^-qz zsi?VbpNXV*RdN@g>V4^X0N=d$Jaf%V$D*%J=AvJm@#)rr+c;jQsq_>!1IQ#|kp|zp z$@Qf}xZrlFl|aEgsI~QABuaS6sfq)~=gl->KDnjsl5?Kkm0&k$?ncqjRB|r^9S?d^ zrJE$=o@wZ;0M0o*1wbJXZCnBMJ?Y^buU9KGY+`^ zwDSI8$6?Jx!!7Na09chq4teQT1SF;b0Z;7U6M{!- znk57fPZe#_G#;m|Q|jkEJ7SYSIUxg}#!XBVDu6l0DlkhCikK2O9D15G2e{Q4>8Lm2|UueF*s4jY*zVQEF706oF~$k8Og`B7jI-1eT*nzaoUu^?Z5{Dx&_qqsK!Q zpK4#Tw@ez?ZGPXhAbu5H^|;%QD;Vj;9)qz%%R6>}a5<)V9>%U6Su%2zhf|FI0LwnK zU5d=8P=n7Og$$%~im-JUx|E<_z(+BDR9%k6V5hI;N%wg4ss7src#P8x+-s5|{OG$2 z4aqChGY{G_+ku<~Me6P6o6r{5^Sgz(oVOK7zL` zz8z}!9E(Q)>s_RO4=ztHENPEZO_#wd3t{f!>RJT z!;1+Sv9zD1OqxXJq;7tKyV!NTH~3cv{mR#~xQ6LNMlSg6U|-;OJyECS#f zRbYgIIrgP@Yuvs>%q8%RfWh)}?~_w6hU`G%1+A|*0f3+moX}=dz!>!>kL62N3Ja<+ z$A@f}qzl%jT_Vvi1PPD#MQC}z9tM82jKJp@6)xY#QMBW{~2mU?eU-y)c=|?mPTI^+QrP?}r)w_){dtsMc zRZ|)I(@y%Lwi*DiM%;<4}i zDPeruj6oRpPAg+4sHU9vs@dHV(tQlg55q9o$s)^hD#YJ(s~$e16|F9k=n1yF4x|2h z3=>Qnvp^+B`4y^~TdSG4G_-7^GT*`p2^@p#gIs@$bbHSbc{qd1k|J_XOjmkK<0p|( zx}KnBpEPfHptP-PoUV@z{*|Z4Zm_S)F`U+sk5jsb{c6LW4&F%o>tRri2;kC%Zo>kz za$ONtX>M}u;YqI_H#bbJ$D=yTf@Rjwp{qL#04X>~8G zh=&V>9jW5w6aomWow7N}%^MwmIxK?r*wi=U(;}EJ&Uga`sBM#g21lhRhC$JDk7`y! z=6f1(YD92(6@K1fFdm1R=h7K-_YdT1#8OC9Nq2Rzgf+6Y*fmu7m3i?^!{FQPSMz1Y}Q81<+n z)I?)DaqUsV1dg7yWps9&0$gqGDc-=XwTi8NvXD@6IjCjSit<3u(zR2=zz{=^af~0r zmAoq)x!ogvT|dT$CDcy#*yoO>CYNE1Hhrp71W%37q0jpww_xzZP(DfCJ$hA@(Uo)Z zgXj;X9Lu1!d!vlhbW2O983YIz^f<3I);tw;9lt6nJXxEb%&qLm!{>6xl z75Zb+sx>7Yg(zR4^Q^uRzD3+%f4~~3-W$CK9!rDtucnhpg?{>n9kRTE^%Td_t&4>) zARUg=*0uV?yAkZ!^9%TX=?UE%55l5t9&w+Mzaw8y%cEGz3Y&C4g;)OogilPBNNZ*1 zp{MnS)eDi>`IzamFAR;3^X*MKe5?16{vg-VviKKBk#JxLdU44<=BUf?<4ln7tiIx} z>kFzU*|dD_bom|^uLLT z*)yrh8K)gKVULsYuV$a&ZO{j6s}b*2B=~2jpe0pEUQJiFirt4RJdEk`IX`-QZK0*^*r1AK1SdvryVLo&{uUQhb~cw zFi5BU+J*FKldNa*HA+eK7O3LJksehP}>r z`x;W+DSxsi9PKsIEPK1-+NJUbbC0DC;jzk%CVxy)jVe-cT|-*$7~N4D7wQ{7T3Xy! zXF2_kBIEpNPL(DzgT-`W+0}NQaY$iq!>te0F3w=-lCMFGv&XjrSjhZ;OEl1HnvYp zQ$EU>;8PXOe8%N^VA5=GIX&xq*`_di(+!C0(v3%R3v&~IO&gyBo`$x*!VgM~^!%Ox z9<+J|%!X$qfN4aIymiHBHG}$8O=#= zpK9;4fl4PHoSJ`=#z@9#3IH%y@}+Qg0dkqi%?5Pq+L~foZ;}QHr*N?2aG>?2^Ow{L zjpGB6)|#*k^`~%4p(&6s>(6R(pOo?qLSpyGq%n{N4J+GFu{J^CnpeFu1b68`Q|rY$ zg@+a+j`ZNU8;jLgUx2oV9Uzh8fj%dgyIiRk83O0~jg)=!uR zN|h!cWlb>}4{WmJf-7za@of_4qJjDf=N9zFKx*nO zt~X$e^c5Uoa!+CFQZ%6SI20r@v*14@WO2nP#yJD=qP~=TDMw;BlIWFvqFyQL!Z8bfA_=xVgnS)P>;Xzt6QX6oa4A zqz#TwL(+h_z=x?PnxA-7z*G3s5=GsXUBGsyJmvirM?fgn<6{m%$4{jzJ1_uK7>wty zw|Y}92LqqwMf!m7m;|1+69P_6F7u4!lS#?yat#60Fi%cR00G}PrQ{p~!Rt*50pl(7 z{{Z#Tbhi$ofC~Fk>^L~%-kjVFAIMM`kaNJK_PE4CNavvK>p^00M@lyg@HsU1TnvxO zm<=q%a(_A@8{Y%*G~J}})9Fq|F>nVx$)%tVZ~-Hnezbwdp`#&s{v^^o%G@s)?L*v2 zbQM${b4URk+^&om$;bJTUGpU5YvG^=jJoC8Ow5Qhl3>7Mk8OM-d|vTYr>$9j5Mo&^`sG$DaV z>sA5~*)<~D73s}9Mce%U04jI5T(9N)`84(bsRPod5abF`2>i{*;&%Nr48|Vb2tKB=-CTPE-y>C(1h1fftp>9luY~lt+y7%}7X~ zFzfv&0h=+$r6?HhN?dXUASCpn>cBgn?+`w;yNKvbG-o_|&`lZ-Q8yOqO*x4ietgq8 z^vOLbN*r)MDlV3V4)ZhCoxF6WBuw1k=A~F9BZcGCoKogXL0DsB81GHTd}lQFw`^pQ z?~_rpByICZqw%DqnC!qX1f21T7=k%C{{TNqX`a|FO~Q}Hm~|T!Bt5_<*k}4t%!g)A zkbfFMCu7A_lU&oI=_5Wp1y*ko+42N(AJT;<*f~u?-Sa}R&riy*+r-GHM4J+@yU`f z0Oy+G-&eTXmUhRfC;Vyyt4R3ykLQ|peaC2>(rNJ?vqmxRN^LCE{P&UZ>CJG5Rg=`3 zkNZ*B@tRg|sH~3g{{Uwv{I@UkALC0M)|xnfF#c7>$1KJrVx%eWTkvQyO9DBOeqo$a zvRbwlHCp#fQNlU=YFM?K2*w4lZ}*L64Fsy?rcwC+0QITsq{5_T;XgWF==SJcZxBKc z$!!=O^n=8#I7s79psGy;gjo%Ap#4QR&eGu=+~$i%WQVTyHKXyCqY(Yv#YdnOaXuvL zsGtk^aoZ>V0IygHd!+Z2;-vo9(U9Yoz@pK82At#6*PF-sGo0zX=E*g;zS= z^O6eV+if26U5U`&vGC+db2Y+|=qrCu@P(b+p%eFw=sMS*{l5KsO|ILWQSa@_T6e};=Af%a!3zolMp0z+biwX%)AyqfbXe-rqo6x$u;z!~n< zZasg*vkc90?Vs^kU|K2QxnH26#amW1?Cz1<{{U#)l0lUKK7>-6xQPK|TmjgF{Hv0%(d}*~3T1X< z*qjROEj}KAs^1~bFnq#BJFmB9T(D3YAiH{RCz`o0 z!`rymK4e@EQax!Yr)v)A;)ub=uU~3Vza|A}>iR_5Q-1eo1ok{uTg?>c#tuE8h4r(Ov7MRj! zcq@!?Ru@{blz^@V52ZV?c?}V9CH+8&eOPZwE1 zq?q*;i3+jCRCKM=i?3u~Kc8 ztd^+vooB}OjGyR+G5z52o_|W@uXSnf{#U&6^PQ?Npne#y7rFSCaG>o#Pi)p@&%}*E z*X1gMt!)}M=qBR!(Dv(HE-TxF`&Ih|&m@oJD}32_Y8z!kDUp|r!-JanhF^(#oPB2> zrB;*TMyoi&T#`POe#TF!9_!V;Pou7*(R#wn*f{x`sN7mvI)JOOk4zJft$gt(jP<8& z!s*Ab=}Nvj*7yeP$R51npRs$~e9>B5_em^JNsag(hpvA*1(YfYG7oGK{x#t9_{&+G zjNZw|qg7||Hm>;w_F_NZ{{ZXL_EYX0(e8Wx+fk4u8TUA*1-4VnfMedic9&YeX#h7e zXZ`Q~y+8X?P{98HNsy2CkNDMlFEMjQ+Mh!2vkVB4PvR-8*1>@;y!(JZ#=d<^o72g- zm)GwaciS&X0NhG_GRBW&`Y`5-J@{POAa9+2@2YUTvSb)^-RZbj&*^n`4n)!n`_sPF zbU)>^zk?b*i|WIkH@ZFd?bb>;l0VOiQGa4^3#bE}+0IyOBbk*87NA7oLKdpEHxwuy1M(gWDm)9|ZOs|3NY5Nzt zVa*=L(GbOJE=oqMI{RXt*02IUa)5hdHS?{XiLaHm!*?>{(a8R^m);`3Ndc1PRwtp} zf5wku`+{-#pG34!A>0uL-rQ6RcVrI4F_H(Umy#23K?v0TWE5BH58 z#jP$5QS5uPnrf5D$nI(}rpBZYe;V`KABuN&0b{;}E_2cPALWYIwfLc6`SW6R^uY$Q zbmX^1sYUG0y{$rkRj?@tkyLDaFz;OKemv6VAgj8L=a?-E=9o zj&eT`l~6Vv-lzWn)mJX{<7aLqk|X7u6(fK@O3)q>w~|kh)C}-(+x%;P#oA_*7lR>+ z;FOtAuHZXlp1+k$noT6P6fWJ8)Zs1sb8B#LSPsMsGB+6NeXD6wQP-e3rMf)`_?80154)dUwJPgYseY@3whJcGyQP)(yuh)iv`EklZG>5a`=%<11%hC`6!(1XDI>SL>07f{l4-c(D@ z;wz*c7P5?PT=Fr2)Agma@YEPPvU_E@{Bcn1)!PnhJ6PqM#Dgy&A6`vUn^BGz;#`6_ zARpz}*PzScdkGLLMbPIt=Bh7*H1>U_09QE~0H4%We9NHkQBKY*vqo{V23&gb!kfH;!j0SDI@ zs9VMwYxI!|bOSZ!8rO#(Rn;#3$abo^z{%idv)93zf?$U{A5&c_5ldD?q?5IeW|vyA zF(bTjka`kEDnxBA;YI{x9(V&Eoq5K;9J*(PE#i@nngAQK&*xm`pQ>usND7B|`i$g{ z=Tpe#vm0qywYlt<7CKafsD*R-AICLLT@yx^Dq{+LfUiG`#`m!tO>f80)v@D*a5qB0 z_r(gGNbDx!&bD6&X;Wkd#~pyI%Z~uq$WNCk{{TOQWn1{d27Hxe=DOQEq`0+NqbF+) z^-0uERxViVa#MUg+DKVJ`j7s#WL)?`t(}39exKx5pv^H-dF$G!T?pnsItIsbD%9#N z*)9=0^5a96E{wmOWn3YMl_!z!*1c-mNV2>_3`!cguMucAac%-I53hRKH6-+8@+Gn7 zGU`SWN2M@pgS!z|?(~=$&IUQ@S+i*olfgB=KXPs4N&UFE`~7LBT~Wv*imuu+m6~Ngd3j^2VL?4qm5FZ^m(-%7c0{#twb!ofk|GElw9@Pc2AR zxzT;3cKm4jK=0PM{{TIYB20ZK^WA~V^`hXB(0S>elo?Q*5DjzUX+OPB6U&Yofi!J9 zLseyvTy>`lr^PFw*t}zhI298@hwi;oGqCJEXfu}Q6+*Kt`2#r( zp42tD>IkaFa^BR2c0oh7<4K~xw%T*+O?cba^{7SD4!?yVx-4)>rPoj;!RI2HeqoGM zZ*k;|?hWlxuBL$U1u)f?Hu1+=AErGhTeM-CLp)@0KwCjZI#R2ZJQ`s!o=Knpc?Ode zhxvyT^?p6790EI1Z1g!LgP(eC)!ow{jYbP} z29Ghlg#smH=ltlDdydvD?qYHq@urCxP6?=tyk?I(J!rcTGf2R5 z#W#7+9Mnwy_@!BL4?m3;b`^%p8q3jee=q4(TE&mGS`2+Sqs`-ynlEAMQ)^}&MK{QN zV+4U$nnBM5QY)Z5ngaTn?{3)XQF%ug=C#R27|l0g_2V7rOdDoVk01SdXrdLd2^spDVxo)^bKe8KM=S0) z=sOBp35{YH@z2(kHz3aJnuwCU@k)gKNuzSKClX}f{NCM#B$IpO`cy!i;B%g|1ytub zJ_7#I2}D{7#1TZj=gEXV-+H(BRtWl5dnjN$vx_0kf3+tG=_}j_hnW21^rnc}Pc<-h#xaUONo?YeAQNWp zym!yFJ}CAQNas9#DR%(g!}-*i&%HIU4J5Z6J?X&$r`DQSgMrEZR4Ta~@=vuIfC$0l zoGATi{{SdlDC_yt1Vpw^;3`0e7y^~Lpd11s?qWTKM!z%X0YU9g@{2b-Mt1?1C$1N|vnb|{N6pSmf_Z;srG zxquESP6?qgSgI}ZGfog=^Q#2&noNWIM>Osk zSV}n9arCE)2kO8m_cc80sM2>TZtF-`{#A4A2)wn#WAUhEdr0%T-m&Dc)@Q}NjIp0Jz0XDJ6zkhmB6E_B{w~S+tB`p5{I;$^@ zF5G_V1W&_1)`ut22QQhfwlX-#Rv(2s`#wSk%6RpzX6MEFuXhMNmlaA+5o$Ax8Q6Ue zDf{evtE)RG^!y%3{OL`cC{FoNK=n1jJ^jn$GQa0hywgZ|c5kVnr`$B$?;cwOIBTT* z4u7RZ{i&rO=6%Pf%0K$m!nAS|-jG{K+!{G=yA3-Xyq+S`VHpbYAJVEz;`>3&cIWY~ zT-wM+O4H-8k@%yPZW|kq<0oEH-3O*>2|QtUAmA$f1!u09Z~= zgZnZy=WRVS_{T)+O+O*Gea>&oknznw?D9A8kzFd?^i%;>uIT zDSpV~lBXX^>*KPFlacyVqfbWXX!=sS4NfP?BeiH~c7l88!|sfjpXSiXF z!x{ZW5l-5aN}6mDo(5{%V9M+tzbRRKLGZT_Y&#gICk0hQd+S`te)~8EV$juzBlX6Wq)gA)R>&;ZXwsAB0 zc_~(_nq?c>sxT_$;OT}a2_M=W~=m$|-vBFdwPfzPM^0IH;q;zX(r=mrIHucZ~vMMo8= zZ;+0Jnk=6|w`6vad`D>;0w6uBT|8GaC_X~-(AS$akd4r;a83xW-rCLlnE3t?){a=d zg!5^kpnOGjoP`VYCZ>bN`pv5Citb319PJEo{OXF>yBvQ?dDEPM@{{x#q~ji_6r{Fh zvG|qZ9lJ)SF5gVzp?`>4>kJ)6Q~v-0BC|%FCrylT?M_`HcRWV;`c)+rqDXFX?mFwm znw{e9mhM*v)rqPvsob|&T#u=%zu1=ruwauPLr?zzgl6rz5I#Fm?xGx<)w3kt$VvIz zpL&U#ap+xS=0|ZA7B3f zT~(sBv1^&DqgGv6->iiGHJ>h|%5jovc=V6}K4yM{{{XL0Us}f+7BA1I`89fMPOoAK zFQjE|c*Q4}T?S8TtuqBsG1jGH@?#w-vTpj7=I1Bh6{VuudF%-%9Oo6AAS}QEp7nCU z_7V%cV0GGQUcmLy8yEKw+5s9Omc?Vd+t{4olb$N->FIqZ?w@xVIXu+!*x30?fg-w) z8=UjsxAmvamq8?EC@sz@vcAP6^cB;=Wu?HVCl0}vAfc){l-et_kXxjlVO|S#29l|F zYZc3-+0HEB${3!Wm86pl3@=(pwAgXU>T6QgeKx{B-^B8^a4>K`ooP8JtE4EWEy_sy z`c%L>V+wfot$QtU#%5s!kokGT8s4<=9-pu*np$V3K_ApsJo3G)S1HD0uvuHZkv2^sA$7b>;#$sNf%3m|M&Cp*bUV?cDx#t=esqqDN1sXje&Z670@E ztynxMcPPk@1M69`c<)h;CEBqj&^QPVe>!9QP}CU$g+)Es01xu6d(^R9s`k0E{u7-W z3^AWxE1vO2jMg#6Sb_=ZSEl%nsmX2=3y?WHoc?u*tZTFC@y29e-SI>nM*ZQ*MJuLY zNfLwG72WtlLQCB_WG8Pm!pb`e*3rCId80%UEELa16{POggruJ4_l&e<)2*VCH{1&l zN4;e|jGx(%UoR+FE8z6}Dz3BQd!1c&%vWeRz!<1(wZm_w+}XFxlHjQ)q54x?$u8jS zdn2KXN|s1f#B5ZIh6966HZv;#6^1>HX?#EN5>IH>a~;hXB}w$Itu+fNIU5c*Jvgi- zQN^^;N-o+ngz|18JQp9GQ`EIv?KKsaSHU>sn)I#qZ-PQF_pWE+1)LJ-W=K&%MjVd9 zoN6gMG&H2OxrL*6s{ZK-S=bdk;N$w%gx)pO^x?S7*vA~2=R5~_3|hC7CGgDw|x~tpaj-+}F@PDe7{}fJ*K20f5H7f$>ClI?sYFWhhB_GT!GE z^2fsjj(uH{MGiPWt$n6!;%@+3laLk+0o(kFBC?t_y&+BpYeT^v zK5q$$qGiaGgXn$v#cx@%Jo=gt8Ch6;E6Q~x-KxbH{{BB&wePl>Qg%zpp5q_INH0}Z zRARoHfj`Qu+xXJO*kdCjrba)NaCV*^ws?@|4S|tY6T{FT$dH5e{{RY}N_#7r^EbLX zgRNOk2rbF>2D3$hw7j^~AzwDqRSPBu-93$A+vz9>#OyKkuS)Qio{8bh?an5`_vaNY zB(Aj`?(MPVb`#r4;sENnUD*fTzJ&0LqiE59!@oYAI5qRhia51NjAL$C;Cgdi)IK4+ z(d{9LS;Cdi3_$%UwRL1wxt;qS%?StSFPAx-@ygUSz(1xzg*_J8%;qH+BFI=pbUEbX)D<)6)j$?p;k>M z+CV{wepQ(^jbwy{kMQkV@@oiN{a(^~fz#_y5osU}NHdl`1xO_9c8Q(99= z9h7SlU?}7YZp%0&3%rfk#!2f`?`|SDz&Q+nV1ZjC*5cCc%Te-7WnJKKDy)(=&N-_w zPX)5LE*dujkWb;-w2D`_x*7K_HynD^eapM0NYR$1NH^!6dkUvwbt2!#N?~v~$6B;43Y?xP zlUeN>fyg~*(%@F!GmK(}a5)03MPz@Aii+agMshmSvL!dMT(E46Re4u$%~LWJ$;}}` zQzY?BHQ2_~A};$>AE@tHH!;fx4;cXTYVP!15w*FpfUrV0U}WO0d_eGD_D+cCgWWOMGUJdtny^|pj>#k1p_uQi)#f(D6P2?Gp_ zdLL@{eN*7ag?=Mw@<*i3G`|tXnGnj@iefRIaga`N!6Xx$9*vc1s-CSk=tQLAn@aXR zV+$0=fHM5oW-F&;cKX1mZJFBAJ znz5;3jo0P(t1F?jpX$hC{_y^$w&&2Tnj&Mk0D+NQo!!*>e8ppt)Pi~wNjCOlw{uCf z8;JVH_=Elx8)-AB`>j;s@kQKYnONi3nzs*&<>So3{0I2c_pPYxx-uhSI`U(0%7bd_ z$?IDd{ws)&n`!!rxdyXqaKm(QocbD$c`eZSe9mOR+3CeI$zke03hkLKB>6(z zZFItL=;`=V_MSqsIFl1+z6Cc29-fuoCx9Y6^H(bYZj0e(}wp~SM z*x*W^&X~C;`qyW3;Axa)SzG99GW)`pR_b5NJ4aF~)kNOnvpH*zyTwIpo1c-IlJpqc z2dFh;PPS+HfX~vly0C1lNT^q(E)85Zj-&4WbcO9ciE({c77m&NEGD zF~}5?MHR@$S~rYio;p>77I_1jVT6O68VSEr9E|ch((W1bsTwi|K}okAsi3&r=Zb4% zY37}@Q%a2B{VIep6pr|%*mL)(+m@K*N{xqe=Gm2)$#|z1% z!*wGR#64M0d{d4a9MmN3>a?MO}d(JoQ`V3O7`!*Jgagup4FH)58^)caieeVng!}_ z?+iKgq|eFNQ(^nmulGf2_b_98#%=+?r-vzwW9v!A%0~Ot4#d88)&BrFrfxXN>rkGQ z@zDJ!7eX@SjXjAJ#r_&|{n{;H6R!mS07`HK3}%}d&S|W~Fr{$lIY_|-^rtkvk6LR0 z21i^}X_BP+(*j^gJ#mUca%qdy3XwqOhoI~k0ATV>7;J&x(-k71&ra0fKb->+RsAth zwnj5jNhwIc2d@5)2^Ed{Z;W6tO6C&tXd4 zc;mGmKnaEG)beS}>PJj~d-tgG^I-Fy^&mmVdRhY9Ma+jM?vKiXqdXk;r};z-E4(X2q(8R zs}ar(U;8Lik~-6tBmsCV$bAd+l*wJ=75DQbDhy4B(BHE7RQ;TPex{|#E~95QSQEt|QhCL8F!&1L zkIX{)ky>{D01uTwGBH1ePqauZ`XkSlJm-pkkWWQFm3m}82ewrtMq3?f8v5Yopqp0F;2%B9K07M^0QKvIXysxF$;|@FW<05?<-Y8# zI~|0cD$?S{$fS^uz*U_)#x_&T6YR+l>|ECpbop7hUIjgL*>`*T(BQl37fV|ny0=R# zL8Ew75y1c&rFD4%f33Yy>@i+)BfW|s1gsa(HAMC^$Tt}YC#W?Z&2?dQx!cdHX`tb* z*!>PFtlEZ=j%H^*nI!)JD&)qMC#gBBFzEywxXG&IR^5j(Mzp%Fn5PPk!~FYIchz+4 zVQrY_)|fQW*URFRSnxhU`Bf;PHi+L-xBd$-D!X4fZ<5^d>g-W_iB9; zrlyiQlm34?>ZH=5h>&Gdk4k8>gS(T8pu0KYrJ(2urwutz8*8FSZ5JfupREYih}kO5 z?TU5_m=Wpf0pL`tVY@g|euBCWwb_hf+o!!a>HwdTB~eb@EKa+aX{RwC>i+;t{{Tvv zG?@-QLNV=H;`VtC5mbEuq-kysEI|CIwTE(*zlW}-Y$QdqS_|OI(77msKCClQeX`*= z^R|y~{2sg7F+W-?*uG}HkAN@kzUl*xyz}}~o4`7ZcWV)Zw(q(?&1S`}Tw6Zq z=XE2vV_iRkJ|oMd>h|XRo10@G^EYxr=aMt++OukFW?ENTJxW@K!<|Ob)+vM}ha_XT z`ii3$f$!xbFt^m#qH2E_EGCv!fj1yyw;*yW9#4v;+X-&Jh^n-$sFWph7Te*y{LBSwXMDq-^xJ=$8(JT0QIT=0PwW0IN1mT(yT-AqgA$CM};1o0a?Ch)f)RL2|vPl ztk9X5BXu44{3{mc!wG8JHxtiVv8en^)%B@QnJ5|Tzss#=U20RX4D3suwL0wCE}WL6 zlIcqvcR_NXnQep7l!VQs>tPt!S0^CF!Bb>pByOLq*E-pK6~=vRyGj zW0!nxKGmmrFmQ{<9Gaw(nHcu~Q}PH`=b%$fUm*0WQEx;`({mqKus>~P91NV*Nio=TtCthK!=9iMP&5P% zb6qiuOHvyvsS0vMN~AI1Q)PhdBm?~FH4Lf-DY+=_X%ybYggcKFH|18hc!Ya}r1toE|vH=RTdisvSpN z4xUj~+zv64f1PBf7VMtIygZ{6-%|>BQ72$dKx)pO*Awp_boTFB7x>+U>fE1URLtH> z0(r-+YMu2)GF-0A(6Q44NdR$@%nm&UYuzBzG-zQ%D#U;&IcyA%#8-)FUL}W5xD&i$ zW*HbgE2**Za%-ThhifX3soNEl;@j6^)`;|rJ6{h=bB2}})Qo}#0s0(PhNWqtSX*yd zA}UA9K^Y&Vc`S0xH}4|>6am2`e=60y-r7C8zuqhbVBzLhELznaI_sJhwAhz!PfwSv zI^$l^geB2mX*>|P2ivW4em1qXf=xfji9-m3J`d|zo*~t&pTlsfzR8igFIpoQwwrsE zB$d)i=-8Xbnq)E&_F~vL0FpnIL_BRA8{#0ms&_*evndNHBM{ zahloFiqRR}BzJc{H5U6%mmb1EBm@36E}!FwF7*VrWdN}XFgw?oJXZ!LSs0L5eFa;v zcGIt6xkd9Ng~MRFdD2u2cC3g|qnn#9N(7ETHrne-uj&aBYim9l{92QKYj1DR}UCZ`~t0k$=T$8zfE)7{n z-lzQW#buvjUz|YH4OCv=dS$SdWS^^-5%bmv<$?H`Z5`;Sp zWA&!z7OV|8vHE1+ZJdLGs-S>61I2UthDNJ5mN0T_e?o{v&NKD*ta$3$^eiaxvdzr7?gDxs}|31eJYn=q#mcGT}d>xMr!-quV)Kd zJcNuX11H+7O>;Sb7*WXUkTY9TCA3h6+@>-BJoPoBp+^2%E(X>I@fALo;plCyBA)It`}x7> zYSmS%xHg)yT9mCk8FO)@3xZWuRI;{l_*QV!V=$+jxqNej*1NwH+B<2nrHLtMz!nO4 z&2dXwnkbdon`!#g0Ukex6m`ZqTIM`MZkDWp0T~?ToF?TfGIg5j-lKOQFvnh=)gv}oA9|JG!vY5ey0zFeO6;PHT24<|ZO881jQiEO;1P|(f;p-a z$YgSA-iS+Aa_*0-K9zqQ>&^}sdXDwdOX4eO?pfm9vBnr@n&OE4Do8RNcAT7w#jBbo z?PIyLSGY$o&cg#cN#s#`aWq;b!pk9G*aI}49lo^_p_z@O8i0<|<4haO>K>Q`7C98?_^- zKdpK-$Bn1D(2~ksz^d)eI6sYLRy@r#j*1ONO}ApEldRk$-ne|T0vkQWaypcf%Qu*Z z4jXVIv90YcL{BsRnxQ{KSZ3j1owsrcJW@){v|QShQaUX+$Bz^V9P#dT7$*ao*MrA4 z(hr$-dV(=rMyGESO&pP&ha0g`7d~DB;QLeMQdhChcO4H)w(*68hXmmJSE=}WT!JqL zO)3z|>T~OiSIl;ejl&qtdUlE8$P>Z_$hOH;V+X&bMCsXRIJGCJ+iAYls6INE;=Kay zDDEv`&N3GSSD1KW`5Ho>3ZlEJ#nF&HERk6PqBT`!TR9Ctj5*#6acfCPd) z>zMK7=zTc|{wi>HXwIcMMmKDur0bT}_Mu$hjUYy;0(r8IDm;HQifk)`B61&m7{shs4)R`oxDk1Hlza z*Il}`W(Gn=ax>Eva)W6xwv%VL{{U@T#(wzxYQk$4(sX4x_pc9=#5WS)mf-PCw($+r zcRpEWWX~SBtkpKb$t_Q$EvB)$(xomCy5xbtKVB=$Z|~RbGB|vk;~A(lh;<*Zt>ZKO zotLgoD;4GQE>2G@-`2X7H65MEjFp>4kM^95FiFOH)0bCtQoWn=t1$sMCdb+73bt)5 zj0YTZ(y{g{%v79XrYZ8PT@RVHbZg#tk9M9DCpg0jeY z4^z+KO(gae$rkq~wOY2g`Lb&k2Hhq2 zVO^fvU`(8_ zXvsZANv7p)bU=;SvUGS~3)pe^&VEYTW`MThB@5NkI-HCT=L$&xj zsM~ly;sgniL5*Bftp9PI8*~8X&$F4dVdM8 zWAHcZeWCno_+Fazo~1N?Acfv1x4;TW0Im*32?QMU&m3bl@z=*MhT6}=omkjv0%wXQ zG3HWtDd&PQz~j(=N~H#>H1}VZk(yVXmgEKKb`_}J0o&4MKMzFZ)KxOO$b>%(TDAp6)o zed{tD`qhQFyF3}PJ?bLEidAnzOXM|v<&?a-jN~$(!l#d1y@2QCKH04CAsl}y zP5Zb#sIu%Ob=2A(ER+6PA>+}%p%nAt%*IijutqvpK2UNydQ?(I2*~S8mMz}JU80Vm zn~R5W$UW7#k2oOX*0z#PHfC>A)5fo+I{fy) z`_m8hE!QPHv-PU}7&iK=G;+iN9OPF;CxvXBXBar*xZR@dscE;+!IMeS7;z|4dxKRZ z@a2nr%Yc6x+?THAC`qf=@VAAYi$>Hd~g5Vmg{vKn%ky^Io@P*jv zASR)I3}3^@JHCdio6Ma%7;oYEknmNiw3pLlwRJqMK8B%^7VbJ%Q5}_}rwuzH_aFUgkoaca;ACAd=}(^9g_+2CEDa%u z`gN|jcsZDSv!i}EsNcbtAbhT;@uQu04NgzXBegEmo`$+c@P+8-83)psd?kI(6OV4R zbFSh!&@=utgptXu*?cj6oOx#-okaQt;&p`M^QM}5TnvJG8bgfYwI4{i1LlN}!mAq@ zIvB__(_IG2e087$^fh6voDcP8n$WoZ0x8@?0|4?f`qJQfa%xi<9PK}qN3{AHdw~-H zjz%jsgf;OKj+q*e(3)IJ9DdyM1a{`Vv1+!lIA; zSo+kj)9|RH{{U7#w8V$6-W2@c3XM97kn}%FQrv8ne)0NK$8W7Qf2BBf`qLR4zjc1J z)6?*$OZQjnO+7yflma*Ux=-PyZ}oJG(yf6ke{!ALnm*{K{{RgkAs6nSr7-nxr9b#h zF!gFX108SuDE=?Th(DzRuj4?(U-hD<{vlB(`q5I3t4WD&N=|Tat)7SC-lG{wnxcK^2yI;Hhed?rrD&4o; zALrJZ*b%W|_mAWJjdXTD+cmW88l_%tEFiHX zX*`_s#d;;zLG&Neyx+&C{URa$&-hmKcSKb94m)a)cpVS*so;qV9H{Tx>roD$g;=)x z$Nc)&q>f_PjRLL|b*F_NHy!CaEhzh~^{E#|45QQdb4^tT1djCP{{Y!&kGIx@44q*{ zd=Zh4@u}mtiBO$`KmNLoKfm}@h5rClYJ`R)DbF6&ucc`=*BLH^kfimjwEnfwXoKZ% z_kY5*Nm-L2$)s9_{{UB<9_E{SZxyos?j#?DUQWM_RMmJ>^#1@F=bnvNW@Ww8M1{hp z?DZ>}ZQikTz>hNk`V3TQ&AIyj0F73-IBIEXTT0sp+NEx?H|I`8GaNIqA6kJ&IIA|F zfAap7q9yK3x@>F#C5=^{B#7+`TB-L(xF6E9Zod6Xc6LOfkX_G=@kxQaFTFiK*3(D& z<@(hll@t$r|%!avTd8eCbb8-EB^r3q!$tqjyb4h z2Q=^FALr7g>}?dA)fX+{c^nem#%ps=(}c2(&TA^na=(Z4u8&Lq0AKl6C3!ToeGg*; zPBMC~rnSUY)(}SiUi06d&bdq044*H{^Bh-Ct^WYWq<@WavvU~#0DJn^9UHw_=wnih zY?Y182Sr(5b2-QW)w|ndf?d0dVyx%`mj3{BR2r}ReK+A&8DvY|EXuRc!r_27Ks~&&7LQ7$zYTEh@yQFLr*!HJA z?V&l~)*JQwNA#p$yP;js6e`5ycR@)7sF6!M;Pu5?)jGgG*+0&iAAiDyswKM% zY6>KgpPWB@WPyV~b&W_|oR50FrYI%OJO2QUIh1d2lzrp%${JWphvE<{Yzw@e>0B`>QfYoUCMERXC@sv!z@#>_HT9PjvB1#+kJ{HIK zx>p_iRAY=(xJFx|l-gPziFfgOd32d96Hm8|Bw%u*kIV9|F85F}5c@*0>yQ5cT~Uok znbV-Fmf$u?=Zei!j9YPAnmI~ICiFwBB9i{)BOP6WjQ6hh#=j5Z@ZN)MKbW#hE&%{? z4l;Y!9bu7jjJz;81a__4yUFh*M4nfbM?l9or1W>}ON(mvITB$|%g|FU z7=#~Md@2}Z){YByK4*4_FCuwRyt5Z&TnwJxmCWjPj@bE*-%9Fr6Xn^+!-5ADiJvwA zTa(RYFLkDccHPX~F_I}6TaS9&7V=5+E0Q{ZD-F~vg@EJ(TGkixeVnlaf=BcJ01C=& zrkV>3p3BM`+0QMKy{Xd3yEcLH_DcapW>K;=~9rBt}Ncone35ra>aw3}ksxNc3l z$P{NMC)cHAJfYZt2=DmR2JTj1OXRm&V9K#4p4GC8Ta=4YKPO?Qmm5xV>r~QH89o01 z8nlNu3&ugmAB8582er+OJ}g`Oz!vHO{3`r+*B(nm#F)s&52ZD56#{^~3V`ksfxC*r z_ici0Y|Q)X1$&|8CP_9xP4I8TD?JB77LzFs2_S*(-nnbN zWhc3kKuF%jtCqNsqlNUPisZ8$#OAW(TC%yG$mo|)^QAC>$sJ8cEDW*z>}T<;jm%^P z8x8ADxVpGZA%Cwm4h_4aUEZwfJUp=rX*|zfwanknuc};Z83d3jOPi?{&N8Hp-ErQr zOP?^eeAh%@yP&Pm*ow;GV$YOt2UF=#$tysN2*Et&h_0pkF$K;s>r|w;E}>68^yJ%G ziq>}~H;&4=bGNX~YH9lA$as=Bz)}a+x%}mE)})GOhXqb6Clw`Tin~2F+r_Q~er!Td zFR2|(XeW=Y44|8Egm=&QSC>Jo%LrgVZNtB6iLRbPPrhrP*rx8$dLKgBy}qlZo7P40 zm}GPPYX;|8)6bm}4bs4c!0n9S*CC_)Nc#_mf3rjOy@Pq9cEZAeAH}iQmirC zH4&k!Om|jH?5@0UR?2u%AW-KA(AOJ3iS9S&gPN~(sx`uu01WlO!K~H;In8y#Dl0;D z>URDgzn51@0)R>`Oq&xuYDjtcmQx}{)IBZZs_C;HhHBe z`>Rb!O76_)B=IDu6c|${js-uT6QoRrJu4q8a+i}iuUa#ZN18SpC%yAZhi7G(;f30#BIt2Ebv=Lp#P%{C~ z)K$j1sTA%G4lSk7=M9XC4iug}DroH*EHUGZ*V0maGScmI=DIQD`6Z4z4l~}oD^`-! z%+aSsB~<%XajVawL1vZNP9_mvDI?@;Zu(ZtO7Y3I0z;?_R3E-QNBC818)$Ckgp#Bv z0fFyZ*SawCW1iMPj-!Fp5l&j6#qzy`pHrI2vZsQfho&jg_>9SEmry|j5$js9SX*nl za8Onowc(JEKD~O@UG9vKTD)zAbO#*Q5^=qn)a$!zLyx<=G2FC~4WBkge13Ie9Y$-i zt1jL_cIbQc=@o*!RK{)G=rBlCw<0bhjdCyv_t24uT z$_c`rKcy|csQ&=!Uo9CJAo^8XOQ3Rn2`!w?6c+ou4D-co>DTs_sPSCDB*`Q;Ijl3b z83{vxdJ2%SZb{_O)yZz_Q+v+SWRXY)A>W$Pi%PJWL5lT8doji@O88Eci}*QI$>R}Z;{@-xp`ifhS(f2=(F(sb_iX3(hOteNc= znv7)vEy6G!;-~vzSHLRA864M(T3^bNuE`totwF5asRT^J9`z29e%@VARQ}MBiB^nM zc6Ut#%uHu%XYj8(wDCpiTt*sAz_153b5Zf7#hvRS?J3%`be_hG5hK2zP>x2yjfObR zE0^)L(DEQm{H>5HDj$e)M&CRt+3I>$KZ*QGHIFUTN^R#E$fpWzJA~_V#ik~U6n2;8 zrv>3BoOZ6d-L$80(YD4OyjKezryHm}`Wkq?idGw64N)t%c5v)<_dXlb?u z2OmmAzqo+qCx6bQ{@T3nI~@9&IiGNLJIy~(7QPQQ5}{d@aqZVN=hC@XIQ6cFPHW5i zSvYA|02~a~HO`+bQjk%HJ;AQ1r5)Jf(^f+tQkmClImS9?ii7PWNUTXXJdDz`C%C3H zvoX@gkQR>h=P(qKV$=%=y#DZLfQF{79=ZIB#~#+IL{P<>FisXe8k4#ntp+)zKyHFbdR;7VA=f-=~;~KG0CQb zki^s0+78xtKF|H1e{6d%ge`WhZw<|*`=Pml<=Qu0nE=MaDn9Y*4tVK`@-N!6gnsh;J?<5Y{IoJRg1Jv=?rF{EyIop880M%(F-ncyeKmB^RNyWDHZ~FeNp|3kp zYq;pA5K2ksHP(18D79T`+9};5ftwGK2Oaq}#6C-U)vI{rz$u)L;MB7wv_6H@J{0J1 zUdAnT80SdD~ywBm6+M8K~vojSS;f`zQ zOKYOG;YrEIuK?FIX(gf*AfC6>=6p?I1*VYdv54?`jw``DO1pJAMHY30U4dMKS?LH9lqZhWa7GmlKUyteHf3C&-Y}~=X~PrOy;rzgJ7oS9K9|ggw`$ew zCUqJvttOQ}nm%EUN3C?0pAfD3OY;3|l!Do%g~02N(y=aL*)|VDSU4#?MxBpSkH;2I z{9t>NQ^(^gBFFcu&%Jp(WmI*@Kcz7IfRJf>D3+z&9?cJlEG`BF>JO%BbE!sfQdD4j z16~^)%UVvda#S8yJ!`VkwGATmSLc!!#~`sFJlR^bnR zywqK%strh%?*0*$g=F^?47$AO%i0n<{{WoUq<3-2(bb36oLzJGK?l>)j=G`i(8d1T zwSSgaSw5>sZ#38Iw?5*wUMQpl7}xR@R&6TEafXdoKj+qquQF|Su|pbU-3ko)(w#QO zQO3eQ3Us~~w0;on`WmkO6$_s(b{~y9u8oV8b{jW_tfk3Ok4ze;KZD|KRfyw^)X$)5 z5Ddj~%}o0~vj;+4RqD24vg&1Ccrlqs45yC#e=6a%ixXjVQHbF7>0X6-sA|^gcU$fN ze{|OY;tQ9#xMz`eHhCWPwL9L;g0-1a+H?}(kXH0sS(YHn3vDAj3g%!@ymcJcqj+ms zf*U1{LxKR#Mr%p7mC+45q;t)t6(7V0(yf?V-n#LBr%pKtexntVJcHBSHh$BqL2Mp`qZ;@Z=k4`?tU~04o;$^JrB~O zPNJp$6ZE1(WUKut!?)I&ztWxk->m?M=IFG>=~7GDqp4pI#BaJO2T#VF-*i%c2>R9a z0qOTeOZ+twebG|?01aj&C-DAM#{MtDo<8CHDTkv~^gzc!{V6;CCYBGnew2&X^Ppf4 z?`mWBhtSk3{;<@;@BRjf0^E7~f`;!-f4V;!Yj>o#7g5pmALq4MU%UQ&>a=}N*ZNh3 z=tueQM20wYH7tL4f1g^2bTur0czr2_$)BjG{{V!2YDqfOE%!&(vqcCxQnyd@??^gQ zp1r3fE0kqT9eQDI+;vZUlKy010YA?@u} zt$MXqPJ`$_rCGMq-6EQTGqlq+J*>;CAXF020c z_)vkCqx<;ws|)_C{Q3&7ANtS0e@d{w>NEa&3brMMv+=6~{yp>Vf03&5{r>>R{A!lwpJfpry;g00_u@a8s>knDn_s@aQT)Yhwlg#% z`-}7+$W~?7-u$agKH&O)L0NZSfAXyF?+F!S{{UEil+^zKT)$d;f9ol!{Z5p@2C65qks7Om*^?Sak8R+u0L9tx?|d*kNtV6kKOz#nJ8?Z>Wx(H z)oxGqMyejQl(#x*?8s#5Y2)t?!kXWF)5qQwJq@C1S$*MM7Loq8x$8f^E27dyW&CT0 z5x2~sA^WL(qKi0i=FNpK{5x3nG z^UzZp?vL~9QZ-xLOszlNszbG1xck57+NqDaC|fZJKl+39tcm{ct$DxHAFudUME?MI zP>MkP$NNM2P@j60{_*;c=}lVSq9Ja}HoQdt08vQ2X>GsNH`amDqp2q8$j}G73dDcE z_*UecXed3!Vv+mG=TBC3bV6Ko{3<(7zCN`3uJp?>^Zqrf+~<3jwNJkY{{Un9(CN|;`=9Gg(>~*UX~(Jh)0W}Th5G&#QseJ_ z6>d(T`&Bvr0M`BjoIwyTT2s^U6pPlE{Ad?KMEm6XRjXcy=~WZ&AEjKi`>pi<01Am3 zqV0(N`21^J{{Y9z{vXb=tbf;!#P ztC~mrdt4v(NAnerb0xT!KX83&NPg$zQC##NjY|(t#=3e3at&XL2|ksKpYiTKhPHKs z{(&dbv6uZo`Wok{&}hz+t9=b!u>Sy$6aMr605eoy?=@bq{{W9K#lM-Xr_i%AQ2zjr z<@(io{$qc``BeA+0P+!EsHks#<9-#adKt4c{q(0df2|?^09ur--nA)iH~5WRi~c@P zf1Z^}uEWjJR&v}r9Wd&@K~O*X=K2bpbzh+VRRjM3ta`z91+g4I@$l8DVgCRhQTr+W zRh0+&_BE?x{{SCr{^|Zy`j)px38`~5zwaOCTOSX{{7(@2DgITDsGr_R?#KDo*Tdoe z0286?r}otyeZG%<=5~vK=oP}2s)poFZ{C~T0zwPC|~tUM?e+3eJSIp`WjP;ojRs3mT>VA|@)o6PO6;Iw2AG=6?@TG6MC__5G0uS_?ulnNu0Lrs2 z$u4v0TAl^sTeJTFOkep{RoD8%^saUBvtP6K{)CVLuzd|C2k$!{sBU(^PZ z8vUwFGy^>K;Yal~a`hlzW8@5I)O#9;mv5{g;~@BA0y87?=3HgXbsP`c`JG&Z8;!H7YA{r)ZZ(sAO-r2sJge^2sPdgyi-W zl?7;MpYjt=$N5nodPxbkPkkgK>yfRplf3x8^Kkf?r<^8Wt{{R>GzdYjh zY-hLcnyZ;PJvUAL*8c!AEIbscMyk5zNww1LT=JjU{{X|0_@?UP!`HS3^4n8&g$NjA zkg||e=YxgDLE}G-Me!^4VDYEJd3Nf0aUk??Fc<;L0!}fWI_KA+$9wxP!XL!G7#QWX z5PuwuAI`q~{iflHUj@06GkD!kgEMghP8bAg)RZ$9Dr)(te;xzQJOb1r|F z;P$2>pzBgx{rX~ow=|p3vBDH?javFV0Lz6VlUXPS99K!wKkSBAjeV7M{t{tYE3+OQxC0F zleVCC{v5U!CRI4j(~7fi;GyChX`0{tY)1?LsJt+ItAoeiQ}}URAA+^F(6#A+Uoj;6 z%6b~*ZL8Rqww=#5)_gU6q)0@O5Tl{RW4Vwi1$eKfbvx}ZT!^K`h`|RW0iK7r`d5+o zufw+5W#bsRjfvonl`yK?vp0n|H0*iAq1<5f?MrVf+qP8Uf$N&xxv_{1pm1|p7dHMw zV>zy*)RB`-Xw98FT~bvgJAK7&#>p~<4Ul~+fzLcu`wdomGsA9QLsH?@2B_^;dE&>F zGmLr~u$tDP8sJP=dyLjbnX0Ye!TDaaxFWN0?iXfesT|samXVH6VbZar(6P?q1$9pz zMsdYZn(1PgDeuSQO*Vq{@YA{REMIer#iuwJn%l+J`_BAcOsdH|?tj;mpaje}Q%o{!aR7p^R zL9Zh5-J-#$F;FmTU&DSNw!eT!HSCu2^cg%F#qo+V+ziex7qg0V>-A);GqI(oE`3U0d?#V(oQu^~ZuOoF6O{ zK9$C-N`H^_uUkz-l?TZ@bp%(QYJL^Gv60@{8s^(SE&(zq;{buw^VDaia4Km@>ZHP6 GNB`MX77$Ya literal 0 HcmV?d00001 diff --git a/tests/models/imgs/dog.jpg b/tests/imgs/dog.jpg similarity index 100% rename from tests/models/imgs/dog.jpg rename to tests/imgs/dog.jpg diff --git a/tests/models/imgs/helmet.jpeg b/tests/imgs/helmet.jpeg similarity index 100% rename from tests/models/imgs/helmet.jpeg rename to tests/imgs/helmet.jpeg diff --git a/tests/models/test_weights.py b/tests/models/test_weights.py index 7687dac..c22c173 100644 --- a/tests/models/test_weights.py +++ b/tests/models/test_weights.py @@ -18,8 +18,8 @@ # tests are made to be run from root project directory # format "imagenet_image_class: PIL Image" IMGS = { - 560: Image.open("tests/models/imgs/helmet.jpeg"), - 207: Image.open("tests/models/imgs/dog.jpg"), + 560: Image.open("tests/imgs/helmet.jpeg"), + 207: Image.open("tests/imgs/dog.jpg"), } # временная заглушка. TODO: убрать @@ -75,4 +75,4 @@ def test_output_mean(arch): inp = torch.ones(1, 3, 256, 256) with torch.no_grad(): out = m(inp).mean().numpy() - assert np.allclose(out, MODEL_MEAN[arch], rtol=1e-4, atol=1e-4) \ No newline at end of file + assert np.allclose(out, MODEL_MEAN[arch], rtol=1e-4, atol=1e-4) From df132ff5dba6911a2c017ab5565a66a9ea433dda Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 21 May 2020 16:54:32 +0300 Subject: [PATCH 31/54] increase version to use torchvision nms --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3a84f92..39dc412 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -torch>=1.1 +torch>=1.4 inplace-abn \ No newline at end of file From ba8e0399bfba2755f87c743d38cb6d1d1e899876 Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 21 May 2020 18:07:52 +0300 Subject: [PATCH 32/54] minor update to docs --- pytorch_tools/detection_models/efficientdet.py | 1 + pytorch_tools/utils/box.py | 8 ++++++++ pytorch_tools/utils/visualization.py | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index dbd617b..cefd7a6 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -115,6 +115,7 @@ def forward(self, x): class_outputs.append(cls_feat.contiguous().view(cls_feat.shape[0], -1, self.num_classes)) # TODO: return back to simplier transpose operations + # This transpose + view aligns with `generate_anchor_boxes` function # class_outputs.append(cls_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, self.num_classes)) # box_outputs.append(box_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, 4)) diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index 92affe9..f66f96f 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -42,6 +42,14 @@ def box_area(box): """ return (box[:, 2] - box[:, 0]) * (box[:, 3] - box[:, 1]) +def clip_bboxes(bboxes, size): + """Args: + bboxes (torch.Tensor): in `ltrb` format. Shape [N, 4] + size (Union[torch.Size, tuple]): (H, W)""" + bboxes[:, 0::2] = bboxes[:, 0::2].clamp(0, size[1]) + bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, size[0]) + return bboxes + # implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py # with slight modifications def box_iou(boxes1, boxes2): diff --git a/pytorch_tools/utils/visualization.py b/pytorch_tools/utils/visualization.py index 761554b..592bec3 100644 --- a/pytorch_tools/utils/visualization.py +++ b/pytorch_tools/utils/visualization.py @@ -4,7 +4,8 @@ import numpy as np -def tensor_from_rgb_image(image: np.ndarray) -> torch.Tensor: +def tensor_from_rgb_image(image): + """Args: image (np.array): Input image in HxWxC format""" image = np.moveaxis(image, -1, 0) image = np.ascontiguousarray(image) image = torch.from_numpy(image) From be17775a32c210935eeef5a9b4d49d05f2db0a9b Mon Sep 17 00:00:00 2001 From: bonlime Date: Thu, 21 May 2020 20:39:50 +0300 Subject: [PATCH 33/54] add bit-M-ResNet (#67) Co-authored-by: zakajd --- pytorch_tools/models/__init__.py | 7 + pytorch_tools/models/bit_resnet.py | 310 +++++++++++++++++++++++++++++ pytorch_tools/models/resnet.py | 2 +- 3 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 pytorch_tools/models/bit_resnet.py diff --git a/pytorch_tools/models/__init__.py b/pytorch_tools/models/__init__.py index 3f98c2d..ef9c0d4 100644 --- a/pytorch_tools/models/__init__.py +++ b/pytorch_tools/models/__init__.py @@ -45,3 +45,10 @@ from .hrnet import hrnet_w44 from .hrnet import hrnet_w48 from .hrnet import hrnet_w64 + +from .bit_resnet import bit_m_50x1 +from .bit_resnet import bit_m_50x3 +from .bit_resnet import bit_m_101x1 +from .bit_resnet import bit_m_101x3 +from .bit_resnet import bit_m_152x2 +from .bit_resnet import bit_m_152x4 \ No newline at end of file diff --git a/pytorch_tools/models/bit_resnet.py b/pytorch_tools/models/bit_resnet.py new file mode 100644 index 0000000..1a12f55 --- /dev/null +++ b/pytorch_tools/models/bit_resnet.py @@ -0,0 +1,310 @@ +# Copyright 2020 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Bottleneck ResNet v2 with GroupNorm and Weight Standardization.""" +import os +import numpy as np +from copy import deepcopy +from functools import wraps +from urllib.parse import urlparse +from collections import OrderedDict # pylint: disable=g-importing-member + +import torch +import torch.nn as nn +import torch.nn.functional as F + +from pytorch_tools.modules.weight_standartization import WS_Conv2d as StdConv2d + + +def conv3x3(cin, cout, stride=1, groups=1, bias=False): + return StdConv2d(cin, cout, kernel_size=3, stride=stride, + padding=1, bias=bias, groups=groups) + + +def conv1x1(cin, cout, stride=1, bias=False): + return StdConv2d(cin, cout, kernel_size=1, stride=stride, + padding=0, bias=bias) + + +def tf2th(conv_weights): + """Possibly convert HWIO to OIHW.""" + if conv_weights.ndim == 4: + conv_weights = conv_weights.transpose([3, 2, 0, 1]) + return torch.from_numpy(conv_weights) + + +class PreActBottleneck(nn.Module): + """Pre-activation (v2) bottleneck block. + + Follows the implementation of "Identity Mappings in Deep Residual Networks": + https://github.com/KaimingHe/resnet-1k-layers/blob/master/resnet-pre-act.lua + + Except it puts the stride on 3x3 conv when available. + """ + + def __init__(self, cin, cout=None, cmid=None, stride=1): + super().__init__() + cout = cout or cin + cmid = cmid or cout//4 + + self.gn1 = nn.GroupNorm(32, cin) + self.conv1 = conv1x1(cin, cmid) + self.gn2 = nn.GroupNorm(32, cmid) + self.conv2 = conv3x3(cmid, cmid, stride) # Original code has it on conv1!! + self.gn3 = nn.GroupNorm(32, cmid) + self.conv3 = conv1x1(cmid, cout) + self.relu = nn.ReLU(inplace=True) + + if (stride != 1 or cin != cout): + # Projection also with pre-activation according to paper. + self.downsample = conv1x1(cin, cout, stride) + + def forward(self, x): + out = self.relu(self.gn1(x)) + + # Residual branch + residual = x + if hasattr(self, 'downsample'): + residual = self.downsample(out) + + # Unit's branch + out = self.conv1(out) + out = self.conv2(self.relu(self.gn2(out))) + out = self.conv3(self.relu(self.gn3(out))) + + return out + residual + + def load_from(self, weights, prefix=''): + convname = 'standardized_conv2d' + with torch.no_grad(): + self.conv1.weight.copy_(tf2th(weights[f'{prefix}a/{convname}/kernel'])) + self.conv2.weight.copy_(tf2th(weights[f'{prefix}b/{convname}/kernel'])) + self.conv3.weight.copy_(tf2th(weights[f'{prefix}c/{convname}/kernel'])) + self.gn1.weight.copy_(tf2th(weights[f'{prefix}a/group_norm/gamma'])) + self.gn2.weight.copy_(tf2th(weights[f'{prefix}b/group_norm/gamma'])) + self.gn3.weight.copy_(tf2th(weights[f'{prefix}c/group_norm/gamma'])) + self.gn1.bias.copy_(tf2th(weights[f'{prefix}a/group_norm/beta'])) + self.gn2.bias.copy_(tf2th(weights[f'{prefix}b/group_norm/beta'])) + self.gn3.bias.copy_(tf2th(weights[f'{prefix}c/group_norm/beta'])) + if hasattr(self, 'downsample'): + w = weights[f'{prefix}a/proj/{convname}/kernel'] + self.downsample.weight.copy_(tf2th(w)) + +# this models are designed for trasfer learning only! not for training from scratch +class ResNetV2(nn.Module): + """ + Implementation of Pre-activation (v2) ResNet mode. + Used to create Bit-M-50/101/152x1/2/3/4 models + + Args: + num_classes (int): Number of classification classes. Defaults to 5 + """ + + def __init__( + self, + block_units, + width_factor, + # in_channels=3, # TODO: add later + num_classes=5, # just a random number + # encoder=False, # TODO: add later + ): + super().__init__() + wf = width_factor # shortcut 'cause we'll use it a lot. + + # The following will be unreadable if we split lines. + # pylint: disable=line-too-long + self.root = nn.Sequential(OrderedDict([ + ('conv', StdConv2d(3, 64*wf, kernel_size=7, stride=2, padding=3, bias=False)), + ('pad', nn.ConstantPad2d(1, 0)), + ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=0)), + # The following is subtly not the same! + # ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)), + ])) + + self.body = nn.Sequential(OrderedDict([ + ('block1', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=64*wf, cout=256*wf, cmid=64*wf))] + + [(f'unit{i:02d}', PreActBottleneck(cin=256*wf, cout=256*wf, cmid=64*wf)) for i in range(2, block_units[0] + 1)], + ))), + ('block2', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=256*wf, cout=512*wf, cmid=128*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=512*wf, cout=512*wf, cmid=128*wf)) for i in range(2, block_units[1] + 1)], + ))), + ('block3', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=512*wf, cout=1024*wf, cmid=256*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=1024*wf, cout=1024*wf, cmid=256*wf)) for i in range(2, block_units[2] + 1)], + ))), + ('block4', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=1024*wf, cout=2048*wf, cmid=512*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=2048*wf, cout=2048*wf, cmid=512*wf)) for i in range(2, block_units[3] + 1)], + ))), + ])) + # pylint: enable=line-too-long + + self.head = nn.Sequential(OrderedDict([ + ('gn', nn.GroupNorm(32, 2048*wf)), + ('relu', nn.ReLU(inplace=True)), + ('avg', nn.AdaptiveAvgPool2d(output_size=1)), + ('conv', nn.Conv2d(2048*wf, num_classes, kernel_size=1, bias=True)), + ])) + + def features(self, x): + return self.body(self.root(x)) + + def logits(self, x): + return self.head(x) + + def forward(self, x): + x = self.logits(self.features(x)) + assert x.shape[-2:] == (1, 1) # We should have no spatial shape left. + return x[...,0,0] + + def load_from(self, weights, prefix='resnet/'): + with torch.no_grad(): + self.root.conv.weight.copy_(tf2th(weights[f'{prefix}root_block/standardized_conv2d/kernel'])) # pylint: disable=line-too-long + self.head.gn.weight.copy_(tf2th(weights[f'{prefix}group_norm/gamma'])) + self.head.gn.bias.copy_(tf2th(weights[f'{prefix}group_norm/beta'])) + # always zero_head + nn.init.zeros_(self.head.conv.weight) + nn.init.zeros_(self.head.conv.bias) + + for bname, block in self.body.named_children(): + for uname, unit in block.named_children(): + unit.load_from(weights, prefix=f'{prefix}{bname}/{uname}/') + + + + +KNOWN_MODELS = OrderedDict([ + ('BiT-M-R50x1', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), + ('BiT-M-R50x3', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), + ('BiT-M-R101x1', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), + ('BiT-M-R101x3', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), + ('BiT-M-R152x2', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), + ('BiT-M-R152x4', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), + + ('BiT-S-R50x1', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), + ('BiT-S-R50x3', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), + ('BiT-S-R101x1', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), + ('BiT-S-R101x3', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), + ('BiT-S-R152x2', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), + ('BiT-S-R152x4', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), +]) + + +PRETRAIN_SETTINGS = { + "input_space": "RGB", + "input_size": [3, 448, 448], + "input_range": [0, 1], + "mean": [0.5, 0.5, 0.5], + "std": [0.5, 0.5, 0.5], + "num_classes": None, +} + +# fmt: off +CFGS = { + # weights are loaded by default + "bit_m_50x1": { + "default": { + "params": {"block_units": [3, 4, 6, 3], "width_factor": 1}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x1.npz", + **PRETRAIN_SETTINGS + }, + }, + "bit_m_50x3": { + "default": { + "params": {"block_units": [3, 4, 6, 3], "width_factor": 3}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x3.npz", + **PRETRAIN_SETTINGS, + }, + }, + "bit_m_101x1": { + "default": { + "params": {"block_units": [3, 4, 23, 3], "width_factor": 1}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x1.npz", + **PPRETRAIN_SETTINGS, + }, + }, + "bit_m_101x3": { + "default": { + "params": {"block_units": [3, 4, 23, 3], "width_factor": 3}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x3.npz", + **PPRETRAIN_SETTINGS, + }, + }, + "bit_m_152x2": { + "default": { + "params": {"block_units": [3, 8, 36, 3], "width_factor": 2}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x2.npz", + **PPRETRAIN_SETTINGS, + }, + }, + "bit_m_152x4": { + "default": { + "params": {"block_units": [3, 8, 36, 3], "width_factor": 4}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x4.npz", + **PPRETRAIN_SETTINGS + }, + }, +} + +# fmt: on +def _bit_resnet(arch, pretrained=None, **kwargs): + cfgs = deepcopy(CFGS) + cfg_settings = cfgs[arch]["default"] + cfg_params = cfg_settings.pop("params") + cfg_url = cfg_settings.pop("url") + kwargs.pop("pretrained", None) + kwargs.update(cfg_params) + model = ResNetV2(**kwargs) + # load weights to torch checkpoints folder + try: + torch.hub.load_state_dict_from_url(cfg_url) + except RuntimeError: + pass # to avoid RuntimeError: Only one file(not dir) is allowed in the zipfile + filename = os.path.basename(urlparse(cfg_url).path) + torch_home = torch.hub._get_torch_home() + cached_file = os.path.join(torch_home, 'checkpoints', filename) + weights = np.load(cached_file) + model.load_from(weights) + return model + +# only want M versions of models for fine-tuning +@wraps(ResNetV2) +def bit_m_50x1(**kwargs): + return _bit_resnet("bit_m_50x1", **kwargs) + +@wraps(ResNetV2) +def bit_m_50x3(**kwargs): + return _bit_resnet("bit_m_50x3", **kwargs) + +@wraps(ResNetV2) +def bit_m_101x1(**kwargs): + return _bit_resnet("bit_m_101x1", **kwargs) + +@wraps(ResNetV2) +def bit_m_101x3(**kwargs): + return _bit_resnet("bit_m_101x3", **kwargs) + +@wraps(ResNetV2) +def bit_m_152x2(**kwargs): + return _bit_resnet("bit_m_152x2", **kwargs) + +@wraps(ResNetV2) +def bit_m_152x4(**kwargs): + return _bit_resnet("bit_m_152x4", **kwargs) + + + diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index 8743dea..0490cb6 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -12,7 +12,7 @@ import torch import torch.nn as nn -from torchvision.models.utils import load_state_dict_from_url +from torch.hub import load_state_dict_from_url from pytorch_tools.modules import BasicBlock, Bottleneck from pytorch_tools.modules import GlobalPool2d, BlurPool From b25b4f7a1195f884dcc3dd18d9a07e53d7612adb Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 21 May 2020 20:52:51 +0300 Subject: [PATCH 34/54] fix typo --- pytorch_tools/models/bit_resnet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pytorch_tools/models/bit_resnet.py b/pytorch_tools/models/bit_resnet.py index 1a12f55..3108e97 100644 --- a/pytorch_tools/models/bit_resnet.py +++ b/pytorch_tools/models/bit_resnet.py @@ -234,28 +234,28 @@ def load_from(self, weights, prefix='resnet/'): "default": { "params": {"block_units": [3, 4, 23, 3], "width_factor": 1}, "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x1.npz", - **PPRETRAIN_SETTINGS, + **PRETRAIN_SETTINGS, }, }, "bit_m_101x3": { "default": { "params": {"block_units": [3, 4, 23, 3], "width_factor": 3}, "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x3.npz", - **PPRETRAIN_SETTINGS, + **PRETRAIN_SETTINGS, }, }, "bit_m_152x2": { "default": { "params": {"block_units": [3, 8, 36, 3], "width_factor": 2}, "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x2.npz", - **PPRETRAIN_SETTINGS, + **PRETRAIN_SETTINGS, }, }, "bit_m_152x4": { "default": { "params": {"block_units": [3, 8, 36, 3], "width_factor": 4}, "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x4.npz", - **PPRETRAIN_SETTINGS + **PRETRAIN_SETTINGS }, }, } From 5ab7368575b47588625ebcf6f7bde0e511e82ec8 Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 22 May 2020 15:00:27 +0300 Subject: [PATCH 35/54] add accumulate batches --- pytorch_tools/fit_wrapper/wrapper.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pytorch_tools/fit_wrapper/wrapper.py b/pytorch_tools/fit_wrapper/wrapper.py index cb57c77..c769d6e 100644 --- a/pytorch_tools/fit_wrapper/wrapper.py +++ b/pytorch_tools/fit_wrapper/wrapper.py @@ -18,10 +18,11 @@ class Runner: must have `name` attribute. Defaults to None. callbacks (List): List of Callbacks to use. Defaults to ConsoleLogger(). gradient_clip_val (float): Gradient clipping value. 0 means no clip. Causes ~5% training slowdown + accumulate_steps (int): if > 1 uses gradient accumulation across iterations to simulate larger batch size """ def __init__( - self, model, optimizer, criterion, metrics=None, callbacks=ConsoleLogger(), gradient_clip_val=0 + self, model, optimizer, criterion, metrics=None, callbacks=ConsoleLogger(), gradient_clip_val=0, accumulate_steps=1, ): super().__init__() @@ -33,6 +34,7 @@ def __init__( self.callbacks = Callbacks(callbacks) self.callbacks.set_state(self.state) self.gradient_clip_val = gradient_clip_val + self.accumulate_steps = accumulate_steps def fit( self, train_loader, steps_per_epoch=None, val_loader=None, val_steps=None, epochs=1, start_epoch=0, @@ -77,13 +79,15 @@ def _make_step(self): output = self.state.model(data) self.state.output = output loss = self.state.criterion(output, target) + loss = loss / self.accumulate_steps if self.state.is_train: - self.state.optimizer.zero_grad() with amp.scale_loss(loss, self.state.optimizer) as scaled_loss: scaled_loss.backward() if self.gradient_clip_val > 0: - torch.nn.utils.clip_grad_norm_(self.state.model.parameters(), self.gradient_clip_val) - self.state.optimizer.step() + torch.nn.utils.clip_grad_norm_(amp.master_params(self.state.optimizer), self.gradient_clip_val) + if self.state.step % self.accumulate_steps == 0: + self.state.optimizer.step() + self.state.optimizer.zero_grad() torch.cuda.synchronize() # update metrics From 305fc4dd1f105e436e5a26bc31161dab56149578 Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 22 May 2020 15:19:00 +0300 Subject: [PATCH 36/54] add tests. move loss scale to preserve loss for logging --- pytorch_tools/fit_wrapper/wrapper.py | 3 +-- tests/fit_wrapper/test_runner.py | 40 +++++++++++++++++++--------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/pytorch_tools/fit_wrapper/wrapper.py b/pytorch_tools/fit_wrapper/wrapper.py index c769d6e..ddf3764 100644 --- a/pytorch_tools/fit_wrapper/wrapper.py +++ b/pytorch_tools/fit_wrapper/wrapper.py @@ -79,9 +79,8 @@ def _make_step(self): output = self.state.model(data) self.state.output = output loss = self.state.criterion(output, target) - loss = loss / self.accumulate_steps if self.state.is_train: - with amp.scale_loss(loss, self.state.optimizer) as scaled_loss: + with amp.scale_loss(loss / self.accumulate_steps, self.state.optimizer) as scaled_loss: scaled_loss.backward() if self.gradient_clip_val > 0: torch.nn.utils.clip_grad_norm_(amp.master_params(self.state.optimizer), self.gradient_clip_val) diff --git a/tests/fit_wrapper/test_runner.py b/tests/fit_wrapper/test_runner.py index 6dd56ca..29232a4 100644 --- a/tests/fit_wrapper/test_runner.py +++ b/tests/fit_wrapper/test_runner.py @@ -100,14 +100,35 @@ def test_val_loader(): runner.fit(TEST_LOADER, epochs=2, steps_per_epoch=100, val_loader=TEST_LOADER, val_steps=200) - def test_grad_clip_loader(): runner = Runner( model=TEST_MODEL, optimizer=TEST_OPTIMIZER, criterion=TEST_CRITERION, metrics=TEST_METRIC, - gradient_clip_val=1.0 + gradient_clip_val=1.0, + ) + runner.fit(TEST_LOADER, epochs=2) + + +def test_accumulate_steps(): + runner = Runner( + model=TEST_MODEL, + optimizer=TEST_OPTIMIZER, + criterion=TEST_CRITERION, + metrics=TEST_METRIC, + accumulate_steps=10, + ) + runner.fit(TEST_LOADER, epochs=2) + + +def test_ModelEma_callback(): + runner = Runner( + model=TEST_MODEL, + optimizer=TEST_OPTIMIZER, + criterion=TEST_CRITERION, + metrics=TEST_METRIC, + callbacks=pt_clb.ModelEma(TEST_MODEL), ) runner.fit(TEST_LOADER, epochs=2) @@ -123,9 +144,7 @@ def test_grad_clip_loader(): pt_clb.Timer(), pt_clb.ReduceLROnPlateau(), pt_clb.CheckpointSaver(TMP_PATH, save_name="model.chpn"), - pt_clb.CheckpointSaver( - TMP_PATH, save_name="model.chpn", monitor=TEST_METRIC.name, mode="max" - ), + pt_clb.CheckpointSaver(TMP_PATH, save_name="model.chpn", monitor=TEST_METRIC.name, mode="max"), pt_clb.TensorBoard(log_dir=TMP_PATH), pt_clb.TensorBoardWithCM(log_dir=TMP_PATH), pt_clb.ConsoleLogger(), @@ -133,7 +152,6 @@ def test_grad_clip_loader(): pt_clb.Mixup(0.2, NUM_CLASSES), pt_clb.Cutmix(1.0, NUM_CLASSES), pt_clb.ScheduledDropout(), - pt_clb.ModelEma(decay=0.9999) ], ) def test_callback(callback): @@ -152,19 +170,17 @@ def test_callback(callback): ) def test_segm_callback(callback): runner = Runner( - model=TEST_SEGM_MODEL, - optimizer=TEST_SEGM_OPTIMZER, - criterion=TEST_CRITERION, - callbacks=callback, + model=TEST_SEGM_MODEL, optimizer=TEST_SEGM_OPTIMZER, criterion=TEST_CRITERION, callbacks=callback, ) runner.fit(TEST_SEGM_LOADER, epochs=2) + def test_invalid_phases_scheduler_mode(): runner = Runner( model=TEST_MODEL, optimizer=TEST_OPTIMIZER, criterion=TEST_CRITERION, - callbacks=pt_clb.PhasesScheduler([{"ep":[0,1], "lr":[0,1], "mode":"new_mode" },]) + callbacks=pt_clb.PhasesScheduler([{"ep": [0, 1], "lr": [0, 1], "mode": "new_mode"},]), ) with pytest.raises(ValueError): - runner.fit(TEST_LOADER, epochs=2) \ No newline at end of file + runner.fit(TEST_LOADER, epochs=2) From c38d97b0aff1261f2f283e07769134ad69d9afce Mon Sep 17 00:00:00 2001 From: zakajd Date: Mon, 25 May 2020 12:33:49 +0300 Subject: [PATCH 37/54] make swish & mish inplace --- pytorch_tools/modules/activations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch_tools/modules/activations.py b/pytorch_tools/modules/activations.py index 4719d3c..78bcc41 100644 --- a/pytorch_tools/modules/activations.py +++ b/pytorch_tools/modules/activations.py @@ -27,7 +27,7 @@ class ACT(Enum): @torch.jit.script def swish_jit_fwd(x): - return x.mul(torch.sigmoid(x)) + return x.mul_(torch.sigmoid(x)) @torch.jit.script @@ -87,7 +87,7 @@ def forward(self, x): @torch.jit.script def mish_jit_fwd(x): - return x.mul(torch.tanh(F.softplus(x))) + return x.mul_(torch.tanh(F.softplus(x))) @torch.jit.script From 0bf0a7cf97e924dfd256b1f455d3e88ab1fe2f7e Mon Sep 17 00:00:00 2001 From: zakajd Date: Mon, 25 May 2020 19:47:30 +0300 Subject: [PATCH 38/54] fully working EffDet + tests + pretrained weigths loading --- .../detection_models/efficientdet.py | 181 ++++++++++++++++-- pytorch_tools/models/efficientnet.py | 2 +- pytorch_tools/modules/bifpn.py | 2 +- pytorch_tools/modules/tf_same_ops.py | 10 +- pytorch_tools/utils/box.py | 80 ++++++-- tests/detection_models/test_det_models.py | 49 +++++ 6 files changed, 283 insertions(+), 41 deletions(-) create mode 100644 tests/detection_models/test_det_models.py diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index cefd7a6..11b68a1 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -1,5 +1,11 @@ +import logging +from copy import deepcopy +from functools import wraps + import torch import torch.nn as nn +from torch.hub import load_state_dict_from_url + from pytorch_tools.modules import ABN from pytorch_tools.modules.bifpn import BiFPN from pytorch_tools.modules import bn_from_name @@ -8,8 +14,12 @@ from pytorch_tools.modules.residual import DepthwiseSeparableConv from pytorch_tools.modules.tf_same_ops import conv_to_same_conv from pytorch_tools.modules.tf_same_ops import maxpool_to_same_maxpool + from pytorch_tools.segmentation_models.encoders import get_encoder +import pytorch_tools.utils.box as box_utils +from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS + def patch_bn(module): """TF ported weights use slightly different eps in BN. Need to adjust for better performance""" if isinstance(module, ABN): @@ -19,6 +29,7 @@ def patch_bn(module): patch_bn(m) class EfficientDet(nn.Module): + """TODO: add docstring""" def __init__( self, encoder_name="efficientnet_b0", @@ -114,37 +125,165 @@ def forward(self, x): cls_feat = cls_feat.permute(0, 2, 3, 1) class_outputs.append(cls_feat.contiguous().view(cls_feat.shape[0], -1, self.num_classes)) - # TODO: return back to simplier transpose operations - # This transpose + view aligns with `generate_anchor_boxes` function - # class_outputs.append(cls_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, self.num_classes)) - # box_outputs.append(box_feat.transpose(1, 3).contiguous().view(x.shape[0], -1, 4)) - class_outputs = torch.cat(class_outputs , 1) box_outputs = torch.cat(box_outputs, 1) + # my anchors are in [x1, y1, x2,y2] format while pretrained weights are in [y1, x1, y2, x2] format + # it may be confusing to reorder x and y every time later so I do it once here. it gives + # compatability with pretrained weigths from Google and doesn't affect training from scratch + box_outputs = box_outputs[..., [1, 0, 3, 2]] return class_outputs, box_outputs + @torch.no_grad() + def predict(self, x): + """Run forward on given images and decode raw prediction into bboxes""" + class_outputs, box_outputs = self.forward(x) + anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] + out_bboxes, out_scores, out_classes = box_utils.decode( + class_outputs, box_outputs, anchors, img_shape=x.shape[-2:] + ) + return out_bboxes, out_scores, out_classes + +PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct":1, "num_classes":90} + +# fmt: off +CFGS = { + "efficientdet_b0": { + "default": { + "params": { + "encoder_name":"efficientnet_b0", + "pyramid_channels":64, + "num_fpn_layers":3, + "num_head_repeats":3, + }, + **PRETRAIN_SETTINGS, + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d0.pth",}, + }, + "efficientdet_b1": { + "default": { + "params": { + "encoder_name":"efficientnet_b1", + "pyramid_channels":88, + "num_fpn_layers":4, + "num_head_repeats":3, + }, + **PRETRAIN_SETTINGS, + "input_size": (640, 640), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d1.pth",}, + }, + "efficientdet_b2": { + "default": { + "params": { + "encoder_name":"efficientnet_b2", + "pyramid_channels":112, + "num_fpn_layers":5, + "num_head_repeats":3, + }, + **PRETRAIN_SETTINGS, + "input_size": (768, 768), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d2.pth",}, + }, + "efficientdet_b3": { + "default": { + "params": { + "encoder_name":"efficientnet_b3", + "pyramid_channels":160, + "num_fpn_layers":6, + "num_head_repeats":4, + }, + **PRETRAIN_SETTINGS, + "input_size": (896, 896), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d3.pth",}, + }, + "efficientdet_b4": { + "default": { + "params": { + "encoder_name":"efficientnet_b4", + "pyramid_channels":224, + "num_fpn_layers":7, + "num_head_repeats":4, + }, + **PRETRAIN_SETTINGS, + "input_size": (1024, 1024), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d4.pth",}, + }, + "efficientdet_b5": { + "default": { + "params": { + "encoder_name":"efficientnet_b5", + "pyramid_channels":288, + "num_fpn_layers":7, + "num_head_repeats":4, + }, + **PRETRAIN_SETTINGS, + "input_size": (1280, 1280), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d5.pth",}, + }, + "efficientdet_b6": { + "default": { + "params": { + "encoder_name":"efficientnet_b6", + "pyramid_channels":384, + "num_fpn_layers":8, + "num_head_repeats":5, + }, + **PRETRAIN_SETTINGS, + "input_size": (1280, 1280), + }, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d6.pth",}, + }, +} +# fmt: on + +def _efficientdet(arch, pretrained=None, **kwargs): + cfgs = deepcopy(CFGS) + cfg_settings = cfgs[arch]["default"] + cfg_params = cfg_settings.pop("params") + kwargs.update(cfg_params) + model = EfficientDet(**kwargs) + if pretrained: + state_dict = load_state_dict_from_url(cfgs[arch][pretrained]["url"]) + kwargs_cls = kwargs.get("num_classes", None) + if kwargs_cls and kwargs_cls != cfg_settings["num_classes"]: + logging.warning(f"Using model pretrained for {cfg_settings['num_classes']} classes with {kwargs_cls} classes. Last layer is initialized randomly") + last_conv_name = f"cls_head_convs.{kwargs['num_head_repeats']}.1" + state_dict[f"{last_conv_name}.weight"] = model.state_dict()[f"{last_conv_name}.weight"] + state_dict[f"{last_conv_name}.bias"] = model.state_dict()[f"{last_conv_name}.bias"] + model.load_state_dict(state_dict) + setattr(model, "pretrained_settings", cfg_settings) + return model +@wraps(EfficientDet) +def efficientdet_b0(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b0", pretrained, **kwargs) -def efficientdet_b0(**kwargs): - return EfficientDet(**kwargs) +@wraps(EfficientDet) +def efficientdet_b1(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b1", pretrained, **kwargs) -def efficientdet_b1(**kwargs): - return EfficientDet( - encoder_name="efficientnet_b1", pyramid_channels=88, num_fpn_layers=4, num_head_repeats=3, **kwargs) +@wraps(EfficientDet) +def efficientdet_b2(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b2", pretrained, **kwargs) -def efficientdet_b2(**kwargs): - return EfficientDet(encoder_name="efficientnet_b2", pyramid_channels=112, num_fpn_layers=5, num_head_repeats=3, **kwargs) +@wraps(EfficientDet) +def efficientdet_b3(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b3", pretrained, **kwargs) -def efficientdet_b3(**kwargs): - return EfficientDet(encoder_name="efficientnet_b3", pyramid_channels=160, num_fpn_layers=6, num_head_repeats=4, **kwargs) +@wraps(EfficientDet) +def efficientdet_b4(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b4", pretrained, **kwargs) -def efficientdet_b4(**kwargs): - return EfficientDet(encoder_name="efficientnet_b4", pyramid_channels=224, num_fpn_layers=7, num_head_repeats=4, **kwargs) - -def efficientdet_b5(**kwargs): - return EfficientDet(encoder_name="efficientnet_b5", pyramid_channels=288, num_fpn_layers=7, num_head_repeats=4, **kwargs) +@wraps(EfficientDet) +def efficientdet_b5(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b5", pretrained, **kwargs) -def efficientdet_b6(*args, **kwargs): - return EfficientDet(encoder_name="efficientnet_b6", pyramid_channels=384, num_fpn_layers=8, num_head_repeats=5, **kwargs) +@wraps(EfficientDet) +def efficientdet_b6(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_b6", pretrained, **kwargs) # No B7 because it's the same model as B6 but with larger input \ No newline at end of file diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index d9ab577..1510c9c 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -389,7 +389,7 @@ def patch_bn(module): module.eps = 1e-3 for m in module.children(): patch_bn(m) - + def _efficientnet(arch, pretrained=None, **kwargs): cfgs = deepcopy(CFGS) cfg_settings = cfgs[arch]["default"] diff --git a/pytorch_tools/modules/bifpn.py b/pytorch_tools/modules/bifpn.py index 406df57..abe55a4 100644 --- a/pytorch_tools/modules/bifpn.py +++ b/pytorch_tools/modules/bifpn.py @@ -51,7 +51,7 @@ def __init__(self, channels=64, norm_layer=ABN, norm_act="relu"): self.down = nn.MaxPool2d(3, stride=2, padding=1) # padding=1 TODO: change back # disable attention for large models. This is very dirty way to check that it's B6 & B7. But i don't care - Fusion = FastNormalizedFusion if channels < 288 else SumFusion + Fusion = SumFusion if channels > 288 else FastNormalizedFusion # There is no activation in SeparableConvs, instead activation is in fusion layer self.fuse_p6_up = Fusion(in_nodes=2, activation=norm_act) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index 6a5a910..4e7b638 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -5,9 +5,7 @@ import torch.nn.functional as F class Conv2dSamePadding(nn.Conv2d): - """ - Assymetric padding matching TensorFlow `same` - """ + """Assymetric padding matching TensorFlow `same`""" def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride[1]) - 1) * self.stride[1] - w + self.kernel_size[1] @@ -17,9 +15,7 @@ def forward(self, x): class MaxPool2dSamePadding(nn.MaxPool2d): - """ - Assymetric padding matching TensorFlow `same` - """ + """Assymetric padding matching TensorFlow `same`""" def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size @@ -29,6 +25,7 @@ def forward(self, x): def conv_to_same_conv(module): + """Turn All Conv2d into SameConv2d to match TF padding""" module_output = module # skip 1x1 convs if isinstance(module, nn.Conv2d) and module.kernel_size[0] != 1: @@ -55,6 +52,7 @@ def conv_to_same_conv(module): return module_output def maxpool_to_same_maxpool(module): + """Turn All MaxPool2d into SameMaxPool2d to match TF padding""" module_output = module if isinstance(module, nn.MaxPool2d): module_output = MaxPool2dSamePadding( diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index f66f96f..09229c0 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -29,12 +29,15 @@ def delta2box(deltas, anchors): bboxes (torch.Tensor): bboxes obtained from anchors by regression [N, 4] """ - anchors_wh = anchors[:, 2:] - anchors[:, :2] + 1 + anchors_wh = anchors[:, 2:] - anchors[:, :2] # + 1 ctr = anchors[:, :2] + 0.5 * anchors_wh pred_ctr = deltas[:, :2] * anchors_wh + ctr pred_wh = torch.exp(deltas[:, 2:]) * anchors_wh - return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh - 1], 1) + # TODO: make sure this +- 1 for centers aligns with Detectron implementation + # return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh - 1], 1) + return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh], 1) + def box_area(box): """Args: @@ -112,25 +115,24 @@ def generate_anchors_boxes( # get offsets for anchor boxes for one pixel # can rewrite in pure Torch but using np is more convenient. This function usually should only be called once num_anchors = len(scale_vals) * len(aspect_ratios) - ratio_vals_sq = np.sqrt(np.repeat(aspect_ratios, len(scale_vals))) - scale_vals_tiled = np.tile(scale_vals, len(aspect_ratios))[:, np.newaxis] + ratio_vals_sq = np.sqrt(np.tile(aspect_ratios, len(scale_vals))) + scale_vals = np.repeat(scale_vals, len(aspect_ratios))[:, np.newaxis] wh = np.stack([np.ones(num_anchors) * ratio_vals_sq, np.ones(num_anchors) / ratio_vals_sq], axis=1) - lt = - 0.5 * wh * scale_vals_tiled - rb = 0.5 * wh * scale_vals_tiled + lt = - 0.5 * wh * scale_vals + rb = 0.5 * wh * scale_vals base_offsets = torch.from_numpy(np.hstack([lt, rb])) # [num_anchors, 4] base_offsets = base_offsets.view(-1, 1, 1, 4) # [num_anchors, 1, 1, 4] - # generate anchor boxes for all given strides all_anchors = [] for stride in strides: - x, y = torch.meshgrid([torch.arange(stride / 2, image_size[i], stride) for i in range(2)]) + y, x = torch.meshgrid([torch.arange(stride / 2, image_size[i], stride) for i in range(2)]) xyxy = torch.stack((x, y, x, y), 2).unsqueeze(0) - anchors = (xyxy + base_offsets * stride).view(-1, 4).contiguous() + # permute to match TF EffDet anchors order after reshape + anchors = (xyxy + base_offsets * stride).permute(1, 2, 0, 3).reshape(-1, 4) all_anchors.append(anchors) all_anchors = torch.cat(all_anchors) # clip boxes to image. Not sure if we really need to clip them - # all_anchors[:, 0::2] = all_anchors[:, 0::2].clamp(0, image_size[0]) - # all_anchors[:, 1::2] = all_anchors[:, 1::2].clamp(0, image_size[1]) + # clip_bboxes(all_anchors, image_size) return all_anchors, num_anchors def generate_targets(anchors, batch_gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): @@ -227,4 +229,58 @@ def batched_nms(boxes, scores, idxs, iou_threshold): offsets = idxs.to(boxes) * (max_coordinate + 1) boxes_for_nms = boxes + offsets[:, None] keep = torch.ops.torchvision.nms(boxes_for_nms, scores, iou_threshold) - return keep \ No newline at end of file + return keep + +def decode(batch_cls_head, batch_box_head, anchors, img_shape=None, threshold=0.05, top_n=1000, iou_threshold=0.5): + """ + Decodes raw outputs of a model for easy visualization of bboxes + + Args: + batch_cls_head (torch.Tensor): shape [BS, *, NUM_CLASSES] + batch_box_head (torch.Tensor): shape [BS, *, 4] + anchors (torch.Tensor): shape [*, 4] + img_shape (Tuple[int]): if given clips predicted bboxes to img height and width + threshold (float): minimum score threshold to consider object detected + top_n (int): maximum number of objects per image + iou_threshold (float): iou_threshold for Non Maximum Supression + + Returns: + out_bboxes (torch.Tensor): bboxes. Shape [BS, TOP_N] If img_shape is not given they are NOT CLIPPED (!) + out_scores (torch.Tensor): Probability scores for each bbox. Shape [BS, TOP_N] + out_classes (torch.Tensor): Predicted class for each bbox. Shape [BS, TOP_N] + """ + + batch_size = batch_cls_head.size(0) + anchors = anchors.to(batch_cls_head) + out_scores = torch.zeros((batch_size, top_n)).to(batch_cls_head) + out_boxes = torch.zeros((batch_size, top_n, 4)).to(batch_cls_head) + out_classes = torch.zeros((batch_size, top_n)).to(batch_cls_head) + # it has to be raw logits but check anyway to avoid aplying sigmoid twice + if batch_cls_head.min() < 0 or batch_cls_head.max() > 1: + batch_cls_head = batch_cls_head.sigmoid() + + for batch in range(batch_size): + # get regressed bboxes + all_img_bboxes = delta2box(batch_box_head[batch], anchors) + if img_shape: # maybe clip + all_img_bboxes = clip_bboxes(all_img_bboxes, img_shape) + # select at most `top_n` bboxes and from them select with score > threshold + max_cls_score, max_cls_idx = batch_cls_head[batch].max(1) + top_cls_score, top_cls_idx = max_cls_score.topk(top_n) + top_cls_idx = top_cls_idx[top_cls_score > threshold] + + im_scores = max_cls_score[top_cls_idx] + im_classes = max_cls_idx[top_cls_idx] + im_bboxes = all_img_bboxes[top_cls_idx] + + # apply NMS + nms_idx = batched_nms(im_bboxes, im_scores, im_classes, iou_threshold) + im_scores = im_scores[nms_idx] + im_classes = im_classes[nms_idx] + im_bboxes = im_bboxes[nms_idx] + + out_scores[batch, :im_scores.size(0)] = im_scores + out_classes[batch, :im_classes.size(0)] = im_classes + out_boxes[batch, :im_bboxes.size(0)] = im_bboxes + + return out_boxes, out_scores, out_classes \ No newline at end of file diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py new file mode 100644 index 0000000..84435cc --- /dev/null +++ b/tests/detection_models/test_det_models.py @@ -0,0 +1,49 @@ +import torch +import pytest +import numpy as np +from PIL import Image +from pytorch_tools.utils.preprocessing import get_preprocessing_fn +from pytorch_tools.utils.visualization import tensor_from_rgb_image + +import pytorch_tools as pt +import pytorch_tools.detection_models as pt_det + +# all weights were tested on 05.2020. for now only leave one model for faster tests +MODEL_NAMES = [ + "efficientdet_b0", + # "efficientdet_b1", + # "efficientdet_b2", + # "efficientdet_b3", + # "efficientdet_b4", + # "efficientdet_b5", + # "efficientdet_b6", +] + +# format "coco image class: PIL Image" +IMGS = { + 17: Image.open("tests/imgs/dog.jpg"), +} + +INP = torch.ones(1, 3, 512, 512) +@torch.no_grad() +def _test_forward(model): + return model(INP) + +@pytest.mark.parametrize("arch", MODEL_NAMES) +def test_coco_pretrain(arch): + m = pt_det.__dict__[arch](pretrained="coco").cuda() + m.eval() + # get size of the images used for pretraining + inp_size = m.pretrained_settings["input_size"][-1] + # get preprocessing fn according to pretrained settings + preprocess_fn = get_preprocessing_fn(m.pretrained_settings) + for im_cls, im in IMGS.items(): + im = np.array(im.resize((inp_size, inp_size))) + im_t = tensor_from_rgb_image(preprocess_fn(im)).unsqueeze(0).float().cuda() + boxes, scores, classes = m.predict(im_t) + assert classes[0, 0] == im_cls # check that most confident bbox is correct + +@pytest.mark.parametrize("arch", MODEL_NAMES[:1]) +def test_pretrain_custom_num_classes(arch): + m = pt_det.__dict__[arch](pretrained="coco", num_classes=80).eval() + _test_forward(m) From 51caf193dbf667165f4163e3161534f3435e9b9c Mon Sep 17 00:00:00 2001 From: zakajd Date: Mon, 25 May 2020 19:52:24 +0300 Subject: [PATCH 39/54] format with black --- pytorch_tools/detection_models/__init__.py | 2 +- .../detection_models/efficientdet.py | 96 +++++++++++-------- pytorch_tools/detection_models/retinanet.py | 28 +++--- pytorch_tools/modules/__init__.py | 1 + pytorch_tools/modules/activated_group_norm.py | 9 +- pytorch_tools/modules/bifpn.py | 43 ++++----- pytorch_tools/modules/fpn.py | 14 +-- pytorch_tools/modules/pooling.py | 8 +- pytorch_tools/modules/residual.py | 20 +++- pytorch_tools/modules/spatial_ocr_block.py | 46 ++++----- pytorch_tools/modules/tf_same_ops.py | 24 ++--- .../modules/weight_standartization.py | 52 +++++----- tests/detection_models/test_det_models.py | 8 +- 13 files changed, 188 insertions(+), 163 deletions(-) diff --git a/pytorch_tools/detection_models/__init__.py b/pytorch_tools/detection_models/__init__.py index 8f2dd8e..f44058f 100644 --- a/pytorch_tools/detection_models/__init__.py +++ b/pytorch_tools/detection_models/__init__.py @@ -5,4 +5,4 @@ from .efficientdet import efficientdet_b3 from .efficientdet import efficientdet_b4 from .efficientdet import efficientdet_b5 -from .efficientdet import efficientdet_b6 \ No newline at end of file +from .efficientdet import efficientdet_b6 diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index 11b68a1..817ad9b 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -20,6 +20,7 @@ import pytorch_tools.utils.box as box_utils from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS + def patch_bn(module): """TF ported weights use slightly different eps in BN. Need to adjust for better performance""" if isinstance(module, ABN): @@ -28,10 +29,12 @@ def patch_bn(module): for m in module.children(): patch_bn(m) + class EfficientDet(nn.Module): """TODO: add docstring""" + def __init__( - self, + self, encoder_name="efficientnet_b0", encoder_weights="imagenet", pyramid_channels=64, @@ -39,12 +42,12 @@ def __init__( num_head_repeats=3, num_classes=90, drop_connect_rate=0, - encoder_norm_layer="abn", # TODO: set to frozenabn when ready + encoder_norm_layer="abn", # TODO: set to frozenabn when ready encoder_norm_act="swish", decoder_norm_layer="abn", decoder_norm_act="swish", match_tf_same_padding=False, - ): + ): super().__init__() self.encoder = get_encoder( encoder_name, @@ -57,9 +60,9 @@ def __init__( self.pyramid6 = nn.Sequential( conv1x1(self.encoder.out_shapes[0], pyramid_channels, bias=True), norm_layer(pyramid_channels, activation="identity"), - nn.MaxPool2d(3, stride=2, padding=1) + nn.MaxPool2d(3, stride=2, padding=1), ) - self.pyramid7 = nn.MaxPool2d(3, stride=2, padding=1) # in EffDet it's a simple maxpool + self.pyramid7 = nn.MaxPool2d(3, stride=2, padding=1) # in EffDet it's a simple maxpool self.bifpn = BiFPN( self.encoder.out_shapes[:-2], @@ -75,18 +78,22 @@ def make_head(out_size): layers += [DepthwiseSeparableConv(pyramid_channels, pyramid_channels, use_norm=False)] layers += [DepthwiseSeparableConv(pyramid_channels, out_size, use_norm=False)] return nn.ModuleList(layers) - + def make_head_norm(): - return nn.ModuleList([ - nn.ModuleList( - [ - norm_layer(pyramid_channels, activation=decoder_norm_act) - for _ in range(num_head_repeats) - ] + [nn.Identity()] # no bn after last depthwise conv - ) - for _ in range(5) - ]) - anchors_per_location = 9 # TODO: maybe allow to pass this arg? + return nn.ModuleList( + [ + nn.ModuleList( + [ + norm_layer(pyramid_channels, activation=decoder_norm_act) + for _ in range(num_head_repeats) + ] + + [nn.Identity()] # no bn after last depthwise conv + ) + for _ in range(5) + ] + ) + + anchors_per_location = 9 # TODO: maybe allow to pass this arg? self.cls_head_convs = make_head(num_classes * anchors_per_location) self.cls_head_norms = make_head_norm() self.box_head_convs = make_head(4 * anchors_per_location) @@ -97,7 +104,6 @@ def make_head_norm(): if match_tf_same_padding: conv_to_same_conv(self) maxpool_to_same_maxpool(self) - def forward(self, x): # don't use p2 and p1 @@ -108,7 +114,7 @@ def forward(self, x): features = [p7, p6, p5, p4, p3] # enhance features features = self.bifpn(features) - # want features from lowest OS to highest to align with `generate_anchors_boxes` function + # want features from lowest OS to highest to align with `generate_anchors_boxes` function features = list(reversed(features)) class_outputs = [] box_outputs = [] @@ -125,11 +131,11 @@ def forward(self, x): cls_feat = cls_feat.permute(0, 2, 3, 1) class_outputs.append(cls_feat.contiguous().view(cls_feat.shape[0], -1, self.num_classes)) - class_outputs = torch.cat(class_outputs , 1) + class_outputs = torch.cat(class_outputs, 1) box_outputs = torch.cat(box_outputs, 1) # my anchors are in [x1, y1, x2,y2] format while pretrained weights are in [y1, x1, y2, x2] format - # it may be confusing to reorder x and y every time later so I do it once here. it gives - # compatability with pretrained weigths from Google and doesn't affect training from scratch + # it may be confusing to reorder x and y every time later so I do it once here. it gives + # compatability with pretrained weigths from Google and doesn't affect training from scratch box_outputs = box_outputs[..., [1, 0, 3, 2]] return class_outputs, box_outputs @@ -143,7 +149,8 @@ def predict(self, x): ) return out_bboxes, out_scores, out_classes -PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct":1, "num_classes":90} + +PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct": 1, "num_classes": 90} # fmt: off CFGS = { @@ -240,50 +247,61 @@ def predict(self, x): } # fmt: on + def _efficientdet(arch, pretrained=None, **kwargs): - cfgs = deepcopy(CFGS) - cfg_settings = cfgs[arch]["default"] - cfg_params = cfg_settings.pop("params") - kwargs.update(cfg_params) - model = EfficientDet(**kwargs) - if pretrained: - state_dict = load_state_dict_from_url(cfgs[arch][pretrained]["url"]) - kwargs_cls = kwargs.get("num_classes", None) - if kwargs_cls and kwargs_cls != cfg_settings["num_classes"]: - logging.warning(f"Using model pretrained for {cfg_settings['num_classes']} classes with {kwargs_cls} classes. Last layer is initialized randomly") - last_conv_name = f"cls_head_convs.{kwargs['num_head_repeats']}.1" - state_dict[f"{last_conv_name}.weight"] = model.state_dict()[f"{last_conv_name}.weight"] - state_dict[f"{last_conv_name}.bias"] = model.state_dict()[f"{last_conv_name}.bias"] - model.load_state_dict(state_dict) - setattr(model, "pretrained_settings", cfg_settings) - return model + cfgs = deepcopy(CFGS) + cfg_settings = cfgs[arch]["default"] + cfg_params = cfg_settings.pop("params") + kwargs.update(cfg_params) + model = EfficientDet(**kwargs) + if pretrained: + state_dict = load_state_dict_from_url(cfgs[arch][pretrained]["url"]) + kwargs_cls = kwargs.get("num_classes", None) + if kwargs_cls and kwargs_cls != cfg_settings["num_classes"]: + logging.warning( + f"Using model pretrained for {cfg_settings['num_classes']} classes with {kwargs_cls} classes. Last layer is initialized randomly" + ) + last_conv_name = f"cls_head_convs.{kwargs['num_head_repeats']}.1" + state_dict[f"{last_conv_name}.weight"] = model.state_dict()[f"{last_conv_name}.weight"] + state_dict[f"{last_conv_name}.bias"] = model.state_dict()[f"{last_conv_name}.bias"] + model.load_state_dict(state_dict) + setattr(model, "pretrained_settings", cfg_settings) + return model + @wraps(EfficientDet) def efficientdet_b0(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b0", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b1(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b1", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b2(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b2", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b3(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b3", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b4(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b4", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b5(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b5", pretrained, **kwargs) + @wraps(EfficientDet) def efficientdet_b6(pretrained="coco", **kwargs): return _efficientdet("efficientdet_b6", pretrained, **kwargs) -# No B7 because it's the same model as B6 but with larger input \ No newline at end of file + +# No B7 because it's the same model as B6 but with larger input diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index e64a12c..575f193 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -1,11 +1,15 @@ import torch import torch.nn as nn + # import torch.nn.functional as F from pytorch_tools.modules.fpn import FPN + # from pytorch_tools.modules.bifpn import BiFPN from pytorch_tools.modules import bn_from_name + # from pytorch_tools.modules.residual import conv1x1 from pytorch_tools.modules.residual import conv3x3 + # from pytorch_tools.modules.decoder import SegmentationUpsample # from pytorch_tools.utils.misc import initialize from pytorch_tools.segmentation_models.encoders import get_encoder @@ -13,15 +17,15 @@ class RetinaNet(nn.Module): def __init__( - self, - encoder_name="resnet34", - encoder_weights="imagenet", - pyramid_channels=256, + self, + encoder_name="resnet34", + encoder_weights="imagenet", + pyramid_channels=256, num_classes=80, norm_layer="abn", norm_act="relu", **encoder_params, - ): + ): super().__init__() self.encoder = get_encoder( encoder_name, @@ -33,10 +37,7 @@ def __init__( norm_layer = bn_from_name(norm_layer) self.pyramid6 = conv3x3(256, 256, 2, bias=True) self.pyramid7 = conv3x3(256, 256, 2, bias=True) - self.fpn = FPN( - self.encoder.out_shapes[:-2], - pyramid_channels=pyramid_channels, - ) + self.fpn = FPN(self.encoder.out_shapes[:-2], pyramid_channels=pyramid_channels,) def make_head(out_size): layers = [] @@ -44,7 +45,7 @@ def make_head(out_size): # some implementations don't use BN here but I think it's needed # TODO: test how it affects results # upd. removed norm_layer. maybe change to group_norm later - layers += [nn.Conv2d(256, 256, 3, padding=1)] # norm_layer(256, activation=norm_act) + layers += [nn.Conv2d(256, 256, 3, padding=1)] # norm_layer(256, activation=norm_act) # layers += [nn.Conv2d(256, 256, 3, padding=1), nn.ReLU()] layers += [nn.Conv2d(256, out_size, 3, padding=1)] @@ -52,7 +53,7 @@ def make_head(out_size): self.ratios = [1.0, 2.0, 0.5] self.scales = [4 * 2 ** (i / 3) for i in range(3)] - anchors = len(self.ratios) * len(self.scales) # 9 + anchors = len(self.ratios) * len(self.scales) # 9 self.cls_head = make_head(num_classes * anchors) self.box_head = make_head(4 * anchors) @@ -66,7 +67,7 @@ def forward(self, x): # coarser FPN levels p6 = self.pyramid6(p5) p7 = self.pyramid7(p6.relu()) - # want features from lowest OS to highest to align with `generate_anchors_boxes` function + # want features from lowest OS to highest to align with `generate_anchors_boxes` function features = [p3, p4, p5, p6, p7] # TODO: (18.03.20) TF implementation has additional BN here before class/box outputs class_outputs = [] @@ -79,6 +80,3 @@ def forward(self, x): class_outputs = torch.cat(class_outputs, 1) box_outputs = torch.cat(box_outputs, 1) return class_outputs, box_outputs - - - \ No newline at end of file diff --git a/pytorch_tools/modules/__init__.py b/pytorch_tools/modules/__init__.py index ee20f3c..9612fa1 100644 --- a/pytorch_tools/modules/__init__.py +++ b/pytorch_tools/modules/__init__.py @@ -24,6 +24,7 @@ from .activated_group_norm import AGN from inplace_abn import InPlaceABN, InPlaceABNSync + def bn_from_name(norm_name): norm_name = norm_name.lower() if norm_name == "abn": diff --git a/pytorch_tools/modules/activated_group_norm.py b/pytorch_tools/modules/activated_group_norm.py index 516b8b8..6b06743 100644 --- a/pytorch_tools/modules/activated_group_norm.py +++ b/pytorch_tools/modules/activated_group_norm.py @@ -7,6 +7,7 @@ from .activations import ACT from .activations import ACT_FUNC_DICT + class AGN(nn.Module): """Activated Group Normalization This gathers a GroupNorm and an activation function in a single module @@ -27,13 +28,7 @@ class AGN(nn.Module): """ def __init__( - self, - num_features, - num_groups=32, - eps=1e-5, - affine=True, - activation="relu", - activation_param=0.01, + self, num_features, num_groups=32, eps=1e-5, affine=True, activation="relu", activation_param=0.01, ): super(AGN, self).__init__() self.num_features = num_features diff --git a/pytorch_tools/modules/bifpn.py b/pytorch_tools/modules/bifpn.py index abe55a4..9b73309 100644 --- a/pytorch_tools/modules/bifpn.py +++ b/pytorch_tools/modules/bifpn.py @@ -7,11 +7,13 @@ from .residual import conv1x1 from . import ABN + class FastNormalizedFusion(nn.Module): """Combines 2 or 3 feature maps into one with weights. Args: input_num (int): 2 for intermediate features, 3 for output features """ + def __init__(self, in_nodes, activation="relu"): super().__init__() self.weights = nn.Parameter(torch.ones(in_nodes, dtype=torch.float32)) @@ -26,6 +28,7 @@ def forward(self, *features): fused_features = sum([p * w for p, w in zip(features, weights)]) return self.act(fused_features) + # need to create weights to allow loading anyway. So inherit from FastNormalizedFusion for simplicity class SumFusion(FastNormalizedFusion): def forward(self, *features): @@ -48,8 +51,8 @@ def __init__(self, channels=64, norm_layer=ABN, norm_act="relu"): super().__init__() self.up = nn.Upsample(scale_factor=2, mode="nearest") - self.down = nn.MaxPool2d(3, stride=2, padding=1) # padding=1 TODO: change back - + self.down = nn.MaxPool2d(3, stride=2, padding=1) # padding=1 TODO: change back + # disable attention for large models. This is very dirty way to check that it's B6 & B7. But i don't care Fusion = SumFusion if channels > 288 else FastNormalizedFusion @@ -75,13 +78,13 @@ def __init__(self, channels=64, norm_layer=ABN, norm_act="relu"): self.p4_out = DepthwiseSeparableConv(channels, channels, **bn_args) self.p5_out = DepthwiseSeparableConv(channels, channels, **bn_args) self.p6_out = DepthwiseSeparableConv(channels, channels, **bn_args) - self.p7_out = DepthwiseSeparableConv(channels, channels, **bn_args) - + self.p7_out = DepthwiseSeparableConv(channels, channels, **bn_args) + def forward(self, features): # p7, p6, p5, p4, p3 p7_in, p6_in, p5_in, p4_in, p3_in = features - + # Top-down pathway (from low res to high res) p6_up = self.p6_up(self.fuse_p6_up(p6_in, self.up(p7_in))) p5_up = self.p5_up(self.fuse_p5_up(p5_in, self.up(p6_up))) @@ -96,38 +99,33 @@ def forward(self, features): return p7_out, p6_out, p5_out, p4_out, p3_out + # additionally downsamples the input class FirstBiFPNLayer(BiFPNLayer): def __init__(self, encoder_channels, channels=64, norm_layer=ABN, norm_act="relu"): super().__init__(channels=channels, norm_layer=norm_layer, norm_act=norm_act) - # TODO: later remove bias from downsample + # TODO: later remove bias from downsample self.p5_downsample_1 = nn.Sequential( - conv1x1(encoder_channels[0], channels, bias=True), - norm_layer(channels, activation="identity") + conv1x1(encoder_channels[0], channels, bias=True), norm_layer(channels, activation="identity") ) self.p4_downsample_1 = nn.Sequential( - conv1x1(encoder_channels[1], channels, bias=True), - norm_layer(channels, activation="identity") + conv1x1(encoder_channels[1], channels, bias=True), norm_layer(channels, activation="identity") ) self.p3_downsample_1 = nn.Sequential( - conv1x1(encoder_channels[2], channels, bias=True), - norm_layer(channels, activation="identity") + conv1x1(encoder_channels[2], channels, bias=True), norm_layer(channels, activation="identity") ) # Devil is in the details. In original repo they use 2 different downsamples from encoder channels - # it makes sense to preseve more information, but most of implementations in the internet + # it makes sense to preseve more information, but most of implementations in the internet # use output of the first downsample self.p4_downsample_2 = nn.Sequential( - conv1x1(encoder_channels[1], channels, bias=True), - norm_layer(channels, activation="identity") + conv1x1(encoder_channels[1], channels, bias=True), norm_layer(channels, activation="identity") ) self.p5_downsample_2 = nn.Sequential( - conv1x1(encoder_channels[0], channels, bias=True), - norm_layer(channels, activation="identity") + conv1x1(encoder_channels[0], channels, bias=True), norm_layer(channels, activation="identity") ) - # only one downsample for p3 - + # only one downsample for p3 def forward(self, features): @@ -155,6 +153,7 @@ def forward(self, features): return p7_out, p6_out, p5_out, p4_out, p3_out + class BiFPN(nn.Sequential): """ Implementation of Bi-directional Feature Pyramid Network @@ -170,10 +169,10 @@ class BiFPN(nn.Sequential): https://arxiv.org/pdf/1911.09070.pdf """ - def __init__(self, encoder_channels, pyramid_channels=64, num_layers=1, **bn_args): - # First layer preprocesses raw encoder features + def __init__(self, encoder_channels, pyramid_channels=64, num_layers=1, **bn_args): + # First layer preprocesses raw encoder features bifpns = [FirstBiFPNLayer(encoder_channels, pyramid_channels, **bn_args)] # Apply BiFPN block `num_layers` times for _ in range(num_layers - 1): bifpns.append(BiFPNLayer(pyramid_channels, **bn_args)) - super().__init__(*bifpns) \ No newline at end of file + super().__init__(*bifpns) diff --git a/pytorch_tools/modules/fpn.py b/pytorch_tools/modules/fpn.py index 2b6ef39..726f7ff 100644 --- a/pytorch_tools/modules/fpn.py +++ b/pytorch_tools/modules/fpn.py @@ -4,6 +4,7 @@ import torch.nn.functional as F from .residual import conv1x1, conv3x3 + class MergeBlock(nn.Module): def forward(self, x): x, skip = x @@ -11,6 +12,7 @@ def forward(self, x): x += skip return x + class FPN(nn.Module): """Feature Pyramid Network for enhancing high-resolution feature maps with semantic meaning from low resolution maps @@ -25,11 +27,11 @@ class FPN(nn.Module): """ def __init__( - self, - encoder_channels, - pyramid_channels=256, - num_layers=1, - **bn_args, # for compatability only. Not used + self, + encoder_channels, + pyramid_channels=256, + num_layers=1, + **bn_args, # for compatability only. Not used ): super().__init__() assert num_layers == 1, "More that 1 layer is not supported in FPN" @@ -52,4 +54,4 @@ def forward(self, features): pyramid_features[idx] = self.merge_block([pyramid_features[idx - 1], pyramid_features[idx]]) # smooth them after merging pyramid_features = [s_conv(feature) for s_conv, feature in zip(self.smooth_convs, pyramid_features)] - return pyramid_features \ No newline at end of file + return pyramid_features diff --git a/pytorch_tools/modules/pooling.py b/pytorch_tools/modules/pooling.py index 0e8c24d..6a359e6 100644 --- a/pytorch_tools/modules/pooling.py +++ b/pytorch_tools/modules/pooling.py @@ -64,6 +64,7 @@ def feat_mult(self): def __repr__(self): return self.__class__.__name__ + " (" + ", pool_type=" + self.pool_type + ")" + # https://github.com/mrT23/TResNet/ class FastGlobalAvgPool2d(nn.Module): def __init__(self, flatten=False): @@ -89,16 +90,17 @@ class BlurPool(nn.Module): def __init__(self, channels=0): super(BlurPool, self).__init__() self.channels = channels - filt = torch.tensor([1., 2., 1.]) + filt = torch.tensor([1.0, 2.0, 1.0]) filt = filt[:, None] * filt[None, :] filt = filt / torch.sum(filt) filt = filt[None, None, :, :].repeat((self.channels, 1, 1, 1)) self.register_buffer("filt", filt) def forward(self, inp): - inp_pad = F.pad(inp, (1, 1, 1, 1), 'reflect') + inp_pad = F.pad(inp, (1, 1, 1, 1), "reflect") return F.conv2d(inp_pad, self.filt, stride=2, padding=0, groups=inp.shape[1]) + # from https://github.com/mrT23/TResNet/ class SpaceToDepth(nn.Module): def forward(self, x): @@ -107,4 +109,4 @@ def forward(self, x): x = x.view(N, C, H // 4, 4, W // 4, 4) # (N, C, H//bs, bs, W//bs, bs) x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # (N, bs, bs, C, H//bs, W//bs) x = x.view(N, C * 16, H // 4, W // 4) # (N, C*bs^2, H//bs, W//bs) - return x \ No newline at end of file + return x diff --git a/pytorch_tools/modules/residual.py b/pytorch_tools/modules/residual.py index 2ea7816..6b865d9 100644 --- a/pytorch_tools/modules/residual.py +++ b/pytorch_tools/modules/residual.py @@ -47,6 +47,7 @@ def forward(self, x): x_se = self.fc2(x_se) return x * x_se.sigmoid() + class ECAModule(nn.Module): """Efficient Channel Attention This implementation is different from the paper. I've removed all hyperparameters and @@ -56,7 +57,8 @@ class ECAModule(nn.Module): https://arxiv.org/abs/1910.03151 """ - def __init__(self, *args, **kwargs): + + def __init__(self, *args, **kwargs): super().__init__() self.pool = FastGlobalAvgPool2d() self.conv = nn.Conv1d(1, 1, kernel_size=3, padding=1, bias=False) @@ -67,6 +69,7 @@ def forward(self, x): x_s = x_s.view(x.size(0), -1, 1, 1).sigmoid() return x * x_s.expand_as(x) + def get_attn(attn_type): """Args: attn_type (Uniont[str, None]): Attention type. Supported: `se` - Squeeze and Excitation @@ -79,13 +82,16 @@ def get_attn(attn_type): else: return ATT_TO_MODULE[attn_type.lower()] + class DepthwiseSeparableConv(nn.Sequential): """Depthwise separable conv with BN after depthwise & pointwise.""" - def __init__(self, in_channels, out_channels, stride=1, dilation=1, norm_layer=ABN, norm_act="relu", use_norm=True): + def __init__( + self, in_channels, out_channels, stride=1, dilation=1, norm_layer=ABN, norm_act="relu", use_norm=True + ): modules = [ conv3x3(in_channels, in_channels, stride=stride, groups=in_channels, dilation=dilation), - conv1x1(in_channels, out_channels, bias=True), # in efficient det they for some reason add bias + conv1x1(in_channels, out_channels, bias=True), # in efficient det they for some reason add bias norm_layer(out_channels, activation=norm_act) if use_norm else nn.Identity(), ] super().__init__(*modules) @@ -165,6 +171,7 @@ def forward(self, x): output = x / self.keep_prob * binary_tensor return output + class BasicBlock(nn.Module): expansion = 1 @@ -274,9 +281,11 @@ def forward(self, x): out = self.drop_connect(self.se_module(self.bn3(out))) + residual return self.final_act(out) + # TResnet models use slightly modified versions of BasicBlock and Bottleneck # need to adjust for it + class TBasicBlock(BasicBlock): def __init__(self, **kwargs): super().__init__(**kwargs) @@ -287,11 +296,12 @@ def __init__(self, **kwargs): planes = kwargs["planes"] self.se_module = SEModule(planes, max(planes // 4, 64)) + class TBottleneck(Bottleneck): def __init__(self, **kwargs): super().__init__(**kwargs) self.final_act = nn.ReLU(inplace=True) - self.bn1.activation_param = 1e-3 # needed for loading weights + self.bn1.activation_param = 1e-3 # needed for loading weights self.bn2.activation_param = 1e-3 if not kwargs.get("attn_type") == "se": return @@ -320,4 +330,4 @@ def forward(self, x): out = self.conv3(out) # avoid 2 inplace ops by chaining into one long op out = self.drop_connect(self.bn3(out)) + residual - return self.final_act(out) \ No newline at end of file + return self.final_act(out) diff --git a/pytorch_tools/modules/spatial_ocr_block.py b/pytorch_tools/modules/spatial_ocr_block.py index cfe5be4..d419f28 100644 --- a/pytorch_tools/modules/spatial_ocr_block.py +++ b/pytorch_tools/modules/spatial_ocr_block.py @@ -5,7 +5,7 @@ ## Copyright (c) 2019 ## ## This source code is licensed under the MIT-style license found in the -## LICENSE file in the root directory of this source tree +## LICENSE file in the root directory of this source tree ##+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ## modified and simplified by @bonlime @@ -24,18 +24,20 @@ class SpatialOCR_Gather(nn.Module): Returns: torch.Tensor (B x C_2 x C_1 x 1) """ + def forward(self, feats, probs): # C_1 is number of final classes. C_2 in number of features in `feats` - probs = probs.view(probs.size(0), probs.size(1), -1) # B x C_1 x H x W => B x C_1 x HW - feats = feats.view(feats.size(0), feats.size(1), -1) # B x C_2 x H x W => B x C_2 x HW - feats = feats.permute(0, 2, 1) # B x HW x C_2 - probs = probs.softmax(dim=2) # B x C_1 x HW + probs = probs.view(probs.size(0), probs.size(1), -1) # B x C_1 x H x W => B x C_1 x HW + feats = feats.view(feats.size(0), feats.size(1), -1) # B x C_2 x H x W => B x C_2 x HW + feats = feats.permute(0, 2, 1) # B x HW x C_2 + probs = probs.softmax(dim=2) # B x C_1 x HW # B x C_1 x HW @ B x HW x C_2 => B x C_1 x C_2 => B x C_2 x C_1 => B x C_2 x C_1 x 1 ocr_context = torch.matmul(probs, feats).permute(0, 2, 1).unsqueeze(3) return ocr_context + # class ObjectAttentionBlock2D(nn.Module): -''' +""" The basic implementation for object context block Input: N X C X H X W @@ -44,7 +46,8 @@ def forward(self, feats, probs): key_channels : the dimension after the key/query transform Return: N X C X H X W -''' +""" + class SpatialOCR(nn.Module): """ @@ -57,14 +60,8 @@ class SpatialOCR(nn.Module): norm_layer (): Normalization layer to use norm_act (str): activation to use in `norm_layer` """ - def __init__( - self, - in_channels, - key_channels, - out_channels, - norm_layer=ABN, - norm_act="relu" - ): + + def __init__(self, in_channels, key_channels, out_channels, norm_layer=ABN, norm_act="relu"): super().__init__() self.in_channels = in_channels @@ -72,28 +69,25 @@ def __init__( self.f_pixel = nn.Sequential( conv1x1(in_channels, key_channels, bias=True), - norm_layer(key_channels, activation=norm_act), + norm_layer(key_channels, activation=norm_act), conv1x1(key_channels, key_channels, bias=True), norm_layer(key_channels, activation=norm_act), ) self.f_object = nn.Sequential( conv1x1(in_channels, key_channels, bias=True), - norm_layer(key_channels, activation=norm_act), + norm_layer(key_channels, activation=norm_act), conv1x1(key_channels, key_channels, bias=True), norm_layer(key_channels, activation=norm_act), ) self.f_down = nn.Sequential( - conv1x1(in_channels, key_channels, bias=True), - norm_layer(key_channels, activation=norm_act), + conv1x1(in_channels, key_channels, bias=True), norm_layer(key_channels, activation=norm_act), ) self.f_up = nn.Sequential( - conv1x1(key_channels, in_channels, bias=True), - norm_layer(in_channels, activation=norm_act), + conv1x1(key_channels, in_channels, bias=True), norm_layer(in_channels, activation=norm_act), ) self.conv_bn = nn.Sequential( - conv1x1(2 * in_channels, out_channels, bias=True), - norm_layer(out_channels, activation=norm_act), + conv1x1(2 * in_channels, out_channels, bias=True), norm_layer(out_channels, activation=norm_act), ) def forward(self, feats, proxy_feats): @@ -112,8 +106,8 @@ def forward(self, feats, proxy_feats): # sim_map.shape = B x H*W//16 x 256 @ B x 256 x C => B x H*W//16 x C sim_map = torch.matmul(query, key) - sim_map = (self.key_channels**-.5) * sim_map - sim_map = sim_map.softmax(dim=-1) + sim_map = (self.key_channels ** -0.5) * sim_map + sim_map = sim_map.softmax(dim=-1) # add bg context ... # context.shape = B x H*W//16 x C @ B x C x 256 => B x H*W//16 x 256 @@ -124,4 +118,4 @@ def forward(self, feats, proxy_feats): context = self.f_up(context) # concat and project output = self.conv_bn(torch.cat([context, feats], 1)) - return output \ No newline at end of file + return output diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index 4e7b638..c9511b5 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -4,8 +4,10 @@ import torch.nn as nn import torch.nn.functional as F + class Conv2dSamePadding(nn.Conv2d): - """Assymetric padding matching TensorFlow `same`""" + """Assymetric padding matching TensorFlow `same`""" + def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride[1]) - 1) * self.stride[1] - w + self.kernel_size[1] @@ -13,9 +15,10 @@ def forward(self, x): x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) return super().forward(x) - + class MaxPool2dSamePadding(nn.MaxPool2d): - """Assymetric padding matching TensorFlow `same`""" + """Assymetric padding matching TensorFlow `same`""" + def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size @@ -23,20 +26,20 @@ def forward(self, x): x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) return super().forward(x) - + def conv_to_same_conv(module): """Turn All Conv2d into SameConv2d to match TF padding""" module_output = module - # skip 1x1 convs + # skip 1x1 convs if isinstance(module, nn.Conv2d) and module.kernel_size[0] != 1: module_output = Conv2dSamePadding( in_channels=module.in_channels, out_channels=module.out_channels, kernel_size=module.kernel_size, stride=module.stride, - padding=0, # explicitly set to 0 + padding=0, # explicitly set to 0 dilation=module.dilation, - groups=module.groups, + groups=module.groups, bias=module.bias is not None, ) with torch.no_grad(): @@ -51,16 +54,15 @@ def conv_to_same_conv(module): del module return module_output + def maxpool_to_same_maxpool(module): """Turn All MaxPool2d into SameMaxPool2d to match TF padding""" module_output = module if isinstance(module, nn.MaxPool2d): module_output = MaxPool2dSamePadding( - kernel_size=module.kernel_size, - stride=module.stride, - padding=0, # explicitly set to 0 + kernel_size=module.kernel_size, stride=module.stride, padding=0, # explicitly set to 0 ) for name, child in module.named_children(): module_output.add_module(name, maxpool_to_same_maxpool(child)) del module - return module_output \ No newline at end of file + return module_output diff --git a/pytorch_tools/modules/weight_standartization.py b/pytorch_tools/modules/weight_standartization.py index 63d0b5d..3e0edd7 100644 --- a/pytorch_tools/modules/weight_standartization.py +++ b/pytorch_tools/modules/weight_standartization.py @@ -3,39 +3,39 @@ import torch.nn.functional as F # implements idea from `Weight Standardization` paper https://arxiv.org/abs/1903.10520 -# eps is inside sqrt to avoid overflow Idea from https://arxiv.org/abs/1911.05920 +# eps is inside sqrt to avoid overflow Idea from https://arxiv.org/abs/1911.05920 class WS_Conv2d(nn.Conv2d): - def forward(self, x): weight = self.weight var, mean = torch.var_mean(weight, dim=[1, 2, 3], keepdim=True, unbiased=False) weight = (weight - mean) / torch.sqrt(var + 1e-7) return F.conv2d(x, weight, self.bias, self.stride, self.padding, self.dilation, self.groups) + # code from SyncBatchNorm in pytorch def conv_to_ws_conv(module): - module_output = module - if isinstance(module, torch.nn.Conv2d): - module_output = WS_Conv2d( - in_channels=module.in_channels, - out_channels=module.out_channels, - kernel_size=module.kernel_size, - stride=module.stride, - padding=module.padding, - dilation=module.dilation, - # groups are also present in DepthWiseConvs which we don't want to patch - # TODO: fix this - groups=module.groups, - bias=module.bias is not None, - ) - with torch.no_grad(): # not sure if torch.no_grad is needed. but just in case - module_output.weight.copy_(module.weight) - module_output.weight.requires_grad = module.weight.requires_grad - if module.bias is not None: - module_output.bias.copy_(module.bias) - module_output.bias.requires_grad = module.bias.requires_grad + module_output = module + if isinstance(module, torch.nn.Conv2d): + module_output = WS_Conv2d( + in_channels=module.in_channels, + out_channels=module.out_channels, + kernel_size=module.kernel_size, + stride=module.stride, + padding=module.padding, + dilation=module.dilation, + # groups are also present in DepthWiseConvs which we don't want to patch + # TODO: fix this + groups=module.groups, + bias=module.bias is not None, + ) + with torch.no_grad(): # not sure if torch.no_grad is needed. but just in case + module_output.weight.copy_(module.weight) + module_output.weight.requires_grad = module.weight.requires_grad + if module.bias is not None: + module_output.bias.copy_(module.bias) + module_output.bias.requires_grad = module.bias.requires_grad - for name, child in module.named_children(): - module_output.add_module(name, conv_to_ws_conv(child)) - del module - return module_output \ No newline at end of file + for name, child in module.named_children(): + module_output.add_module(name, conv_to_ws_conv(child)) + del module + return module_output diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py index 84435cc..5fc177f 100644 --- a/tests/detection_models/test_det_models.py +++ b/tests/detection_models/test_det_models.py @@ -8,7 +8,7 @@ import pytorch_tools as pt import pytorch_tools.detection_models as pt_det -# all weights were tested on 05.2020. for now only leave one model for faster tests +# all weights were tested on 05.2020. for now only leave one model for faster tests MODEL_NAMES = [ "efficientdet_b0", # "efficientdet_b1", @@ -25,10 +25,13 @@ } INP = torch.ones(1, 3, 512, 512) + + @torch.no_grad() def _test_forward(model): return model(INP) + @pytest.mark.parametrize("arch", MODEL_NAMES) def test_coco_pretrain(arch): m = pt_det.__dict__[arch](pretrained="coco").cuda() @@ -41,7 +44,8 @@ def test_coco_pretrain(arch): im = np.array(im.resize((inp_size, inp_size))) im_t = tensor_from_rgb_image(preprocess_fn(im)).unsqueeze(0).float().cuda() boxes, scores, classes = m.predict(im_t) - assert classes[0, 0] == im_cls # check that most confident bbox is correct + assert classes[0, 0] == im_cls # check that most confident bbox is correct + @pytest.mark.parametrize("arch", MODEL_NAMES[:1]) def test_pretrain_custom_num_classes(arch): From c2883a10732a0e2be358e6a29ab1d05860e634c5 Mon Sep 17 00:00:00 2001 From: zakajd Date: Mon, 25 May 2020 19:58:17 +0300 Subject: [PATCH 40/54] fix tests to use TF same padding --- tests/detection_models/test_det_models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py index 5fc177f..19e6a09 100644 --- a/tests/detection_models/test_det_models.py +++ b/tests/detection_models/test_det_models.py @@ -34,7 +34,8 @@ def _test_forward(model): @pytest.mark.parametrize("arch", MODEL_NAMES) def test_coco_pretrain(arch): - m = pt_det.__dict__[arch](pretrained="coco").cuda() + # want TF same padding for better results + m = pt_det.__dict__[arch](pretrained="coco", match_tf_same_padding=True).cuda() m.eval() # get size of the images used for pretraining inp_size = m.pretrained_settings["input_size"][-1] From 655d6ea1bd975f83ffd0d9e38c88e69d23bddee6 Mon Sep 17 00:00:00 2001 From: zakajd Date: Tue, 26 May 2020 11:58:55 +0300 Subject: [PATCH 41/54] make swish & mish not inplace again. inplace version is incorrect --- pytorch_tools/modules/activations.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytorch_tools/modules/activations.py b/pytorch_tools/modules/activations.py index 78bcc41..4719d3c 100644 --- a/pytorch_tools/modules/activations.py +++ b/pytorch_tools/modules/activations.py @@ -27,7 +27,7 @@ class ACT(Enum): @torch.jit.script def swish_jit_fwd(x): - return x.mul_(torch.sigmoid(x)) + return x.mul(torch.sigmoid(x)) @torch.jit.script @@ -87,7 +87,7 @@ def forward(self, x): @torch.jit.script def mish_jit_fwd(x): - return x.mul_(torch.tanh(F.softplus(x))) + return x.mul(torch.tanh(F.softplus(x))) @torch.jit.script From 9951c8576a0fb0ee18abbfab8cd957f94fdaf955 Mon Sep 17 00:00:00 2001 From: bonlime Date: Wed, 27 May 2020 16:47:16 +0300 Subject: [PATCH 42/54] Fully working RetinaNet (#69) * add Activated No Norm (which is a simple activation) * fully working Retina matching both mmdetection and TF Tpu versions * add & update docs * allow loading weights in RetinaNet * add tests * fix typos Co-authored-by: zakajd --- pytorch_tools/detection_models/__init__.py | 4 + .../detection_models/efficientdet.py | 4 + pytorch_tools/detection_models/retinanet.py | 181 ++++++++++++++---- pytorch_tools/modules/__init__.py | 3 + pytorch_tools/modules/activated_batch_norm.py | 4 + pytorch_tools/modules/activated_no_norm.py | 37 ++++ pytorch_tools/segmentation_models/linknet.py | 2 +- tests/detection_models/test_det_models.py | 23 ++- 8 files changed, 211 insertions(+), 47 deletions(-) create mode 100644 pytorch_tools/modules/activated_no_norm.py diff --git a/pytorch_tools/detection_models/__init__.py b/pytorch_tools/detection_models/__init__.py index f44058f..62c83ad 100644 --- a/pytorch_tools/detection_models/__init__.py +++ b/pytorch_tools/detection_models/__init__.py @@ -1,4 +1,8 @@ from .retinanet import RetinaNet +from .retinanet import retinanet_r50_fpn +from .retinanet import retinanet_r101_fpn + +from .efficientdet import EfficientDet from .efficientdet import efficientdet_b0 from .efficientdet import efficientdet_b1 from .efficientdet import efficientdet_b2 diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index 817ad9b..24c9442 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -35,6 +35,7 @@ class EfficientDet(nn.Module): def __init__( self, + pretrained="coco", # Not used. here for proper signature encoder_name="efficientnet_b0", encoder_weights="imagenet", pyramid_channels=64, @@ -79,6 +80,9 @@ def make_head(out_size): layers += [DepthwiseSeparableConv(pyramid_channels, out_size, use_norm=False)] return nn.ModuleList(layers) + # The convolution layers in the head are shared among all levels, but + # each level has its batch normalization to capture the statistical + # difference among different levels. def make_head_norm(): return nn.ModuleList( [ diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index 575f193..f179ec7 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -1,82 +1,181 @@ +import logging +from copy import deepcopy +from functools import wraps + import torch import torch.nn as nn -# import torch.nn.functional as F from pytorch_tools.modules.fpn import FPN - -# from pytorch_tools.modules.bifpn import BiFPN from pytorch_tools.modules import bn_from_name - -# from pytorch_tools.modules.residual import conv1x1 from pytorch_tools.modules.residual import conv3x3 - -# from pytorch_tools.modules.decoder import SegmentationUpsample -# from pytorch_tools.utils.misc import initialize from pytorch_tools.segmentation_models.encoders import get_encoder +import pytorch_tools.utils.box as box_utils +from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS class RetinaNet(nn.Module): + """RetinaNet + Main difference from other implementations are: + * support of any custom encoder from this repo + * optional normalization layer in box classification head + * ability to freeze batch norm in encoder with one line + + Args: + pretrained (str): one of `coco` or None. if `coco` - load pretrained weights + encoder_name (str): name of classification model (without last dense layers) used as feature + extractor to build detection model + encoder_weights (str): one of ``None`` (random initialization), ``imagenet`` (pre-trained on ImageNet) + pyramid_channels (int): size of features after FPN. Default 256 + num_classes (int): a number of classes to predict + class_outputs shape is (BS, *, NUM_CLASSES) where each row in * corresponds to one bbox + encoder_norm_layer (str): Normalization layer to use in encoder. If using pretrained + it should be the same as in pretrained weights + encoder_norm_act (str): Activation for normalization layer in encoder + decoder_norm_layer (str): Normalization to use in head convolutions. Default (none) is not to use normalization. + Current implementation is optimized for `GroupNorm`, not `BatchNorm` check code for details + decoder_norm_act (str): Activation for normalization layer in head convolutions + + Ref: + Focal Loss for Dense Object Detection - https://arxiv.org/abs/1708.02002 + Mmdetection - https://github.com/open-mmlab/mmdetection/ (at commit b9daf23) + TF TPU version - https://github.com/tensorflow/tpu/tree/master/models/official/retinanet + """ + def __init__( self, - encoder_name="resnet34", + pretrained="coco", # not used here for proper signature + encoder_name="resnet50", encoder_weights="imagenet", pyramid_channels=256, num_classes=80, - norm_layer="abn", - norm_act="relu", + # drop_connect_rate=0, # TODO: add + encoder_norm_layer="abn", + encoder_norm_act="relu", + decoder_norm_layer="none", # None by default to match detectron & mmdet versions + decoder_norm_act="relu", **encoder_params, ): super().__init__() self.encoder = get_encoder( encoder_name, - norm_layer=norm_layer, - norm_act=norm_act, + norm_layer=encoder_norm_layer, + norm_act=encoder_norm_act, encoder_weights=encoder_weights, **encoder_params, ) - norm_layer = bn_from_name(norm_layer) - self.pyramid6 = conv3x3(256, 256, 2, bias=True) - self.pyramid7 = conv3x3(256, 256, 2, bias=True) - self.fpn = FPN(self.encoder.out_shapes[:-2], pyramid_channels=pyramid_channels,) + norm_layer = bn_from_name(decoder_norm_layer) + self.pyramid6 = nn.Sequential( + conv3x3(self.encoder.out_shapes[0], pyramid_channels, 2, bias=True), + norm_layer(pyramid_channels, activation="identity"), + ) + self.pyramid7 = nn.Sequential( + conv3x3(pyramid_channels, pyramid_channels, 2, bias=True), + norm_layer(pyramid_channels, activation="identity"), + ) + self.fpn = FPN(self.encoder.out_shapes[:-2], pyramid_channels=pyramid_channels) - def make_head(out_size): + def make_final_convs(): layers = [] for _ in range(4): - # some implementations don't use BN here but I think it's needed - # TODO: test how it affects results - # upd. removed norm_layer. maybe change to group_norm later - layers += [nn.Conv2d(256, 256, 3, padding=1)] # norm_layer(256, activation=norm_act) - # layers += [nn.Conv2d(256, 256, 3, padding=1), nn.ReLU()] - - layers += [nn.Conv2d(256, out_size, 3, padding=1)] + layers += [conv3x3(pyramid_channels, pyramid_channels, bias=True)] + # Norm here is fine for GroupNorm but for BN it should be implemented the other way + # see EffDet for example. Maybe need to change this implementation to align with EffDet + layers += [norm_layer(pyramid_channels, activation=decoder_norm_act)] return nn.Sequential(*layers) - self.ratios = [1.0, 2.0, 0.5] - self.scales = [4 * 2 ** (i / 3) for i in range(3)] - anchors = len(self.ratios) * len(self.scales) # 9 - - self.cls_head = make_head(num_classes * anchors) - self.box_head = make_head(4 * anchors) + anchors_per_location = 9 + self.cls_convs = make_final_convs() + self.cls_head_conv = conv3x3(pyramid_channels, num_classes * anchors_per_location, bias=True) + self.box_convs = make_final_convs() + self.box_head_conv = conv3x3(pyramid_channels, 4 * anchors_per_location, bias=True) self.num_classes = num_classes - def forward(self, x): + # Name from mmdetectin for convenience + def extract_features(self, x): + """Extract features from backbone + enchance with FPN""" # don't use p2 and p1 p5, p4, p3, _, _ = self.encoder(x) - # enhance features - p5, p4, p3 = self.fpn([p5, p4, p3]) # coarser FPN levels p6 = self.pyramid6(p5) - p7 = self.pyramid7(p6.relu()) + p7 = self.pyramid7(p6.relu()) # in mmdet there is no relu here. but i think it's needed + # enhance features + p5, p4, p3 = self.fpn([p5, p4, p3]) # want features from lowest OS to highest to align with `generate_anchors_boxes` function features = [p3, p4, p5, p6, p7] - # TODO: (18.03.20) TF implementation has additional BN here before class/box outputs + return features + + def forward(self, x): + features = self.extract_features(x) class_outputs = [] box_outputs = [] - for f in features: - cls_anchor = self.cls_head(f).transpose(1, 3).contiguous().view(x.shape[0], -1, self.num_classes) - box_anchor = self.box_head(f).transpose(1, 3).contiguous().view(x.shape[0], -1, 4) - class_outputs.append(cls_anchor) - box_outputs.append(box_anchor) + for feat in features: + cls_feat = self.cls_head_conv(self.cls_convs(feat)) + box_feat = self.box_head_conv(self.box_convs(feat)) + cls_feat = cls_feat.permute(0, 2, 3, 1).contiguous().view(x.size(0), -1, self.num_classes) + box_feat = box_feat.permute(0, 2, 3, 1).contiguous().view(x.size(0), -1, 4) + class_outputs.append(cls_feat) + box_outputs.append(box_feat) class_outputs = torch.cat(class_outputs, 1) box_outputs = torch.cat(box_outputs, 1) return class_outputs, box_outputs + + @torch.no_grad() + def predict(self, x): + """Run forward on given images and decode raw prediction into bboxes""" + class_outputs, box_outputs = self.forward(x) + anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] + out_bboxes, out_scores, out_classes = box_utils.decode( + class_outputs, box_outputs, anchors, img_shape=x.shape[-2:] + ) + return out_bboxes, out_scores, out_classes + + +# Don't really know input size for the models. 512 is just a guess +PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct": 1, "num_classes": 80} + +# weights below were ported from caffe to mmdetection and them ported again by @bonlime +# mmdetection resnet is slightly different (stride 2 in conv1x1 instead of conv3x3) +# and order of anchors is also different so it's impossible to do inference using this weights but they work +# much better for transfer learning than starting from imagenet pretrain +# fmt: off +CFGS = { + "retinanet_r50_fpn": { + "default": {"params": {"encoder_name":"resnet50"}, **PRETRAIN_SETTINGS}, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/retinanet_r50_fpn_3x_coco.pth",}, + }, + "retinanet_r101_fpn": { + "default": {"params": {"encoder_name":"resnet101"}, **PRETRAIN_SETTINGS}, + "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/retinanet_r101_fpn_2x_coco.pth",}, + }, +} +# fmt: on + + +def _retinanet(arch, pretrained=None, **kwargs): + cfgs = deepcopy(CFGS) + cfg_settings = cfgs[arch]["default"] + cfg_params = cfg_settings.pop("params") + kwargs.update(cfg_params) + model = RetinaNet(**kwargs) + if pretrained: + state_dict = torch.hub.load_state_dict_from_url(cfgs[arch][pretrained]["url"]) + kwargs_cls = kwargs.get("num_classes", None) + if kwargs_cls and kwargs_cls != cfg_settings["num_classes"]: + logging.warning( + f"Using model pretrained for {cfg_settings['num_classes']} classes with {kwargs_cls} classes. Last layer is initialized randomly" + ) + state_dict["cls_head_conv.weight"] = model.state_dict()["cls_head_conv.weight"] + state_dict["cls_head_conv.bias"] = model.state_dict()["cls_head_conv.bias"] + model.load_state_dict(state_dict) + setattr(model, "pretrained_settings", cfg_settings) + return model + + +@wraps(RetinaNet) +def retinanet_r50_fpn(pretrained="coco", **kwargs): + return _retinanet("retinanet_r50_fpn", pretrained, **kwargs) + + +@wraps(RetinaNet) +def retinanet_r101_fpn(pretrained="coco", **kwargs): + return _retinanet("retinanet_r101_fpn", pretrained, **kwargs) diff --git a/pytorch_tools/modules/__init__.py b/pytorch_tools/modules/__init__.py index 9612fa1..3a436df 100644 --- a/pytorch_tools/modules/__init__.py +++ b/pytorch_tools/modules/__init__.py @@ -22,6 +22,7 @@ from .activated_batch_norm import ABN from .activated_group_norm import AGN +from .activated_no_norm import NoNormAct from inplace_abn import InPlaceABN, InPlaceABNSync @@ -37,5 +38,7 @@ def bn_from_name(norm_name): return partial(ABN, frozen=True) elif norm_name in ("agn", "groupnorm", "group_norm"): return AGN + elif norm_name in ("none",): + return NoNormAct else: raise ValueError(f"Normalization {norm_name} not supported") diff --git a/pytorch_tools/modules/activated_batch_norm.py b/pytorch_tools/modules/activated_batch_norm.py index 8a32930..f4e0786 100644 --- a/pytorch_tools/modules/activated_batch_norm.py +++ b/pytorch_tools/modules/activated_batch_norm.py @@ -45,6 +45,8 @@ def __init__( self.momentum = momentum self.activation = ACT(activation) self.activation_param = activation_param + self.frozen = frozen + if frozen: self.register_buffer("weight", torch.ones(num_features)) self.register_buffer("bias", torch.zeros(num_features)) @@ -101,4 +103,6 @@ def extra_repr(self): rep = "{num_features}, eps={eps}, momentum={momentum}, affine={affine}, activation={activation}" if self.activation in ["leaky_relu", "elu"]: rep += "[{activation_param}]" + if self.frozen: + rep += ", frozen=True" return rep.format(**self.__dict__) diff --git a/pytorch_tools/modules/activated_no_norm.py b/pytorch_tools/modules/activated_no_norm.py new file mode 100644 index 0000000..7271287 --- /dev/null +++ b/pytorch_tools/modules/activated_no_norm.py @@ -0,0 +1,37 @@ +import torch +import torch.nn as nn +import torch.nn.functional as F +from torch.nn import init +from torch.nn.parameter import Parameter + +from .activations import ACT +from .activations import ACT_FUNC_DICT + + +class NoNormAct(nn.Module): + """Activated No Normalization + This is just an activation wrapped in class to allow easy swaping with BN and GN + Args: + num_features (int): Not used. here for compatability + activation (str): Name of the activation functions + activation_param (float): Negative slope for the `leaky_relu` activation. + """ + + def __init__(self, num_features, activation="relu", activation_param=0.01): + super().__init__() + self.num_features = num_features + self.activation = ACT(activation) + self.activation_param = activation_param + + def forward(self, x): + func = ACT_FUNC_DICT[self.activation] + if self.activation == ACT.LEAKY_RELU: + return func(x, inplace=True, negative_slope=self.activation_param) + elif self.activation == ACT.ELU: + return func(x, inplace=True, alpha=self.activation_param) + else: + return func(x, inplace=True) + + def extra_repr(self): + rep = "activation={activation}" + return rep.format(**self.__dict__) diff --git a/pytorch_tools/segmentation_models/linknet.py b/pytorch_tools/segmentation_models/linknet.py index e755c41..e51636d 100644 --- a/pytorch_tools/segmentation_models/linknet.py +++ b/pytorch_tools/segmentation_models/linknet.py @@ -52,7 +52,7 @@ class Linknet(EncoderDecoder): drop_rate (float): Probability of spatial dropout on last feature map norm_layer (str): Normalization layer to use. One of 'abn', 'inplaceabn'. The inplace version lowers memory footprint. But increases backward time. Defaults to 'abn'. - norm_act (str): Activation for normalizion layer. 'inplaceabn' doesn't support `ReLU` activation. + norm_act (str): Activation for normalization layer. 'inplaceabn' doesn't support `ReLU` activation. Returns: ``torch.nn.Module``: **Linknet** .. _Linknet: diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py index 19e6a09..23715a2 100644 --- a/tests/detection_models/test_det_models.py +++ b/tests/detection_models/test_det_models.py @@ -8,15 +8,18 @@ import pytorch_tools as pt import pytorch_tools.detection_models as pt_det + # all weights were tested on 05.2020. for now only leave one model for faster tests MODEL_NAMES = [ "efficientdet_b0", + "retinanet_r50_fpn", # "efficientdet_b1", # "efficientdet_b2", # "efficientdet_b3", # "efficientdet_b4", # "efficientdet_b5", # "efficientdet_b6", + # "retinanet_r101_fpn", ] # format "coco image class: PIL Image" @@ -24,7 +27,7 @@ 17: Image.open("tests/imgs/dog.jpg"), } -INP = torch.ones(1, 3, 512, 512) +INP = torch.ones(1, 3, 512, 512).cuda() @torch.no_grad() @@ -35,7 +38,10 @@ def _test_forward(model): @pytest.mark.parametrize("arch", MODEL_NAMES) def test_coco_pretrain(arch): # want TF same padding for better results - m = pt_det.__dict__[arch](pretrained="coco", match_tf_same_padding=True).cuda() + kwargs = {} + if "eff" in arch: + kwargs["match_tf_same_padding"] = True + m = pt_det.__dict__[arch](pretrained="coco", **kwargs).cuda() m.eval() # get size of the images used for pretraining inp_size = m.pretrained_settings["input_size"][-1] @@ -45,10 +51,17 @@ def test_coco_pretrain(arch): im = np.array(im.resize((inp_size, inp_size))) im_t = tensor_from_rgb_image(preprocess_fn(im)).unsqueeze(0).float().cuda() boxes, scores, classes = m.predict(im_t) - assert classes[0, 0] == im_cls # check that most confident bbox is correct + # check that most confident bbox is close to correct class. The reason for such strange test is + # because in different models class mappings are shifted by +- 1 + assert (classes[0, 0] - im_cls) < 2 -@pytest.mark.parametrize("arch", MODEL_NAMES[:1]) +@pytest.mark.parametrize("arch", MODEL_NAMES[:2]) def test_pretrain_custom_num_classes(arch): - m = pt_det.__dict__[arch](pretrained="coco", num_classes=80).eval() + m = pt_det.__dict__[arch](pretrained="coco", num_classes=80).eval().cuda() + _test_forward(m) + +@pytest.mark.parametrize("arch", MODEL_NAMES[:2]) +def test_encoder_frozenabn(arch): + m = pt_det.__dict__[arch](encoder_norm_layer="frozenabn").eval().cuda() _test_forward(m) From aea23a1bc16ed816a0f7538b5d6fe30106266a7e Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 29 May 2020 13:04:28 +0300 Subject: [PATCH 43/54] fix initialization of EffNet models --- .../detection_models/efficientdet.py | 10 +++++++ pytorch_tools/detection_models/retinanet.py | 9 ++++++ pytorch_tools/models/efficientnet.py | 1 - pytorch_tools/utils/misc.py | 30 ++++++++++++++----- 4 files changed, 41 insertions(+), 9 deletions(-) diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index 24c9442..adbe83e 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -19,6 +19,7 @@ import pytorch_tools.utils.box as box_utils from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS +from pytorch_tools.utils.misc import initialize_iterator def patch_bn(module): @@ -105,6 +106,7 @@ def make_head_norm(): self.num_classes = num_classes patch_bn(self) + self._initialize_weights() if match_tf_same_padding: conv_to_same_conv(self) maxpool_to_same_maxpool(self) @@ -153,6 +155,14 @@ def predict(self, x): ) return out_bboxes, out_scores, out_classes + def _initialize_weights(self): + # init everything except encoder + no_encoder_m = [m for n, m in self.named_modules() if not "encoder" in n] + initialize_iterator(no_encoder_m) + # need to init last bias so that after sigmoid it's 0.01 + cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 + nn.init.constant_(self.cls_head_convs[-1][1].bias, cls_bias_init) + PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct": 1, "num_classes": 90} diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index f179ec7..1ca1078 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -11,6 +11,7 @@ from pytorch_tools.segmentation_models.encoders import get_encoder import pytorch_tools.utils.box as box_utils from pytorch_tools.utils.misc import DEFAULT_IMAGENET_SETTINGS +from pytorch_tools.utils.misc import initialize_iterator class RetinaNet(nn.Module): @@ -89,6 +90,7 @@ def make_final_convs(): self.box_convs = make_final_convs() self.box_head_conv = conv3x3(pyramid_channels, 4 * anchors_per_location, bias=True) self.num_classes = num_classes + self. _initialize_weights() # Name from mmdetectin for convenience def extract_features(self, x): @@ -129,6 +131,13 @@ def predict(self, x): ) return out_bboxes, out_scores, out_classes + def _initialize_weights(self): + # init everything except encoder + no_encoder_m = [m for n, m in self.named_modules() if not "encoder" in n] + initialize_iterator(no_encoder_m) + # need to init last bias so that after sigmoid it's 0.01 + cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 + nn.init.constant_(self.cls_head_conv.bias, cls_bias_init) # Don't really know input size for the models. 512 is just a guess PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct": 1, "num_classes": 80} diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index 1510c9c..ec6207d 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -139,7 +139,6 @@ def __init__( self.dropout = nn.Dropout(drop_rate, inplace=True) self.classifier = nn.Linear(num_features, num_classes) - # TODO: change to init from official repo. It may be the source of problem for training from scratch initialize(self) def encoder_features(self, x): diff --git a/pytorch_tools/utils/misc.py b/pytorch_tools/utils/misc.py index 72b5fca..cf1fc85 100644 --- a/pytorch_tools/utils/misc.py +++ b/pytorch_tools/utils/misc.py @@ -11,15 +11,29 @@ from functools import partial -def initialize(model): - for m in model.modules(): - if isinstance(m, nn.Conv2d): - nn.init.kaiming_normal_(m.weight, mode="fan_out", nonlinearity="relu") - elif isinstance(m, nn.BatchNorm2d): - nn.init.constant_(m.weight, 1) +def initialize_fn(m): + """m (nn.Module): module""" + if isinstance(m, nn.Conv2d): + # nn.init.kaiming_uniform_ doesn't take into account groups + # remove when https://github.com/pytorch/pytorch/issues/23854 is resolved + # this is needed for proper init of EffNet models + fan_out = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + fan_out //= m.groups + m.weight.data.normal_(0, math.sqrt(2.0 / fan_out)) + if m.bias is not None: nn.init.constant_(m.bias, 0) - elif isinstance(m, nn.Linear): - nn.init.kaiming_uniform_(m.weight, mode="fan_in", nonlinearity="linear") + # No check for BN because in PyTorch it is initialized with 1 & 0 by default + elif isinstance(m, nn.Linear): + nn.init.kaiming_uniform_(m.weight, mode="fan_out", nonlinearity="linear") + nn.init.constant_(m.bias, 0) + +def initialize(module): + for m in module.modules(): + initialize_fn(m) + +def initialize_iterator(module_iterator): + for m in module_iterator: + initialize_fn(m) def set_random_seed(seed): From 1258c2cf211afed5e67aebcb4975cff1e51a599a Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 29 May 2020 19:31:10 +0300 Subject: [PATCH 44/54] fix same MaxPool --- pytorch_tools/modules/tf_same_ops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index c9511b5..a5b3bd2 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -23,7 +23,7 @@ def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size pad_h = (math.ceil(h / self.stride) - 1) * self.stride - h + self.kernel_size - x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=-float("inf")) return super().forward(x) From 22c6b1fffef90c7f76974164302b35a1926dc1cd Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 29 May 2020 19:31:59 +0300 Subject: [PATCH 45/54] patch BN in effnet by default --- pytorch_tools/detection_models/efficientdet.py | 8 +++++++- pytorch_tools/models/efficientnet.py | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index adbe83e..c0a8776 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -111,7 +111,9 @@ def make_head_norm(): conv_to_same_conv(self) maxpool_to_same_maxpool(self) - def forward(self, x): + # Name from mmdetectin for convenience + def extract_features(self, x): + """Extract features from backbone + enchance with BiFPN""" # don't use p2 and p1 p5, p4, p3, _, _ = self.encoder(x) # coarser FPN levels @@ -122,6 +124,10 @@ def forward(self, x): features = self.bifpn(features) # want features from lowest OS to highest to align with `generate_anchors_boxes` function features = list(reversed(features)) + return features + + def forward(self, x): + features = self.extract_features(x) class_outputs = [] box_outputs = [] for feat, (cls_bns, box_bns) in zip(features, zip(self.cls_head_norms, self.box_head_norms)): diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index ec6207d..514df3b 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -139,6 +139,7 @@ def __init__( self.dropout = nn.Dropout(drop_rate, inplace=True) self.classifier = nn.Linear(num_features, num_classes) + patch_bn(self) # adjust epsilon initialize(self) def encoder_features(self, x): @@ -420,7 +421,6 @@ def _efficientnet(arch, pretrained=None, **kwargs): if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels state_dict["conv_stem.weight"] = repeat_channels(state_dict["conv_stem.weight"], kwargs["in_channels"]) model.load_state_dict(state_dict) - patch_bn(model) # adjust epsilon setattr(model, "pretrained_settings", cfg_settings) return model From defbab1cf98dd5cc1f66353bbb4aeb864fa699b2 Mon Sep 17 00:00:00 2001 From: zakajd Date: Fri, 29 May 2020 19:34:01 +0300 Subject: [PATCH 46/54] change init of last layer for Unet & HRNet --- pytorch_tools/segmentation_models/hrnet.py | 16 +++++++++------- pytorch_tools/segmentation_models/unet.py | 7 ++++--- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pytorch_tools/segmentation_models/hrnet.py b/pytorch_tools/segmentation_models/hrnet.py index 9690ea9..8e7c3dd 100644 --- a/pytorch_tools/segmentation_models/hrnet.py +++ b/pytorch_tools/segmentation_models/hrnet.py @@ -112,8 +112,7 @@ def __init__( self.name = f"segm-{encoder_name}" # use lower momemntum patch_bn_mom(self) - # works better without init (for some reason) - # self._init_weights() + self._init_weights() def forward(self, x): """Sequentially pass `x` trough model`s `encoder` and `head` (return logits!)""" @@ -139,12 +138,15 @@ def forward(self, x): return x def _init_weights(self): - # init all weights except encoder (to allow pretrain) - initialize(self.head) + # it works better if we only init last bias not whole decoder part + # set last layer bias for better convergence with sigmoid loss + # -4.59 = -np.log((1 - 0.01) / 0.01) if self.OCR: - initialize(self.aux_head) - initialize(self.ocr_distri_head) - initialize(self.conv3x3) + nn.init.constant_(self.head.bias, -4.59) + nn.init.constant_(self.aux_head[2].bias, -4.59) + else: + nn.init.constant_(self.head[2].bias, -4.59) + # fmt: off SETTINGS = { diff --git a/pytorch_tools/segmentation_models/unet.py b/pytorch_tools/segmentation_models/unet.py index 42eae40..5490a6e 100644 --- a/pytorch_tools/segmentation_models/unet.py +++ b/pytorch_tools/segmentation_models/unet.py @@ -40,9 +40,7 @@ def __init__( self.layer4 = UnetDecoderBlock(in_channels[3], out_channels[3], **bn_params) self.layer5 = UnetDecoderBlock(in_channels[4], out_channels[4], **bn_params) self.dropout = nn.Dropout2d(drop_rate, inplace=False) # inplace=True raises a backprop error - self.final_conv = conv1x1(out_channels[4], final_channels) - # it works much better without initializing decoder. maybe need to investigate into this issue - # initialize(self) + self.final_conv = conv1x1(out_channels[4], final_channels, bias=True) def compute_channels(self, encoder_channels, decoder_channels): channels = [ @@ -126,3 +124,6 @@ def __init__( super().__init__(encoder, decoder) self.name = f"u-{encoder_name}" + # set last layer bias for better convergence with sigmoid loss + # -4.59 = -np.log((1 - 0.01) / 0.01) + nn.init.constant_(self.decoder.final_conv.bias, -4.59) \ No newline at end of file From 54a91311e8fdeb494671258fdc93489a7fa5ede8 Mon Sep 17 00:00:00 2001 From: zakajd Date: Mon, 1 Jun 2020 19:08:30 +0300 Subject: [PATCH 47/54] add TF-like RMSprop --- pytorch_tools/optim/__init__.py | 4 ++- pytorch_tools/optim/rmsprop.py | 61 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 pytorch_tools/optim/rmsprop.py diff --git a/pytorch_tools/optim/__init__.py b/pytorch_tools/optim/__init__.py index bcd9e6d..798ccd9 100644 --- a/pytorch_tools/optim/__init__.py +++ b/pytorch_tools/optim/__init__.py @@ -6,6 +6,7 @@ from .radam import RAdam, PlainRAdam from .sgdw import SGDW from .schedulers import LinearLR, ExponentialLR +from .rmsprop import RMSprop from .lookahead import Lookahead from torch import optim @@ -25,7 +26,8 @@ def optimizer_from_name(optim_name): # in this implementation eps in inside sqrt so it can be smaller return partial(AdamW_my, center=True, eps=1e-7) elif optim_name == "rmsprop": - return partial(optim.RMSprop, eps=2e-5) + # in this implementation eps in inside sqrt so it can be smaller + return partial(RMSprop, eps=1e-7) elif optim_name == "radam": return partial(RAdam, eps=2e-5) elif optim_name in ["fused_sgd", "fusedsgd"]: diff --git a/pytorch_tools/optim/rmsprop.py b/pytorch_tools/optim/rmsprop.py new file mode 100644 index 0000000..b1fb033 --- /dev/null +++ b/pytorch_tools/optim/rmsprop.py @@ -0,0 +1,61 @@ +"""Implementation of TF-like RMSprop with epsilong inside sqrt. The only difference is at line 52""" +import torch +from torch.optim import RMSprop as _RMSprop + +class RMSprop(_RMSprop): + @torch.no_grad() + def step(self, closure=None): + """Performs a single optimization step. + Arguments: + closure (callable, optional): A closure that reevaluates the model + and returns the loss. + """ + loss = None + if closure is not None: + with torch.enable_grad(): + loss = closure() + + for group in self.param_groups: + for p in group['params']: + if p.grad is None: + continue + grad = p.grad + if grad.is_sparse: + raise RuntimeError('RMSprop does not support sparse gradients') + state = self.state[p] + + # State initialization + if len(state) == 0: + state['step'] = 0 + state['square_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format) + if group['momentum'] > 0: + state['momentum_buffer'] = torch.zeros_like(p, memory_format=torch.preserve_format) + if group['centered']: + state['grad_avg'] = torch.zeros_like(p, memory_format=torch.preserve_format) + + square_avg = state['square_avg'] + alpha = group['alpha'] + + state['step'] += 1 + + if group['weight_decay'] != 0: + grad = grad.add(p, alpha=group['weight_decay']) + + square_avg.mul_(alpha).addcmul_(grad, grad, value=1 - alpha) + + if group['centered']: + grad_avg = state['grad_avg'] + grad_avg.mul_(alpha).add_(grad, alpha=1 - alpha) + avg = square_avg.addcmul(grad_avg, grad_avg, value=-1).sqrt_().add_(group['eps']) + else: + # avg = square_avg.sqrt().add_(group['eps']) + avg = square_avg.add_(group['eps']).sqrt() + + if group['momentum'] > 0: + buf = state['momentum_buffer'] + buf.mul_(group['momentum']).addcdiv_(grad, avg) + p.add_(buf, alpha=-group['lr']) + else: + p.addcdiv_(grad, avg, value=-group['lr']) + + return loss \ No newline at end of file From 7c7e331906fd32bf5bd56425d1ae4363f5560400 Mon Sep 17 00:00:00 2001 From: zakajd Date: Tue, 2 Jun 2020 14:25:22 +0300 Subject: [PATCH 48/54] also reduce `loss_meter` to save based on reduced loss --- pytorch_tools/fit_wrapper/callbacks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytorch_tools/fit_wrapper/callbacks.py b/pytorch_tools/fit_wrapper/callbacks.py index e8d04fb..70bb146 100644 --- a/pytorch_tools/fit_wrapper/callbacks.py +++ b/pytorch_tools/fit_wrapper/callbacks.py @@ -530,6 +530,7 @@ def _format_meters(loss, metrics): def reduce_metrics(self): # can't reduce AverageMeter so need to reduce every attribute separately meters = self.state.train_metrics + [self.state.train_loss,] + meters = meters + self.state.metric_meters + [self.state.loss_meter,] if self.state.val_loss is not None: meters = meters + self.state.val_metrics + [self.state.val_loss,] reduce_attributes = ["val", "avg", "avg_smooth", "sum", "count"] From be43c0eedebe6e16bdfb96d797a68c9d7bafda6e Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 3 Jun 2020 21:42:28 +0300 Subject: [PATCH 49/54] renaming and minor changes --- pytorch_tools/detection_models/__init__.py | 14 +++--- .../detection_models/efficientdet.py | 44 +++++++++---------- pytorch_tools/models/efficientnet.py | 8 ++++ pytorch_tools/modules/tf_same_ops.py | 6 ++- pytorch_tools/segmentation_models/README.md | 2 +- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/pytorch_tools/detection_models/__init__.py b/pytorch_tools/detection_models/__init__.py index 62c83ad..04079d7 100644 --- a/pytorch_tools/detection_models/__init__.py +++ b/pytorch_tools/detection_models/__init__.py @@ -3,10 +3,10 @@ from .retinanet import retinanet_r101_fpn from .efficientdet import EfficientDet -from .efficientdet import efficientdet_b0 -from .efficientdet import efficientdet_b1 -from .efficientdet import efficientdet_b2 -from .efficientdet import efficientdet_b3 -from .efficientdet import efficientdet_b4 -from .efficientdet import efficientdet_b5 -from .efficientdet import efficientdet_b6 +from .efficientdet import efficientdet_d0 +from .efficientdet import efficientdet_d1 +from .efficientdet import efficientdet_d2 +from .efficientdet import efficientdet_d3 +from .efficientdet import efficientdet_d4 +from .efficientdet import efficientdet_d5 +from .efficientdet import efficientdet_d6 diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index c0a8776..5b3c299 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -37,7 +37,7 @@ class EfficientDet(nn.Module): def __init__( self, pretrained="coco", # Not used. here for proper signature - encoder_name="efficientnet_b0", + encoder_name="efficientnet_d0", encoder_weights="imagenet", pyramid_channels=64, num_fpn_layers=3, @@ -174,7 +174,7 @@ def _initialize_weights(self): # fmt: off CFGS = { - "efficientdet_b0": { + "efficientdet_d0": { "default": { "params": { "encoder_name":"efficientnet_b0", @@ -186,7 +186,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d0.pth",}, }, - "efficientdet_b1": { + "efficientdet_d1": { "default": { "params": { "encoder_name":"efficientnet_b1", @@ -199,7 +199,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d1.pth",}, }, - "efficientdet_b2": { + "efficientdet_d2": { "default": { "params": { "encoder_name":"efficientnet_b2", @@ -212,7 +212,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d2.pth",}, }, - "efficientdet_b3": { + "efficientdet_d3": { "default": { "params": { "encoder_name":"efficientnet_b3", @@ -225,7 +225,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d3.pth",}, }, - "efficientdet_b4": { + "efficientdet_d4": { "default": { "params": { "encoder_name":"efficientnet_b4", @@ -238,7 +238,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d4.pth",}, }, - "efficientdet_b5": { + "efficientdet_d5": { "default": { "params": { "encoder_name":"efficientnet_b5", @@ -251,7 +251,7 @@ def _initialize_weights(self): }, "coco": {"url": "https://github.com/bonlime/pytorch-tools/releases/download/v0.1.5/efficientdet-d5.pth",}, }, - "efficientdet_b6": { + "efficientdet_d6": { "default": { "params": { "encoder_name":"efficientnet_b6", @@ -290,38 +290,38 @@ def _efficientdet(arch, pretrained=None, **kwargs): @wraps(EfficientDet) -def efficientdet_b0(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b0", pretrained, **kwargs) +def efficientdet_d0(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d0", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b1(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b1", pretrained, **kwargs) +def efficientdet_d1(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d1", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b2(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b2", pretrained, **kwargs) +def efficientdet_d2(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d2", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b3(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b3", pretrained, **kwargs) +def efficientdet_d3(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d3", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b4(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b4", pretrained, **kwargs) +def efficientdet_d4(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d4", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b5(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b5", pretrained, **kwargs) +def efficientdet_d5(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d5", pretrained, **kwargs) @wraps(EfficientDet) -def efficientdet_b6(pretrained="coco", **kwargs): - return _efficientdet("efficientdet_b6", pretrained, **kwargs) +def efficientdet_d6(pretrained="coco", **kwargs): + return _efficientdet("efficientdet_d6", pretrained, **kwargs) # No B7 because it's the same model as B6 but with larger input diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index 514df3b..ca5e337 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -24,6 +24,8 @@ from pytorch_tools.modules import bn_from_name from pytorch_tools.modules.residual import InvertedResidual from pytorch_tools.modules.residual import conv1x1, conv3x3 +from pytorch_tools.modules.tf_same_ops import conv_to_same_conv +from pytorch_tools.modules.tf_same_ops import maxpool_to_same_maxpool from pytorch_tools.utils.misc import initialize from pytorch_tools.utils.misc import add_docs_for from pytorch_tools.utils.misc import make_divisible @@ -70,6 +72,8 @@ class EfficientNet(nn.Module): But increases backward time and doesn't support `swish` activation. Defaults to 'abn'. norm_act (str): Activation for normalizion layer. It's reccomended to use `leacky_relu` with `inplaceabn`. Defaults to `swish` + match_tf_same_padding (bool): If True patches Conv and MaxPool to implements tf-like asymmetric padding + Should only be used to validate pretrained weights. Not needed for training. Gives ~10% slowdown """ def __init__( @@ -87,6 +91,7 @@ def __init__( stem_size=32, norm_layer="abn", norm_act="swish", + match_tf_same_padding=False, ): super().__init__() norm_layer = bn_from_name(norm_layer) @@ -141,6 +146,9 @@ def __init__( patch_bn(self) # adjust epsilon initialize(self) + if match_tf_same_padding: + conv_to_same_conv(self) + maxpool_to_same_maxpool(self) def encoder_features(self, x): x0 = self.conv_stem(x) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index a5b3bd2..2ce7e0f 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -12,7 +12,8 @@ def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride[1]) - 1) * self.stride[1] - w + self.kernel_size[1] pad_h = (math.ceil(h / self.stride[0]) - 1) * self.stride[0] - h + self.kernel_size[0] - x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) + if pad_w > 0 or pad_h > 0: + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) return super().forward(x) @@ -23,7 +24,8 @@ def forward(self, x): h, w = x.shape[-2:] pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size pad_h = (math.ceil(h / self.stride) - 1) * self.stride - h + self.kernel_size - x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=-float("inf")) + if pad_w > 0 or pad_h > 0: + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=-float("inf")) return super().forward(x) diff --git a/pytorch_tools/segmentation_models/README.md b/pytorch_tools/segmentation_models/README.md index e31e395..b9ca9f7 100644 --- a/pytorch_tools/segmentation_models/README.md +++ b/pytorch_tools/segmentation_models/README.md @@ -4,7 +4,7 @@ All models here use `Activated Normalization` layers instead of traditional `Nor ## Encoders -All [models](./pytorch_tools/models/) could be used as feature extractors (aka backbones) for segmentation architectures. Almost all combinations of backbones and segm.model are supported. +All [models](../models/) could be used as feature extractors (aka backbones) for segmentation architectures. Almost all combinations of backbones and segm.model are supported. ## Features From 1e69ec30ea9a1773f45c3544be57c405cab74c20 Mon Sep 17 00:00:00 2001 From: zakajd Date: Wed, 3 Jun 2020 21:59:56 +0300 Subject: [PATCH 50/54] rework some box utils and cover with tests --- pytorch_tools/utils/box.py | 192 ++++++++++++++++------ tests/detection_models/test_det_models.py | 18 +- tests/utils/test_utils.py | 125 ++++++++++++++ 3 files changed, 273 insertions(+), 62 deletions(-) create mode 100644 tests/utils/test_utils.py diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index 09229c0..f48a86d 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -1,61 +1,90 @@ """Various functions to help with bboxes""" import torch import numpy as np +from functools import wraps def box2delta(boxes, anchors): + # type: (Tensor, Tensor)->Tensor """Convert boxes to deltas from anchors. Boxes are expected in 'ltrb' format Args: - boxes (torch.Tensor): shape [N, 4] - anchors (torch.Tensor): shape [N, 4] + boxes (torch.Tensor): shape [N, 4] or [BS, N, 4] + anchors (torch.Tensor): shape [N, 4] or [BS, N, 4] Returns: - deltas (torch.Tensor): shape [N, 4]. offset_x, offset_y, scale_x, scale_y + deltas (torch.Tensor): shape [N, 4] or [BS, N, 4] + offset_x, offset_y, scale_x, scale_y """ - anchors_wh = anchors[:, 2:] - anchors[:, :2] + 1 - anchors_ctr = anchors[:, :2] + 0.5 * anchors_wh - boxes_wh = boxes[:, 2:] - boxes[:, :2] + 1 - boxes_ctr = boxes[:, :2] + 0.5 * boxes_wh + anchors_wh = anchors[..., 2:] - anchors[..., :2] # + 1 + anchors_ctr = anchors[..., :2] + 0.5 * anchors_wh + boxes_wh = boxes[..., 2:] - boxes[..., :2] # + 1 + boxes_ctr = boxes[..., :2] + 0.5 * boxes_wh offset_delta = (boxes_ctr - anchors_ctr) / anchors_wh scale_delta = torch.log(boxes_wh / anchors_wh) - return torch.cat([offset_delta, scale_delta], 1) + cat_dim = boxes.dim() - 1 # 1 if [N, 4] else [BS, N, 4] + return torch.cat([offset_delta, scale_delta], cat_dim) def delta2box(deltas, anchors): + # type: (Tensor, Tensor)->Tensor """Convert anchors to boxes using deltas. Boxes are expected in 'ltrb' format Args: - deltas (torch.Tensor): shape [N, 4]. - anchors (torch.Tensor): shape [N, 4] + deltas (torch.Tensor): shape [N, 4] or [BS, N, 4] + anchors (torch.Tensor): shape [N, 4] or [BS, N, 4] Returns: - bboxes (torch.Tensor): bboxes obtained from anchors by regression [N, 4] + bboxes (torch.Tensor): bboxes obtained from anchors by regression + Output shape is [N, 4] or [BS, N, 4] depending on input """ + anchors_wh = anchors[..., 2:] - anchors[..., :2] + ctr = anchors[..., :2] + 0.5 * anchors_wh + pred_ctr = deltas[..., :2] * anchors_wh + ctr - anchors_wh = anchors[:, 2:] - anchors[:, :2] # + 1 - ctr = anchors[:, :2] + 0.5 * anchors_wh - pred_ctr = deltas[:, :2] * anchors_wh + ctr - pred_wh = torch.exp(deltas[:, 2:]) * anchors_wh + # Value for clamping large dw and dh predictions. The heuristic is that we clamp + # such that dw and dh are no larger than what would transform a 16px box into a + # 1000px box (based on a small anchor, 16px, and a typical image size, 1000px). + SCALE_CLAMP = 4.135 # ~= np.log(1000. / 16.) + deltas[..., 2:] = deltas[..., 2:].clamp(min=-SCALE_CLAMP, max=SCALE_CLAMP) - # TODO: make sure this +- 1 for centers aligns with Detectron implementation - # return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh - 1], 1) - return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh], 1) + pred_wh = deltas[..., 2:].exp() * anchors_wh + cat_dim = deltas.dim() - 1 # 1 if [N, 4] else [BS, N, 4] + return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh], cat_dim) def box_area(box): """Args: - box (torch.Tensor): shape [N, 4] in 'ltrb' format + box (torch.Tensor): shape [N, 4] or [BS, N, 4] in 'ltrb' format """ - return (box[:, 2] - box[:, 0]) * (box[:, 3] - box[:, 1]) + return (box[..., 2] - box[..., 0]) * (box[..., 3] - box[..., 1]) def clip_bboxes(bboxes, size): """Args: bboxes (torch.Tensor): in `ltrb` format. Shape [N, 4] - size (Union[torch.Size, tuple]): (H, W)""" + size (Union[torch.Size, tuple]): (H, W). Shape [2,]""" bboxes[:, 0::2] = bboxes[:, 0::2].clamp(0, size[1]) bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, size[0]) return bboxes +def clip_bboxes_batch(bboxes, size): + # type: (Tensor, Tensor)->Tensor + """Args: + bboxes (torch.Tensor): in `ltrb` format. Shape [BS, N, 4] + size (torch.Tensor): (H, W). Shape [BS, 2] """ + size = size.to(bboxes) + h_size = size[..., 0].view(-1, 1, 1) #.float() + w_size = size[..., 1].view(-1, 1, 1) #.float() + h_bboxes = bboxes[..., 1::2] + w_bboxes = bboxes[..., 0::2] + zeros = torch.zeros_like(h_bboxes) + bboxes[..., 1::2] = h_bboxes.where(h_bboxes > 0, zeros).where(h_bboxes < h_size, h_size) + bboxes[..., 0::2] = w_bboxes.where(w_bboxes > 0, zeros).where(w_bboxes < w_size, w_size) + # FIXME: I'm using where to support passing tensor. change to `clamp` when PR #32587 is resolved + # bboxes[:, 0::2] = bboxes[:, 0::2].clamp(0, size[1].item()) + # bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, size[0].item()) + return bboxes + # implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py # with slight modifications def box_iou(boxes1, boxes2): + # type: (Tensor, Tensor)->Tensor """ Return intersection-over-union (Jaccard index) of boxes. Both sets of boxes are expected to be in `ltrb`: (x1, y1, x2, y2) format. @@ -120,7 +149,7 @@ def generate_anchors_boxes( wh = np.stack([np.ones(num_anchors) * ratio_vals_sq, np.ones(num_anchors) / ratio_vals_sq], axis=1) lt = - 0.5 * wh * scale_vals rb = 0.5 * wh * scale_vals - base_offsets = torch.from_numpy(np.hstack([lt, rb])) # [num_anchors, 4] + base_offsets = torch.from_numpy(np.hstack([lt, rb])).float() # [num_anchors, 4] base_offsets = base_offsets.view(-1, 1, 1, 4) # [num_anchors, 1, 1, 4] # generate anchor boxes for all given strides all_anchors = [] @@ -162,8 +191,7 @@ def _generate_single_targets(gt_boxes): # Keep best box per anchor overlap, indices = overlap.max(1) - box_target = box2delta(gt_boxes[indices], anchors) # I've double checked that it's corrects - # TODO: add test that anchor + box target gives gt_bbox + box_target = box2delta(gt_boxes[indices], anchors) # There are three types of anchors. # matched (with objects), unmatched (with background), and in between (which should be ignored) @@ -195,7 +223,7 @@ def _generate_single_targets(gt_boxes): # copied from torchvision def batched_nms(boxes, scores, idxs, iou_threshold): - # type: (Tensor, Tensor, Tensor, float) + # type: (Tensor, Tensor, Tensor, float)->Tensor """ Performs non-maximum suppression in a batched fashion. Each index value correspond to a category, and NMS @@ -231,7 +259,21 @@ def batched_nms(boxes, scores, idxs, iou_threshold): keep = torch.ops.torchvision.nms(boxes_for_nms, scores, iou_threshold) return keep -def decode(batch_cls_head, batch_box_head, anchors, img_shape=None, threshold=0.05, top_n=1000, iou_threshold=0.5): +# jit actually makes it slower for fp16 and results are different! +# FIXME: check it after 1.6 release. maybe they will fix JIT by that time +# @torch.jit.script +def decode( + batch_cls_head, + batch_box_head, + anchors, + img_shapes=None, + img_scales=None, + threshold=0.05, + max_detection_points=5000, + max_detection_per_image=100, + iou_threshold=0.5, +): + # type: (Tensor, Tensor, Tensor, Tensor, Tensor, float, int, int, float)->Tensor """ Decodes raw outputs of a model for easy visualization of bboxes @@ -239,48 +281,92 @@ def decode(batch_cls_head, batch_box_head, anchors, img_shape=None, threshold=0. batch_cls_head (torch.Tensor): shape [BS, *, NUM_CLASSES] batch_box_head (torch.Tensor): shape [BS, *, 4] anchors (torch.Tensor): shape [*, 4] - img_shape (Tuple[int]): if given clips predicted bboxes to img height and width + img_shapes (torch.Tensor): if given clips predicted bboxes to img height and width. Shape [BS, 2] or [2,] + img_scales (torch.Tensor): if given used to rescale img_shapes. Shape [BS,] threshold (float): minimum score threshold to consider object detected - top_n (int): maximum number of objects per image + max_detection_points (int): Maximum number of bboxes to consider for NMS for one image + max_detection_per_image (int): Maximum number of bboxes to return per image iou_threshold (float): iou_threshold for Non Maximum Supression Returns: - out_bboxes (torch.Tensor): bboxes. Shape [BS, TOP_N] If img_shape is not given they are NOT CLIPPED (!) - out_scores (torch.Tensor): Probability scores for each bbox. Shape [BS, TOP_N] - out_classes (torch.Tensor): Predicted class for each bbox. Shape [BS, TOP_N] + torch.Tensor with bboxes, scores and classes + shape [BS, MAX_DETECTION_PER_IMAGE, 6]. + bboxes in 'ltrb' format. If img_shape is not given they are NOT CLIPPED (!) """ batch_size = batch_cls_head.size(0) - anchors = anchors.to(batch_cls_head) - out_scores = torch.zeros((batch_size, top_n)).to(batch_cls_head) - out_boxes = torch.zeros((batch_size, top_n, 4)).to(batch_cls_head) - out_classes = torch.zeros((batch_size, top_n)).to(batch_cls_head) - # it has to be raw logits but check anyway to avoid aplying sigmoid twice + num_classes = batch_cls_head.size(-1) + + anchors = anchors.to(batch_cls_head).unsqueeze(0).expand(batch_size, -1, -1) # [N, 4] -> [BS, N, 4] + # it has to be raw logits but check anyway to avoid applying sigmoid twice if batch_cls_head.min() < 0 or batch_cls_head.max() > 1: batch_cls_head = batch_cls_head.sigmoid() + # It's much faster to calculate topk once for full batch here rather than doing it inside loop + # In TF The same bbox may belong to two different objects + # select `max_detection_points` scores and corresponding bboxes + scores_topk_all, cls_topk_indices_all = torch.topk(batch_cls_head.view(batch_size, -1), k=max_detection_points) + indices_all = cls_topk_indices_all / num_classes + classes_all = cls_topk_indices_all % num_classes + + # Gather corresponding bounding boxes & anchors, then regress and clip + box_topk_all = torch.gather(batch_box_head, 1, indices_all.unsqueeze(2).expand(-1, -1, 4)) + anchors_topk_all = torch.gather(anchors, 1, indices_all.unsqueeze(2).expand(-1, -1, 4)) + regressed_boxes_all = delta2box(box_topk_all, anchors_topk_all) + if img_shapes is not None: + if img_scales is not None: + img_shapes = img_shapes / img_scales.unsqueeze(1) + regressed_boxes_all = clip_bboxes_batch(regressed_boxes_all, img_shapes) + + # prepare output tensors + out_scores = torch.zeros((batch_size, max_detection_per_image)).to(batch_cls_head) + out_boxes = torch.zeros((batch_size, max_detection_per_image, 4)).to(batch_cls_head) + out_classes = torch.zeros((batch_size, max_detection_per_image)).to(batch_cls_head) + for batch in range(batch_size): + scores_topk = scores_topk_all[batch] # , cls_topk_indices_all[batch] + classes = classes_all[batch] #cls_topk_indices % num_classes + box_topk = box_topk_all[batch] # torch.gather(batch_box_head[batch], 0, indices) + anchor_topk = anchors_topk_all[batch] + regressed_boxes = regressed_boxes_all[batch] # delta2box(box_topk, anchor_topk) + + # apply NMS + nms_idx = batched_nms(regressed_boxes, scores_topk, classes, iou_threshold) + nms_idx = nms_idx[:min(len(nms_idx), max_detection_per_image)] + # select suppressed bboxes + im_scores = scores_topk[nms_idx] + im_classes = classes[nms_idx] + im_bboxes = regressed_boxes[nms_idx] + im_classes += 1 # back to class idx with background class = 0 + + out_scores[batch, :im_scores.size(0)] = im_scores + out_classes[batch, :im_classes.size(0)] = im_classes + out_boxes[batch, :im_bboxes.size(0)] = im_bboxes + # no need to pad because it's already padded with 0's + + + ## old way ## # get regressed bboxes - all_img_bboxes = delta2box(batch_box_head[batch], anchors) - if img_shape: # maybe clip - all_img_bboxes = clip_bboxes(all_img_bboxes, img_shape) + # all_img_bboxes = delta2box(batch_box_head[batch], anchors) + # if img_shape: # maybe clip + # all_img_bboxes = clip_bboxes(all_img_bboxes, img_shape) # select at most `top_n` bboxes and from them select with score > threshold - max_cls_score, max_cls_idx = batch_cls_head[batch].max(1) - top_cls_score, top_cls_idx = max_cls_score.topk(top_n) - top_cls_idx = top_cls_idx[top_cls_score > threshold] + # max_cls_score, max_cls_idx = batch_cls_head[batch].max(1) + # top_cls_score, top_cls_idx = max_cls_score.topk(top_n) + # top_cls_idx = top_cls_idx[top_cls_score > threshold] - im_scores = max_cls_score[top_cls_idx] - im_classes = max_cls_idx[top_cls_idx] - im_bboxes = all_img_bboxes[top_cls_idx] + # im_scores = max_cls_score[top_cls_idx] + # im_classes = max_cls_idx[top_cls_idx] + # im_bboxes = all_img_bboxes[top_cls_idx] # apply NMS - nms_idx = batched_nms(im_bboxes, im_scores, im_classes, iou_threshold) - im_scores = im_scores[nms_idx] - im_classes = im_classes[nms_idx] - im_bboxes = im_bboxes[nms_idx] + # nms_idx = batched_nms(im_bboxes, im_scores, im_classes, iou_threshold) + # im_scores = im_scores[nms_idx] + # im_classes = im_classes[nms_idx] + # im_bboxes = im_bboxes[nms_idx] - out_scores[batch, :im_scores.size(0)] = im_scores - out_classes[batch, :im_classes.size(0)] = im_classes - out_boxes[batch, :im_bboxes.size(0)] = im_bboxes + # out_scores[batch, :im_scores.size(0)] = im_scores + # out_classes[batch, :im_classes.size(0)] = im_classes + # out_boxes[batch, :im_bboxes.size(0)] = im_bboxes - return out_boxes, out_scores, out_classes \ No newline at end of file + return torch.cat([out_boxes, out_scores.unsqueeze(-1), out_classes.unsqueeze(-1)], dim=2) \ No newline at end of file diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py index 23715a2..62ebb13 100644 --- a/tests/detection_models/test_det_models.py +++ b/tests/detection_models/test_det_models.py @@ -11,14 +11,14 @@ # all weights were tested on 05.2020. for now only leave one model for faster tests MODEL_NAMES = [ - "efficientdet_b0", + "efficientdet_d0", "retinanet_r50_fpn", - # "efficientdet_b1", - # "efficientdet_b2", - # "efficientdet_b3", - # "efficientdet_b4", - # "efficientdet_b5", - # "efficientdet_b6", + # "efficientdet_d1", + # "efficientdet_d2", + # "efficientdet_d3", + # "efficientdet_d4", + # "efficientdet_d5", + # "efficientdet_d6", # "retinanet_r101_fpn", ] @@ -50,10 +50,10 @@ def test_coco_pretrain(arch): for im_cls, im in IMGS.items(): im = np.array(im.resize((inp_size, inp_size))) im_t = tensor_from_rgb_image(preprocess_fn(im)).unsqueeze(0).float().cuda() - boxes, scores, classes = m.predict(im_t) + boxes_scores_classes = m.predict(im_t) # check that most confident bbox is close to correct class. The reason for such strange test is # because in different models class mappings are shifted by +- 1 - assert (classes[0, 0] - im_cls) < 2 + assert (boxes_scores_classes[0, 0, 5] - im_cls) < 2 @pytest.mark.parametrize("arch", MODEL_NAMES[:2]) diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py new file mode 100644 index 0000000..13b4f23 --- /dev/null +++ b/tests/utils/test_utils.py @@ -0,0 +1,125 @@ +import torch +import pytest +import pytorch_tools as pt + +def random_boxes(mean_box, stdev, N): + return torch.rand(N, 4) * stdev + torch.tensor(mean_box, dtype=torch.float) + +DEVICE_DTYPE = [ + ("cpu", torch.float), + ("cuda", torch.float), + ("cuda", torch.half) +] +# check that it works for all combinations of dtype and device +@pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) +def test_clip_bboxes(device_dtype): + device, dtype = device_dtype + bboxes = torch.tensor( + [ + [-5, -10, 50, 100], + [10, 15, 20, 25], + ], + device=device, + dtype=dtype, + ) + expected_bboxes = torch.tensor( + [ + [0, 0, 40, 60], + [10, 15, 20, 25], + ], + device=device, + dtype=dtype, + ) + size = (60, 40) + # test single bbox clip + res1 = pt.utils.box.clip_bboxes(bboxes, size) + assert torch.allclose(res1, expected_bboxes) + # test single bbox clip passing torch.Size + res2 = pt.utils.box.clip_bboxes(bboxes, torch.Size(size)) + assert torch.allclose(res2, expected_bboxes) + + BS = 4 + batch_bboxes = bboxes.unsqueeze(0).expand(BS, -1, -1) + batch_expected = expected_bboxes.unsqueeze(0).expand(BS, -1, -1) + batch_sizes = torch.tensor(size).repeat(BS, 1) + # test batch clipping + res3 = pt.utils.box.clip_bboxes_batch(batch_bboxes.clone(), batch_sizes) + assert torch.allclose(res3, batch_expected) + + # check that even in batch mode we can pass single size + res4 = pt.utils.box.clip_bboxes_batch(batch_bboxes.clone(), torch.tensor(size)) + assert torch.allclose(res4, batch_expected) + + jit_clip = torch.jit.script(pt.utils.box.clip_bboxes_batch) + # check that function is JIT script friendly + res5 = jit_clip(batch_bboxes.clone(), batch_sizes) + assert torch.allclose(res5, batch_expected) + +@pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) +def test_delta2box(device_dtype): + device, dtype = device_dtype + anchors = torch.tensor( + [ + [ 0., 0., 1., 1.], + [ 0., 0., 1., 1.], + [ 0., 0., 1., 1.], + [ 5., 5., 5., 5.] + ], + device=device, + dtype=dtype, + ) + deltas = torch.tensor( + [ + [ 0., 0., 0., 0.], + [ 1., 1., 1., 1.], + [ 0., 0., 2., -1.], + [ 0.7, -1.9, -0.5, 0.3] + ], + device=device, + dtype=dtype, + ) + # by default we don't expect results to be clipped + expected_res = torch.tensor( + [ + [0.0000, 0.0000, 1.0000, 1.0000], + [0.1409, 0.1409, 2.8591, 2.8591], + [-3.1945, 0.3161, 4.1945, 0.6839], + [5.0000, 5.0000, 5.0000, 5.0000] + ], + device=device, + dtype=dtype, + ) + res1 = pt.utils.box.delta2box(deltas, anchors) + assert torch.allclose(res1, expected_res, atol=3e-4) + + BS = 4 + batch_anchors = anchors.unsqueeze(0).expand(BS, -1, -1) + batch_deltas = deltas.unsqueeze(0).expand(BS, -1, -1) + batch_expected = expected_res.unsqueeze(0).expand(BS, -1, -1) + + # test applying to batch + res2 = pt.utils.box.delta2box(batch_deltas.clone(), batch_anchors) + assert torch.allclose(res2, batch_expected, atol=3e-4) + + # check that function is JIT script friendly + jit_func = torch.jit.script(pt.utils.box.delta2box) + res3 = jit_func(batch_deltas.clone(), batch_anchors) + assert torch.allclose(res3, batch_expected, atol=3e-4) + +@pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) +def test_box2delta(device_dtype): + ## this test only checks that encoding and decoding gives the same result + device, dtype = device_dtype + boxes = random_boxes([10, 10, 20, 20], 10, 10).to(device).to(dtype) + anchors = random_boxes([10, 10, 20, 20], 10, 10).to(device).to(dtype) + deltas = pt.utils.box.box2delta(boxes, anchors) + boxes_reconstructed = pt.utils.box.delta2box(deltas, anchors) + atol = 2e-2 if dtype == torch.half else 1e-6 # for fp16 sometimes error is large + assert torch.allclose(boxes, boxes_reconstructed, atol=atol) + + # check that it's jit friendly + jit_box2delta = torch.jit.script(pt.utils.box.box2delta) + jit_delta2box = torch.jit.script(pt.utils.box.delta2box) + deltas2 = jit_box2delta(boxes, anchors) + boxes_reconstructed2 = jit_delta2box(deltas2, anchors) + assert torch.allclose(boxes, boxes_reconstructed2, atol=atol) \ No newline at end of file From 56e00473da880664f98b9c6aa4208adced667914 Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 4 Jun 2020 11:40:15 +0300 Subject: [PATCH 51/54] make same ops scriptable --- pytorch_tools/modules/tf_same_ops.py | 41 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index 2ce7e0f..1c5adb6 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -3,30 +3,45 @@ import torch import torch.nn as nn import torch.nn.functional as F +from torch.nn.modules.utils import _pair +def pad_same(x, k, s, d, value=0): + # type: (Tensor, int, int, int, float)->Tensor + # x - input tensor, s - stride, k - kernel_size, d - dilation + ih, iw = x.size()[-2:] + pad_h = max((math.ceil(ih / s) - 1) * s + (k - 1) * d + 1 - ih, 0) + pad_w = max((math.ceil(iw / s) - 1) * s + (k - 1) * d + 1 - iw, 0) + if pad_h > 0 or pad_w > 0: + x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=value) + return x + +# current implementation is only for symmetric case. But there are no non symmetric cases +def conv2d_same(x, weight, bias=None, stride=(1, 1), dilation=(1, 1), groups=1): + # type: (Tensor, Tensor, Optional[torch.Tensor], Tuple[int, int], Tuple[int, int], int)->Tensor + x = pad_same(x, weight.shape[-1], stride[0], dilation[0]) + return F.conv2d(x, weight, bias, stride, (0, 0), dilation, groups) + +def maxpool2d_same(x, kernel_size, stride): + # type: (Tensor, Tuple[int, int], Tuple[int, int])->Tensor + x = pad_same(x, kernel_size[0], stride[0], 1, value=-float('inf')) + return F.max_pool2d(x, kernel_size, stride, (0, 0)) class Conv2dSamePadding(nn.Conv2d): """Assymetric padding matching TensorFlow `same`""" def forward(self, x): - h, w = x.shape[-2:] - pad_w = (math.ceil(w / self.stride[1]) - 1) * self.stride[1] - w + self.kernel_size[1] - pad_h = (math.ceil(h / self.stride[0]) - 1) * self.stride[0] - h + self.kernel_size[0] - if pad_w > 0 or pad_h > 0: - x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2]) - return super().forward(x) - + return conv2d_same(x, self.weight, self.bias, self.stride, self.dilation, self.groups) +# as of 1.5 there is no _pair in MaxPool. Remove when this is fixed class MaxPool2dSamePadding(nn.MaxPool2d): """Assymetric padding matching TensorFlow `same`""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.kernel_size = _pair(self.kernel_size) + self.stride = _pair(self.stride) def forward(self, x): - h, w = x.shape[-2:] - pad_w = (math.ceil(w / self.stride) - 1) * self.stride - w + self.kernel_size - pad_h = (math.ceil(h / self.stride) - 1) * self.stride - h + self.kernel_size - if pad_w > 0 or pad_h > 0: - x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=-float("inf")) - return super().forward(x) + return maxpool2d_same(x, self.kernel_size, self.stride) def conv_to_same_conv(module): From e2d35d8afb9845ec5b468b0b7e2cf30a8cc5d72e Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 4 Jun 2020 11:47:42 +0300 Subject: [PATCH 52/54] fully matching EffDet --- pytorch_tools/detection_models/efficientdet.py | 14 +++++++++----- pytorch_tools/detection_models/retinanet.py | 3 +-- pytorch_tools/utils/box.py | 2 +- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index 5b3c299..e765f97 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -104,6 +104,7 @@ def make_head_norm(): self.box_head_convs = make_head(4 * anchors_per_location) self.box_head_norms = make_head_norm() self.num_classes = num_classes + self.num_head_repeats = num_head_repeats patch_bn(self) self._initialize_weights() @@ -132,6 +133,8 @@ def forward(self, x): box_outputs = [] for feat, (cls_bns, box_bns) in zip(features, zip(self.cls_head_norms, self.box_head_norms)): cls_feat, box_feat = feat, feat + # it looks like that with drop_connect there is an additional residual here + # TODO: need to investigate using pretrained weights for cls_conv, cls_bn in zip(self.cls_head_convs, cls_bns): cls_feat = cls_bn(cls_conv(cls_feat)) for box_conv, box_bn in zip(self.box_head_convs, box_bns): @@ -148,18 +151,19 @@ def forward(self, x): # my anchors are in [x1, y1, x2,y2] format while pretrained weights are in [y1, x1, y2, x2] format # it may be confusing to reorder x and y every time later so I do it once here. it gives # compatability with pretrained weigths from Google and doesn't affect training from scratch - box_outputs = box_outputs[..., [1, 0, 3, 2]] + # box_outputs = box_outputs[..., [1, 0, 3, 2]] # TODO: return back return class_outputs, box_outputs @torch.no_grad() def predict(self, x): - """Run forward on given images and decode raw prediction into bboxes""" + """Run forward on given images and decode raw prediction into bboxes + Returns: bboxes, scores, classes + """ class_outputs, box_outputs = self.forward(x) anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] - out_bboxes, out_scores, out_classes = box_utils.decode( - class_outputs, box_outputs, anchors, img_shape=x.shape[-2:] + return box_utils.decode( + class_outputs, box_outputs, anchors, #img_shape=x.shape[-2:] ) - return out_bboxes, out_scores, out_classes def _initialize_weights(self): # init everything except encoder diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index 1ca1078..0ddf559 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -126,10 +126,9 @@ def predict(self, x): """Run forward on given images and decode raw prediction into bboxes""" class_outputs, box_outputs = self.forward(x) anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] - out_bboxes, out_scores, out_classes = box_utils.decode( + return box_utils.decode( class_outputs, box_outputs, anchors, img_shape=x.shape[-2:] ) - return out_bboxes, out_scores, out_classes def _initialize_weights(self): # init everything except encoder diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index f48a86d..5b73c93 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -1,4 +1,4 @@ -"""Various functions to help with bboxes""" +"""Various functions to help with bboxes for object detection""" import torch import numpy as np from functools import wraps From 19a2385e893a977dca949dcd45cdc5285922c6cc Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 4 Jun 2020 11:52:23 +0300 Subject: [PATCH 53/54] simplify cat --- pytorch_tools/utils/box.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index 5b73c93..73bb377 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -20,8 +20,7 @@ def box2delta(boxes, anchors): boxes_ctr = boxes[..., :2] + 0.5 * boxes_wh offset_delta = (boxes_ctr - anchors_ctr) / anchors_wh scale_delta = torch.log(boxes_wh / anchors_wh) - cat_dim = boxes.dim() - 1 # 1 if [N, 4] else [BS, N, 4] - return torch.cat([offset_delta, scale_delta], cat_dim) + return torch.cat([offset_delta, scale_delta], -1) def delta2box(deltas, anchors): @@ -45,8 +44,7 @@ def delta2box(deltas, anchors): deltas[..., 2:] = deltas[..., 2:].clamp(min=-SCALE_CLAMP, max=SCALE_CLAMP) pred_wh = deltas[..., 2:].exp() * anchors_wh - cat_dim = deltas.dim() - 1 # 1 if [N, 4] else [BS, N, 4] - return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh], cat_dim) + return torch.cat([pred_ctr - 0.5 * pred_wh, pred_ctr + 0.5 * pred_wh], -1) def box_area(box): From 0568102788f8bd67a3ea54f450e8589b7bbebf6a Mon Sep 17 00:00:00 2001 From: zakajd Date: Thu, 4 Jun 2020 20:17:57 +0300 Subject: [PATCH 54/54] formatting --- .../detection_models/efficientdet.py | 8 +- pytorch_tools/detection_models/retinanet.py | 13 +- pytorch_tools/models/__init__.py | 2 +- pytorch_tools/models/bit_resnet.py | 455 +++++++++--------- pytorch_tools/models/efficientnet.py | 11 +- pytorch_tools/models/hrnet.py | 136 +++--- pytorch_tools/models/resnet.py | 7 +- pytorch_tools/models/tresnet.py | 37 +- pytorch_tools/models/vgg.py | 1 - pytorch_tools/modules/activated_batch_norm.py | 2 +- pytorch_tools/modules/tf_same_ops.py | 12 +- pytorch_tools/utils/box.py | 117 ++--- pytorch_tools/utils/misc.py | 9 +- tests/detection_models/test_det_models.py | 3 +- tests/losses/test_losses.py | 11 +- tests/models/test_models.py | 21 +- tests/models/test_weights.py | 2 + tests/modules/test_modules.py | 2 + tests/segmentation_models/test_segm_models.py | 11 +- tests/utils/test_utils.py | 24 +- 20 files changed, 473 insertions(+), 411 deletions(-) diff --git a/pytorch_tools/detection_models/efficientdet.py b/pytorch_tools/detection_models/efficientdet.py index e765f97..0446b56 100644 --- a/pytorch_tools/detection_models/efficientdet.py +++ b/pytorch_tools/detection_models/efficientdet.py @@ -161,16 +161,14 @@ def predict(self, x): """ class_outputs, box_outputs = self.forward(x) anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] - return box_utils.decode( - class_outputs, box_outputs, anchors, #img_shape=x.shape[-2:] - ) + return box_utils.decode(class_outputs, box_outputs, anchors) def _initialize_weights(self): # init everything except encoder no_encoder_m = [m for n, m in self.named_modules() if not "encoder" in n] initialize_iterator(no_encoder_m) - # need to init last bias so that after sigmoid it's 0.01 - cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 + # need to init last bias so that after sigmoid it's 0.01 + cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 nn.init.constant_(self.cls_head_convs[-1][1].bias, cls_bias_init) diff --git a/pytorch_tools/detection_models/retinanet.py b/pytorch_tools/detection_models/retinanet.py index 0ddf559..2fbdf74 100644 --- a/pytorch_tools/detection_models/retinanet.py +++ b/pytorch_tools/detection_models/retinanet.py @@ -44,7 +44,7 @@ class RetinaNet(nn.Module): def __init__( self, - pretrained="coco", # not used here for proper signature + pretrained="coco", # not used here for proper signature encoder_name="resnet50", encoder_weights="imagenet", pyramid_channels=256, @@ -90,7 +90,7 @@ def make_final_convs(): self.box_convs = make_final_convs() self.box_head_conv = conv3x3(pyramid_channels, 4 * anchors_per_location, bias=True) self.num_classes = num_classes - self. _initialize_weights() + self._initialize_weights() # Name from mmdetectin for convenience def extract_features(self, x): @@ -126,18 +126,17 @@ def predict(self, x): """Run forward on given images and decode raw prediction into bboxes""" class_outputs, box_outputs = self.forward(x) anchors = box_utils.generate_anchors_boxes(x.shape[-2:])[0] - return box_utils.decode( - class_outputs, box_outputs, anchors, img_shape=x.shape[-2:] - ) + return box_utils.decode(class_outputs, box_outputs, anchors) def _initialize_weights(self): # init everything except encoder no_encoder_m = [m for n, m in self.named_modules() if not "encoder" in n] initialize_iterator(no_encoder_m) - # need to init last bias so that after sigmoid it's 0.01 - cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 + # need to init last bias so that after sigmoid it's 0.01 + cls_bias_init = -torch.log(torch.tensor((1 - 0.01) / 0.01)) # -4.59 nn.init.constant_(self.cls_head_conv.bias, cls_bias_init) + # Don't really know input size for the models. 512 is just a guess PRETRAIN_SETTINGS = {**DEFAULT_IMAGENET_SETTINGS, "input_size": (512, 512), "crop_pct": 1, "num_classes": 80} diff --git a/pytorch_tools/models/__init__.py b/pytorch_tools/models/__init__.py index ef9c0d4..4fb855e 100644 --- a/pytorch_tools/models/__init__.py +++ b/pytorch_tools/models/__init__.py @@ -51,4 +51,4 @@ from .bit_resnet import bit_m_101x1 from .bit_resnet import bit_m_101x3 from .bit_resnet import bit_m_152x2 -from .bit_resnet import bit_m_152x4 \ No newline at end of file +from .bit_resnet import bit_m_152x4 diff --git a/pytorch_tools/models/bit_resnet.py b/pytorch_tools/models/bit_resnet.py index 3108e97..c5acbd5 100644 --- a/pytorch_tools/models/bit_resnet.py +++ b/pytorch_tools/models/bit_resnet.py @@ -4,7 +4,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -29,179 +29,181 @@ def conv3x3(cin, cout, stride=1, groups=1, bias=False): - return StdConv2d(cin, cout, kernel_size=3, stride=stride, - padding=1, bias=bias, groups=groups) + return StdConv2d(cin, cout, kernel_size=3, stride=stride, padding=1, bias=bias, groups=groups) def conv1x1(cin, cout, stride=1, bias=False): - return StdConv2d(cin, cout, kernel_size=1, stride=stride, - padding=0, bias=bias) + return StdConv2d(cin, cout, kernel_size=1, stride=stride, padding=0, bias=bias) def tf2th(conv_weights): - """Possibly convert HWIO to OIHW.""" - if conv_weights.ndim == 4: - conv_weights = conv_weights.transpose([3, 2, 0, 1]) - return torch.from_numpy(conv_weights) + """Possibly convert HWIO to OIHW.""" + if conv_weights.ndim == 4: + conv_weights = conv_weights.transpose([3, 2, 0, 1]) + return torch.from_numpy(conv_weights) class PreActBottleneck(nn.Module): - """Pre-activation (v2) bottleneck block. - - Follows the implementation of "Identity Mappings in Deep Residual Networks": - https://github.com/KaimingHe/resnet-1k-layers/blob/master/resnet-pre-act.lua - - Except it puts the stride on 3x3 conv when available. - """ - - def __init__(self, cin, cout=None, cmid=None, stride=1): - super().__init__() - cout = cout or cin - cmid = cmid or cout//4 - - self.gn1 = nn.GroupNorm(32, cin) - self.conv1 = conv1x1(cin, cmid) - self.gn2 = nn.GroupNorm(32, cmid) - self.conv2 = conv3x3(cmid, cmid, stride) # Original code has it on conv1!! - self.gn3 = nn.GroupNorm(32, cmid) - self.conv3 = conv1x1(cmid, cout) - self.relu = nn.ReLU(inplace=True) - - if (stride != 1 or cin != cout): - # Projection also with pre-activation according to paper. - self.downsample = conv1x1(cin, cout, stride) - - def forward(self, x): - out = self.relu(self.gn1(x)) - - # Residual branch - residual = x - if hasattr(self, 'downsample'): - residual = self.downsample(out) - - # Unit's branch - out = self.conv1(out) - out = self.conv2(self.relu(self.gn2(out))) - out = self.conv3(self.relu(self.gn3(out))) - - return out + residual - - def load_from(self, weights, prefix=''): - convname = 'standardized_conv2d' - with torch.no_grad(): - self.conv1.weight.copy_(tf2th(weights[f'{prefix}a/{convname}/kernel'])) - self.conv2.weight.copy_(tf2th(weights[f'{prefix}b/{convname}/kernel'])) - self.conv3.weight.copy_(tf2th(weights[f'{prefix}c/{convname}/kernel'])) - self.gn1.weight.copy_(tf2th(weights[f'{prefix}a/group_norm/gamma'])) - self.gn2.weight.copy_(tf2th(weights[f'{prefix}b/group_norm/gamma'])) - self.gn3.weight.copy_(tf2th(weights[f'{prefix}c/group_norm/gamma'])) - self.gn1.bias.copy_(tf2th(weights[f'{prefix}a/group_norm/beta'])) - self.gn2.bias.copy_(tf2th(weights[f'{prefix}b/group_norm/beta'])) - self.gn3.bias.copy_(tf2th(weights[f'{prefix}c/group_norm/beta'])) - if hasattr(self, 'downsample'): - w = weights[f'{prefix}a/proj/{convname}/kernel'] - self.downsample.weight.copy_(tf2th(w)) + """Pre-activation (v2) bottleneck block. + + Follows the implementation of "Identity Mappings in Deep Residual Networks": + https://github.com/KaimingHe/resnet-1k-layers/blob/master/resnet-pre-act.lua + + Except it puts the stride on 3x3 conv when available. + """ + + def __init__(self, cin, cout=None, cmid=None, stride=1): + super().__init__() + cout = cout or cin + cmid = cmid or cout // 4 + + self.gn1 = nn.GroupNorm(32, cin) + self.conv1 = conv1x1(cin, cmid) + self.gn2 = nn.GroupNorm(32, cmid) + self.conv2 = conv3x3(cmid, cmid, stride) # Original code has it on conv1!! + self.gn3 = nn.GroupNorm(32, cmid) + self.conv3 = conv1x1(cmid, cout) + self.relu = nn.ReLU(inplace=True) + + if stride != 1 or cin != cout: + # Projection also with pre-activation according to paper. + self.downsample = conv1x1(cin, cout, stride) + + def forward(self, x): + out = self.relu(self.gn1(x)) + + # Residual branch + residual = x + if hasattr(self, "downsample"): + residual = self.downsample(out) + + # Unit's branch + out = self.conv1(out) + out = self.conv2(self.relu(self.gn2(out))) + out = self.conv3(self.relu(self.gn3(out))) + + return out + residual + + def load_from(self, weights, prefix=""): + convname = "standardized_conv2d" + with torch.no_grad(): + self.conv1.weight.copy_(tf2th(weights[f"{prefix}a/{convname}/kernel"])) + self.conv2.weight.copy_(tf2th(weights[f"{prefix}b/{convname}/kernel"])) + self.conv3.weight.copy_(tf2th(weights[f"{prefix}c/{convname}/kernel"])) + self.gn1.weight.copy_(tf2th(weights[f"{prefix}a/group_norm/gamma"])) + self.gn2.weight.copy_(tf2th(weights[f"{prefix}b/group_norm/gamma"])) + self.gn3.weight.copy_(tf2th(weights[f"{prefix}c/group_norm/gamma"])) + self.gn1.bias.copy_(tf2th(weights[f"{prefix}a/group_norm/beta"])) + self.gn2.bias.copy_(tf2th(weights[f"{prefix}b/group_norm/beta"])) + self.gn3.bias.copy_(tf2th(weights[f"{prefix}c/group_norm/beta"])) + if hasattr(self, "downsample"): + w = weights[f"{prefix}a/proj/{convname}/kernel"] + self.downsample.weight.copy_(tf2th(w)) + # this models are designed for trasfer learning only! not for training from scratch class ResNetV2(nn.Module): - """ - Implementation of Pre-activation (v2) ResNet mode. - Used to create Bit-M-50/101/152x1/2/3/4 models - - Args: - num_classes (int): Number of classification classes. Defaults to 5 - """ - - def __init__( - self, - block_units, - width_factor, - # in_channels=3, # TODO: add later - num_classes=5, # just a random number - # encoder=False, # TODO: add later + """ + Implementation of Pre-activation (v2) ResNet mode. + Used to create Bit-M-50/101/152x1/2/3/4 models + + Args: + num_classes (int): Number of classification classes. Defaults to 5 + """ + + def __init__( + self, + block_units, + width_factor, + # in_channels=3, # TODO: add later + num_classes=5, # just a random number + # encoder=False, # TODO: add later ): - super().__init__() - wf = width_factor # shortcut 'cause we'll use it a lot. - - # The following will be unreadable if we split lines. - # pylint: disable=line-too-long - self.root = nn.Sequential(OrderedDict([ - ('conv', StdConv2d(3, 64*wf, kernel_size=7, stride=2, padding=3, bias=False)), - ('pad', nn.ConstantPad2d(1, 0)), - ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=0)), - # The following is subtly not the same! - # ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)), - ])) - - self.body = nn.Sequential(OrderedDict([ - ('block1', nn.Sequential(OrderedDict( - [('unit01', PreActBottleneck(cin=64*wf, cout=256*wf, cmid=64*wf))] + - [(f'unit{i:02d}', PreActBottleneck(cin=256*wf, cout=256*wf, cmid=64*wf)) for i in range(2, block_units[0] + 1)], - ))), - ('block2', nn.Sequential(OrderedDict( - [('unit01', PreActBottleneck(cin=256*wf, cout=512*wf, cmid=128*wf, stride=2))] + - [(f'unit{i:02d}', PreActBottleneck(cin=512*wf, cout=512*wf, cmid=128*wf)) for i in range(2, block_units[1] + 1)], - ))), - ('block3', nn.Sequential(OrderedDict( - [('unit01', PreActBottleneck(cin=512*wf, cout=1024*wf, cmid=256*wf, stride=2))] + - [(f'unit{i:02d}', PreActBottleneck(cin=1024*wf, cout=1024*wf, cmid=256*wf)) for i in range(2, block_units[2] + 1)], - ))), - ('block4', nn.Sequential(OrderedDict( - [('unit01', PreActBottleneck(cin=1024*wf, cout=2048*wf, cmid=512*wf, stride=2))] + - [(f'unit{i:02d}', PreActBottleneck(cin=2048*wf, cout=2048*wf, cmid=512*wf)) for i in range(2, block_units[3] + 1)], - ))), - ])) - # pylint: enable=line-too-long - - self.head = nn.Sequential(OrderedDict([ - ('gn', nn.GroupNorm(32, 2048*wf)), - ('relu', nn.ReLU(inplace=True)), - ('avg', nn.AdaptiveAvgPool2d(output_size=1)), - ('conv', nn.Conv2d(2048*wf, num_classes, kernel_size=1, bias=True)), - ])) - - def features(self, x): - return self.body(self.root(x)) - - def logits(self, x): - return self.head(x) - - def forward(self, x): - x = self.logits(self.features(x)) - assert x.shape[-2:] == (1, 1) # We should have no spatial shape left. - return x[...,0,0] - - def load_from(self, weights, prefix='resnet/'): - with torch.no_grad(): - self.root.conv.weight.copy_(tf2th(weights[f'{prefix}root_block/standardized_conv2d/kernel'])) # pylint: disable=line-too-long - self.head.gn.weight.copy_(tf2th(weights[f'{prefix}group_norm/gamma'])) - self.head.gn.bias.copy_(tf2th(weights[f'{prefix}group_norm/beta'])) - # always zero_head - nn.init.zeros_(self.head.conv.weight) - nn.init.zeros_(self.head.conv.bias) - - for bname, block in self.body.named_children(): - for uname, unit in block.named_children(): - unit.load_from(weights, prefix=f'{prefix}{bname}/{uname}/') - - - - -KNOWN_MODELS = OrderedDict([ - ('BiT-M-R50x1', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), - ('BiT-M-R50x3', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), - ('BiT-M-R101x1', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), - ('BiT-M-R101x3', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), - ('BiT-M-R152x2', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), - ('BiT-M-R152x4', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), - - ('BiT-S-R50x1', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), - ('BiT-S-R50x3', lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), - ('BiT-S-R101x1', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), - ('BiT-S-R101x3', lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), - ('BiT-S-R152x2', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), - ('BiT-S-R152x4', lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), -]) + super().__init__() + wf = width_factor # shortcut 'cause we'll use it a lot. + + # The following will be unreadable if we split lines. + # pylint: disable=line-too-long + # fmt: off + self.root = nn.Sequential(OrderedDict([ + ('conv', StdConv2d(3, 64*wf, kernel_size=7, stride=2, padding=3, bias=False)), + ('pad', nn.ConstantPad2d(1, 0)), + ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=0)), + # The following is subtly not the same! + # ('pool', nn.MaxPool2d(kernel_size=3, stride=2, padding=1)), + ])) + + self.body = nn.Sequential(OrderedDict([ + ('block1', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=64*wf, cout=256*wf, cmid=64*wf))] + + [(f'unit{i:02d}', PreActBottleneck(cin=256*wf, cout=256*wf, cmid=64*wf)) for i in range(2, block_units[0] + 1)], + ))), + ('block2', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=256*wf, cout=512*wf, cmid=128*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=512*wf, cout=512*wf, cmid=128*wf)) for i in range(2, block_units[1] + 1)], + ))), + ('block3', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=512*wf, cout=1024*wf, cmid=256*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=1024*wf, cout=1024*wf, cmid=256*wf)) for i in range(2, block_units[2] + 1)], + ))), + ('block4', nn.Sequential(OrderedDict( + [('unit01', PreActBottleneck(cin=1024*wf, cout=2048*wf, cmid=512*wf, stride=2))] + + [(f'unit{i:02d}', PreActBottleneck(cin=2048*wf, cout=2048*wf, cmid=512*wf)) for i in range(2, block_units[3] + 1)], + ))), + ])) + # pylint: enable=line-too-long + + self.head = nn.Sequential(OrderedDict([ + ('gn', nn.GroupNorm(32, 2048*wf)), + ('relu', nn.ReLU(inplace=True)), + ('avg', nn.AdaptiveAvgPool2d(output_size=1)), + ('conv', nn.Conv2d(2048*wf, num_classes, kernel_size=1, bias=True)), + ])) + # fmt: on + + def features(self, x): + return self.body(self.root(x)) + + def logits(self, x): + return self.head(x) + + def forward(self, x): + x = self.logits(self.features(x)) + assert x.shape[-2:] == (1, 1) # We should have no spatial shape left. + return x[..., 0, 0] + + def load_from(self, weights, prefix="resnet/"): + with torch.no_grad(): + self.root.conv.weight.copy_( + tf2th(weights[f"{prefix}root_block/standardized_conv2d/kernel"]) + ) # pylint: disable=line-too-long + self.head.gn.weight.copy_(tf2th(weights[f"{prefix}group_norm/gamma"])) + self.head.gn.bias.copy_(tf2th(weights[f"{prefix}group_norm/beta"])) + # always zero_head + nn.init.zeros_(self.head.conv.weight) + nn.init.zeros_(self.head.conv.bias) + + for bname, block in self.body.named_children(): + for uname, unit in block.named_children(): + unit.load_from(weights, prefix=f"{prefix}{bname}/{uname}/") + + +KNOWN_MODELS = OrderedDict( + [ + ("BiT-M-R50x1", lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), + ("BiT-M-R50x3", lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), + ("BiT-M-R101x1", lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), + ("BiT-M-R101x3", lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), + ("BiT-M-R152x2", lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), + ("BiT-M-R152x4", lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), + ("BiT-S-R50x1", lambda *a, **kw: ResNetV2([3, 4, 6, 3], 1, *a, **kw)), + ("BiT-S-R50x3", lambda *a, **kw: ResNetV2([3, 4, 6, 3], 3, *a, **kw)), + ("BiT-S-R101x1", lambda *a, **kw: ResNetV2([3, 4, 23, 3], 1, *a, **kw)), + ("BiT-S-R101x3", lambda *a, **kw: ResNetV2([3, 4, 23, 3], 3, *a, **kw)), + ("BiT-S-R152x2", lambda *a, **kw: ResNetV2([3, 8, 36, 3], 2, *a, **kw)), + ("BiT-S-R152x4", lambda *a, **kw: ResNetV2([3, 8, 36, 3], 4, *a, **kw)), + ] +) PRETRAIN_SETTINGS = { @@ -215,96 +217,99 @@ def load_from(self, weights, prefix='resnet/'): # fmt: off CFGS = { - # weights are loaded by default - "bit_m_50x1": { - "default": { - "params": {"block_units": [3, 4, 6, 3], "width_factor": 1}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x1.npz", - **PRETRAIN_SETTINGS + # weights are loaded by default + "bit_m_50x1": { + "default": { + "params": {"block_units": [3, 4, 6, 3], "width_factor": 1}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x1.npz", + **PRETRAIN_SETTINGS + }, }, - }, - "bit_m_50x3": { - "default": { - "params": {"block_units": [3, 4, 6, 3], "width_factor": 3}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x3.npz", - **PRETRAIN_SETTINGS, + "bit_m_50x3": { + "default": { + "params": {"block_units": [3, 4, 6, 3], "width_factor": 3}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R50x3.npz", + **PRETRAIN_SETTINGS, + }, }, - }, - "bit_m_101x1": { - "default": { - "params": {"block_units": [3, 4, 23, 3], "width_factor": 1}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x1.npz", - **PRETRAIN_SETTINGS, + "bit_m_101x1": { + "default": { + "params": {"block_units": [3, 4, 23, 3], "width_factor": 1}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x1.npz", + **PRETRAIN_SETTINGS, + }, }, - }, - "bit_m_101x3": { - "default": { - "params": {"block_units": [3, 4, 23, 3], "width_factor": 3}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x3.npz", - **PRETRAIN_SETTINGS, + "bit_m_101x3": { + "default": { + "params": {"block_units": [3, 4, 23, 3], "width_factor": 3}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R101x3.npz", + **PRETRAIN_SETTINGS, + }, }, - }, - "bit_m_152x2": { - "default": { - "params": {"block_units": [3, 8, 36, 3], "width_factor": 2}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x2.npz", - **PRETRAIN_SETTINGS, + "bit_m_152x2": { + "default": { + "params": {"block_units": [3, 8, 36, 3], "width_factor": 2}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x2.npz", + **PRETRAIN_SETTINGS, + }, }, - }, - "bit_m_152x4": { - "default": { - "params": {"block_units": [3, 8, 36, 3], "width_factor": 4}, - "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x4.npz", - **PRETRAIN_SETTINGS + "bit_m_152x4": { + "default": { + "params": {"block_units": [3, 8, 36, 3], "width_factor": 4}, + "url": "https://storage.googleapis.com/bit_models/BiT-M-R152x4.npz", + **PRETRAIN_SETTINGS + }, }, - }, } # fmt: on def _bit_resnet(arch, pretrained=None, **kwargs): - cfgs = deepcopy(CFGS) - cfg_settings = cfgs[arch]["default"] - cfg_params = cfg_settings.pop("params") - cfg_url = cfg_settings.pop("url") - kwargs.pop("pretrained", None) - kwargs.update(cfg_params) - model = ResNetV2(**kwargs) - # load weights to torch checkpoints folder - try: - torch.hub.load_state_dict_from_url(cfg_url) - except RuntimeError: - pass # to avoid RuntimeError: Only one file(not dir) is allowed in the zipfile - filename = os.path.basename(urlparse(cfg_url).path) - torch_home = torch.hub._get_torch_home() - cached_file = os.path.join(torch_home, 'checkpoints', filename) - weights = np.load(cached_file) - model.load_from(weights) - return model + cfgs = deepcopy(CFGS) + cfg_settings = cfgs[arch]["default"] + cfg_params = cfg_settings.pop("params") + cfg_url = cfg_settings.pop("url") + kwargs.pop("pretrained", None) + kwargs.update(cfg_params) + model = ResNetV2(**kwargs) + # load weights to torch checkpoints folder + try: + torch.hub.load_state_dict_from_url(cfg_url) + except RuntimeError: + pass # to avoid RuntimeError: Only one file(not dir) is allowed in the zipfile + filename = os.path.basename(urlparse(cfg_url).path) + torch_home = torch.hub._get_torch_home() + cached_file = os.path.join(torch_home, "checkpoints", filename) + weights = np.load(cached_file) + model.load_from(weights) + return model + # only want M versions of models for fine-tuning @wraps(ResNetV2) def bit_m_50x1(**kwargs): - return _bit_resnet("bit_m_50x1", **kwargs) + return _bit_resnet("bit_m_50x1", **kwargs) + @wraps(ResNetV2) def bit_m_50x3(**kwargs): - return _bit_resnet("bit_m_50x3", **kwargs) + return _bit_resnet("bit_m_50x3", **kwargs) + @wraps(ResNetV2) def bit_m_101x1(**kwargs): - return _bit_resnet("bit_m_101x1", **kwargs) + return _bit_resnet("bit_m_101x1", **kwargs) + @wraps(ResNetV2) def bit_m_101x3(**kwargs): - return _bit_resnet("bit_m_101x3", **kwargs) + return _bit_resnet("bit_m_101x3", **kwargs) + @wraps(ResNetV2) def bit_m_152x2(**kwargs): - return _bit_resnet("bit_m_152x2", **kwargs) + return _bit_resnet("bit_m_152x2", **kwargs) + @wraps(ResNetV2) def bit_m_152x4(**kwargs): - return _bit_resnet("bit_m_152x4", **kwargs) - - - + return _bit_resnet("bit_m_152x4", **kwargs) diff --git a/pytorch_tools/models/efficientnet.py b/pytorch_tools/models/efficientnet.py index ca5e337..17e67c0 100644 --- a/pytorch_tools/models/efficientnet.py +++ b/pytorch_tools/models/efficientnet.py @@ -144,7 +144,7 @@ def __init__( self.dropout = nn.Dropout(drop_rate, inplace=True) self.classifier = nn.Linear(num_features, num_classes) - patch_bn(self) # adjust epsilon + patch_bn(self) # adjust epsilon initialize(self) if match_tf_same_padding: conv_to_same_conv(self) @@ -397,7 +397,8 @@ def patch_bn(module): module.eps = 1e-3 for m in module.children(): patch_bn(m) - + + def _efficientnet(arch, pretrained=None, **kwargs): cfgs = deepcopy(CFGS) cfg_settings = cfgs[arch]["default"] @@ -426,8 +427,10 @@ def _efficientnet(arch, pretrained=None, **kwargs): ) state_dict["classifier.weight"] = model.state_dict()["classifier.weight"] state_dict["classifier.bias"] = model.state_dict()["classifier.bias"] - if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels - state_dict["conv_stem.weight"] = repeat_channels(state_dict["conv_stem.weight"], kwargs["in_channels"]) + if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels + state_dict["conv_stem.weight"] = repeat_channels( + state_dict["conv_stem.weight"], kwargs["in_channels"] + ) model.load_state_dict(state_dict) setattr(model, "pretrained_settings", cfg_settings) return model diff --git a/pytorch_tools/models/hrnet.py b/pytorch_tools/models/hrnet.py index 0a65182..cbf5817 100644 --- a/pytorch_tools/models/hrnet.py +++ b/pytorch_tools/models/hrnet.py @@ -43,22 +43,23 @@ def make_layer(inplanes, planes, blocks, norm_layer=ABN, norm_act="relu"): layers = [] layers.append(block(inplanes, planes, downsample=downsample, **bn_args)) inplanes = planes * block.expansion - for i in range(1, blocks): + for _ in range(1, blocks): layers.append(block(inplanes, planes, **bn_args)) return nn.Sequential(*layers) + class HighResolutionModule(nn.Module): def __init__( - self, - num_branches, # number of parallel branches - num_blocks, # number of blocks + self, + num_branches, # number of parallel branches + num_blocks, # number of blocks num_channels, norm_layer=ABN, norm_act="relu", ): super(HighResolutionModule, self).__init__() self.block = BasicBlock - self.num_branches = num_branches # used in forward + self.num_branches = num_branches # used in forward self.num_inchannels = num_channels self.bn_args = {"norm_layer": norm_layer, "norm_act": norm_act} branches = [self._make_branch(n_bl, n_ch) for n_bl, n_ch in zip(num_blocks, num_channels)] @@ -69,6 +70,7 @@ def __init__( def _make_branch(self, b_blocks, b_channels): return nn.Sequential(*[self.block(b_channels, b_channels, **self.bn_args) for _ in range(b_blocks)]) + # fmt: off # don't want to rewrite this piece it's too fragile def _make_fuse_layers(self, norm_layer, norm_act): if self.num_branches == 1: @@ -104,23 +106,24 @@ def _make_fuse_layers(self, norm_layer, norm_act): fuse_layers.append(nn.ModuleList(fuse_layer)) return nn.ModuleList(fuse_layers) - + # fmt: on def forward(self, x): if self.num_branches == 1: return [self.branches[0](x[0])] - + x = [branch(x_i) for branch, x_i in zip(self.branches, x)] x_fuse = [] for i in range(len(self.fuse_layers)): y = x[0] if i == 0 else self.fuse_layers[i][0](x[0]) for j in range(1, self.num_branches): - y = y + self.fuse_layers[i][j](x[j]) + y = y + self.fuse_layers[i][j](x[j]) x_fuse.append(self.relu(y)) return x_fuse + class TransitionBlock(nn.Module): """Transition is where new branches for smaller resolution are born -- ==> -- @@ -129,7 +132,7 @@ class TransitionBlock(nn.Module): \ \=> -- """ - + def __init__(self, prev_channels, current_channels, norm_layer=ABN, norm_act="relu"): super().__init__() transition_layers = [] @@ -140,40 +143,40 @@ def __init__(self, prev_channels, current_channels, norm_layer=ABN, norm_act="re transition_layers.append(nn.Sequential(*layers)) else: transition_layers.append(nn.Identity()) - - if len(current_channels) > len(prev_channels): # only works for ONE extra branch + + if len(current_channels) > len(prev_channels): # only works for ONE extra branch layers = [ - conv3x3(prev_channels[-1], current_channels[-1], 2), - norm_layer(current_channels[-1], activation=norm_act) + conv3x3(prev_channels[-1], current_channels[-1], 2), + norm_layer(current_channels[-1], activation=norm_act), ] transition_layers.append(nn.Sequential(*layers)) self.trans_layers = nn.ModuleList(transition_layers) - - def forward(self, x): # x is actually an array + + def forward(self, x): # x is actually an array out_x = [trans_l(x_i) for x_i, trans_l in zip(x, self.trans_layers)] out_x.append(self.trans_layers[-1](x[-1])) return out_x + class HRClassificationHead(nn.Module): def __init__(self, pre_channels, norm_layer=ABN, norm_act="relu"): super().__init__() head_block = Bottleneck head_channels = [32, 64, 128, 256] - # Increasing the #channels on each resolution + # Increasing the #channels on each resolution # from C, 2C, 4C, 8C to 128, 256, 512, 1024 incre_modules = [] for (pre_c, head_c) in zip(pre_channels, head_channels): incre_modules.append(make_layer(pre_c, head_c, 1, norm_layer, norm_act)) self.incre_modules = nn.ModuleList(incre_modules) - + # downsampling modules downsamp_modules = [] - for i in range(len(pre_channels)-1): + for i in range(len(pre_channels) - 1): in_ch = head_channels[i] * head_block.expansion - out_ch = head_channels[i+1] * head_block.expansion + out_ch = head_channels[i + 1] * head_block.expansion downsamp_module = nn.Sequential( - conv3x3(in_ch, out_ch, 2, bias=True), - norm_layer(out_ch, activation=norm_act) + conv3x3(in_ch, out_ch, 2, bias=True), norm_layer(out_ch, activation=norm_act) ) downsamp_modules.append(downsamp_module) self.downsamp_modules = nn.ModuleList(downsamp_modules) @@ -182,13 +185,13 @@ def __init__(self, pre_channels, norm_layer=ABN, norm_act="relu"): conv1x1(head_channels[3] * head_block.expansion, 2048, bias=True), norm_layer(2048, activation=norm_act), ) - + def forward(self, x): - x = [self.incre_modules[i](x[i]) for i in range(4)] + x = [self.incre_modules[i](x[i]) for i in range(4)] for i in range(1, 4): - x[i] = x[i] + self.downsamp_modules[i-1](x[i-1]) + x[i] = x[i] + self.downsamp_modules[i - 1](x[i - 1]) return self.final_layer(x[3]) - + class HighResolutionNet(nn.Module): """HighResolution Nets constructor @@ -219,13 +222,14 @@ class HighResolutionNet(nn.Module): NOTE: HRNet first features have resolution 4x times smaller than input, not 2x as all other models. So it CAN'T be used as encoder in Unet and Linknet models """ - # drop_rate (float): - # Dropout probability before classifier, for training. Defaults to 0. + + # drop_rate (float): + # Dropout probability before classifier, for training. Defaults to 0. def __init__( - self, + self, width=18, small=False, - pretrained=None, # not used. here for proper signature + pretrained=None, # not used. here for proper signature num_classes=1000, in_channels=3, norm_layer="abn", @@ -241,27 +245,25 @@ def __init__( self.conv2 = conv3x3(stem_width, stem_width, stride=2) self.bn2 = norm_layer(stem_width, activation=norm_act) - + channels = [width, width * 2, width * 4, width * 8] n_blocks = [2 if small else 4] * 4 - + self.layer1 = make_layer(stem_width, stem_width, n_blocks[0], **bn_args) - + self.transition1 = TransitionBlock([stem_width * Bottleneck.expansion], channels[:2], **bn_args) - self.stage2 = self._make_stage( - n_modules=1, n_branches=2, n_blocks=n_blocks[:2], n_chnls=channels[:2] - ) - + self.stage2 = self._make_stage(n_modules=1, n_branches=2, n_blocks=n_blocks[:2], n_chnls=channels[:2]) + self.transition2 = TransitionBlock(channels[:2], channels[:3], **bn_args) - self.stage3 = self._make_stage( # 3 if small else 4 - n_modules=(4,3)[small], n_branches=3, n_blocks=n_blocks[:3], n_chnls=channels[:3] + self.stage3 = self._make_stage( # 3 if small else 4 + n_modules=(4, 3)[small], n_branches=3, n_blocks=n_blocks[:3], n_chnls=channels[:3] ) - + self.transition3 = TransitionBlock(channels[:3], channels, **bn_args) - self.stage4 = self._make_stage( # 2 if small else 3 - n_modules=(3,2)[small], n_branches=4, n_blocks=n_blocks, n_chnls=channels, + self.stage4 = self._make_stage( # 2 if small else 3 + n_modules=(3, 2)[small], n_branches=4, n_blocks=n_blocks, n_chnls=channels, ) - + self.encoder = encoder if encoder: self.forward = self.encoder_features @@ -276,16 +278,9 @@ def __init__( def _make_stage(self, n_modules, n_branches, n_blocks, n_chnls): modules = [] for i in range(n_modules): - modules.append( - HighResolutionModule( - n_branches, - n_blocks, - n_chnls, - **self.bn_args, - ) - ) + modules.append(HighResolutionModule(n_branches, n_blocks, n_chnls, **self.bn_args,)) return nn.Sequential(*modules) - + def encoder_features(self, x): # stem x = self.conv1(x) @@ -293,46 +288,46 @@ def encoder_features(self, x): x = self.conv2(x) x = self.bn2(x) x = self.layer1(x) - - x = self.transition1([x]) # x is actually a list now + + x = self.transition1([x]) # x is actually a list now x = self.stage2(x) - + x = self.transition2(x) x = self.stage3(x) - + x = self.transition3(x) x = self.stage4(x) - if self.encoder: # want to return from lowest resolution to highest + if self.encoder: # want to return from lowest resolution to highest x = [x[3], x[2], x[1], x[0], x[0]] return x - + def features(self, x): x = self.encoder_features(x) x = self.cls_head(x) return x - + def logits(self, x): x = self.global_pool(x) x = torch.flatten(x, 1) -# x = self.dropout(x) + # x = self.dropout(x) x = self.last_linear(x) return x - + def forward(self, x): x = self.features(x) x = self.logits(x) return x - + def load_state_dict(self, state_dict, **kwargs): self_keys = list(self.state_dict().keys()) sd_keys = list(state_dict.keys()) - sd_keys = [k for k in sd_keys if "num_batches_tracked" not in k] # filter + sd_keys = [k for k in sd_keys if "num_batches_tracked" not in k] # filter new_state_dict = {} for new_key, old_key in zip(self_keys, sd_keys): new_state_dict[new_key] = state_dict[old_key] super().load_state_dict(new_state_dict, **kwargs) - - + + # fmt: off CFGS = { "hrnet_w18_small": { @@ -368,9 +363,10 @@ def load_state_dict(self, state_dict, **kwargs): "imagenet": {"url": None}, }, } - + # fmt:on - + + def _hrnet(arch, pretrained=None, **kwargs): cfgs = deepcopy(CFGS) cfg_settings = cfgs[arch]["default"] @@ -420,7 +416,7 @@ def hrnet_w18_small(**kwargs): def hrnet_w18(**kwargs): r"""Constructs a HRNetv2-18 model.""" return _hrnet("hrnet_w18", **kwargs) - + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) @@ -428,33 +424,37 @@ def hrnet_w30(**kwargs): r"""Constructs a HRNetv2-30 model.""" return _hrnet("hrnet_w30", **kwargs) + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) def hrnet_w32(**kwargs): r"""Constructs a HRNetv2-32 model.""" return _hrnet("hrnet_w32", **kwargs) + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) def hrnet_w40(**kwargs): r"""Constructs a HRNetv2-40 model.""" return _hrnet("hrnet_w40", **kwargs) + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) def hrnet_w44(**kwargs): r"""Constructs a HRNetv2-44 model.""" return _hrnet("hrnet_w44", **kwargs) + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) def hrnet_w48(**kwargs): r"""Constructs a HRNetv2-48 model.""" return _hrnet("hrnet_w48", **kwargs) + @wraps(HighResolutionNet) @add_docs_for(HighResolutionNet) def hrnet_w64(**kwargs): r"""Constructs a HRNetv2-64 model.""" return _hrnet("hrnet_w64", **kwargs) - diff --git a/pytorch_tools/models/resnet.py b/pytorch_tools/models/resnet.py index 0490cb6..bdc9e57 100644 --- a/pytorch_tools/models/resnet.py +++ b/pytorch_tools/models/resnet.py @@ -214,7 +214,7 @@ def _make_stem(self, stem_type, stem_width, in_channels, norm_layer, norm_act): # in the paper they use conv1x1 but in code conv3x3 (which seems better) self.conv1 = nn.Sequential(SpaceToDepth(), conv3x3(in_channels * 16, stem_width)) self.bn1 = norm_layer(stem_width, activation=norm_act) - self.maxpool = nn.Identity() # not used but needed for code compatability + self.maxpool = nn.Identity() # not used but needed for code compatability else: if stem_type == "deep": self.conv1 = nn.Sequential( @@ -225,7 +225,9 @@ def _make_stem(self, stem_type, stem_width, in_channels, norm_layer, norm_act): conv3x3(stem_width // 2, stem_width), ) else: - self.conv1 = nn.Conv2d(in_channels, stem_width, kernel_size=7, stride=2, padding=3, bias=False) + self.conv1 = nn.Conv2d( + in_channels, stem_width, kernel_size=7, stride=2, padding=3, bias=False + ) self.bn1 = norm_layer(stem_width, activation=norm_act) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) @@ -296,6 +298,7 @@ def keep_prob(self): self.block_idx += 1 return keep_prob + # fmt: off CFGS = { # RESNET MODELS diff --git a/pytorch_tools/models/tresnet.py b/pytorch_tools/models/tresnet.py index 022c9b0..9be0c36 100644 --- a/pytorch_tools/models/tresnet.py +++ b/pytorch_tools/models/tresnet.py @@ -19,6 +19,7 @@ # avoid overwriting doc string wraps = partial(wraps, assigned=("__module__", "__name__", "__qualname__", "__annotations__")) + class TResNet(ResNet): """TResNet M / TResNet L / XL @@ -71,13 +72,13 @@ def __init__( drop_rate=0.0, drop_connect_rate=0.0, ): - nn.Module.__init__(self) + nn.Module.__init__(self) stem_width = int(64 * width_factor) norm_layer = bn_from_name(norm_layer) self.inplanes = stem_width self.num_classes = num_classes - self.groups = 1 # not really used but needed inside _make_layer - self.base_width = 64 # used inside _make_layer + self.groups = 1 # not really used but needed inside _make_layer + self.base_width = 64 # used inside _make_layer self.norm_act = norm_act self.block_idx = 0 self.num_blocks = sum(layers) @@ -89,9 +90,9 @@ def __init__( raise ValueError("Output stride should be in [8, 16, 32]") # TODO add OS later # if output_stride == 8: - # stride_3, stride_4, dilation_3, dilation_4 = 1, 1, 2, 4 + # stride_3, stride_4, dilation_3, dilation_4 = 1, 1, 2, 4 # elif output_stride == 16: - # stride_3, stride_4, dilation_3, dilation_4 = 2, 1, 1, 2 + # stride_3, stride_4, dilation_3, dilation_4 = 2, 1, 1, 2 # elif output_stride == 32: stride_3, stride_4, dilation_3, dilation_4 = 2, 2, 1, 1 @@ -101,11 +102,15 @@ def __init__( self.layer1 = self._make_layer(stem_width, layers[0], stride=1, **largs) self.layer2 = self._make_layer(stem_width * 2, layers[1], stride=2, **largs) - self.block = TBottleneck # first 2 - Basic, last 2 - Bottleneck + self.block = TBottleneck # first 2 - Basic, last 2 - Bottleneck self.expansion = TBottleneck.expansion - self.layer3 = self._make_layer(stem_width * 4, layers[2], stride=stride_3, dilation=dilation_3, **largs) - largs.update(attn_type=None) # no se in last layer - self.layer4 = self._make_layer(stem_width * 8, layers[3], stride=stride_4, dilation=dilation_4, **largs) + self.layer3 = self._make_layer( + stem_width * 4, layers[2], stride=stride_3, dilation=dilation_3, **largs + ) + largs.update(attn_type=None) # no se in last layer + self.layer4 = self._make_layer( + stem_width * 8, layers[3], stride=stride_4, dilation=dilation_4, **largs + ) self.global_pool = FastGlobalAvgPool2d(flatten=True) self.num_features = stem_width * 8 * self.expansion self.encoder = encoder @@ -123,6 +128,7 @@ def load_state_dict(self, state_dict, **kwargs): state_dict.pop("last_linear.bias") nn.Module.load_state_dict(self, state_dict, **kwargs) + # fmt: off # images should be normalized to [0, 1] PRETRAIN_SETTINGS = { @@ -169,6 +175,7 @@ def load_state_dict(self, state_dict, **kwargs): } # fmt: on + def patch_bn(module): """changes weight from InplaceABN to be compatible with usual ABN""" if isinstance(module, ABN): @@ -176,6 +183,7 @@ def patch_bn(module): for m in module.children(): patch_bn(m) + def _resnet(arch, pretrained=None, **kwargs): cfgs = deepcopy(CFGS) cfg_settings = cfgs[arch]["default"] @@ -204,27 +212,32 @@ def _resnet(arch, pretrained=None, **kwargs): # if there is last_linear in state_dict, it's going to be overwritten state_dict["last_linear.weight"] = model.state_dict()["last_linear.weight"] state_dict["last_linear.bias"] = model.state_dict()["last_linear.bias"] - if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels - state_dict["conv1.1.weight"] = repeat_channels(state_dict["conv1.1.weight"], kwargs["in_channels"] * 16, 3 * 16) + if kwargs.get("in_channels", 3) != 3: # support pretrained for custom input channels + state_dict["conv1.1.weight"] = repeat_channels( + state_dict["conv1.1.weight"], kwargs["in_channels"] * 16, 3 * 16 + ) model.load_state_dict(state_dict) patch_bn(model) setattr(model, "pretrained_settings", cfg_settings) return model + @wraps(TResNet) @add_docs_for(TResNet) def tresnetm(**kwargs): r"""Constructs a TResnetM model.""" return _resnet("tresnetm", **kwargs) + @wraps(TResNet) @add_docs_for(TResNet) def tresnetl(**kwargs): r"""Constructs a TResnetL model.""" return _resnet("tresnetl", **kwargs) + @wraps(TResNet) @add_docs_for(TResNet) def tresnetxl(**kwargs): r"""Constructs a TResnetXL model.""" - return _resnet("tresnetxl", **kwargs) \ No newline at end of file + return _resnet("tresnetxl", **kwargs) diff --git a/pytorch_tools/models/vgg.py b/pytorch_tools/models/vgg.py index b2037ce..0f17b85 100644 --- a/pytorch_tools/models/vgg.py +++ b/pytorch_tools/models/vgg.py @@ -90,7 +90,6 @@ def forward(self, x): x = self.logits(x) return x - def _make_layers(self, cfg): layers = [] in_channels = self.in_channels diff --git a/pytorch_tools/modules/activated_batch_norm.py b/pytorch_tools/modules/activated_batch_norm.py index f4e0786..586ae0d 100644 --- a/pytorch_tools/modules/activated_batch_norm.py +++ b/pytorch_tools/modules/activated_batch_norm.py @@ -46,7 +46,7 @@ def __init__( self.activation = ACT(activation) self.activation_param = activation_param self.frozen = frozen - + if frozen: self.register_buffer("weight", torch.ones(num_features)) self.register_buffer("bias", torch.zeros(num_features)) diff --git a/pytorch_tools/modules/tf_same_ops.py b/pytorch_tools/modules/tf_same_ops.py index 1c5adb6..cfb8026 100644 --- a/pytorch_tools/modules/tf_same_ops.py +++ b/pytorch_tools/modules/tf_same_ops.py @@ -5,6 +5,7 @@ import torch.nn.functional as F from torch.nn.modules.utils import _pair + def pad_same(x, k, s, d, value=0): # type: (Tensor, int, int, int, float)->Tensor # x - input tensor, s - stride, k - kernel_size, d - dilation @@ -15,26 +16,31 @@ def pad_same(x, k, s, d, value=0): x = F.pad(x, [pad_w // 2, pad_w - pad_w // 2, pad_h // 2, pad_h - pad_h // 2], value=value) return x -# current implementation is only for symmetric case. But there are no non symmetric cases + +# current implementation is only for symmetric case. But there are no non symmetric cases def conv2d_same(x, weight, bias=None, stride=(1, 1), dilation=(1, 1), groups=1): # type: (Tensor, Tensor, Optional[torch.Tensor], Tuple[int, int], Tuple[int, int], int)->Tensor x = pad_same(x, weight.shape[-1], stride[0], dilation[0]) return F.conv2d(x, weight, bias, stride, (0, 0), dilation, groups) + def maxpool2d_same(x, kernel_size, stride): # type: (Tensor, Tuple[int, int], Tuple[int, int])->Tensor - x = pad_same(x, kernel_size[0], stride[0], 1, value=-float('inf')) + x = pad_same(x, kernel_size[0], stride[0], 1, value=-float("inf")) return F.max_pool2d(x, kernel_size, stride, (0, 0)) + class Conv2dSamePadding(nn.Conv2d): """Assymetric padding matching TensorFlow `same`""" def forward(self, x): return conv2d_same(x, self.weight, self.bias, self.stride, self.dilation, self.groups) -# as of 1.5 there is no _pair in MaxPool. Remove when this is fixed + +# as of 1.5 there is no _pair in MaxPool. Remove when this is fixed class MaxPool2dSamePadding(nn.MaxPool2d): """Assymetric padding matching TensorFlow `same`""" + def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.kernel_size = _pair(self.kernel_size) diff --git a/pytorch_tools/utils/box.py b/pytorch_tools/utils/box.py index 73bb377..16b349e 100644 --- a/pytorch_tools/utils/box.py +++ b/pytorch_tools/utils/box.py @@ -3,6 +3,7 @@ import numpy as np from functools import wraps + def box2delta(boxes, anchors): # type: (Tensor, Tensor)->Tensor """Convert boxes to deltas from anchors. Boxes are expected in 'ltrb' format @@ -14,9 +15,9 @@ def box2delta(boxes, anchors): offset_x, offset_y, scale_x, scale_y """ - anchors_wh = anchors[..., 2:] - anchors[..., :2] # + 1 + anchors_wh = anchors[..., 2:] - anchors[..., :2] anchors_ctr = anchors[..., :2] + 0.5 * anchors_wh - boxes_wh = boxes[..., 2:] - boxes[..., :2] # + 1 + boxes_wh = boxes[..., 2:] - boxes[..., :2] boxes_ctr = boxes[..., :2] + 0.5 * boxes_wh offset_delta = (boxes_ctr - anchors_ctr) / anchors_wh scale_delta = torch.log(boxes_wh / anchors_wh) @@ -40,7 +41,7 @@ def delta2box(deltas, anchors): # Value for clamping large dw and dh predictions. The heuristic is that we clamp # such that dw and dh are no larger than what would transform a 16px box into a # 1000px box (based on a small anchor, 16px, and a typical image size, 1000px). - SCALE_CLAMP = 4.135 # ~= np.log(1000. / 16.) + SCALE_CLAMP = 4.135 # ~= np.log(1000. / 16.) deltas[..., 2:] = deltas[..., 2:].clamp(min=-SCALE_CLAMP, max=SCALE_CLAMP) pred_wh = deltas[..., 2:].exp() * anchors_wh @@ -53,6 +54,7 @@ def box_area(box): """ return (box[..., 2] - box[..., 0]) * (box[..., 3] - box[..., 1]) + def clip_bboxes(bboxes, size): """Args: bboxes (torch.Tensor): in `ltrb` format. Shape [N, 4] @@ -61,14 +63,15 @@ def clip_bboxes(bboxes, size): bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, size[0]) return bboxes + def clip_bboxes_batch(bboxes, size): # type: (Tensor, Tensor)->Tensor """Args: bboxes (torch.Tensor): in `ltrb` format. Shape [BS, N, 4] size (torch.Tensor): (H, W). Shape [BS, 2] """ size = size.to(bboxes) - h_size = size[..., 0].view(-1, 1, 1) #.float() - w_size = size[..., 1].view(-1, 1, 1) #.float() + h_size = size[..., 0].view(-1, 1, 1) # .float() + w_size = size[..., 1].view(-1, 1, 1) # .float() h_bboxes = bboxes[..., 1::2] w_bboxes = bboxes[..., 0::2] zeros = torch.zeros_like(h_bboxes) @@ -79,6 +82,7 @@ def clip_bboxes_batch(bboxes, size): # bboxes[:, 1::2] = bboxes[:, 1::2].clamp(0, size[0].item()) return bboxes + # implementation from https://github.com/kuangliu/torchcv/blob/master/torchcv/utils/box.py # with slight modifications def box_iou(boxes1, boxes2): @@ -107,13 +111,9 @@ def box_iou(boxes1, boxes2): # based on https://github.com/NVIDIA/retinanet-examples/ -# and on https://github.com/google/automl/ +# and on https://github.com/google/automl/ def generate_anchors_boxes( - image_size, - num_scales=3, - aspect_ratios=(1.0, 2.0, 0.5), - pyramid_levels=[3, 4, 5, 6, 7], - anchor_scale=4, + image_size, num_scales=3, aspect_ratios=(1.0, 2.0, 0.5), pyramid_levels=[3, 4, 5, 6, 7], anchor_scale=4, ): """Generates multiscale anchor boxes Minimum object size which could be detected is anchor_scale * 2**pyramid_levels[0]. By default it's 32px @@ -132,28 +132,28 @@ def generate_anchors_boxes( boxes are in 'ltrb' format num_anchors (int): number of anchors per location """ - + if isinstance(image_size, int): image_size = (image_size, image_size) scale_vals = [anchor_scale * 2 ** (i / num_scales) for i in range(num_scales)] # from lowest stride to largest. Anchors from models should be in the same order! - strides = [2**i for i in pyramid_levels] - + strides = [2 ** i for i in pyramid_levels] + # get offsets for anchor boxes for one pixel # can rewrite in pure Torch but using np is more convenient. This function usually should only be called once num_anchors = len(scale_vals) * len(aspect_ratios) ratio_vals_sq = np.sqrt(np.tile(aspect_ratios, len(scale_vals))) scale_vals = np.repeat(scale_vals, len(aspect_ratios))[:, np.newaxis] - wh = np.stack([np.ones(num_anchors) * ratio_vals_sq, np.ones(num_anchors) / ratio_vals_sq], axis=1) - lt = - 0.5 * wh * scale_vals + wh = np.stack([np.ones(num_anchors) * ratio_vals_sq, np.ones(num_anchors) / ratio_vals_sq], axis=1) + lt = -0.5 * wh * scale_vals rb = 0.5 * wh * scale_vals - base_offsets = torch.from_numpy(np.hstack([lt, rb])).float() # [num_anchors, 4] - base_offsets = base_offsets.view(-1, 1, 1, 4) # [num_anchors, 1, 1, 4] + base_offsets = torch.from_numpy(np.hstack([lt, rb])).float() # [num_anchors, 4] + base_offsets = base_offsets.view(-1, 1, 1, 4) # [num_anchors, 1, 1, 4] # generate anchor boxes for all given strides all_anchors = [] for stride in strides: y, x = torch.meshgrid([torch.arange(stride / 2, image_size[i], stride) for i in range(2)]) - xyxy = torch.stack((x, y, x, y), 2).unsqueeze(0) + xyxy = torch.stack((x, y, x, y), 2).unsqueeze(0) # permute to match TF EffDet anchors order after reshape anchors = (xyxy + base_offsets * stride).permute(1, 2, 0, 3).reshape(-1, 4) all_anchors.append(anchors) @@ -162,6 +162,7 @@ def generate_anchors_boxes( # clip_bboxes(all_anchors, image_size) return all_anchors, num_anchors + def generate_targets(anchors, batch_gt_boxes, num_classes, matched_iou=0.5, unmatched_iou=0.4): """Generate targets for regression and classification @@ -169,34 +170,35 @@ def generate_targets(anchors, batch_gt_boxes, num_classes, matched_iou=0.5, unma 1) IoU >= matched_iou: Highest similarity. Matched/Positive. Mask value is 1 2) matched_iou > IoU >= unmatched_iou: Medium similarity. Ignored. Mask value is -1 3) unmatched_iou > IoU: Lowest similarity. Unmatched/Negative. Mask value is 0 - + Args: anchors (torch.Tensor): all anchors on a single image. shape [N, 4] - batch_gt_boxes (torch.Tesor): all groud truth bounding boxes and classes for the batch. shape [BS, N, 5] - classes are expected to be in the last column. + batch_gt_boxes (torch.Tensor): all ground truth bounding boxes and classes for the batch. shape [BS, N, 5] + classes are expected to be in the last column. bboxes are in `ltrb` format! num_classes (int): number of classes. needed for one-hot encoding labels - matched_iou (float): + matched_iou (float): unmatched_iou (float): - + Returns: box_target, cls_target, matches_mask - + """ + def _generate_single_targets(gt_boxes): gt_boxes, gt_classes = gt_boxes.split(4, dim=1) overlap = box_iou(anchors, gt_boxes) - + # Keep best box per anchor overlap, indices = overlap.max(1) box_target = box2delta(gt_boxes[indices], anchors) - - # There are three types of anchors. + + # There are three types of anchors. # matched (with objects), unmatched (with background), and in between (which should be ignored) IGNORED_VALUE = -1 UNMATCHED_VALUE = 0 matches_mask = torch.ones_like(overlap) * IGNORED_VALUE - matches_mask[overlap < unmatched_iou] = UNMATCHED_VALUE # background + matches_mask[overlap < unmatched_iou] = UNMATCHED_VALUE # background matches_mask[overlap >= matched_iou] = 1 # Generate one-hot-encoded target classes @@ -206,11 +208,11 @@ def _generate_single_targets(gt_boxes): gt_classes = gt_classes[indices].long() gt_classes[overlap < unmatched_iou] = num_classes # background has no class cls_target.scatter_(1, gt_classes, 1) - cls_target = cls_target[:, :num_classes] # remove background class from one-hot + cls_target = cls_target[:, :num_classes] # remove background class from one-hot return cls_target, box_target, matches_mask - - anchors = anchors.to(batch_gt_boxes) # change device & type if needed + + anchors = anchors.to(batch_gt_boxes) # change device & type if needed batch_results = ([], [], []) for single_gt_boxes in batch_gt_boxes: single_target_results = _generate_single_targets(single_gt_boxes) @@ -218,7 +220,8 @@ def _generate_single_targets(gt_boxes): batch_res.append(single_res) b_cls_target, b_box_target, b_matches_mask = [torch.stack(targets) for targets in batch_results] return b_cls_target, b_box_target, b_matches_mask - + + # copied from torchvision def batched_nms(boxes, scores, idxs, iou_threshold): # type: (Tensor, Tensor, Tensor, float)->Tensor @@ -257,9 +260,10 @@ def batched_nms(boxes, scores, idxs, iou_threshold): keep = torch.ops.torchvision.nms(boxes_for_nms, scores, iou_threshold) return keep + # jit actually makes it slower for fp16 and results are different! # FIXME: check it after 1.6 release. maybe they will fix JIT by that time -# @torch.jit.script +# @torch.jit.script def decode( batch_cls_head, batch_box_head, @@ -274,8 +278,8 @@ def decode( # type: (Tensor, Tensor, Tensor, Tensor, Tensor, float, int, int, float)->Tensor """ Decodes raw outputs of a model for easy visualization of bboxes - - Args: + + Args: batch_cls_head (torch.Tensor): shape [BS, *, NUM_CLASSES] batch_box_head (torch.Tensor): shape [BS, *, 4] anchors (torch.Tensor): shape [*, 4] @@ -285,25 +289,27 @@ def decode( max_detection_points (int): Maximum number of bboxes to consider for NMS for one image max_detection_per_image (int): Maximum number of bboxes to return per image iou_threshold (float): iou_threshold for Non Maximum Supression - + Returns: torch.Tensor with bboxes, scores and classes shape [BS, MAX_DETECTION_PER_IMAGE, 6]. bboxes in 'ltrb' format. If img_shape is not given they are NOT CLIPPED (!) """ - + batch_size = batch_cls_head.size(0) num_classes = batch_cls_head.size(-1) - anchors = anchors.to(batch_cls_head).unsqueeze(0).expand(batch_size, -1, -1) # [N, 4] -> [BS, N, 4] + anchors = anchors.to(batch_cls_head).unsqueeze(0).expand(batch_size, -1, -1) # [N, 4] -> [BS, N, 4] # it has to be raw logits but check anyway to avoid applying sigmoid twice if batch_cls_head.min() < 0 or batch_cls_head.max() > 1: batch_cls_head = batch_cls_head.sigmoid() - - # It's much faster to calculate topk once for full batch here rather than doing it inside loop + + # It's much faster to calculate topk once for full batch here rather than doing it inside loop # In TF The same bbox may belong to two different objects # select `max_detection_points` scores and corresponding bboxes - scores_topk_all, cls_topk_indices_all = torch.topk(batch_cls_head.view(batch_size, -1), k=max_detection_points) + scores_topk_all, cls_topk_indices_all = torch.topk( + batch_cls_head.view(batch_size, -1), k=max_detection_points + ) indices_all = cls_topk_indices_all / num_classes classes_all = cls_topk_indices_all % num_classes @@ -322,32 +328,29 @@ def decode( out_classes = torch.zeros((batch_size, max_detection_per_image)).to(batch_cls_head) for batch in range(batch_size): - scores_topk = scores_topk_all[batch] # , cls_topk_indices_all[batch] - classes = classes_all[batch] #cls_topk_indices % num_classes - box_topk = box_topk_all[batch] # torch.gather(batch_box_head[batch], 0, indices) - anchor_topk = anchors_topk_all[batch] - regressed_boxes = regressed_boxes_all[batch] # delta2box(box_topk, anchor_topk) + scores_topk = scores_topk_all[batch] # , cls_topk_indices_all[batch] + classes = classes_all[batch] # cls_topk_indices % num_classes + regressed_boxes = regressed_boxes_all[batch] # delta2box(box_topk, anchor_topk) # apply NMS nms_idx = batched_nms(regressed_boxes, scores_topk, classes, iou_threshold) - nms_idx = nms_idx[:min(len(nms_idx), max_detection_per_image)] + nms_idx = nms_idx[: min(len(nms_idx), max_detection_per_image)] # select suppressed bboxes im_scores = scores_topk[nms_idx] im_classes = classes[nms_idx] im_bboxes = regressed_boxes[nms_idx] im_classes += 1 # back to class idx with background class = 0 - out_scores[batch, :im_scores.size(0)] = im_scores - out_classes[batch, :im_classes.size(0)] = im_classes - out_boxes[batch, :im_bboxes.size(0)] = im_bboxes + out_scores[batch, : im_scores.size(0)] = im_scores + out_classes[batch, : im_classes.size(0)] = im_classes + out_boxes[batch, : im_bboxes.size(0)] = im_bboxes # no need to pad because it's already padded with 0's - ## old way ## # get regressed bboxes # all_img_bboxes = delta2box(batch_box_head[batch], anchors) # if img_shape: # maybe clip - # all_img_bboxes = clip_bboxes(all_img_bboxes, img_shape) + # all_img_bboxes = clip_bboxes(all_img_bboxes, img_shape) # select at most `top_n` bboxes and from them select with score > threshold # max_cls_score, max_cls_idx = batch_cls_head[batch].max(1) # top_cls_score, top_cls_idx = max_cls_score.topk(top_n) @@ -356,15 +359,15 @@ def decode( # im_scores = max_cls_score[top_cls_idx] # im_classes = max_cls_idx[top_cls_idx] # im_bboxes = all_img_bboxes[top_cls_idx] - + # apply NMS # nms_idx = batched_nms(im_bboxes, im_scores, im_classes, iou_threshold) # im_scores = im_scores[nms_idx] # im_classes = im_classes[nms_idx] # im_bboxes = im_bboxes[nms_idx] - + # out_scores[batch, :im_scores.size(0)] = im_scores # out_classes[batch, :im_classes.size(0)] = im_classes # out_boxes[batch, :im_bboxes.size(0)] = im_bboxes - - return torch.cat([out_boxes, out_scores.unsqueeze(-1), out_classes.unsqueeze(-1)], dim=2) \ No newline at end of file + + return torch.cat([out_boxes, out_scores.unsqueeze(-1), out_classes.unsqueeze(-1)], dim=2) diff --git a/pytorch_tools/utils/misc.py b/pytorch_tools/utils/misc.py index cf1fc85..733a22e 100644 --- a/pytorch_tools/utils/misc.py +++ b/pytorch_tools/utils/misc.py @@ -5,10 +5,10 @@ import random import collections import numpy as np +from functools import partial import torch.nn as nn import torch.nn.functional as F import torch.distributed as dist -from functools import partial def initialize_fn(m): @@ -27,10 +27,12 @@ def initialize_fn(m): nn.init.kaiming_uniform_(m.weight, mode="fan_out", nonlinearity="linear") nn.init.constant_(m.bias, 0) + def initialize(module): for m in module.modules(): initialize_fn(m) + def initialize_iterator(module_iterator): for m in module_iterator: initialize_fn(m) @@ -219,6 +221,7 @@ def make_divisible(v, divisor=8): new_v += divisor return new_v + def repeat_channels(conv_weights, new_channels, old_channels=3): """Repeat channels to match new number of input channels Args: @@ -228,5 +231,5 @@ def repeat_channels(conv_weights, new_channels, old_channels=3): """ rep_times = math.ceil(new_channels / old_channels) new_weights = conv_weights.repeat(1, rep_times, 1, 1)[:, :new_channels, :, :] - new_weights *= old_channels / new_channels # to keep the same output amplitude - return new_weights \ No newline at end of file + new_weights *= old_channels / new_channels # to keep the same output amplitude + return new_weights diff --git a/tests/detection_models/test_det_models.py b/tests/detection_models/test_det_models.py index 62ebb13..0ab1ff2 100644 --- a/tests/detection_models/test_det_models.py +++ b/tests/detection_models/test_det_models.py @@ -51,7 +51,7 @@ def test_coco_pretrain(arch): im = np.array(im.resize((inp_size, inp_size))) im_t = tensor_from_rgb_image(preprocess_fn(im)).unsqueeze(0).float().cuda() boxes_scores_classes = m.predict(im_t) - # check that most confident bbox is close to correct class. The reason for such strange test is + # check that most confident bbox is close to correct class. The reason for such strange test is # because in different models class mappings are shifted by +- 1 assert (boxes_scores_classes[0, 0, 5] - im_cls) < 2 @@ -61,6 +61,7 @@ def test_pretrain_custom_num_classes(arch): m = pt_det.__dict__[arch](pretrained="coco", num_classes=80).eval().cuda() _test_forward(m) + @pytest.mark.parametrize("arch", MODEL_NAMES[:2]) def test_encoder_frozenabn(arch): m = pt_det.__dict__[arch](encoder_norm_layer="frozenabn").eval().cuda() diff --git a/tests/losses/test_losses.py b/tests/losses/test_losses.py index cf3090e..2f7a741 100644 --- a/tests/losses/test_losses.py +++ b/tests/losses/test_losses.py @@ -52,9 +52,7 @@ def test_focal_loss_fn_basic(): @pytest.mark.parametrize("reduction", ["sum", "mean", "none"]) def test_focal_loss_fn_reduction(reduction): - torch_ce = F.binary_cross_entropy_with_logits( - INP_BINARY, TARGET_BINARY.float(), reduction=reduction - ) + torch_ce = F.binary_cross_entropy_with_logits(INP_BINARY, TARGET_BINARY.float(), reduction=reduction) my_ce = pt_F.focal_loss_with_logits(INP_BINARY, TARGET_BINARY, alpha=0.5, gamma=0, reduction=reduction) assert torch.allclose(torch_ce, my_ce * 2) @@ -108,6 +106,7 @@ def test_focal_loss(): fl_i = losses.FocalLoss(mode="binary", reduction="sum", ignore_label=-100)(INP_IMG_BINARY, y_true) assert torch.allclose(fl.sum() - loss_diff, fl_i) + @pytest.mark.parametrize( ["y_true", "y_pred", "expected"], [ @@ -333,9 +332,7 @@ def test_binary_cross_entropy(reduction): assert torch.allclose(torch_ce, my_ce) # test for images - torch_ce = F.binary_cross_entropy_with_logits( - INP_IMG_BINARY, TARGET_IMG_BINARY, reduction=reduction - ) + torch_ce = F.binary_cross_entropy_with_logits(INP_IMG_BINARY, TARGET_IMG_BINARY, reduction=reduction) my_ce = my_ce_loss(INP_IMG_BINARY, TARGET_IMG_BINARY) assert torch.allclose(torch_ce, my_ce) @@ -391,4 +388,4 @@ def test_binary_hinge(): @pytest.mark.parametrize("reduction", ["sum", "mean", "none"]) def test_smoothl1(reduction): loss_my = losses.SmoothL1Loss(delta=1, reduction=reduction)(INP, TARGET_MULTILABEL) - loss_torch = F.smooth_l1_loss(INP, TARGET_MULTILABEL, reduction=reduction) \ No newline at end of file + loss_torch = F.smooth_l1_loss(INP, TARGET_MULTILABEL, reduction=reduction) diff --git a/tests/models/test_models.py b/tests/models/test_models.py index d5fb396..0042a85 100644 --- a/tests/models/test_models.py +++ b/tests/models/test_models.py @@ -23,7 +23,14 @@ HRNET_NAMES = [name for name in ALL_MODEL_NAMES if "hrnet" in name] # test only part of the models -TEST_MODEL_NAMES = DENSENET_NAMES[:1] + EFFNET_NAMES[:1] + VGG_NAMES[:1] + RESNET_NAMES[:1] + TRESNET_NAMES[:1] + HRNET_NAMES[:1] +TEST_MODEL_NAMES = ( + DENSENET_NAMES[:1] + + EFFNET_NAMES[:1] + + VGG_NAMES[:1] + + RESNET_NAMES[:1] + + TRESNET_NAMES[:1] + + HRNET_NAMES[:1] +) # TEST_MODEL_NAMES = HRNET_NAMES[:1] INP = torch.ones(2, 3, 128, 128) @@ -52,6 +59,7 @@ def test_custom_in_channels(arch): with torch.no_grad(): m(torch.ones(2, 5, 128, 128)) + @pytest.mark.parametrize("arch", EFFNET_NAMES[:2] + RESNET_NAMES[:2]) def test_pretrained_custom_in_channels(arch): m = models.__dict__[arch](in_channels=5, pretrained="imagenet") @@ -82,11 +90,13 @@ def test_dilation(arch, output_stride): W, H = INP.shape[-2:] assert res.shape[-2:] == (W // output_stride, H // output_stride) + @pytest.mark.parametrize("arch", EFFNET_NAMES[:2] + RESNET_NAMES[:2]) def test_drop_connect(arch): m = models.__dict__[arch](drop_connect_rate=0.2) _test_forward(m) + NUM_PARAMS = { "tresnetm": 31389032, "tresnetl": 55989256, @@ -96,13 +106,16 @@ def test_drop_connect(arch): "efficientnet_b2": 9109994, "efficientnet_b3": 12233232, } -@pytest.mark.parametrize('name_num_params', zip(NUM_PARAMS.items())) + + +@pytest.mark.parametrize("name_num_params", zip(NUM_PARAMS.items())) def test_num_parameters(name_num_params): name, num_params = name_num_params[0] m = models.__dict__[name]() assert pt.utils.misc.count_parameters(m)[0] == num_params -@pytest.mark.parametrize('stem_type', ["", "deep", "space2depth"]) + +@pytest.mark.parametrize("stem_type", ["", "deep", "space2depth"]) def test_resnet_stem_type(stem_type): m = models.resnet50(stem_type=stem_type) - _test_forward(m) \ No newline at end of file + _test_forward(m) diff --git a/tests/models/test_weights.py b/tests/models/test_weights.py index c22c173..c68cd72 100644 --- a/tests/models/test_weights.py +++ b/tests/models/test_weights.py @@ -55,6 +55,7 @@ def test_imagenet_pretrain(arch): pred_cls = m(im).argmax() assert pred_cls == im_cls + # test that output mean for fixed input is the same MODEL_NAMES2 = [ "resnet34", @@ -68,6 +69,7 @@ def test_imagenet_pretrain(arch): "efficientnet_b0": 0.0070, } + @pytest.mark.parametrize("arch", MODEL_NAMES2) def test_output_mean(arch): m = models.__dict__[arch](pretrained="imagenet") diff --git a/tests/modules/test_modules.py b/tests/modules/test_modules.py index 969adc0..1be5837 100644 --- a/tests/modules/test_modules.py +++ b/tests/modules/test_modules.py @@ -13,12 +13,14 @@ def test_activations_init(activation): res = act(inp) assert res.mean() + def test_frozen_abn(): l = modules.bn_from_name("frozen_abn")(10) assert list(l.parameters()) == [] l = modules.ABN(10, frozen=True) assert list(l.parameters()) == [] + # need to test and resnet and vgg because in resnet there are no Convs with bias # and in VGG there are no Convs without bias @pytest.mark.parametrize("norm_layer", ["abn", "agn"]) diff --git a/tests/segmentation_models/test_segm_models.py b/tests/segmentation_models/test_segm_models.py index d454815..cb6378d 100644 --- a/tests/segmentation_models/test_segm_models.py +++ b/tests/segmentation_models/test_segm_models.py @@ -7,12 +7,13 @@ INP = torch.ones(2, 3, 64, 64) ENCODERS = ["resnet34", "se_resnet50", "efficientnet_b1", "densenet121"] -SEGM_ARCHS = [pt_sm.Unet, pt_sm.Linknet, pt_sm.DeepLabV3, pt_sm.SegmentationFPN, pt_sm.SegmentationBiFPN] +SEGM_ARCHS = [pt_sm.Unet, pt_sm.Linknet, pt_sm.DeepLabV3, pt_sm.SegmentationFPN] # pt_sm.SegmentationBiFPN # this lines are usefull for quick tests # ENCODERS = ["se_resnet50"] # SEGM_ARCHS = [pt_sm.SegmentationFPN, pt_sm.SegmentationFPN] + def _test_forward(model): with torch.no_grad(): return model(INP) @@ -47,21 +48,24 @@ def test_num_classes(encoder_name, model_class): out = _test_forward(m) assert out.size(1) == 5 + @pytest.mark.parametrize("encoder_name", ENCODERS) @pytest.mark.parametrize("model_class", SEGM_ARCHS) def test_drop_rate(encoder_name, model_class): m = model_class(encoder_name=encoder_name, drop_rate=0.2) _test_forward(m) + @pytest.mark.parametrize("encoder_name", ENCODERS) @pytest.mark.parametrize("model_class", [pt_sm.DeepLabV3]) # pt_sm.Unet, pt_sm.Linknet @pytest.mark.parametrize("output_stride", [32, 16, 8]) def test_dilation(encoder_name, model_class, output_stride): if output_stride == 8 and model_class != pt_sm.DeepLabV3: - return None # OS=8 only supported for Deeplab + return None # OS=8 only supported for Deeplab m = model_class(encoder_name=encoder_name, output_stride=output_stride) _test_forward(m) + @pytest.mark.parametrize("model_class", [pt_sm.DeepLabV3, pt_sm.SegmentationFPN]) def test_deeplab_last_upsample(model_class): m = model_class(last_upsample=True) @@ -74,7 +78,8 @@ def test_deeplab_last_upsample(model_class): # should be 4 times smaller assert tuple(out.shape[-2:]) == (W // 4, H // 4) + @pytest.mark.parametrize("merge_policy", ["add", "cat"]) def test_merge_policy(merge_policy): m = pt_sm.SegmentationFPN(merge_policy=merge_policy) - _test_forward(m) \ No newline at end of file + _test_forward(m) diff --git a/tests/utils/test_utils.py b/tests/utils/test_utils.py index 13b4f23..e3257f4 100644 --- a/tests/utils/test_utils.py +++ b/tests/utils/test_utils.py @@ -2,18 +2,23 @@ import pytest import pytorch_tools as pt + def random_boxes(mean_box, stdev, N): return torch.rand(N, 4) * stdev + torch.tensor(mean_box, dtype=torch.float) + +# fmt: off DEVICE_DTYPE = [ ("cpu", torch.float), ("cuda", torch.float), ("cuda", torch.half) ] +# fmt: on # check that it works for all combinations of dtype and device @pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) def test_clip_bboxes(device_dtype): device, dtype = device_dtype + # fmt: off bboxes = torch.tensor( [ [-5, -10, 50, 100], @@ -30,6 +35,7 @@ def test_clip_bboxes(device_dtype): device=device, dtype=dtype, ) + # fmt: on size = (60, 40) # test single bbox clip res1 = pt.utils.box.clip_bboxes(bboxes, size) @@ -55,9 +61,11 @@ def test_clip_bboxes(device_dtype): res5 = jit_clip(batch_bboxes.clone(), batch_sizes) assert torch.allclose(res5, batch_expected) + @pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) def test_delta2box(device_dtype): device, dtype = device_dtype + # fmt: off anchors = torch.tensor( [ [ 0., 0., 1., 1.], @@ -84,12 +92,13 @@ def test_delta2box(device_dtype): [0.0000, 0.0000, 1.0000, 1.0000], [0.1409, 0.1409, 2.8591, 2.8591], [-3.1945, 0.3161, 4.1945, 0.6839], - [5.0000, 5.0000, 5.0000, 5.0000] + [5.0000, 5.0000, 5.0000, 5.0000], ], device=device, dtype=dtype, ) - res1 = pt.utils.box.delta2box(deltas, anchors) + # fmt: on + res1 = pt.utils.box.delta2box(deltas, anchors) assert torch.allclose(res1, expected_res, atol=3e-4) BS = 4 @@ -97,8 +106,8 @@ def test_delta2box(device_dtype): batch_deltas = deltas.unsqueeze(0).expand(BS, -1, -1) batch_expected = expected_res.unsqueeze(0).expand(BS, -1, -1) - # test applying to batch - res2 = pt.utils.box.delta2box(batch_deltas.clone(), batch_anchors) + # test applying to batch + res2 = pt.utils.box.delta2box(batch_deltas.clone(), batch_anchors) assert torch.allclose(res2, batch_expected, atol=3e-4) # check that function is JIT script friendly @@ -106,6 +115,7 @@ def test_delta2box(device_dtype): res3 = jit_func(batch_deltas.clone(), batch_anchors) assert torch.allclose(res3, batch_expected, atol=3e-4) + @pytest.mark.parametrize("device_dtype", DEVICE_DTYPE) def test_box2delta(device_dtype): ## this test only checks that encoding and decoding gives the same result @@ -114,12 +124,12 @@ def test_box2delta(device_dtype): anchors = random_boxes([10, 10, 20, 20], 10, 10).to(device).to(dtype) deltas = pt.utils.box.box2delta(boxes, anchors) boxes_reconstructed = pt.utils.box.delta2box(deltas, anchors) - atol = 2e-2 if dtype == torch.half else 1e-6 # for fp16 sometimes error is large - assert torch.allclose(boxes, boxes_reconstructed, atol=atol) + atol = 2e-2 if dtype == torch.half else 1e-6 # for fp16 sometimes error is large + assert torch.allclose(boxes, boxes_reconstructed, atol=atol) # check that it's jit friendly jit_box2delta = torch.jit.script(pt.utils.box.box2delta) jit_delta2box = torch.jit.script(pt.utils.box.delta2box) deltas2 = jit_box2delta(boxes, anchors) boxes_reconstructed2 = jit_delta2box(deltas2, anchors) - assert torch.allclose(boxes, boxes_reconstructed2, atol=atol) \ No newline at end of file + assert torch.allclose(boxes, boxes_reconstructed2, atol=atol)