Skip to content
ExponaBLOG
PRACTICAL AI & MACHINE LEARNING · DAY 022 / 100

Feature scaling: when it matters for AI & ML developers

Some models care about scale, some do not.

Expona Admin8 min read

Feature scaling: when it matters for AI & ML developers

We've all heard that normalizing or scaling features is important for machine learning, but the truth is more nuanced: some models are indifferent to scale, while others will fail silently or converge poorly without it. In this article, we'll explore which algorithms demand scaled features, which ones ignore them entirely, and the critical mistakes that trip up teams in production.

Understanding the scaling problem

Before diving into which models need scaling, let's establish why it matters. Consider a dataset with age (range: 18–80) and annual income (range: 20,000–500,000). If we feed these raw values into a linear regression model, the income feature will dominate the learning process simply because its values are larger in magnitude. Gradient descent will take much larger steps to adjust the income weight than the age weight, leading to slower convergence and potentially worse generalization.

Feature scaling solves this by transforming all features to a comparable range, typically 0–1 (min-max scaling) or with mean 0 and standard deviation 1 (standardization). This ensures that no single feature hijacks the optimization process based purely on its numeric scale rather than its predictive power.

Standardize for linear models, SVMs and neural nets

Distance-based and gradient-based algorithms are sensitive to feature magnitude. If you're using linear regression, logistic regression, support vector machines (SVMs), or neural networks, scaling your features is not optional—it's essential for reliable performance.

Linear and logistic regression: These models estimate coefficients using gradient descent. Unscaled features with large ranges will cause the optimizer to take erratic steps and may require you to tune learning rates carefully just to achieve basic convergence.

SVMs: These algorithms compute distances between data points in feature space. A feature with a range of 100,000 will overwhelm one with a range of 1 in distance calculations, rendering the smaller-range feature nearly irrelevant. SVM kernels (RBF, polynomial) amplify this problem.

Neural networks: Deep networks rely on weight initialization schemes (like He or Xavier initialization) that assume features are roughly centered around zero with unit variance. Unscaled inputs can cause vanishing or exploding gradients during backpropagation, making training unstable or extremely slow.

Here's a practical example using scikit-learn:

python
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
import numpy as np

# Raw training data: age (18-80) and income (20k-500k)
X_train = np.array([
    [25, 35000],
    [45, 85000],
    [60, 120000],
])
y_train = np.array([0, 1, 1])

# Without scaling: convergence may be slow or weights unbalanced
# With scaling: convergence is stable and interpretable

pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', LogisticRegression(max_iter=200))
])

pipeline.fit(X_train, y_train)

# When we predict on new data, the scaler is applied automatically
X_test = np.array([[30, 50000]])
prediction = pipeline.predict(X_test)
print(prediction)

Notice that we use a Pipeline. This is crucial: the scaler learns statistics (mean and std) from training data, then applies the same transformation to test data. We'll revisit this pattern later—it's where many teams make critical mistakes.

Trees do not need scaling

Decision trees, random forests, gradient boosting (XGBoost, LightGBM, CatBoost), and other tree-based models are invariant to feature scaling. These algorithms make decisions by splitting on feature values, not distances. Whether income ranges from 20,000 to 500,000 or 0.02 to 0.5 makes no difference—the tree will find the same optimal split points.

This is one reason tree-based models are so popular in practice: they require less preprocessing. You can feed raw features directly without standardization, normalization, or dummy encoding (though categorical encoding is still needed).

However, there's a nuance: if you're using tree models in an ensemble with distance-based models (rare but possible), or if you're using tree models downstream in a stacked ensemble where predictions become features for a linear model, scaling becomes necessary for those downstream components.

For a typical XGBoost workflow, no scaling is needed:

python
import xgboost as xgb

X_train = np.array([
    [25, 35000],
    [45, 85000],
    [60, 120000],
])
y_train = np.array([0, 1, 1])

# No StandardScaler needed
model = xgb.XGBClassifier(max_depth=5, learning_rate=0.1)
model.fit(X_train, y_train)

X_test = np.array([[30, 50000]])
prediction = model.predict(X_test)
print(prediction)

The model trains perfectly well without any scaling step. This simplicity is a feature, not a bug—it's one reason teams gravitate toward gradient boosting for tabular data competitions and production systems.

Fit scalers on training data only

This is where the logic gets critical. A scaler has to learn what "normal" looks like from your training data. It computes statistics: mean and standard deviation for standardization, min and max for min-max scaling. These statistics must come exclusively from the training set, never from the test set or any data you encounter during inference.

