The configuration file

Every field a specification accepts, generated from the schema.

A configuration is one YAML file describing a whole run: where the data is, and one or more models to train on it. It is the same file in both packages - R reads it with platypus_spec(yaml = ...) and Python with from_yaml(), and both hand it to the same pydantic models, which is where every description on this page comes from.

# 2018 Data Science Bowl: find the nuclei.
#
# Two models, one file, one run. This is the whole point of the YAML path - and the
# identical spec can be built from arguments in R and behave the same way.

task: semantic_segmentation

data:
  train_path: examples/data/data_science_bowl/stage1_train
  validation_path: examples/data/data_science_bowl/stage1_validation
  test_path: examples/data/data_science_bowl/stage1_test
  mode: nested_dirs
  colormap:
    - [0, 0, 0]          # background
    - [255, 255, 255]    # nucleus
  shuffle: true

output_dir: examples/output
seed: 0

models:
  - name: unet
    architecture: u_net
    input_shape: [160, 160]
    channels: 3
    blocks: 4
    filters: 16
    dropout: 0.1
    loss:
      name: cce_dice
      cce_weight: 0.5
    metrics:
      - name: dice
        include_background: false    # nuclei are a small part of the picture
      - name: iou
        include_background: false
    optimizer:
      name: adam
      learning_rate: 0.001
    callbacks:
      - name: early_stopping
        monitor: val_dice
        patience: 4
      - name: model_checkpoint
        path: examples/output/unet.pt
        monitor: val_dice
      - name: csv_logger
        path: examples/output/unet_history.csv
      - name: terminate_on_nan
    augmentation:
      - name: HorizontalFlip
        params: {p: 0.5}
      - name: VerticalFlip
        params: {p: 0.5}
      - name: RandomRotate90
        params: {p: 0.5}
    epochs: 6
    batch_size: 8

  - name: linknet
    architecture: linknet
    input_shape: [160, 160]
    channels: 3
    blocks: 4
    filters: 16
    dropout: 0.1
    loss:
      name: focal_tversky
      alpha: 0.7            # weight missed nuclei more than false alarms
      gamma: 1.3
    metrics:
      - name: dice
        include_background: false
      - name: iou
        include_background: false
    optimizer:
      name: adamw
      learning_rate: 0.001
    callbacks:
      - name: early_stopping
        monitor: val_dice
        patience: 4
      - name: model_checkpoint
        path: examples/output/linknet.pt
        monitor: val_dice
      - name: csv_logger
        path: examples/output/linknet_history.csv
    epochs: 6
    batch_size: 8

Validation happens before anything is read from disk and before torch is imported, so a mistake here costs a second rather than an epoch. Every refusal names the field.

Schema for your editor

The same definitions are published as JSON Schema, so an editor can complete and check a configuration as you type it. In VS Code with the YAML extension, or any editor using yaml-language-server, put this on the first line:

# yaml-language-server: $schema=https://maju116.github.io/pyplatypus/schema/spec.schema.json

task, which decides the rest

task is required, and it is the only field that is: it chooses which shape the rest of the file has. Give it wrong and the refusal names the tag rather than reporting every field of the other task as unexpected.

task what it describes
semantic_segmentation SegmentationSpec
instance_segmentation InstanceSpec
object_detection DetectionSpec

task: semantic_segmentation

The SegmentationSpec shape. Its top level:

task
text — semantic_segmentation Fixed for this specification: semantic_segmentation.
seed
a whole number — unset Makes a run repeatable as far as the arithmetic is deterministic, which goes further on some hardware than others - so it is worth knowing where it stops. Repeatable: the split (split_dataset gives the same partition on any machine), a detector’s fitted anchors, the order the data arrives in and its augmentation - including inside the loader’s worker processes - and, on the processor, every number to twelve decimal places. Not repeatable: a run on a CUDA device. Measured on a GTX 1070: two runs at one seed agreed to twelve decimals on the processor and differed in the fourth after a single epoch on the card, because cuDNN chooses algorithms this does not constrain. Nor across versions of this package - anything that changes the order data is drawn in changes what one seed draws, and 0.8.0a3 did exactly that. So it is the field for comparing two of your own runs, and not for publishing a number somebody else can land on.
deterministic
true or false — false Ask torch for deterministic algorithms as well, which is the only thing that makes a run on a card repeatable - seed alone does not, and the field above says so. It is an opt-in because the cost is real and arrives late. Some convolutions become much slower, and an operation that has no deterministic implementation raises when it is reached - so a run can stop ten minutes in rather than when the specification was checked. Every other refusal here happens before anything is read; this one cannot, because whether an operation has a deterministic algorithm is a fact about the installed torch and the device, not about the configuration. Needs seed: constraining the algorithms while the draw still varies produces a run that is no more repeatable and only slower, so asking for it without a seed is refused rather than quietly accepted.
output_dir
text — platypus_output Where a run record is written, and where fit() puts anything it is asked to save. Nothing is written unless something asks to write: naming this does not by itself create a directory.
data
a SegmentationData block — required Where the images and their masks are, and how to read them.
models
a list of SegmentationModel blocks — required One or more models, trained in turn against the same data. Names must be unique: a run is recorded under its model’s name, so two models called the same thing would overwrite one record and report it as two. Several entries of the same architecture with different settings is the ordinary case - that is how a comparison is written.

data: for semantic segmentation

train_path
text — required Where the training data is. For nested_dirs, a directory of sample directories; for config_file, a CSV naming the files. With a split, this is the only path given and the divisions are cut from it.
validation_path
text — unset Optional only in the sense that split is the other way to get one, and validation: false the third. Exactly one of them, because two sources for the validation set are two places to change it.
test_path
text — unset A third set, scored only when asked. Whether it can be scored at all depends on whether it carries labels: a folder of images alone is accepted and predict works on it, while evaluate refuses it by name rather than failing two layers down on the first missing mask.
validation
true or false — true Whether this run validates at all. false is the third and last way of answering the question validation_path and split answer, and it has to be written down. A final fit on every case you have, once the hyperparameters are settled, is a legitimate thing to want - and until now it meant inventing a split and ignoring the number it produced. What is not legitimate is arriving here by omission: a specification with neither a path nor a split is still an error, because ‘I have no validation set’ and ‘I forgot’ look identical and only one of them is a decision. With false, giving validation_path or split as well is refused: they say where the validation set comes from and this says there is not one.
split
a SplitSpec block — unset Divide train_path instead of naming a second folder. Nothing is written: the samples that were already discovered are partitioned, so a specification stays a description and leaves no files behind. split_dataset() is the tool when the CSVs are the point - to keep, to hand to a colleague, or to cite.
mode
nested_dirs or config_file — nested_dirs How the files are arranged: nested_dirs for one directory per sample, config_file for a CSV whose columns name the files. The CSV form is what to use when the data cannot be moved or copied into a layout.
subdirs
a list — ["images", "masks"] For nested_dirs, the two subdirectories of each sample directory: the images and their masks. A sample may hold several mask files - one per object, as Data Science Bowl does - and they are united into one mask when read.
column_sep
text — ; For config_file, what separates several paths inside one cell - a sample with one mask file per object, as Data Science Bowl has, or one file per channel.
shuffle
true or false — true Shuffle the training data between epochs. Only the training data: validation is read in order so that two epochs measure the same thing in the same way.
window
text or a list — auto How values in real units are mapped to 0-1: a named window such as ‘lung’ or ‘soft_tissue’, an explicit (centre, width) pair, ‘auto’ for the window the file recorded - DICOM only, since NIfTI stores none - or ‘full’ for the whole range present. A fixed window is what makes two scans comparable: scaling each one by its own extremes lets a single bright voxel rescale everything else. Ignored for ordinary pictures, which are already 0-255. Accepted as dicom_window too, the name it had while DICOM was the only format that needed it.
colormap
a list of lists — unset One colour per class; index in this list is the class index.
labels
a list of whole numbers — unset For masks stored as label maps rather than pictures: the voxel value of each class, in class order. This is how volumes label anything - NIfTI holds integers, not colours - and how a single-channel PNG mask can be read too. Exactly one of colormap and labels is given. Include 0 unless the masks really do fill every voxel: a value matching no entry falls back to the first class, so a list without 0 trains the background as whatever that first class is, and fit refuses the combination rather than let it look plausible.
regions
a list of Region blocks — unset Overlapping classes, named as unions of a label map’s values - the third way of saying what a mask’s classes are, and the only one that lets a voxel belong to more than one. Each entry gives a name and the values it is made of. With regions the model’s channels are independent binary questions: a sigmoid per channel rather than one softmax across them, and no background entry, for the same reason detection’s classes has none. Exactly one of colormap, labels and regions is given. Two regions are the minimum - one region is ordinary binary segmentation, which labels: [0, 1] already says.
classes_from
a list of text values — unset For masks kept as one binary file per class - the fourth way of saying what a mask’s classes are, and the only one that can express classes which overlap independently: a vessel crossing an organ, or two annotators’ masks of the same structure. One pattern per class, in class order, each a regular expression matched against a sample’s mask file names, and every pattern must match exactly one file - the rules channels_from uses, through the same matcher. Stated rather than inferred for the reason channels_from gives, with worse consequences: sorting the names is reproducible and anatomically meaningless, and a model trained with ‘tumour’ in channel 1 and used where channel 1 is ‘oedema’ answers plausibly, in range, and nothing downstream can tell. Like regions this makes every channel an independent binary question - a sigmoid per channel rather than one softmax across them - and has no background entry. The difference between the two is the file on disk and nothing else: regions derives overlap from one label map’s values, which is what BraTS and the liver-and-tumour family need, and this reads overlap that was drawn separately in the first place. Exactly one of colormap, labels, regions and classes_from is given.
channels_from
a list of text values — unset For datasets that keep one channel per file - BraTS ships T1, T1ce, T2 and FLAIR per patient; 38-Cloud keeps its bands apart - one pattern per channel, in channel order, each a regular expression matched against the file names. Every pattern must match exactly one of a sample’s files. Stated rather than inferred because sorting gives flair, t1, t1ce, t2: reproducible, and anatomically meaningless. A model trained with FLAIR in channel one and used on data where channel one is T1 returns a plausible answer and no error.
target_spacing
a list — unset Resample every volume to this many millimetres per voxel before training, then centre-crop or pad to the model’s input_shape. Without it a volume is simply resized to input_shape, which gives two scans of the same anatomy different physical scale when they were acquired at different slice thicknesses - and the network has no way to know. Volumes only; ignored for 2D.

