Winning Kaggle the Reproducible Way

Interactive chapter

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

Chapter 42: Image Augmentation

Try the decision first

When does adding transformed copies of the training images raise held-out accuracy, and when does it lower it?

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

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

Open the chapter activity

Worked notebook and saved results

When does adding transformed copies of the training images raise held-out accuracy, and when does it lower it?

A default augmentation stack can silently teach the model something false. The chapter asks for a label-preservation check per transform and for every policy to be compared with an unaugmented baseline on unchanged assessment images.

Real small images and a linear model. Shifts hurt here, which would likely not hold for larger images or convolutional networks; the mirrored variant is constructed so that the flip is valid, and the result says nothing about natural photographs. The size of each gain and loss moves noticeably between random draws; the signs did not.

The experiment

scikit-learn's bundled digits dataset (1,797 real 8 x 8 grayscale scans). The control is the number of training images (100, 300 or 900); 500 other images are the test set. A multinomial logistic regression is trained on the originals alone, or on the originals plus one transformed copy of each: an unchanged duplicate (control for doubling the rows), a random shift of up to one pixel, a left-right flip, or a half-turn. This is run on the digits as scanned, and on a constructed variant in which each image, in training and test alike, is mirrored with probability one half and keeps its label. Results are means over 12 random splits.

Calculation code
import warnings

import numpy as np
from sklearn.datasets import load_digits
from sklearn.linear_model import LogisticRegression

from kaggle_companion.activities._common import clean

warnings.filterwarnings("ignore")      # lbfgs convergence notices on tiny training sets
SEED = 42
REPLICATES = 12          # independent train/test splits per training size; results are means over them
TEST_ROWS = 500
POLICIES = ("none", "duplicate", "shift", "hflip", "rot180")


def shifted(image, rng):
    """Move the 8 x 8 image by -1, 0 or +1 pixel in each direction, padding with zeros."""
    dx, dy = rng.integers(-1, 2, 2)
    out = np.zeros_like(image)
    src = image[max(0, -dy):8 - max(0, dy), max(0, -dx):8 - max(0, dx)]
    out[max(0, dy):max(0, dy) + src.shape[0], max(0, dx):max(0, dx) + src.shape[1]] = src
    return out


TRANSFORMS = {"duplicate": lambda im, rng: im,                 # control: same rows twice, no change to the picture
              "shift": shifted,
              "hflip": lambda im, rng: im[:, ::-1],            # mirror left to right
              "rot180": lambda im, rng: im[::-1, ::-1]}        # turn upside down (a 6 becomes a 9)


def fit_predict(images, labels, test_images, policy, rng):
    """Train on the originals plus one transformed copy of each (or on the originals alone) and predict the test images."""
    if policy != "none":
        copies = np.stack([TRANSFORMS[policy](im, rng) for im in images])
        images, labels = np.concatenate([images, copies]), np.concatenate([labels, labels])
    model = LogisticRegression(max_iter=300).fit(images.reshape(len(images), -1), labels)
    return model.predict(test_images.reshape(len(test_images), -1))


def run(training_images):
    digits = load_digits()
    pictures, labels = digits.images / 16.0, digits.target     # 1,797 real 8 x 8 grayscale scans, scaled to [0, 1]
    accuracy = {v: {p: [] for p in POLICIES} for v in ("scanned", "mirrored")}
    swapped = {p: [] for p in ("none", "rot180")}              # share of true 6s and 9s predicted as the other one
    for r in range(REPLICATES):
        rng = np.random.default_rng(SEED * 100 + r)
        order = rng.permutation(len(labels))
        test, train = order[:TEST_ROWS], order[TEST_ROWS:TEST_ROWS + training_images]
        mirror = rng.random(len(labels)) < 0.5
        variants = {"scanned": pictures,                                                   # digits as scanned: orientation carries the label
                    "mirrored": np.where(mirror[:, None, None], pictures[:, :, ::-1], pictures)}   # constructed: half the images mirrored, labels unchanged
        for variant, images in variants.items():
            for policy in POLICIES:
                pred = fit_predict(images[train], labels[train], images[test], policy, np.random.default_rng(r))
                accuracy[variant][policy].append(float((pred == labels[test]).mean()))
                if variant == "scanned" and policy in swapped:
                    six_nine = np.isin(labels[test], (6, 9))
                    swapped[policy].append(float((pred[six_nine] == 15 - labels[test][six_nine]).mean()))
    mean = {v: {p: float(np.mean(a)) for p, a in d.items()} for v, d in accuracy.items()}
    return clean({"training_images": training_images, "accuracy": mean, "swapped_six_nine": {p: float(np.mean(a)) for p, a in swapped.items()},
                  "replicates": REPLICATES, "test_images": TEST_ROWS})

