Spec-Zone.ru › scikit-learn

Примечание

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

Рабочая характеристика приемника (ROC) для многоклассовой классификации

В этом примере описывается использование метрики рабочей характеристики приемника (ROC) для оценки качества многоклассовых классификаторов.

На ROC-кривых обычно по оси Y откладывается доля истинно положительных результатов (TPR), а по оси X — доля ложноположительных результатов (FPR). Это означает, что верхний левый угол графика — «идеальная» точка: FPR равна нулю, а TPR — единице. На практике такое встречается редко, но обычно чем больше площадь под кривой (AUC), тем лучше. Важна и «крутизна» ROC-кривых: в идеале нужно максимизировать TPR и минимизировать FPR.

ROC-кривые обычно используют в задачах бинарной классификации, где TPR и FPR можно однозначно определить. В случае многоклассовой классификации понятия TPR и FPR можно определить только после бинаризации выходных данных. Это можно сделать двумя способами:

  • стратегия «один против остальных» сравнивает каждый класс со всеми остальными, рассматриваемыми как один класс;
  • стратегия «один против одного» сравнивает все уникальные пары классов.

В этом примере мы рассмотрим обе стратегии и покажем, как с помощью микро- и макроусреднения обобщать данные многоклассовых ROC-кривых.

Примечание

См. Рабочая характеристика приемника (ROC) с перекрестной проверкой, где приведено расширение этого примера, позволяющее оценить дисперсию ROC-кривых и соответствующих значений AUC.

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

Загрузка и подготовка данных

Мы импортируем набор данных растений ириса, содержащий 3 класса, каждый из которых соответствует одному виду ириса. Один класс линейно отделим от двух других, которые не являются линейно отделимыми друг от друга.

Здесь мы бинаризуем выходные данные и добавляем шумовые признаки, чтобы усложнить задачу.

import numpy as np

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

iris = load_iris()
target_names = iris.target_names
X, y = iris.data, iris.target
y = iris.target_names[y]

random_state = np.random.RandomState(0)
n_samples, n_features = X.shape
n_classes = len(np.unique(y))
X = np.concatenate([X, random_state.randn(n_samples, 200 * n_features)], axis=1)
(
    X_train,
    X_test,
    y_train,
    y_test,
) = train_test_split(X, y, test_size=0.5, stratify=y, random_state=0)

Мы обучаем модель LogisticRegression, которая может естественным образом обрабатывать многоклассовые задачи благодаря использованию мультиномиальной формулировки.

from sklearn.linear_model import LogisticRegression

classifier = LogisticRegression()
y_score = classifier.fit(X_train, y_train).predict_proba(X_test)

Многоклассовая ROC-кривая «один против остальных»

Многоклассовая стратегия «один против остальных» (OvR), также известная как «один против всех», заключается в вычислении ROC-кривой для каждого из n_classes. На каждом шаге заданный класс считается положительным, а все остальные классы вместе — отрицательными.

Примечание

Не следует путать стратегию OvR, используемую для оценки многоклассовых классификаторов, со стратегией OvR, используемой для обучения многоклассового классификатора путем подгонки набора бинарных классификаторов (например, с помощью метаоценщика OneVsRestClassifier). Оценку ROC по OvR можно использовать для анализа любых моделей классификации независимо от способа их обучения (см. раздел Многоклассовые алгоритмы и алгоритмы с несколькими выходами).

В этом разделе мы используем LabelBinarizer, чтобы бинаризовать целевой вектор с помощью one-hot-кодирования в стиле OvR. Это означает, что целевой вектор формы (n_samples,) преобразуется в целевой вектор формы (n_samples, n_classes).

from sklearn.preprocessing import LabelBinarizer

label_binarizer = LabelBinarizer().fit(y_train)
y_onehot_test = label_binarizer.transform(y_test)
y_onehot_test.shape  # (n_samples, n_classes)
(75, 3)

Также можно легко проверить кодирование определенного класса:

label_binarizer.transform(["virginica"])
array([[0, 0, 1]])

ROC-кривая для конкретного класса

На следующем графике показана ROC-кривая, полученная при разделении цветов ириса на «virginica» (class_id=2) и «не virginica» (все остальные).

class_of_interest = "virginica"
class_id = np.flatnonzero(label_binarizer.classes_ == class_of_interest)[0]
class_id
np.int64(2)
import matplotlib.pyplot as plt

