Why fewer features can be better
What it is
Each extra column adds parameters to fit and distance to every pairwise comparison. Past some point a model's held-out error rises as features are added, even though its training error keeps falling — the added columns carry noise the model fits as if it were signal.
Why it matters
It reframes feature selection from housekeeping into modelling. Dropping columns can raise accuracy, not merely shrink the file: it lowers variance, cuts training and inference cost, and leaves a model whose behaviour someone can still explain to the person who has to act on it.
In the real world: A credit model with 20 clear features is cheaper to run, easier to explain to regulators and breaks less often than one with 2,000.
Visual example
Curse of dimensionalityTraining error falls forever as columns are added; held-out error turns around once the useful columns run out.
Reading the gap
12 features: train 0.090, CV 0.175 — gap 0.085
60 features: train 0.005, CV 0.310 — gap 0.305
48 noise columns bought 0.085 of training error and cost 0.135 of real error
Why this matters
The two curves diverging is what overfitting to *width* looks like. Watching only the blue line — which is what a single fit on all the data shows you — you would add columns forever.
Common misconception
Wrong idea: more features can only help; a useless column will just get a small weight.
Correct idea: a useless column gets a nonzero weight fitted to noise. Fifty of them add up to a model that has memorised the training sample's accidents.
import numpy as np
from sklearn.datasets import make_classification
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
# 8 informative columns, then a rising pile of pure noise.
X, y = make_classification(n_samples=1500, n_features=8, n_informative=8,
n_redundant=0, random_state=0)
rng = np.random.default_rng(0)
for extra in (0, 12, 52):
Xn = np.hstack([X, rng.standard_normal((len(X), extra))])
score = cross_val_score(RandomForestClassifier(random_state=0),
Xn, y, cv=5, scoring="accuracy").mean()
print(f"{8 + extra:>3} features -> {score:.3f}")Judging a feature set by training score, or by the score of the same split you used to choose it. Both improve monotonically as features are added, so both will always tell you to keep everything. The only honest signal is a cross-validated score on data the selection never saw.