split: inside semantic segmentation data:

fractions
a list of numbers — required Two or three: train, validation, and optionally test. The length rule is enforced by the splitting module rather than restated here - one rule, one place. A third fraction is scoreable, because it comes from annotated data.
group_by
text — required Required, and may be null. A regular expression read against the sample key; everything sharing a group lands in exactly one split. Usually a patient, sometimes a study, a scanner or a site - whatever must not straddle the division. Required rather than optional because the mistake it prevents is invisible. Slices of one patient in training and validation at once make validation measure memory instead of generalisation, and Dice comes out several points too high with nothing in the output to say so. An omitted field would be that choice made silently; null is the same choice made on purpose, which is all this asks for.
seed
a whole number — 0 The split is deterministic given the samples, the fractions, the pattern and this. A split that moves between runs makes two results incomparable and the reason for a difference impossible to find.

regions: inside semantic segmentation data:

name
text — required What this region is called. It names a channel of the output, so it is what a per-class score is reported under and what save_class_masks puts in a filename. Distinct from every other region’s name.
of
a list of whole numbers — required The label-map values belonging to this region, in any order. A voxel is in the region when its value is one of these, so regions may overlap - that is the point - and a single value is normal: BraTS’s enhancing tumour is of: [4]. Disjoint regions are allowed and mean something useful, grouping several labels into one class and dropping the background channel; they are not a mistake and are not refused.

models: for semantic segmentation

name
text — required Unique within a spec; names the outputs.
input_shape
a list of whole numbers — required Spatial size: (height, width) in 2D, (depth, height, width) in 3D.
channels
a whole number — 3 How many channels the network takes: 3 for colour, 1 for greyscale, and one per sequence for multi-modal data such as the four MRI sequences in BraTS. Derived rather than declared when channels_from names the files, because then the data decides. Not adopted from a weights file - the data pipeline needs it before a network exists, and a specification has to be checkable offline.
optimizer
one of adadelta, adagrad, adam, adamax, adamw, nadam, rmsprop, sgd — optional How the weights are updated. name selects which one and decides which other keys this block accepts.
callbacks
a list, each one of cosine_annealing, csv_logger, early_stopping, model_checkpoint, reduce_lr_on_plateau, swa, terminate_on_nan — optional Things that happen between epochs: stopping early, writing checkpoints, moving the learning rate, averaging the weights. A callback watching a number no metric will produce is refused when the specification is built rather than waited on forever, and so is one watching val_* in a run with no validation set.
augmentation
a list of AugmentationStep blocks — unset albumentations transforms, applied to the training data only - never to validation, where the point is to measure the same thing every epoch. Each step is probed at the model’s own input size while the pipeline is built, because support for volumes is uneven and a transform that cannot take one raises from inside the library.
epochs
a whole number — 10 How many passes over the training data. The default is deliberately small: it is enough to see that a run works and not enough to mistake for a result. With cosine_annealing this is also the length the schedule is spread over.
batch_size
a whole number — 8 How many samples go through the network at once. Limited by memory rather than by the problem, and it is the first thing to lower when a 3D run will not fit. It interacts with batch_normalization: statistics taken over very few samples are noisy.
weights
text — unset Registry name (e.g. ‘dsbowl-unet’) or a path to a local checkpoint.
fit
true or false — true Set false to load weights and skip training.
architecture
u_net or u_net_plus_plus or res_u_net or linknet — u_net Which U-shaped network. Measured on Data Science Bowl, one split, 60 epochs, the four span 0.9187 to 0.9217 of Dice - a spread of 0.0030, against 0.054 between images within any one of them, and 0.0012 from re-running one of them with another seed. On that problem the architecture is not where the result comes from. Adopted from the weights file when weights names one.
blocks
a whole number — 4 How many times the resolution is halved, so how much context the deepest layer sees. input_shape must divide by 2^blocks. Adopted from the weights file when weights is given and this is not; with an encoder, the backbone’s own depth is the ceiling.
filters
a whole number — 16 Filters in the first block, doubled at every level, so this sets the model’s size more than anything else does. Adopted from the weights file when weights is given and this is not.
block_width
a whole number — 2 Convolutions per block, at every level. 2 is what every U-Net paper uses and what the published weights were trained with; raising it adds depth without adding levels, which is the lever to reach for when the objects are small enough that halving the resolution again would lose them.
dropout
a number — 0.0 Drop this fraction during training. 0 switches it off, which is the default because these models are small enough that batch normalisation usually carries the regularisation on its own.
batch_normalization
true or false — true Normalise between convolutions. On by default, and worth knowing about when weights are averaged: swa has to recompute these statistics over the training data, because an averaged weight tensor inherits them from whichever epoch was last rather than averaging them.
separable_conv
true or false — false Depthwise-separable convolutions: far fewer parameters for the same shape, and a little slower to converge.
spatial_dropout
true or false — true Drop whole feature maps rather than individual activations. The right kind for images, because neighbouring pixels in one map are correlated and dropping them one at a time removes less than it appears to. Has no effect unless dropout is above 0.
upsample
true or false — false Upsample+conv instead of transposed convolution.
deep_supervision
true or false — false Train every decoder depth rather than only the last, each at input resolution. u_net_plus_plus only, since it is the architecture with intermediate outputs to supervise.
activation
relu or leaky_relu or elu or selu or gelu or silu or tanh — relu The non-linearity between convolutions, the same one throughout.
initialiser
he_normal or he_uniform or glorot_normal or glorot_uniform — he_normal How the weights start. The he_* family is scaled for ReLU-like activations and glorot_* for symmetric ones, so this and activation are a pair.
loss
one of boundary, cce, cce_dice, combo, dice, focal, focal_tversky, generalized_dice, iou, l1, lovasz, mse, smooth_l1, tversky — optional The objective trained against. The loss column of a score table is comparable only between models trained on the same one - a Focal-Tversky of 0.05 is not better than a cross-entropy of 0.14, it is not the same question - which is why the table carries the loss’s name beside it.
metrics
a list, each one of cldice, dice, iou, surface_dice, tversky — optional What to report beside the loss, computed on the mask rather than on the objective, so these stay comparable across models. A callback watching val_<metric> is refused when nothing here will produce it.
splits
a list of whole numbers — unset Cut each image into a grid of tiles instead of shrinking it. One entry per spatial dimension, e.g. [2, 3] cuts into 2 rows by 3 columns. The source is read at splits * input_shape and divided; every tile is input_shape. Leave unset to resize the whole image instead. Training then shuffles in windows rather than over every tile, so one decode serves all of a sample’s tiles: measured on 2048-pixel retinas cut sixteen ways, the loader costs 254 ms a tile shuffled wholesale against 49 ms this way, and a batch still draws from several sources.
encoder
text — unset A timm backbone to use as the contracting path, e.g. ‘resnet34’. Leave unset for the built-in encoder, which is the only one that works in 3D.
pretrained
true or false — false Load the encoder’s ImageNet weights. Separate from encoder on purpose: naming a backbone builds that architecture, and this is the flag that reaches the network, which matters where there is no network.
encoder_learning_rate
a number — unset A separate, usually smaller learning rate for the layers that arrived pretrained. The full-resolution stage added in front of them is ours and starts random, so it trains at the optimizer’s own rate. Unset means one rate for the whole network.
freeze_encoder
a whole number — 0 Keep the pretrained layers fixed for this many epochs, then train them. Lets the random decoder settle before its gradients reach weights worth keeping. A number at or above epochs freezes them for the whole run.

augmentation: inside semantic segmentation models:

name
text — required An albumentations transform, by its own class name - HorizontalFlip, RandomBrightnessContrast. Checked against what the installed albumentations offers, and available_transforms() lists them; at rank 3 the list is shorter, because support for volumes is uneven.
params
a mapping — optional Arguments passed to that transform, exactly as albumentations names them - so p is the probability of applying it, defaulting to the transform’s own. Not validated here: they are albumentations’ to accept, and each step is probed at the model’s input size when the pipeline is built, so a bad argument is named there rather than guessed at here.

task: instance_segmentation