from sklearn.metrics import RocCurveDisplay

display = RocCurveDisplay.from_predictions(
    y_onehot_test[:, class_id],
    y_score[:, class_id],
    name=f"{class_of_interest} vs the rest",
    curve_kwargs=dict(color="darkorange"),
    plot_chance_level=True,
    despine=True,
)
_ = display.ax_.set(
    xlabel="False Positive Rate",
    ylabel="True Positive Rate",
    title="One-vs-Rest ROC curves:\nVirginica vs (Setosa & Versicolor)",
)
One-vs-Rest ROC curves: Virginica vs (Setosa & Versicolor)

ROC-кривая для OvR с микроусреднением

При микроусреднении вклады всех классов объединяются (с помощью numpy.ravel), чтобы вычислить усредненные метрики следующим образом:

\(TPR=\frac{\sum_{c}TP_c}{\sum_{c}(TP_c + FN_c)}\) ;

\(FPR=\frac{\sum_{c}FP_c}{\sum_{c}(FP_c + TN_c)}\) .

Мы вкратце покажем эффект применения numpy.ravel:

print(f"y_score:\n{y_score[0:2, :]}")
print()
print(f"y_score.ravel():\n{y_score[0:2, :].ravel()}")
y_score:
[[0.38 0.05 0.57]
 [0.07 0.28 0.65]]

y_score.ravel():
[0.38 0.05 0.57 0.07 0.28 0.65]

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

display = RocCurveDisplay.from_predictions(
    y_onehot_test.ravel(),
    y_score.ravel(),
    name="micro-average OvR",
    curve_kwargs=dict(color="darkorange"),
    plot_chance_level=True,
    despine=True,
)
_ = display.ax_.set(
    xlabel="False Positive Rate",
    ylabel="True Positive Rate",
    title="Micro-averaged One-vs-Rest\nReceiver Operating Characteristic",
)
Micro-averaged One-vs-Rest Receiver Operating Characteristic

Если нас интересует не столько график, сколько само значение ROC-AUC, его можно воспроизвести с помощью roc_auc_score.

from sklearn.metrics import roc_auc_score

micro_roc_auc_ovr = roc_auc_score(
    y_test,
    y_score,
    multi_class="ovr",
    average="micro",
)

print(f"Micro-averaged One-vs-Rest ROC AUC score:\n{micro_roc_auc_ovr:.2f}")
Micro-averaged One-vs-Rest ROC AUC score:
0.77

Это эквивалентно вычислению ROC-кривой с помощью roc_curve, а затем площади под кривой с помощью auc для развернутых векторов истинных и предсказанных классов.

from sklearn.metrics import auc, roc_curve

# store the fpr, tpr, and roc_auc for all averaging strategies
fpr, tpr, roc_auc = dict(), dict(), dict()
# Compute micro-average ROC curve and ROC area
fpr["micro"], tpr["micro"], _ = roc_curve(y_onehot_test.ravel(), y_score.ravel())
roc_auc["micro"] = auc(fpr["micro"], tpr["micro"])

print(f"Micro-averaged One-vs-Rest ROC AUC score:\n{roc_auc['micro']:.2f}")
Micro-averaged One-vs-Rest ROC AUC score:
0.77

Примечание

По умолчанию при вычислении ROC-кривой добавляется одна точка при максимальном значении FPR с помощью линейной интерполяции и поправки Макклиша [Анализ части ROC-кривой. Med Decis Making. Июль–сентябрь 1989; 9(3):190–5.].

ROC-кривая для OvR с макроусреднением

Для макроусреднения нужно вычислить метрику отдельно для каждого класса, а затем усреднить значения, изначально считая все классы равнозначными. Сначала объединим значения долей истинно положительных и ложноположительных результатов для каждого класса:

\(TPR=\frac{1}{C}\sum_{c}\frac{TP_c}{TP_c + FN_c}\) ;

\(FPR=\frac{1}{C}\sum_{c}\frac{FP_c}{FP_c + TN_c}\) .

где C — общее количество классов.

for i in range(n_classes):
    fpr[i], tpr[i], _ = roc_curve(y_onehot_test[:, i], y_score[:, i])
    roc_auc[i] = auc(fpr[i], tpr[i])

fpr_grid = np.linspace(0.0, 1.0, 1000)

# Interpolate all ROC curves at these points
mean_tpr = np.zeros_like(fpr_grid)

