ROC curve는 Receiver Operating Characteristic curve 의 약자이다. 이것은 여러 모델의 성능을 평가하기 위해 민감도 sensitivity 및 특이도 specificity 로 이루어진 그래프이고, 이 내용을 예제를 통해서 이해하고 ROC curve 를 그린다.
참고도서: R을 활용한 기계학습, 브레드 란츠, page 366~399 내용을 참조했다.
스팸메일인지를 판단하는 이진 분류기가 있다. 메일이 스팸(spam)인지 아니면 정상(ham)인지를 분류한다. 스팸이 positive 속성값이다. 이 모델의 성능 평가를 ROC curve 를 통해서 알아보자.
먼저, ROC curve 를 이해하기 위해서는 예측확률, 민감도, 특이도를 알아야 한다.
일반적인 지도학습 알고리즘 중 클래스를 이진 분류하는 알고리즘은 입력값을 받아서 출력값을 예측을 할때, 어느 정도 확신을 갖고 예측을 하는지에 대한 정보를 같이 준다. 이를 예측확률이라고 부른다. 예를 들어, 2개의 이진 분류 모델이 있는데 A 모델은 90%의 확률로 스팸메일이라고 예측하고 B 모델은 51%의 확률로 스팸메일이라고 예측한다면, 어떤 모델을 선택하겠는가?
민감도는 실제 스팸메일에 대해서 스팸이라고 판정한 비율이 되고, 특이도는 실제 정상메일에 대해서 정상이라고 판정한 비율이 된다.
따라서 민감도와 특이도가 모두 높으면 좋겠지만, 어느 정도 trade off 가 있다. 민감도가 높으면 특이도가 낮아지고, 민감도가 낮으면 특이도가 높아진다. 예를 들어, 모두 스팸이라고 예측하면 민감도가 1이 되고 특이도는 0이 된다. 모두 정상메일이라고 예측하면 민감도는 0이 되고 특이도는 1이 된다. (1-특이도) 의 값은 거짓 긍정 비율(False Positive Rate, fpr) 이고 ROC curve 는 거짓 긍정 값에 따른 참 긍정 비율(True Positive Rate, tpr) 이라고 볼 수 있다.
여러개의 모델 성능을 비교할 때 정확도처럼 하나의 값으로 비교할 수 있다. 하지만, 정확도는 비슷하지만 서로 다른 예측확률을 가진다면 모델 성능 비교를 어떻게 하지? 이것이 ROC curve 를 그리는 이유가 된다. ROC curve 를 그려서 plot의 하단인 면적을 구하고 이 면적의 크기를 비교한다. 이 면적이 AUC (Area under the ROC curve) 가 된다.
핵심 아이디어: 예측확률이 높을수록 좋은 모델이라는건 알겠는데, 어떻게 이 내용을 평가할 수 있을까?
ROC curve 를 그리기 위해 3가지 데이터를 준비한다. 이들 데이터는 분류 모델을 training set 으로 학습하면 나오는 데이터이다.
보통 분류기는 스팸일 확률 50% 이상이면, 스팸으로 분류한다. 따라서 ROC curve 를 그리기 위해 실제로 필요한 정보는 actual_type, prob_spam 2가지이다.
아래 예제에서는 전체 메일 1000 건중 300건이 스팸이고 700건이 정상메일인 훈련데이터 셋을 임의로 만들었다.
#install.packages("ROCR")
library(ROCR)
# spam 구분: spam: 스팸메일, ham: 정상메일
mail_class<- c('spam', 'ham')
mail_spam_count <- 300 # spam 갯수
mail_ham_count <- 700 # 정상메일 갯수
# 실제범주를 입력데이터로 임의로 생성한다.
actual_type <- c(rep('spam', mail_spam_count), rep('ham', mail_ham_count))
actual_type <- actual_type[sample(length(actual_type))] # 랜덤 섞기
actual_type_len <- length(actual_type) # 입력데이터 전체 길이
# 실제범주를 대상으로 스팸일 확률을 무작위로 생성한다.
prob_spam <- seq(0, 1, length.out = actual_type_len) # 예측 확률
# 위 데이터를 데이터프레임으로 구성한다.
sms_result <- data.frame('actual_type'=actual_type, 'prob_spam'=prob_spam)
# ROC curve 를 그리기 위한 데이터를 생성한다.
my_roc <- function (predictions, labels) {
# 예측확률이 민감도와 특이도에 어떤 영향을 미칠까?
# 예를 들어, cutoff 확률을 0에서 1로 조금씩 증가시키면서, 예측확률이 이보다 높을 경우의
# 민감도와 특이도를 계산해보자.
cutoff_prob <- seq(0, 1.1, by=0.001)
# 결과값을 저장하는 데이터프레임을 생성한다.
df_output = data.frame('sensitivity'=c(), 'specificity'=c(), 'accuracy'=c())
labels_count = length(labels) # 전체 갯수
# cutoff 확률을 증가시키면서 민감도와 특이도 값을 구한다.
for(cp in cutoff_prob) {
# 예측확률이 cutoff 확률보다 큰 것을 대상으로
#cp = 0
ix = predictions >= cp
# 예측확률 >= cutoff 이면, 모두 스팸메일이라고 예측을 하고
TP <- sum(labels[ix] == 'spam') # True Positive: 실제 스팸메일 건수
FP <- sum(labels[ix] == 'ham') # Fale Positive: 실제 정상메일 건수
# 예측확률 < cutoff 이면, 모두 정상메일이라고 예측을 하고
TN <- sum(labels[! ix] == 'ham') # True Negative: 실제 정상메일 건수
FN <- sum(labels[! ix] == 'spam') # False Negative: 실제 스팸메일 건수
# 정확도 accuracy 계산
accuracy <- (TP + TN) / (TP + FP + TN + FN)
#accuracy <- (TP + TN) / labels_count
# 민감도와 특이도 계산
sensitvity <- TP / (TP + FN) # 민감도: 전체 스팸메일 중 스펨메일이라고 판단한 비율
specificity <- TN / (FP + TN) # 특이도: 전체 정상메일 중 정상메일이라고 판단한 비율
# 위예서 계산된 민감도, 특이도 정보를 데이터 프레임으로 저장한다.
df_output <- rbind(df_output, data.frame('sensitivity'=sensitvity,
'specificity'=specificity, 'accuracy'=accuracy))
}
# (1-특이도) 값을 구한다.
df_output$one_minus_specificity <- 1 - df_output$specificity
# (1-특이도) 기준으로 값을 정렬한다.
#df_output <- df_output[order(df_output$one_minus_specificity),]
return (df_output)
}
my_perf = my_roc(predictions = sms_result$prob_spam, labels = sms_result$actual_type)
# ROC curve 를 그린다.
plot(my_perf$one_minus_specificity, my_perf$sensitivity, type='l',
main="My ROC curve", ylab='True positive rate',
xlab = 'False positive rate', xlim=c(0, 1), ylim=c(0, 1))
위 내용을 R 패키지를 이용해서 ROC curve를 그려보고, AUC 값을 확인해보자.
pred <- prediction(predictions = sms_result$prob_spam, labels = sms_result$actual_type)
#pred
perf <- performance(pred, measure = "tpr", x.measure = "fpr")
plot(perf, main="ROC curve for SMS spam filter", col="blue", lwd=3)
# AUC를 계산한다.
perf.auc <- performance(pred, measure = "auc")
unlist(perf.auc@y.values)
## [1] 0.51
이번에는 책에서 예제로 제공하는 csv 파일을 이용해서 ROC curve 를 그려보자.
sms_result1 <- read.csv('https://raw.githubusercontent.com/PacktPublishing/Machine-Learning-with-R-Third-Edition/master/Chapter10/sms_results.csv')
head(sms_result1)
## actual_type predict_type prob_spam prob_ham
## 1 ham ham 0.00000 1
## 2 ham ham 0.00000 1
## 3 ham ham 0.00016 1
## 4 ham ham 0.00004 1
## 5 spam spam 1.00000 0
## 6 ham ham 0.00020 1
# 위에서 만든 함수로 그린다.
my_perf <- my_roc(predictions = sms_result1$prob_spam, labels = sms_result1$actual_type)
# ROC curve 를 그린다.
plot(my_perf$one_minus_specificity, my_perf$sensitivity, type='l',
main="My ROC curve", ylab='True positive rate',
xlab = 'False positive rate', xlim=c(0, 1), ylim=c(0, 1))
abline(a=0, b=1, lwd=2, lty=2)
# R 패키지를 이용해서 그린다.
pred <- prediction(predictions = sms_result1$prob_spam, labels = sms_result1$actual_type)
perf <- performance(pred, measure = "tpr", x.measure = "fpr")
plot(perf, main="ROC curve for SMS spam filter", col="blue", lwd=3)
abline(a=0, b=1, lwd=2, lty=2)
# AUC를 계산한다.
perf.auc <- performance(pred, measure = "auc")
unlist(perf.auc@y.values)
## [1] 0.98