Back to explorations

Code / Research in progress

Finding patterns in time

Adapting PULSAR for multiple signal channels. Latest experiment: a probability blend tested on four UEA datasets across five seeds.

My involvement
Multivariate extension, experiments & benchmarking
Tools & interests
Python, Multivariate PULSAR
Year
2026
Original procedural illustration. A visual interpretation of the project, not experimental data.

What I’m testing

PULSAR was built for single-channel time series. I’m testing ways to use it when a recording contains several channels. My first version joined the channels end to end. The newer version extracts features from each channel separately, then tests whether short-window relationships between channel pairs add useful information.

The channel relationship model

The input stays in (cases, channels, time) form. I calculate relationships over windows of 16, 32 and 64 samples, including small time shifts. A selector uses training data to choose up to 12 relationship groups within a 512-feature budget. The classifiers remain PULSAR’s Ridge and Extra Trees heads. Adding relationships helped Handwriting, but lost to the channel-only control on the other three datasets.

The latest completed experiment

On 16 October I finished testing a blend of the channel-only and relationship-model probabilities. The weight comes from held-out training predictions, with the full feature-selection pipeline refitted inside each validation fold. This version starts from the original relationship selector; it does not combine that model with the separate selector revision. The table below reports all four datasets, including the losses.

Meetings and next checks

I discuss the experiments in weekly meetings with Nestor. Next I want to test windows defined as a fraction of recording length and check the method on untouched data. Those are next steps, not completed results. The four test sets below have already influenced development, so they cannot provide a fresh confirmation of the method.

Latest results: probability blending.

Completed 16 October 2026. Mean test accuracy across seeds 42 to 46 on the original UEA splits. “Relationships” is the original channel relationship model; “Blend” is the latest version.

Four datasets · five seeds per dataset · accuracy (%)
DatasetChannel onlyRelationshipsBlend
SelfRegulationSCP257.78%56.22%57.67%
EthanolConcentration79.24%78.17%78.94%
Handwriting41.84%46.75%46.61%
HandMovementDirection56.49%55.95%55.68%

The blend improved on the relationship model for SelfRegulationSCP2 and EthanolConcentration, and decreased the other two scores. The channel-only control still has the higher mean on three of four datasets.

These are repeated seeds on fixed splits, not independent resamples. Handwriting’s blend validation covered 146 of 150 training cases and 24 of 26 classes. These test sets have already informed development; untouched data is needed for confirmation. Per-seed results and standard deviations are in the downloads.

The code behind the results.

The download contains my multivariate extension, probability-blending wrapper, tests and saved results. The unchanged upstream PULSAR source is included with its revision and licence. Dataset files and fitted models are separate.

Read the blend-weight selection code

Exact excerpt from blended_pulsar.py. The caller supplies held-out training predictions and labels.

def blend_probabilities(base, augmented, weight):
    """The weight is the contribution of the relationship-augmented model."""
    return (1.0-weight)*base + weight*augmented


def choose_blend_weight(base, augmented, labels, weights=(0., .25, .5, .75, 1.)):
    """Maximise pooled held-out training accuracy; ties prefer the smaller weight."""
    table = []
    for weight in sorted(set(map(float, weights))):
        pred = blend_probabilities(base, augmented, weight).argmax(axis=1)
        correct = int(np.sum(pred == labels))
        table.append({"weight": weight, "correct": correct, "cases": len(labels),
                      "accuracy": correct/len(labels)})
    best = max(table, key=lambda row: (row["correct"], -row["weight"]))
    return best["weight"], table
Read my research note

Earlier work: the concatenated baseline.

On 13 October 2026, the concatenated baseline was run on 19 UEA datasets using their original train and test splits, with seed 42. Mean test accuracy was 77.86%, with each dataset weighted equally.

This earlier experiment used one seed and one split per dataset. Its 19-dataset mean is not directly comparable with the newer four-dataset experiment. Neither establishes statistical superiority.

Earlier 19-dataset baseline (CSV)
A thought to take forwardThe extra channel relationships help Handwriting. I still need to understand when they’re worth adding.
Keep following the thread

A place to make & play