for i in range(n_classes):
    mean_tpr += np.interp(fpr_grid, fpr[i], tpr[i])  # linear interpolation

# Average it and compute AUC
mean_tpr /= n_classes

fpr["macro"] = fpr_grid
tpr["macro"] = mean_tpr
roc_auc["macro"] = auc(fpr["macro"], tpr["macro"])

print(f"Macro-averaged One-vs-Rest ROC AUC score:\n{roc_auc['macro']:.2f}")
Macro-averaged One-vs-Rest ROC AUC score:
0.78

Эти вычисления эквивалентны простому вызову

macro_roc_auc_ovr = roc_auc_score(
    y_test,
    y_score,
    multi_class="ovr",
    average="macro",
)

print(f"Macro-averaged One-vs-Rest ROC AUC score:\n{macro_roc_auc_ovr:.2f}")
Macro-averaged One-vs-Rest ROC AUC score:
0.78

Все ROC-кривые OvR на одном графике

from itertools import cycle

fig, ax = plt.subplots(figsize=(6, 6))

plt.plot(
    fpr["micro"],
    tpr["micro"],
    label=f"micro-average ROC curve (AUC = {roc_auc['micro']:.2f})",
    color="deeppink",
    linestyle=":",
    linewidth=4,
)

plt.plot(
    fpr["macro"],
    tpr["macro"],
    label=f"macro-average ROC curve (AUC = {roc_auc['macro']:.2f})",
    color="navy",
    linestyle=":",
    linewidth=4,
)

colors = cycle(["aqua", "darkorange", "cornflowerblue"])
for class_id, color in zip(range(n_classes), colors):
    RocCurveDisplay.from_predictions(
        y_onehot_test[:, class_id],
        y_score[:, class_id],
        name=f"ROC curve for {target_names[class_id]}",
        curve_kwargs=dict(color=color),
        ax=ax,
        plot_chance_level=(class_id == 2),
        despine=True,
    )

_ = ax.set(
    xlabel="False Positive Rate",
    ylabel="True Positive Rate",
    title="Extension of Receiver Operating Characteristic\nto One-vs-Rest multiclass",
)
Extension of Receiver Operating Characteristic to One-vs-Rest multiclass

Многоклассовая ROC-кривая «один против одного»

Многоклассовая стратегия «один против одного» (OvO) заключается в обучении одного классификатора для каждой пары классов. Поскольку для этого нужно обучить n_classes * (n_classes - 1) / 2 классификаторов, этот метод обычно медленнее стратегии «один против остальных» из-за сложности O(n_classes ^2).

В этом разделе мы покажем макроусредненное значение AUC с использованием стратегии OvO для трех возможных сочетаний классов в наборе данных растений ириса: «setosa» против «versicolor», «versicolor» против «virginica» и «virginica» против «setosa». Обратите внимание, что для стратегии OvO микроусреднение не определено.

ROC-кривая для OvO с макроусреднением

В стратегии OvO сначала нужно определить все возможные уникальные пары. Оценки вычисляются путем рассмотрения одного из элементов пары как положительного класса, а другого — как отрицательного, после чего роли меняются местами и вычисляется среднее двух оценок.

from itertools import combinations

pair_list = list(combinations(np.unique(y), 2))
print(pair_list)
[(np.str_('setosa'), np.str_('versicolor')), (np.str_('setosa'), np.str_('virginica')), (np.str_('versicolor'), np.str_('virginica'))]
pair_scores = []
mean_tpr = dict()

