본문 바로가기

AI & Computer Science

DeLong’s Test를 활용한 AUC 비교: Python 구현 가이드

반응형

머신러닝과 딥러닝 모델을 평가할 때, AUC(Area Under the ROC Curve)는 중요한 성능 지표 중 하나입니다. 특히, 두 모델 간의 성능을 비교할 때는 단순히 AUC 값을 비교하는 것 이상으로, 그 차이가 통계적으로 유의한지를 확인하는 것이 중요합니다. 이를 위해 DeLong’s test라는 강력한 통계 검정 방법을 사용할 수 있습니다. 이번 포스트에서는 Python을 사용하여 DeLong’s test를 구현하는 방법과 그 작동 원리에 대해 설명하겠습니다.

 

 

DeLong’s Test란?

 

DeLong’s test는 두 개의 ROC(Receiver Operating Characteristic) 곡선의 AUC를 비교하여 두 모델 간의 성능 차이가 통계적으로 유의한지를 평가하는 방법입니다. 이 검정은 두 AUC 간의 차이에 대한 p-value를 계산하여, 차이가 우연에 의한 것이 아닐 가능성을 판단하게 해줍니다.

 

Python 코드로 구현하기

이번 포스트에서는 DeLong’s test를 직접 구현하는 방법을 다룰 것입니다. 아래의 코드는 DeLong’s test를 수행하는 주요 함수들로만 구성되어있습니다. 바로 구동되는 코드가 아닙니다 ! 여러분의 작업물에 적용할 수 있도록 함수를 제공한다고 생각해주세요 !
import numpy as np
import scipy.stats

def compute_midrank(x):
    J = np.argsort(x)
    Z = x[J]
    N = len(x)
    T = np.zeros(N, dtype=float)  # 수정: np.float 대신 float 사용
    i = 0
    while i < N:
        j = i
        while j < N and Z[j] == Z[i]:
            j += 1
        T[i:j] = 0.5*(i + j - 1)
        i = j
    T2 = np.empty(N, dtype=float)  # 수정: np.float 대신 float 사용
    T2[J] = T + 1
    return T2

def fastDeLong(predictions_sorted_transposed, label_1_count):
    m = label_1_count
    n = predictions_sorted_transposed.shape[1] - m
    positive_examples = predictions_sorted_transposed[:, :m]
    negative_examples = predictions_sorted_transposed[:, m:]
    k = predictions_sorted_transposed.shape[0]

    tx = np.empty([k, m], dtype=float)  # 수정: np.float 대신 float 사용
    ty = np.empty([k, n], dtype=float)  # 수정: np.float 대신 float 사용
    tz = np.empty([k, m + n], dtype=float)  # 수정: np.float 대신 float 사용
    for r in range(k):
        tx[r, :] = compute_midrank(positive_examples[r, :])
        ty[r, :] = compute_midrank(negative_examples[r, :])
        tz[r, :] = compute_midrank(predictions_sorted_transposed[r, :])
    aucs = tz[:, :m].sum(axis=1) / m / n - float(m + 1.0) / 2.0 / n
    v01 = (tz[:, :m] - tx[:, :]) / n
    v10 = 1.0 - (tz[:, m:] - ty[:, :]) / m
    sx = np.cov(v01)
    sy = np.cov(v10)
    delongcov = sx / m + sy / n
    return aucs, delongcov

def calc_pvalue(aucs, sigma):
    l = np.array([[1, -1]])
    z = np.abs(np.diff(aucs)) / np.sqrt(np.dot(np.dot(l, sigma), l.T))
    return scipy.stats.norm.sf(z) * 2  # Two-sided p-value

def compute_ground_truth_statistics(ground_truth):
    assert np.array_equal(np.unique(ground_truth), [0, 1])
    order = (-ground_truth).argsort()
    label_1_count = int(ground_truth.sum())
    return order, label_1_count

def delong_roc_variance(ground_truth, predictions):
    order, label_1_count = compute_ground_truth_statistics(ground_truth)
    predictions_sorted_transposed = predictions[np.newaxis, order]
    aucs, delongcov = fastDeLong(predictions_sorted_transposed, label_1_count)
    return aucs[0], delongcov

def delong_roc_test(ground_truth, predictions_one, predictions_two):
    order, label_1_count = compute_ground_truth_statistics(ground_truth)
    predictions_sorted_transposed = np.vstack((predictions_one, predictions_two))[:, order]
    aucs, delongcov = fastDeLong(predictions_sorted_transposed, label_1_count)
    return calc_pvalue(aucs, delongcov)

 

 

한줄한줄 파해쳐 볼까요?

import numpy as np
import scipy.stats

def compute_midrank(x):
    J = np.argsort(x)
    Z = x[J]
    N = len(x)
    T = np.zeros(N, dtype=float)
    i = 0
    while i < N:
        j = i
        while j < N and Z[j] == Z[i]:
            j += 1
        T[i:j] = 0.5*(i + j - 1)
        i = j
    T2 = np.empty(N, dtype=float)
    T2[J] = T + 1
    return T2
이 함수는 입력된 데이터 배열의 midrank 값을 계산합니다. Midrank는 DeLong’s test의 기본적인 단계로, 각 예측값의 순위를 계산하고 동일한 값들에 대해 평균 순위를 할당합니다.

 

