Spec-Zone.ru › scikit-learn

Примечание

Перейти в конец, чтобы скачать полный код примера или запустить этот пример в браузере с помощью JupyterLite или Binder.

Настройка порога функции принятия решений после обучения

После обучения бинарного классификатора метод predict выдает предсказания меток классов, соответствующие пороговому значению выхода либо decision_function, либо predict_proba. Пороговое значение по умолчанию определяется как апостериорная оценка вероятности 0.5 или оценка решения 0.0. Однако такая стратегия по умолчанию может быть неоптимальной для конкретной задачи.

В этом примере показано, как использовать TunedThresholdClassifierCV для настройки порога принятия решений в зависимости от интересующей нас метрики.

# Authors: The scikit-learn developers
# SPDX-License-Identifier: BSD-3-Clause

Набор данных diabetes

Чтобы проиллюстрировать настройку порога принятия решений, мы воспользуемся набором данных diabetes. Этот набор данных доступен на OpenML: https://www.openml.org/d/37. Для загрузки этого набора данных мы используем функцию fetch_openml.

from sklearn.datasets import fetch_openml

diabetes = fetch_openml(data_id=37, as_frame=True, parser="pandas")
data, target = diabetes.data, diabetes.target

Рассмотрим целевую переменную, чтобы понять, с каким типом задачи мы имеем дело.

target.value_counts()
class
tested_negative    500
tested_positive    268
Name: count, dtype: int64

Мы видим, что имеем дело с задачей бинарной классификации. Поскольку метки не закодированы числами 0 и 1, явно укажем, что класс с меткой “tested_negative” считаем отрицательным (он также является наиболее частым), а класс с меткой “tested_positive” — положительным:

neg_label, pos_label = target.value_counts().index

Также можно заметить, что в этой задаче бинарной классификации наблюдается небольшой дисбаланс: примеров отрицательного класса примерно вдвое больше, чем положительного. При оценке следует учитывать этот аспект, чтобы правильно интерпретировать результаты.

Базовый классификатор

Определим базовую модель предсказания, состоящую из масштабирования данных и следующего за ним классификатора логистической регрессии.

from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

model = make_pipeline(StandardScaler(), LogisticRegression())
model
Pipeline(steps=[('standardscaler', StandardScaler()),
                ('logisticregression', LogisticRegression())])
В среде Jupyter повторно выполните эту ячейку, чтобы отобразить HTML-представление, или доверьте выполнение записной книжки.
На GitHub HTML-представление отобразить нельзя; попробуйте открыть эту страницу на nbviewer.org.
Параметры
steps steps: список кортежей

Список кортежей (имя шага, оцениватель), которые будут объединены
в указанном последовательном порядке. Для совместимости с API scikit-learn все шаги
должны определять `fit`. Все шаги, кроме последнего, также должны определять `transform`. Подробнее см.
:ref:`Объединение оценивателей <combining_estimators>`.
[('standardscaler', ...), ('logisticregression', ...)]
transform_input transform_input: список строк, по умолчанию None

Имена параметров :term:`метаданных`, которые должны быть преобразованы конвейером
перед передачей шагу, который их использует.

Это позволяет преобразовывать некоторые входные аргументы ``fit`` (кроме ``X``)
с помощью шагов конвейера вплоть до шага, которому они нужны.
Необходимость преобразования задается с помощью :ref:`маршрутизации метаданных <metadata_routing>`.
Например, так можно передать проверочную выборку через конвейер.

Этот параметр можно задать, только если включена маршрутизация метаданных. Ее можно
включить с помощью ``sklearn.set_config(enable_metadata_routing=True)``.

.. versionadded:: 1.6
None
memory memory: строка или объект с интерфейсом joblib.Memory, по умолчанию None

