Winning Kaggle the Reproducible Way

Interactive chapter

This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.

Chapter 46: Segmentation Architectures

Try the decision first

Which threshold and post-processing rule is best depends on how Dice is averaged: how large is that effect, and when does it appear?

Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.

Your answers stay on this page until you download them. They are not sent to a server.

Open the chapter activity

Worked notebook and saved results

Which threshold and post-processing rule is best depends on how Dice is averaged: how large is that effect, and when does it appear?

The chapter asks to specify the Dice reduction and the empty-target behaviour before reporting a score. In a competition with many empty images that choice decides which threshold wins, so a model compared under the wrong reduction is ranked under the wrong rule.

Constructed data and a generated probability map; the sizes of these effects are properties of this generator, not a competition result.

The experiment

Constructed 32 x 32 images with one disc each (radius 2 to 8 pixels), where the control sets the share of images with no disc. A stand-in for a trained network supplies the probability map: the blurred mask plus spatially smooth noise, so small objects are weak and empty images carry speckle. Three rules are chosen on 150 development images: the threshold that maximises pooled Dice, the threshold that maximises mean per-image Dice (an empty prediction on an empty image scores 1), and a threshold plus a minimum object size (0, 5, 10 or 20 pixels, via connected components) for per-image Dice. Each is scored on 300 held-out images with both reductions, averaged over 10 draws.

Calculation code
import numpy as np
from scipy import ndimage

from kaggle_companion.activities._common import clean

SEED = 46
DRAWS = 10                 # independent image sets; every estimate is a mean over draws
SIZE = 32                  # images are 32 x 32 pixels
N_DEV, N_HELD = 150, 300   # development images (choose the rule) and held-out images (score it)
THRESHOLDS = np.linspace(0.2, 0.9, 15)
AREAS = (0, 5, 10, 20)     # minimum object sizes in pixels tried by the post-processing rule


def generate(n, empty_share, rng):
    """Masks with one disc each (radius 2 to 8 px; a share of the images is empty) and a stand-in model's probability map.
    The map is the blurred mask plus spatially smooth noise, so it misses small objects and speckles over empty images."""
    yy, xx = np.mgrid[:SIZE, :SIZE]
    masks = np.zeros((n, SIZE, SIZE), bool)
    for i in np.where(rng.random(n) >= empty_share)[0]:
        radius = rng.choice([2, 3, 4, 6, 8], p=[0.25, 0.25, 0.2, 0.15, 0.15])
        cy, cx = rng.integers(radius + 1, SIZE - radius - 1, 2)
        masks[i] = (yy - cy) ** 2 + (xx - cx) ** 2 <= radius ** 2
    blur = ndimage.gaussian_filter(masks.astype(float), (0, 1.2, 1.2))
    field = ndimage.gaussian_filter(rng.normal(size=masks.shape), (0, 1.5, 1.5))
    field /= field.std()
    return masks, 1 / (1 + np.exp(-(9 * (blur - 0.35) + 1.44 * field - 1.0)))


def pooled_dice(pred, target):
    """Intersection and support summed over the whole set, then one ratio."""
    return (2 * (pred & target).sum() + 1e-6) / (pred.sum() + target.sum() + 1e-6)


def image_dice(pred, target):
    """One ratio per image, averaged later. An empty target with an empty prediction scores 1 (the rule stated in the text)."""
    inter = (pred & target).sum((1, 2))
    total = pred.sum((1, 2)) + target.sum((1, 2))
    return np.where(total == 0, 1.0, 2 * inter / np.maximum(total, 1))


def remove_small(pred, area):
    """Delete connected components smaller than `area` pixels."""
    if area == 0:
        return pred
    out = pred.copy()
    for i in np.where(pred.any((1, 2)))[0]:
        labels, count = ndimage.label(pred[i])
        for k, size in enumerate(ndimage.sum(pred[i], labels, range(1, count + 1)), 1):
            if size < area:
                out[i][labels == k] = False
    return out


