К обычному разбору
Тренировка по собеседованию1 задачаТехническое собеседованиеДата не указана

БЕЛЫЕ САДЫ 29: ROC-AUC и метрики модерации

Идите сверху вниз: сначала попробуйте сами, затем откройте разбор. Если шаг с кодом, пишите решение прямо здесь и запускайте проверки на странице.

1ЗадачаMedium

ROC-AUC после дублирования классов

Условие

Есть бинарные метки и score классификатора.

Каждый positive объект дублируется positive_copies раз, каждый negative объект дублируется negative_copies раз. Score у копий такой же, как у исходного объекта.

Нужно вернуть ROC-AUC на выборке после такого дублирования, не материализуя все копии.

Считайте ROC-AUC как долю positive-negative пар, где score positive выше score negative. Tie дает 0.5.

Сигнатура

def roc_auc_after_duplication(
    y_true: list[int],
    scores: list[float],
    positive_copies: int,
    negative_copies: int,
) -> float:

Решение прямо на странице

Напишите код, запустите проверки и только потом открывайте разбор.

Проверка решения

Нажмите «Запустить проверки» или Ctrl+Enter.

Показать разбор

Подсказки

  • Pairwise view

    ROC-AUC считайте через пары positive-negative, а не через пороговую кривую.

  • Множители сокращаются

    Каждая исходная пара после дублирования появляется positive_copies * negative_copies раз.

Идея решения

ROC-AUC можно понимать как вероятность, что случайный positive объект получит score выше случайного negative.

Если каждый positive продублировать a раз, а каждый negative b раз, каждая исходная positive-negative пара повторится a * b раз. Числитель и знаменатель ROC-AUC умножатся на один и тот же коэффициент, поэтому значение не изменится.

Эталонный код

def roc_auc_after_duplication(
    y_true: list[int],
    scores: list[float],
    positive_copies: int,
    negative_copies: int,
) -> float:
    if len(y_true) != len(scores):
        raise ValueError('y_true and scores must have the same length')
    if positive_copies <= 0 or negative_copies <= 0:
        raise ValueError('copy counts must be positive')

    positive_scores = []
    negative_scores = []
    for label, score in zip(y_true, scores):
        if label == 1:
            positive_scores.append(score)
        elif label == 0:
            negative_scores.append(score)
        else:
            raise ValueError('labels must be 0 or 1')

    if not positive_scores or not negative_scores:
        raise ValueError('both classes are required')

    wins = 0.0
    for positive_score in positive_scores:
        for negative_score in negative_scores:
            if positive_score > negative_score:
                wins += 1.0
            elif positive_score == negative_score:
                wins += 0.5

    return wins / (len(positive_scores) * len(negative_scores))
Сложность
Время: O(P * N). Память: O(P + N).
Для тренажерной версии достаточно pairwise подсчета по positive и negative score; множители копий сокращаются в числителе и знаменателе.