Idenya
"Modelnya 80% akurat" terdengar bagus, sampai kamu ingat bahwa memprediksi "tidak ada yang churn" sudah 66,5% akurat di data ini, dan bahwa model penipuan bisa 99% akurat sambil tidak menangkap satu penipuan pun. Akurasi memperlakukan setiap kesalahan sama. Dalam bisnis, kesalahan punya biaya yang berbeda:
- Melewatkan customer yang churn (tidak ada voucher yang dikirim, customer hilang) memakan biaya seorang customer.
- Menandai customer setia sebagai churn memakan biaya sebuah voucher.
Untuk melihat pertukarannya, hitung keempat kemungkinan hasilnya.
Empat hasil
Untuk kelas "positif" (churned = 1):
| diprediksi churn | diprediksi bertahan | |
|---|---|---|
| sebenarnya churn | TP true positive: tertangkap | FN false negative: terlewat |
| sebenarnya bertahan | FP false positive: alarm palsu | TN true negative: dengan benar dibiarkan |
import numpy as np
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
churn = pd.read_csv("churn.csv")
FEATURES = ["tenure_months", "orders_90d", "days_since_last_order", "support_tickets", "app_user"]
X_train, X_test, y_train, y_test = train_test_split(
churn[FEATURES], churn["churned"], test_size=0.25, random_state=42, stratify=churn["churned"])
model = LogisticRegression(max_iter=1000).fit(X_train, y_train)
y_true = y_test.to_numpy()
y_pred = model.predict(X_test)
tp = int(((y_pred == 1) & (y_true == 1)).sum())
fp = int(((y_pred == 1) & (y_true == 0)).sum())
fn = int(((y_pred == 0) & (y_true == 1)).sum())
tn = int(((y_pred == 0) & (y_true == 0)).sum())
print(tp, fp, fn, tn)
24 11 9 56
Precision dan recall
Dua pertanyaan, dua metrik:
- Precision = TP / (TP + FP): dari customer yang kita tandai, berapa banyak yang benar-benar churn? Precision tinggi = sedikit voucher yang terbuang.
- Recall = TP / (TP + FN): dari customer yang churn, berapa banyak yang kita tangkap? Recall tinggi = sedikit customer hilang yang lolos.
precision = tp / (tp + fp)
recall = tp / (tp + fn)
accuracy = (tp + tn) / len(y_true)
print(round(precision, 3), round(recall, 3), round(accuracy, 3))
0.686 0.727 0.8
Keduanya menarik ke arah yang berlawanan. Turunkan ambangnya dan kamu menandai lebih banyak orang: recall naik (kamu menangkap lebih banyak customer churn), precision turun (lebih banyak alarm palsu). Naikkan ambangnya dan kebalikannya terjadi.
probs = model.predict_proba(X_test)[:, 1]
for threshold in [0.2, 0.35, 0.5, 0.65, 0.8]:
pred = (probs >= threshold).astype(int)
tp_t = ((pred == 1) & (y_true == 1)).sum()
p = tp_t / max(pred.sum(), 1)
r = tp_t / y_true.sum()
print(f"{threshold:.2f} precision {p:.2f} recall {r:.2f}")
0.20 precision 0.53 recall 0.97
0.35 precision 0.69 recall 0.88
0.50 precision 0.69 recall 0.73
0.65 precision 0.79 recall 0.58
0.80 precision 0.87 recall 0.39
F1: satu angka untuk keduanya
Saat kamu butuh satu angka yang menyeimbangkan keduanya, skor F1 adalah rata-rata harmonisnya:
F1 = 2 × precision × recall / (precision + recall)
Rata-rata harmonis menghukum ketidakseimbangan: precision 1,0 dengan recall 0,1 menghasilkan F1 0,18, bukan 0,55. F1 hanya tinggi saat keduanya lumayan.
Dengan scikit-learn
Kamu sudah menghitung setiap metrik secara manual dari empat hitungan itu, dan itulah cara terbaik untuk memahaminya. Dalam praktiknya, scikit-learn menghitungnya untukmu, dan classification_report mencetak ringkasan lengkap dalam satu panggilan:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, classification_report
print(round(precision_score(y_true, y_pred), 3), round(recall_score(y_true, y_pred), 3), round(f1_score(y_true, y_pred), 3))
print(classification_report(y_true, y_pred, target_names=["stayed", "churned"]))
0.686 0.727 0.706
precision recall f1-score support
stayed 0.86 0.84 0.85 67
churned 0.69 0.73 0.71 33
accuracy 0.80 100
macro avg 0.77 0.78 0.78 100
weighted avg 0.80 0.80 0.80 100
classification_report menampilkan precision, recall dan F1 untuk setiap kelas, ditambah jumlah contohnya (support). Baca baris kelas yang kamu pedulikan, biasanya yang lebih jarang.
Urutan argumen itu penting: metrik menerima (y_true, y_pred). Menukarnya diam-diam menukar precision dan recall.
Memilih metriknya
| Situasi | Prioritaskan | Kenapa |
|---|---|---|
| voucher retensi murah, customer berharga | recall | melewatkan customer churn lebih mahal daripada voucher yang terbuang |
| tim sales menelepon setiap lead yang ditandai (mahal) | precision | setiap alarm palsu membuang waktu seseorang |
| pemeriksaan penipuan yang memblokir pembayaran | precision dan recall, ditimbang berdasarkan biaya | memblokir customer yang baik dan melewatkan penipuan sama-sama merugikan |
| kelas seimbang, biaya sama | akurasi tidak masalah |
Tentukan berapa biaya sebuah false positive dan false negative dalam situasimu, lalu pilih metrik (dan ambang) yang mencerminkannya. Jangan pernah melaporkan akurasi saja pada data yang tidak seimbang.
Mengapa ini penting untuk kode yang bersih
Metrik adalah kontrak antara sebuah model dan bisnisnya. Tuliskan metrik dan ambang yang dipilih, beserta alasannya, di samping kode yang menghitungnya. Saat model dilatih ulang, fungsi yang sama menilainya dengan cara yang sama, dan tidak ada yang bisa diam-diam beralih ke angka yang lebih menyanjung.
Rangkuman
- Empat hasil: TP, FP (alarm palsu), FN (terlewat), TN.
- Precision = TP/(TP+FP): seberapa bisa dipercaya sebuah prediksi positif.
- Recall = TP/(TP+FN): berapa banyak positif sebenarnya yang ditemukan.
- F1 = rata-rata harmonis precision dan recall.
precision_score,recall_score,f1_score,classification_report, selalu(y_true, y_pred).- Pilih metrik dan ambang dari biaya setiap jenis kesalahan.