Measure it across the control

The control is training images (before augmentation). Each value below runs the whole experiment from the generator up.

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

VALUES = [100, 300, 900]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                             100    300    900
No augmentation, scanned   0.882  0.933  0.960
No augmentation, mirrored  0.749  0.862  0.906
Flip, scanned              0.840  0.893  0.922
Flip, mirrored             0.840  0.893  0.922
Half-turn, scanned         0.786  0.859  0.890
6/9 swapped by half-turn     20%    11%    10%

Look at it

The figure shows the default setting, training images (before augmentation) = 100.

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

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

What the result says

With 100 training images, a left-right flip changes accuracy by 0.840 - 0.882 = -0.042 on digits as scanned and by 0.840 - 0.749 = +0.091 on digits in random mirror orientation. A one-pixel shift changes it by -0.032 and -0.043, and a half-turn by -0.096 and -0.111. The half-turn makes 20% of true 6s and 9s be predicted as the other digit, against 0% without augmentation. An unchanged duplicate of every image changes accuracy by +0.003.

Apply this to your competition

Book location: Chapter 42, Decide Which Changes Preserve the Target.

Technical corrections

Apply a lesson from this chapter

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

Reject augmentations that alter relevant image semantics

Conditions

  • image augmentation may violate the target distribution

Action

  • Proposed conditional application: Audit augmentation-specific errors and reconsider intensity inversion after it causes test failures.

Check

  • Proposed check: Remove inversion alone in matched runs; inspect polarity-specific failures and instance metric.

Limits

  • Inversion may be valid in other acquisition settings; no isolated numeric harm supplied.

Find source links and value grades in the lesson atlas

Match preprocessing to each pretrained ensemble member

Conditions

  • a vision ensemble mixes backbones with different training normalization and input sizes

Action

  • Proposed: bind each model checkpoint to its own normalization and input size rather than imposing one common transform.

Check

  • Proposed: verify checkpoint training contracts and compare normalization against the intended model metadata; assert shared output class and row order.

Limits

  • Twelve crop/flip views are built for each member; utils.predict_tta aggregation internals are outside this packet, so its averaging rule is not inferred.

Find source links and value grades in the lesson atlas

Preserve borders in composite detection augmentation

Conditions

  • mosaic cropping can remove important object-border information

Action

  • Proposed conditional application: Build four-image mosaic augmentation that retains borders and transforms boxes consistently.

Check

  • Proposed check: Visualize transformed boxes at all seams and compare border-object AP against ordinary mosaic on source holdouts.

Limits

  • Geometry missing from text; stitching can create unrealistic border context.

Find source links and value grades in the lesson atlas

Balance augmentation against class evidence

Conditions

  • cropping or lighting can remove or change the visual cues defining image classes

Action

  • Proposed: compare augmentation strength on a fixed split and inspect transformed examples for label preservation.

Check

  • Proposed: inspect damaged regions under each transform and compare fixed-split validation across mild, standard and reduced augmentation.

Limits

  • Winner-owned failed Experiment 3 was not submitted. Faster apparent overfit after strong augmentation reduction is qualitative; reduced augmentation is not shown to improve accuracy.

Find source links and value grades in the lesson atlas

Budget image crop expansion against lost content

Conditions

  • fixed-size image networks see variable-aspect inputs

Action

  • Proposed adaptation: Compare aspect-preserving center crops with a bounded multicrop trial, inspecting lost content and correlated expansion.

Check

  • Measure held-out accuracy, retained visual cues and training time per crop policy.