The InstanceSpec shape. Its top level:

task
text — instance_segmentation Fixed for this specification: instance_segmentation.
seed
a whole number — unset Makes a run repeatable as far as the arithmetic is deterministic, which goes further on some hardware than others - so it is worth knowing where it stops. Repeatable: the split (split_dataset gives the same partition on any machine), a detector’s fitted anchors, the order the data arrives in and its augmentation - including inside the loader’s worker processes - and, on the processor, every number to twelve decimal places. Not repeatable: a run on a CUDA device. Measured on a GTX 1070: two runs at one seed agreed to twelve decimals on the processor and differed in the fourth after a single epoch on the card, because cuDNN chooses algorithms this does not constrain. Nor across versions of this package - anything that changes the order data is drawn in changes what one seed draws, and 0.8.0a3 did exactly that. So it is the field for comparing two of your own runs, and not for publishing a number somebody else can land on.
deterministic
true or false — false Ask torch for deterministic algorithms as well, which is the only thing that makes a run on a card repeatable - seed alone does not, and the field above says so. It is an opt-in because the cost is real and arrives late. Some convolutions become much slower, and an operation that has no deterministic implementation raises when it is reached - so a run can stop ten minutes in rather than when the specification was checked. Every other refusal here happens before anything is read; this one cannot, because whether an operation has a deterministic algorithm is a fact about the installed torch and the device, not about the configuration. Needs seed: constraining the algorithms while the draw still varies produces a run that is no more repeatable and only slower, so asking for it without a seed is refused rather than quietly accepted.
output_dir
text — platypus_output Where a run record is written, and where fit() puts anything it is asked to save. Nothing is written unless something asks to write: naming this does not by itself create a directory.
data
a InstanceData block — required Where the images and their per-object masks are, and how to read them.
models
a list of InstanceModel blocks — required One or more models, trained in turn against the same data. Names must be unique, for the same reason as in a segmentation specification. Several entries differing only in separation is the comparison this task exists to make easy - and decode widens the table again without a second run.

data: for instance segmentation

train_path
text — required Where the training data is. For nested_dirs, a directory of sample directories; for config_file, a CSV naming the files. With a split, this is the only path given and the divisions are cut from it.
validation_path
text — unset Optional only in the sense that split is the other way to get one, and validation: false the third. Exactly one of them, because two sources for the validation set are two places to change it.
test_path
text — unset A third set, scored only when asked. Whether it can be scored at all depends on whether it carries labels: a folder of images alone is accepted and predict works on it, while evaluate refuses it by name rather than failing two layers down on the first missing mask.
validation
true or false — true Whether this run validates at all. false is the third and last way of answering the question validation_path and split answer, and it has to be written down. A final fit on every case you have, once the hyperparameters are settled, is a legitimate thing to want - and until now it meant inventing a split and ignoring the number it produced. What is not legitimate is arriving here by omission: a specification with neither a path nor a split is still an error, because ‘I have no validation set’ and ‘I forgot’ look identical and only one of them is a decision. With false, giving validation_path or split as well is refused: they say where the validation set comes from and this says there is not one.
split
a SplitSpec block — unset Divide train_path instead of naming a second folder. Nothing is written: the samples that were already discovered are partitioned, so a specification stays a description and leaves no files behind. split_dataset() is the tool when the CSVs are the point - to keep, to hand to a colleague, or to cite.
mode
nested_dirs or config_file — nested_dirs How the files are arranged: nested_dirs for one directory per sample, config_file for a CSV whose columns name the files. The CSV form is what to use when the data cannot be moved or copied into a layout.
subdirs
a list — ["images", "masks"] For nested_dirs, the two subdirectories of each sample directory: the images, and one mask file per object. A case with 375 nuclei has 375 files here, which is the Data Science Bowl’s own layout; they are read as a label map with one value per object rather than united into one mask, which is the whole difference between this task and semantic_segmentation.
column_sep
text — ; For config_file, what separates several paths inside one cell - a sample with one mask file per object, as Data Science Bowl has, or one file per channel.
shuffle
true or false — true Shuffle the training data between epochs. Only the training data: validation is read in order so that two epochs measure the same thing in the same way.
window
text or a list — auto How values in real units are mapped to 0-1: a named window such as ‘lung’ or ‘soft_tissue’, an explicit (centre, width) pair, ‘auto’ for the window the file recorded - DICOM only, since NIfTI stores none - or ‘full’ for the whole range present. A fixed window is what makes two scans comparable: scaling each one by its own extremes lets a single bright voxel rescale everything else. Ignored for ordinary pictures, which are already 0-255. Accepted as dicom_window too, the name it had while DICOM was the only format that needed it.

split: inside instance segmentation data:

fractions
a list of numbers — required Two or three: train, validation, and optionally test. The length rule is enforced by the splitting module rather than restated here - one rule, one place. A third fraction is scoreable, because it comes from annotated data.
group_by
text — required Required, and may be null. A regular expression read against the sample key; everything sharing a group lands in exactly one split. Usually a patient, sometimes a study, a scanner or a site - whatever must not straddle the division. Required rather than optional because the mistake it prevents is invisible. Slices of one patient in training and validation at once make validation measure memory instead of generalisation, and Dice comes out several points too high with nothing in the output to say so. An omitted field would be that choice made silently; null is the same choice made on purpose, which is all this asks for.
seed
a whole number — 0 The split is deterministic given the samples, the fractions, the pattern and this. A split that moves between runs makes two results incomparable and the reason for a difference impossible to find.

models: for instance segmentation

