ROC Curve ve AUC Nedir? Sınıflandırma Modellerini Karşılaştırma

AUC %85 doğruluk demek değildir. ROC eğrisinin iki eksenini elle hesaplamaktan AUC'nin gerçek yorumuna, eşiğin neden ayrı bir iş kararı olduğundan dengesiz veride neden yanıltabileceğine kadar.

  • Makine Öğrenmesi
  • İstatistik

ROC eğrisi neyi gösterir?

İkili bir sınıflandırıcı aslında doğrudan "evet/hayır" üretmez. Her gözlem için pozitif sınıfa ait bir skor ya da olasılık verir. "Evet" kararı, bu skoru bir eşikle karşılaştırdığınızda ortaya çıkar. Eşiği 0,5'ten 0,3'e indirdiğinizde model daha cömert davranır: daha çok gerçek pozitif yakalar ama daha çok yanlış alarm da üretir.

ROC eğrisi (Receiver Operating Characteristic) işte bu değiş tokuşun haritasıdır. Olası her eşik için iki oran hesaplanır ve bir nokta olarak çizilir.

İki eksen: Yatay eksen FPR = FP / (FP + TN) — gerçek negatiflerin ne kadarını yanlışlıkla pozitif işaretlediniz. Dikey eksen TPR = TP / (TP + FN) — gerçek pozitiflerin ne kadarını yakaladınız. TPR aynı zamanda duyarlılık (recall) demektir.

Eşiği çok yüksek tuttuğunuzda neredeyse hiçbir şey pozitif seçilmez; iki oran da sıfıra yaklaşır ve eğri sol alt köşeden başlar. Eşiği en aşağı indirdiğinizde her şey pozitif sayılır; iki oran da 1'e ulaşır ve eğri sağ üst köşede biter. Arasındaki yol, modelin pozitifleri negatiflerden ne kadar iyi sıraladığını gösterir.

Tek bir eşiği elle hesaplayalım

Soyut kalmaması için somut bir örnek: 100 gerçek pozitif ve 900 gerçek negatif gözlemimiz olsun. Belirli bir eşikte model şu sonucu veriyor.

Model: PozitifModel: NegatifToplam
Gerçek: PozitifTP = 80FN = 20100
Gerçek: NegatifFP = 90TN = 810900

Buradan TPR = 80 / 100 = 0,80 ve FPR = 90 / 900 = 0,10 çıkar. Bu eşik, ROC eğrisi üzerinde (0,10 ; 0,80) noktasıdır. Eşiği biraz daha düşürürseniz nokta sağa ve yukarı kayar: daha çok hasta yakalarsınız, daha çok sağlıklı kişiyi boşuna uyarırsınız.

Dikkat edilecek bir nokta: 90 yanlış pozitif, oran olarak sadece %10 görünüyor. Ama mutlak sayı olarak 90 kişi — bu, gerçek bir operasyonda 90 ek telefon, 90 ek inceleme demek. Oranın küçük olması iş yükünün küçük olduğu anlamına gelmez.

ROC sizin için eşik seçmez. Eğri, modelin bütün eşiklerdeki davranışını özetler. Sahada kullanacağınız eşik, yanlış negatif ile yanlış pozitifin maliyetine bakılarak ayrıca seçilir.

AUC ne anlama gelir?

AUC (Area Under the Curve), ROC eğrisinin altında kalan alandır ve tek bir sayıya iner. En kullanışlı yorumu şudur: rastgele seçilmiş bir pozitif örneğin skoru, rastgele seçilmiş bir negatif örneğin skorundan ne sıklıkta yüksektir? Eşit skorlar bu hesapta yarım sayılır.

Yani AUC = 0,85 demek, "model %85 doğru tahmin ediyor" demek değildir. "Rastgele bir pozitif–negatif çiftinde, pozitif olanın skoru %85 olasılıkla daha yüksektir" demektir. Bu ayrım önemli, çünkü AUC bir doğruluk ölçüsü değil, bir sıralama kalitesi ölçüsüdür.

