Conversation
Pipelines could not describe their own data. ClassifierPipeline inherits Classifier but no __init__ in the chain called Classifier.__init__, so pipeline.schema raised AttributeError; nothing aggregated element schemas; and MOATransformer.get_schema() returned the schema it was given rather than the one its filter emits. With a filter that changes the feature set the transformer reported 6 attributes while emitting 3, silently. - BasePipeline gains get_input_schema() and get_schema(), walking elements so nested pipelines compose. - ClassifierPipeline/RegressorPipeline now carry schema and random_seed, satisfying the base-class contract. schema is a property reporting the input schema, so it tracks elements added later. - Schema.is_compatible_with() and Schema.describe_difference() compare attribute structure and target, ignoring the dataset name. - Adding an element whose schema does not fit raises ValueError naming the differences; validate_schema=False opts out. - MOATransformer separates input from output schema, deriving the output from the header the transformed instance carries, since some filters never publish one on the filter object. Behaviour changes, described here rather than marked: get_schema() on a transformer now reports the output schema (unchanged for every feature-preserving filter); __str__ drops its trailing separator; incompatible elements raise instead of being accepted; and constructing a MOATransformer without a schema raises a clear RuntimeError instead of a Java NullPointerException. Adds tests/test_pipeline.py; the module previously had no pytest coverage. Addresses adaptive-machine-learning/backlog#154 Addresses adaptive-machine-learning/backlog#87 Assisted-by: claude-code:claude-opus-5
CapyMOA has few transformers, so the filter inventory can be complete rather than representative. Parameterised over every moa.streams.filters entry reachable through MOATransformer, across ElectricityTiny, CovtypeTiny and FriedTiny. - The central invariant, over every usable filter and stream: the schema a transformer reports must match the number of features the instances it emits actually carry. - Feature-preserving filters stay compatible with their input; HashingTrickFilter reports its new, smaller shape. - Mismatched wiring is refused across five stream pairings, with tests that the message names the attribute count, the class labels and the classification/regression split. - A learner placed after a feature-reducing transformer is rejected. - No false positives: correctly wired pipelines build without complaint, and validate_schema=False still accepts every mismatch. Four filters (RemoveDiscreteAttributeFilter, RBFFilter, ReLUFilter, RandomProjectionFilter) raise inside MOA before producing a usable instance. Verified identical on upstream/main, so they are documented in a test rather than fixed here; if one starts working the test fails and says where to move it. Note RemoveDiscreteAttributeFilter fails only when the instance's `x` is read: transform_instance() returns a headerless instance quite happily, and Instance.x is lazy. Addresses adaptive-machine-learning/backlog#154 Addresses adaptive-machine-learning/backlog#87 Assisted-by: claude-code:claude-opus-5
Adds section 7.8 to 07_pipelines.ipynb: the difference between what a pipeline consumes and what it emits, a transformer that changes the feature set, the errors raised when schemas do not line up, and the escape hatch. The error cells catch the ValueError and print it, so the notebook shows the message a user would see without failing under nbmake. Purely additive -- 221 insertions, no deletions. Stored outputs were produced by executing the cells, not written by hand, which matters because nbsphinx will not re-execute a notebook that already has them. Addresses adaptive-machine-learning/backlog#154 Assisted-by: claude-code:claude-opus-5
hmgomes
force-pushed
the
feat/pipeline-schema
branch
from
September 22, 2026 04:08
f24628b to
24edf5a
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pipelines could not describe their own data.
ClassifierPipelineinheritsClassifierbut no__init__in the chain ever calledClassifier.__init__, sopipeline.schemaraisedAttributeError; nothing aggregated the schemas of the elements; andMOATransformer.get_schema()returned the schema it was given rather than the one its filter emits. Users threaded schemas
by hand through every example in
notebooks/07_pipelines.ipynb, and nothing checked the result.That last point is the one with teeth. With a filter that changes the feature set, the transformer
reported the wrong shape and no error was raised:
What changed
BasePipeline.get_input_schema()returns what the pipelineconsumes,
get_schema()what it emits; both walk the elements, so a nested pipeline composes.schemaandrandom_seednow exist onClassifierPipeline/RegressorPipeline, satisfyingthe base-class contract.
schemais a property, so it tracks elements added after construction,and it reports the input schema — the instances handed to
train/predict.Schema.is_compatible_with()andSchema.describe_difference()compare attribute structureand target while ignoring the dataset name, which normally differs downstream of a filter.
ValueErrornaming the differences,rather than being accepted silently. This applies to elements passed to the constructor as well
as appended ones.
validate_schema=Falseopts out.MOATransformerdistinguishes input from output schema and derives the output one, preferringthe header the transformed instance carries over the filter's own — some filters never publish
the latter.
Design decisions
Schema.is_compatible_with()rather than__eq__. Defining__eq__would have madeSchemaunhashable unless
__hash__were defined alongside, and "equal" is the wrong word for whatpipelines need: a filter's output legitimately carries a different relation name while remaining
interchangeable for a learner. A named predicate says what it checks, and
describe_difference()turns a rejection into a message that names the mismatch.
Output-schema derivation prefers the transformed instance's own header over
filter.getHeader().Filters that change the feature set publish a header only after an instance has passed through,
and some (
HashingTrickFilter) never publish one at all while still stamping it on what theyemit. Derivation is therefore lazy and retried on the first
transform_instance.PipelineElement.get_schema()/get_input_schema()have defaults returningNonerather thanbeing abstract, so existing third-party
PipelineElementimplementations keep working.Behaviour changes
No public API is removed, but four behaviours differ. None uses a breaking-change marker; they are
described here instead.
MOATransformer.get_schema()returns the filter's output schema. For every feature-preservingfilter (normalisation, standardisation, added noise) that is the same value as before.
BasePipeline.__str__no longer leaves a trailing" | ".ValueErrorwhere it was previously accepted. Coderelying on the old silence needs
validate_schema=False.MOATransformer(moa_filter=...)without a schema raises a clearRuntimeErrorinstead of a JavaNullPointerException. That path never worked; only the error improved.Validation
tests/test_pipeline.pyis new, with 78 tests — the module previously had no pytest coverageat all, its only exercise being
07_pipelines.ipynb, which nbmake runs without checking output.CapyMOA has few transformers, so the filter coverage is exhaustive rather than representative:
every
moa.streams.filtersentry reachable throughMOATransformer, parameterised acrossElectricityTiny,CovtypeTinyandFriedTiny(6 numeric attributes over 2 classes; 54attributes of which 44 nominal, over 7 classes; and 10 numeric attributes for regression).
The central assertion, over every usable filter and stream: the schema a transformer reports
must equal the number of features the instances it emits actually carry. That is the invariant
whose absence caused the bug above.
Mismatched wiring is checked across five stream pairings, asserting that the message names the
attribute count, the class labels, and the classification/regression split. Two further tests
assert equivalence rather than a pinned constant — a
ClassifierPipeline, with and without atransformer, must score exactly what the hand-written test-then-train loop it replaces scores,
which is what guards the schema swap inside
transform_instance.Two limits are asserted rather than left to be discovered:
publishes a filter's output header only after it has seen one. A pipeline assembled and never
run therefore accepts a mismatched learner.
RemoveDiscreteAttributeFilter,RBFFilter,ReLUFilter,RandomProjectionFilter) raise inside MOA before producing a usable instance, identically onmain. They are documented in a test that fails if one starts working.07_pipelines.ipynbgains section 7.8, explaining consumed-versus-emitted schemas, atransformer that changes the feature set, the errors raised when schemas do not line up, and the
escape hatch. The error cells catch the
ValueErrorand print it, so the notebook shows the realmessage without failing under nbmake. Purely additive: 221 insertions, no deletions.
Two pre-existing stored outputs were updated for the
__str__change. Every stored value here,new and updated, was produced by executing the code rather than typed — which matters because
nbsphinx will not re-execute a notebook that already carries outputs. Accuracy figures are
untouched, since nothing in this change affects learning.
All four CI jobs were reproduced locally: ruff format and lint clean, 378 pytest passed,
135 doctests passed, 22 notebooks passed under
NB_FAST, andinvoke docs.buildsucceeded withzero warnings.
Follow-ups, deliberately not in this PR
SelectAttributesFilteris not amoa.streams.filters.StreamFilter, soFilteredQueueStreamrejects it and it cannot be used in a pipeline at all. The hashing trick is currently the only
reachable filter that changes the feature set.
MOATransformerbuilds its filter CLI as-f (<creation string>), which MOA fails to parse whenthe filter's own options contain commas.
stream.get_schema()rather than the pipeline's schema._repr_html_, are theremaining workstreams of the issue below.
rather than be polled, are tracked separately as
backlog#158andbacklog#157.Addresses adaptive-machine-learning/backlog#154
Addresses adaptive-machine-learning/backlog#87
Assisted-by: claude-code:claude-opus-5