Curbe ROC în două linii de cod

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

(Acest articol a fost publicat pentru prima dată pe R Funcționeazăși cu amabilitate a contribuit la R-bloggeri). (Puteți raporta problema legată de conținutul acestei pagini aici)


Doriți să vă distribuiți conținutul pe R-bloggeri? dați clic aici dacă aveți un blog, sau aici dacă nu aveți.

Nota editorului: Această postare a apărut pentru prima dată pe blogul Revolution Analytics/Microsoft, „Revolutions” pe 2 august 2016. Întrucât blogul Revolutions nu mai există, îl republicăm aici cu permisiunea autorului.

Curbele ROC sunt utilizate în mod obișnuit pentru a caracteriza compromisurile sensibilitate/specificitate pentru un clasificator binar. Majoritatea clasificatoarelor de învățare automată produc scoruri cu valoare reală care corespund cu puterea predicției că un anumit caz este pozitiv. Transformarea acestor scoruri cu valoare reală în predicții da sau nu necesită stabilirea unui prag; cazurile cu scoruri peste prag sunt clasificate drept pozitive, iar cazurile cu scoruri sub prag sunt estimate a fi negative. Valorile de prag diferite dau niveluri diferite de sensibilitate și specificitate. Un prag ridicat este mai conservator în ceea ce privește etichetarea unui caz ca pozitiv; acest lucru îl face mai puțin probabil să producă rezultate fals pozitive, dar mai probabil să rateze cazurile care sunt de fapt pozitive (rată mai scăzută de pozitive adevărate). Un prag scăzut produce etichete pozitive mai liberal, deci este mai puțin specific (mai multe pozitive false), dar și mai sensibil (mai multe pozitive adevărate). Curba ROC grafică rata pozitivă adevărată față de rata pozitivă fals, oferind o imagine a întregului spectru de astfel de compromisuri.

Există pachete utilizate în mod obișnuit pentru a trasa aceste curbe și pentru a calcula valorile din ele, dar poate fi totuși util să ne gândim la modul în care sunt calculate aceste curbe pentru a încerca să înțelegem mai bine ceea ce ne arată. Aici vă prezint o funcție simplă pentru a calcula o curbă ROC dintr-un set de rezultate și scoruri asociate.

Calculul are două etape:

  1. Sortați rezultatele observate după scorurile prezise, ​​cu cele mai mari scoruri mai întâi.
  2. Calculați rata pozitivă adevărată (TPR) și rata negativă adevărată (TNR) cumulativă pentru rezultatele observate ordonate.
simple_roc <- function(labels, scores){
  labels <- labels(order(scores, decreasing=TRUE))
  data.frame(TPR=cumsum(labels)/sum(labels), FPR=cumsum(!labels)/sum(!labels), labels)
}

Funcția are două intrări: labels este un vector boolean cu clasificarea reală a fiecărui caz și scores este un vector de scoruri de predicție cu valoare reală atribuite de un clasificator.

Deoarece acesta este un rezultat binar, vectorul etichetelor este o serie de valori TRUE și FALS (sau uni și zerouri, dacă preferați). Vă puteți gândi la această serie de valori binare ca la o secvență de instrucțiuni pentru grafica țestoasă, doar în acest caz țestoasa are o busolă și ia instrucțiuni în termeni de direcții absolute ale diagramei (Nord sau Est) în loc de stânga sau dreapta relativă. Țestoasa începe de la origine (cum fac țestoasele) și urmărește o cale de-a lungul paginii dictată de secvența de instrucțiuni. Când vede unul (adevărat), face un pas spre nord (în direcția y pozitivă); când vede un zero (FALS) face un pas spre Est (direcția x pozitivă). Dimensiunile pașilor de-a lungul fiecărei axe sunt scalate astfel încât, odată ce țestoasa le-a văzut pe toate, aceasta va fi la 1,0 pe axa y, iar odată ce a văzut toate zerourile, va fi la 1,0 pe axa x. Calea de-a lungul paginii este determinată de ordinea celor și a zerourilor și se termină întotdeauna în colțul din dreapta sus.

Progresul țestoasei de-a lungul biților șirului de instrucțiuni reprezintă ajustarea pragului de clasificare pentru a fi din ce în ce mai puțin strict. Odată ce țestoasa a trecut puțin, a decis să clasifice acel bit drept pozitiv. Dacă bitul a fost de fapt pozitiv, este un adevărat pozitiv; altfel este un fals pozitiv. Axa y arată rata pozitivă adevărată (TPR), care este numărul de pozitive adevărate întâlnite până acum împărțit la numărul total de pozitive reale. Axa x arată rata de fals pozitive (numărul de fals pozitive întâlnite până în acel moment împărțit la numărul total de negative reale). Implementarea vectorizată a acestei logici folosește sume cumulate (funcția cumsum) în loc să parcurgă valorile una câte una, deși asta face computerul la un nivel inferior.

