{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# Release Highlights for scikit-learn 1.9\n\n.. currentmodule:: sklearn\n\nWe are pleased to announce the release of scikit-learn 1.9! Many bug fixes\nand improvements were added, as well as some key new features. Below we\ndetail the highlights of this release. **For an exhaustive list of\nall the changes**, please refer to the `release notes `.\n\nTo install the latest version (with pip)::\n\n pip install --upgrade scikit-learn\n\nor with conda::\n\n conda install -c conda-forge scikit-learn\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Callbacks\nThis release introduces **experimental** support for callbacks in scikit-learn. They\nare objects that can be registered on estimators, through the `set_callbacks` method,\nto be invoked at the beginning and end of key steps during fit. See the\n`user guide ` for more details. **Only a few estimators support\ncallbacks for now**, see the\n`list of supported estimators `.\n\nTwo built-in callbacks are provided in this release:\n\n- :class:`~sklearn.callback.ProgressBar`, to display progress bars.\n- :class:`~sklearn.callback.ScoringMonitor`, to compute and log scoring metrics.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "from sklearn.callback import ProgressBar, ScoringMonitor\nfrom sklearn.datasets import make_classification\nfrom sklearn.linear_model import LogisticRegression\n\nX, y = make_classification(\n n_samples=1000, n_features=50, n_classes=10, n_informative=20, random_state=0\n)\n\nscoring_monitor = ScoringMonitor(scoring=\"d2_log_loss_score\")\nlogreg = LogisticRegression(solver=\"lbfgs\")\nlogreg.set_callbacks(scoring_monitor, ProgressBar())\nlogreg.fit(X, y)\n\nlog = scoring_monitor.get_logs().data_as_pandas\nlog[[\"task_name\", \"task_id\", \"d2_log_loss_score\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Progress bars can also be displayed for compositions of estimators.\n\n```python\nfrom sklearn.callback import ProgressBar\nfrom sklearn.datasets import load_iris\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.model_selection import GridSearchCV\n\nX, y = load_iris(return_X_y=True)\nlogreg = LogisticRegression(solver=\"lbfgs\")\ngrid_search = GridSearchCV(logreg, {\"C\": [10, 1, 0.1]}, n_jobs=2)\ngrid_search.set_callbacks(ProgressBar())\ngrid_search.fit(X, y)\n```\nIntermediate output. Note that two sub-tasks progress concurrently because we\nset `n_jobs=2`::\n\n GridSearchCV - fit \u2501\u2501\u2501\u2501\u2501\u2501\u2578 17% 0:00:02\n GridSearchCV - search #0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2578 34% 0:00:01\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #1 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #2 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #3 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #4 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2578 54% 0:00:01\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #5 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 44% 0:00:01\n\nFinal output displaying all the completed nested subtasks::\n\n GridSearchCV - fit \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - search #0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #1 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #0 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #2 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #3 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #4 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #5 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #6 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #7 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #8 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #9 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #10 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #11 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #12 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #13 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - candidate-split-evaluation | LogisticRegression - fit #14 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n GridSearchCV - refit-with-best-params | LogisticRegression - fit #1 \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 100% 0:00:00\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is also a public API to implement callback support in third-party estimators and\nto implement custom callbacks. See the `developer's guide ` for more\ndetails.\n\nNew callbacks and callback support in more estimators will be added in future\nreleases. The callback API is experimental and may evolve without deprecation.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Improvements to the HTML representation of estimators\nThe HTML representation of estimators now includes information made available after\nfit. There is a new \"Fitted attributes\" table that lists the fitted attributes and\ntheir type and values. In addition, the HTML representation of transformers includes\nnew visual blocks showing the number and names of the output features.\n\nExpand the diagram below by clicking on the different visual blocks to see the new\nfeatures.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import pandas as pd\n\nfrom sklearn.compose import make_column_transformer\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.pipeline import make_pipeline\nfrom sklearn.preprocessing import OneHotEncoder, StandardScaler\n\nX = pd.DataFrame({\"num\": [0.1, 0.2, 0.3, 0.4], \"cat\": [\"A\", \"C\", \"B\", \"C\"]})\ny = [1, 3, 1, 2]\n\npipe = make_pipeline(\n make_column_transformer((StandardScaler(), [\"num\"]), (OneHotEncoder(), [\"cat\"])),\n LogisticRegression(),\n)\npipe.fit(X, y)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Computing metrics across thresholds\nA new function :func:`~sklearn.metrics.metric_at_thresholds` has been added to compute\nan arbitrary binary classification metric across all possible decision thresholds.\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n\nfrom sklearn.datasets import make_classification\nfrom sklearn.linear_model import LogisticRegression\nfrom sklearn.metrics import accuracy_score, f1_score, metric_at_thresholds\n\nX, y = make_classification(weights=[0.9, 0.1], random_state=0)\nlr = LogisticRegression().fit(X, y)\ny_score = lr.predict_proba(X)[:, 1]\n\naccuracy, thresholds = metric_at_thresholds(y, y_score, accuracy_score)\nf1, _ = metric_at_thresholds(y, y_score, f1_score)\n\n_, ax = plt.subplots()\nax.plot(thresholds, accuracy, label=\"Accuracy\")\nax.plot(thresholds, f1, label=\"F1\")\nax.set_xlabel(\"threshold\")\nax.set_ylabel(\"metric value\")\nax.legend()\nplt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sparse array configuration\nA new configuration key `\"sparse_interface\"` has been added to control the type of\nsparse objects produced by functions and estimators. It is now possible to produce\nsparse arrays instead of sparse matrices (default).\nThis continues the effort to prepare for\n[SciPy's migration from sparse matrices to sparse arrays](https://docs.scipy.org/doc/scipy/reference/sparse.migration_to_sparray.html).\n\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import sklearn\nfrom sklearn.preprocessing import OneHotEncoder\n\nX = [[\"fox\", \"dog\", \"cat\"]]\nohe = OneHotEncoder()\n\nwith sklearn.config_context(sparse_interface=\"sparray\"):\n Xt = ohe.fit_transform(X)\nXt" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.14.6" } }, "nbformat": 4, "nbformat_minor": 0 }