Part 02 · Machine Learning
Model Training and Evaluation
৯৯% accuracy নিয়েও model ভাঙা হতে পারে। Confusion matrix, precision বনাম recall, ROC-AUC, cross-validation, আর তোমার product আসলে কোন metric নিয়ে ভাবে সেটা বাছাই।
⏱ 1-2 ঘণ্টা · Station 12 / 92
জিনিসটা কী, কেন দরকার
একটা bank এর fraud model 80% accurate। শুনতে খারাপ না, যতক্ষণ না খেয়াল করো test set এর 80% payment ই সৎ, তাই যে model কখনো কিছুই flag করে না সেটাও 80% accurate। সে একটা fraud ও ধরে না।
Accuracy ঠিক সেই ভুলগুলো লুকিয়ে ফেলে যেগুলো নিয়ে তোমার মাথাব্যথা। আসল evaluation দুইটা আলাদা প্রশ্ন করে: model যা flag করেছে তার কতটা সত্যিই fraud? আর সব আসল fraud এর কতটা সে ধরেছে? এই lesson এ এর answer দেওয়ার যন্ত্রগুলো: confusion matrix, precision আর recall, ROC-AUC, আর data কম হলে cross-validation।
ভেতরে কীভাবে চলে
Confusion matrix। প্রতিটা prediction চারটা ঘরের একটায় পড়ে:
| আসলে fraud | আসলে ঠিক | |
|---|---|---|
| flag হয়েছে | true positive (TP) | false positive (FP): একজন সৎ customer আটকে গেল |
| flag হয়নি | false negative (FN): fraud বেরিয়ে গেল | true negative (TN) |
Precision = TP / (TP + FP): model যখন বলে "fraud", কতবার ঠিক? Precision কম মানে card আটকে যাওয়া রাগী customer।
Recall = TP / (TP + FN): সব fraud এর কতটা ধরলাম? Recall কম মানে টাকা গেল।
Threshold একটা দিয়ে আরেকটা কেনে। Classifier একটা score দেয়; cut-off তুমি ঠিক করো। নামালে বেশি fraud ধরবে (recall বাড়ে) কিন্তু বেশি সৎ payment ও flag হবে (precision কমে)। কোন দিকে ঝুঁকবে সেটা একটা business এর সিদ্ধান্ত: cancer মিস করা ভুল alarm এর চেয়ে অনেক খারাপ; checkout এ ভালো customer আটকালে বিক্রি কমে। F1 হলো দুইটার harmonic mean, যখন একটা সংখ্যা লাগবেই তখন কাজে লাগে।
ROC-AUC threshold বাদ দিয়ে জিজ্ঞেস করে: random একটা fraud আর random একটা সৎ payment নিলে, কতবার fraud টা বেশি score পায়? 0.5 মানে টস করার সমান, 1.0 মানে perfect সাজানো। Model তুলনা করতে ভালো; কোন threshold ship করবে সেটা বলে না।
Cross-validation। Data কম হলে একটা test ভাগ noisy: কোন row গুলো ওই ভাগে পড়ল তাতে ভাগ্য ভালো বা খারাপ হতে পারে। k-fold cross-validation data কে k ভাগে ভাগ করে, k বার train করে, প্রতিবার আলাদা একটা ভাগে test করে, আর k টা score এর গড় আর ছড়ানো দুইটাই জানায়।
একটা cancer screening model threshold A তে চলতে পারে (precision 0.90, recall 0.60) বা B তে (precision 0.40, recall 0.97)। Default কোনটা হওয়া উচিত, আর কেন?
ছবিতে দেখো
Code এ চার রকম মাপ
20 টা payment এ একটা fraud model এর score, চারভাবে মাপা: confusion matrix, চারটা threshold এ precision আর recall, জোড়া গুনে ROC-AUC, আর আগের lesson গুলোর এক-threshold ওয়ালা student model এর 5-fold cross-validation।
# A fraud model's scores on 20 payments, with the truth (1 = fraud). Fraud is rare: 4 of 20.
SCORES = [0.95, 0.80, 0.62, 0.40, 0.70, 0.55, 0.30, 0.25, 0.20, 0.18,
0.15, 0.12, 0.10, 0.08, 0.07, 0.05, 0.04, 0.03, 0.02, 0.01]
TRUTH = [1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
def confusion(threshold):
tp = fp = fn = tn = 0
for s, y in zip(SCORES, TRUTH):
flagged = s >= threshold
if flagged and y: tp += 1
elif flagged: fp += 1
elif y: fn += 1
else: tn += 1
return tp, fp, fn, tn
tp, fp, fn, tn = confusion(0.5)
print("threshold 0.5 actual fraud actual ok")
print(f" flagged as fraud {tp:>12} {fp:>9}")
print(f" let through {fn:>12} {tn:>9}")
print(f"accuracy {(tp + tn) / 20:.2f} (a model that never flags anything: {16 / 20:.2f})")
print("\nthreshold precision recall")
for t in (0.3, 0.5, 0.7, 0.9):
tp, fp, fn, tn = confusion(t)
print(f"{t:>9} {tp / (tp + fp):>9.2f} {tp / (tp + fn):>6.2f}")
# ROC-AUC: the chance that a random fraud scores higher than a random normal payment.
pos = [s for s, y in zip(SCORES, TRUTH) if y]
neg = [s for s, y in zip(SCORES, TRUTH) if not y]
wins = sum(p > n for p in pos for n in neg)
print(f"\nROC-AUC = {wins}/{len(pos) * len(neg)} = {wins / (len(pos) * len(neg)):.3f}")
# 5-fold cross-validation of a one-threshold model on 20 students (hours, passed).
STUDENTS = [(1.0, 0), (6.5, 1), (2.5, 0), (8.0, 1), (4.0, 0), (5.5, 1), (3.0, 0), (7.0, 1), (4.5, 1), (2.0, 0),
(6.0, 1), (3.5, 0), (5.0, 1), (1.5, 0), (4.2, 1), (4.8, 0), (3.8, 0), (5.8, 1), (2.8, 0), (6.2, 1)]
def fit(rows):
def acc(t):
return sum((h >= t) == bool(y) for h, y in rows)
return max(sorted(h for h, _ in rows), key=acc)
fold_scores = []
for k in range(5):
test = STUDENTS[k * 4:(k + 1) * 4]
train = STUDENTS[:k * 4] + STUDENTS[(k + 1) * 4:]
t = fit(train)
fold_scores.append(sum((h >= t) == bool(y) for h, y in test) / len(test))
print(f"fold {k + 1}: threshold {t}, test accuracy {fold_scores[-1]:.2f}")
mean = sum(fold_scores) / 5
std = (sum((s - mean) ** 2 for s in fold_scores) / 5) ** 0.5
print(f"cross-validated accuracy {mean:.2f} ± {std:.2f}")package main
import (
"fmt"
"math"
"sort"
)
// A fraud model's scores on 20 payments, with the truth (1 = fraud). Fraud is rare: 4 of 20.
var scores = []float64{0.95, 0.80, 0.62, 0.40, 0.70, 0.55, 0.30, 0.25, 0.20, 0.18,
0.15, 0.12, 0.10, 0.08, 0.07, 0.05, 0.04, 0.03, 0.02, 0.01}
var truth = []int{1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
func confusion(threshold float64) (tp, fp, fn, tn int) {
for i, s := range scores {
flagged := s >= threshold
switch {
case flagged && truth[i] == 1:
tp++
case flagged:
fp++
case truth[i] == 1:
fn++
default:
tn++
}
}
return
}
type student struct {
hours float64
passed bool
}
func correct(rows []student, t float64) int {
n := 0
for _, r := range rows {
if (r.hours >= t) == r.passed {
n++
}
}
return n
}
func fit(rows []student) float64 {
var ts []float64
for _, r := range rows {
ts = append(ts, r.hours)
}
sort.Float64s(ts)
best := ts[0]
for _, t := range ts {
if correct(rows, t) > correct(rows, best) {
best = t
}
}
return best
}
func main() {
tp, fp, fn, tn := confusion(0.5)
fmt.Println("threshold 0.5 actual fraud actual ok")
fmt.Printf(" flagged as fraud %12d %9d\n", tp, fp)
fmt.Printf(" let through %12d %9d\n", fn, tn)
fmt.Printf("accuracy %.2f (a model that never flags anything: %.2f)\n", float64(tp+tn)/20, 16.0/20)
fmt.Println("\nthreshold precision recall")
for _, t := range []float64{0.3, 0.5, 0.7, 0.9} {
tp, fp, fn, _ := confusion(t)
fmt.Printf("%9v %9.2f %6.2f\n", t, float64(tp)/float64(tp+fp), float64(tp)/float64(tp+fn))
}
// ROC-AUC: the chance that a random fraud scores higher than a random normal payment.
wins, pairs := 0, 0
for i, p := range scores {
for j, n := range scores {
if truth[i] == 1 && truth[j] == 0 {
pairs++
if p > n {
wins++
}
}
}
}
fmt.Printf("\nROC-AUC = %d/%d = %.3f\n", wins, pairs, float64(wins)/float64(pairs))
// 5-fold cross-validation of a one-threshold model on 20 students (hours, passed).
students := []student{{1.0, false}, {6.5, true}, {2.5, false}, {8.0, true}, {4.0, false}, {5.5, true}, {3.0, false},
{7.0, true}, {4.5, true}, {2.0, false}, {6.0, true}, {3.5, false}, {5.0, true}, {1.5, false}, {4.2, true},
{4.8, false}, {3.8, false}, {5.8, true}, {2.8, false}, {6.2, true}}
var foldScores []float64
for k := 0; k < 5; k++ {
test := students[k*4 : (k+1)*4]
train := append(append([]student{}, students[:k*4]...), students[(k+1)*4:]...)
t := fit(train)
foldScores = append(foldScores, float64(correct(test, t))/float64(len(test)))
fmt.Printf("fold %d: threshold %v, test accuracy %.2f\n", k+1, t, foldScores[k])
}
mean, sq := 0.0, 0.0
for _, s := range foldScores {
mean += s / 5
}
for _, s := range foldScores {
sq += (s - mean) * (s - mean) / 5
}
fmt.Printf("cross-validated accuracy %.2f ± %.2f\n", mean, math.Sqrt(sq))
}Output:
threshold 0.5 actual fraud actual ok
flagged as fraud 3 2
let through 1 14
accuracy 0.85 (a model that never flags anything: 0.80)
threshold precision recall
0.3 0.57 1.00
0.5 0.60 0.75
0.7 0.67 0.50
0.9 1.00 0.25
ROC-AUC = 61/64 = 0.953
fold 1: threshold 4.2, test accuracy 1.00
fold 2: threshold 4.2, test accuracy 1.00
fold 3: threshold 4.2, test accuracy 1.00
fold 4: threshold 4.5, test accuracy 0.50
fold 5: threshold 4.2, test accuracy 1.00
cross-validated accuracy 0.90 ± 0.20
কী দেখলাম:
- Threshold 0.5 এ model 5 টা payment flag করে, তার 3 টা fraud, আর 1 টা fraud বেরিয়ে যায়। Accuracy 0.85 শুনতে দারুণ, কিন্তু "কখনো flag না করা" পায় 0.80। এখানে accuracy কাজের আর অকাজের model কে প্রায় আলাদাই করতে পারে না।
- আসল গল্প precision/recall এর table এ: 0.9 নির্ভুল কিন্তু fraud এর চার ভাগের এক ভাগ ধরে; 0.3 সবগুলো ধরে আর flag এর 43% ভুল alarm। প্রতিটা ভুলের খরচ দেখে team একটা row বাছে।
- ROC-AUC হলো 64 টা জোড়ার 61 টা, 0.953: model প্রায় সবসময় fraud কে সৎ payment এর উপরে রাখে। তিনটা ব্যর্থতা হলো 0.40 এর fraud টা 0.70 আর 0.55 এর সৎ payment এর কাছে হারা, আর 0.62 হারা 0.70 এর কাছে।
- Cross-validation দেয় 0.90 ± 0.20। চারটা fold perfect, একটা 0.50। একটা মাত্র ভাগ হলে ভাগ্য অনুযায়ী 1.00 বা 0.50 বলত; পাঁচটা fold সাধারণ score আর সেটা কতটা কাঁপে দুইটাই দেখায়।
Production এ যা মাথায় রাখবে
- Train করার আগে metric ঠিক করো। কোন ভুলের খরচ বেশি লিখে রাখো, আর একটা নির্দিষ্ট recall এ precision (বা উল্টোটা) বাছো, "পরে যেটা ভালো দেখায়" না।
- Threshold হলো configuration, code না। Model ship করো score সহ, threshold রাখো config এ, আর খরচ বদলালে retrain ছাড়াই বদলাও।
- দল ধরে report করো। মোট recall 0.9 এর ভেতরে নতুন customer বা একটা এলাকার জন্য 0.5 লুকিয়ে থাকতে পারে। Launch এর আগে segment ধরে metric ভাগ করে দেখো।
- Cross-validation মানে k বার training। ছোট table এর model এ ঠিক আছে; বড় neural network এ তার বদলে ঠিকঠাক সাইজের একটা validation set নাও। Time series এ কখনো fold এর মধ্যে shuffle করবে না; সময় ধরে এগোনো fold নাও।
- Confidence interval। একটা সংখ্যা না, ছড়ানোটাও জানাও (fold বা bootstrapping থেকে)। ± 0.20 এর ছড়ানোর ভেতরে 0.02 এর "improvement" আসলে noise, Part 1 এর statistics এর সেই একই শিক্ষা।
এক কথায়
Imbalanced data তে accuracy মিথ্যা বলে; confusion matrix দেখো, false positive আর false negative এর খরচ দেখে threshold বাছো, model তুলনা করো ROC-AUC দিয়ে, আর ছোট data তে একটা ভাগের বদলে cross-validated গড় ± ছড়ানো বিশ্বাস করো।
নিজে করে দেখো
20 টা score দিয়ে threshold 0.6 এ precision আর recall হাতে বের করো। তারপর ধরো একটা fraud মিস করলে খরচ 10,000 টাকা, আর একটা সৎ payment আটকালে 500 টাকা। 0.3, 0.5, 0.7 আর 0.9 এর মধ্যে কোন threshold সবচেয়ে সস্তা?
Answer
0.6 এ flag হয় 0.95, 0.80, 0.70, 0.62: তিনটা fraud আর একটা সৎ payment, তাই precision 3/4 = 0.75, recall 3/4 = 0.75। খরচ (মিস হওয়া fraud × 10,000 + ভুল flag × 500): 0.3 → 0 + 3 × 500 = 1,500; 0.5 → 1 × 10,000 + 2 × 500 = 11,000; 0.7 → 2 × 10,000 + 1 × 500 = 20,500; 0.9 → 3 × 10,000 + 0 = 30,000। 0.3 অনেক বেশি সস্তা, যদিও তার precision সবচেয়ে খারাপ।
Station stamp
Chapter শেষ করো
পড়া শেষ হলে Complete চাপো, progress save হবে। XP শুধু Pro তে — Pro থাকলে এই chapter এ +10 XP পেতে।
Pro নিয়ে XP পাও →