Chapter 7 of 16
How to measure performance honestly when you're searching for it
ML Fundamentals chapter-এ train/validation/test split-এর প্রাথমিক ধারণা দেওয়া হয়েছিল। এই chapter-এ validation অংশটা আরো গভীরভাবে দেখা হবে — validation structure করার কত রকম উপায় আছে, কোনটা কখন উপযুক্ত, আর "validation set-এর ওপর overfit করে ফেলা" নামের ফাঁদ থেকে কীভাবে বাঁচা যায়। scikit-learn-এ RandomizedSearchCV-তে cv=5 লেখা দেখে যদি কখনো ভেবে থাকেন এর মানে ঠিক কী, আর কেন ৫ সংখ্যাটাই বেছে নেওয়া হয় — এই chapter সেটার উত্তর দেবে।
সবচেয়ে সহজ পদ্ধতি — training data-কে একবার training subset আর validation subset-এ ভাগ করা (যেমন ৮০%/২০%), তারপর প্রথম অংশে model fit করে দ্বিতীয় অংশে প্রতিটা candidate configuration evaluate করা। দ্রুত আর simple, কিন্তু validation score একটাই নয়েজি estimate — কোন example ভাগ্যক্রমে validation-এ পড়লো তার ওপর ফলাফল অনেকটা নির্ভর করে, বিশেষত ছোট dataset-এ। বড় dataset-এ, যেখানে single split-ও একটা low-variance estimate দেওয়ার জন্য যথেষ্ট example রাখে, এটা ভালো choice।
Hold-out validation-এর মূল দুর্বলতা — একটা মাত্র, হয়তো দুর্ভাগ্যজনক split — সমাধান করে k-fold cross-validation। Data-কে সমান আকারের টা "fold"-এ ভাগ করে, প্রতিবার একটা fold validation হিসেবে ব্যবহার করে বাকি টা দিয়ে train করে মোট টা আলাদা মডেল বানানো হয়, তারপর টা score-এর গড় নেওয়া হয়।
k=5 আর k=10 সবচেয়ে বেশি ব্যবহৃত — reliability আর compute cost-এর মধ্যে ভালো ভারসাম্য দেয়। ছোট k (যেমন ৩) দ্রুত চলে কিন্তু প্রতিটা fold-এর training set ছোট হয়, score-এর variance বাড়ে; বড় k প্রতি training run-এ বেশি data ব্যবহার করে (bias কম) কিন্তু compute খরচ সেই অনুপাতে বাড়ে।
K-fold-এ একটা মডেলের বদলে টা মডেল train করতে হয়, তাই cost হয় hold-out-এর গুণ। HPO loop-এর ভেতরে ব্যবহার করলে প্রতিটা trial-এই এখন টা training run লাগবে, একটা নয়।
সাধারণ random fold assignment ভাগ্যক্রমে এমন fold তৈরি করতে পারে যেখানে rare class-এর example খুব কম বা একেবারেই নেই — তখন সেই fold-এর validation score অর্থহীন হয়ে যায় (rare class একবারও না থাকলে precision-ও অসংজ্ঞায়িত হয়ে যায়)।
Stratified k-fold প্রতিটা fold-এ পুরো dataset-এর মতোই class proportion বজায় রেখে এই সমস্যা সমাধান করে। Classification সমস্যায় এটাই default choice হওয়া উচিত — scikit-learn-এ integer cv value দিলে classifier-এর জন্য এটাই স্বয়ংক্রিয়ভাবে ব্যবহৃত হয়।
Repeated k-fold ভিন্ন ভিন্ন random fold assignment দিয়ে বারবার k-fold চালিয়ে সব ফলাফলের গড় নেয়, ফলে final estimate-এর variance আরো কমে — খরচ সেই অনুপাতে বাড়ে (৫-fold তিনবার repeat মানে ৫টার বদলে ১৫টা training run)। Dataset ছোট হলে, fold-এর মধ্যে variance বেশি হলে, বা evaluation metric নয়েজি হলে (ছোট imbalanced dataset-এ F1-এর মতো) এটা কাজে লাগে।
LOOCV হলো k-fold-এর চরম রূপ, যেখানে (dataset-এর আকার) — প্রতিটা run টা example দিয়ে train হয়, একটামাত্র বাদ রাখা example validation হিসেবে ব্যবহৃত হয়। এটা সবচেয়ে কম bias-এর estimate দেয়, কিন্তু compute cost সবচেয়ে বেশি (টা training run লাগে) আর কিছু estimator-এর জন্য variance-ও বেশ বেশি হতে পারে। খুব ছোট dataset (১০০-র কম example) ছাড়া এটা ব্যবহার করার দরকার প্রায় নেই।
এখানে একটা সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ সমস্যা আছে — hyperparameter বাছাই আর generalization performance রিপোর্ট করা, দুটোর জন্যই যদি একই cross-validation loop ব্যবহার করেন, তাহলে সেই estimate optimistically biased হয়ে যায় — কার্যত আপনি validation fold-এর ওপর overfit করে ফেলেছেন।
Nested cross-validation এটা সমাধান করে দুটো loop দিয়ে:
বাইরের loop (k_outer fold)
Data-কে বাইরের train আর বাইরের test-এ ভাগ করে। Generalization performance-এর unbiased estimate দেয়।
ভেতরের loop (বাইরের train-এর মধ্যেই k_inner fold)
Hyperparameter tune করতে ব্যবহৃত হয়। শুধু বাইরের training fold-এই সেরা configuration খোঁজে।
চূড়ান্ত evaluation
ভেতরের loop-এর সেরা configuration বাইরের পুরো training data-য় retrain করে বাইরের test fold-এ evaluate করা হয়। এটাই unbiased estimate।
Nested cross-validation computationally বেশ খরচসাধ্য (প্রতি configuration-এ outer_k × inner_k training run)। যখন আপনার selection procedure-টা কতটা ভালো generalize করে সেটার unbiased estimate দরকার — যেমন academic paper-এ model comparison রিপোর্ট করার সময়, বা ছোট clinical dataset-এ — তখন এটা ব্যবহার করুন। সঠিকভাবে আলাদা রাখা test set থাকলে বেশিরভাগ production workflow-এ সাধারণ CV-ই যথেষ্ট।
Time-series data-য় সাধারণ k-fold ব্যবহার করা ঠিক নয়, কারণ এতে ভবিষ্যতের তথ্য দিয়ে অতীত predict করার সুযোগ থেকে যায় — এক ধরনের সূক্ষ্ম data leakage। Time-series split (walk-forward validation) সময়ের ক্রম বজায় রাখে — প্রতিটা training set শুধু সংশ্লিষ্ট validation set-এর আগের সময়ের data দিয়ে গঠিত হয়।
সাধারণ k-fold randomly data fold-এ assign করে, যার ফলে সোমবারের example validation-এ থাকতে পারে আর বুধবারের example training-এ। এটা data leakage — model training-এর সময়ই "ভবিষ্যৎ দেখে ফেলছে"। Time-series split নিশ্চিত করে training set সবসময় validation set শুরুর আগেই শেষ হয়।
Validation set-এর তথ্য অজান্তেই model training বা hyperparameter selection-কে প্রভাবিত করলে সেটাকে বলে validation leakage।
| Leakage-এর ধরন | উদাহরণ | সমাধান |
|---|---|---|
| Preprocessing leakage | Split করার আগেই পুরো dataset-এ (validation-সহ) scaler বা imputer fit করা | Preprocessing সবসময় শুধু training fold-এ fit করুন; validation-এ শুধু apply করুন, fit নয় |
| Feature selection leakage | Split করার আগেই পুরো data ব্যবহার করে target-এর সাথে correlation দেখে feature বাছাই করা | Feature selection-কে cross-validation loop-এর ভেতরে wrap করুন |
| Test set leakage | Model বাছাই বা hyperparameter tune করতে test set ব্যবহার করা | Test set ঠিক একবার ছোঁয়া হবে, সব সিদ্ধান্ত চূড়ান্ত হওয়ার পরে |
| Temporal leakage | Time series-এ ভবিষ্যতের data দিয়ে অতীত predict করা | Time-series split ব্যবহার করুন; কোনো ভবিষ্যৎ feature ব্যবহৃত না হয় তা নিশ্চিত করুন |
সমাধান একটাই নীতি — preprocessing সবসময় শুধু training fold-এ fit করুন, cross-validation loop-এর ভেতরেই।