AUCSezgiNasıl yorumlanır?
0,50Yazı turaBu veride ayırt edici sinyal yok denecek kadar az.
0,70Bir miktar ayrımYeterli mi, tamamen kullanım amacına bağlı.
0,85Güçlü sıralamaÇoğu eşikte pozitifler negatiflerin üstünde.
0,99+Neredeyse kusursuzÖnce veri sızıntısı olup olmadığına bakın.

Son satırı özellikle vurgulamak istiyorum. Gerçek bir projede 0,99'un üzerinde AUC gördüğünüzde ilk tepkiniz sevinmek değil, şüphelenmek olmalı. Genellikle açıklaması şudur: hedefin bir türevi özellik olarak modele sızmıştır, ya da eğitim ve test kümeleri arasında aynı kayıt tekrarlanmıştır.

Ayrıca bu değerler evrensel kalite etiketi değildir. Tıbbi tarama ile reklam tıklama tahmininde "iyi AUC" aynı sayı değildir; alan, veri kalitesi ve hata maliyeti belirleyicidir.

Python ile ROC ve AUC

Aşağıdaki örnek, scikit-learn'ün meme kanseri veri kümesinde bir lojistik regresyon eğitiyor. Bu veri kümesinde sınıf 1 benign (iyi huylu) etiketine karşılık gelir; pozitif sınıfı açıkça belirtmek, sonradan ters okumaların önüne geçer.

Model, skorlar ve ROC eğrisi — Python
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.25,
    stratify=data.target, random_state=42
)

model = make_pipeline(
    StandardScaler(),
    LogisticRegression(max_iter=2000)
).fit(X_train, y_train)

# Sütun 1, model.classes_[1] sınıfına aittir
y_score = model.predict_proba(X_test)[:, 1]

fpr, tpr, thresholds = roc_curve(y_test, y_score, pos_label=1)
auc = roc_auc_score(y_test, y_score)
print(f"AUC: {auc:.3f}")    # AUC: 0.998

plt.plot(fpr, tpr, label=f"Lojistik regresyon (AUC={auc:.3f})")
plt.plot([0, 1], [0, 1], "--", color="gray", label="Rastgele")
plt.xlabel("Yanlış pozitif oranı (FPR)")
plt.ylabel("Doğru pozitif oranı (TPR)")
plt.legend(); plt.grid(alpha=0.25); plt.show()

Bu veri kümesinde AUC yaklaşık 0,998 çıkar. Tam da yukarıda uyardığım bölgede — ama burada açıklaması masum: bu küme ders kitabı örneği olarak seçilmiş, sınıfları gerçekten iyi ayrılan bir veridir. Kendi projenizde aynı sayıyı gördüğünüzde aynı rahatlıkla kabul etmeyin.

Kritik ayrıntı: ROC için modelin predict() çıktısını (0/1 etiketler) değil, predict_proba() veya decision_function() skorunu kullanın. Sadece etiket verirseniz eğri tek bir noktaya çöker ve AUC anlamını yitirir. Ayrıca predict_proba sütunlarının sırası model.classes_ ile belirlenir; pozitif sınıfın hangi sütunda olduğunu doğrulayın.

İki modeli karşılaştırmak

Model karşılaştırmasında kural basit ama sık ihlal edilir: aynı test kümesi, aynı pozitif sınıf tanımı, aynı ön işleme. Bunlardan biri değişirse karşılaştırma geçersizdir.

İki modelin AUC'si
from sklearn.ensemble import RandomForestClassifier

modeller = {
    "Lojistik regresyon": model,
    "Random Forest": RandomForestClassifier(
        n_estimators=300, random_state=42
    ).fit(X_train, y_train),
}

for ad, clf in modeller.items():
    skor = clf.predict_proba(X_test)[:, 1]
    print(f"{ad}: AUC={roc_auc_score(y_test, skor):.3f}")