name
text — required Unique within a spec; names the outputs.
input_shape
a list of whole numbers — required Spatial size: (height, width) in 2D, (depth, height, width) in 3D.
channels
a whole number — 3 How many channels the network takes: 3 for colour, 1 for greyscale, and one per sequence for multi-modal data such as the four MRI sequences in BraTS. Derived rather than declared when channels_from names the files, because then the data decides. Not adopted from a weights file - the data pipeline needs it before a network exists, and a specification has to be checkable offline.
optimizer
one of adadelta, adagrad, adam, adamax, adamw, nadam, rmsprop, sgd — optional How the weights are updated. name selects which one and decides which other keys this block accepts.
callbacks
a list, each one of cosine_annealing, csv_logger, early_stopping, model_checkpoint, reduce_lr_on_plateau, swa, terminate_on_nan — optional Things that happen between epochs: stopping early, writing checkpoints, moving the learning rate, averaging the weights. A callback watching a number no metric will produce is refused when the specification is built rather than waited on forever, and so is one watching val_* in a run with no validation set.
augmentation
a list of AugmentationStep blocks — unset albumentations transforms, applied to the training data only - never to validation, where the point is to measure the same thing every epoch. Each step is probed at the model’s own input size while the pipeline is built, because support for volumes is uneven and a transform that cannot take one raises from inside the library.
epochs
a whole number — 10 How many passes over the training data. The default is deliberately small: it is enough to see that a run works and not enough to mistake for a result. With cosine_annealing this is also the length the schedule is spread over.
batch_size
a whole number — 8 How many samples go through the network at once. Limited by memory rather than by the problem, and it is the first thing to lower when a 3D run will not fit. It interacts with batch_normalization: statistics taken over very few samples are noisy.
weights
text — unset Registry name (e.g. ‘dsbowl-unet’) or a path to a local checkpoint.
fit
true or false — true Set false to load weights and skip training.
architecture
u_net or u_net_plus_plus or res_u_net or linknet — u_net Which U-shaped network. Measured on Data Science Bowl, one split, 60 epochs, the four span 0.9187 to 0.9217 of Dice - a spread of 0.0030, against 0.054 between images within any one of them, and 0.0012 from re-running one of them with another seed. On that problem the architecture is not where the result comes from. Adopted from the weights file when weights names one.
blocks
a whole number — 4 How many times the resolution is halved, so how much context the deepest layer sees. input_shape must divide by 2^blocks. Adopted from the weights file when weights is given and this is not; with an encoder, the backbone’s own depth is the ceiling.
filters
a whole number — 16 Filters in the first block, doubled at every level, so this sets the model’s size more than anything else does. Adopted from the weights file when weights is given and this is not.
block_width
a whole number — 2 Convolutions per block, at every level. 2 is what every U-Net paper uses and what the published weights were trained with; raising it adds depth without adding levels, which is the lever to reach for when the objects are small enough that halving the resolution again would lose them.
dropout
a number — 0.0 Drop this fraction during training. 0 switches it off, which is the default because these models are small enough that batch normalisation usually carries the regularisation on its own.
batch_normalization
true or false — true Normalise between convolutions. On by default, and worth knowing about when weights are averaged: swa has to recompute these statistics over the training data, because an averaged weight tensor inherits them from whichever epoch was last rather than averaging them.
separable_conv
true or false — false Depthwise-separable convolutions: far fewer parameters for the same shape, and a little slower to converge.
spatial_dropout
true or false — true Drop whole feature maps rather than individual activations. The right kind for images, because neighbouring pixels in one map are correlated and dropping them one at a time removes less than it appears to. Has no effect unless dropout is above 0.
upsample
true or false — false Upsample+conv instead of transposed convolution.
deep_supervision
true or false — false Train every decoder depth rather than only the last, each at input resolution. u_net_plus_plus only, since it is the architecture with intermediate outputs to supervise.
activation
relu or leaky_relu or elu or selu or gelu or silu or tanh — relu The non-linearity between convolutions, the same one throughout.
initialiser
he_normal or he_uniform or glorot_normal or glorot_uniform — he_normal How the weights start. The he_* family is scaled for ReLU-like activations and glorot_* for symmetric ones, so this and activation are a pair.
loss
one of boundary, cce, cce_dice, combo, dice, focal, focal_tversky, generalized_dice, iou, l1, lovasz, mse, smooth_l1, tversky — optional The objective trained against. The loss column of a score table is comparable only between models trained on the same one - a Focal-Tversky of 0.05 is not better than a cross-entropy of 0.14, it is not the same question - which is why the table carries the loss’s name beside it.
metrics
a list, each one of cldice, dice, iou, surface_dice, tversky — optional What to report beside the loss, computed on the mask rather than on the objective, so these stay comparable across models. A callback watching val_<metric> is refused when nothing here will produce it.
splits
a list of whole numbers — unset Cut each image into a grid of tiles instead of shrinking it. One entry per spatial dimension, e.g. [2, 3] cuts into 2 rows by 3 columns. The source is read at splits * input_shape and divided; every tile is input_shape. Leave unset to resize the whole image instead. Training then shuffles in windows rather than over every tile, so one decode serves all of a sample’s tiles: measured on 2048-pixel retinas cut sixteen ways, the loader costs 254 ms a tile shuffled wholesale against 49 ms this way, and a batch still draws from several sources.
encoder
text — unset A timm backbone to use as the contracting path, e.g. ‘resnet34’. Leave unset for the built-in encoder, which is the only one that works in 3D.
pretrained
true or false — false Load the encoder’s ImageNet weights. Separate from encoder on purpose: naming a backbone builds that architecture, and this is the flag that reaches the network, which matters where there is no network.
encoder_learning_rate
a number — unset A separate, usually smaller learning rate for the layers that arrived pretrained. The full-resolution stage added in front of them is ours and starts random, so it trains at the optimizer’s own rate. Unset means one rate for the whole network.
freeze_encoder
a whole number — 0 Keep the pretrained layers fixed for this many epochs, then train them. Lets the random decoder settle before its gradients reach weights worth keeping. A number at or above epochs freezes them for the whole run.
separation
one of flows, rim — unset What the network predicts so that one mask can become many objects: a block named rim or flows, carrying that mechanism’s own settings. Required unless weights names a file that records what it was trained to predict, because it decides the width of the head and which losses are admissible - the two things a model cannot be built without - and because neither value is a safe guess at what somebody meant. A published file is the one place it does not have to be guessed. dsbowl-instances records rim at width 2, so leaving this out beside weights: dsbowl-instances adopts a training decision rather than inventing one, and the engine prints what it took. It was required outright until 0.8.0a28, which left four of this model’s fields as the file’s own fingerprint with no way to learn them but a model card - and separation is the one nobody can guess, since a rim’s width is not a shape. pyplatypus#236. A block rather than a bare name because the settings differ between the two and belong inside the one they describe: width under name: flows is an unknown key rather than something a validator has to refuse. It is also the field whose documentation carries a result: on the Data Science Bowl the two are worth +0.0640 and +0.0680 of instance average precision over the same mask through connected_components, a difference below the noise between two runs of one configuration, while a perfect mask is worth +0.2398. Read the blocks’ own descriptions for what that means before planning a comparison around this axis.
decode
a list, each one of connected_components, follow_flows, nearest_seed, watershed — unset How the predicted channels become objects. A list, because several decodings share one forward pass: a second decoding of 134 images costs 1.41 s against 16 minutes for a second training run, so this is metrics: rather than callbacks: - evaluate() returns one row per (model, decoding). Each entry is a block with a name and that decoding’s own parameters. A decoding the separation cannot feed - follow_flows with rim, say - is refused at load time with both halves named, rather than returning a label map of something else. Left unset it is the signal’s own decoding plus connected_components, which is the pair that makes a row readable: the same model and the same mask with and without the separation signal. Write the list to choose; leave it out and the comparison every one of these runs needs is already there. With splits, decoding happens after the tiles are stitched, never per tile - an object crossing a seam would otherwise become two objects, and that is a constraint of the task rather than a setting.

augmentation: inside instance segmentation models:

name
text — required An albumentations transform, by its own class name - HorizontalFlip, RandomBrightnessContrast. Checked against what the installed albumentations offers, and available_transforms() lists them; at rank 3 the list is shorter, because support for volumes is uneven.
params
a mapping — optional Arguments passed to that transform, exactly as albumentations names them - so p is the probability of applying it, defaulting to the transform’s own. Not validated here: they are albumentations’ to accept, and each step is probed at the model’s input size when the pipeline is built, so a bad argument is named there rather than guessed at here.

task: object_detection

The DetectionSpec shape. Its top level:

task
text — required Fixed for this specification: object_detection.
seed
a whole number — unset Makes a run repeatable as far as the arithmetic is deterministic, which goes further on some hardware than others - so it is worth knowing where it stops. Repeatable: the split (split_dataset gives the same partition on any machine), a detector’s fitted anchors, the order the data arrives in and its augmentation - including inside the loader’s worker processes - and, on the processor, every number to twelve decimal places. Not repeatable: a run on a CUDA device. Measured on a GTX 1070: two runs at one seed agreed to twelve decimals on the processor and differed in the fourth after a single epoch on the card, because cuDNN chooses algorithms this does not constrain. Nor across versions of this package - anything that changes the order data is drawn in changes what one seed draws, and 0.8.0a3 did exactly that. So it is the field for comparing two of your own runs, and not for publishing a number somebody else can land on.
deterministic
true or false — false Ask torch for deterministic algorithms as well, which is the only thing that makes a run on a card repeatable - seed alone does not, and the field above says so. It is an opt-in because the cost is real and arrives late. Some convolutions become much slower, and an operation that has no deterministic implementation raises when it is reached - so a run can stop ten minutes in rather than when the specification was checked. Every other refusal here happens before anything is read; this one cannot, because whether an operation has a deterministic algorithm is a fact about the installed torch and the device, not about the configuration. Needs seed: constraining the algorithms while the draw still varies produces a run that is no more repeatable and only slower, so asking for it without a seed is refused rather than quietly accepted.
output_dir
text — platypus_output Where a run record is written, and where fit() puts anything it is asked to save. Nothing is written unless something asks to write: naming this does not by itself create a directory.
data
a DetectionData block — required Where the images and their annotations are, and how to read them.
models
a list of DetectionModel blocks — required One or more detectors, trained in turn against the same data. Names must be unique, for the same reason as in a segmentation specification.

data: for object detection

train_path
text — required Where the training data is. For nested_dirs, a directory of sample directories; for config_file, a CSV naming the files. With a split, this is the only path given and the divisions are cut from it.
validation_path
text — unset Optional only in the sense that split is the other way to get one, and validation: false the third. Exactly one of them, because two sources for the validation set are two places to change it.
test_path
text — unset A third set, scored only when asked. Whether it can be scored at all depends on whether it carries labels: a folder of images alone is accepted and predict works on it, while evaluate refuses it by name rather than failing two layers down on the first missing mask.
validation
true or false — true Whether this run validates at all. false is the third and last way of answering the question validation_path and split answer, and it has to be written down. A final fit on every case you have, once the hyperparameters are settled, is a legitimate thing to want - and until now it meant inventing a split and ignoring the number it produced. What is not legitimate is arriving here by omission: a specification with neither a path nor a split is still an error, because ‘I have no validation set’ and ‘I forgot’ look identical and only one of them is a decision. With false, giving validation_path or split as well is refused: they say where the validation set comes from and this says there is not one.
split
a SplitSpec block — unset Divide train_path instead of naming a second folder. Nothing is written: the samples that were already discovered are partitioned, so a specification stays a description and leaves no files behind. split_dataset() is the tool when the CSVs are the point - to keep, to hand to a colleague, or to cite.
mode
nested_dirs or config_file — nested_dirs How the files are arranged: nested_dirs for one directory per sample, config_file for a CSV whose columns name the files. The CSV form is what to use when the data cannot be moved or copied into a layout.
subdirs
a list — ["images", "annotations"] For nested_dirs, the two subdirectories of each sample directory: the images and the files holding their boxes. Which format those files are in is said by annotation_format, never guessed from the extension.
column_sep
text — ; For config_file, what separates several paths inside one cell - a sample with one mask file per object, as Data Science Bowl has, or one file per channel.
shuffle
true or false — true Shuffle the training data between epochs. Only the training data: validation is read in order so that two epochs measure the same thing in the same way.
window
text or a list — auto How values in real units are mapped to 0-1: a named window such as ‘lung’ or ‘soft_tissue’, an explicit (centre, width) pair, ‘auto’ for the window the file recorded - DICOM only, since NIfTI stores none - or ‘full’ for the whole range present. A fixed window is what makes two scans comparable: scaling each one by its own extremes lets a single bright voxel rescale everything else. Ignored for ordinary pictures, which are already 0-255. Accepted as dicom_window too, the name it had while DICOM was the only format that needed it.
classes
a list of text values — required Class names in class order. Position is the class index. No background entry - an image region with no object is not a box.
annotation_format
pascal_voc or labelme — pascal_voc Pascal VOC XML, the format BCCD and most detection datasets ship, or LabelMe JSON. Both readers are in pyplatypus.detection.annotations.
coordinates
voc or zero_based — unset How to read a Pascal VOC box. The format’s own convention is 1-based and inclusive of both endpoints, so a 10-pixel-wide box runs xmin=1 xmax=10 and the width is xmax - xmin + 1. Plenty of tools write the same files 0-based and exclusive, where that box is xmin=0 xmax=10. Reading one as the other shifts every box by a pixel and shrinks it by one, which is invisible at a glance and costs real IoU on small objects. Unset means ‘voc’. describe_annotations reports the evidence: a minimum coordinate of 0 proves the file is 0-based, since a 1-based index cannot be zero.
strict_labels
true or false — true Refuse an object whose class is not in classes. False skips it instead, which is how you train on two of a dataset’s three classes. True by default: an unexpected label is more often a typo in classes than an intention.

