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'.
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).