# Lojistik regresyon: AUC=0.998
# Random Forest:      AUC=0.995

Burada 0,998 ile 0,995 arasındaki fark, 143 gözlemlik tek bir test bölmesinden geliyor. Bu kadar küçük bir farkı "lojistik regresyon daha iyi" diye raporlamak yanıltıcı olur. Küçük veri kümelerinde tek bölme yerine çapraz doğrulama yapıp sonuçların hem ortalamasını hem de değişkenliğini verin.

Bir başka incelik: iki modelin ROC eğrileri kesişebilir. Genel alanı büyük olan model, sizin çalışmayı planladığınız düşük FPR bölgesinde daha kötü olabilir. Eğrileri aynı grafiğe çizin ve ilgilendiğiniz aralığa bakın.

Eşik seçimi ayrı bir karardır

AUC yüksek diye model sahada iyi çalışmaz; eşiği de doğru seçmeniz gerekir. Üstelik bu seçim istatistiksel değil, iş kararıdır: iki hata türünün maliyeti nedir?

SenaryoPahalı olan hataEşik yönü
Hastalık taramasıHastayı kaçırmak (FN)Düşür — recall'ü yükselt
Spam filtresiÖnemli e-postayı engellemek (FP)Yükselt — precision'ı koru
Dolandırıcılık tespitiDuruma göre değişirİnceleme kapasitesine göre ayarla
Churn arama listesiBoşa yapılan arama (FP)Kapasiteye göre üstten N kişi seç

Son satır pratikte çok işe yarar. Çağrı merkeziniz günde 200 arama yapabiliyorsa, "eşik kaç olmalı" sorusunun cevabı aslında "skoru en yüksek 200 kişiyi al"dır. Eşik, kapasitenin bir sonucu olur.

Önemli: Eşiği test kümesinde en iyi sonucu verecek şekilde ayarlamayın. Bu, test setini gizlice eğitim verisine çevirir. Eşiği doğrulama verisinde seçin, test kümesini son değerlendirme için saklayın.

Sınıf dengesizliğinde ROC yanıltabilir

ROC'nin FPR paydasında gerçek negatifler vardır. Negatif sınıf çok büyükse bu payda şişer ve FPR olduğundan küçük görünür. Somut bir örnek verelim: 1.000.000 işlemde 1.000 dolandırıcılık var. Modeliniz FPR = 0,01 ile çalışıyor; kulağa harika geliyor. Ama bu, 999.000 temiz işlemin %1'i, yani yaklaşık 9.990 yanlış alarm demek. Gerçek dolandırıcılıkların hepsini yakalasanız bile, inceleme listenizin onda dokuzu boş çıkacak.

Bu yüzden nadir pozitif sınıf durumunda Precision–Recall eğrisi ve Average Precision (AP) daha bilgilendiricidir. PR eğrisi, "pozitif dediklerimin ne kadarı gerçekten pozitifti?" sorusunu doğrudan görünür kılar ve negatif sınıfın büyüklüğünden ROC kadar etkilenmez.

Raporunuzda ROC AUC'nin yanına şunları koyun: sınıf oranı, PR-AUC veya AP, seçtiğiniz eşikteki confusion matrix ve o eşikte beklenen mutlak hata sayıları. Yüzdeler değil, sayılar karar verdirir.

AUC'nin söylemedikleri

1

Eşik performansı

AUC bütün eşiklerin ortalaması gibidir; sizin kullanacağınız tek eşikte ne olacağını söylemez.

2

Kalibrasyon

Skorlar iyi sıralanmış olabilir ama "0,8" gerçekten %80 olasılık anlamına gelmeyebilir. Kalibrasyon ayrı ölçülür.

3

Belirsizlik

Tek bir test örneklemindeki AUC bir tahmindir. 0,91 ile 0,89 arasındaki fark gürültü olabilir.