Используется для кэширования обученных преобразователей конвейера. Последний шаг
не кэшируется, даже если он является преобразователем. По умолчанию кэширование
не выполняется. Если задана строка, она указывает путь к каталогу кэширования.
Включение кэширования вызывает клонирование преобразователей
перед обучением. Поэтому экземпляр преобразователя, переданный в
конвейер, нельзя проверить напрямую. Используйте атрибут ``named_steps``
или ``steps``, чтобы просмотреть оцениватели внутри конвейера. Кэширование
преобразователей полезно, если обучение занимает много времени. См.
:ref:`sphx_glr_auto_examples_neighbors_plot_caching_nearest_neighbors.py`
с примером включения кэширования.
None
verbose verbose: bool, по умолчанию False

Если задано значение True, по завершении каждого шага будет выводиться время,
затраченное на его обучение.
False
Параметры
copy copy: bool, по умолчанию True

Если задано значение False, выполняется попытка избежать копирования и масштабировать данные на месте.
Это не всегда гарантированно работает на месте; например, если данные
не являются массивом NumPy или разреженной матрицей scipy.sparse CSR,
все равно может быть возвращена копия.
True
with_mean with_mean: bool, по умолчанию True

Если задано значение True, перед масштабированием данные центрируются.
Это не работает (и вызывает исключение) для разреженных матриц,
поскольку их центрирование требует создания плотной матрицы,
которая в типичных случаях может оказаться слишком большой для размещения в
памяти.
True
with_std with_std: bool, по умолчанию True

Если задано значение True, данные масштабируются до единичной дисперсии (или, что эквивалентно,
единичного стандартного отклонения).
True
Параметры
penalty penalty: {'l1', 'l2', 'elasticnet', None}, по умолчанию='l2'

Задаёт норму штрафа:

- `None`: штраф не добавляется;
- `'l2'`: добавляется штрафной член L2; это вариант по умолчанию;
- `'l1'`: добавляется штрафной член L1;
- `'elasticnet'`: добавляются оба штрафных члена — L1 и L2.

.. warning::
Некоторые виды штрафа могут быть несовместимы с некоторыми решателями. См. параметр
`solver` ниже, чтобы узнать о совместимости штрафа и
решателя.

.. versionadded:: 0.19
Штраф L1 с решателем SAGA (поддержка 'multinomial' + L1)

.. deprecated:: 1.8
Параметр `penalty` объявлен устаревшим в версии 1.8 и будет удалён в 1.10.
Вместо него используйте `l1_ratio` и `C`. Для `penalty='l2'` задайте `l1_ratio=0`,
для `penalty='l1'` — `l1_ratio=1`, для `penalty='elasticnet'` задайте `l1_ratio` равным любому числу с плавающей точкой от 0 до 1,
а для `penalty=None` — `C=np.inf`.
'deprecated'
C C: число с плавающей точкой, по умолчанию=1.0

Обратная сила регуляризации; должно быть положительным числом с плавающей точкой.
Как и в методе опорных векторов, меньшие значения задают более сильную
регуляризацию. `C=np.inf` приводит к логистической регрессии без штрафа.
Наглядный пример влияния настройки параметра `C`
при штрафе L1 см. в:
:ref:`sphx_glr_auto_examples_linear_model_plot_logistic_path.py`.
1.0
l1_ratio l1_ratio: число с плавающей точкой, по умолчанию=0.0

Параметр смешивания Elastic-Net, для которого выполняется `0 <= l1_ratio <= 1`. При
`l1_ratio=1` используется только штраф L1, а при `l1_ratio=0` — только штраф L2.
Любое значение от 0 до 1 задаёт штраф Elastic-Net вида
`l1_ratio * L1 + (1 - l1_ratio) * L2`.

.. warning::
Некоторые значения `l1_ratio`, то есть некоторые виды штрафа, могут быть несовместимы с некоторыми
решателями. См. параметр `solver` ниже, чтобы узнать о совместимости штрафа и решателя.

.. versionchanged:: 1.8
Значение по умолчанию изменено с None на 0.0.

.. deprecated:: 1.8
Значение `None` объявлено устаревшим и будет удалено в версии 1.10. Всегда используйте
`l1_ratio`, чтобы указать тип штрафа.
0.0
dual dual: bool, по умолчанию=False

