Примечание
Перейти к концу для скачивания полного примера кода. Или запустите этот пример в вашем браузере через JupyterLite или Binder
Постобработка настройки точки отсечения функции принятия решений
После обучения бинарного классификатора метод predict выводит предсказания меток классов, соответствующие пороговому значению либо decision_function, либо результату predict_proba. По умолчанию порог определяется как оценка вероятности апостериорной вероятности 0,5 или значение принятия решения 0,0. Однако эта стратегия по умолчанию может не быть оптимальной для поставленной задачи.
В этом примере показано, как использовать TunedThresholdClassifierCV для настройки порога принятия решений в зависимости от интересующей метрики.
# Authors: The scikit-learn developers # SPDX-License-Identifier: BSD-3-Clause
Набор данных по диабету
Для иллюстрации настройки порога принятия решений мы будем использовать набор данных по диабету. Этот набор данных доступен на 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
Мы оцениваем нашу модель с помощью перекрестной проверки. Мы используем точность и сбалансированную точность для отчета о производительности нашей модели. Сбалансированная точность – это метрика, которая менее чувствительна к несбалансированности классов и позволит нам поставить показатель точности в перспективу.
Перекрестная проверка позволяет изучить дисперсию порога принятия решений при разных разбиениях данных. Однако набор данных довольно мал, и использование более 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
Наша предсказательная модель успешно улавливает взаимосвязь между данными и целевой переменной. Значения точности на обучающей и тестовой выборках близки, что означает, что наша предсказательная модель не переобучается. Также мы можем заметить, что сбалансированная точность ниже, чем точность, из-за упомянутой ранее несбалансированности классов.
Для этого классификатора мы оставляем порог принятия решений, используемый для преобразования вероятности положительного класса в предсказание класса, на своем значении по умолчанию: 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
В сравнении с базовой моделью, мы наблюдаем увеличение значения сбалансированной точности. Конечно, это происходит за счёт снижения общей точности. Это означает, что наша модель теперь более чувствительна к положительному классу, но допускает больше ошибок на отрицательном классе.
Однако важно отметить, что эта настроенная модель предсказания внутренне идентична базовой модели: они имеют одинаковые подобранные коэффициенты.
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")

Только порог принятия решений каждой модели был изменён во время перекрёстной проверки.
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"
)

В среднем, порог принятия решений около 0.32 максимизирует сбалансированную точность, что отличается от стандартного порога в 0.5. Таким образом, настройка порога принятия решений особенно важна, когда выходная информация модели используется для принятия решений. Кроме того, метрика, используемая для настройки порога принятия решений, должна быть выбрана тщательно. Здесь мы использовали сбалансированную точность, но она может не быть самой подходящей метрикой для данной задачи. Выбор «правильной» метрики обычно зависит от задачи и может потребовать определённых предметных знаний. См. пример под названием Настройка порога принятия решений для задач с учётом затрат для более подробной информации.
Общее время выполнения скрипта: (0 минут 35.994 секунды)
Связанные примеры
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/auto_examples/model_selection/plot_tuned_decision_threshold.html