split: inside object detection data:

fractions
a list of numbers — required Two or three: train, validation, and optionally test. The length rule is enforced by the splitting module rather than restated here - one rule, one place. A third fraction is scoreable, because it comes from annotated data.
group_by
text — required Required, and may be null. A regular expression read against the sample key; everything sharing a group lands in exactly one split. Usually a patient, sometimes a study, a scanner or a site - whatever must not straddle the division. Required rather than optional because the mistake it prevents is invisible. Slices of one patient in training and validation at once make validation measure memory instead of generalisation, and Dice comes out several points too high with nothing in the output to say so. An omitted field would be that choice made silently; null is the same choice made on purpose, which is all this asks for.
seed
a whole number — 0 The split is deterministic given the samples, the fractions, the pattern and this. A split that moves between runs makes two results incomparable and the reason for a difference impossible to find.

models: for object detection

name
text — required Unique within a spec; names the outputs.
input_shape
a list of whole numbers — required Spatial size: (height, width) in 2D, (depth, height, width) in 3D.
channels
a whole number — 3 How many channels the network takes: 3 for colour, 1 for greyscale, and one per sequence for multi-modal data such as the four MRI sequences in BraTS. Derived rather than declared when channels_from names the files, because then the data decides. Not adopted from a weights file - the data pipeline needs it before a network exists, and a specification has to be checkable offline.
optimizer
one of adadelta, adagrad, adam, adamax, adamw, nadam, rmsprop, sgd — optional How the weights are updated. name selects which one and decides which other keys this block accepts.
callbacks
a list, each one of cosine_annealing, csv_logger, early_stopping, model_checkpoint, reduce_lr_on_plateau, swa, terminate_on_nan — optional Things that happen between epochs: stopping early, writing checkpoints, moving the learning rate, averaging the weights. A callback watching a number no metric will produce is refused when the specification is built rather than waited on forever, and so is one watching val_* in a run with no validation set.
augmentation
a list of AugmentationStep blocks — unset albumentations transforms, applied to the training data only - never to validation, where the point is to measure the same thing every epoch. Each step is probed at the model’s own input size while the pipeline is built, because support for volumes is uneven and a transform that cannot take one raises from inside the library.
epochs
a whole number — 10 How many passes over the training data. The default is deliberately small: it is enough to see that a run works and not enough to mistake for a result. With cosine_annealing this is also the length the schedule is spread over.
batch_size
a whole number — 8 How many samples go through the network at once. Limited by memory rather than by the problem, and it is the first thing to lower when a 3D run will not fit. It interacts with batch_normalization: statistics taken over very few samples are noisy.
weights
text — unset Registry name (e.g. ‘dsbowl-unet’) or a path to a local checkpoint.
fit
true or false — true Set false to load weights and skip training.
architecture
yolo3 — yolo3 Which detector. YOLOv3 is the only one, and it is 2D: boxes in a volume are a different problem with different annotations, so a detection specification at rank 3 is refused while the specification is read rather than failing later. Adopted from the weights file when weights names one, together with the anchors - weights mean nothing without the anchors they were trained with, so a specification naming both is refused.
anchors
a list of a list of lists — unset The box shapes the model predicts offsets from, as fractions of the input size, one group per grid from coarsest to finest. Unset fits them to the training annotations with k-means under an IoU distance, which is what you want: measured on BCCD’s 2,805 training boxes, COCO’s anchors cover them at a mean IoU of 0.65 against 0.88 for anchors fitted to them. Fitted anchors are recorded with the run, since a detector cannot be reloaded without them.
anchors_per_grid
a whole number — 3 How many anchors to fit per grid, when anchors is not given. It also sets the head’s width, which is anchors_per_grid * (n_class + 5).
ignore_threshold
a number — 0.5 A cell that is not responsible for an object but whose box overlaps one this much is left unsupervised rather than trained towards ‘no object’. Without it a nearly-correct guess beside the responsible cell is punished, which is the one thing a detector should not learn.
box_loss
offsets or giou — offsets How the box coordinates are scored. offsets is YOLOv3’s own: cross-entropy on the two cell offsets and squared error on the two log sizes, each weighted by 2 - w*h so a small object is not worth less than a large one. giou scores the decoded box against the true box directly, as 1 - GIoU. The reason to prefer giou is that it can reach zero. Cross-entropy against a soft target bottoms out at that target’s entropy, so the offsets term is still 4.21 on a prediction that is exactly right - measured on three boxes - and a converged run cannot be told from a stalled one by reading it. The same prediction scores 0.000000 under giou. It is not the better objective here, and that is measured. BCCD, three seeds each, 150 epochs, its own test split:
            mAP@0.5          mAP@COCO         matched IoU
giou        0.8666 +-0.0147  0.5078 +-0.0083  0.7929 +-0.0069
offsets     0.8583 +-0.0167  0.5172 +-0.0195  0.8038 +-0.0001
Average precision cannot separate them in either direction - both gaps are inside the noise. How well the matched boxes fit can, and giou is worse by 0.0109, which is 2.7 standard errors of the difference. It is also far less repeatable on exactly the quantity it targets: offsets gave 0.803853, 0.803656 and 0.803861 across its seeds, sixty times tighter than giou. The reason is in the same run’s own numbers. GIoU exists because IoU is a flat zero for boxes that do not touch, so it has no gradient where a detector is most wrong - but anchors fitted by k-means already cover BCCD’s truths at a mean IoU of 0.877, so predictions never start disjoint and the advantage never arrives while the cost does. Where fitted anchors cover poorly it should be a different story, which is untested here and therefore not claimed. So choose giou to make the objective readable, not to raise the score: at 150 epochs its coordinate term sits at 0.05 on the training split against 0.47 on validation, which is a localisation gap you can see, while offsets reads 5.92 and 6.85 against a floor of 5.891 and tells you nothing. On BCCD that legibility costs about 0.011 of matched IoU.
score_threshold
a number — 0.01 Discard a predicted box below this confidence. Low on purpose: average precision is computed over the whole ranking, so a cut here is a ceiling on recall that no threshold chosen later can lift.
nms_threshold
a number — 0.45 Two boxes of the same class overlapping this much are one object, and the lower-scoring one is dropped. Per class, so a platelet sitting on a red cell survives.
operating_point
a number — 0.5 The confidence at which precision and recall are reported. Separate from score_threshold because they answer different questions: that one decides what is computed at all, this one decides where on the curve to stand.
min_visibility
a number — 0.25 How much of a box must survive an augmentation that removes part of the frame, as a fraction of its original area, for the box to be kept. A convention rather than a measurement; what is defensible is the direction. A box keeping two pixels of a cell teaches the model that a two-pixel fragment is a whole cell, which produces false positives everywhere, while dropping a heavily truncated object only fails to teach it about that object. Pascal VOC marks such objects truncated for the same reason. Irrelevant unless a transform can lose part of the frame: flips and rotations never do.

augmentation: inside object detection models:

name
text — required An albumentations transform, by its own class name - HorizontalFlip, RandomBrightnessContrast. Checked against what the installed albumentations offers, and available_transforms() lists them; at rank 3 the list is shorter, because support for volumes is uneven.
params
a mapping — optional Arguments passed to that transform, exactly as albumentations names them - so p is the probability of applying it, defaulting to the transform’s own. Not validated here: they are albumentations’ to accept, and each step is probed at the model’s input size when the pipeline is built, so a bad argument is named there rather than guessed at here.

Components

Each of these is named by its name: and carries its own fields.

Losses

boundary

A region loss plus a term that knows how far a wrong voxel is from the truth.

name
text — boundary Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
region
one of cce, cce_dice, combo, dice, focal, focal_tversky, generalized_dice, iou, lovasz, tversky — optional The overlap term this is added to. Dice unless you say otherwise.
alpha
a number — 0.9 How much of the objective is the region term, 1 - alpha the surface term. 0.9 because 0.5 was measured and is unusable. Three seeds on lesions with ambiguous edges, 60 epochs:
                 Dice             volume bias       volume |error|
dice alone       0.9525 ±0.0027   -5.18% ±3.11%     16.90% ±0.90%
boundary a=0.5   0.9164 ±0.0240  +32.22% ±28.28%    42.95% ±19.12%
boundary a=0.9   0.9411 ±0.0093   -0.06% ±4.15%     22.17% ±2.88%
At 0.5 the surface term has half the objective while the prediction is still random, which is where it is known to be unstable - the seed spread is six times the baseline’s and every number is worse. Kervadec schedules this downwards from near 1 rather than fixing it; 0.9 is the closest a single number comes, and the spec deliberately has no schedule to hide behind. Excludes both ends: at 1 the surface term is absent and the name is a lie, and at 0 nothing measures how much of the object was found.