def fastDeLong(predictions_sorted_transposed, label_1_count):
    m = label_1_count
    n = predictions_sorted_transposed.shape[1] - m
    positive_examples = predictions_sorted_transposed[:, :m]
    negative_examples = predictions_sorted_transposed[:, m:]
    k = predictions_sorted_transposed.shape[0]

    tx = np.empty([k, m], dtype=float)
    ty = np.empty([k, n], dtype=float)
    tz = np.empty([k, m + n], dtype=float)
    for r in range(k):
        tx[r, :] = compute_midrank(positive_examples[r, :])
        ty[r, :] = compute_midrank(negative_examples[r, :])
        tz[r, :] = compute_midrank(predictions_sorted_transposed[r, :])
    aucs = tz[:, :m].sum(axis=1) / m / n - float(m + 1.0) / 2.0 / n
    v01 = (tz[:, :m] - tx[:, :]) / n
    v10 = 1.0 - (tz[:, m:] - ty[:, :]) / m
    sx = np.cov(v01)
    sy = np.cov(v10)
    delongcov = sx / m + sy / n
    return aucs, delongcov
fastDeLong 함수는 DeLong’s test의 핵심 계산을 담당합니다. 이 함수는 두 모델의 예측 결과를 사용하여 AUC와 그에 대한 분산을 계산합니다.

 

def calc_pvalue(aucs, sigma):
    l = np.array([[1, -1]])
    z = np.abs(np.diff(aucs)) / np.sqrt(np.dot(np.dot(l, sigma), l.T))
    return scipy.stats.norm.sf(z) * 2  # Two-sided p-value
이 함수는 두 AUC 값의 차이에 대한 p-value를 계산합니다. p-value는 두 모델 간의 AUC 차이가 통계적으로 유의미한지 여부를 판단하는 데 사용됩니다.

 

def compute_ground_truth_statistics(ground_truth):
    assert np.array_equal(np.unique(ground_truth), [0, 1])
    order = (-ground_truth).argsort()
    label_1_count = int(ground_truth.sum())
    return order, label_1_count
이 함수는 주어진 레이블 데이터를 기반으로 AUC 계산을 위한 정렬 및 클래스 1(양성 클래스) 수를 계산합니다.

 

def delong_roc_variance(ground_truth, predictions):
    order, label_1_count = compute_ground_truth_statistics(ground_truth)
    predictions_sorted_transposed = predictions[np.newaxis, order]
    aucs, delongcov = fastDeLong(predictions_sorted_transposed, label_1_count)
    return aucs[0], delongcov
이 함수는 특정 모델의 예측 결과에 대해 AUC와 그에 대한 분산을 계산하는데 사용됩니다.

 

def delong_roc_test(ground_truth, predictions_one, predictions_two):
    order, label_1_count = compute_ground_truth_statistics(ground_truth)
    predictions_sorted_transposed = np.vstack((predictions_one, predictions_two))[:, order]
    aucs, delongcov = fastDeLong(predictions_sorted_transposed, label_1_count)
    return calc_pvalue(aucs, delongcov)
마지막으로, 이 함수는 두 모델 간의 AUC 차이를 검정합니다. 두 모델의 예측값과 실제 레이블을 입력으로 받아 p-value를 계산합니다.

 

 

DeLong’s test는 두 ROC 곡선의 AUC를 비교하는 데 있어 매우 유용한 방법입니다. 이를 통해 단순한 AUC 비교를 넘어, 그 차이가 우연에 의한 것이 아닐 가능성을 평가할 수 있습니다. 이 포스트에서는 DeLong’s test를 Python으로 직접 구현하는 방법을 다뤘으며, 이 코드를 활용하여 다양한 모델 간의 성능 차이를 검정할 수 있습니다.

 

 

마지막으로

이번 포스트에서는 DeLong’s test를 활용하여 두 모델의 AUC를 비교하는 방법을 살펴보았습니다.
AUC는 머신러닝 모델의 성능을 평가하는 중요한 지표이지만, 단순한 AUC 비교만으로는 두 모델 간의 차이가 실제로 의미 있는지 판단하기 어렵습니다.
DeLong’s test를 통해 이러한 차이가 통계적으로 유의미한지 여부를 확인할 수 있으며, 이를 통해 보다 신뢰할 수 있는 결론을 도출할 수 있습니다.

 

Python으로 직접 구현한 DeLong’s test 코드는 실전에서 바로 사용할 수 있을 만큼 실용적이며, 다양한 모델 간의 성능을 비교하는 데 유용합니다. 앞으로 여러분이 머신러닝 프로젝트에서 여러 모델을 평가할 때, 이 방법을 활용하여 더 깊이 있는 분석을 수행해 보시길 바랍니다.

 

모델 성능 비교는 단순한 수치 이상의 의미를 지닐 수 있습니다. DeLong’s test와 같은 통계적 검정을 통해 더 정확하고 의미 있는 결론을 내릴 수 있도록, 지속적인 연구와 학습을 이어나가시길 바랍니다.

 

앞으로의 프로젝트에서도 이 방법이 도움이 되기를 바랍니다.

반응형