Limits

  • Multicrop was not used in the final solution; center crop can discard style cues.

Find source links and value grades in the lesson atlas

Test crop-based inference against resizing

Conditions

  • source images exceed model resolution and inference runs without internet

Action

  • Compare overlapping-patch probability averaging and center-crop resizing with plain resizing.

Check

  • Measure crop coverage, held-out accuracy, inference time and memory.

Limits

  • Crop aggregation adds cost and can remove important context; no isolated crop gain is supplied.
  • Offline pretrained-asset caching is a separate reproducibility requirement.

Find source links and value grades in the lesson atlas

Vet augmentation on small models before scaling training

Conditions

  • fine-grained image classes depend on color and edge damage

Action

  • Proposed conditional application: Compare safe flips/rotations with limited crops and lighting changes on small models before adopting them for larger ensembles.

Check

  • Proposed check: Compare candidate augmentations on fixed splits using classwise confusion; rerun shortlist on final backbone.

Limits

  • Small-model ranking may not transfer; clipping/lighting can erase target cues.

Find source links and value grades in the lesson atlas

Randomize valid molecular strings for training and median TTA

Conditions

  • a molecular text model is sensitive to equivalent string serialization

Action

  • Proposed adaptation of source observation: Generate chemically equivalent randomized SMILES and compare training augmentation and median TTA separately.

Check

  • Proposed verification: Verify molecular identity, including stereochemistry, and score/cost as view counts change.

Limits

  • Training gain was not carefully ablated; altering stereochemistry can change molecule semantics and should not be copied blindly.

Find source links and value grades in the lesson atlas

Assess test-time augmentation on labeled holdout data

Conditions

  • TTA adds inference cost and may alter classification behavior

Action

  • Proposed: measure ordinary and augmented prediction accuracy on the same validation loader before adopting TTA.

Check

  • Proposed: compare per-class errors and inference runtime on a fixed independent holdout and ensure all ensemble outputs share row order.

Limits

  • Different ensemble members use different split seeds, so their scores are not directly comparable across members. The reported 81.6% public accuracy cannot isolate TTA or augmentation gain.

Find source links and value grades in the lesson atlas

Compare Mixup and CutMix instead of assuming either wins

Conditions

  • both Mixup and CutMix are plausible image augmentations

Action

  • Proposed recipe: compare them rather than treating the more elaborate method as automatically better.

Check

  • Proposed check: Compare Mixup and CutMix on identical folds and training budgets, tuning each strength and inspecting class-wise errors.

Limits

  • Source tested one recipe family; CutMix strength and image semantics can change the outcome.

Find source links and value grades in the lesson atlas

Use deterministic prediction transforms for a pretrained grayscale ensemble

Conditions

  • grayscale CT images feed three-channel pretrained backbones

Action

  • Proposed conditional application: Replicate grayscale input to three channels, apply matched normalization, and reserve geometric and appearance augmentation for training.

Check

  • Proposed check: Assert repeated validation transforms identical; inspect anatomy-preserving augmentation and compare pretrained input alternatives.

Limits

  • Code establishes implementation only; three-channel replication adds no image information and medical invariances need validation.

Find source links and value grades in the lesson atlas

Test biological augmentation invariances instead of copying image defaults

Conditions

  • neural responses can change under flips or interpolation

Action

  • Proposed conditional application: Restrict augmentation to tested saturation changes and separately assess flips and rotations.

Check

  • Proposed check: Test each transform separately on repeated held-out images and inspect whether the biological response target should remain unchanged.

Limits

  • Tiny evaluation does not establish biological invariance; rotation artifacts are the author's suggested explanation.

Find source links and value grades in the lesson atlas

Evaluate landmark corruption and decoder-input augmentations separately

Conditions

  • landmark-to-text models need task-specific regularization

Action

  • Proposed adaptation of source observation: Test anatomical landmark dropout and decoder-input masking as separate augmentations, then compare with the unchanged model.

Check

  • Proposed verification: Run paired ablations under the same split and include sequence length and decoding budget.

Limits

  • The listed ablations are explicitly rough and not necessarily independent; transformation validity must be checked.

Find source links and value grades in the lesson atlas