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 58: Sensor Embeddings and Augmentation

Try the decision first

When does a left-right swap help a wearable activity classifier, and what does it cost when the label is not updated with the swap?

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 a left-right swap help a wearable activity classifier, and what does it cost when the label is not updated with the swap?

A swap is a hypothesis about symmetry. It is a free source of examples for rare handed classes when the label is updated with the geometry, and a source of wrong labels when it is not. Scores must come from people the model has not seen.

Constructed data in which the mirror assumption holds exactly. The large gain at 0 and 1 people comes from a class that is nearly absent from the training set; with plenty of left-hand data the swap is close to neutral.

The experiment

Constructed wrist-sensor recordings: two three-axis units (left and right, mounted as mirror images), 32 Hz, 2 s windows with 50% overlap. Four activities: rest, walk (arms swing in antiphase), wave with the right hand and wave with the left hand. People differ in sensor gain and offset and in walking and waving style. A random forest on window summaries (mean, std, min, max, spectral peak per sensor, left-right correlation) is trained on 12 people, of whom only the number set by the control recorded the left-hand wave, and scored by macro F1 on 20 unseen people who recorded all four activities. The swap exchanges the two units and flips the lateral axis. It is applied three ways: not at all, with waving relabelled to the other hand, and with the original label kept.

Calculation code
import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
from sklearn.model_selection import GroupKFold, KFold

from kaggle_companion.activities._common import clean

SEED = 58
REPLICATES = 6                     # independent constructed cohorts; every estimate is their mean
TRAIN_SUBJECTS, TEST_SUBJECTS = 12, 20
RATE, WINDOW, STRIDE, RECORDING = 32, 64, 32, 320   # 32 Hz, 2 s windows with 50% overlap, 10 s recordings
NOISE = 0.6
CLASSES = ["rest", "walk", "wave_right", "wave_left"]
MIRROR = {0: 0, 1: 1, 2: 3, 3: 2}  # what a left-right swap does to each label: waving with one hand becomes waving with the other


def new_subject(rng):
    """Per-person differences: sensor gains and offsets, walking and waving style."""
    return {"gain": np.exp(rng.normal(0, 0.2, 2)), "bias": rng.normal(0, 0.4, (2, 3)), "swing": rng.uniform(1.0, 2.0),
            "step_hz": rng.uniform(1.6, 2.4), "phase": rng.uniform(0, 6.28), "wave": rng.uniform(0.5, 1.6), "wave_hz": rng.uniform(1.5, 3.0)}


def recording(activity, subject, rng):
    """(time, sensor, axis) readings. Sensor 0 is the left wrist, 1 the right; axes are lateral x, forward y, vertical z.
    The two wrist units are mounted as mirror images, so a left-hand wave has the opposite lateral sign to a right-hand wave."""
    t = np.arange(RECORDING) / RATE
    x = np.zeros((RECORDING, 2, 3))
    if activity == 1:                                    # walk: arms swing forward and back in antiphase
        a, f, ph = subject["swing"], subject["step_hz"], subject["phase"]
        x[:, 0, 1] = a * np.sin(2 * np.pi * f * t + ph)
        x[:, 1, 1] = a * np.sin(2 * np.pi * f * t + ph + np.pi)
        x[:, :, 2] = (0.3 * a * np.sin(4 * np.pi * f * t + 0.5))[:, None]
    if activity in (2, 3):                               # wave: sideways oscillation with the arm raised to the side
        sensor, sign = (1, 1.0) if activity == 2 else (0, -1.0)
        x[:, sensor, 0] = sign * (subject["wave"] * np.sin(2 * np.pi * subject["wave_hz"] * t) + 0.8)
    return x * subject["gain"][None, :, None] + subject["bias"][None] + rng.normal(0, NOISE, x.shape)