Двойственная (с ограничениями) или прямая (с регуляризацией, см. также
:ref:`это уравнение <regularized-logistic-loss>`) формулировка. Двойственная формулировка
реализована только для штрафа L2 с решателем liblinear. Если n_samples > n_features, предпочтительно значение `dual=False`.
False
tol tol: число с плавающей точкой, по умолчанию=1e-4

Допуск для критерия остановки.
0.0001
fit_intercept fit_intercept: bool, по умолчанию=True

Указывает, следует ли добавлять в функцию принятия решения
константу (также называемую смещением или свободным членом).
True
intercept_scaling intercept_scaling: число с плавающей точкой, по умолчанию=1

Используется только при выборе решателя `liblinear`
и значении `True` у `self.fit_intercept`. В этом случае `x` становится
`[x, self.intercept_scaling]`,
то есть к вектору экземпляра добавляется «синтетический» признак с постоянным значением, равным
`intercept_scaling`.
Свободный член становится равен
``intercept_scaling * synthetic_feature_weight``.

.. note::
Вес синтетического признака подвергается регуляризации L1 или L2, как и все остальные признаки.
Чтобы уменьшить влияние регуляризации на вес синтетического признака
(и, следовательно, на свободный член), следует увеличить `intercept_scaling`.
1
class_weight class_weight: словарь или 'balanced', по умолчанию=None

Веса классов в формате ``{class_label: weight}``.
Если значение не задано, вес всех классов считается равным единице.

Режим "balanced" использует значения y для автоматической корректировки
весов, обратно пропорциональных частотам классов во входных данных:
``n_samples / (n_classes * np.bincount(y))``.

Обратите внимание: если задан sample_weight, эти веса будут умножены на него (он передаётся
через метод fit).

.. versionadded:: 0.17
*class_weight='balanced'*
None
random_state random_state: целое число, экземпляр RandomState, по умолчанию=None

Используется для перемешивания данных, когда ``solver`` == 'sag', 'saga' или 'liblinear'.
Подробности см. в :term:`глоссарии <random_state>`.
None
solver solver: {'lbfgs', 'liblinear', 'newton-cg', 'newton-cholesky', 'sag', 'saga'}, по умолчанию='lbfgs'

Алгоритм, используемый в задаче оптимизации. По умолчанию — 'lbfgs'.
При выборе решателя можно учитывать следующие аспекты:

- 'lbfgs' — хороший вариант по умолчанию, поскольку он достаточно хорошо работает для широкого
круга задач.
- В задачах :term:`многоклассовой классификации` (`n_classes >= 3`) все решатели, кроме
'liblinear', минимизируют полную мультиномиальную функцию потерь; 'liblinear' вызовет
ошибку.
- 'newton-cholesky' — хороший выбор, если
`n_samples` >> `n_features * n_classes`, особенно для категориальных признаков с редкими категориями, закодированных методом one-hot.
Учтите, что объём памяти, используемый этим решателем, квадратично зависит от `n_features * n_classes`,
поскольку он явно вычисляет полную матрицу Гессе.
- Для небольших наборов данных хорошо подходит 'liblinear', тогда как 'sag'
и 'saga' работают быстрее на больших наборах;
- 'liblinear' по умолчанию поддерживает только бинарную классификацию. Чтобы применить схему «один против остальных» для многоклассовой задачи, его можно обернуть в
:class:`~sklearn.multiclass.OneVsRestClassifier`.

.. warning::
Выбор алгоритма зависит от выбранного штрафа (`l1_ratio=0`
для штрафа L2, `l1_ratio=1` для штрафа L1 и `0 < l1_ratio < 1` для
Elastic-Net), а также от поддержки многоклассовой классификации (multinomial):

================= ======================== ======================
решатель l1_ratio многоклассовая классификация
================= ======================== ======================
'lbfgs' l1_ratio=0 да
'liblinear' l1_ratio=1 или l1_ratio=0 нет
'newton-cg' l1_ratio=0 да
'newton-cholesky' l1_ratio=0 да
'sag' l1_ratio=0 да
'saga' 0<=l1_ratio<=1 да
================= ======================== ======================

