.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/pitfalls_and_solutions/plot_skd014_skd015_hyperparameter_search.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_pitfalls_and_solutions_plot_skd014_skd015_hyperparameter_search.py: .. _example_skd014_hyperparams_at_search_edge_skd015_hyperparameters_worth_tuning: SKD014 & SKD015: Hyperparameter search pitfalls =============================================== This example walks through mitigations when checks :ref:`SKD014 ` and :ref:`SKD015 ` fire on a fitted search object. SKD014 is an issue: numeric ``best_params_`` sit on the minimum or maximum value tried, so the true optimum may lie outside the searched range. SKD015 is a tip: a hyperparameter is missing from the search space, which is incomplete rather than necessarily wrong. Usually, those tests are related to the search CV object and we advocate to address them jointly when both fire. Mitigations from the :ref:`automated_checks` user guide: **SKD014: hyperparameters at search edge** (issue) - extend ``param_grid`` or ``param_distributions`` beyond the flagged bounds, - for :class:`~sklearn.model_selection.RandomizedSearchCV`, increase ``n_iter`` and sample from a wider range, - if SKD015 also fires, widen the search on every recommended hyperparameter. **SKD015: hyperparameters worth tuning** (tip) - add the suggested parameters to ``param_grid`` or ``param_distributions``. In this example, we tune a :class:`~sklearn.ensemble.HistGradientBoostingClassifier` inside :func:`~skrub.tabular_pipeline` on a stratified subsample of the employee salaries dataset (above-median salary as the positive class). The walkthrough has three parts: missing hyperparameters (SKD015), edge hits (SKD014), then one joint fix that clears both. .. GENERATED FROM PYTHON SOURCE LINES 40-48 Load the employee salaries dataset ================================== The raw target is continuous salary. We turn the regression problem into a binary classification task: predict whether an employee earns more than the median salary among employees in the dataset. Mixed HR features suit :func:`~skrub.tabular_pipeline`. A 3,000-row stratified subsample keeps the gallery grids short while preserving class balance. .. GENERATED FROM PYTHON SOURCE LINES 48-64 .. code-block:: Python from sklearn.model_selection import train_test_split from skrub.datasets import fetch_employee_salaries dataset = fetch_employee_salaries() X_full = dataset.X y_full = (dataset.y > dataset.y.median()).astype(int).rename("high_earner") X, _, y, _ = train_test_split( X_full, y_full, train_size=3_000, stratify=y_full, random_state=42, ) .. GENERATED FROM PYTHON SOURCE LINES 65-67 Let us inspect predictors and the binary target with :class:`~skrub.TableReport`. .. GENERATED FROM PYTHON SOURCE LINES 67-72 .. code-block:: Python from skrub import TableReport TableReport(X) .. raw:: html

Please enable javascript

The skrub table reports need javascript to display correctly. If you are displaying a report in a Jupyter notebook and you see this message, you may need to re-execute the cell or to trust the notebook (button on the top right or "File > Trust notebook").



.. GENERATED FROM PYTHON SOURCE LINES 73-75 .. code-block:: Python TableReport(y) .. raw:: html

Please enable javascript

The skrub table reports need javascript to display correctly. If you are displaying a report in a Jupyter notebook and you see this message, you may need to re-execute the cell or to trust the notebook (button on the top right or "File > Trust notebook").