O „curbă” ROC calculată în acest fel este de fapt o funcție pas. Dacă ați avea un număr foarte mare de cazuri pozitive și negative, acești pași ar fi foarte mici, iar curba ar părea netedă. (Dacă doriți de fapt să trasați curbele ROC pentru un număr mare de cazuri, ar putea fi problematic să reprezentați fiecare punct; acesta este motivul pentru care funcțiile ROC de nivel producție necesită mai mult de două linii de cod.)

De exemplu, vom simula date despre widget-uri. Aici avem o caracteristică de intrare x care este liniar legată de un rezultat latent y care include și o anumită aleatorie. Valoarea lui y determină dacă widgetul depășește cerințele de toleranță; dacă o face, este un widget prost.

Cod
set.seed(1)
sim_widget_data <- function(N, noise=100){
  x <- runif(N, min=0, max=100)
  y <- 122 - x/2 + rnorm(N, sd=noise)
  bad_widget <- factor(y > 100)
  data.frame(x, y, bad_widget)
}
widget_data <- sim_widget_data(500, 10)

test_set_idx <- sample(1:nrow(widget_data), size=floor(nrow(widget_data)/4))

test_set <- widget_data(test_set_idx,)
training_set <- widget_data(-test_set_idx,)
library(ggplot2)
library(dplyr)
test_set %>% 
  ggplot(aes(x=x, y=y, col=bad_widget)) + 
  scale_color_manual(values=c("black", "red")) + 
  geom_point() + 
  ggtitle("Bad widgets related to x")

Rezervăm aproximativ 1/4 din cazuri pentru setul de testare, iar restul îl vom folosi pentru antrenarea unui model predictiv. Graficul arată setul de testare, deoarece acestea sunt datele pe care le vom folosi pentru a genera curbele ROC. Dacă x este sub aproximativ 20, toate punctele sunt roșii, iar dacă este peste aproximativ 80, toate sunt negre. Între este o regiune cu incertitudine variabilă, cu mai mult roșu la un capăt și mai mult negru la celălalt.

Folosim setul de antrenament pentru a se potrivi unui model de regresie logistică folosind caracteristica x pentru a prezice dacă un anumit widget este probabil să fie rău. Acest model va fi folosit pentru a genera scoruri pentru setul de teste, care vor fi utilizate împreună cu etichetele reale ale cazurilor de testare pentru a calcula curbele ROC. Valorile din vectorul de scoruri nu vor apărea în grafic; sunt folosite doar pentru sortarea etichetelor. Două clasificatoare care pun etichetele în aceeași ordine vor avea exact aceeași curbă ROC, indiferent de valorile absolute ale scorurilor. Acest lucru este arătat prin compararea curbei ROC pe care o obțineți folosind fie predicțiile „răspuns”, fie „legătura” dintr-un model de regresie logistică. Scorurile „răspuns” au fost mapate în intervalul între 0 și 1 printr-o funcție sigmoidă, iar scorurile „link” nu. Dar oricare dintre aceste scoruri va pune punctele în aceeași ordine.

Cod
fit_glm <- glm(bad_widget ~ x, training_set, family=binomial(link="logit"))

glm_link_scores <- predict(fit_glm, test_set, type="link")

glm_response_scores <- predict(fit_glm, test_set, type="response")

score_data <- data.frame(link=glm_link_scores, 
                         response=glm_response_scores,
                         bad_widget=test_set$bad_widget,
                         stringsAsFactors=FALSE)

score_data %>% 
  ggplot(aes(x=link, y=response, col=bad_widget)) + 
  scale_color_manual(values=c("black", "red")) + 
  geom_point() + 
  geom_rug() + 
  ggtitle("Both link and response scores put cases in the same order")