.. note::
Быстрая сходимость 'sag' и 'saga' гарантируется только для признаков
примерно одинакового масштаба. Данные можно предварительно обработать с помощью
средства масштабирования из :mod:`sklearn.preprocessing`.

.. seealso::
Дополнительную информацию о :class:`LogisticRegression` см. в :ref:`руководстве пользователя <Logistic_regression>`,
а сводную информацию о поддержке решателей и штрафов — в
:ref:`таблице <logistic_regression_solvers>`.

.. versionadded:: 0.17
Решатель стохастического градиентного спуска с усреднением (SAG). Поддержка мультиномиального режима добавлена в
версии 0.18.
.. versionadded:: 0.19
Решатель SAGA.
.. versionchanged:: 0.22
В версии 0.22 решатель по умолчанию изменён с 'liblinear' на 'lbfgs'.
.. versionadded:: 1.2
Решатель newton-cholesky. Поддержка мультиномиального режима добавлена в версии 1.6.
'lbfgs'
max_iter max_iter: целое число, по умолчанию=100

Максимальное количество итераций, выполняемых решателями для достижения сходимости.
100
verbose verbose: целое число, по умолчанию=0

Для решателей liblinear и lbfgs задайте любое положительное значение, чтобы включить
подробный вывод.
0
warm_start warm_start: bool, по умолчанию=False

Если задано значение True, решение предыдущего вызова fit повторно используется в качестве
начального приближения; в противном случае предыдущее решение удаляется.
Не применяется для решателя liblinear. См. :term:`глоссарий <warm_start>`.

.. versionadded:: 0.17
Поддержка *warm_start* для решателей *lbfgs*, *newton-cg*, *sag*, *saga*.
False
n_jobs n_jobs: целое число, по умолчанию=None

Не оказывает никакого влияния.

.. deprecated:: 1.8
Параметр `n_jobs` объявлен устаревшим в версии 1.8 и будет удалён в версии 1.10.
None


Мы оцениваем нашу модель с помощью перекрёстной проверки. Для оценки качества модели мы используем точность и сбалансированную точность. Сбалансированная точность — это метрика, менее чувствительная к дисбалансу классов; она позволяет оценить показатель точности в контексте.

Перекрёстная проверка позволяет изучить дисперсию порога принятия решения на разных разбиениях данных. Однако набор данных довольно мал, и использование более чем 5 фолдов для оценки разброса было бы нежелательным. Поэтому мы используем RepeatedStratifiedKFold, выполняя несколько повторов перекрёстной проверки с 5 фолдами.

import pandas as pd

from sklearn.model_selection import RepeatedStratifiedKFold, cross_validate

scoring = ["accuracy", "balanced_accuracy"]
cv_scores = [
    "train_accuracy",
    "test_accuracy",
    "train_balanced_accuracy",
    "test_balanced_accuracy",
]
cv = RepeatedStratifiedKFold(n_splits=5, n_repeats=10, random_state=42)
cv_results_vanilla_model = pd.DataFrame(
    cross_validate(
        model,
        data,
        target,
        scoring=scoring,
        cv=cv,
        return_train_score=True,
        return_estimator=True,
    )
)
cv_results_vanilla_model[cv_scores].aggregate(["mean", "std"]).T
mean std
train_accuracy 0.779751 0.007822
test_accuracy 0.770926 0.030585
train_balanced_accuracy 0.732913 0.009788
test_balanced_accuracy 0.723665 0.035914


Наша прогностическая модель успешно выявляет взаимосвязь между данными и целевой переменной. Оценки на обучающей и тестовой выборках близки друг к другу, а значит, наша прогностическая модель не переобучается. Также можно заметить, что сбалансированная точность ниже обычной точности из-за упомянутого ранее дисбаланса классов.

Для этого классификатора мы оставили порог принятия решения, используемый для преобразования вероятности положительного класса в предсказание класса, равным значению по умолчанию: 0.5. Однако этот порог может быть не оптимальным. Если мы хотим максимизировать сбалансированную точность, следует выбрать другой порог, который максимизирует эту метрику.