def run(empty_share):
    rng = np.random.default_rng(SEED)
    keys = ("pooled_rule", "image_rule", "area_rule")
    rows = {k: {"pooled": [], "image": []} for k in keys}
    chosen = {"pooled_rule": [], "image_rule": [], "area_rule": [], "area": []}
    curve_pooled, curve_image = [], []
    for _ in range(DRAWS):
        dev_mask, dev_prob = generate(N_DEV, empty_share, rng)
        held_mask, held_prob = generate(N_HELD, empty_share, rng)
        # Rule 1: the threshold that maximises pooled Dice on the development images.
        t_pooled = THRESHOLDS[np.argmax([pooled_dice(dev_prob > t, dev_mask) for t in THRESHOLDS])]
        # Rule 2: the threshold that maximises mean per-image Dice (empty rule included).
        t_image = THRESHOLDS[np.argmax([image_dice(dev_prob > t, dev_mask).mean() for t in THRESHOLDS])]
        # Rule 3: threshold and minimum object size that maximise mean per-image Dice.
        best = max(((image_dice(remove_small(dev_prob > t, a), dev_mask).mean(), t, a) for a in AREAS for t in THRESHOLDS[::2]),
                   key=lambda s: s[0])
        preds = {"pooled_rule": held_prob > t_pooled, "image_rule": held_prob > t_image,
                 "area_rule": remove_small(held_prob > best[1], best[2])}
        for k, pred in preds.items():
            rows[k]["pooled"].append(pooled_dice(pred, held_mask))
            rows[k]["image"].append(image_dice(pred, held_mask).mean())
        chosen["pooled_rule"].append(t_pooled)
        chosen["image_rule"].append(t_image)
        chosen["area_rule"].append(best[1])
        chosen["area"].append(best[2])
        curve_pooled.append([pooled_dice(held_prob > t, held_mask) for t in THRESHOLDS])
        curve_image.append([image_dice(held_prob > t, held_mask).mean() for t in THRESHOLDS])
    gain = np.array(rows["area_rule"]["image"]) - np.array(rows["pooled_rule"]["image"])
    return clean({
        "empty_share": empty_share,
        "rules": {k: {"pooled": np.mean(v["pooled"]), "image": np.mean(v["image"])} for k, v in rows.items()},
        "threshold": {k: np.mean(chosen[k]) for k in keys}, "min_area": np.mean(chosen["area"]),
        "gain_image": gain.mean(), "gain_se": gain.std(ddof=1) / np.sqrt(DRAWS), "win_rate": float((gain > 0).mean()),
        "thresholds": THRESHOLDS, "curve_pooled": np.mean(curve_pooled, 0), "curve_image": np.mean(curve_image, 0),
        "draws": DRAWS, "dev_images": N_DEV, "held_images": N_HELD,
    })

Measure it across the control

The control is share of images with an empty mask. Each value below runs the whole experiment from the generator up.

Calculation code
import pandas as pd
from kaggle_companion.activities.ch46 import explain

VALUES = [0, 0.25, 0.5, 0.75]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                                                0            0.25             0.5            0.75
Pooled rule, per-image Dice                 0.817           0.744           0.723           0.728
Pooled rule, pooled Dice                    0.905           0.901           0.894           0.876
Per-image rule, per-image Dice              0.822           0.765           0.801           0.866
Minimum-size rule, per-image Dice           0.831           0.833           0.872           0.922
Minimum-size rule, pooled Dice              0.909           0.908           0.903           0.899
Gain over pooled rule (SE)         +0.013 (0.002)  +0.090 (0.006)  +0.149 (0.009)  +0.194 (0.018)

Look at it

The figure shows the default setting, share of images with an empty mask = 0.5.

Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch46 import draw, NCOLS, HEIGHT

DEFAULT = 0.5
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()
Saved figure from this chapter calculation

What the result says

With 50% of images empty, the threshold tuned for pooled Dice (0.630) scores pooled Dice 0.894 but mean per-image Dice 0.723. Tuning for per-image Dice moves the threshold to 0.825 and gives 0.801 per image and 0.855 pooled. Adding a minimum object size (mean 7.0 pixels) reaches 0.872 per image: 0.872 - 0.723 = 0.149 over the pooled-Dice rule (standard error 0.009, better in 100% of draws).

