<jupyter_start><jupyter_text>Lab 5: Mask R-CNNMask R-CNN was originally released under the Caffe2 framework by the FAIR team as part of a project called "Detectron."After Caffe2 and PyTorch merged, eventually, FAIR released a ground-up reimplementation of Detectron with new models andfeatures. This framework is [Detectron2](https://github.com/facebookresearch/detectron2). If you're looking for a completeapproach to bounding box regression, mask regression, and skeleton point (pose) estimation, take a look.In the meantime, however, other groups quickly implemented Mask R-CNN directly in TensorFlow and Keras.Today we'll work with a [PyTorch implementation of Mask R-CNN](https://github.com/multimodallearning/pytorch-mask-rcnn) derivedfrom those efforts.The full of Mask R-CNN structure is below: For each intance of an object in an image, Mask R-CNN attempts to generate - A bounding box - A segmentation maskThe backbone and neck of Mask R-CNN are based on - A feature pyramid networks (FPN) - ResNet 101 Feature Pyramid NetworksWe've seen the feature pyramid network (FPN) in YOLOv3 and YOLOv4. It is a feature extractor using a pyramid concept.We begin with ordinary progressive downsampling of the input to get a multiscale representation of the input, but ratherthan using that "low-level" multiscale representation directly, we progressively upsample the coarse representation ofthe input using input from the low-level feature maps. The idea is shown in the left-hand panel of the diagram above.By incorporating information from both the low-level "bottom-up" fine grained feature map and the upsampled coarser grained featuremap in the pyramid, the fine grained representation at the bottom of the pyramid contain much more useful or more "semantic"information about the input.Feature pyramids can, in principle, come in many different forms. Refer to the figure below, which isfrom [Towards Data Science](https://towardsdatascience.com/review-fpn-feature-pyramid-network-object-detection-262fc7482610): The classical image pyramid, used by many techniques aiming at multiscale detection, looks like (a).Classifiers we built early on in the class, such as AlexNet, look like (b). (c) and (d) utilize multiplefeature maps derived from the input through progressive downscaling. The difference in the FPN (d) isthe inclusion of both bottom up and top down pathways: We know that as we process the input in multiple progressively downsampled layers, we are increasingly analyzing higher-level featureswith larger receptive fields with some invariance to imaging conditions (translation, scale, lighting, etc.).The top-down representations use progressively *upsampled* layers in which we are increasingly analyzing the input at high resolution butwith all the benefits of the downsampled representation.The main risk of the top-down upsampling is that we would lose information about the details of the original input in constructing thefine-grained feature maps. For that reason, we add lateral connections to the bottom-up feature maps of the same size:  ResNet backboneThe bottom-up part of the FPN used in Mask-RCNN is ResNet. It is used similar to howDarknet-53 is used in YOLO. We take the classifier structure as the bottom-up half ofthe pyramid, then we add the top down part to obtain the FPN.Mask R-CNN taps into ResNet in 4 or 5 places according to the implementation, at the ouptut of various residual blocks.Here's a figure to explain this, this time from [Jonathan Hui's Medium site](https://jonathan-hui.medium.com/understanding-feature-pyramid-networks-for-object-detection-fpn-45b227b9106c):(There would be a P6 there as well if we're extracting a 5-scale pyramid).  Region Proposal Network (RPN)The Faster R-CNN RPN connects to the top of the pyramid. It performs classification and bounding box regression for each possible proposal. Detection networkThe detection network uses the results of the RPN as well as the output of the FPN. With the RPN bounding box as input, we assign the box to one of the levels of the pyramid.Specifically, we use$$ k = \left\lfloor k_0 +\log_2\left( \frac{\sqrt{wh}}{224} \right) \right\rfloor $$Then the ROIAlign block interpolates the appropriate set of features from the best level (level $k$) of the pyramid. The region is aligned and scaled to a size of56$\times$56, and the resulting representation is forwarded to the mask prediction head. Mask HeadThe mask head is a FCN that up-samples from the detection result, and the patch size is finally re-scaled back to the input size.  CodingSo let's start investigating how Mask R-CNN works in detail.First, clone the Github respository:<jupyter_code>!git clone https://github.com/multimodallearning/pytorch-mask-rcnn.git<jupyter_output>Cloning into 'pytorch-mask-rcnn'...
remote: Enumerating objects: 91, done.[K
remote: Total 91 (delta 0), reused 0 (delta 0), pack-reused 91[K
Unpacking objects: 100% (91/91), 8.75 MiB | 1.03 MiB/s, done.<jupyter_text>MaskRCNN class (model.py)First, let's cisit the MaskRCNN class and its init function:The most important action here is calling `build()`, which creates the network based on aconfiguration object. `set_log_dir()` just sets up saving to a log file, and `initialize_weights()` loadsweights. Let's take a look at the `build()` function:<jupyter_code>def build(self, config):
    """Build Mask R-CNN architecture.
    """

    # Image size must be dividable by 2 multiple times
    h, w = config.IMAGE_SHAPE[:2]
    if h / 2**6 != int(h / 2**6) or w / 2**6 != int(w / 2**6):
        raise Exception("Image size must be dividable by 2 at least 6 times "
                        "to avoid fractions when downscaling and upscaling."
                        "For example, use 256, 320, 384, 448, 512, ... etc. ")

    # Build the shared convolutional layers.
    # Bottom-up Layers
    # Returns a list of the last layers of each stage, 5 in total.
    # Don't create the thead (stage 5), so we pick the 4th item in the list.
    resnet = ResNet("resnet101", stage5=True)
    C1, C2, C3, C4, C5 = resnet.stages()

    # Top-down Layers
    # TODO: add assert to varify feature map sizes match what's in config
    self.fpn = FPN(C1, C2, C3, C4, C5, out_channels=256)

    # Generate Anchors
    self.anchors = Variable(torch.from_numpy(utils.generate_pyramid_anchors(config.RPN_ANCHOR_SCALES,
                                                                            config.RPN_ANCHOR_RATIOS,
                                                                            config.BACKBONE_SHAPES,
                                                                            config.BACKBONE_STRIDES,
                                                                            config.RPN_ANCHOR_STRIDE)).float(), requires_grad=False)
    if self.config.GPU_COUNT:
        self.anchors = self.anchors.cuda()

    # RPN
    self.rpn = RPN(len(config.RPN_ANCHOR_RATIOS), config.RPN_ANCHOR_STRIDE, 256)

    # FPN Classifier
    self.classifier = Classifier(256, config.POOL_SIZE, config.IMAGE_SHAPE, config.NUM_CLASSES)

    # FPN Mask
    self.mask = Mask(256, config.MASK_POOL_SIZE, config.IMAGE_SHAPE, config.NUM_CLASSES)

    # Fix batch norm layers
    def set_bn_fix(m):
        classname = m.__class__.__name__
        if classname.find('BatchNorm') != -1:
            for p in m.parameters(): p.requires_grad = False

    self.apply(set_bn_fix)<jupyter_output><empty_output><jupyter_text>BackboneThe backbone is initialized then tapped into in the lines    resnet = ResNet("resnet101", stage5=True)    C1, C2, C3, C4, C5 = resnet.stages()We see that this version is using ResNet101 and extracting a 5-stages pyramid. How to find out what stages are being used? Takea look at the ResNet class itself and take a look at its `__init__` and forward methods. We see that the C1-C5 are the mainblocks of the network. The first (C1) is a single 7$\times$7 convolution with batch norm, ReLU, and a MaxPool operation. The others(C2-C5) are ResNet residual blocks.<jupyter_code>class ResNet(nn.Module):

    def __init__(self, architecture, stage5=False):
        super(ResNet, self).__init__()
        assert architecture in ["resnet50", "resnet101"]
        self.inplanes = 64
        self.layers = [3, 4, {"resnet50": 6, "resnet101": 23}[architecture], 3]
        self.block = Bottleneck
        self.stage5 = stage5

        self.C1 = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
            nn.BatchNorm2d(64, eps=0.001, momentum=0.01),
            nn.ReLU(inplace=True),
            SamePad2d(kernel_size=3, stride=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.C2 = self.make_layer(self.block, 64, self.layers[0])
        self.C3 = self.make_layer(self.block, 128, self.layers[1], stride=2)
        self.C4 = self.make_layer(self.block, 256, self.layers[2], stride=2)
        if self.stage5:
            self.C5 = self.make_layer(self.block, 512, self.layers[3], stride=2)
        else:
            self.C5 = None

    def forward(self, x):
        x = self.C1(x)
        x = self.C2(x)
        x = self.C3(x)
        x = self.C4(x)
        x = self.C5(x)
        return x

    def stages(self):
        return [self.C1, self.C2, self.C3, self.C4, self.C5]

    def make_layer(self, block, planes, blocks, stride=1):
        downsample = None
        if stride != 1 or self.inplanes != planes * block.expansion:
            downsample = nn.Sequential(
                nn.Conv2d(self.inplanes, planes * block.expansion,
                          kernel_size=1, stride=stride),
                nn.BatchNorm2d(planes * block.expansion, eps=0.001, momentum=0.01),
            )

        layers = []
        layers.append(block(self.inplanes, planes, stride, downsample))
        self.inplanes = planes * block.expansion
        for i in range(1, blocks):
            layers.append(block(self.inplanes, planes))

        return nn.Sequential(*layers)<jupyter_output><empty_output><jupyter_text>FPNLet's go back to the MaskRCNN class. The next stage is FPN (Top-down layers)    self.fpn = FPN(C1, C2, C3, C4, C5, out_channels=256)The main idea of the FPN is in its `forward()` method. Let's take a look.<jupyter_code>class FPN(nn.Module):
    def __init__(self, C1, C2, C3, C4, C5, out_channels):
        super(FPN, self).__init__()
        self.out_channels = out_channels
        self.C1 = C1
        self.C2 = C2
        self.C3 = C3
        self.C4 = C4
        self.C5 = C5
        self.P6 = nn.MaxPool2d(kernel_size=1, stride=2)
        self.P5_conv1 = nn.Conv2d(2048, self.out_channels, kernel_size=1, stride=1)
        self.P5_conv2 = nn.Sequential(
            SamePad2d(kernel_size=3, stride=1),
            nn.Conv2d(self.out_channels, self.out_channels, kernel_size=3, stride=1),
        )
        self.P4_conv1 =  nn.Conv2d(1024, self.out_channels, kernel_size=1, stride=1)
        self.P4_conv2 = nn.Sequential(
            SamePad2d(kernel_size=3, stride=1),
            nn.Conv2d(self.out_channels, self.out_channels, kernel_size=3, stride=1),
        )
        self.P3_conv1 = nn.Conv2d(512, self.out_channels, kernel_size=1, stride=1)
        self.P3_conv2 = nn.Sequential(
            SamePad2d(kernel_size=3, stride=1),
            nn.Conv2d(self.out_channels, self.out_channels, kernel_size=3, stride=1),
        )
        self.P2_conv1 = nn.Conv2d(256, self.out_channels, kernel_size=1, stride=1)
        self.P2_conv2 = nn.Sequential(
            SamePad2d(kernel_size=3, stride=1),
            nn.Conv2d(self.out_channels, self.out_channels, kernel_size=3, stride=1),
        )

    def forward(self, x):
        x = self.C1(x)
        x = self.C2(x)
        c2_out = x          # keep C2 output
        x = self.C3(x)
        c3_out = x          # keep C3 output
        x = self.C4(x)
        c4_out = x          # keep C4 output
        x = self.C5(x)
        p5_out = self.P5_conv1(x)       # top-most of pyramid
        p4_out = self.P4_conv1(c4_out) + F.upsample(p5_out, scale_factor=2)         # lateral connections, 2nd top output
        p3_out = self.P3_conv1(c3_out) + F.upsample(p4_out, scale_factor=2)         # lateral connections, 3rd top output
        p2_out = self.P2_conv1(c2_out) + F.upsample(p3_out, scale_factor=2)         # lateral connections, 4th top output

        p5_out = self.P5_conv2(p5_out)
        p4_out = self.P4_conv2(p4_out)
        p3_out = self.P3_conv2(p3_out)
        p2_out = self.P2_conv2(p2_out)

        # P6 is used for the 5th anchor scale in RPN. Generated by
        # subsampling from P5 with stride of 2.
        p6_out = self.P6(p5_out)        # max pooling for RPN

        return [p2_out, p3_out, p4_out, p5_out, p6_out]<jupyter_output><empty_output><jupyter_text>AnchorsAnchor box sizes are set in the configuration file.  RPNNext, let's see how the RPN is produced.    self.rpn = RPN(len(config.RPN_ANCHOR_RATIOS), config.RPN_ANCHOR_STRIDE, 256)The RPN class is reproduced below. But also take a look at its output. It releases classes (score, and softmax), and bounding boxes.<jupyter_code>class RPN(nn.Module):
    """Builds the model of Region Proposal Network.

    anchors_per_location: number of anchors per pixel in the feature map
    anchor_stride: Controls the density of anchors. Typically 1 (anchors for
                   every pixel in the feature map), or 2 (every other pixel).

    Returns:
        rpn_logits: [batch, H, W, 2] Anchor classifier logits (before softmax)
        rpn_probs: [batch, W, W, 2] Anchor classifier probabilities.
        rpn_bbox: [batch, H, W, (dy, dx, log(dh), log(dw))] Deltas to be
                  applied to anchors.
    """

    def __init__(self, anchors_per_location, anchor_stride, depth):
        super(RPN, self).__init__()
        self.anchors_per_location = anchors_per_location
        self.anchor_stride = anchor_stride
        self.depth = depth

        self.padding = SamePad2d(kernel_size=3, stride=self.anchor_stride)
        self.conv_shared = nn.Conv2d(self.depth, 512, kernel_size=3, stride=self.anchor_stride)
        self.relu = nn.ReLU(inplace=True)
        self.conv_class = nn.Conv2d(512, 2 * anchors_per_location, kernel_size=1, stride=1)     # class, score
        self.softmax = nn.Softmax(dim=2)
        self.conv_bbox = nn.Conv2d(512, 4 * anchors_per_location, kernel_size=1, stride=1)      # x,y,w,h

    def forward(self, x):
        # Shared convolutional base of the RPN
        x = self.relu(self.conv_shared(self.padding(x)))

        # Anchor Score. [batch, anchors per location * 2, height, width].
        rpn_class_logits = self.conv_class(x)

        # Reshape to [batch, 2, anchors]
        rpn_class_logits = rpn_class_logits.permute(0,2,3,1)
        rpn_class_logits = rpn_class_logits.contiguous()
        rpn_class_logits = rpn_class_logits.view(x.size()[0], -1, 2)

        # Softmax on last dimension of BG/FG.
        rpn_probs = self.softmax(rpn_class_logits)              # output class

        # Bounding box refinement. [batch, H, W, anchors per location, depth]
        # where depth is [x, y, log(w), log(h)]
        rpn_bbox = self.conv_bbox(x)

        # Reshape to [batch, 4, anchors]
        rpn_bbox = rpn_bbox.permute(0,2,3,1)
        rpn_bbox = rpn_bbox.contiguous()
        rpn_bbox = rpn_bbox.view(x.size()[0], -1, 4)

        return [rpn_class_logits, rpn_probs, rpn_bbox]<jupyter_output><empty_output><jupyter_text>Proposal classifierThe Faster R-CNN head contains the region proposal classifier.    self.classifier = Classifier(256, config.POOL_SIZE, config.IMAGE_SHAPE, config.NUM_CLASSES)The classifier is mainly composed of convolutional layers. The mostinteresting process is the pyramidal ROI alignment at multiple scales.At the Classifier class, the combine ROIs aligned is in function pyramid_roi_align.Let's take a look the `pyramid_roi_align()` method, which contains the cropping and resizing of incoming feature maps according to region proposals.<jupyter_code>def pyramid_roi_align(inputs, pool_size, image_shape):
    """Implements ROI Pooling on multiple levels of the feature pyramid.

    Params:
    - pool_size: [height, width] of the output pooled regions. Usually [7, 7]
    - image_shape: [height, width, channels]. Shape of input image in pixels

    Inputs:
    - boxes: [batch, num_boxes, (y1, x1, y2, x2)] in normalized
             coordinates.
    - Feature maps: List of feature maps from different levels of the pyramid.
                    Each is [batch, channels, height, width]

    Output:
    Pooled regions in the shape: [num_boxes, height, width, channels].
    The width and height are those specific in the pool_shape in the layer
    constructor.
    """

    # Currently only supports batchsize 1
    for i in range(len(inputs)):
        inputs[i] = inputs[i].squeeze(0)

    # Crop boxes [batch, num_boxes, (y1, x1, y2, x2)] in normalized coords
    boxes = inputs[0]

    # Feature Maps. List of feature maps from different level of the
    # feature pyramid. Each is [batch, height, width, channels]
    feature_maps = inputs[1:]

    # Assign each ROI to a level in the pyramid based on the ROI area.
    y1, x1, y2, x2 = boxes.chunk(4, dim=1)
    h = y2 - y1
    w = x2 - x1

    # Equation 1 in the Feature Pyramid Networks paper. Account for
    # the fact that our coordinates are normalized here.
    # e.g. a 224x224 ROI (in pixels) maps to P4
    image_area = Variable(torch.FloatTensor([float(image_shape[0]*image_shape[1])]), requires_grad=False)
    if boxes.is_cuda:
        image_area = image_area.cuda()
    roi_level = 4 + log2(torch.sqrt(h*w)/(224.0/torch.sqrt(image_area)))
    roi_level = roi_level.round().int()
    roi_level = roi_level.clamp(2,5)


    # Loop through levels and apply ROI pooling to each. P2 to P5.
    pooled = []
    box_to_level = []
    for i, level in enumerate(range(2, 6)):
        ix  = roi_level==level
        if not ix.any():
            continue
        ix = torch.nonzero(ix)[:,0]
        level_boxes = boxes[ix.data, :]

        # Keep track of which box is mapped to which level
        box_to_level.append(ix.data)

        # Stop gradient propogation to ROI proposals
        level_boxes = level_boxes.detach()

        # Crop and Resize
        # From Mask R-CNN paper: "We sample four regular locations, so
        # that we can evaluate either max or average pooling. In fact,
        # interpolating only a single value at each bin center (without
        # pooling) is nearly as effective."
        #
        # Here we use the simplified approach of a single value per bin,
        # which is how it's done in tf.crop_and_resize()
        # Result: [batch * num_boxes, pool_height, pool_width, channels]
        ind = Variable(torch.zeros(level_boxes.size()[0]),requires_grad=False).int()
        if level_boxes.is_cuda:
            ind = ind.cuda()
        feature_maps[i] = feature_maps[i].unsqueeze(0)  #CropAndResizeFunction needs batch dimension
        pooled_features = CropAndResizeFunction(pool_size, pool_size, 0)(feature_maps[i], level_boxes, ind)
        pooled.append(pooled_features)

    # Pack pooled features into one tensor
    pooled = torch.cat(pooled, dim=0)

    # Pack box_to_level mapping into one array and add another
    # column representing the order of pooled boxes
    box_to_level = torch.cat(box_to_level, dim=0)

    # Rearrange pooled features to match the order of the original boxes
    _, box_to_level = torch.sort(box_to_level)
    pooled = pooled[box_to_level, :, :]

    return pooled<jupyter_output><empty_output><jupyter_text>Mask HeadThe last step creates the mask head. We have convolutions and upsampling to the original image size.The difference between the classifier head and the mask head is that the classifier head takes the proposal bounding boxesand outputs final bounding boxes whereas the mask head uses the final bounding boxes to create masks.The mask creation in the MaskRCNN class is here:    self.mask = Mask(256, config.MASK_POOL_SIZE, config.IMAGE_SHAPE, config.NUM_CLASSES)  Prediction functionNow let's look at the overall`predict()` method. There are two modes there: inference (evaluate) mode and training mode.Both modes have the same steps, but during training, we have to calculate ROI sizes for comparisons between the predicted output and the ground truth.<jupyter_code>def predict(self, input, mode):
        molded_images = input[0]
        image_metas = input[1]

        if mode == 'inference':
            self.eval()
        elif mode == 'training':
            self.train()

            # Set batchnorm always in eval mode during training
            def set_bn_eval(m):
                classname = m.__class__.__name__
                if classname.find('BatchNorm') != -1:
                    m.eval()

            self.apply(set_bn_eval)

        # Feature extraction
        [p2_out, p3_out, p4_out, p5_out, p6_out] = self.fpn(molded_images)

        # Note that P6 is used in RPN, but not in the classifier heads.
        rpn_feature_maps = [p2_out, p3_out, p4_out, p5_out, p6_out]
        mrcnn_feature_maps = [p2_out, p3_out, p4_out, p5_out]

        # Loop through pyramid layers
        layer_outputs = []  # list of lists
        for p in rpn_feature_maps:
            layer_outputs.append(self.rpn(p))

        # Concatenate layer outputs
        # Convert from list of lists of level outputs to list of lists
        # of outputs across levels.
        # e.g. [[a1, b1, c1], [a2, b2, c2]] => [[a1, a2], [b1, b2], [c1, c2]]
        outputs = list(zip(*layer_outputs))
        outputs = [torch.cat(list(o), dim=1) for o in outputs]
        rpn_class_logits, rpn_class, rpn_bbox = outputs

        # Generate proposals
        # Proposals are [batch, N, (y1, x1, y2, x2)] in normalized coordinates
        # and zero padded.
        proposal_count = self.config.POST_NMS_ROIS_TRAINING if mode == "training" \
            else self.config.POST_NMS_ROIS_INFERENCE
        rpn_rois = proposal_layer([rpn_class, rpn_bbox],
                                 proposal_count=proposal_count,
                                 nms_threshold=self.config.RPN_NMS_THRESHOLD,
                                 anchors=self.anchors,
                                 config=self.config)

        if mode == 'inference':
            # Network Heads
            # Proposal classifier and BBox regressor heads
            mrcnn_class_logits, mrcnn_class, mrcnn_bbox = self.classifier(mrcnn_feature_maps, rpn_rois)

            # Detections
            # output is [batch, num_detections, (y1, x1, y2, x2, class_id, score)] in image coordinates
            detections = detection_layer(self.config, rpn_rois, mrcnn_class, mrcnn_bbox, image_metas)

            # Convert boxes to normalized coordinates
            # TODO: let DetectionLayer return normalized coordinates to avoid
            #       unnecessary conversions
            h, w = self.config.IMAGE_SHAPE[:2]
            scale = Variable(torch.from_numpy(np.array([h, w, h, w])).float(), requires_grad=False)
            if self.config.GPU_COUNT:
                scale = scale.cuda()
            detection_boxes = detections[:, :4] / scale

            # Add back batch dimension
            detection_boxes = detection_boxes.unsqueeze(0)

            # Create masks for detections
            mrcnn_mask = self.mask(mrcnn_feature_maps, detection_boxes)

            # Add back batch dimension
            detections = detections.unsqueeze(0)
            mrcnn_mask = mrcnn_mask.unsqueeze(0)

            return [detections, mrcnn_mask]

        elif mode == 'training':

            gt_class_ids = input[2]
            gt_boxes = input[3]
            gt_masks = input[4]

            # Normalize coordinates
            h, w = self.config.IMAGE_SHAPE[:2]
            scale = Variable(torch.from_numpy(np.array([h, w, h, w])).float(), requires_grad=False)
            if self.config.GPU_COUNT:
                scale = scale.cuda()
            gt_boxes = gt_boxes / scale

            # Generate detection targets
            # Subsamples proposals and generates target outputs for training
            # Note that proposal class IDs, gt_boxes, and gt_masks are zero
            # padded. Equally, returned rois and targets are zero padded.
            rois, target_class_ids, target_deltas, target_mask = \
                detection_target_layer(rpn_rois, gt_class_ids, gt_boxes, gt_masks, self.config)

            if not rois.size():
                mrcnn_class_logits = Variable(torch.FloatTensor())
                mrcnn_class = Variable(torch.IntTensor())
                mrcnn_bbox = Variable(torch.FloatTensor())
                mrcnn_mask = Variable(torch.FloatTensor())
                if self.config.GPU_COUNT:
                    mrcnn_class_logits = mrcnn_class_logits.cuda()
                    mrcnn_class = mrcnn_class.cuda()
                    mrcnn_bbox = mrcnn_bbox.cuda()
                    mrcnn_mask = mrcnn_mask.cuda()
            else:
                # Network Heads
                # Proposal classifier and BBox regressor heads
                mrcnn_class_logits, mrcnn_class, mrcnn_bbox = self.classifier(mrcnn_feature_maps, rois)

                # Create masks for detections
                mrcnn_mask = self.mask(mrcnn_feature_maps, rois)

            return [rpn_class_logits, rpn_bbox, target_class_ids, mrcnn_class_logits, target_deltas, mrcnn_bbox, target_mask, mrcnn_mask]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08678593848085374, 'fraction_numerical': 0.021304143126177025, 'mean_word_length': 2.7677753141167774, 'pattern_counts': {'":': 2, '<': 28, '<?xml version=': 0, '>': 29, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 19, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '630993', 'license': 'CC0-1.0', 'n_tokens_mistral': 8261, 'n_tokens_neox': 7746, 'n_words': 2478, 'path': 'Labs/06-Mask-R-CNN/05-Mask-R-CNN.ipynb', 'repo_name': 'beaupranisaa/RTML', 'size': '25488'}