def cohort(subjects, left_wave_subjects, rng):
    """Windows, labels and subject ids. Only the first `left_wave_subjects` people recorded the left-hand wave."""
    windows, labels, owner = [], [], []
    for i in range(subjects):
        person = new_subject(rng)
        for activity in range(4):
            if activity == 3 and i >= left_wave_subjects:
                continue
            x = recording(activity, person, rng)
            for start in range(0, RECORDING - WINDOW + 1, STRIDE):
                windows.append(x[start:start + WINDOW])
                labels.append(activity)
                owner.append(i)
    return np.array(windows), np.array(labels), np.array(owner)


def swap_wrists(windows):
    """The augmentation: exchange the two sensors and flip the lateral axis, because the units are mirrored."""
    swapped = windows[:, :, ::-1, :].copy()
    swapped[..., 0] *= -1
    return swapped


def features(windows):
    """Per sensor and axis: mean, std, min, max; per sensor: strongest spectral peak; plus left-right forward correlation."""
    centered = windows - windows.mean(axis=1, keepdims=True)
    stats = np.stack([windows.mean(1), windows.std(1), windows.min(1), windows.max(1)], axis=-1)       # (n, sensor, axis, 4)
    peak = np.abs(np.fft.rfft(centered, axis=1))[:, 1:].max(axis=(1, 3))                                # (n, sensor)
    a, b = centered[:, :, 0, 1], centered[:, :, 1, 1]
    corr = (a * b).sum(1) / np.sqrt((a ** 2).sum(1) * (b ** 2).sum(1))
    return np.column_stack([stats.reshape(len(windows), -1), peak, corr])


def forest():
    return RandomForestClassifier(60, random_state=0, n_jobs=1)


def macro_f1(y_true, y_pred, labels):
    return float(f1_score(y_true, y_pred, labels=labels, average="macro", zero_division=0))


def one_cohort(left_wave_subjects, replicate):
    rng = np.random.default_rng(SEED * 100 + replicate)
    W, y, owner = cohort(TRAIN_SUBJECTS, left_wave_subjects, rng)
    W_test, y_test, _ = cohort(TEST_SUBJECTS, TEST_SUBJECTS, rng)         # unseen people, every class recorded
    F, F_test = features(W), features(W_test)
    out = {}
    for policy in ("none", "swap_relabel", "swap_keep_labels"):
        if policy == "none":
            F_fit, y_fit = F, y
        else:
            relabel = np.array([MIRROR[k] for k in y]) if policy == "swap_relabel" else y   # the wrong version keeps the old label
            F_fit, y_fit = np.vstack([F, features(swap_wrists(W))]), np.concatenate([y, relabel])
        pred = forest().fit(F_fit, y_fit).predict(F_test)
        out[policy] = {"macro_f1": macro_f1(y_test, pred, [0, 1, 2, 3]), "left_wave_f1": macro_f1(y_test, pred, [3])}
    # Two ways to estimate the unaugmented model from the training people alone, over the classes present in training.
    seen = sorted(set(y))
    window_cv, subject_cv = np.zeros(len(y), dtype=int), np.zeros(len(y), dtype=int)
    for fit, held in KFold(3, shuffle=True, random_state=0).split(F):
        window_cv[held] = forest().fit(F[fit], y[fit]).predict(F[held])
    for fit, held in GroupKFold(3).split(F, y, owner):
        subject_cv[held] = forest().fit(F[fit], y[fit]).predict(F[held])
    out["window_cv"], out["subject_cv"] = macro_f1(y, window_cv, seen), macro_f1(y, subject_cv, seen)
    return out