Apply this to your competition

Book location: Chapter 46, Segmentation Losses.

Technical corrections

Apply a lesson from this chapter

Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.

Track pseudo-label sources separately in segmentation

Conditions

  • Segmentation training uses multiple pseudo-label sources.

Action

  • Track teacher/source identity for each pseudo-labeled slide and compare source-specific training variants.

Check

  • Evaluate on clean patient-held-out labels and audit teacher training provenance.

Limits

  • The proposed ensemble-diversity explanation is speculative; source does not establish hand-labeling superiority.
  • Nonempty deep-supervision loss is a separate training choice.

Find source links and value grades in the lesson atlas

Separate face localization from identity classification

Conditions

  • an image contains multiple faces requiring separate identity predictions

Action

  • Proposed adaptation of source observation: Detect individual face regions and classify each detected region with a separate recognition model.

Check

  • Proposed verification: Measure end-to-end recall as well as classification accuracy on correctly localized regions.

Limits

  • The first-place claim is for classifier Part1; timing of the winner-owned full detector/classifier continuation is unspecified and it is not established as the competition submission. Detector failures propagate; no measured latency or isolated pipeline gain is supplied.

Find source links and value grades in the lesson atlas

Handle 180-degree yaw ambiguity before box averaging

Conditions

  • 3D box orientations are equivalent modulo180degrees

Action

  • Proposed recipe: compare circular averaging of doubled yaw angles with naive angle averaging.

Check

  • Proposed check: Test circular doubled-angle averaging at175/5degrees and near wraparound, halve the final angle, and compare box IoUwith naive averaging.

Limits

  • Applicable to orientation modulo180degrees only; directed headings require360degree treatment.

Find source links and value grades in the lesson atlas

Iterate detector pseudo-label refinement

Conditions

  • a detector ensemble can supply labels for extra unlabeled images

Action

  • Proposed conditional application: Fine-tune from the base checkpoint on teacher-ensemble labels, then repeat using the first pseudo-label model outputs.

Check

  • Proposed check: Generate leakage-safe teacher boxes, compare one versus two rounds at equal training budget, and measure quality on untouched source-held-out images.

Limits

  • Hidden-test pseudo-labeling is competition transduction; deployment reuse requires eligible unlabeled images and an untouched labeled holdout.

Find source links and value grades in the lesson atlas

Undo every 3D test-time transform before fusing boxes

Conditions

  • point clouds are transformed for test-time augmentation

Action

  • Proposed recipe: map predicted box centers and yaw back to the original coordinates before fusion.

Check

  • Proposed check: Round-trip synthetic boxes through each transform and inverse, including flips, scaling and wraparound, before comparing fused predictions.

Limits

  • Box conventions and yaw direction differ across libraries; inverse correctness must be tested independently of quality gains.

Find source links and value grades in the lesson atlas

Tune the balance between classification and box-regression losses

Conditions

  • classification and localization are trained jointly

Action

  • Proposed recipe: compare loss weights instead of assuming equal task weighting.

Check

  • Proposed check: Sweep loss weights on inner validation, report accuracy/IoUtradeoffs on an untouched holdout, and monitor relative loss/gradient scales.

Limits

  • Alpha0.7depends on loss scales and task priorities; no controlled weight sweep is reported.

Find source links and value grades in the lesson atlas

Keep bounding-box targets consistent with image resizing

Conditions

  • image resizing changes the coordinate system used for localization

Action

  • Proposed recipe: verify normalized box targets and all resize and augmentation transformations.

Check

  • Proposed check: Overlay transformed boxes on resize/crop/flip fixtures and round-trip coordinates to the original image, checking edges and degenerate boxes.

Limits

  • Normalization conventions must match resize and augmentation; published description does not prove transform implementation correctness.

Find source links and value grades in the lesson atlas

Learn proposal separation quality from morphology