for ix, (label_a, label_b) in enumerate(pair_list):
    a_mask = y_test == label_a
    b_mask = y_test == label_b
    ab_mask = np.logical_or(a_mask, b_mask)

    a_true = a_mask[ab_mask]
    b_true = b_mask[ab_mask]

    idx_a = np.flatnonzero(label_binarizer.classes_ == label_a)[0]
    idx_b = np.flatnonzero(label_binarizer.classes_ == label_b)[0]

    fpr_a, tpr_a, _ = roc_curve(a_true, y_score[ab_mask, idx_a])
    fpr_b, tpr_b, _ = roc_curve(b_true, y_score[ab_mask, idx_b])

    mean_tpr[ix] = np.zeros_like(fpr_grid)
    mean_tpr[ix] += np.interp(fpr_grid, fpr_a, tpr_a)
    mean_tpr[ix] += np.interp(fpr_grid, fpr_b, tpr_b)
    mean_tpr[ix] /= 2
    mean_score = auc(fpr_grid, mean_tpr[ix])
    pair_scores.append(mean_score)

    fig, ax = plt.subplots(figsize=(6, 6))
    plt.plot(
        fpr_grid,
        mean_tpr[ix],
        label=f"Mean {label_a} vs {label_b} (AUC = {mean_score:.2f})",
        linestyle=":",
        linewidth=4,
    )
    RocCurveDisplay.from_predictions(
        a_true,
        y_score[ab_mask, idx_a],
        ax=ax,
        name=f"{label_a} as positive class",
    )
    RocCurveDisplay.from_predictions(
        b_true,
        y_score[ab_mask, idx_b],
        ax=ax,
        name=f"{label_b} as positive class",
        plot_chance_level=True,
        despine=True,
    )
    ax.set(
        xlabel="False Positive Rate",
        ylabel="True Positive Rate",
        title=f"{target_names[idx_a]} vs {label_b} ROC curves",
    )

print(f"Macro-averaged One-vs-One ROC AUC score:\n{np.average(pair_scores):.2f}")
  • setosa vs versicolor ROC curves
  • setosa vs virginica ROC curves
  • versicolor vs virginica ROC curves
Macro-averaged One-vs-One ROC AUC score:
0.78

Также можно убедиться, что вычисленное нами «вручную» макроусреднение эквивалентно параметру average="macro", реализованному в функции roc_auc_score.

macro_roc_auc_ovo = roc_auc_score(
    y_test,
    y_score,
    multi_class="ovo",
    average="macro",
)

print(f"Macro-averaged One-vs-One ROC AUC score:\n{macro_roc_auc_ovo:.2f}")
Macro-averaged One-vs-One ROC AUC score:
0.78

Все ROC-кривые OvO на одном графике

ovo_tpr = np.zeros_like(fpr_grid)

fig, ax = plt.subplots(figsize=(6, 6))
for ix, (label_a, label_b) in enumerate(pair_list):
    ovo_tpr += mean_tpr[ix]
    ax.plot(
        fpr_grid,
        mean_tpr[ix],
        label=f"Mean {label_a} vs {label_b} (AUC = {pair_scores[ix]:.2f})",
    )

ovo_tpr /= sum(1 for pair in enumerate(pair_list))

ax.plot(
    fpr_grid,
    ovo_tpr,
    label=f"One-vs-One macro-average (AUC = {macro_roc_auc_ovo:.2f})",
    linestyle=":",
    linewidth=4,
)
ax.plot([0, 1], [0, 1], "k--", label="Chance level (AUC = 0.5)")
_ = ax.set(
    xlabel="False Positive Rate",
    ylabel="True Positive Rate",
    title="Extension of Receiver Operating Characteristic\nto One-vs-One multiclass",
    aspect="equal",
    xlim=(-0.01, 1.01),
    ylim=(-0.01, 1.01),
)
Extension of Receiver Operating Characteristic to One-vs-One multiclass

Мы подтверждаем, что линейный классификатор не позволяет хорошо различать классы «versicolor» и «virginica». Обратите внимание, что значение ROC-AUC для «virginica» против остальных (0.77) находится между значениями ROC-AUC OvO для «versicolor» против «virginica» (0.64) и «setosa» против «virginica» (0.90). Стратегия OvO действительно дает дополнительную информацию о смешении пары классов, но требует больше вычислительных ресурсов при большом количестве классов.

Стратегия OvO рекомендуется, если пользователя в первую очередь интересует правильное распознавание определенного класса или подмножества классов. При этом общую производительность классификатора по-прежнему можно обобщить с помощью выбранной стратегии усреднения.

При работе с несбалансированными наборами данных крайне важно выбирать подходящую метрику с учетом бизнес-контекста или решаемой задачи. Также важно выбрать подходящий метод усреднения (микро- или макроусреднение) в зависимости от желаемого результата:

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

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

Launch binder
Launch JupyterLite

Download Jupyter notebook: plot_roc.ipynb

Download Python source code: plot_roc.py

Download zipped: plot_roc.zip

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

Рабочая характеристика приемника (ROC) с перекрестной проверкой

Кривая компромисса между обнаружением и ошибкой (DET)

ROC-кривая с API визуализации

Визуализация с помощью объектов отображения

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

Spec-Zone.ru

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