I’m going to assume something about you.
You’ve trained models. You’ve debugged pipelines at 2AM. You’ve probably copy-pasted more sklearn code than you’d like to admit. And yet… when things scale, everything starts feeling like duct tape.
That’s not a tooling problem. It’s a structure problem.
The difference between a “works-on-my-machine” ML script and a production-ready system is almost always patterns. Not libraries. Not frameworks. Patterns.
Here are 7 that quietly separate solid ML engineers from the ones people rely on.
1. The Configuration-Driven Pipeline (Stop Hardcoding Your Life)
If your hyperparameters are scattered across files like Easter eggs, you’re doing it wrong.
Pattern: Centralize everything into a config.
from dataclasses import dataclass
@dataclass
class Config:
data_path: str = "data/train.csv"
test_size: float = 0.2
random_state: int = 42
model_type: str = "random_forest"
n_estimators: int = 100
config = Config()
Now plug it in:
from sklearn.model_selection import train_test_split
import pandas as pd
df = pd.read_csv(config.data_path)
train, test = train_test_split(df, test_size=config.test_size, random_state=config.random_state)
Why this matters: A 2023 study from Neptune.ai showed that poor experiment tracking is one of the top 3 causes of ML reproducibility failures.
Hardcoding = unreproducible chaos.
2. The Functional Core, Imperative Shell
Your ML code shouldn’t feel like a bowl of spaghetti.
Pattern: Keep logic pure, keep side effects outside.
def train_model(X, y, n_estimators):
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=n_estimators)
model.fit(X, y)
return modeldef train_model(X, y, n_estimators):
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier(n_estimators=n_estimators)
model.fit(X, y)
return model
Then wrap it:
def main(config):
# IO happens here
data = load_data(config.data_path)
model = train_model(data.X, data.y, config.n_estimators)
save_model(model)
Why this matters: Pure functions are testable. Testable code is reliable. Reliable code gets promoted to production.
Everything else gets rewritten.
3. The Pipeline Object Pattern (Stop Reinventing Preprocessing)
If you’re manually scaling, encoding, and transforming data step-by-step… you’re wasting time.
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
pipeline = Pipeline([
("scaler", StandardScaler()),
("model", RandomForestClassifier(n_estimators=100))
])
pipeline.fit(X_train, y_train)
Hidden superpower: You eliminate data leakage without even thinking about it.
And yes, that one bug has ruined more models than bad algorithms ever did.
4. The Lazy Evaluation Pattern (Compute Only When Needed)
Training is expensive. Don’t do it unless you have to.
class LazyModel:
def __init__(self, trainer):
self.trainer = trainer
self._model = None
@property
def model(self):
if self._model is None:
print("Training model...")
self._model = self.trainer()
return self._model
Usage:
lazy_model = LazyModel(lambda: train_model(X, y, 100))
# Training happens only here
preds = lazy_model.model.predict(X_test)
Why this matters: In real systems, unnecessary computation = wasted money. (And if you’ve ever looked at a cloud bill, you know this isn’t theoretical.)
5. The Experiment Tracker Pattern (Because Memory Lies)
“I think the model with 200 trees performed better…”
No. It didn’t. Or maybe it did. You don’t know.
Track it.
import json
from datetime import datetime
def log_experiment(config, metrics):
record = {
"timestamp": str(datetime.now()),
"config": config.__dict__,
"metrics": metrics
}
with open("experiments.json", "a") as f:
f.write(json.dumps(record) + "\n")
Fact: Teams using experiment tracking tools (MLflow, Weights & Biases) report up to 30–40% faster iteration cycles.
Because guessing is slow.
6. The Data Validation Gate (Garbage In Still Wins)
Most ML failures don’t come from models. They come from bad data.
Pattern: Validate before training.
def validate_data(df):
assert not df.isnull().sum().any(), "Missing values detected"
assert df.shape[0] > 100, "Not enough data"
assert "target" in df.columns, "Target column missing"
Use it:
df = load_data()
validate_data(df)
Reality check: You can have a state-of-the-art model and still get garbage predictions if your data pipeline silently breaks.
This happens more often than people admit.
7. The Modular Trainer Pattern (Swap Models Like LEGO)
You shouldn’t rewrite your pipeline every time you try a new model.
class Trainer:
def train(self, X, y):
raise NotImplementedError
class RandomForestTrainer(Trainer):
def train(self, X, y):
from sklearn.ensemble import RandomForestClassifier
model = RandomForestClassifier()
model.fit(X, y)
return model
class LogisticRegressionTrainer(Trainer):
def train(self, X, y):
from sklearn.linear_model import LogisticRegression
model = LogisticRegression()
model.fit(X, y)
return model
Now swap models like this:
trainer = RandomForestTrainer()
model = trainer.train(X_train, y_train)
Why this matters: This is how real ML systems evolve — by swapping components, not rewriting everything.
Thanks for reading. If you enjoyed this, feel free to subscribe to get my stories directly in your inbox.
Comments
Loading comments…