ROC curve가 나오게 된 배경, 이유, 한계, 활용, 해석
1. 배경
ROC는 원래 2차 세계대전 당시 레이더를 조작하던 엔지니어들이 오경보확률과 적중확률을 매칭시키기 위해 개발하였다고 합니다. 레이더 운용자(Receiver Operator)가 쓴다고 하여 이름도 Receiver Operating Characteristics입니다.
그려보기
실제로 한 번 그려봅시다.
| Actual | Predicted |
|---|---|
| 1 | 0.9 |
| 1 | 0.8 |
| 1 | 0.7 |
| 1 | 0.6 |
| 1 | 0.55 |
| 1 | 0.54 |
| 1 | 0.53 |
| 0 | 0.52 |
| 0 | 0.51 |
| 0 | 0.505 |
| 0 | 0.504 |
| 0 | 0.503 |
| 0 | 0.502 |
AOC 곡선을 그릴 때에는 Threshold를 움직여가며 점을 찍습니다. 예컨데 Threshold를 0.9로 잡으면 TPR=1/7, FPR=0/6이 됩니다. 0.8로 Threshold를 잡는다면 TPR=2/7, FPT=0.6이 되고, 이렇게 Threshold를 계속 조정해가면 아래와 같이 점이 찍히게 됩니다.
| Actual | Predicted | ROC 곡선 위의 점 |
|---|---|---|
| 1 | 0.9 | (0, 1/7) |
| 1 | 0.8 | (0, 2/7) |
| 1 | 0.7 | (0, 3/7) |
| 1 | 0.6 | (0, 4/7) |
| 1 | 0.55 | (0, 5/7) |
| 1 | 0.54 | (0, 6/7) |
| 1 | 0.53 | (0, 7/7) |
| 0 | 0.52 | (1/6, 7/7) |
| 0 | 0.51 | (2/6, 7/7) |
| 0 | 0.505 | (3/6, 7/7) |
| 0 | 0.504 | (4/6, 7/7) |
| 0 | 0.503 | (5/6, 7/7) |
| 0 | 0.502 | (6/6, 7/7) |
AOC를 구해보면 1이 나옵니다. 완벽한 모델이라 볼 수 있습니다. 모델이 특정한 Threshold를 기점으로 양성과 음성으로 명확히 나뉘어질 수 있다는 뜻입니다.
import numpy as np
import matplotlib.pyplot as plt
# x += 1/6 at odds step, y += 1/7 at even step
points = np.array([[0, 1/7],[0, 2/7], [0, 3/7], [0, 4/7], [0, 5/7], [0, 6/7], [0, 7/7], [1/6, 1], [2/6, 1], [3/6, 1], [4/6, 1], [5/6, 1], [6/6,1]])
plt.plot(points[:,0], points[:,1], 'o-')

반대로 그냥 아무렇게나 분류를 한다고 생각해봅시다.
| Actual | Predicted | ROC 곡선 위의 점 |
|---|---|---|
| 1 | 0.9 | (0, 1/7) |
| 0 | 0.8 | (1/6, 1/7) |
| 1 | 0.7 | (1/6, 2/7) |
| 0 | 0.6 | (2/6, 2/7) |
| 1 | 0.55 | (2/6, 3/7) |
| 0 | 0.54 | (3/6, 3/7) |
| 1 | 0.53 | (3/6, 4/7) |
| 0 | 0.52 | (4/6, 4/7) |
| 1 | 0.51 | (4/6, 5/7) |
| 0 | 0.505 | (5/6, 5/7) |
| 1 | 0.504 | (5/6, 6/7) |
| 0 | 0.503 | (6/6, 6/7) |
| 1 | 0.502 | (6/6, 7/7) |
대강 점을 찍어보면 아래와 같이 나옵니다.
import numpy as np
import matplotlib.pyplot as plt
# x += 1/6 at odds step, y += 1/7 at even step
points = np.array([((i // 2) * 1/6, (i // 2 + i % 2) * 1/7) for i in range(1,14)])
plt.plot(points[:,0], points[:,1], 'o-')

코드를 약간 수정하면 아래와 같습니다.
import numpy as np
import matplotlib.pyplot as plt
max = 10000
points = np.array([((i // 2) * 1/(max // 2), (i // 2 + i % 2) * 1/(max // 2)) for i in range(1,max)])
plt.plot(points[:,0], points[:,1], 'o-')

즉 완벽하게 랜덤으로 분류하는 모델은 45도로 기울어진 직선의 그래프가 나오고, 어떤 Threshold를 기점으로 양성과 음성을 정확하게 나눌 수 있다면 좌상단에 점이 찍힌 직선이 나옵니다.
2. 왜 쓰는가

결국 ROC 커브는 모델이 예측할 때의 확신도가 실제 확률 분포와 더 일치하는 지를 보여줍니다. 더 정확하게 말해서 ROC 커브는 어떤 모델의 예측값이 진양성을 얼마나 잘 예측하는지를 보여줍니다. Precision보다는 Recall을 더 잘 반영한다고도 볼 수 있습니다.
더욱이 ROC 커브의 면적을 구하면 모델의 성능을 정량적으로 평가할 수 있습니다. ROC 커브가 좌상단에 가까울수록 위양성이 적다는 의미니까요. ROC 커브 아래의 이 면적을 AUC(Area Under Curve)라고 합니다.
Multi-class 모델에서도 일단 Threshold를 면이나 곡면으로 잡아야 겠지만 그래도 쓸 수는 있습니다.
3. 한계
Multi-class 모델에서도 이론상 쓸 수는 있지만 면이나 곡면을 움직여야 하므로 굉장히 복잡해지며, 해석도 난감해집니다.
무엇보다 위음성이 많은 경우나 데이터가 편향되어 분포된 경우 (P(x=1)이 0.01인 경우 등) F1 score와 부조화가 상당히 심해집니다. Precision이 낮아지는 것을 ROC 커브는 반영하지 못하니까요.