.. GENERATED FROM PYTHON SOURCE LINES 76-85 Shared search setup =================== Let us wrap HGB in :func:`~skrub.tabular_pipeline`. Early stopping lets a wide ``max_iter`` grid pick an interior budget in the first beat below. The outer hold-out uses :class:`~skore.TrainTestSplit` when we call :func:`~skore.evaluate`; each :class:`~sklearn.model_selection.GridSearchCV` below sets its own inner ``cv``. .. GENERATED FROM PYTHON SOURCE LINES 85-100 .. code-block:: Python from sklearn.ensemble import HistGradientBoostingClassifier from skrub import tabular_pipeline base_pipeline = tabular_pipeline( HistGradientBoostingClassifier( max_iter=200, random_state=42, early_stopping=True, validation_fraction=0.1, n_iter_no_change=10, ) ) base_pipeline .. raw:: html
Pipeline(steps=[('tablevectorizer',
                     TableVectorizer(low_cardinality=ToCategorical())),
                    ('histgradientboostingclassifier',
                     HistGradientBoostingClassifier(early_stopping=True,
                                                    max_iter=200,
                                                    random_state=42))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.


.. GENERATED FROM PYTHON SOURCE LINES 101-110 Trigger SKD015: grid with only ``max_iter`` =========================================== ``max_iter`` is a budget parameter, not a complexity knob in the SKD015 table. We use :class:`~sklearn.model_selection.GridSearchCV` (not randomized search) so every candidate is evaluated and the run is reproducible. The grid is wide on purpose: with early stopping, the best ``max_iter`` lands strictly inside the list, so SKD014 stays quiet and this beat isolates the SKD015 tip about missing recommended hyperparameters. .. GENERATED FROM PYTHON SOURCE LINES 110-143 .. code-block:: Python from sklearn.model_selection import GridSearchCV from skore import TrainTestSplit, evaluate splitter = TrainTestSplit(test_size=0.2, random_state=42, stratify=y) max_iter_only_search = GridSearchCV( base_pipeline, param_grid={ "histgradientboostingclassifier__max_iter": [ 10, 25, 50, 100, 200, 500, 1000, ], }, cv=3, scoring="neg_log_loss", n_jobs=4, refit=True, ) report = evaluate( max_iter_only_search, X=X, y=y, pos_label=1, splitter=splitter, ) .. GENERATED FROM PYTHON SOURCE LINES 144-150 In the Tips tab, SKD015 should tip that learning rate, depth, and leaf size were not searched. SKD014 should not fire here: the best ``max_iter`` is not the minimum or maximum of the grid above. ``best_params_`` only contains ``max_iter``: that incompleteness is the point. A search that only tweaks training budget ignores the hyperparameters that usually move generalization for tree ensembles. .. GENERATED FROM PYTHON SOURCE LINES 150-153 .. code-block:: Python report.checks.summarize(fast_mode=True) .. raw:: html


.. GENERATED FROM PYTHON SOURCE LINES 154-156 .. code-block:: Python report.estimator_.best_params_ .. rst-class:: sphx-glr-script-out .. code-block:: none {'histgradientboostingclassifier__max_iter': 50} .. GENERATED FROM PYTHON SOURCE LINES 157-166 Trigger SKD014: two-point ``GridSearchCV`` ========================================== With exactly two values on each searched hyperparameter, whichever value wins is always the tried minimum or maximum, so SKD014 fires deterministically. We still omit depth / leaf hyperparameters so SKD015 tips as well. Prefer a small grid over :class:`~sklearn.model_selection.RandomizedSearchCV` here: every candidate is evaluated, and the edge story does not depend on which draws were sampled. .. GENERATED FROM PYTHON SOURCE LINES 166-187 .. code-block:: Python edge_search = GridSearchCV( base_pipeline, param_grid={ "histgradientboostingclassifier__learning_rate": [0.05, 0.1], "histgradientboostingclassifier__max_iter": [100, 200], }, cv=3, scoring="neg_log_loss", n_jobs=4, refit=True, ) report_edge = evaluate( edge_search, X=X, y=y, pos_label=1, splitter=splitter, ) .. GENERATED FROM PYTHON SOURCE LINES 188-190 SKD014 should list numeric parameters at search edges as an issue; in the Tips tab, SKD015 should tip because depth / leaf hyperparameters are still missing. .. GENERATED FROM PYTHON SOURCE LINES 190-193 .. code-block:: Python report_edge.checks.summarize(fast_mode=True) .. raw:: html


.. GENERATED FROM PYTHON SOURCE LINES 194-196 .. code-block:: Python report_edge.estimator_.best_params_ .. rst-class:: sphx-glr-script-out .. code-block:: none {'histgradientboostingclassifier__learning_rate': 0.05, 'histgradientboostingclassifier__max_iter': 100} .. GENERATED FROM PYTHON SOURCE LINES 197-199 .. code-block:: Python report_edge.metrics.summarize(data_source="both").frame() .. raw:: html
GridSearchCV (train) GridSearchCV (test)
metric
score -0.068969 -0.185339
accuracy 0.986667 0.935000
precision 0.985050 0.942373
recall 0.988333 0.926667
roc_auc 0.997617 0.978739
log_loss 0.068969 0.185339
brier_score 0.014416 0.052072
fit_time 9.694582 9.694582
predict_time 0.177579 0.078583


.. GENERATED FROM PYTHON SOURCE LINES 200-208 SKD014 & SKD015: widen bounds and add recommended hyperparameters ================================================================= Let us pad beyond the previous two-point edges so those learning-rate values become interior grid points, and add ``max_depth`` so SKD015 clears (``max_depth`` covers the tree-complexity family). ``None`` in ``max_depth`` is non-numeric, so SKD014 ignores that hyperparameter and only watches learning rate: fewer ways for the gallery to flake. .. GENERATED FROM PYTHON SOURCE LINES 208-229 .. code-block:: Python full_search = GridSearchCV( base_pipeline, param_grid={ "histgradientboostingclassifier__learning_rate": [0.01, 0.05, 0.1, 0.2], "histgradientboostingclassifier__max_depth": [3, 5, None], }, cv=3, scoring="neg_log_loss", n_jobs=4, refit=True, ) report_full = evaluate( full_search, X=X, y=y, pos_label=1, splitter=splitter, ) .. GENERATED FROM PYTHON SOURCE LINES 230-234 SKD015 should clear once a recommended complexity hyperparameter is present with learning rate. SKD014 clears when the best learning rate sits strictly inside ``[0.01, 0.05, 0.1, 0.2]`` (not at the padded ends). Check ``best_params_`` against the grid above. .. GENERATED FROM PYTHON SOURCE LINES 234-237 .. code-block:: Python report_full.checks.summarize(fast_mode=True) .. raw:: html


.. GENERATED FROM PYTHON SOURCE LINES 238-240 .. code-block:: Python report_full.estimator_.best_params_ .. rst-class:: sphx-glr-script-out .. code-block:: none {'histgradientboostingclassifier__learning_rate': 0.05, 'histgradientboostingclassifier__max_depth': 5} .. GENERATED FROM PYTHON SOURCE LINES 241-248 Compare search strategies ========================= Hold-out log-loss and ROC AUC for the incomplete grid, the two-point edge grid, and the joint fix. Clearing the findings means the *search design* improved; still judge models on validation metrics and cost, not check status alone. .. GENERATED FROM PYTHON SOURCE LINES 248-264 .. code-block:: Python from skore import compare metrics = ( compare( { "max_iter_only": report, "two_point_edge_grid": report_edge, "padded_recommended_params": report_full, } ) .metrics.summarize(data_source="both", metric=["log_loss", "roc_auc"]) .frame() ) metrics.transpose() .. raw:: html
metric log_loss roc_auc
max_iter_only (train) 0.068406 0.997792
max_iter_only (test) 0.188134 0.978422
two_point_edge_grid (train) 0.068969 0.997617
two_point_edge_grid (test) 0.185339 0.978739
padded_recommended_params (train) 0.078624 0.997178
padded_recommended_params (test) 0.195059 0.976078


.. GENERATED FROM PYTHON SOURCE LINES 265-274 Conclusion ========== SKD015 tips about incomplete search spaces; SKD014 issues when optima stick to the boundary of the box you tried. In this walkthrough, a ``max_iter``-only search missed key hyperparameters, a two-point grid forced edge hits, and one padded complete grid addressed both. Expand the search before deploying ``best_params_``: passing checks are about search hygiene, not a guarantee of the best model. .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 55.319 seconds) .. _sphx_glr_download_auto_examples_pitfalls_and_solutions_plot_skd014_skd015_hyperparameter_search.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: plot_skd014_skd015_hyperparameter_search.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: plot_skd014_skd015_hyperparameter_search.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: plot_skd014_skd015_hyperparameter_search.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_