Aici curba ROC pentru scorurile de răspuns din modelul de regresie logistică este calculată cu cele utilizate pe scară largă pROC pachet și trasat ca o linie galbenă. The simple_roc funcția a fost folosită și pentru a calcula o curbă ROC, dar în acest caz este calculată din scorurile linkului. Deoarece ambele seturi de scoruri pun etichetele în aceeași ordine și din moment ce ambele funcții fac în esență același lucru, obținem aceeași curbă. Punctele curbei simple_roc sunt trasate ca cercuri deschise, care aterizează exact deasupra liniei galbene. Fiecare punct reprezintă un singur caz în setul de testare, iar culorile conturului cercurilor arată dacă acel caz a fost un „widget rău” (roșu) sau nu (negru). Cercurile roșii îi spun broaștei țestoase să meargă spre nord, iar cercurile negre îi spun să meargă spre est.

Cod
library(pROC)
plot(roc(test_set$bad_widget, glm_response_scores, direction="<"),
         col="yellow", lwd=3, asp=1, main="The turtle finds its way")

glm_simple_roc <- simple_roc(test_set$bad_widget=="TRUE", glm_link_scores)
with(glm_simple_roc, points(1 - FPR, TPR, col=1 + labels))

Rețineți că pROC pachetul etichetează axa x „Specificie” cu 1,0 în stânga și 0 în dreapta (specificitatea este 1 minus rata fals pozitive). Aceasta înseamnă că a trebuit să facem o scădere similară pentru a reprezenta rezultatele simple_roc în același mod. De asemenea, lăsat la dispoziție, roc decide cum să eticheteze cazurile și controalele în funcție de grupul care are scorul median mai mare, ceea ce înseamnă că poate inversa un ASC negativ pentru a fi pozitiv (practic, un predictor greșit în mod consecvent poate fi totuși util dacă folosiți psihologia inversă). Specific direction=“<” pentru a preveni acest lucru, deoarece funcția simple_roc nu este atât de inteligentă.

Aceasta aduce o altă limitare a acestei abordări simple; presupunând că ordinea de clasare a rezultatelor încorporează informații predictive din model, nu gestionează în mod corespunzător secvențele de cazuri care au toate același scor. Țestoasa presupune că ordinea etichetelor are sens, dar în situația unor scoruri identice nu există o ordine semnificativă. Aceste segmente ar trebui să fie reprezentate corect printr-o linie diagonală, în timp ce broasca țestoasă simplă va trasa cu bucurie pași fără sens.

Vom arăta un caz extrem prin crearea unui set de date dezechilibrat care este pozitiv doar în aproximativ 1% din cazuri. Pentru predicție, ghicim întotdeauna că rezultatul va fi negativ (atingând o precizie de 99%). Deoarece toate scorurile sunt aceleași, nu avem cu adevărat nicio bază pentru sortarea rezultatelor; pROC se descurcă corect și desenează o linie diagonală. Țestoasa presupune că ordinea cazurilor înseamnă ceva când de fapt nu este, și este nevoie de un fel de mers aleatoriu până în colțul din dreapta sus.

Cod
set.seed(1)
N <- 2000
P <- 0.01
rare_success <- sample(c(TRUE, FALSE), N, replace=TRUE, prob=c(P, 1-P))
guess_not <- rep(0, N)
plot(roc(rare_success, guess_not), print.auc=TRUE)

simp_roc <- simple_roc(rare_success, guess_not)
with(simp_roc, lines(1 - FPR, TPR, col="blue", lty=2))

Cod
simp_roc2 <- simple_roc(rare_success, runif(length(guess_not)))

Dacă repetați această simulare cu o valoare mai mare de N, veți vedea că calea broaștei țestoase tinde să aproximeze diagonala mai îndeaproape, dar cu cât rezultatele sunt mai dezechilibrate, cu atât este mai mare numărul total de cazuri, probabil că va trebui să împiedicați traseele să se apropie de diagonală.

Pentru un exemplu mai puțin extrem, puteți genera de obicei segmente diagonale într-o curbă ROC obișnuită prin rotunjirea scorurilor, astfel încât mai multe puncte să obțină ranguri identice; acesta este lăsat ca un exercițiu pentru cititor.

Deoarece curbele ROC sunt atât de instructive și utilizate în mod obișnuit, merită puțin studiu și contemplare. Pentru informații suplimentare, recomand această aplicație strălucitoare care arată curbe ROC cu valori continue calculate din distribuții de probabilitate și lucrarea excelentă a lui Tom Fawcett intitulată O introducere în analiza ROC.

Bob Horton și-a început cariera ca biolog molecular, studiind genele implicate în răspunsurile imune (evoluția MHC și repertoriul TCR) și dezvoltând tehnici de inginerie genetică. Analiza și simularea datelor biologice l-au condus către știința datelor și interesele sale actuale, care includ căutarea semantică a datelor text și modelarea deciziilor.

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.