Build and adapt a pipeline¶
IAML pipelines are editable recipes: combine reusable components, choose the alternatives the search may explore, and configure their parameters. You can start from the AutoML preset or describe a pipeline from scratch. The same operations work when you revisit an existing recipe.
For metrics, descriptive statistics and explanations, see Configure a complete study. The complete signatures and validation rules are in Pipeline and study API reference.
Start from the preset or your own recipe¶
The integrated workflow trains without a recipe. To adjust its preset, obtain an editable copy:
from iaml import IAML
from iaml.flow import PipelineSpec
from iaml.steps import MaxAbsScaler, UnitNormScaler
pipeline = PipelineSpec.default()
pipeline.main.normalize.remove(UnitNormScaler, MaxAbsScaler)
search = IAML(pipeline=pipeline)
IAML().pipeline and PipelineSpec.default() expose the same preset, with
two visible branches. pipeline.main contains the main strategy;
pipeline.minimal contains the minimal strategy, whose models are available
through pipeline.minimal.minimal_predictor. Remove the latter strategy with
pipeline.remove("minimal"). Editing a group in one branch leaves the other
branch independent. Unique aliases also allow direct access to groups such as
pipeline.normalize and pipeline.predictor.
The main strategy starts without feature selection. Its features_selection
choice can use one selector at a time, including SelectPercentile. Custom
recipes can still declare successive selections explicitly.
The minimal strategy imputes missing values when needed, then ordinal-encodes categorical features before fitting its predictor. The encoder is learned again on each cross-validation training fold and retained for later predictions.
An explicitly supplied pipeline defines the requested search scope: IAML
generates only its declared branches. No additional minimal candidates are
added to a custom recipe. Omitting pipeline selects the complete preset,
even when you configure only the analytical collections.
IAML(pipeline=pipeline) copies the recipe. To edit the study after
construction, navigate from search.pipeline rather than the original
pipeline. Each fit creates independent execution instances.
Compose components with >>¶
use(Component, **params) describes a component. Import the IAML adapters
from iaml.steps using names such as StandardScaler and
RandomForestClassifier. Historical imports with the Act prefix remain
available.
from iaml.flow import Int, choice, optional, use
from iaml.steps import (
LogisticRegression,
RandomForestClassifier,
RobustScaler,
SimpleImputer,
SMOTE,
StandardScaler,
)
normalization = choice(StandardScaler, RobustScaler).named("normalize")
models = choice(
use(LogisticRegression).named("logistic"),
use(
RandomForestClassifier,
n_estimators=Int(100, 500, initial=200),
).named("forest"),
).named("predictor")
pipeline = (
use(SimpleImputer).named("cleaning")
>> normalization
>> optional(use(SMOTE)).named("imbalance")
>> models
)
Read a >> b as “a, then b”: candidates produced by a continue through
b in that order. The operator composes recipes, so write
use(A) >> use(B). A class supplied directly to choice is equivalent to
use(Class).
choice declares allowed alternatives. By default, each participates in
initial candidate generation. The example allows two normalizations, with or
without SMOTE, followed by two models: eight possible structures before
applicability checks. The budget and compatibility with the data determine
which are generated and evaluated. This structural scope does not guarantee
exhaustive hyperparameter search.
optional also permits the absence of its transformation or resampler.
Every modeling path must end in exactly one predictor; a predictor cannot be
made optional.
Some preset groups use adaptive ordering: their components run according to
their priority. Editing such a group with add or remove preserves this
mode. An adaptive group executes its applicable components rather than
selecting one model alternative.
Start with a family, then subtract¶
Families let you use registered components without enumerating them. Remove the components outside your intended search scope:
from iaml.flow import normalizers, predictors
from iaml.steps import KNeighborsClassifier, MaxAbsScaler, UnitNormScaler
normalization = (
normalizers()
.remove(UnitNormScaler, MaxAbsScaler)
.named("normalize")
)
models = predictors().remove(KNeighborsClassifier).named("predictor")
pipeline = use(SimpleImputer).named("cleaning") >> normalization >> models
normalizers and predictors discover the corresponding component
families. choice(tag="normalize") provides the generic form using a registry
tag. remove(Class) removes that exact class’s variants from the targeted
group; remove("alias") removes the named variant. An explicit add can
introduce a configured variant of a previously excluded component, without
restoring its other registry variants.
Families resolve when a search starts. The resulting catalogue, including exclusions, additions and variant configurations, is frozen for that run. Component replacements by the optimizer respect this scope. Each component’s applicability is still checked against the data it receives.
Target one variant or several¶
A component class can occur several times with different configurations. Aliases distinguish its variants:
models = choice(
use(
RandomForestClassifier,
n_estimators=Int(100, 400, initial=200),
).named("forest_small"),
use(
RandomForestClassifier,
n_estimators=Int(200, 600, initial=300),
).named("forest_deep"),
).named("predictor")
models.find_all(RandomForestClassifier).configure(n_estimators=300)
# Each variant retains its own domain.
models["forest_small"].configure(n_estimators=350)
# Only this variant's initial value changes.
find_all matches the exact class and returns an iterable Selection.
Its configure method validates all targets before modifying them; each
variant retains its own unspecified settings and domain. The selection is a
snapshot: query again after removing or replacing a target. See
the editing contracts for stale selections,
empty groups and strict removal behavior.
Named access returns objects attached to the recipe. Thus,
models["forest_small"].configure(...) edits that variant inside models.
[] accepts an alias; class selection uses find_all.
An explicit alias is unique throughout a pipeline tree, so it can be used
from the root without a qualified path. Name variants individually with
.named(...).
Add, remove and replace¶
Editing uses the same components as construction:
from iaml.flow import PipelineSpec, normalizers
from iaml.steps import KNeighborsClassifier, MaxAbsScaler, UnitNormScaler
pipeline = PipelineSpec.default()
pipeline.predictor.remove(KNeighborsClassifier).add(
use(
RandomForestClassifier,
n_estimators=Int(50, 150, initial=100),
).named("forest_small"),
)
pipeline.remove("imbalance")
previous = pipeline.normalize
replacement = previous.replace(
normalizers().remove(UnitNormScaler, MaxAbsScaler),
)
add and remove edit the targeted group: alternatives in a choice,
steps in a sequence, or methods in an analytical collection. In a sequence,
before="alias" specifies where to insert a component. The alias can belong
to a nested sequence: pipeline.add(use(BMI).named("bmi"), before="cleaning")
inserts the component into the preset’s main strategy, just before cleaning.
Edits are atomic. A group may be empty while you edit it, but the completed recipe must have one terminal predictor per path before training.
replace returns a new attached block, preserving the old block’s position
and alias. In the example, replacement is now pipeline.normalize;
previous is detached and no longer edits the pipeline. Continue from the
returned replacement. See Editing methods for replacement
validation and container requirements.
Configure parameters¶
The same notation works in use and configure:
Value |
Effect on a training component |
|---|---|
|
Change the initial value, retaining the current or default domain. |
|
Keep the value fixed throughout the search. |
|
Set an integer domain and its initial value. |
|
Set a real-valued domain and its initial value. |
from iaml.flow import Const, Int
forest = use(
RandomForestClassifier,
n_estimators=Int(100, 500, initial=200),
max_depth=Const(10),
)
forest.configure(n_estimators=300)
# Start at 300 within [100, 500]; depth stays fixed at 10.
forest.configure(n_estimators=Const(300))
# Fix the value at 300; retain [100, 500] as an inactive domain.
forest.configure(n_estimators=350)
# Resume optimization within [100, 500], starting at 350.
forest.configure(n_estimators=Int(200, 800, initial=400))
# Replace the domain and its initial value.
Omitted parameters retain their configuration. An initial value outside its active domain is rejected without widening the bounds. A constant must satisfy the component’s own constraints, but may lie outside a retained, inactive search domain. If a parameter has no domain, supplying a plain value does not invent one; the component’s parameter policy remains in effect.
This convention also applies to resamplers. Analytical parameters configure fixed calculations instead; see Build analytical collections.
Choose the starting alternatives¶
start selects the alternatives initially generated by a choice, while
keeping the other alternatives available. Give individual alternatives aliases
with named:
models = choice(
use(LogisticRegression).named("logistic"),
use(RandomForestClassifier).named("forest"),
).named("predictor")
complete = use(SimpleImputer) >> models
partial = complete.clone()
partial.predictor.start("logistic")
restricted = complete.clone()
restricted.predictor.remove("forest")
complete allows both models and starts with both. partial still allows
both, but starts with logistic regression; an optimizer that can replace
components can subsequently reach the forest. restricted excludes the
forest throughout the run.
partial.predictor.start() restores a full start. Before removing an
alternative referenced by start, reset the starting alternatives with
start() or select a start that no longer references it. Otherwise removal
fails without changing the group.
A partial start requires an optimizer supporting component replacement,
such as GeneticOptimizer. Choose the optimizer and validation settings in
Choose validation and search settings. Choices between complete sub-pipelines use
full initial generation.
Keep preparation and models together¶
Independent preparation and model choices allow all their combinations. To keep a preparation strategy associated with its model, choose between complete sub-pipelines instead:
def build_pipeline():
"""Partager le nettoyage, puis choisir entre deux stratégies complètes."""
linear = (
use(StandardScaler)
>> use(LogisticRegression, tol=Float(1e-5, 1e-2, initial=1e-4))
).named("linear")
forest = use(
RandomForestClassifier,
n_estimators=Int(100, 500, initial=200),
max_depth=Int(3, 30, initial=15),
).named("forest")
strategy = choice(linear, forest).named("strategy")
return use(SimpleImputer).named("cleaning") >> strategy
The linear branch normalizes the data before logistic regression. The
forest branch uses its own random forest configuration and search domains.
Both share the preceding cleaning step. Nested composition expresses this
structural relationship directly.
Their parameters are optimized within their respective domains, while the optimizer preserves each strategy’s component scope. These complete branches use full initial generation; whole-strategy replacement during partial search is unsupported.
Reuse recipes independently¶
Composition with >> and clone produce independent recipes. Group
constructors, IAML(pipeline=recipe), add(recipe) and replace(recipe)
also copy their inputs. After construction, edit the attached objects reached
through the new container:
preparation = (
use(SimpleImputer).named("cleaning")
>> normalizers().named("normalize")
)
forest_pipeline = preparation >> use(RandomForestClassifier).named("predictor")
logistic_pipeline = preparation >> use(LogisticRegression).named("predictor")
forest_pipeline.normalize.remove(UnitNormScaler)
# preparation and logistic_pipeline remain independent.
fixed_depth = forest_pipeline.clone()
fixed_depth.predictor.configure(max_depth=Const(10))
A reference retained before composition points to its original fragment. Navigate from the final pipeline, or use its aliases, to edit the composed copy. Recipe edits after training prepare a later run and do not change existing fitted candidates.
Integrate a custom component¶
Extensions use IAML’s existing component contract: registration with
is_step, applicability checks, and transformation or prediction methods.
Pass the registered class to use:
def with_business_feature(feature_class):
pipeline = PipelineSpec.default()
pipeline.add(use(feature_class).named("bmi"), before="cleaning")
return pipeline
The custom component example contains a complete body mass
index component and its insertion. Its registry tag also determines which
automatic families discover it. See Extending IAML for component
contracts and other extension points.
Inspect a recipe and its search¶
base = PipelineSpec.default()
pipeline = base.clone()
pipeline["normalize"].remove(UnitNormScaler)
print(pipeline.describe())
print(pipeline.diff(base))
code = pipeline.to_code()
describe shows the tree, allowed and initial alternatives, exclusions, and
parameters with their domains or fixed-value mode. diff reports changes
relative to another recipe. Both return structured, readable RecipeReport
objects: print the report or obtain a copy of its data with to_dict().
to_code generates reconstruction code, preserving aliases, constants and
both active and inactive domains.
Before fit, open families are unresolved. Afterward, search.describe()
reports the frozen catalogue, observed applicability and evaluated candidates.
Save this report alongside reconstruction code: an open family can resolve
to a different catalogue in another environment. See
Inspection and reconstruction for report fields and reconstruction limits.
See Pipeline and study API reference for signatures and diagnostics.