cce

Per-pixel classification, with no notion of the object.

name
text — cce Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
label_smoothing
a number — 0.0 Train towards 1 - this value instead of towards 1, so the model is not pushed to be certain. Worth having where the labels themselves are uncertain, which on a hand-drawn boundary they are.

cce_dice

The blend to start a segmentation run with.

name
text — cce_dice Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
cce_weight
a number — 0.5 How much of the objective is cross-entropy; the rest is Dice. Cross-entropy cares about every pixel and Dice about the overlap, so raising this helps a class that covers very little of the image and lowering it favours the shape.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

combo

A lopsided cross-entropy blended with Dice.

name
text — combo Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
alpha
a number — 0.5 How much of the objective is the weighted cross-entropy term; the rest is Dice.
ce_ratio
a number — 0.5 Within the cross-entropy term, how much weight goes on the positive class. This is the key that makes combo worth choosing over cce_dice: it lets the two halves of the objective disagree about which class matters.

dice

Overlap, and nothing about where the error sits.

name
text — dice Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

focal

Cross-entropy that loses interest in the voxels it already gets right.

name
text — focal Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
gamma
a number — 2.0 How hard to discount the pixels already classified well, so the gradient comes from the ones that are not. 0 is plain cross-entropy; 2 is the usual value.
alpha
a number — unset Weight on the positive class, between 0 and 1. Unset means no weighting, which is not the same as 0.5: at 0.5 both classes are halved and the gradients are smaller, which looks like a learning-rate change.

focal_tversky

Tversky, with the same loss of interest in what is already right.

name
text — focal_tversky Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
alpha
a number — 0.5 Weight on false negatives, as in tversky, with false positives taking 1 - alpha.
gamma
a number — 1.0 Exponent on the Tversky index, which is where this differs from tversky: above 1 it concentrates the gradient on the cases scoring badly. Note 1 is the default and means plain Tversky, not ‘off’.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

generalized_dice

Dice with every class weighted by the inverse square of how much of the picture it occupies, so a rare class counts for more than an equal share rather than for less.

name
text — generalized_dice Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

iou

Dice’s harsher twin: the same ordering, a lower number.

name
text — iou Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

l1

Absolute error on a continuous target.