4

Alt gruplar

Genel AUC yüksekken belirli bir müşteri segmentinde model çok daha kötü olabilir. Ayrı ayrı bakın.

5

Veri kayması

Test anındaki dağılım canlıda değişirse AUC de değişir. Üretimde izlemeye devam edin.

6

Çok sınıflılık

İkiden fazla sınıfta one-vs-rest mi one-vs-one mu, macro mu weighted mi — yöntemi raporda belirtin.

Kalibrasyon konusu özellikle gözden kaçar. Bir skoru doğrudan beklenen değer hesabında kullanacaksanız (örneğin "bu müşterinin ayrılma olasılığı × yaşam boyu değeri"), sıralama yetmez; olasılığın gerçekten olasılık olması gerekir. Bunun için kalibrasyon eğrisi veya Brier skoru kullanın.

Uygulamalı değerlendirme akışı

Altı adımda değerlendirme
  1. Pozitif Sınıfı
    Tanımla
  2. Veriyi
    Böl
  3. Skorları
    Al
  4. ROC ve AUC
    Hesapla
  5. Eşiği
    Seç
  6. Testte
    Raporla
  1. Pozitif sınıfı iş açısından tanımlayın ve iki hata türünün maliyetini yazın.
  2. Eğitim, doğrulama ve test bölmelerini ayırın; zaman boyutu varsa kronolojik bölün.
  3. Olasılık veya karar skorunu alın, etiketleri değil.
  4. ROC eğrisini çizin, AUC'yi bağımsız veride hesaplayın.
  5. Eşiği doğrulama verisinde iş hedefine göre seçin.
  6. Test kümesinde confusion matrix, precision, recall ve mutlak hata sayılarını raporlayın.

Sonuç

ROC eğrisi, bir sınıflandırıcının farklı eşiklerde yakaladığı pozitifler ile ürettiği yanlış alarmlar arasındaki dengeyi gösterir. AUC bu eğriyi tek sayıya indirir ve modelin sıralama kalitesini özetler — doğruluğunu değil.

Pratikte en işe yarayan yaklaşım, AUC'yi bir başlangıç noktası olarak görmektir. Modeli seçerken AUC'ye bakın; sahaya çıkarırken eşiği, mutlak hata sayılarını ve o eşikteki confusion matrix'i konuşun. Sınıf dengesizse PR eğrisini de ekleyin. Tek bir sayı hiçbir zaman kararın tamamı olmamalı.

What does a ROC curve show?

A binary classifier does not really produce "yes/no" directly. For every observation it gives a score or probability for the positive class. The "yes" decision appears only when you compare that score against a threshold. Lower the threshold from 0.5 to 0.3 and the model becomes more generous: it catches more true positives, but it also raises more false alarms.

The ROC (Receiver Operating Characteristic) curve is the map of that trade-off. For every possible threshold, two rates are computed and plotted as a point.

The two axes: the horizontal axis is FPR = FP / (FP + TN) — how many of the true negatives you wrongly flagged as positive. The vertical axis is TPR = TP / (TP + FN) — how many of the true positives you caught. TPR is also what we call sensitivity, or recall.

Keep the threshold very high and almost nothing is selected as positive; both rates approach zero and the curve starts in the bottom-left corner. Lower it all the way and everything counts as positive; both rates reach 1 and the curve ends in the top-right corner. The path in between shows how well the model ranks positives above negatives.

Working one threshold out by hand

To keep it concrete: suppose we have 100 true positives and 900 true negatives. At a particular threshold the model produces this.

Model: PositiveModel: NegativeTotal
Actual: PositiveTP = 80FN = 20100
Actual: NegativeFP = 90TN = 810900

From this, TPR = 80 / 100 = 0.80 and FPR = 90 / 900 = 0.10. That threshold is the point (0.10, 0.80) on the ROC curve. Drop the threshold a little further and the point moves right and up: you catch more sick patients and you needlessly alarm more healthy ones.