Conditions

  • segmentation candidates have uncertain watershed thresholds

Action

  • Proposed conditional application: Predict candidate IoU using morphological features with LightGBM and choose separation thresholds per candidate.

Check

  • Proposed check: Fit IoU regressor on OOF candidates; compare adaptive/fixed thresholds on untouched image types.

Limits

  • Candidate labels can leak from fitted base masks; distribution shift affects learned quality.

Find source links and value grades in the lesson atlas

Predict touching borders to separate adjacent instances

Conditions

  • binary masks merge neighboring nuclei

Action

  • Proposed conditional application: Predict cell-interior and touching-border channels and use watershed; combine full-mask predictions when border removal hurts IoU.

Check

  • Proposed check: Compare border/full-mask targets with watershed on image-source-held-out instance precision and IoU thresholds.

Limits

  • Border removal improves separation but removes area; multiple networks increase cost.

Find source links and value grades in the lesson atlas

Share a backbone while separating classification and localization heads

Conditions

  • each image requires both a class and bounding box

Action

  • Proposed recipe: compare a shared feature extractor with task-specific prediction heads.

Check

  • Proposed check: Compare shared and separate backbones under the same split and budget, report both accuracy and IoU, and inspect negative task transfer.

Limits

  • Joint learning can harm either task; source's win explanation does not establish component causality.

Find source links and value grades in the lesson atlas

Use crops for training when full-image detection exceeds memory

Conditions

  • high-resolution document detection with limited GPU memory

Action

  • Proposed: train on bounded image crops and evaluate multiscale full images under an explicit size cap.

Check

  • Proposed: ensure boundary-crossing boxes are handled consistently and compare crop training/full-image inference across scale settings while recording peak memory.

Limits

  • The author reports two 1080 Ti GPUs and leaderboard scores of 0.935 and 0.946 for different training/testing scale packages, plus 0.950 for an ensemble. The metric definition and leaderboard partition are unspecified; crop and scale effects are not isolated.

Find source links and value grades in the lesson atlas

Budget 3D TTA by measured incremental benefit

Conditions

  • additional point-cloud transformations increase inference work

Action

  • Proposed recipe: compare the limited flip-only TTA with more rotations and transformations under the runtime constraint.

Check

  • Proposed check: Profile each added transform's validation gain and end-to-end inference time after inverse mapping and fusion, stopping when budget or marginal utility is exhausted.

Limits

  • TTA benefits depend on fusion and transform validity; source supplies no quantified extra-copy speed tradeoff.

Find source links and value grades in the lesson atlas

Discard tile borders while budgeting overlap inference cost

Conditions

  • large-image segmentation has edge artifacts in tiled predictions

Action

  • Proposed adaptation of source observation: Use overlapping context tiles and retain central predictions, optionally gating empty tiles with a classifier.

Check

  • Proposed verification: Check complete coverage, border quality and missed-positive gating errors plus total inference time.

Limits

  • The source reports fourfold inference cost; aggressive gating can miss small masks and validation used the old dataset.

Find source links and value grades in the lesson atlas

Fuse boxes across detector scales

Conditions

  • multiple detectors produce competing boxes for the same objects

Action

  • Proposed conditional application: Use weighted box fusion to combine multi-scale detector predictions and compare it with the existing suppression/ensemble baseline.

Check

  • Proposed check: Compare WBF and NMS with identical detectors on source-held-out AP; check class mapping and coordinate rescaling.

Limits

  • Correlated detectors can reinforce errors; nine-model inference is expensive.

Find source links and value grades in the lesson atlas

Test gradient accumulation rather than assuming equivalence

Conditions

  • large detection models motivate smaller micro-batches

Action

  • Proposed conditional application: Verify accumulation behavior against actual-batch training before adopting it.

Check

  • Proposed check: Match effective batch, optimizer steps and LR; compare actual/accumulated batches with normalization behavior controlled.

Limits

  • Microbatch statistics, loss scaling or implementation may differ; failure is local.

Find source links and value grades in the lesson atlas