অধ্যায় 6 · মডেল মাপা ও পরের ধাপ
মডেল মাপা: প্রিসিশন, রিকল ও কনফিউশন ম্যাট্রিক্স
- পৃষ্ঠা 16 / 17
- 4 মিনিট পড়া
"মডেলটা ৯৫% নির্ভুল" শুনতে ভালো — যতক্ষণ না জানছেন যে যেটা ধরার জন্য এটা বানানো, সেটা এটা কখনোই ধরে না। এই পাতা সেই সংখ্যাগুলো দেয়, যা আসল গল্প বলে। যেকোনো হ্যাঁ/না সিদ্ধান্তে এগুলো খাটে: স্প্যাম ফিল্টার, প্রতারণার সতর্কতা, কনটেন্ট মডারেশন, কোনো উত্তর সঠিক কিনা বিচার করা একটা LLM।
কনফিউশন ম্যাট্রিক্স
প্রতিটা অনুমান চারটা ঘরের একটায় পড়ে:
| আসলে হ্যাঁ | আসলে না | |
|---|---|---|
| অনুমান হ্যাঁ | ট্রু পজিটিভ (TP) | ফলস পজিটিভ (FP) — মিথ্যা সতর্কতা |
| অনুমান না | ফলস নেগেটিভ (FN) — ফসকে যাওয়া | ট্রু নেগেটিভ (TN) |
import numpy as np
# 1 = স্প্যাম, 0 = স্প্যাম না, ১২টা ইমেইলের জন্য
actual = np.array([1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0])
predicted = np.array([1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0])
tp = np.sum((predicted == 1) & (actual == 1)) # ধরা পড়া স্প্যাম
fp = np.sum((predicted == 1) & (actual == 0)) # ভালো ইমেইল ভুল করে চিহ্নিত
fn = np.sum((predicted == 0) & (actual == 1)) # যে স্প্যাম ফাঁক গলে ঢুকেছে
tn = np.sum((predicted == 0) & (actual == 0)) # ভালো ইমেইল যেমন ছিল
print(f"TP={tp} FP={fp} FN={fn} TN={tn}")
precision = tp / (tp + fp)
recall = tp / (tp + fn)
print(f"accuracy {(tp + tn) / len(actual):.2f}")
print(f"precision {precision:.2f}")
print(f"recall {recall:.2f}")
print(f"F1 {2 * precision * recall / (precision + recall):.2f}")TP=3 FP=1 FN=1 TN=7
accuracy 0.83
precision 0.75
recall 0.75
F1 0.75| মাপ | সূত্র | যে প্রশ্নের উত্তর দেয় |
|---|---|---|
| Accuracy | (TP + TN) / সব | সব অনুমানের কত অংশ সঠিক? |
| প্রিসিশন | TP / (TP + FP) | যখন "হ্যাঁ" বলে, কতবার সঠিক? |
| রিকল | TP / (TP + FN) | সব আসল "হ্যাঁ"-এর মধ্যে কতগুলো খুঁজে পেয়েছে? |
| F1 | 2 × P × R / (P + R) | এমন একটা সংখ্যা, যা দুটোই বেশি হলে তবেই বেশি |
রিকলকে সেনসিটিভিটি-ও বলে — পাতা ১৪-এর মেডিকেল টেস্টের সেই একই ধারণা।
Accuracy কেন মিথ্যা বলে
একটা শ্রেণি বিরল হলে accuracy প্রায় অর্থহীন। এমন একটা "মডেল", যা কখনো প্রতারণা অনুমান করে না:
import numpy as np
rng = np.random.default_rng(4)
actual = (rng.random(10_000) < 0.01).astype(int) # ১% লেনদেন প্রতারণা
lazy = np.zeros_like(actual) # এমন একটা "মডেল", যা কখনো প্রতারণা বলে না
print(f"accuracy of always saying 'not fraud': {np.mean(lazy == actual):.1%}")
print(f"frauds it catches (recall): {np.sum((lazy == 1) & (actual == 1))} of {actual.sum()}")accuracy of always saying 'not fraud': 98.9%
frauds it catches (recall): 0 of 109৯৮.৯% নির্ভুল, আর ১০৯টা প্রতারণার একটাও ধরে না — রিকল ০। অসম ডেটায় সবসময় বিরল শ্রেণির প্রিসিশন আর রিকল দেখুন।
টানাপোড়েন: থ্রেশহোল্ড বাছাই
বেশিরভাগ মডেল একটা সম্ভাবনা দেয়, আর আপনি থ্রেশহোল্ড বাছেন, যার ওপরে "হ্যাঁ" বলবেন। এটা সরালে প্রিসিশন আর রিকলের মধ্যে অদলবদল হয়:
import numpy as np
rng = np.random.default_rng(8)
actual = rng.random(2000) < 0.2 # ২০% পজিটিভ
# একটা মডেলের সম্ভাবনা: আসল পজিটিভের জন্য গড়ে বেশি
score = np.clip(np.where(actual, rng.normal(0.7, 0.15, 2000), rng.normal(0.35, 0.15, 2000)), 0, 1)
for threshold in [0.3, 0.5, 0.7]:
pred = score >= threshold
tp = np.sum(pred & actual)
precision = tp / pred.sum()
recall = tp / actual.sum()
print(f"threshold {threshold}: precision {precision:.2f}, recall {recall:.2f}")threshold 0.3: precision 0.27, recall 1.00
threshold 0.5: precision 0.56, recall 0.89
threshold 0.7: precision 0.93, recall 0.49কম থ্রেশহোল্ড প্রতিটা পজিটিভ ধরে (রিকল ১.০০), কিন্তু বেশিরভাগ সতর্কতা মিথ্যা (প্রিসিশন ০.২৭)। বেশিটা প্রায় সবসময় সঠিক (০.৯৩), কিন্তু অর্ধেক ফসকায়। সাধারণভাবে কোনো সঠিক থ্রেশহোল্ড নেই — এটা নির্ভর করে প্রতিটা ভুলের খরচের ওপর:
- ফসকানো ব্যয়বহুল (ক্যান্সার স্ক্রিনিং, প্রতারণা, নিরাপত্তা ফিল্টার): রিকলকে প্রাধান্য দিন।
- মিথ্যা সতর্কতা ব্যয়বহুল (আসল গ্রাহকের পেমেন্ট আটকানো, ভালো ইমেইলকে স্প্যাম বলা): প্রিসিশনকে প্রাধান্য দিন।
বাস্তবে sklearn.metrics ব্যবহার করুন — confusion_matrix, precision_score, recall_score, classification_report — তবে এখন ঠিক জানেন এরা কী হিসাব করে।
নিজে চেষ্টা করুন
- কনফিউশন উদাহরণে একটা অনুমান এমনভাবে বদলান যাতে প্রিসিশন ১.০ হয়। রিকলের কী হয়?
- শেষ উদাহরণে কোন থ্রেশহোল্ডে F1 সবচেয়ে বেশি, খুঁজুন।
- শিশুদের অ্যাপের জন্য LLM-ভিত্তিক মডারেশন ফিল্টারে আপনি প্রিসিশন নাকি রিকলকে প্রাধান্য দেবেন? কেন?