feat: add an estimator interface delegating to train - #17
Merged
Conversation
fit, transform, predict, fit_transform, score, get_params and set_params on SOM, modelled on KMeans, which is the right precedent: a SOM is a topologically-constrained k-means. train remains the primary way to train and is unchanged, so nothing existing is affected. These are pass-through methods by Ousterhout's definition (A Philosophy of Software Design, 7.1) and would ordinarily be a smell. They are here because their value is conformance to an external convention rather than abstraction: Pipeline, GridSearchCV and cross_val_score call these exact names. Keeping them one-liners is deliberate, and a test asserts fit and train produce weights identical at 0.0 for every mode, so the two cannot drift. predict returns a flat node index, not (row, column). A 1-D label array is what scorers, confusion_matrix and cross_val_score assume, so coordinates would read better for a grid and compose with nothing. np.unravel_index recovers the position, and winner() still returns coordinates. score is the negated quantization error. Every scikit-learn scorer treats larger as better and GridSearchCV maximises, so without the sign a parameter search would confidently select the worst map. set_params changes the rates, radii, decays and distance function. It refuses to change the grid shape or input_len: silently rebuilding would discard trained weights, and leaving them would produce a map whose models do not match its own description. It re-runs learning-rate validation, so it cannot be used to get around the constructor's check. Two differences from scikit-learn are deliberate and documented rather than papered over. fit continues instead of resetting, because a SOM's models are its state and train has always continued. And the models exist before training, since initialization is a separate step, so there is no not-fitted state to raise about. Not sufficient on its own for Pipeline.predict or GridSearchCV, which since scikit-learn 1.7 require __sklearn_tags__. The adapter that provides it follows separately.
ruff format applies to fenced python in markdown as well as to .py files. I ran ruff check after writing the page but not ruff format --check, so CI caught it.
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.
fit,transform,predict,fit_transform,score,get_params,set_paramsonSOM. Entirelyadditive:
trainis unchanged and remains the primary way to train. Second of three PRs for0.6.0.
Modelled on KMeans, verified against the installed 1.9
KMeansSOMfit(X, y=None)transform(X)→(n, n_clusters)(n, x*y)predict(X)→(n,)(n,)score(X)cluster_centers_weights_inertia_quantization_error_Nonebefore trainingn_features_in_On Ousterhout
These are pass-through methods by the definition in A Philosophy of Software Design §7.1 — "does
little except invoke another method, whose signature is similar or identical" — and that is ordinarily
a smell. They are here because their value is conformance to an external convention rather than
abstraction:
Pipeline,GridSearchCVandcross_val_scorecall these exact names.The mitigation is to keep them one-liners and prove they cannot drift:
Exactly
0.0, for every mode. A pass-through that has diverged from what it passes through is thefailure this guards.
Two decisions worth reviewing
predictreturns a flat index, not(row, column). A 1-D label array is what scorers,confusion_matrixandcross_val_scoreassume, so coordinates would read better for a grid andcompose with nothing.
np.unravel_index(som.predict(X), som.get_shape())recovers the position, anda test asserts it matches
winner()sample by sample.winner()itself is unchanged.scoreis the negated error. Every scikit-learn scorer treats larger as better andGridSearchCVmaximises. Without the sign, a parameter search would confidently select the worstmap. A test asserts a trained map outscores an untrained one, so the sign is checked rather than
assumed.
Two differences from scikit-learn, deliberate and documented
fitcontinues rather than resetting. scikit-learn estimators conventionally discard fittedstate on refit. A SOM's models are its state, and
trainhas always continued. Pinned by a testso it stays a decision rather than becoming a surprise.
not-fitted state to raise about.
set_params refuses some things
It changes the rates, radii, decays and distance function. It raises for the grid shape and
input_len:Silently rebuilding would discard trained weights; leaving them would give a map whose models
contradict its own description. It also re-runs learning-rate validation, so it cannot be used to get
around the constructor's check.
set_paramsis whatset_learning_rate/set_neighborhood_radiusbecome. Both still work, and a testasserts the two routes agree.
Not sufficient for Pipeline on its own
Stated plainly because the earlier plan got this wrong: since scikit-learn 1.7,
Pipeline.predict,GridSearchCVandcross_val_scorerequire__sklearn_tags__. These method names alone give youfitand nothing else. The adapter that provides the rest is the next PR, keeping scikit-learn anoptional dependency.
Verification
407 tests (24 new), 100% coverage, ruff /
ruff format/mypy --strictclean,mkdocs build --strictexits 0, bandit clean, architecture profile--strictpasses. New docs page in the nav.