def run(left_wave_subjects):
    sets = [one_cohort(left_wave_subjects, i) for i in range(REPLICATES)]
    mean = lambda f: float(np.mean([f(s) for s in sets]))
    policies = {p: {"macro_f1": mean(lambda s: s[p]["macro_f1"]), "left_wave_f1": mean(lambda s: s[p]["left_wave_f1"]),
                    "per_cohort": [s[p]["macro_f1"] for s in sets]} for p in ("none", "swap_relabel", "swap_keep_labels")}
    return clean({"left_wave_subjects": left_wave_subjects, "policies": policies,
                  "window_cv": mean(lambda s: s["window_cv"]), "subject_cv": mean(lambda s: s["subject_cv"]),
                  "replicates": REPLICATES, "train_subjects": TRAIN_SUBJECTS, "test_subjects": TEST_SUBJECTS})

Measure it across the control

The control is training people who recorded the left-hand wave (of 12). Each value below runs the whole experiment from the generator up.

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

VALUES = [0, 1, 3, 12]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                                       0      1      3     12
No augmentation                    0.650  0.900  0.940  0.956
Swap, relabelled                   0.954  0.966  0.960  0.959
Swap, label kept                   0.643  0.651  0.684  0.748
Left-wave F1, relabelled swap      0.952  0.962  0.955  0.957
Window-level CV (no augmentation)  0.990  0.987  0.989  0.980

Look at it

The figure shows the default setting, training people who recorded the left-hand wave (of 12) = 1.

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

DEFAULT = 1
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

When 1 of 12 training people recorded the left-hand wave, the model with no augmentation scores a macro F1 of 0.900 on unseen people and the relabelled swap 0.966, so 0.966 - 0.900 = 0.066 is the gain (left-wave F1 0.799 to 0.962). The swap that keeps the label scores 0.651, 0.900 - 0.651 = 0.249 below no augmentation. Window-level cross-validation on the training people reports 0.987 and person-level 0.716, against 0.900 on unseen people.

Apply this to your competition

Book location: Chapter 58, Keep Assessment Subjects Independent.

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.

Refine only the strongest coarse signal-template candidates

Conditions

  • an exhaustive fine frequency search would be costly

Action

  • Proposed conditional application: After coarse power summation, use a width-eight sinc kernel at one-eighth-bin stride around the best line for a subset of four hundred templates.

Check

  • Proposed check: Compare coarse/refined searches on untouched simulations and real noise; measure score and added runtime across frequency offsets.

Limits

  • Public-only reported comparison; top-hat STFT assumption and coarse candidate misses limit refinement.

Find source links and value grades in the lesson atlas

Mix temporal segments with their corresponding phrases

Conditions

  • landmark sequences need stronger regularization

Action

  • Proposed conditional application: Apply timewise CutMix to both sequences and phrases at the same fraction, restricting mixing to the same signer.

Check

  • Proposed check: Ablate same-signer and unrestricted CutMix on signer-held-out edit distance; inspect phrase/sequence alignment.

Limits

  • Fractional cuts need not coincide with character boundaries; signer restrictions reduce diversity.

Find source links and value grades in the lesson atlas

Concentrate 3D aggregation after cached 2D feature extraction

Conditions

  • small video-event datasets require temporal context and fast inference

Action

  • Proposed adaptation of source observation: Compare grayscale 2.5D stacks with a final temporal 3D block while caching repeated backbone features.

Check

  • Proposed verification: Evaluate held-out videos, frame alignment, color information loss and speed.

Limits

  • Larger-backbone overfitting was local; the fifteen-frame design is source-specific.

Find source links and value grades in the lesson atlas

Compare compact EEG band-power features with simple classifiers

Conditions

  • small EEG trials have meaningful frequency structure

Action

  • Proposed adaptation of source observation: Compute checked band powers and summary statistics, then compare simple classifiers before complex models.

Check

  • Proposed verification: Use participant/session-held-out splits and verify frequency limits versus sampling rate.

Limits

  • The packet gives no split protocol or isolated feature gain; disjoint validation is proposed.

Find source links and value grades in the lesson atlas

Initialize nonlinear solvers with coarse and simplified fits

Conditions

  • nonlinear transit fitting needs reliable initialization
  • nonlinear Bayesian inference needs a reasonable initial point