One thing worth noticing: 90 false positives looks like a mere 10%. But in absolute terms that is 90 people — 90 extra phone calls, 90 extra reviews in a real operation. A small rate does not mean a small workload.

ROC does not pick a threshold for you. The curve summarises the model's behaviour across all thresholds. The threshold you actually deploy is chosen separately, by weighing the cost of a false negative against a false positive.

What does AUC mean?

AUC (Area Under the Curve) is the area beneath the ROC curve, collapsed into one number. Its most useful reading is this: how often does a randomly chosen positive example score higher than a randomly chosen negative one? Ties count as a half.

So AUC = 0.85 does not mean "the model is 85% accurate". It means "in a random positive–negative pair, the positive one scores higher 85% of the time". That distinction matters, because AUC is not an accuracy measure — it is a measure of ranking quality.

AUCIntuitionHow to read it
0.50A coin flipAlmost no discriminating signal in this data.
0.70Some separationWhether that is enough depends entirely on the use case.
0.85Strong rankingAt most thresholds, positives sit above negatives.
0.99+Nearly perfectCheck for data leakage before celebrating.

I want to underline that last row. In a real project, an AUC above 0.99 should make you suspicious rather than pleased. The usual explanation is that a derivative of the target leaked into the features, or that the same record appears in both the training and test sets.

These values are also not universal quality labels. "Good AUC" is a different number in medical screening than in ad click prediction; the domain, the data quality and the cost of errors decide.

ROC and AUC in Python

The example below trains a logistic regression on scikit-learn's breast cancer dataset. In that dataset class 1 corresponds to the benign label; stating the positive class explicitly prevents readings that come out backwards later.

Model, scores and the ROC curve — Python
import matplotlib.pyplot as plt
from sklearn.datasets import load_breast_cancer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_curve, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

data = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    data.data, data.target, test_size=0.25,
    stratify=data.target, random_state=42
)

model = make_pipeline(
    StandardScaler(),
    LogisticRegression(max_iter=2000)
).fit(X_train, y_train)

# Column 1 belongs to model.classes_[1]
y_score = model.predict_proba(X_test)[:, 1]

fpr, tpr, thresholds = roc_curve(y_test, y_score, pos_label=1)
auc = roc_auc_score(y_test, y_score)
print(f"AUC: {auc:.3f}")    # AUC: 0.998

plt.plot(fpr, tpr, label=f"Logistic regression (AUC={auc:.3f})")
plt.plot([0, 1], [0, 1], "--", color="gray", label="Random")
plt.xlabel("False positive rate (FPR)")
plt.ylabel("True positive rate (TPR)")
plt.legend(); plt.grid(alpha=0.25); plt.show()

On this dataset the AUC comes out around 0.998 — precisely in the region I just warned about. Here the explanation is innocent: this set was chosen as a textbook example and its classes really do separate cleanly. Do not accept the same number as readily in your own project.

A critical detail: for ROC, use predict_proba() or decision_function() scores, not the 0/1 labels from predict(). With labels alone the curve collapses to a single point and AUC loses its meaning. Also, the column order of predict_proba is set by model.classes_; verify which column holds the positive class.

Comparing two models

The rule for model comparison is simple but often broken: same test set, same positive-class definition, same preprocessing. Change any one of those and the comparison is void.

AUC of two models
from sklearn.ensemble import RandomForestClassifier

models = {
    "Logistic regression": model,
    "Random Forest": RandomForestClassifier(
        n_estimators=300, random_state=42
    ).fit(X_train, y_train),
}

for name, clf in models.items():
    score = clf.predict_proba(X_test)[:, 1]
    print(f"{name}: AUC={roc_auc_score(y_test, score):.3f}")

# Logistic regression: AUC=0.998
# Random Forest:       AUC=0.995