where
foreground or everywhere — foreground Where the target means anything, and so where the error is averaged. A continuous instance target is defined on the objects and undefined off them - there is no direction towards the centre for a pixel in no object - so the builder writes zero on the background. Measured on the 536 Data Science Bowl cases: the foreground is 13.97% of the pixels at 256x256 (13.48% at native size, 0.03% to 59.2% per case, 167 cases under 5%), so everywhere spends 86% of the objective on a value a single output bias satisfies exactly. foreground divides by the mask’s own total, which makes the number mean average error per object voxel. everywhere is the other defensible answer and is what CellPose and HoVer-Net’s plain regression terms do - their background zero is a real value rather than a missing one. Both are offered and neither is guessed; foreground is the default because it is the reading whose number means something. foreground asks the loader for the mask, which is the same mechanism boundary uses for its distance map. It is never read off the target: a flow field is exactly zero at every object’s centre, so a mask taken from the non-zero voxels would drop precisely the seeds - and the seeds are what limits this approach (pyplatypus#211).
name
text — l1 Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.

lovasz

Optimise the overlap you are going to report, rather than a proxy for it.

name
text — lovasz Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
per_image
true or false — false Compute the loss for each image and average, rather than over the whole batch at once. Per image is the honest thing when images differ in how much of them is foreground, because otherwise a large object in one image dominates the batch; it is also noisier.

mse

Squared error on a continuous target.

where
foreground or everywhere — foreground Where the target means anything, and so where the error is averaged. A continuous instance target is defined on the objects and undefined off them - there is no direction towards the centre for a pixel in no object - so the builder writes zero on the background. Measured on the 536 Data Science Bowl cases: the foreground is 13.97% of the pixels at 256x256 (13.48% at native size, 0.03% to 59.2% per case, 167 cases under 5%), so everywhere spends 86% of the objective on a value a single output bias satisfies exactly. foreground divides by the mask’s own total, which makes the number mean average error per object voxel. everywhere is the other defensible answer and is what CellPose and HoVer-Net’s plain regression terms do - their background zero is a real value rather than a missing one. Both are offered and neither is guessed; foreground is the default because it is the reading whose number means something. foreground asks the loader for the mask, which is the same mechanism boundary uses for its distance map. It is never read off the target: a flow field is exactly zero at every object’s centre, so a mask taken from the non-zero voxels would drop precisely the seeds - and the seeds are what limits this approach (pyplatypus#211).
name
text — mse Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.

smooth_l1

mse close to the truth and l1 far from it, switching at beta.

where
foreground or everywhere — foreground Where the target means anything, and so where the error is averaged. A continuous instance target is defined on the objects and undefined off them - there is no direction towards the centre for a pixel in no object - so the builder writes zero on the background. Measured on the 536 Data Science Bowl cases: the foreground is 13.97% of the pixels at 256x256 (13.48% at native size, 0.03% to 59.2% per case, 167 cases under 5%), so everywhere spends 86% of the objective on a value a single output bias satisfies exactly. foreground divides by the mask’s own total, which makes the number mean average error per object voxel. everywhere is the other defensible answer and is what CellPose and HoVer-Net’s plain regression terms do - their background zero is a real value rather than a missing one. Both are offered and neither is guessed; foreground is the default because it is the reading whose number means something. foreground asks the loader for the mask, which is the same mechanism boundary uses for its distance map. It is never read off the target: a flow field is exactly zero at every object’s centre, so a mask taken from the non-zero voxels would drop precisely the seeds - and the seeds are what limits this approach (pyplatypus#211).
name
text — smooth_l1 Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
beta
a number — 1.0 Where the quadratic part ends and the linear one begins, in the target’s own units. For a flow field scaled to roughly [-1, 1] the default makes the whole range quadratic, so lower it to get any of the robustness this loss is for.

tversky

Dice with a thumb on the scale: miss less, or over-segment less.

name
text — tversky Which loss, and it decides which other keys this block accepts: tversky takes alpha, focal takes gamma, and giving a key the chosen loss does not have is refused by name rather than ignored.
alpha
a number — 0.5 Weight on false negatives; raising it buys recall at the cost of precision, which is the trade a screening task usually wants. The weight on false positives is 1 - alpha and is not a separate key, so the two cannot disagree. At alpha 0.5 this is Dice exactly, but only when smooth is 0.
smooth
a number — 1.0 Added to both numerator and denominator so a class absent from an image gives a finite number instead of 0/0. Leave it alone unless you know why you are moving it: raising it makes an empty class look better than it is.

Metrics

cldice

Whether the structure is connected, rather than whether the voxels coincide.

smooth
a number — 1.0 Zero is allowed here, unlike in the losses. Smoothing inflates a reported score: a class absent from an image scores a perfect 1.0 with smooth>0. Use 0 for an honest number, at the cost of NaN on absent classes.
include_background
true or false — false False here where the other metrics default to True, and not for the usual reason. The background’s skeleton lies inside the background by construction, so that class scores 1.0 whatever the model did: measured on a 3-pixel vessel, averaging it in moved 0.7865 to 0.8933. It is not a flattering average, it is a number with no meaning in it.
name
text — cldice Centreline Dice, which measures whether a structure is connected rather than whether its pixels coincide. A skeleton is a property of a whole mask, so on a tiled run (splits) it is reported per case by evaluate_cases, which reassembles a case’s tiles before measuring it. It is absent from the epoch and from the history there, because a per-batch average of a per-tile skeleton would be a number about nothing - which also means a callback cannot watch val_cldice on a tiled run, and the specification says so when it is read.
iterations
a whole number — 5 How many times the skeleton is peeled. It has to reach the half-width of the thickest structure or its core is never reduced to a centreline, and five covers a ten-pixel object. A retinal vessel is about nine pixels wide in the data - measured on FIVES as mask area over skeleton length, not the one to five often quoted, which is what a naive resize leaves behind - so five is enough for vessels and not much more than enough. Raise it for something thicker, at a cost linear in the number; too few is reported rather than scored, because an empty skeleton otherwise reads as a perfect 1.0.

dice

The number to report unless there is a reason not to.

smooth
a number — 1.0 Zero is allowed here, unlike in the losses. Smoothing inflates a reported score: a class absent from an image scores a perfect 1.0 with smooth>0. Use 0 for an honest number, at the cost of NaN on absent classes.
include_background
true or false — true Average over every class, background included. In medical images the background is often 95% of the pixels, so leaving it in flatters the score; papers usually report foreground only. Set false for that.
name
text — dice Which metric. These are computed on the mask rather than on the objective, so they stay comparable between models trained on different losses.

iou

The same ordering as Dice, on the scale papers outside medicine use.

smooth
a number — 1.0 Zero is allowed here, unlike in the losses. Smoothing inflates a reported score: a class absent from an image scores a perfect 1.0 with smooth>0. Use 0 for an honest number, at the cost of NaN on absent classes.
include_background
true or false — true Average over every class, background included. In medical images the background is often 95% of the pixels, so leaving it in flatters the score; papers usually report foreground only. Set false for that.
name
text — iou Which metric. These are computed on the mask rather than on the objective, so they stay comparable between models trained on different losses.

surface_dice

How much of each boundary lies within a tolerance of the other, rather than how much of the volume coincides.

smooth
a number — 1.0 Zero is allowed here, unlike in the losses. Smoothing inflates a reported score: a class absent from an image scores a perfect 1.0 with smooth>0. Use 0 for an honest number, at the cost of NaN on absent classes.
include_background
true or false — false False here where dice and iou default to True, and for a measured reason rather than the usual one. The background’s boundary is the foreground’s boundary plus the edge of the picture, and that edge agrees trivially between any two masks - so the background class reads high whatever the model did. Measured on four random pairs at tau 2: background 0.6110 to 0.7030 against foreground 0.5583 to 0.6266, higher every time, by 0.05 to 0.12. That is agreement on the field of view rather than on the object.
name
text — surface_dice Normalised surface Dice: the fraction of each boundary lying within tau of the other. A surface is a property of a whole mask, so on a tiled run (splits) it is reported per case by evaluate_cases, which reassembles a case’s tiles first, and it is absent from the epoch history - a per-batch average of per-tile boundaries would be a number about nothing.
tau
a number — required The tolerance, and it is required because no default is honest: a tolerance belongs to the structure and the dataset, and is conventionally the inter-observer variability for that structure - the distance two people drawing the same boundary differ by. Published values exist per organ and per challenge; take one from the protocol you are comparing against rather than from here. In millimetres when target_spacing puts the data on a common grid, and in voxels when it does not.

tversky

A score that can be asked to care more about what was missed.

smooth
a number — 1.0 Zero is allowed here, unlike in the losses. Smoothing inflates a reported score: a class absent from an image scores a perfect 1.0 with smooth>0. Use 0 for an honest number, at the cost of NaN on absent classes.
include_background
true or false — true Average over every class, background included. In medical images the background is often 95% of the pixels, so leaving it in flatters the score; papers usually report foreground only. Set false for that.
name
text — tversky Which metric. These are computed on the mask rather than on the objective, so they stay comparable between models trained on different losses.
alpha
a number — 0.5 Weight on false negatives, with 1 - alpha on false positives - the same meaning as in the Tversky loss. At 0.5 this is Dice, so it is worth moving or not reporting at all: a Tversky metric at 0.5 beside a Dice metric is one number twice.

Optimisers

adadelta

Adagrad’s idea, bounded by a window instead of a sum.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — adadelta Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
rho
a number — 0.9 How long a window of squared gradients to remember. Adadelta exists to avoid Adagrad’s ever-shrinking step, and this is the parameter that does it.
eps
a number — 1e-06 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

adagrad

Accumulates every squared gradient it has ever seen, and so slows to a stop.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — adagrad Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
lr_decay
a number — 0.0 Shrink the rate further as training goes on, on top of the shrinking Adagrad already does by accumulating every squared gradient it has seen. Adagrad’s step only ever gets smaller, which is why long runs with it can stop learning.
eps
a number — 1e-10 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

adam

The one to use unless there is a reason not to.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — adam Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
beta_1
a number — 0.9 Decay for the running average of the gradient. Lower reacts to the last few batches, higher smooths over more of them; 0.9 is the usual value and rarely worth moving.
beta_2
a number — 0.999 Decay for the running average of the squared gradient, which is what scales each parameter’s step. Lowering it below about 0.99 makes training noticeably noisier.
eps
a number — 1e-08 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.
amsgrad
true or false — false Keep the largest squared-gradient average seen so far rather than the current one, so the step size never grows back. It fixes a convergence case in Adam’s proof and usually changes nothing in practice; worth trying if a run plateaus and then gets worse.

adamax

Adam under the infinity norm, which a few huge batches cannot upset.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — adamax Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
beta_1
a number — 0.9 Decay for the running average of the gradient. Lower reacts to the last few batches, higher smooths over more of them; 0.9 is the usual value and rarely worth moving.
beta_2
a number — 0.999 As for Adam, except Adamax tracks the largest gradient seen rather than the average of their squares, which makes it the steadier of the two when a few batches carry very large gradients.
eps
a number — 1e-08 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

adamw

Adam, with weight decay that actually decays the weights.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.01 As for the other optimisers, but applied to the weights directly rather than through the gradient - which is the whole difference between AdamW and Adam, and why the default here is 0.01 rather than 0.
name
text — adamw Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
beta_1
a number — 0.9 Decay for the running average of the gradient. Lower reacts to the last few batches, higher smooths over more of them; 0.9 is the usual value and rarely worth moving.
beta_2
a number — 0.999 Decay for the running average of the squared gradient, which is what scales each parameter’s step. Lowering it below about 0.99 makes training noticeably noisier.
eps
a number — 1e-08 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

nadam

Adam with the look-ahead that sgd calls nesterov.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — nadam Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
beta_1
a number — 0.9 Decay for the running average of the gradient. Lower reacts to the last few batches, higher smooths over more of them; 0.9 is the usual value and rarely worth moving.
beta_2
a number — 0.999 Decay for the running average of the squared gradient, which is what scales each parameter’s step. Lowering it below about 0.99 makes training noticeably noisier.
eps
a number — 1e-08 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

rmsprop

Adam’s denominator without Adam’s momentum.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — rmsprop Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
alpha
a number — 0.99 Decay for the running average of the squared gradient - RmsProp’s name for what Adam calls beta_2, and it does the same work.
momentum
a number — 0.0 Carry a fraction of the previous step into this one. 0 is plain gradient descent; 0.9 is the usual choice and is what makes SGD competitive with the adaptive methods.
eps
a number — 1e-08 Added to the denominator so a parameter whose gradient has been zero for a while does not take an enormous step. It is a guard, not a knob.

sgd

The plain step, and the one that rewards being tuned.

learning_rate
a number — 0.001 How big a step to take. The one setting worth trying first when a run will not learn: too high and the loss moves without improving, too low and it improves too slowly to tell from not improving at all. Each parameter group decays from its own rate, so an encoder_learning_rate is not flattened by a schedule.
weight_decay
a number — 0.0 Pull weights towards zero at every step, which is L2 regularisation. Off by default because it interacts with the loss and is not free: on a small dataset it can cost more than the overfitting it prevents.
name
text — sgd Which optimiser, and it decides which other keys this block accepts - sgd takes momentum, the Adam family takes beta_1 and beta_2. learning_rate and weight_decay are accepted by all of them.
momentum
a number — 0.0 Carry a fraction of the previous step into this one. 0 is plain gradient descent; 0.9 is the usual choice and is what makes SGD competitive with the adaptive methods.
nesterov
true or false — false Look ahead to where momentum is about to carry the weights before measuring the gradient. Needs momentum above 0, and giving it without momentum is refused rather than ignored.

Callbacks

cosine_annealing

Decay the learning rate along a cosine, from its initial value to min_lr.

name
text — cosine_annealing Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
min_lr
a number — 0.0 The rate at the last epoch, which the cosine decays to from the optimiser’s own learning_rate. Each parameter group decays from its own initial rate, so an encoder_learning_rate is not flattened at the first epoch - that would have been invisible, since the history records one learning_rate, the first group’s.
epochs
a whole number — unset How many epochs to spread the decay over. Unset means the model’s epochs, which is what you want: a cosine that ends where the run ends. Setting it shorter parks the rate at min_lr for the remaining epochs, and longer stops the run part way down the curve.

csv_logger

The history, written as it arrives.

name
text — csv_logger Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
path
text — required Where to append one row per epoch. The same numbers fit() returns, written as they arrive - so a run that is interrupted still leaves its history behind.

early_stopping

Stop when the watched number stops improving, and keep the weights that did.

name
text — early_stopping Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
monitor
text — val_loss What to watch: val_loss, train_loss, or val_ followed by a metric the model asks for, such as val_dice. Whether the number should rise or fall is worked out from its name - anything ending in loss is minimised. Watching something no metric will produce is refused when the specification is built, and so is watching val_* in a run with validation: false, rather than waiting for a number that never arrives.
patience
a whole number — 10 Epochs to wait, with no improvement, before acting.
min_delta
a number — 0.0 Improvement smaller than this does not count as improvement. Useful where the watched number wanders: without it, noise alone keeps resetting the patience.
restore_best
true or false — true Put the best weights back when training stops, rather than keeping the last - which are by definition the ones that were not improving. Note that swa replaces nothing if a run stops before averaging began.

model_checkpoint

The run’s safety net - one file, overwritten.

name
text — model_checkpoint Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
path
text — required Where to write the checkpoint. One file, overwritten - this is the run’s safety net, not a history; export_weights() is what produces a file to publish, with a sidecar recording what it is.
monitor
text — val_loss What to watch: val_loss, train_loss, or val_ followed by a metric the model asks for, such as val_dice. Whether the number should rise or fall is worked out from its name - anything ending in loss is minimised. Watching something no metric will produce is refused when the specification is built, and so is watching val_* in a run with validation: false, rather than waiting for a number that never arrives.
save_best_only
true or false — true Write only when the watched number improves. With false every epoch is written, which is what to use when a run may be interrupted rather than when the best epoch is wanted.

reduce_lr_on_plateau

Cut the learning rate when the evidence says the run has stalled.

name
text — reduce_lr_on_plateau Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
monitor
text — val_loss What to watch: val_loss, train_loss, or val_ followed by a metric the model asks for, such as val_dice. Whether the number should rise or fall is worked out from its name - anything ending in loss is minimised. Watching something no metric will produce is refused when the specification is built, and so is watching val_* in a run with validation: false, rather than waiting for a number that never arrives.
factor
a number — 0.1 Multiply the learning rate by this when the watched number stops moving.
patience
a whole number — 5 Epochs to wait, with no improvement, before acting.
min_lr
a number — 0.0 The floor the rate will not go below. Unlike cosine_annealing, this one acts on evidence rather than on a schedule, so it may never fire at all - which is why it is not refused alongside swa and a cosine is.

swa

Average the weights over the last part of the run instead of taking the final ones.

name
text — swa Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.
start
a number — 0.75 The fraction of the run after which averaging begins - 0.75 folds the last quarter. A fraction rather than an epoch so that it survives a change to epochs, and the final epoch is always folded, so 1.0 averages one set of weights rather than none.
learning_rate
a number — unset Hold the rate at this value once averaging begins. Unset leaves whatever the run was doing. Worth setting, and the reason is the whole mechanism: averaging is only worth something while the weights are still moving. A rate that has decayed towards zero produces a set of nearly identical snapshots, and their average is the last one.

terminate_on_nan

End the run the moment the loss stops being a number.

name
text — terminate_on_nan Which callback, and it decides which other keys this block accepts. Giving a key the chosen callback does not have is refused by name rather than ignored.

Separations

flows

A unit vector field pointing at each object’s sink, plus the semantic channels.

name
text — flows Which separation signal, and it decides which other keys this block accepts: rim takes width, flows takes mode, loss and weight. A setting that belongs to the other mechanism is not refused here, it is unwritable - width under name: flows is an unknown key, which is the answer every typo in a specification gets. That is this block’s reason to be a block, and it was built flat first: separation: rim beside rim_width, flow_mode, flow_loss and flow_weight. The flat form parses, so it needs a validator and a table of which field belongs to which mechanism to refuse half of what it accepts, and it shows every reader four fields of which two can apply to them. This form shows a reader of name: rim one field, and the refusal is gone rather than written - extra="forbid" was already doing that job for every other key in the file.
mode
centroid or diffusion — diffusion How the vector field is built: towards each object’s centroid in a straight line, or down the gradient of heat diffused inside it. diffusion is the default because the two were measured on the ground truth before anything was trained: decoded back into objects they reach 0.9924 and 0.9478. The obvious explanation for the gap is not the cause - only 19 of 6000 objects have their centroid outside themselves, 0.32% - and the real one is one level down: a straight line between two pixels of a discrete set can leave the set even when the object looks convex, which splits an object whose own centroid is inside it. centroid is 23 times cheaper to build (4.9 ms a case against 113.6 ms), which is the reason to reach for it and is small beside an epoch.
loss
one of boundary, cce, cce_dice, combo, dice, focal, focal_tversky, generalized_dice, iou, l1, lovasz, mse, smooth_l1, tversky — unset The objective on the two vector channels, which is a different question from the model’s own loss - that one scores the mask. A continuous target has nothing in the overlap-and-cross-entropy losses to be trained against, so this is one of mse, l1 or smooth_l1, and anything else is refused by name: loss: dice against a vector field would return a number and a gradient, which is the worst available outcome. Left unset it is mse with where: foreground, which is what the measured run used. where is the field to read before changing it, because the foreground is 13.97% of a Data Science Bowl image and everywhere spends 86% of the objective on a background zero.
weight
a number — 1.0 How much the vector term counts against the mask term, which are summed. The default is 1.0 and it is measured rather than conventional: on the 60-epoch run the two terms are the same order of magnitude throughout - 0.5937 against 0.6345 after the first epoch and 0.0734 against 0.0659 at the last - so neither half of the objective is drowning the other at this weight.

rim

Background, object interior, object rim - three classes, one extra channel.

name
text — rim Which separation signal, and it decides which other keys this block accepts: rim takes width, flows takes mode, loss and weight. A setting that belongs to the other mechanism is not refused here, it is unwritable - width under name: flows is an unknown key, which is the answer every typo in a specification gets. That is this block’s reason to be a block, and it was built flat first: separation: rim beside rim_width, flow_mode, flow_loss and flow_weight. The flat form parses, so it needs a validator and a table of which field belongs to which mechanism to refuse half of what it accepts, and it shows every reader four fields of which two can apply to them. This form shows a reader of name: rim one field, and the refusal is gone rather than written - extra="forbid" was already doing that job for every other key in the file.
width
a whole number — 2 How many pixels wide the rim class is, in pixels of the model’s own input - so it means the same thing whether the image was resized or tiled, because the target is built after the resize and the augmentation rather than before (pyplatypus#210). There is a measured sweep of this quantity and it is not a sweep of this field, which is worth knowing before quoting either. Four runs differing in nothing else scored 0.5169, 0.5486, 0.5587 and 0.5306 of instance average precision at widths 1 to 4, against a 0.0074 spread between two runs of one configuration - but that pipeline derived the rim at the source resolution and resized the three-class map afterwards. A Data Science Bowl case is around 520 pixels and the model reads 256, so the peak of that sweep is roughly 1.5 pixels of model input, not 3. The default is the nearest width above it. A sweep in these units has not been run. What that sweep does establish is the shape of the trade, which is the part that transfers: a rim wide enough to learn is wide enough to hollow the object out, so rim Dice climbed 0.48 to 0.76 across those widths while interior Dice fell 0.90 to 0.81, and past the peak the model found more regions and matched fewer of them. Both halves are reported per run, because a single instance number cannot say which of them moved.

Decodings

connected_components

Label the thresholded foreground and ignore the separation signal entirely.

name
text — connected_components Which decoding, and it decides which other keys this block accepts - follow_flows takes steps and step_size, the others do not. A decoding the chosen separation cannot feed is refused by name with both halves stated, rather than returning a label map of something else.
minimum_area
a whole number — 0 Drop an object smaller than this many pixels. 0 keeps everything, which is the default because the right floor is a property of the data rather than of the method - a nucleus at 256x256 in the Data Science Bowl runs from 4 pixels up, so a floor chosen here would silently delete the smallest real objects in one dataset while letting speckle through in another. It is applied after the objects are formed, so it removes answers rather than changing how they were reached.

follow_flows

Follow the field from every foreground pixel and label the basins they arrive in.

name
text — follow_flows Which decoding, and it decides which other keys this block accepts - follow_flows takes steps and step_size, the others do not. A decoding the chosen separation cannot feed is refused by name with both halves stated, rather than returning a label map of something else.
minimum_area
a whole number — 0 Drop an object smaller than this many pixels. 0 keeps everything, which is the default because the right floor is a property of the data rather than of the method - a nucleus at 256x256 in the Data Science Bowl runs from 4 pixels up, so a floor chosen here would silently delete the smallest real objects in one dataset while letting speckle through in another. It is applied after the objects are formed, so it removes answers rather than changing how they were reached.
steps
a whole number — 200 How many Euler steps each pixel takes. Measured as not mattering: one step scores 0.9914 against 0.9923 converged, a span below the 0.0013 two runs differ by. The field is continuous, so one object’s pixels stay together, and it diverges at a seam, so the gap between two objects opens on the first step rather than after many. Left adjustable because that argument is about this data’s object sizes, and lowering it is the cheap thing to try on larger ones.
step_size
a number — 1.0 How far one step moves, in pixels, on a unit-length field. This one is a cliff: 0.9923 at 1.0, 0.4715 at 2.0, 0.0303 at 10.0. A step longer than a pixel jumps over the sink it was converging on and lands in a neighbour’s basin, so this is not a speed knob - raise it and the decoding stops working.

nearest_seed

Grow the predicted interiors outward: every other foreground pixel joins the nearest.

name
text — nearest_seed Which decoding, and it decides which other keys this block accepts - follow_flows takes steps and step_size, the others do not. A decoding the chosen separation cannot feed is refused by name with both halves stated, rather than returning a label map of something else.
minimum_area
a whole number — 0 Drop an object smaller than this many pixels. 0 keeps everything, which is the default because the right floor is a property of the data rather than of the method - a nucleus at 256x256 in the Data Science Bowl runs from 4 pixels up, so a floor chosen here would silently delete the smallest real objects in one dataset while letting speckle through in another. It is applied after the objects are formed, so it removes answers rather than changing how they were reached.

watershed

Flood the foreground from the predicted interiors, over the rim evidence as terrain.

name
text — watershed Which decoding, and it decides which other keys this block accepts - follow_flows takes steps and step_size, the others do not. A decoding the chosen separation cannot feed is refused by name with both halves stated, rather than returning a label map of something else.
minimum_area
a whole number — 0 Drop an object smaller than this many pixels. 0 keeps everything, which is the default because the right floor is a property of the data rather than of the method - a nucleus at 256x256 in the Data Science Bowl runs from 4 pixels up, so a floor chosen here would silently delete the smallest real objects in one dataset while letting speckle through in another. It is applied after the objects are formed, so it removes answers rather than changing how they were reached.