Skip to content

Recipes

sezgi.Problem subclasses for two common "optimize against data" shapes.

recipes

Data recipes (M4-1 Task 6) -- the "optimize against data" door: two sezgi.Problem subclasses that bind a search space to a caller-supplied dataset/objective, so a data-analysis workflow reaches the engine (and every built-in wrapper class, py-sezgi/python/sezgi/builtins.py) through the SAME sezgi.Problem ABC (py-sezgi/python/sezgi/problem.py) every other Task 1-5 example uses -- no bespoke glue code per dataset.

FeatureSelection: wraps a Binary(n_features) search over a 2D dataset's columns, scored by a caller-supplied scorer(X_sub, y) -> float.

MixedTuning: the general "tune anything" door -- binds a caller-supplied objective(x) -> float over ANY declared sezgi.Space (a single block or a Mixed multi-block space), with zero dataset-specific assumptions.

Pure Python; numpy only (no sklearn/scipy import anywhere in this module -- see FeatureSelection's own docstring for the sklearn-shaped scorer note, kept as a COMMENT, never an import).

FeatureSelection

FeatureSelection(X, y, scorer, penalty=0.0)

Bases: sezgi.problem.Problem

Binary feature-selection search over a 2D dataset's columns.

space() is sezgi.Binary(n_features): a genotype is a length- n_features boolean mask (list[bool], per sezgi.Problem's own genotype conversion table -- see problem.py's module docstring), True selecting a column.

MINIMIZE convention: scorer(X_sub, y) must return a LOWER-is-better number (e.g. an error/loss/residual metric, NOT accuracy/R^2/any higher-is-better score) -- evaluate never negates or inverts it. An sklearn cross-val ACCURACY scorer must be wrapped to flip its sign before it can be used here; a plain error metric (RMSE, log-loss, ...) already fits directly. Example (sklearn is NOT a dependency of this module or this project's Python package -- this is a comment only, no import anywhere in sezgi):

# from sklearn.linear_model import LogisticRegression
# from sklearn.model_selection import cross_val_score
#
# def scorer(X_sub, y):
#     # cross_val_score's default scoring is HIGHER-is-better
#     # (accuracy) -- negate it so lower is better, matching this
#     # class's minimize convention.
#     return -cross_val_score(LogisticRegression(), X_sub, y, cv=3).mean()
#
# FeatureSelection(X, y, scorer, penalty=0.01)

evaluate(mask) = scorer(X[:, mask], y) + penalty * (popcount(mask) / n_features) -- the penalty term is a fraction of the FULL feature count (popcount / n_features, not a raw popcount), so it stays on a comparable scale to scorer's own output regardless of n_features and its size is penalty at the all-features mask, 0 at the empty mask. Larger penalty biases the search toward smaller feature subsets; penalty=0.0 (default) is a pure scorer-value search with no feature-count preference of its own.

EMPTY MASK (popcount == 0): scorer is NEVER called -- X[:, mask] would be a (n_samples, 0) array, and most real scorers (a model fit, a distance-correlation-like statistic, ...) cannot meaningfully score zero columns; the popcount == 0 case is instead handled directly, as a DOCUMENTED SENTINEL: evaluate([False, ..., False]) == float("inf"). +inf was chosen over, say, a large-but-finite number because it is unambiguous (no dataset-dependent magnitude to pick or accidentally beat by a legitimately bad but non-empty selection) and it sorts correctly under every consumer's own comparison (<) with no special -casing needed -- the empty mask is simply never the minimizer of any run that has at least one non-empty candidate in its population, which every population-based search here always does. The bridge's fitness channel accepts a non-finite return here (see problem.py's module docstring on evaluate's return); this is a genotype-side FITNESS value, not a genotype coordinate, so the reverse block_value_from_py finiteness check (Task 1, Float coordinates only) does not apply to it.

X: a 2D array-like (n_samples, n_features) -- coerced via np.asarray, ValueError if not 2D. y: the target array-like, coerced via np.asarray unchanged (shape/dtype are the caller's responsibility -- scorer receives it as-is). scorer(X_sub, y) -> float: caller-supplied, MINIMIZE convention (see the class docstring). penalty: feature-count regularization weight, default 0.0 (no preference for smaller subsets) -- see the class docstring for the exact evaluate() formula.

__abstractmethods__ class-attribute

__abstractmethods__ = frozenset()

frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object

Build an immutable unordered collection of unique elements.

__doc__ class-attribute

__doc__ = 'Binary feature-selection search over a 2D dataset\'s columns.\n\n    `space()` is `sezgi.Binary(n_features)`: a genotype is a length-\n    `n_features` boolean mask (`list[bool]`, per `sezgi.Problem`\'s own\n    genotype conversion table -- see `problem.py`\'s module docstring),\n    `True` selecting a column.\n\n    MINIMIZE convention: `scorer(X_sub, y)` must return a LOWER-is-better\n    number (e.g. an error/loss/residual metric, NOT accuracy/R^2/any\n    higher-is-better score) -- `evaluate` never negates or inverts it. An\n    sklearn cross-val ACCURACY scorer must be wrapped to flip its sign\n    before it can be used here; a plain error metric (RMSE, log-loss, ...)\n    already fits directly. Example (sklearn is NOT a dependency of this\n    module or this project\'s Python package -- this is a comment only, no\n    import anywhere in sezgi):\n\n        # from sklearn.linear_model import LogisticRegression\n        # from sklearn.model_selection import cross_val_score\n        #\n        # def scorer(X_sub, y):\n        #     # cross_val_score\'s default scoring is HIGHER-is-better\n        #     # (accuracy) -- negate it so lower is better, matching this\n        #     # class\'s minimize convention.\n        #     return -cross_val_score(LogisticRegression(), X_sub, y, cv=3).mean()\n        #\n        # FeatureSelection(X, y, scorer, penalty=0.01)\n\n    `evaluate(mask)` = `scorer(X[:, mask], y) + penalty * (popcount(mask) /\n    n_features)` -- the penalty term is a fraction of the FULL feature\n    count (`popcount / n_features`, not a raw popcount), so it stays on a\n    comparable scale to `scorer`\'s own output regardless of `n_features`\n    and its size is `penalty` at the all-features mask, `0` at the empty\n    mask. Larger `penalty` biases the search toward smaller feature\n    subsets; `penalty=0.0` (default) is a pure `scorer`-value search with\n    no feature-count preference of its own.\n\n    EMPTY MASK (popcount == 0): `scorer` is NEVER called -- `X[:, mask]`\n    would be a `(n_samples, 0)` array, and most real scorers (a model fit,\n    a distance-correlation-like statistic, ...) cannot meaningfully score\n    zero columns; the popcount == 0 case is instead handled directly, as a\n    DOCUMENTED SENTINEL: `evaluate([False, ..., False]) == float("inf")`.\n    `+inf` was chosen over, say, a large-but-finite number because it is\n    unambiguous (no dataset-dependent magnitude to pick or accidentally\n    beat by a legitimately bad but non-empty selection) and it sorts\n    correctly under every consumer\'s own comparison (`<`) with no special\n    -casing needed -- the empty mask is simply never the minimizer of any\n    run that has at least one non-empty candidate in its population, which\n    every population-based search here always does. The bridge\'s fitness\n    channel accepts a non-finite return here (see `problem.py`\'s module\n    docstring on `evaluate`\'s return); this is a genotype-side FITNESS\n    value, not a genotype coordinate, so the reverse `block_value_from_py`\n    finiteness check (Task 1, Float coordinates only) does not apply to it.\n    '

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'sezgi.recipes'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

space

space()

sezgi.Binary(n_features) -- see the class docstring for the boolean-mask genotype convention this implies.

evaluate

evaluate(mask)

mask: a length-n_features bool-coercible sequence (the genotype sezgi.Problem's bridge hands this method, per Binary's own conversion). Returns float("inf") for the empty mask (no scorer call), otherwise scorer(X[:, mask], y) + the popcount-fraction penalty term -- see the class docstring for the exact formula and the rationale for both choices.

MixedTuning

MixedTuning(space, objective)

Bases: sezgi.problem.Problem

The general "tune anything" door: binds a caller-supplied objective(x) -> float over ANY declared sezgi.Space -- a single block (sezgi.Float(...), sezgi.Categorical(...), ...) or a multi-block sezgi.Space(...) (a "Mixed" space, per this crate's own gen/compound terminology). evaluate delegates to objective UNCHANGED -- x's exact shape follows sezgi.Problem's own genotype conversion table (bare value for a single-block space, a tuple of per-block values in space()'s own block order for a multi-block one).

A hyperparameter-tuning example over a Float learning-rate block plus a Categorical optimizer-choice block (2 hyperparameters, minimizing a toy validation loss):

space = sezgi.Space(sezgi.Float(1e-4, 1e-1, 1), sezgi.Categorical(3, 1))

def objective(x):
    lr_block, opt_block = x  # per-block values, in space() order
    lr = lr_block[0]         # Float block -> list[float]
    opt_idx = opt_block[0]   # Categorical block -> list[int]
    # ... train/validate with these hyperparameters, return a loss ...
    return validation_loss

MixedTuning(space, objective)

Note GeneticAlgorithm (sezgi.builtins) auto-dispatches only over a SINGLE-kind space (all-Float, all-Binary, ...) -- it raises NotImplementedError on a genuinely mixed space like the one above (see GeneticAlgorithm's own docstring); a mixed-space MixedTuning instance is run via a hand-built gen/compound AlgorithmSpec passed to sezgi.solve(...) directly (see sezgi.problems.mixed_diagnostic's own doc for a worked mixed-space example), or evaluated directly for a non-engine use (grid search, a notebook sanity check, ...). A single-kind MixedTuning space (e.g. Float-only, tuning several continuous hyperparameters at once) runs through GeneticAlgorithm exactly like any other single-kind sezgi.Problem.

space: any declared sezgi.Space -- a single block (sezgi.Float(...), sezgi.Categorical(...), ...) or a multi-block sezgi.Space(...), returned unchanged by space(). objective(x) -> float: caller-supplied, MINIMIZE convention, called unchanged by evaluate() with x in space's own genotype shape (see the class docstring for a worked example).

__abstractmethods__ class-attribute

__abstractmethods__ = frozenset()

frozenset() -> empty frozenset object frozenset(iterable) -> frozenset object

Build an immutable unordered collection of unique elements.

__doc__ class-attribute

__doc__ = 'The general "tune anything" door: binds a caller-supplied\n    `objective(x) -> float` over ANY declared `sezgi.Space` -- a single\n    block (`sezgi.Float(...)`, `sezgi.Categorical(...)`, ...) or a\n    multi-block `sezgi.Space(...)` (a "Mixed" space, per this crate\'s own\n    `gen/compound` terminology). `evaluate` delegates to `objective`\n    UNCHANGED -- `x`\'s exact shape follows `sezgi.Problem`\'s own genotype\n    conversion table (bare value for a single-block space, a tuple of\n    per-block values in `space()`\'s own block order for a multi-block one).\n\n    A hyperparameter-tuning example over a Float learning-rate block plus a\n    Categorical optimizer-choice block (2 hyperparameters, minimizing a\n    toy validation loss):\n\n        space = sezgi.Space(sezgi.Float(1e-4, 1e-1, 1), sezgi.Categorical(3, 1))\n\n        def objective(x):\n            lr_block, opt_block = x  # per-block values, in space() order\n            lr = lr_block[0]         # Float block -> list[float]\n            opt_idx = opt_block[0]   # Categorical block -> list[int]\n            # ... train/validate with these hyperparameters, return a loss ...\n            return validation_loss\n\n        MixedTuning(space, objective)\n\n    Note `GeneticAlgorithm` (`sezgi.builtins`) auto-dispatches only over a\n    SINGLE-kind space (all-Float, all-Binary, ...) -- it raises\n    `NotImplementedError` on a genuinely mixed space like the one above\n    (see `GeneticAlgorithm`\'s own docstring); a mixed-space `MixedTuning`\n    instance is run via a hand-built `gen/compound` `AlgorithmSpec` passed\n    to `sezgi.solve(...)` directly (see `sezgi.problems.mixed_diagnostic`\'s\n    own doc for a worked mixed-space example), or `evaluate`d directly for\n    a non-engine use (grid search, a notebook sanity check, ...). A\n    single-kind `MixedTuning` space (e.g. Float-only, tuning several\n    continuous hyperparameters at once) runs through `GeneticAlgorithm`\n    exactly like any other single-kind `sezgi.Problem`.\n    '

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

__module__ class-attribute

__module__ = 'sezgi.recipes'

str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str

Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.str() (if defined) or repr(object). encoding defaults to sys.getdefaultencoding(). errors defaults to 'strict'.

space

space()

Returns the space given at construction time, unchanged.

evaluate

evaluate(x)

Delegates to self.objective(x) unchanged -- x follows sezgi.Problem's own genotype conversion table for self.space() (bare value for a single-block space, a tuple of per-block values in space() order for a multi-block one).