Метаоценщик TunedThresholdClassifierCV позволяет настроить порог принятия решения классификатора с учетом выбранной метрики.

Настройка порога принятия решения

Мы создаем TunedThresholdClassifierCV и настраиваем его для максимизации сбалансированной точности. Мы оцениваем модель с помощью той же стратегии кросс-валидации, что и ранее.

from sklearn.model_selection import TunedThresholdClassifierCV

tuned_model = TunedThresholdClassifierCV(estimator=model, scoring="balanced_accuracy")
cv_results_tuned_model = pd.DataFrame(
    cross_validate(
        tuned_model,
        data,
        target,
        scoring=scoring,
        cv=cv,
        return_train_score=True,
        return_estimator=True,
    )
)
cv_results_tuned_model[cv_scores].aggregate(["mean", "std"]).T
mean std
train_accuracy 0.752470 0.015579
test_accuracy 0.739950 0.036592
train_balanced_accuracy 0.757915 0.009747
test_balanced_accuracy 0.744029 0.035445


По сравнению с обычной моделью мы видим, что показатель сбалансированной точности вырос. Разумеется, это происходит за счет снижения обычной точности. Это означает, что теперь наша модель более чувствительна к положительному классу, но чаще ошибается на отрицательном классе.

Однако важно отметить, что эта настроенная прогностическая модель внутри является той же моделью, что и обычная: у них одинаковые обученные коэффициенты.

import matplotlib.pyplot as plt

vanilla_model_coef = pd.DataFrame(
    [est[-1].coef_.ravel() for est in cv_results_vanilla_model["estimator"]],
    columns=diabetes.feature_names,
)
tuned_model_coef = pd.DataFrame(
    [est.estimator_[-1].coef_.ravel() for est in cv_results_tuned_model["estimator"]],
    columns=diabetes.feature_names,
)

fig, ax = plt.subplots(ncols=2, figsize=(12, 4), sharex=True, sharey=True)
vanilla_model_coef.boxplot(ax=ax[0])
ax[0].set_ylabel("Coefficient value")
ax[0].set_title("Vanilla model")
tuned_model_coef.boxplot(ax=ax[1])
ax[1].set_title("Tuned model")
_ = fig.suptitle("Coefficients of the predictive models")
Coefficients of the predictive models, Vanilla model, Tuned model

Во время кросс-валидации изменялся только порог принятия решения каждой модели.

decision_threshold = pd.Series(
    [est.best_threshold_ for est in cv_results_tuned_model["estimator"]],
)
ax = decision_threshold.plot.kde()
ax.axvline(
    decision_threshold.mean(),
    color="k",
    linestyle="--",
    label=f"Mean decision threshold: {decision_threshold.mean():.2f}",
)
ax.set_xlabel("Decision threshold")
ax.legend(loc="upper right")
_ = ax.set_title(
    "Distribution of the decision threshold \nacross different cross-validation folds"
)
Distribution of the decision threshold  across different cross-validation folds

В среднем порог принятия решения около 0.32 максимизирует сбалансированную точность, что отличается от значения порога по умолчанию, равного 0.5. Поэтому настройка порога принятия решения особенно важна, когда выходные данные прогностической модели используются для принятия решений. Кроме того, метрику для настройки порога принятия решения следует выбирать внимательно. Здесь мы использовали сбалансированную точность, но она может быть не самой подходящей метрикой для рассматриваемой задачи. Выбор «правильной» метрики обычно зависит от задачи и может потребовать специальных знаний в предметной области. Подробнее см. пример Настройка порога принятия решения после обучения для обучения с учетом затрат.

Общее время выполнения скрипта: (0 минут 41.564 секунды)

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_tuned_decision_threshold.ipynb

Download Python source code: plot_tuned_decision_threshold.py

Download zipped: plot_tuned_decision_threshold.zip

Связанные примеры

Настройка порога принятия решения после обучения для обучения с учетом затрат

Основные нововведения scikit-learn 1.5

Влияние изменения порога на самообучение

Примеры использования FrozenEstimator

© 2007–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/auto_examples/model_selection/plot_tuned_decision_threshold.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API