The gap between 0.998 and 0.995 here comes from a single test split of 143 observations. Reporting a difference that small as "logistic regression is better" would be misleading. On small datasets, use cross-validation instead of one split and report both the mean and the spread.

Another subtlety: two ROC curves can cross. The model with the larger overall area may be worse in the low-FPR region where you actually plan to operate. Plot both curves on the same axes and look at the range you care about.

Threshold selection is a separate decision

A high AUC does not make a model work in the field; you still have to pick the right threshold. And that choice is not statistical but a business decision: what does each kind of error cost?

ScenarioThe expensive errorThreshold direction
Disease screeningMissing a patient (FN)Lower it — raise recall
Spam filterBlocking an important e-mail (FP)Raise it — protect precision
Fraud detectionDepends on the caseTune to review capacity
Churn call listA wasted call (FP)Take the top N by capacity

That last row is very practical. If your call centre can make 200 calls a day, the answer to "what should the threshold be?" is really "take the 200 highest-scoring customers". The threshold becomes a consequence of capacity.

Important: do not tune the threshold to whatever performs best on the test set. That quietly turns the test set into training data. Choose the threshold on validation data and keep the test set for the final assessment.

With class imbalance, ROC can mislead

The denominator of FPR contains the true negatives. When the negative class is huge, that denominator inflates and FPR looks smaller than it is. A concrete example: 1,000,000 transactions contain 1,000 frauds. Your model runs at FPR = 0.01, which sounds excellent. But that is 1% of 999,000 clean transactions — roughly 9,990 false alarms. Even if you catch every single fraud, nine out of ten items on your review list will be empty.

That is why, with a rare positive class, the Precision–Recall curve and Average Precision (AP) are more informative. The PR curve directly exposes the question "of the things I called positive, how many really were?" and is not distorted by the size of the negative class the way ROC is.

Alongside ROC AUC, put these in your report: the class ratio, PR-AUC or AP, the confusion matrix at your chosen threshold, and the absolute error counts expected at that threshold. Counts drive decisions, percentages do not.

What AUC does not tell you

1

Threshold performance

AUC is like an average over all thresholds; it says nothing about the single threshold you will use.

2

Calibration

Scores may rank well while "0.8" does not actually mean an 80% probability. Calibration is measured separately.

3

Uncertainty

AUC on one test sample is an estimate. The gap between 0.91 and 0.89 can be noise.

4

Subgroups

Overall AUC can be high while the model is far worse on a particular customer segment. Check them separately.

5

Data drift

If the live distribution shifts away from the test set, AUC shifts with it. Keep monitoring in production.

6

Multi-class

With more than two classes: one-vs-rest or one-vs-one, macro or weighted — state the method in the report.

Calibration is the one most often overlooked. If you plan to feed a score straight into an expected-value calculation (say, "this customer's churn probability × lifetime value"), ranking is not enough; the probability has to be a real probability. Use a calibration curve or the Brier score for that.

A practical evaluation workflow

Evaluation in six steps
  1. Define the
    Positive Class
  2. Split the
    Data
  3. Get the
    Scores
  4. Compute ROC
    and AUC
  5. Choose the
    Threshold
  6. Report on
    Test
  1. Define the positive class in business terms and write down the cost of each error type.
  2. Separate training, validation and test splits; if there is a time dimension, split chronologically.
  3. Take the probability or decision score, not the labels.
  4. Plot the ROC curve and compute AUC on held-out data.
  5. Choose the threshold on validation data according to the business goal.
  6. On the test set, report the confusion matrix, precision, recall and absolute error counts.

Conclusion

A ROC curve shows the balance between the positives a classifier catches and the false alarms it raises at different thresholds. AUC reduces that curve to a single number and summarises the model's ranking quality — not its accuracy.

In practice, the most useful attitude is to treat AUC as a starting point. Use it while selecting a model; when you deploy, talk about the threshold, the absolute error counts and the confusion matrix at that threshold. If the classes are imbalanced, add the PR curve. A single number should never be the whole decision.