Look for the interactive boxes: The Lucky-Split Machine (Section 01), K-Fold Rotator (03), Stratified vs Plain K-Fold (04), Group Leakage Lab (07), Time-Series Split Explorer (08), The Optimism Gap (10), Preprocessing Leakage (12) and Which Cross-Validation Should I Use? (13). Every number in them is calculated live in your browser.
The Story That Explains Cross-Validation
So the exam board uses a smarter system: it rotates. In round 1, one group of students is tested on new cases while the others study. In round 2, the next group is tested. After 10 rounds, every student has been tested on cases they never studied. The board now has a fair picture of every student's real ability.
This rotating exam is cross-validation. It is the standard way to measure how well a machine learning model will work on data it has never seen.
Cross-validation (CV) is a way to test a model many times on the same dataset. You split the data into parts. You train on some parts and test on the part the model did not see. Then you rotate, so every part gets a turn as the test set. Finally, you average the scores.
With one 80/20 split, your accuracy depends on which rows ended up in the 20%. If the test rows happen to be easy, the score is too high. If they happen to be hard, the score is too low. Cross-validation averages over several test sets. This removes most of the luck and gives a much more reliable number.
Try It — The Lucky-Split Machine
Below is one small dataset (60 patients) and one simple model. The only thing that changes is which 12 rows are used for testing. Watch how much the accuracy jumps around with single splits, and how much calmer the 5-fold CV average is.
इन interactive boxes को देखिए: The Lucky-Split Machine (सेक्शन 01), K-Fold Rotator (03), Stratified vs Plain K-Fold (04), Group Leakage Lab (07), Time-Series Split Explorer (08), The Optimism Gap (10), Preprocessing Leakage (12) और Which Cross-Validation Should I Use? (13)। इनका हर नंबर आपके browser में live calculate होता है।
वह कहानी जो Cross-Validation समझाती है
इसलिए exam board एक समझदार तरीका अपनाता है: वह बारी-बारी से परखता है। Round 1 में छात्रों के एक group को नए cases पर test किया जाता है, बाकी पढ़ते हैं। Round 2 में अगले group का test होता है। 10 rounds के बाद हर छात्र उन cases पर test हो चुका है जो उसने कभी पढ़े ही नहीं। अब board के पास हर छात्र की असली काबिलियत की सही तस्वीर है।
यही बारी-बारी वाला exam cross-validation है। यह मापने का standard तरीका है कि एक machine learning model उस data पर कितना अच्छा काम करेगा जो उसने कभी नहीं देखा।
Cross-validation (CV) एक तरीका है जिससे हम एक ही dataset पर model को कई बार test करते हैं। आप data को हिस्सों में बाँटते हैं। कुछ हिस्सों पर train करते हैं और उस हिस्से पर test करते हैं जो model ने नहीं देखा। फिर बारी बदलते हैं, ताकि हर हिस्से को test set बनने का मौका मिले। आख़िर में आप scores का औसत लेते हैं।
एक 80/20 split में आपकी accuracy इस पर निर्भर करती है कि कौन-सी rows 20% में पहुँचीं। अगर test rows संयोग से आसान हैं, तो score बहुत ऊँचा आता है। अगर संयोग से मुश्किल हैं, तो score बहुत नीचा आता है। Cross-validation कई test sets का औसत लेता है। इससे किस्मत का ज़्यादातर असर हट जाता है और कहीं ज़्यादा भरोसेमंद नंबर मिलता है।
ख़ुद आज़माइए — The Lucky-Split Machine
नीचे एक छोटा dataset (60 मरीज़) और एक सीधा-सा model है। बदलती सिर्फ़ एक चीज़ है: test के लिए कौन-सी 12 rows ली गईं। देखिए कि अकेले splits में accuracy कितना उछलती है, और 5-fold CV का औसत कितना शांत रहता है।
请留意这些可以互动的方框:The Lucky-Split Machine(第 01 节)、K-Fold Rotator(03)、Stratified vs Plain K-Fold(04)、Group Leakage Lab(07)、Time-Series Split Explorer(08)、The Optimism Gap(10)、Preprocessing Leakage(12)和 Which Cross-Validation Should I Use?(13)。里面的每个数字都是在你的浏览器里实时计算出来的。
一个解释 Cross-Validation 的故事
所以考试委员会用了一个更聪明的办法:轮换。第 1 轮,一组学生用新病例考试,其他人学习。 第 2 轮,下一组学生考试。10 轮之后,每个学生都在从没学过的病例上考过试了。 现在委员会对每个学生的真实能力有了公平的了解。
这种轮换考试就是 cross-validation。它是衡量一个机器学习 model 在从没见过的数据上表现如何的标准方法。
Cross-validation(CV)是一种在同一个 dataset 上多次 test model 的方法。 你把数据分成几份。在其中几份上 train,在 model 没见过的那一份上 test。 然后轮换,让每一份都轮到一次当 test set。最后,把分数取平均。
只做一次 80/20 split,你的 accuracy 取决于哪些行落进了那 20%。 如果 test 行刚好很简单,分数就太高。如果刚好很难,分数就太低。 Cross-validation 在几个 test set 上取平均。这样能去掉大部分运气,得到一个可靠得多的数字。
动手试试 —— The Lucky-Split Machine
下面是一个小 dataset(60 个病人)和一个简单的 model。唯一变化的是哪 12 行用来 test。看看单次 split 的 accuracy 跳动有多大,而 5-fold CV 的平均值又平稳多少。
The Three Mistakes CV Protects You From
Standard K-Fold Cross-Validation — The Foundation
K-Fold CV is the most common method. The data is cut into k equal parts called folds. The model trains on k−1 folds and is tested on the one fold left over. This repeats k times, so each fold is the test set exactly once. The final score is the mean of the k scores, and the standard deviation shows how much they vary.
Try It — The K-Fold Rotator
Here are 20 rows. Each row of the grid on the left is one round of CV. Press Next fold to train and test one round at a time. Try k = 2, 5, 10 and 20.
तीन गलतियाँ जिनसे CV आपको बचाता है
Standard K-Fold Cross-Validation — नींव
K-Fold CV सबसे आम तरीका है। Data को k बराबर हिस्सों में काटा जाता है, जिन्हें folds कहते हैं। Model k−1 folds पर train होता है और बचे हुए एक fold पर test होता है। यह k बार दोहराया जाता है, इसलिए हर fold ठीक एक बार test set बनता है। आख़िरी score k scores का mean होता है, और standard deviation बताता है कि वे कितना बदलते हैं।
ख़ुद आज़माइए — The K-Fold Rotator
यहाँ 20 rows हैं। बाईं ओर grid की हर row CV का एक round है। एक बार में एक round train और test करने के लिए अगला fold दबाइए। k = 2, 5, 10 और 20 आज़माइए।
CV 帮你避免的三个错误
标准 K-Fold Cross-Validation —— 基础
K-Fold CV 是最常用的方法。数据被切成 k 个相等的部分,叫做 fold。 model 在 k−1 个 fold 上 train,在剩下的那一个 fold 上 test。 这样重复 k 次,所以每个 fold 正好当一次 test set。 最终分数是 k 个分数的 mean,而 standard deviation 显示它们变化有多大。
动手试试 —— K-Fold Rotator
这里有 20 行数据。左边网格的每一行是 CV 的一轮。请按 下一个 fold,一次 train 和 test 一轮。试试 k = 2、5、10 和 20。
Python — Standard K-Fold (k = 5)
Python — Standard K-Fold (k = 5)
Python —— 标准 K-Fold(k = 5)
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import KFold, cross_val_score
# ── Dataset: 1000 samples, 20 features ──────────────
X, y = make_classification(
n_samples=1000, n_features=20,
n_informative=12, random_state=42
)
# ── Model ────────────────────────────────────────────
model = RandomForestClassifier(n_estimators=100, random_state=42)
# ── K-Fold: 5 folds, shuffled ────────────────────────
kf = KFold(n_splits=5, shuffle=True, random_state=42)
# ── cross_val_score trains and tests on every fold ───
scores = cross_val_score(model, X, y, cv=kf, scoring='accuracy')
print("Per-Fold Scores:")
for i, s in enumerate(scores, 1):
print(f" Fold {i}: {s:.4f}")
print(f"\nMean Accuracy : {scores.mean():.4f}")
print(f"Std Deviation : {scores.std():.4f}")
print(f"Lowest-Highest: {scores.min():.4f} - {scores.max():.4f}")
The same model scored from 82% to 91% across the five folds. A single 80/20 split could have shown you any of these numbers. Report the mean and the standard deviation: 87.3% ± 3.0.
k = 5 is the usual default. k = 10 trains on more data per fold (90% instead of 80%), so its estimate is a little less pessimistic, but it costs twice the training time. k = 3 is fine for very large datasets or very slow models. k = N (Leave-One-Out) trains N models — only practical for small data (Section 05). If unsure, start with k = 5.
Stratified K-Fold — The Fix for Imbalanced Classes
Stratified K-Fold fixes this. It deals the sick patients out like playing cards, so every fold gets the same 10% as the full dataset — 20 sick patients each.
StratifiedKFold keeps the class percentages the same in every fold. This matters most when one class is rare. Plain K-Fold can produce folds with very few, or even zero, examples of the rare class.
Try It — Where Do the Sick Patients Go?
100 patients, 10 sick. The data file lists the sick patients last, as real files often do. Switch between the three methods, change k, and press Repeat 500× to see the worst case of each method.
उसी model ने पाँच folds में 82% से 91% तक score किया। एक अकेला 80/20 split आपको इनमें से कोई भी नंबर दिखा सकता था। Mean और standard deviation दोनों बताइए: 87.3% ± 3.0।
k = 5 आम default है। k = 10 हर fold में ज़्यादा data पर train करता है (80% की जगह 90%), इसलिए उसका अनुमान थोड़ा कम निराशावादी होता है, पर training का समय दोगुना लगता है। k = 3 बहुत बड़े datasets या बहुत धीमे models के लिए ठीक है। k = N (Leave-One-Out) N models train करता है — यह सिर्फ़ छोटे data के लिए व्यावहारिक है (सेक्शन 05)। अगर पक्का न हो, तो k = 5 से शुरू करें।
Stratified K-Fold — असंतुलित Classes का इलाज
Stratified K-Fold इसे ठीक करता है। यह बीमार मरीज़ों को ताश के पत्तों की तरह बाँटता है, ताकि हर fold को पूरे dataset जैसा ही 10% मिले — हर fold में 20 बीमार मरीज़।
StratifiedKFold हर fold में class का प्रतिशत एक जैसा रखता है। यह सबसे ज़्यादा तब मायने रखता है जब एक class दुर्लभ हो। साधारण K-Fold ऐसे folds बना सकता है जिनमें दुर्लभ class के बहुत कम, या एक भी, उदाहरण न हों।
ख़ुद आज़माइए — बीमार मरीज़ कहाँ जाते हैं?
100 मरीज़, 10 बीमार। Data file में बीमार मरीज़ आख़िर में लिखे हैं, जैसा असली files में अक्सर होता है। तीनों तरीकों के बीच बदलिए, k बदलिए, और हर तरीके का सबसे बुरा case देखने के लिए 500× दोहराएँ दबाइए।
同一个 model 在五个 fold 上的分数从 82% 到 91%。一次 80/20 split 可能给你其中任何一个数字。 要报告 mean 和 standard deviation:87.3% ± 3.0。
k = 5 是常用的默认值。k = 10 每个 fold 用更多数据 train(90% 而不是 80%),所以估计没那么悲观,但要花两倍的 training 时间。 k = 3 适合非常大的 dataset 或非常慢的 model。 k = N(Leave-One-Out)要 train N 个 model —— 只适合小数据(第 05 节)。 如果不确定,就从 k = 5 开始。
Stratified K-Fold —— 解决 Class 不平衡的问题
Stratified K-Fold 解决了这个问题。它像发扑克牌一样把生病的病人分出去,所以每个 fold 都和完整 dataset 一样是 10% —— 每个 fold 20 个生病的病人。
StratifiedKFold 让每个 fold 里的 class 比例保持一样。 当某个 class 很少见时,这一点最重要。普通的 K-Fold 可能产生只有很少、甚至没有少见 class 例子的 fold。
动手试试 —— 生病的病人去哪了?
100 个病人,10 个生病。数据文件把生病的病人列在最后,真实的文件也经常这样。在三种方法之间切换,改变 k,然后按 重复 500×,看看每种方法最坏的情况。
Python — Stratified K-Fold
Python — Stratified K-Fold
Python —— Stratified K-Fold
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import KFold, StratifiedKFold, cross_val_score
# ── Imbalanced dataset: about 90% class 0, 10% class 1 ──
X, y = make_classification(
n_samples=2000, n_features=20, n_informative=10,
weights=[0.9, 0.1], # imbalanced!
random_state=42
)
print(f"Class counts: {np.bincount(y)}")
# ── Compare: how many class-1 samples land in each test fold? ──
kf = KFold(n_splits=5, shuffle=True, random_state=7)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=7)
print("\nClass-1 samples per test fold:")
print(" KFold :", [int(y[te].sum()) for _, te in kf.split(X, y)])
print(" StratifiedKFold:", [int(y[te].sum()) for _, te in skf.split(X, y)])
# ── Evaluate with F1 and AUC (better than accuracy for imbalance) ──
model = GradientBoostingClassifier(n_estimators=100, random_state=42)
f1_scores = cross_val_score(model, X, y, cv=skf, scoring='f1')
auc_scores = cross_val_score(model, X, y, cv=skf, scoring='roc_auc')
print("\nStratified K-Fold (k=5) results:")
for i in range(5):
print(f" Fold {i+1}: F1={f1_scores[i]:.4f} AUC={auc_scores[i]:.4f}")
print(f"\nMean F1 : {f1_scores.mean():.4f} ± {f1_scores.std():.4f}")
print(f"Mean AUC : {auc_scores.mean():.4f} ± {auc_scores.std():.4f}")
Plain KFold put between 37 and 49 rare-class rows in its test folds. StratifiedKFold put exactly 42 in every fold. Also notice the F1 score: it moves from 0.49 to 0.72 between folds even with stratification. With rare classes, scores are noisy — always report the spread.
If you pass a number, like cv=5, to cross_val_score or cross_validate with a classifier, scikit-learn already uses StratifiedKFold — but without shuffling.
For regression it uses plain KFold, also without shuffling.
Passing StratifiedKFold(n_splits=5, shuffle=True, random_state=42) yourself makes the choice clear and repeatable.
Leave-One-Out CV (LOOCV) — The Extreme Case
Leave-One-Out CV is K-Fold with k = N, the number of rows. Each round tests on one row and trains on all the others. This repeats N times, so you train N models. Each round's score is either 1 (correct) or 0 (wrong).
Python — Leave-One-Out CV
साधारण KFold ने अपने test folds में दुर्लभ class की 37 से 49 rows रखीं। StratifiedKFold ने हर fold में ठीक 42 रखीं। F1 score पर भी ध्यान दीजिए: stratification के साथ भी यह folds के बीच 0.49 से 0.72 तक बदलता है। दुर्लभ classes के साथ scores में noise होता है — हमेशा फैलाव भी बताइए।
अगर आप classifier के साथ cross_val_score या cross_validate को कोई नंबर देते हैं, जैसे cv=5, तो scikit-learn पहले से ही StratifiedKFold इस्तेमाल करता है — पर बिना shuffle किए।
Regression के लिए यह साधारण KFold इस्तेमाल करता है, वह भी बिना shuffle किए।
ख़ुद StratifiedKFold(n_splits=5, shuffle=True, random_state=42) देने से आपका चुनाव साफ़ और दोहराने लायक बनता है।
Leave-One-Out CV (LOOCV) — सबसे आख़िरी हद
Leave-One-Out CV वह K-Fold है जिसमें k = N, यानी rows की संख्या। हर round एक row पर test करता है और बाकी सब पर train करता है। यह N बार दोहराया जाता है, इसलिए आप N models train करते हैं। हर round का score या तो 1 (सही) होता है या 0 (गलत)।
Python — Leave-One-Out CV
普通的 KFold 在它的 test fold 里放了 37 到 49 个少见 class 的行。StratifiedKFold 在每个 fold 里都正好放了 42 个。 也请注意 F1 score:即使用了 stratification,它在不同 fold 之间也从 0.49 变到 0.72。class 少见时,分数噪声很大 —— 一定要报告波动。
如果你给 cross_val_score 或 cross_validate 传一个数字,比如 cv=5,并且用的是 classification model,scikit-learn 已经在用 StratifiedKFold 了 —— 但是不会 shuffle。
对 regression,它用普通的 KFold,同样不 shuffle。
自己传入 StratifiedKFold(n_splits=5, shuffle=True, random_state=42),能让你的选择清楚、可重复。
Leave-One-Out CV(LOOCV)—— 最极端的情况
Leave-One-Out CV 就是 k = N 的 K-Fold,N 是行数。 每一轮只在一行上 test,在其他所有行上 train。 这样重复 N 次,所以你要 train N 个 model。每一轮的分数不是 1(对)就是 0(错)。
Python —— Leave-One-Out CV
from sklearn.model_selection import LeaveOneOut, cross_val_score
from sklearn.svm import SVC
from sklearn.datasets import load_iris
# ── Small dataset: LOOCV is practical here ────────────
X, y = load_iris(return_X_y=True)
print(f"Dataset size: {X.shape}") # 150 samples → 150 models
model = SVC(kernel='rbf', C=1.0)
loo = LeaveOneOut()
# ── Each score is 1 (correct) or 0 (wrong) ────────────
scores = cross_val_score(model, X, y, cv=loo, scoring='accuracy')
print(f"Total iterations : {len(scores)}")
print(f"Scores (first 10) : {scores[:10]}")
print(f"Mean Accuracy : {scores.mean():.4f}")
print(f"Misclassified : {int((scores == 0).sum())} / {len(scores)}")
| Property | K-Fold (k=5) | K-Fold (k=10) | LOOCV (k=N) |
|---|---|---|---|
| Training rows per model | 80% of data | 90% of data | all rows but one |
| Test rows per round | 20% of data | 10% of data | 1 row |
| Models trained | 5 | 10 | N |
| Pessimism (bias) of the estimate | A little | Less | Least |
| Stability of the estimate | Good | Good | Can be unstable |
| Compute cost | Low | Medium | Very high for big N |
| Best for | General use | Medium datasets | Very small datasets |
LOOCV trains on almost all the data, so it is the least pessimistic. But its N training sets are almost identical, so the estimate can still wobble a lot from one dataset to another. Research has found that 5- or 10-fold CV is usually as good or better — and much cheaper. Use LOOCV mainly when you have very few rows.
Stratified Shuffle Split — Many Random Splits
StratifiedShuffleSplit makes a number of independent random train/test splits that you choose (for example 10 splits of 80/20), each keeping the class balance. Unlike K-Fold, the test sets can overlap: one row may be tested several times, and another row never. It is useful when the data is large and you want quick, repeated checks with a chosen test size.
K-Fold guarantees that every row is tested exactly once. StratifiedShuffleSplit does not. Each split is drawn fresh, so you control the number of splits and the test size separately — for example 20 splits with a 10% test set.
Python — StratifiedShuffleSplit
| गुण | K-Fold (k=5) | K-Fold (k=10) | LOOCV (k=N) |
|---|---|---|---|
| हर model की training rows | data का 80% | data का 90% | एक को छोड़कर सभी rows |
| हर round की test rows | data का 20% | data का 10% | 1 row |
| Train हुए models | 5 | 10 | N |
| अनुमान का निराशावाद (bias) | थोड़ा | कम | सबसे कम |
| अनुमान की स्थिरता | अच्छी | अच्छी | अस्थिर हो सकता है |
| Compute का ख़र्च | कम | मध्यम | बड़े N पर बहुत ज़्यादा |
| किसके लिए सबसे अच्छा | आम इस्तेमाल | मध्यम datasets | बहुत छोटे datasets |
LOOCV लगभग पूरे data पर train करता है, इसलिए यह सबसे कम निराशावादी है। पर इसके N training sets लगभग एक जैसे होते हैं, इसलिए अनुमान एक dataset से दूसरे dataset तक फिर भी बहुत डगमगा सकता है। Research में पाया गया है कि 5- या 10-fold CV आमतौर पर उतना ही अच्छा या बेहतर होता है — और बहुत सस्ता। LOOCV मुख्य रूप से तब इस्तेमाल करें जब आपके पास बहुत कम rows हों।
Stratified Shuffle Split — कई Random Splits
StratifiedShuffleSplit आपकी चुनी हुई संख्या में अलग-अलग random train/test splits बनाता है (जैसे 80/20 के 10 splits), और हर split में class का संतुलन बना रहता है। K-Fold से अलग, यहाँ test sets overlap कर सकते हैं: कोई row कई बार test हो सकती है, और कोई row कभी नहीं। यह तब काम का है जब data बड़ा हो और आप चुने हुए test size के साथ जल्दी-जल्दी बार-बार जाँच करना चाहें।
K-Fold पक्का करता है कि हर row ठीक एक बार test हो। StratifiedShuffleSplit ऐसा नहीं करता। हर split नए सिरे से बनता है, इसलिए splits की संख्या और test size आप अलग-अलग तय करते हैं — जैसे 10% test set के साथ 20 splits।
Python — StratifiedShuffleSplit
| 特性 | K-Fold(k=5) | K-Fold(k=10) | LOOCV(k=N) |
|---|---|---|---|
| 每个 model 的 training 行数 | 数据的 80% | 数据的 90% | 除了一行以外的所有行 |
| 每轮的 test 行数 | 数据的 20% | 数据的 10% | 1 行 |
| train 的 model 数 | 5 | 10 | N |
| 估计的悲观程度(bias) | 有一点 | 较少 | 最少 |
| 估计的稳定性 | 好 | 好 | 可能不稳定 |
| 计算成本 | 低 | 中 | N 很大时非常高 |
| 最适合 | 一般用途 | 中等 dataset | 非常小的 dataset |
LOOCV 几乎在全部数据上 train,所以它最不悲观。但它的 N 个 training set 几乎一模一样,所以换一个 dataset,估计仍可能摇摆很大。 研究发现,5-fold 或 10-fold CV 通常一样好,甚至更好 —— 而且便宜得多。主要在行数非常少时才用 LOOCV。
Stratified Shuffle Split —— 很多次随机 Split
StratifiedShuffleSplit 做多次互相独立的随机 train/test split,次数由你选择(例如 10 次 80/20 split),每次都保持 class 平衡。 和 K-Fold 不同,test set 可以重叠:一行可能被 test 好几次,另一行可能从来没被 test。 当数据很大,而你想用选定的 test 大小做快速、重复的检查时,它很有用。
K-Fold 保证每一行正好被 test 一次。StratifiedShuffleSplit 不保证。 每次 split 都是重新抽取的,所以你可以分别控制 split 的次数和 test 的大小 —— 例如 20 次 split,每次 10% 的 test set。
Python —— StratifiedShuffleSplit
from sklearn.model_selection import StratifiedShuffleSplit, cross_val_score
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
X, y = make_classification(n_samples=5000, n_features=15,
weights=[0.8, 0.2], random_state=42)
model = LogisticRegression(max_iter=1000)
# ── 10 independent random stratified 80/20 splits ────
sss = StratifiedShuffleSplit(n_splits=10, test_size=0.2, random_state=42)
auc_scores = cross_val_score(model, X, y, cv=sss, scoring='roc_auc')
print("StratifiedShuffleSplit (10 runs x 20% test):")
for i, s in enumerate(auc_scores, 1):
print(f" Run {i:2d}: AUC = {s:.4f}")
print(f"\nMean AUC : {auc_scores.mean():.4f}")
print(f"Std : {auc_scores.std():.4f}")
# ── How often was each sample in a test set? ─────────
import numpy as np
count = np.zeros(len(y), int)
for _, te in sss.split(X, y):
count[te] += 1
print(f"\nNever tested: {(count == 0).sum()} samples | tested 2+ times: {(count >= 2).sum()} samples")
The last line shows the difference from K-Fold: after 10 random splits, 527 of the 5,000 rows were never tested, and more than 3,000 were tested two or more times.
Group K-Fold — When Rows Are Not Independent
But look inside fold 1. Patient #47's Monday scan is in the training set, and their Friday scan is in the test set. The model has already seen this patient. It can recognise their body shape and simply copy the answer it learned. Of course it scores high — it is recognising people, not detecting disease.
On truly new patients, the score drops a lot. Group K-Fold keeps all scans of one patient in the same fold. If patient #47 is in the test fold, all five of their scans are there, and none are in training.
Try It — Group Leakage Lab
Each patient has several scans close together (joined by thin lines). The model is a simple "nearest neighbour": it copies the label of the most similar training scan. Compare KFold with GroupKFold, and with the truth on brand-new patients.
आख़िरी line K-Fold से अंतर दिखाती है: 10 random splits के बाद, 5,000 में से 527 rows कभी test नहीं हुईं, और 3,000 से ज़्यादा rows दो या ज़्यादा बार test हुईं।
Group K-Fold — जब Rows आपस में स्वतंत्र न हों
पर fold 1 के अंदर देखिए। मरीज़ #47 का सोमवार वाला scan training set में है, और शुक्रवार वाला scan test set में। Model इस मरीज़ को पहले ही देख चुका है। वह उसके शरीर की बनावट पहचान सकता है और सीखा हुआ जवाब बस कॉपी कर सकता है। ज़ाहिर है score ऊँचा आता है — वह लोगों को पहचान रहा है, बीमारी नहीं पकड़ रहा।
सच में नए मरीज़ों पर score बहुत गिर जाता है। Group K-Fold एक मरीज़ के सारे scans एक ही fold में रखता है। अगर मरीज़ #47 test fold में है, तो उसके पाँचों scans वहीं हैं, और training में एक भी नहीं।
ख़ुद आज़माइए — Group Leakage Lab
हर मरीज़ के कई scans पास-पास हैं (पतली लाइनों से जुड़े)। Model एक सीधा "nearest neighbour" है: वह सबसे मिलते-जुलते training scan का label कॉपी करता है। KFold की तुलना GroupKFold से कीजिए, और बिल्कुल नए मरीज़ों वाले सच से भी।
最后一行显示了它和 K-Fold 的区别:10 次随机 split 之后,5,000 行中有 527 行从来没被 test 过,而超过 3,000 行被 test 了两次或更多次。
Group K-Fold —— 当行与行之间不独立时
但看看 fold 1 里面。47 号病人周一的扫描片在 training set 里,周五的扫描片在 test set 里。 model 已经见过这个病人了。它能认出这个人的身体形状,然后直接照抄它学到的答案。 它的分数当然高 —— 它是在认人,而不是在发现疾病。
在真正的新病人身上,分数会掉很多。 Group K-Fold 把一个病人的所有扫描片放在同一个 fold 里。如果 47 号病人在 test fold 里,他的全部五张扫描片都在那里,training 里一张也没有。
动手试试 —— Group Leakage Lab
每个病人有几张靠得很近的扫描片(用细线连起来)。model 是一个简单的“最近邻”:它照抄最相似的 training 扫描片的 label。比较 KFold 和 GroupKFold,再和全新病人上的真相比较。
Python — Group K-Fold
Python — Group K-Fold
Python —— Group K-Fold
import numpy as np
from sklearn.model_selection import KFold, GroupKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
# ── Simulate: 400 patients, 5 scans each ─────────────
rng = np.random.default_rng(42)
n_patients, scans = 400, 5
groups = np.repeat(np.arange(n_patients), scans) # [0,0,0,0,0, 1,1,1,1,1, ...]
body = rng.normal(size=(n_patients, 20)) # each patient's own "anatomy"
y_pat = (body[:, 0] + rng.normal(scale=2.0, size=n_patients) > 0).astype(int) # weak real signal
X = body[groups] + rng.normal(scale=0.3, size=(n_patients * scans, 20)) # 5 similar scans
y = y_pat[groups]
model = RandomForestClassifier(n_estimators=200, random_state=42)
# ── ❌ Plain KFold: scans of one patient land in train AND test ──
leaky = cross_val_score(model, X, y, cv=KFold(5, shuffle=True, random_state=42))
# ── ✅ GroupKFold: all scans of a patient stay in one fold ──
gkf = GroupKFold(n_splits=5)
honest = cross_val_score(model, X, y, cv=gkf, groups=groups)
print(f"KFold (leaky) : {leaky.mean():.4f} ± {leaky.std():.4f}")
print(f"GroupKFold (honest): {honest.mean():.4f} ± {honest.std():.4f}")
# ── Verify: no patient appears in both train and test ─
for fold, (tr, te) in enumerate(gkf.split(X, y, groups), 1):
leak = set(groups[tr]) & set(groups[te])
print(f" Fold {fold}: {len(set(groups[te]))} test patients, shared patients = {len(leak)}")
Plain KFold reported 93% because test scans had a "twin" from the same patient in training. GroupKFold reported the honest 58%.
Use group-aware CV whenever one real-world unit appears in many rows: many scans per patient, many purchases per customer, many posts per user, many frames per video, many readings per machine.
For classification with groups, StratifiedGroupKFold also keeps the class balance.
Time-Series Cross-Validation — Respecting Time
In real life you always train on the past and predict the future. A model that peeks at future data looks brilliant in CV and then disappoints in production — because time only moves forward.
TimeSeriesSplit follows this rule: the training data is always older than the test data. No peeking.
Try It — Time-Series Split Explorer
Each row is one split over 24 months. Compare TimeSeriesSplit with shuffled KFold, and try the gap and sliding window options.
साधारण KFold ने 93% बताया, क्योंकि test scans का उसी मरीज़ वाला एक "जुड़वाँ" training में था। GroupKFold ने ईमानदार 58% बताया।
जब भी असली दुनिया की एक इकाई कई rows में आए, तो group का ध्यान रखने वाला CV इस्तेमाल करें: हर मरीज़ के कई scans, हर customer की कई खरीदारी, हर user के कई posts, हर video के कई frames, हर machine की कई readings।
Groups वाले classification के लिए StratifiedGroupKFold class का संतुलन भी बनाए रखता है।
Time-Series Cross-Validation — समय का सम्मान
असल ज़िंदगी में आप हमेशा अतीत पर train करते हैं और भविष्य का अनुमान लगाते हैं। जो model भविष्य के data में झाँक लेता है, वह CV में शानदार दिखता है और फिर production में निराश करता है — क्योंकि समय सिर्फ़ आगे बढ़ता है।
TimeSeriesSplit इस नियम को मानता है: training data हमेशा test data से पुराना होता है। कोई झाँकना नहीं।
ख़ुद आज़माइए — Time-Series Split Explorer
हर row 24 महीनों पर एक split है। TimeSeriesSplit की तुलना shuffle वाले KFold से कीजिए, और gap तथा sliding window वाले options आज़माइए।
普通的 KFold 报告了 93%,因为 test 扫描片在 training 里有一个来自同一病人的“双胞胎”。GroupKFold 报告了诚实的 58%。
只要一个真实世界的单位出现在很多行里,就要用考虑 group 的 CV:一个病人很多张扫描片,一个顾客很多次购买,一个用户很多条帖子,一个视频很多帧,一台机器很多次读数。
对有 group 的 classification,StratifiedGroupKFold 还能保持 class 平衡。
Time Series Cross-Validation —— 尊重时间
在真实生活中,你总是用过去 train,去预测未来。 一个偷看未来数据的 model 在 CV 里看起来很厉害,到了生产环境就让人失望 —— 因为时间只会向前走。
TimeSeriesSplit 遵守这个规则:training 数据总是比 test 数据更早。不许偷看。
动手试试 —— Time-Series Split Explorer
每一行是 24 个月上的一次 split。比较 TimeSeriesSplit 和 shuffle 过的 KFold,再试试 gap 和 sliding window 选项。
Python — TimeSeriesSplit
Python — TimeSeriesSplit
Python —— TimeSeriesSplit
import numpy as np
from sklearn.model_selection import TimeSeriesSplit, KFold, cross_val_score
from sklearn.ensemble import GradientBoostingRegressor
from sklearn.metrics import mean_absolute_error
# ── Simulate 360 days of sales: trend + monthly cycle + noise ──
rng = np.random.default_rng(42)
n = 360
t = np.arange(n)
X = np.column_stack([
np.sin(2*np.pi*t/30), # monthly cycle
np.sin(2*np.pi*t/7), # weekly cycle
t/360, # time (trend)
rng.normal(size=(n, 9)) # noise features
])
y = 10 + 0.02*t + 3*np.sin(2*np.pi*t/30) + rng.normal(scale=0.5, size=n)
model = GradientBoostingRegressor(n_estimators=100, random_state=42)
tscv = TimeSeriesSplit(n_splits=5)
maes = []
for fold, (tr, te) in enumerate(tscv.split(X), 1):
model.fit(X[tr], y[tr])
mae = mean_absolute_error(y[te], model.predict(X[te]))
maes.append(mae)
print(f"Fold {fold}: train days 0-{tr[-1]} -> test days {te[0]}-{te[-1]} | MAE={mae:.3f}")
print(f"\nTimeSeriesSplit mean MAE : {np.mean(maes):.3f}")
# ── ❌ Shuffled KFold lets the model see the future ───
kf = KFold(n_splits=5, shuffle=True, random_state=42)
mae_kf = -cross_val_score(model, X, y, cv=kf, scoring='neg_mean_absolute_error')
print(f"Shuffled KFold mean MAE : {mae_kf.mean():.3f} <- looks better, but it is cheating")
Shuffled KFold reported an error of 0.44 — less than half the honest 0.96. It looked better only because it could see days around each test day, including future ones.
Also notice that the honest error did not fall as the training window grew. Sales keep rising, and tree models such as gradient boosting cannot predict values higher than anything they have seen. Much of each new test block is above the training range.
TimeSeriesSplit exposes this problem; shuffled KFold hides it. If errors grow in later folds, also suspect drift — the data changing over time.
Repeated K-Fold — For Steadier Estimates
RepeatedKFold and RepeatedStratifiedKFold run K-Fold several times, each time with a different shuffle, and average all the scores. 5 folds repeated 10 times gives 50 scores. The average is steadier because it no longer depends on one particular way of cutting the folds.
Use it when the dataset is small, or when you compare two models whose scores are close (for example 84.2% vs 84.7%). It costs n_repeats times more training, so on large datasets plain 5-fold is usually enough.
Python — Comparing Two Models Fairly
Shuffle वाले KFold ने 0.44 की error बताई — ईमानदार 0.96 के आधे से भी कम। यह सिर्फ़ इसलिए बेहतर दिखा क्योंकि वह हर test दिन के आस-पास के दिन देख सकता था, भविष्य के दिन भी।
यह भी देखिए कि training window बढ़ने पर ईमानदार error नहीं घटी। बिक्री लगातार बढ़ रही है, और gradient boosting जैसे tree models उससे ऊँची values का अनुमान नहीं लगा सकते जो उन्होंने कभी देखी हों। हर नए test block का बड़ा हिस्सा training की range से ऊपर है।
TimeSeriesSplit इस समस्या को सामने लाता है; shuffle वाला KFold इसे छिपा देता है। अगर बाद के folds में errors बढ़ती हैं, तो drift का भी शक कीजिए — यानी समय के साथ data का बदलना।
Repeated K-Fold — ज़्यादा स्थिर अनुमानों के लिए
RepeatedKFold और RepeatedStratifiedKFold K-Fold को कई बार चलाते हैं, हर बार अलग shuffle के साथ, और सभी scores का औसत लेते हैं। 5 folds को 10 बार दोहराने से 50 scores मिलते हैं। औसत ज़्यादा स्थिर होता है क्योंकि वह अब folds काटने के किसी एक तरीके पर निर्भर नहीं रहता।
इसे तब इस्तेमाल करें जब dataset छोटा हो, या जब आप दो ऐसे models की तुलना करें जिनके scores पास-पास हों (जैसे 84.2% बनाम 84.7%)। इसमें n_repeats गुना ज़्यादा training लगती है, इसलिए बड़े datasets पर साधारण 5-fold आमतौर पर काफ़ी है।
Python — दो Models की ईमानदार तुलना
Shuffle 过的 KFold 报告的误差是 0.44 —— 不到诚实值 0.96 的一半。它看起来更好,只是因为它能看到每个 test 日前后的日子,包括未来的日子。
还要注意,随着 training 窗口变大,诚实的误差并没有下降。销售额一直在上涨,而像 gradient boosting 这样的树 model 无法预测比它见过的任何值都高的值。每个新 test 块的大部分都高于 training 的范围。
TimeSeriesSplit 暴露了这个问题;shuffle 过的 KFold 把它藏起来了。如果后面 fold 的误差变大,也要怀疑 drift —— 数据随时间发生了变化。
Repeated K-Fold —— 得到更稳定的估计
RepeatedKFold 和 RepeatedStratifiedKFold 把 K-Fold 运行好几次,每次用不同的 shuffle,然后平均所有分数。 5 个 fold 重复 10 次,得到 50 个分数。平均值更稳定,因为它不再依赖某一种特定的切 fold 方式。
当 dataset 很小,或者你要比较两个分数很接近的 model 时(例如 84.2% 对比 84.7%),就用它。 它要多花 n_repeats 倍的 training,所以在大 dataset 上,普通的 5-fold 通常就够了。
Python —— 公平地比较两个 Model
import numpy as np
from scipy import stats
from sklearn.model_selection import RepeatedStratifiedKFold, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=500, n_features=20,
n_informative=10, random_state=42)
# ── 5 folds x 10 repeats = 50 scores per model ────────
k, r = 5, 10
rskf = RepeatedStratifiedKFold(n_splits=k, n_repeats=r, random_state=42)
rf = cross_val_score(RandomForestClassifier(n_estimators=100, random_state=42), X, y, cv=rskf)
knn = cross_val_score(KNeighborsClassifier(n_neighbors=5), X, y, cv=rskf)
print(f"Random Forest : {rf.mean():.4f} ± {rf.std():.4f}")
print(f"kNN (k=5) : {knn.mean():.4f} ± {knn.std():.4f}")
# ── The 50 scores share training data, so they are NOT independent.
# ── Use the corrected resampled t-test (Nadeau & Bengio, 2003).
d = knn - rf
n_test = len(y) // k
n_train = len(y) - n_test
var = d.var(ddof=1)
t_naive = d.mean() / np.sqrt(var / len(d))
t_corr = d.mean() / np.sqrt((1/len(d) + n_test/n_train) * var)
p_naive = 2 * stats.t.sf(abs(t_naive), df=len(d) - 1)
p_corr = 2 * stats.t.sf(abs(t_corr), df=len(d) - 1)
print(f"\nMean difference : {d.mean():+.4f}")
print(f"Naive t-test : p = {p_naive:.4f} (too optimistic)")
print(f"Corrected t-test: p = {p_corr:.4f}")
The 50 scores are not independent: every training set shares most of its rows with the others. A normal paired t-test ignores this and becomes far too confident. Here it says p ≈ 0.0000, "kNN is clearly better". The corrected resampled t-test (Nadeau & Bengio, 2003) adds the term n_test / n_train to the variance. It says p = 0.18: the 2-point difference could easily be luck.
Nested Cross-Validation — Honest Scores After Tuning
There is a problem. You chose that setting because it did well on these exact folds. Part of its success is luck. So the best score is usually higher than what you will get on truly new data. This is called optimistic selection bias.
Nested CV uses two loops. The inner loop tunes the hyperparameters. The outer loop measures performance on folds that the tuning never touched.
5 outer folds × 3 inner folds = 15 model fits for every hyperparameter setting. With 18 settings that is 270 fits, plus 5 refits. Nested CV is slow, but it is the honest way to report a score when tuning and evaluation happen together.
Try It — The Optimism Gap
This dataset is pure noise: the labels are coin flips, so the true accuracy of any model is 50%. Each "setting" is a model that uses 3 random features. Try more and more settings, keep the best CV score, and compare it with nested CV.
ये 50 scores स्वतंत्र नहीं हैं: हर training set अपनी ज़्यादातर rows बाकी sets के साथ बाँटता है। एक सामान्य paired t-test इसे नज़रअंदाज़ करता है और ज़रूरत से कहीं ज़्यादा पक्का हो जाता है। यहाँ वह कहता है p ≈ 0.0000, "kNN साफ़ तौर पर बेहतर है"। Corrected resampled t-test (Nadeau & Bengio, 2003) variance में n_test / n_train वाला term जोड़ता है। वह कहता है p = 0.18: 2 points का अंतर आसानी से किस्मत हो सकता है।
Nested Cross-Validation — Tuning के बाद ईमानदार Scores
यहाँ एक समस्या है। आपने वह setting इसलिए चुनी क्योंकि उसने इन्हीं folds पर अच्छा किया। उसकी सफलता का कुछ हिस्सा किस्मत है। इसलिए सबसे अच्छा score आमतौर पर उससे ऊँचा होता है जो आपको सच में नए data पर मिलेगा। इसे optimistic selection bias कहते हैं।
Nested CV दो loops इस्तेमाल करता है। Inner loop hyperparameters की tuning करता है। Outer loop उन folds पर performance मापता है जिन्हें tuning ने कभी छुआ नहीं।
5 outer folds × 3 inner folds = हर hyperparameter setting के लिए 15 model fits। 18 settings के साथ यह 270 fits हैं, साथ में 5 refits। Nested CV धीमा है, पर जब tuning और evaluation साथ हों, तो score report करने का यही ईमानदार तरीका है।
ख़ुद आज़माइए — The Optimism Gap
यह dataset सिर्फ़ noise है: labels सिक्का उछालने जैसे हैं, इसलिए किसी भी model की असली accuracy 50% है। हर "setting" एक model है जो 3 random features इस्तेमाल करता है। ज़्यादा से ज़्यादा settings आज़माइए, सबसे अच्छा CV score रखिए, और उसकी तुलना nested CV से कीजिए।
这 50 个分数并不独立:每个 training set 都和其他的共享大部分行。 普通的配对 t-test 忽略了这一点,变得过于自信。在这里它说 p ≈ 0.0000,“kNN 明显更好”。 修正的重抽样 t-test(Nadeau & Bengio, 2003)在 variance 上加了 n_test / n_train 这一项。它说 p = 0.18:这 2 分的差距很可能只是运气。
Nested Cross-Validation —— Tuning 之后的诚实分数
这有一个问题。你选择那个设置,是因为它在这几个特定的 fold 上表现好。它的成功有一部分是运气。 所以最好的分数通常比你在真正的新数据上得到的更高。这叫做乐观的选择 bias(optimistic selection bias)。
Nested CV 用两个循环。inner loop 负责 tuning hyperparameter。outer loop 在 tuning 从没碰过的 fold 上测量表现。
5 个 outer fold × 3 个 inner fold = 每种 hyperparameter 设置要 fit 15 次 model。18 种设置就是 270 次 fit,再加 5 次重新 fit。Nested CV 很慢,但当 tuning 和评估一起进行时,它是报告分数的诚实方法。
动手试试 —— The Optimism Gap
这个 dataset 是纯噪声:label 是抛硬币得来的,所以任何 model 的真实 accuracy 都是 50%。每一种“设置”是一个使用 3 个随机 feature 的 model。试越来越多的设置,保留最好的 CV score,再和 nested CV 比较。
Python — Nested CV with GridSearchCV
Python — GridSearchCV के साथ Nested CV
Python —— 用 GridSearchCV 做 Nested CV
from sklearn.model_selection import StratifiedKFold, GridSearchCV, cross_val_score
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=600, n_features=20,
n_informative=10, random_state=42)
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # honest evaluation
inner_cv = StratifiedKFold(n_splits=3, shuffle=True, random_state=1) # tuning
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [3, 5, None],
'max_features': ['sqrt', 'log2']
} # 3x3x2 = 18 combinations x 3 inner folds = 54 fits per outer fold
# ── GridSearchCV is the inner loop ────────────────────
clf = GridSearchCV(RandomForestClassifier(random_state=42),
param_grid, cv=inner_cv, scoring='accuracy', n_jobs=-1)
# ── cross_val_score is the outer loop ─────────────────
nested = cross_val_score(clf, X, y, cv=outer_cv, scoring='accuracy')
print("Nested CV outer scores:")
for i, s in enumerate(nested, 1):
print(f" Outer fold {i}: {s:.4f}")
print(f"\nNested CV mean : {nested.mean():.4f} ± {nested.std():.4f}")
# ── Non-nested: tune on all data, report the best inner score ──
clf.fit(X, y)
print(f"\nNon-nested best CV score : {clf.best_score_:.4f} <- picked as the best, so it is optimistic")
print(f"Nested CV estimate : {nested.mean():.4f}")
print(f"Difference : {clf.best_score_ - nested.mean():+.4f}")
Here the non-nested score (0.892) is actually lower than the nested score (0.908). So the gap is not always positive on one dataset — both numbers have their own noise. The bias shows up on average, and it grows with the number of settings you try, as the simulator above shows. With a small grid like this one it is small. With a large search it can be several points. When you tune and report together, nested CV (or a separate untouched test set) is the honest choice.
cross_validate — Many Metrics in One Call
cross_val_score returns one metric. cross_validate can return several metrics at once, plus the training scores and the time each fold took.
Use it whenever you need more than one number per fold.
Python — cross_validate with Several Metrics
यहाँ non-nested score (0.892) असल में nested score (0.908) से कम है। यानी एक dataset पर यह अंतर हमेशा positive नहीं होता — दोनों नंबरों में अपना-अपना noise है। Bias औसत में दिखता है, और जितनी ज़्यादा settings आप आज़माते हैं, उतना बढ़ता है, जैसा ऊपर का simulator दिखाता है। इस जैसे छोटे grid के साथ यह छोटा है। बड़ी search के साथ यह कई points हो सकता है। जब आप tuning और report साथ करते हैं, तो nested CV (या एक अलग, अछूता test set) ही ईमानदार चुनाव है।
cross_validate — एक ही Call में कई Metrics
cross_val_score एक metric लौटाता है। cross_validate एक साथ कई metrics लौटा सकता है, साथ में training scores और हर fold में लगा समय भी।
जब भी आपको हर fold के लिए एक से ज़्यादा नंबर चाहिए, इसे इस्तेमाल करें।
Python — कई Metrics के साथ cross_validate
这里非 nested 的分数(0.892)其实比 nested 的分数(0.908)更低。所以在单个 dataset 上,差距不一定是正的 —— 两个数字都有各自的噪声。 bias 是平均来看才出现的,而且你试的设置越多,它就越大,就像上面的模拟器显示的那样。 像这样的小网格,它很小。如果搜索范围很大,它可能有好几分。 当你同时做 tuning 和报告时,nested CV(或一个单独的、没碰过的 test set)是诚实的选择。
cross_validate —— 一次调用得到多个指标
cross_val_score 只返回一个指标。cross_validate 可以一次返回好几个指标,还有 training 分数和每个 fold 花的时间。
只要你每个 fold 需要不止一个数字,就用它。
Python —— 用 cross_validate 得到多个指标
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.datasets import make_classification
X, y = make_classification(n_samples=2000, n_features=20,
weights=[0.8, 0.2], random_state=42)
model = GradientBoostingClassifier(n_estimators=100, random_state=42)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# ── Five metrics in one call, plus train scores ──────
metrics = ['accuracy', 'f1', 'roc_auc', 'precision', 'recall']
res = cross_validate(model, X, y, cv=skf, scoring=metrics,
return_train_score=True) # train scores help spot overfitting
print(f"{'Metric':10s} {'Train':>6s} {'Test':>6s} {'Gap':>7s}")
print("-" * 36)
for m in metrics:
tr, te = res[f'train_{m}'].mean(), res[f'test_{m}'].mean()
print(f"{m:10s} {tr:.4f} {te:.4f} {tr-te:+.4f}")
The gap between train and test scores is your overfitting signal. Accuracy has a small gap (0.05), but F1, precision and recall have gaps of about 0.12.
The model is memorising part of the rare class. More regularisation, a simpler model or more data would help.
With return_train_score=True, this check costs nothing extra.
CV with Pipelines — The Only Safe Way
Any step that learns from the data — scaling, filling missing values, choosing features, target encoding — must be learned on the training fold only. If you run it on all rows before CV, the test rows have already influenced the model. The score becomes too optimistic. Put every such step inside a Pipeline, so CV refits it on each training fold.
Try It — Preprocessing Leakage
60 rows of pure noise, so the honest accuracy is 50%. Compare "prepare on all rows, then CV" with "prepare inside each fold". Increase the number of features, and switch between feature selection and scaling.
Train और test scores के बीच का अंतर आपका overfitting signal है। Accuracy में अंतर छोटा है (0.05), पर F1, precision और recall में अंतर लगभग 0.12 है।
Model दुर्लभ class का कुछ हिस्सा रट रहा है। ज़्यादा regularisation, एक सरल model या ज़्यादा data मदद करेगा।
return_train_score=True के साथ यह जाँच मुफ़्त में हो जाती है।
Pipelines के साथ CV — इकलौता सुरक्षित तरीका
हर वह step जो data से सीखता है — scaling, खाली values भरना, features चुनना, target encoding — सिर्फ़ training fold पर ही सीखा जाना चाहिए। अगर आप इसे CV से पहले सारी rows पर चलाते हैं, तो test rows पहले ही model पर असर डाल चुकी होती हैं। Score ज़रूरत से ज़्यादा अच्छा आता है। हर ऐसे step को Pipeline के अंदर रखें, ताकि CV उसे हर training fold पर फिर से fit करे।
ख़ुद आज़माइए — Preprocessing Leakage
सिर्फ़ noise की 60 rows, इसलिए ईमानदार accuracy 50% है। "सारी rows पर तैयारी, फिर CV" की तुलना "हर fold के अंदर तैयारी" से कीजिए। Features की संख्या बढ़ाइए, और feature selection तथा scaling के बीच बदलिए।
train 和 test 分数之间的差距就是你的 overfitting 信号。accuracy 的差距很小(0.05),但 F1、precision 和 recall 的差距大约是 0.12。
model 正在背下少见 class 的一部分。更多的正则化、更简单的 model 或更多的数据都会有帮助。
用 return_train_score=True,这个检查不需要额外的成本。
CV 配合 Pipeline —— 唯一安全的做法
任何从数据中学习的步骤 —— scaling、填补缺失值、选择 feature、target encoding —— 都只能在 training fold 上学习。 如果你在 CV 之前对所有行运行它,test 行就已经影响了 model。分数会变得太乐观。 把每一个这样的步骤都放进 Pipeline,这样 CV 会在每个 training fold 上重新 fit 它。
动手试试 —— Preprocessing Leakage
60 行纯噪声,所以诚实的 accuracy 是 50%。比较“先在所有行上做准备,再做 CV”和“在每个 fold 内部做准备”。增加 feature 的数量,并在 feature selection 和 scaling 之间切换。
Python — Wrong vs Right
Python — गलत बनाम सही
Python —— 错误做法 vs 正确做法
import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
# ── Pure noise: 100 samples, 5000 random features, random labels ──
# ── No model can truly beat 50% here.
rng = np.random.default_rng(0)
X = rng.normal(size=(100, 5000))
y = rng.integers(0, 2, size=100)
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# ─── ❌ WRONG: pick the 20 "best" features using ALL rows, then CV ──
X_sel = SelectKBest(f_classif, k=20).fit_transform(X, y) # test rows leak here!
wrong = cross_val_score(LogisticRegression(max_iter=1000), X_sel, y, cv=skf)
# ─── ✅ RIGHT: selection + scaling happen inside each training fold ──
pipe = Pipeline([
('scale', StandardScaler()),
('select', SelectKBest(f_classif, k=20)),
('model', LogisticRegression(max_iter=1000)),
])
right = cross_val_score(pipe, X, y, cv=skf)
print(f"❌ Select features first, then CV : {wrong.mean():.2f}")
print(f"✅ Pipeline inside CV : {right.mean():.2f}")
print(" (the true accuracy on noise is 0.50)")
It depends on the step. Choosing features on all rows turned pure noise into "86% accuracy" — a huge lie. Scaling on all rows usually leaks very little. You cannot easily tell in advance, so make the Pipeline a habit for every step that learns from data.
All CV Methods at a Glance — Decision Guide
Answer the five questions below. The guide picks a splitter and writes the scikit-learn code for you.
यह step पर निर्भर करता है। सारी rows पर features चुनने से सिर्फ़ noise "86% accuracy" बन गया — एक बहुत बड़ा झूठ। सारी rows पर scaling आमतौर पर बहुत कम leak करती है। पहले से यह बताना आसान नहीं, इसलिए data से सीखने वाले हर step के लिए Pipeline को आदत बना लें।
सभी CV तरीके एक नज़र में — चुनने की गाइड
नीचे के पाँच सवालों के जवाब दीजिए। गाइड एक splitter चुनती है और आपके लिए scikit-learn code लिख देती है।
这取决于是哪一步。在所有行上选择 feature,把纯噪声变成了“86% accuracy” —— 一个巨大的谎言。在所有行上做 scaling,通常只 leak 很少。 你很难事先知道,所以对每一个从数据中学习的步骤,都要养成用 Pipeline 的习惯。
所有 CV 方法一览 —— 选择指南
回答下面五个问题。指南会帮你选出一个 splitter,并写好 scikit-learn 代码。
| CV Method | Keeps Class Balance | Group-Safe | Time-Safe | Compute Cost | Best For |
|---|---|---|---|---|---|
| KFold | No | No | No | Low | Regression |
| StratifiedKFold | Yes | No | No | Low | Classification (default choice) |
| LeaveOneOut | No | No | No | Very high | Very small datasets |
| StratifiedShuffleSplit | Yes | No | No | You choose | Large datasets, quick repeated checks |
| GroupKFold | No | Yes | No | Low | Patient / user / device data |
| StratifiedGroupKFold | Yes | Yes | No | Low | Grouped classification data |
| TimeSeriesSplit | No | No | Yes | Low | Time-ordered data, forecasting |
| RepeatedStratifiedKFold | Yes | No | No | High | Small data, close model comparisons |
| Nested CV | Yes | If the inner/outer splitters are | If the inner/outer splitters are | Very high | Tuning + honest reporting |
Golden Rules of Cross-Validation
StratifiedKFold(5, shuffle=True, random_state=42), so your results are clear and repeatable.
| CV तरीका | Class का संतुलन रखता है | Group के लिए सुरक्षित | Time के लिए सुरक्षित | Compute ख़र्च | किसके लिए सबसे अच्छा |
|---|---|---|---|---|---|
| KFold | नहीं | नहीं | नहीं | कम | Regression |
| StratifiedKFold | हाँ | नहीं | नहीं | कम | Classification (default चुनाव) |
| LeaveOneOut | नहीं | नहीं | नहीं | बहुत ज़्यादा | बहुत छोटे datasets |
| StratifiedShuffleSplit | हाँ | नहीं | नहीं | आप तय करते हैं | बड़े datasets, जल्दी-जल्दी बार-बार जाँच |
| GroupKFold | नहीं | हाँ | नहीं | कम | मरीज़ / user / device data |
| StratifiedGroupKFold | हाँ | हाँ | नहीं | कम | Groups वाला classification data |
| TimeSeriesSplit | नहीं | नहीं | हाँ | कम | समय के क्रम वाला data, forecasting |
| RepeatedStratifiedKFold | हाँ | नहीं | नहीं | ज़्यादा | छोटा data, पास-पास वाले models की तुलना |
| Nested CV | हाँ | अगर inner/outer splitters हों | अगर inner/outer splitters हों | बहुत ज़्यादा | Tuning + ईमानदार reporting |
Cross-Validation के सुनहरे नियम
StratifiedKFold(5, shuffle=True, random_state=42), ताकि आपके नतीजे साफ़ और दोहराने लायक हों।
| CV 方法 | 保持 Class 平衡 | Group 安全 | 时间安全 | 计算成本 | 最适合 |
|---|---|---|---|---|---|
| KFold | 否 | 否 | 否 | 低 | Regression |
| StratifiedKFold | 是 | 否 | 否 | 低 | Classification(默认选择) |
| LeaveOneOut | 否 | 否 | 否 | 非常高 | 非常小的 dataset |
| StratifiedShuffleSplit | 是 | 否 | 否 | 由你选择 | 大 dataset,快速重复检查 |
| GroupKFold | 否 | 是 | 否 | 低 | 病人 / 用户 / 设备数据 |
| StratifiedGroupKFold | 是 | 是 | 否 | 低 | 有 group 的 classification 数据 |
| TimeSeriesSplit | 否 | 否 | 是 | 低 | 按时间排序的数据,预测未来 |
| RepeatedStratifiedKFold | 是 | 否 | 否 | 高 | 小数据,分数接近的 model 比较 |
| Nested CV | 是 | 取决于 inner/outer splitter | 取决于 inner/outer splitter | 非常高 | Tuning + 诚实报告 |
Cross-Validation 的黄金规则
StratifiedKFold(5, shuffle=True, random_state=42),这样你的结果清楚、可重复。