Action

  • Proposed adaptation of source observation: Find coarse transit parameters, refine on an averaged signal and pass them to the full solver; flag large residuals for alternative starts and frame checks.
  • Proposed conditional application: Initialize transit parameters using a depth/time grid search, fit mean-wavelength signal with BFGS, then iteratively linearize the full Bayesian solver.

Check

  • Proposed verification: Compare residuals and recovered parameters on known simulations, including cropped versus intact signals.
  • Proposed check: Compare random and staged initialization on held-out synthetic transits; measure convergence, posterior calibration and runtime.

Limits

  • Alternative-planet starts and cropping are task-specific; the cause of boundary residuals was unknown.
  • Requires a credible physical prior and nonlinear solver; local solutions and model misspecification remain.

Find source links and value grades in the lesson atlas

Combine global and per-part landmark representations

Conditions

  • landmark sequences containing hands face and pose with differing geometry

Action

  • Proposed: normalize each landmark part separately and combine its extracted features with global landmark features.

Check

  • Proposed: ablate part normalization and part feature branches while retaining the same signer-aware validation design.

Limits

  • Mean/std are per-frame and per-part; zero or missing landmarks require explicit handling. No branch ablation or gain is reported.

Find source links and value grades in the lesson atlas

Refine event timing with dense local frames and sparse context

Conditions

  • video event localization after a coarse time estimate

Action

  • Proposed: sample a dense local window plus sparse surrounding context, then sort and deduplicate timestamps.

Check

  • Proposed: compare timing error and inference runtime to coarse-only localization; inspect boundary and duplicated-frame cases.

Limits

  • This source shows an implementation, without an isolated gain, execution result, or confirmed final-submission provenance. Historical dependencies and dataset assumptions require local checks.

Find source links and value grades in the lesson atlas

Engineer CAN-message timing and byte patterns before choosing sequence models

Conditions

  • vehicle intrusion classes involve bursts and identifier-specific attacks

Action

  • Proposed adaptation of source observation: Sort messages chronologically, derive lag and byte-distribution features and compare trees with sequence baselines.

Check

  • Proposed verification: Use time-blocked validation and inspect attack-specific recall.

Limits

  • The source does not document split isolation; temporally clustered attacks can leak through random splits.

Find source links and value grades in the lesson atlas

Search the order of spectral preprocessing

Conditions

  • Spectral prediction with candidate MSC, baseline correction, smoothing and scaling steps.

Action

  • Proposed conditional application: Use the reported MSC → baseline correction → smoothing → scaling recipe as a starting candidate.
  • Proposed conditional application: Compare combinations and parameters on device data; preserve order explicitly.

Check

  • Proposed check: Fit preprocessing only on training data and compare order/parameters on held-out measurements matching intended device deployment, reporting device-wise R2.

Limits

  • No held-out-deviceCVprotocol is reported; best tested order is not universally optimal.

Find source links and value grades in the lesson atlas

Segment radar models by observation density

Conditions

  • rows have radically different numbers of scans

Action

  • Proposed conditional application: Split by scan count so high-density subsets can use temporal weather features and low-density subsets use error-code counts.

Check

  • Proposed check: Compare scan-count specialists/unified model on year-held-out CRPS; check sparse-bin support and routing.

Limits

  • Acquisition density correlates with rainfall and may shift across sensors/years.

Find source links and value grades in the lesson atlas

Switch from coherent-wave addition to power templates when phase modeling fails

Conditions

  • physical signal phase patterns exceed feasible compute

Action

  • Proposed conditional application: Sum amplitude-weighted power along Doppler-adjusted templates instead of attempting accurate complex-mode addition.

Check

  • Proposed check: Compare power and feasible phase baselines on simulated positives plus held-out real noise, stratified by slope.

Limits

  • Phase information discarded and slope coverage restricted; specialist physics and substantial compute required.

Find source links and value grades in the lesson atlas