Why? Because test data (and real production data) represent unseen future examples. If you fit your scaler on the combined training + test data, you're leaking information from the test set into your scaler. This distorts evaluation metrics and, more critically, your scaler may encounter values in production that are outside the range it was fitted on, leading to inconsistent or extreme scaled values.

Consider a toy scenario:

python
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split

# Example: house prices
X = np.array([
    [2000, 300000],  # sqft, price
    [1500, 250000],
    [3000, 450000],
    [2500, 400000],
])
y = np.array([1, 0, 1, 1])

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.25, random_state=42
)

# WRONG: fit scaler on combined data
scaler_wrong = StandardScaler()
scaler_wrong.fit(np.vstack([X_train, X_test]))
X_train_scaled_wrong = scaler_wrong.transform(X_train)
X_test_scaled_wrong = scaler_wrong.transform(X_test)

# RIGHT: fit scaler on training data only
scaler_right = StandardScaler()
scaler_right.fit(X_train)
X_train_scaled_right = scaler_right.transform(X_train)
X_test_scaled_right = scaler_right.transform(X_test)

print("Scaler fitted on combined data:", scaler_wrong.mean_)
print("Scaler fitted on training only:", scaler_right.mean_)
# These will differ, and the second is correct for honest evaluation

The scaler fitted only on training data may produce scaled test values outside the [-3, 3] range typical for standardized features. This is normal and expected—it tells you your test data has some examples your training data didn't fully capture. In production, this happens frequently, and your model should handle it gracefully.

Keep scalers in the pipeline for inference

Once you've fitted a scaler on training data, you must apply it consistently to every future example, including production inference. The most reliable way to do this is to embed the scaler directly in your prediction pipeline.

If you serialize your scaler separately from your model, or worse, train a scaler, then serialize just the model, you'll eventually make a mistake: someone will forget to apply the scaler, or apply a different scaler, or re-train the scaler on production data. Use pipelines to make scaling automatic and transparent.

Here's a production-ready pattern using scikit-learn:

python
import joblib
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline

X_train = np.array([
    [25, 35000],
    [45, 85000],
    [60, 120000],
    [35, 55000],
])
y_train = np.array([0, 1, 1, 0])

# Create pipeline: scaler + model
pipeline = Pipeline([
    ('scaler', StandardScaler()),
    ('model', RandomForestClassifier(n_estimators=10, random_state=42))
])

# Fit on training data
pipeline.fit(X_train, y_train)

# Save the entire pipeline
joblib.dump(pipeline, 'model_pipeline.pkl')

# Later, in production: load and predict
loaded_pipeline = joblib.load('model_pipeline.pkl')
X_new = np.array([[50, 90000]])
prediction = loaded_pipeline.predict(X_new)
print(prediction)

By saving and loading the pipeline as a single unit, scaling is guaranteed to happen exactly as it did during training. No room for error, no manual steps.

For more complex systems (FastAPI, Flask, Kubernetes), you can export the scaler's statistics (mean, std) separately and rebuild the scaling logic on the inference side, but make sure version control ties scaler parameters to model code. Better yet, use model serving frameworks (MLflow, KServe, BentoML) that handle this automatically.

Common mistakes

Mistake 1: Scaling before train-test split. This leaks test information into the scaler. Always split first, fit scaler on training data, then transform both train and test.

Mistake 2: Forgetting to scale during inference. You trained with scaled features but deployed a model that receives raw features. Predictions will be garbage. Use pipelines to avoid this.

Mistake 3: Scaling tree-based models unnecessarily. Not a functional mistake, but it wastes computation. Skip scaling for XGBoost, LightGBM, and random forests. Reserve scaling for linear and distance-based models.

Mistake 4: Using a different scaler for different data splits. Train one StandardScaler on training data, save its parameters, and apply it everywhere. Don't fit separate scalers for validation and test sets.

Mistake 5: Scaling categorical features after one-hot encoding. One-hot encoded features are already binary (0 or 1). Scaling them is harmless but unnecessary. Scale numeric features, then one-hot encode categorical ones, or scale everything—just be aware that one-hot features don't need it.

Key takeaways

  • Linear models, SVMs, and neural networks are sensitive to feature scale; tree-based models are not.
  • Always fit scalers on training data only, never on test or combined data.
  • Embed scalers in prediction pipelines to ensure consistent application during inference.
  • If you must deploy scalers separately, version control their parameters alongside model code.
  • Unscaled features fed to a model trained on scaled features will produce incorrect predictions.
  • Use scikit-learn's Pipeline or similar abstractions to eliminate manual scaling steps.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles