ML Model Degradation — Detecting Data Drift in Production
Fraud losses rose 6 months silently from data drift.
20+ years shipping production ML systems and the infrastructure behind them. Everything here is grounded in real deployments.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- Machine learning lets computers discover patterns in data without explicit programming rules
- Three core types: supervised (labeled data), unsupervised (no labels), reinforcement (reward signals) — plus self-supervised, which powers every foundation model
- Core pipeline: collect data, preprocess, train model, evaluate, deploy, monitor
- In 2026, classical ML and foundation models coexist — choosing the right tool for the problem is now a core engineering skill
- Performance insight: training time scales with data volume and model complexity — a 10x data increase can mean 100x training time for deep learning
- Production insight: models degrade over time as real-world data drifts from training data — silent degradation is the most common production failure
- Biggest mistake: assuming a model that works on test data will work identically in production
Machine learning is a branch of artificial intelligence where systems learn patterns from data without being explicitly programmed for every rule. Instead of hardcoding logic like 'if temperature > 30°C, turn on AC,' you feed thousands of historical temperature and AC usage examples to an algorithm, which discovers the relationship itself.
The core insight: ML models generalize from past examples to make predictions or decisions on new, unseen data. This is fundamentally different from traditional software, where behavior is deterministic and defined line by line. ML thrives in domains where rules are too complex to articulate manually—think spam detection, recommendation engines, or fraud scoring.
The three major paradigms are supervised, unsupervised, and reinforcement learning. Supervised learning uses labeled data (e.g., emails tagged 'spam' or 'not spam') to train a model that maps inputs to outputs. This covers classification (predicting categories like 'fraudulent transaction') and regression (predicting continuous values like house prices).
Unsupervised learning finds hidden structure in unlabeled data—clustering customers by purchasing behavior or reducing dimensionality for visualization. Reinforcement learning trains an agent to make sequences of decisions by rewarding desired outcomes, used in robotics and game-playing AIs like AlphaGo.
In production, supervised learning dominates because business problems usually have clear target variables.
Data preprocessing is the unsung bottleneck. Raw data is messy: missing values, inconsistent formats, outliers, and irrelevant features. A model trained on garbage data will fail silently in production. Common preprocessing steps include handling nulls (imputation or removal), scaling numerical features (standardization or normalization), encoding categorical variables (one-hot or label encoding), and splitting data into training, validation, and test sets.
Tools like pandas, scikit-learn's Pipeline, and TensorFlow's tf.data handle this at scale. Skipping or rushing preprocessing is the #1 cause of model degradation after deployment—your model memorizes noise, not signal.
The core ML pipeline flows from raw data ingestion through preprocessing, feature engineering, model training, evaluation, and deployment to a serving endpoint. In production, this pipeline must be automated and monitored. A model that achieved 95% accuracy on your test set can degrade to 60% within weeks as real-world data drifts.
Detecting this drift—changes in input distributions (covariate shift) or the relationship between inputs and outputs (concept drift)—is the central challenge of maintaining ML systems. Tools like Evidently AI, WhyLabs, and MLflow track these metrics. Without drift detection, your model becomes a liability: it makes confident but wrong predictions, eroding user trust and causing financial loss.
Think about how you learned to spot a suspicious email. Nobody handed you a rulebook. You read enough of them over time, got fooled a few times, got corrected, and gradually built up an intuition. Machine learning gives computers that same ability — instead of a programmer writing every rule by hand, the system processes thousands of labeled examples and figures out the patterns itself. The result is a trained model: a mathematical function that maps new inputs to predictions, built entirely from data rather than from human-written logic.
Machine learning is the practice of training algorithms to find patterns in data and make predictions without being explicitly programmed for each scenario. Traditional software follows hardcoded rules — ML systems learn rules from examples. This distinction matters because real-world data is too complex and variable for manual rule-writing at scale. Fraud detection systems, recommendation engines, medical image classifiers, and demand forecasting models all rely on ML systems trained on large datasets rather than hand-authored decision trees.
In 2026, the ML landscape has matured into two parallel tracks that every developer needs to understand from day one. Classical ML — gradient boosting, random forests, logistic regression — remains the dominant approach for structured tabular data and powers the majority of production ML workloads in banking, retail, logistics, and healthcare. Foundation models — large language models, vision transformers, multimodal systems — have become the default for unstructured data: text, images, audio, and code. Knowing which track to reach for given a specific problem is now as fundamental as knowing how to train a model.
The core workflow is consistent across both tracks: collect data, preprocess it, choose an algorithm or model, train, evaluate honestly, deploy, and monitor for degradation. This guide covers that workflow end to end, with your first working model included.
What Machine Learning Actually Is
Machine learning is a subset of artificial intelligence where algorithms learn patterns from data rather than following explicitly programmed rules. The key distinction from traditional software: in traditional code, a programmer writes rules that process data to produce outputs. In ML, data and desired outputs are fed to an algorithm, and the algorithm discovers the rules itself. The output is a trained model — a mathematical function that maps inputs to predictions.
In 2026, this definition needs one critical addition. There are now two fundamentally different ways to apply ML in practice. You can train a model from scratch on your own labeled data — this is classical ML and remains the dominant approach for structured tabular data. Or you can start from a foundation model — a large pre-trained system like a language model or vision transformer — and adapt it to your problem through fine-tuning, prompting, or retrieval-augmented generation. Understanding when to reach for each approach is a core engineering judgment that belongs early in your learning path, not something to defer until you are advanced. The answer almost always depends on your data type: structured tabular data points toward classical ML, unstructured text or images points toward foundation models.
# TheCodeForge — ML vs Traditional Software # Traditional approach: human writes explicit rules def traditional_spam_filter(email_text: str) -> str: """Rules written by a human. Brittle. Breaks on new spam patterns. Every new attack vector requires a programmer to update this manually. """ if 'buy now' in email_text.lower(): return 'spam' if 'click here' in email_text.lower(): return 'spam' if 'free money' in email_text.lower(): return 'spam' return 'not spam' # ML approach: algorithm learns rules from labeled examples from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression emails = [ 'buy now limited offer click here', 'free money transfer urgent', 'meeting agenda for tomorrow', 'project update attached for review', 'win a prize claim your reward', 'quarterly report is ready' ] labels = ['spam', 'spam', 'not spam', 'not spam', 'spam', 'not spam'] vectorizer = TfidfVectorizer() X = vectorizer.fit_transform(emails) model = LogisticRegression() model.fit(X, labels) new_email = ['urgent free offer claim now'] prediction = model.predict(vectorizer.transform(new_email)) print(f"Traditional filter: {traditional_spam_filter(new_email[0])}") print(f"ML model prediction: {prediction[0]}") print("ML generalises to new patterns — traditional filter only catches what it was explicitly told about")
- Traditional: programmer writes IF-THEN rules manually — maintainable but brittle and expensive to scale to new patterns
- Classical ML: algorithm finds patterns by processing thousands of labeled examples — adaptive but requires clean data and labels
- Output of ML training is a model — a mathematical function, not a set of conditions
- The model replaces hand-written rules at prediction time and can improve as more labeled data arrives
- In 2026: foundation models add a third path — adapt a pre-trained system rather than training from scratch, dominant for unstructured data
The Three Types of Machine Learning
All machine learning falls into three categories based on how the algorithm learns from data. Supervised learning uses labeled examples — input-output pairs where the correct answer is known. Unsupervised learning works with unlabeled data and finds hidden structures. Reinforcement learning trains an agent through trial and error with reward signals. Each type solves different classes of problems and requires different data preparation strategies.
In 2026, a fourth paradigm has become mainstream enough to warrant its own discussion in any honest beginner guide: self-supervised learning. This is how large language models are trained — the model generates its own supervision signal from the structure of raw data (predict the next token, reconstruct a masked word). You will not implement self-supervised pre-training from scratch as a beginner, but understanding it conceptually matters because every foundation model you use — GPT-class models, BERT-class models, vision transformers — was built this way. When you fine-tune or prompt one of these models, you are building directly on top of self-supervised pre-training.
# TheCodeForge — Three types of ML in action import numpy as np from sklearn.linear_model import LinearRegression from sklearn.cluster import KMeans np.random.seed(42) # SUPERVISED LEARNING: predict house prices from features # Data has labels (known prices) — algorithm learns the input-to-output mapping X_train = np.array([[1400, 3], [1600, 3], [1700, 2], [1875, 3], [1100, 2]]) y_train = np.array([245000, 312000, 279000, 308000, 199000]) model = LinearRegression() model.fit(X_train, y_train) prediction = model.predict([[1500, 3]]) print(f"Supervised — Predicted price: ${prediction[0]:,.0f}") # UNSUPERVISED LEARNING: group customers by behaviour # No labels — algorithm discovers natural clusters in the data X_behavior = np.array([[5, 1], [4, 0], [1, 4], [2, 5], [3, 2]]) kmeans = KMeans(n_clusters=2, random_state=42, n_init='auto') clusters = kmeans.fit_predict(X_behavior) print(f"Unsupervised — Cluster assignments: {clusters}") # REINFORCEMENT LEARNING: conceptual illustration # Agent takes actions in an environment and receives reward signals # Full RL requires a simulation environment — see the gymnasium library # Core loop: observe state -> select action -> receive reward -> update policy print("Reinforcement — Agent learns optimal actions via reward signals") # SELF-SUPERVISED LEARNING: conceptual illustration # No human labels required — the model creates its own training signal # Example: given 'The cat sat on the ___', predict the missing word # This is how GPT-class models are pre-trained on billions of tokens print("Self-supervised — Foundation models learn by predicting masked or next tokens from raw data") print("When you fine-tune or prompt an LLM, you are building on top of self-supervised pre-training")
- Have labeled data with known answers and structured tabular features? Use supervised learning
- Have data but no labels? Use unsupervised learning to discover structure or anomalies
- Need an agent to make sequential decisions in an environment? Use reinforcement learning
- Working with raw text, images, or audio at scale? You are almost certainly building on top of self-supervised foundation models
- Most production ML systems on structured tabular data still use supervised learning — it works, it is auditable, and it is cheap to serve
Supervised Learning: Classification and Regression
Supervised learning is the most common ML type in production. It splits into two subtypes: classification (predicting categories) and regression (predicting continuous values). Classification answers 'which category?' — spam or not, fraud or legitimate, cat or dog. Regression answers 'how much?' — house price, temperature, revenue forecast. The training process feeds the algorithm input features and known correct outputs, and the algorithm adjusts internal parameters to minimise prediction error.
One thing that trips up beginners consistently: the choice between classification and regression is determined by your target variable, not by the algorithm family. Random Forests can do both. Gradient Boosting can do both. XGBoost can do both. Read your target variable first — if it is a discrete category, you need a classifier. If it is a continuous number, you need a regressor. Getting this backwards produces output that looks plausible but is conceptually meaningless.
# TheCodeForge — Supervised Learning: Classification and Regression import numpy as np from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score, mean_absolute_error, classification_report np.random.seed(42) # CLASSIFICATION: predict email spam or not spam # Features: word_count_ratio, link_count, exclamation_marks, sender_reputation_score X_class = np.random.rand(1000, 4) y_class = ( X_class[:, 0] * 0.3 + X_class[:, 1] * 0.5 + X_class[:, 2] * 0.4 - X_class[:, 3] * 0.6 > 0.3 ).astype(int) # stratify=y preserves class distribution in both train and test splits X_train, X_test, y_train, y_test = train_test_split( X_class, y_class, test_size=0.2, random_state=42, stratify=y_class ) clf = RandomForestClassifier(n_estimators=100, random_state=42) clf.fit(X_train, y_train) accuracy = accuracy_score(y_test, clf.predict(X_test)) print(f"Classification accuracy: {accuracy:.2%}") print(classification_report(y_test, clf.predict(X_test), target_names=['not spam', 'spam'])) # REGRESSION: predict house price from features # Features: square_feet, bedrooms, age_years, distance_to_city_km X_reg = np.random.rand(1000, 4) * np.array([3000, 5, 50, 20]) y_reg = ( X_reg[:, 0] * 150 + X_reg[:, 1] * 25000 - X_reg[:, 2] * 1000 - X_reg[:, 3] * 5000 + 100000 ) X_train, X_test, y_train, y_test = train_test_split( X_reg, y_reg, test_size=0.2, random_state=42 ) reg = RandomForestRegressor(n_estimators=100, random_state=42) reg.fit(X_train, y_train) mae = mean_absolute_error(y_test, reg.predict(X_test)) print(f"Regression MAE: ${mae:,.0f}")
Data Preprocessing: The Step That Makes or Breaks Your Model
Before any data reaches a model, it must be cleaned, transformed, and structured. This is preprocessing, and it is where most beginners lose the most time — and where most production ML failures originate. Raw data from databases, APIs, and CSV files almost always contains missing values, inconsistent types, categorical strings that algorithms cannot consume directly, and numeric features at wildly different scales.
There are three preprocessing operations you will use on nearly every project. First, handling missing values — either drop rows with nulls (acceptable when data is abundant) or impute them using the column median or a learned strategy. Second, encoding categorical features — converting strings like 'retail' or 'travel' into numeric representations the algorithm can process, typically using one-hot encoding or ordinal encoding. Third, feature scaling — normalising numeric features to a common range so that a feature measured in thousands (square footage) does not dominate a feature measured in single digits (number of bedrooms) simply because of its magnitude.
The critical rule: fit all preprocessing on training data only. Fitting on the full dataset before splitting leaks test set statistics into your training pipeline and inflates accuracy by 10 to 30 percent. This mistake is invisible during development and catastrophic in production.
# TheCodeForge — Data Preprocessing: handling the messy reality of real datasets import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler, OneHotEncoder from sklearn.impute import SimpleImputer from sklearn.compose import ColumnTransformer from sklearn.pipeline import Pipeline np.random.seed(42) # Simulate a messy real-world dataset — missing values, mixed types, different scales data = pd.DataFrame({ 'age': np.random.randint(18, 70, 1000).astype(float), 'income': np.random.exponential(50000, 1000), 'category': np.random.choice(['retail', 'tech', 'healthcare', None], 1000), 'tenure_years': np.random.randint(0, 30, 1000).astype(float), 'churned': np.random.choice([0, 1], 1000, p=[0.85, 0.15]) }) # Inject realistic missing values data.loc[np.random.choice(1000, 50, replace=False), 'age'] = np.nan data.loc[np.random.choice(1000, 30, replace=False), 'income'] = np.nan print(f"Missing values before preprocessing:") print(data.isnull().sum()) print(f"\nData types:\n{data.dtypes}") # Separate features and target BEFORE any preprocessing X = data.drop('churned', axis=1) y = data['churned'] # Split FIRST — preprocessing sees only training data X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # Define preprocessing for each column type numeric_features = ['age', 'income', 'tenure_years'] categorical_features = ['category'] numeric_pipeline = Pipeline([ ('imputer', SimpleImputer(strategy='median')), # fill missing with median from TRAIN only ('scaler', StandardScaler()) # scale to mean=0, std=1 from TRAIN only ]) categorical_pipeline = Pipeline([ ('imputer', SimpleImputer(strategy='most_frequent')), # fill missing categoricals ('encoder', OneHotEncoder(handle_unknown='ignore', sparse_output=False)) ]) # Combine into a single preprocessing step preprocessor = ColumnTransformer([ ('num', numeric_pipeline, numeric_features), ('cat', categorical_pipeline, categorical_features) ]) # fit_transform on TRAIN only — transform on TEST X_train_processed = preprocessor.fit_transform(X_train) X_test_processed = preprocessor.transform(X_test) # transform only, never fit_transform print(f"\nTraining set shape after preprocessing: {X_train_processed.shape}") print(f"Test set shape after preprocessing: {X_test_processed.shape}") print(f"Missing values after preprocessing: {np.isnan(X_train_processed).sum()}") print("Preprocessing complete — no data leakage, ready for model training")
Core ML Pipeline: From Data to Deployment
Every production ML system follows the same pipeline: data collection, preprocessing, feature engineering, model training, evaluation, deployment, and monitoring. Skipping or rushing any step causes failures downstream. The most common production failures trace back to data quality issues, not model architecture choices. A simple model on clean data consistently outperforms a complex model on dirty data.
In 2026, the pipeline includes two additional steps that have become non-negotiable at most organisations. Model cards — structured documentation describing what the model does, what data it was trained on, its known limitations, and where it should not be used — are now a deployment requirement, not a nice-to-have. And for any system that involves a foundation model or embedding pipeline, drift monitoring must cover vector-level staleness in addition to feature distribution shifts.
# TheCodeForge — Complete ML Pipeline import pandas as pd import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report import joblib # Step 1: Data Collection (simulated fraud detection dataset) np.random.seed(42) data = pd.DataFrame({ 'transaction_amount': np.random.exponential(100, 5000), 'merchant_category': np.random.choice(['retail', 'food', 'travel', 'online'], 5000), 'hour_of_day': np.random.randint(0, 24, 5000), 'distance_from_home': np.random.exponential(10, 5000), 'is_fraud': np.random.choice([0, 1], 5000, p=[0.97, 0.03]) }) # Step 2: Preprocessing — encode categoricals, drop nulls data = pd.get_dummies(data, columns=['merchant_category'], drop_first=True) data = data.dropna() # Step 3: Feature Engineering — separate features from target X = data.drop('is_fraud', axis=1) y = data['is_fraud'] # Step 4: Train/Test Split — stratify to preserve the 97/3 fraud ratio X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) # Step 5: Feature Scaling — fit ONLY on train, transform both scaler = StandardScaler() X_train_scaled = scaler.fit_transform(X_train) X_test_scaled = scaler.transform(X_test) # transform only — never fit_transform on test # Step 6: Model Training model = GradientBoostingClassifier( n_estimators=200, learning_rate=0.1, max_depth=5, random_state=42 ) model.fit(X_train_scaled, y_train) # Step 7: Evaluation — look at recall for the fraud class specifically # The output below reveals the class imbalance problem: # 97% accuracy but 0% fraud recall means the model catches nothing predictions = model.predict(X_test_scaled) print(classification_report(y_test, predictions, target_names=['legitimate', 'fraud'], zero_division=0)) # Step 8: Save model AND scaler with matching version numbers joblib.dump(model, 'fraud_model_v1.pkl') joblib.dump(scaler, 'feature_scaler_v1.pkl') print('Model v1 and scaler v1 saved — version both artifacts together') print('WARNING: 0% fraud recall — this model needs class weight adjustment before deployment') print('Next: fix class imbalance, configure drift monitoring, write model card')
Your First Complete ML Model: From Raw Data to Prediction
This section puts everything together. You will load a dataset, preprocess it, train a model, evaluate it honestly, fix a common failure mode, and produce a working prediction — all in one continuous flow. This is not a toy example. The dataset has class imbalance, and the first model will fail in a way that mirrors real production failures. You will then fix it.
The goal is not just to see working code. The goal is to understand why each step exists, what happens when you skip it, and how to interpret the output critically rather than optimistically. By the end of this section, you will have built, evaluated, broken, and fixed your first ML model.
# TheCodeForge — Your First Complete ML Model import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, cross_val_score from sklearn.preprocessing import StandardScaler from sklearn.ensemble import GradientBoostingClassifier from sklearn.metrics import classification_report, confusion_matrix, roc_auc_score import joblib np.random.seed(42) # --- Step 1: Create a realistic dataset --- n_samples = 5000 data = pd.DataFrame({ 'amount': np.random.exponential(100, n_samples), 'hour': np.random.randint(0, 24, n_samples), 'distance_km': np.random.exponential(10, n_samples), 'is_fraud': np.random.choice([0, 1], n_samples, p=[0.97, 0.03]) }) X = data.drop('is_fraud', axis=1) y = data['is_fraud'] print(f"Dataset: {len(data)} rows, fraud rate: {y.mean():.1%}") print(f"Class distribution: {dict(y.value_counts())}\n") # --- Step 2: Split FIRST, preprocess SECOND --- X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_test_s = scaler.transform(X_test) # --- Step 3: Train without handling imbalance (the naive approach) --- model_v1 = GradientBoostingClassifier(n_estimators=100, random_state=42) model_v1.fit(X_train_s, y_train) print("=== Model v1: No class balancing ===") print(classification_report(y_test, model_v1.predict(X_test_s), target_names=['legit', 'fraud'], zero_division=0)) print(f"Confusion Matrix:\n{confusion_matrix(y_test, model_v1.predict(X_test_s))}") print("Problem: 97% accuracy but 0% fraud recall — model is useless for its intended purpose\n") # --- Step 4: Fix class imbalance with sample_weight --- # Give fraud cases 30x the weight of legitimate cases during training # This forces the model to pay attention to the minority class weights = np.where(y_train == 1, 30.0, 1.0) model_v2 = GradientBoostingClassifier(n_estimators=200, max_depth=4, random_state=42) model_v2.fit(X_train_s, y_train, sample_weight=weights) print("=== Model v2: With class balancing ===") preds_v2 = model_v2.predict(X_test_s) print(classification_report(y_test, preds_v2, target_names=['legit', 'fraud'], zero_division=0)) print(f"Confusion Matrix:\n{confusion_matrix(y_test, preds_v2)}") # --- Step 5: Use AUC-ROC for a threshold-independent evaluation --- probs_v2 = model_v2.predict_proba(X_test_s)[:, 1] auc = roc_auc_score(y_test, probs_v2) print(f"AUC-ROC: {auc:.4f} (0.5 = random, 1.0 = perfect)") # --- Step 6: Cross-validate for honest accuracy --- cv_scores = cross_val_score(model_v2, scaler.transform(X), y, cv=5, scoring='roc_auc') print(f"Cross-validated AUC: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})\n") # --- Step 7: Save versioned artifacts --- joblib.dump(model_v2, 'fraud_model_v2.pkl') joblib.dump(scaler, 'fraud_scaler_v2.pkl') print('Saved: fraud_model_v2.pkl, fraud_scaler_v2.pkl') print('Next steps: configure drift monitoring, write model card, set up shadow deployment')
- v1: 97% accuracy, catches 0 out of 30 fraud cases — useless for its actual purpose
- v2: 91% accuracy, catches 12 out of 30 fraud cases — imperfect but functional
- Accuracy dropped because the model now flags some legitimate transactions for review — an acceptable cost
- AUC-ROC measures ranking ability independent of threshold — more reliable than accuracy for imbalanced problems
- Cross-validation confirms the improvement is real, not an artifact of one lucky test split
When Machine Learning Fails: Common Pitfalls
ML fails in production for predictable reasons, and most of them are not related to model architecture. Overfitting means the model memorised training data but cannot generalise to new examples. Data drift means production data no longer resembles training data. Class imbalance means the model ignores minority classes. Feature leakage means the model uses information unavailable at prediction time. Each failure mode has specific diagnostic signals and clear remediation paths.
In 2026, two additional failure modes have become common enough to include in any beginner guide. Model over-reliance occurs when teams use a large language model or foundation model for a task that a simple logistic regression would solve more reliably, more cheaply, and more auditably — and the added complexity introduces new failure modes without delivering better results. Vector store staleness occurs when embeddings in a retrieval system were generated by a different model version than the one handling current queries — similarity scores become unreliable, search quality degrades silently, and the failure pattern mirrors data drift but requires a completely different fix.
# TheCodeForge — Diagnosing ML Failure Modes import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split, cross_val_score from sklearn.metrics import confusion_matrix np.random.seed(42) # OVERFITTING DIAGNOSIS: training accuracy far exceeds test accuracy X = np.random.rand(1000, 10) y = (X[:, 0] + X[:, 1] > 1).astype(int) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # Unconstrained depth — model memorises training data including noise overfit_model = RandomForestClassifier(n_estimators=500, max_depth=None, random_state=42) overfit_model.fit(X_train, y_train) train_acc = overfit_model.score(X_train, y_train) test_acc = overfit_model.score(X_test, y_test) print(f"Overfitting signal — Train: {train_acc:.2%}, Test: {test_acc:.2%}") print(f"Gap of {(train_acc - test_acc):.2%} indicates overfitting — constrain depth and use cross-validation") # FIX: constrain depth and use cross-validation for honest accuracy estimate scores = cross_val_score( RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42), X, y, cv=5 ) print(f"Cross-validation accuracy: {scores.mean():.2%} (+/- {scores.std():.2%})") # CLASS IMBALANCE DIAGNOSIS # The model predicts 'legitimate' for everything and achieves 97% accuracy # This is the most dangerous failure mode — it looks correct until you read the confusion matrix y_imbalanced = np.concatenate([np.zeros(970), np.ones(30)]) X_imbalanced = np.random.rand(1000, 5) X_train_imb, X_test_imb, y_train_imb, y_test_imb = train_test_split( X_imbalanced, y_imbalanced, test_size=0.3, random_state=42 ) model = RandomForestClassifier(random_state=42) model.fit(X_train_imb, y_train_imb) preds = model.predict(X_test_imb) print(f"\nImbalanced dataset — Accuracy: {model.score(X_test_imb, y_test_imb):.2%}") print(f"Confusion Matrix:\n{confusion_matrix(y_test_imb, preds)}") print("Bottom-left value = fraud cases the model missed — that number matters more than accuracy")
- Overfitting: model memorises training data and fails on new examples — fix with cross-validation, regularisation, and depth constraints
- Data drift: production data distribution shifts away from training data — fix with statistical drift monitoring and scheduled retraining
- Class imbalance: model ignores rare but critical cases such as fraud — fix with class weights, sample weighting, or prediction threshold tuning
- Feature leakage: model uses information unavailable at prediction time — fix with careful feature pipeline audit before training begins
- Model over-reliance: using a foundation model for a task a simple classical model handles better — fix with honest benchmarking before committing to architecture
- Vector store staleness: embeddings generated by a different model version than current queries — fix with full corpus re-embedding after any embedding model update
Classical ML vs Foundation Models: Choosing the Right Tool in 2026
In 2026, one of the most common questions a beginner asks is: 'Should I train a machine learning model or just use an LLM?' This is not a beginner question — it is the central engineering decision in most ML projects, and the answer is not obvious.
Classical ML — gradient boosting, random forests, logistic regression — is the right tool when your data is structured and tabular, your training labels are available, your problem has a well-defined numeric or categorical target, and you need fast, auditable, cost-efficient inference. These models train in minutes, run cheaply on CPU, are fully explainable with SHAP, and are straightforward to monitor and debug.
Foundation models — LLMs, vision transformers, multimodal systems — are the right tool when your data is unstructured (text, images, audio), when you have limited labeled training data, when the task requires language understanding or generation, or when you need to generalise across many tasks with a single system. The trade-off is cost, latency, opacity, and operational complexity.
The mistake beginners make in 2026 is reaching for an LLM by default because it feels more modern. A logistic regression trained on structured customer data will outperform a prompted LLM on the same task, run 1000x faster, cost a fraction as much to serve, and be fully auditable when a business stakeholder asks why a specific customer was flagged.
# TheCodeForge — Classical ML vs Foundation Model decision guide # Use this as a mental checklist before choosing your approach def recommend_ml_approach( data_type: str, has_labels: bool, sample_count: int, needs_language_understanding: bool, latency_requirement_ms: int ) -> str: """ A simplified decision function — not a substitute for engineering judgment, but a useful gut-check before committing weeks to an architecture. """ if data_type == 'tabular' and has_labels and sample_count > 1000: return ( "Classical ML recommended: gradient boosting or random forest. " "Fast to train, cheap to serve, fully auditable. " "Start with XGBoost or LightGBM." ) if needs_language_understanding or data_type in ['text', 'image', 'audio']: if sample_count < 500: return ( "Foundation model with few-shot prompting recommended. " "You do not have enough data to fine-tune reliably. " "Use RAG if you need domain-specific knowledge." ) if sample_count >= 500: return ( "Fine-tuned foundation model recommended. " "Use LoRA or QLoRA for parameter-efficient fine-tuning. " "Consider smaller models first — Phi-3, Gemma-2, or Mistral variants." ) if latency_requirement_ms < 50: return ( "Classical ML strongly recommended. " "Foundation model inference rarely achieves sub-50ms P99 latency " "without aggressive quantisation. Consider distilled models if foundation models are required." ) return "Evaluate both approaches on a small prototype before committing to architecture." # Example decisions a team would face print("Scenario 1: Tabular churn prediction") print(recommend_ml_approach('tabular', True, 50000, False, 200)) print() print("Scenario 2: Customer support ticket classification") print(recommend_ml_approach('text', False, 200, True, 500)) print() print("Scenario 3: Real-time pricing engine") print(recommend_ml_approach('tabular', True, 10000, False, 30))
- Structured tabular data with labels → classical ML first, always
- Unstructured text, images, or audio → foundation model or fine-tuned model
- Sub-50ms latency requirement → classical ML or heavily quantised small model
- Limited labeled data (under 500 examples) → few-shot prompting or RAG, not fine-tuning
- Need full explainability for regulatory or audit requirements → classical ML with SHAP values
- Need to answer questions over a private knowledge base → RAG over a foundation model
Getting Started With AI: The 80/20 That Actually Matters
You don't need a PhD to ship ML. You need three things: a problem that needs pattern recognition, clean-enough data, and a metric that tells you when you're wrong.
Most tutorials drown you in theory before you've written a single line of prediction. That's cargo-cult teaching. Here's the real start: clone a working pipeline, break it, fix it, then understand why it broke. Your first model should be a linear regression on housing prices, not a transformer from scratch.
The fastest path to production is stealing patterns from code that already works. The AWS Toolkit, Hugging Face, and scikit-learn are your starter pack. You don't build a car engine before you learn to drive. You don't build a neural net before you can interpret a confusion matrix.
Stop reading. Start predicting.
// io.thecodeforge — ml-ai tutorial import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error # Real dataset: Boston Housing (simulated) df = pd.read_csv("boston_housing.csv") X = df.drop("median_value", axis=1) y = df["median_value"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) model = LinearRegression() model.fit(X_train, y_train) predictions = model.predict(X_test) error = mean_absolute_error(y_test, predictions) print(f"First model MAE: ${error:.2f}k")
Learn by Doing: PartyRock Your Way Past Tutorial Hell
Reading about ML is like reading about swimming. You'll drown in theory and never get wet. PartyRock (and tools like it) let you build generative AI apps without writing a single line of infrastructure code. You drag, drop, and break chains of foundation models.
Why this matters: You learn the shape of a problem before you learn the syntax of a solution. You'll see prompt injection wreck your chatbot before you read about adversarial robustness. You'll watch a summariser hallucinate before you study attention mechanisms.
This isn't cheating. It's scaffolding. You build a functional app in 20 minutes, then reverse-engineer why it works. The cognitive load of 'how do I deploy this?' vanishes, leaving you free to focus on 'does this solve the actual user problem?'.
Stop debugging YAML. Start breaking prompts.
// io.thecodeforge — ml-ai tutorial // This is pseudocode for the PartyRock workflow, not raw Python. // Represents a 3-node chain you'd build visually. WORKFLOW_CHAIN = { "node_1": { "model": "Claude Instant", "prompt": "Extract all product names from this customer transcript.", "input": "{{user_query}}" }, "node_2": { "model": "Claude Instant", "prompt": "Categorize each product name: ['Electronics', 'Apparel', 'Food'].", "input": "{{node_1.output}}" }, "node_3": { "model": "Claude Haiku", "prompt": "Check if any extracted product names are on the recall list. Output 'RECALL' or 'CLEAR'.", "input": "{{node_2.output}}" } } // Run: PartyRock invokes each node sequentially.
Clustering: The Easiest Way to Find Patterns When You Have No Labels
Real-world data rarely comes with labels. You've got a million customer records, server logs, or sensor readings — no ground truth, no target variable. Clustering is how you make sense of that chaos. Instead of predicting a value, you're asking the machine: "group these things that look alike."
k-Means is the workhorse here. Pick a number of clusters (k), randomly toss centroids into the feature space, and iterate: assign each point to the nearest centroid, then move centroids to the center of their new cluster. Repeat until nothing moves. Simple. Brutal. It breaks on irregular shapes and requires you to guess k beforehand — but it runs fast and scales to millions of points.
Production trap: k-Means assumes spherical clusters of equal size. That's almost never true. Real data has weird shapes. DBSCAN handles density-based clusters and outliers for free. Always run at least two algorithms before trusting a cluster assignment.
// io.thecodeforge — ml-ai tutorial from sklearn.cluster import KMeans import numpy as np # 100 customer records: [annual_spend, session_count] X = np.random.rand(100, 2) * 100 kmeans = KMeans(n_clusters=3, random_state=42, n_init=10) kmeans.fit(X) predictions = kmeans.predict(X) print("Cluster assignments for first 10 customers:") print(predictions[:10]) print("\nCentroids:") print(kmeans.cluster_centers_)
Dimensionality Reduction: Shut Up Noise, Keep the Signal
Your dataset has 500 columns. Half of them are redundant. Another quarter are pure noise. Dimensionality reduction is the surgical strike that kills useless features while preserving the information that matters. Think of it as compression with a purpose: fewer features means faster training, simpler models, and less overfitting.
Principal Component Analysis (PCA) is the default. It finds the directions (principal components) that capture maximum variance in your data. You project your high-dimensional mess onto these top-k components and boom — your data is now 2D or 10D instead of 500D. The catch: PCA assumes linear relationships. Your components are also uninterpretable linear combinations of original features. You lose the "this feature means dollars spent" story.
Senior shortcut: t-SNE and UMAP are nonlinear alternatives for visualization, but they're stochastic and slow. Use PCA for preprocessing pipelines and feature engineering. Use t-SNE for one-time eyeballing in two dimensions. Never use t-SNE on test data — it won't generalize.
// io.thecodeforge — ml-ai tutorial from sklearn.decomposition import PCA from sklearn.datasets import make_classification # Fake high-dimensional data: 1000 samples, 50 features X, y = make_classification(n_samples=1000, n_features=50, random_state=42) pca = PCA(n_components=2) X_reduced = pca.fit_transform(X) print(f"Original shape: {X.shape}") print(f"Reduced shape: {X_reduced.shape}") print(f"Explained variance ratio: {pca.explained_variance_ratio_}") print(f"Total variance retained: {sum(pca.explained_variance_ratio_):.2%}")
k-Nearest Neighbors: The Lazy Learner That Never Generalizes
Why fit a complex model when you can just remember everything? k-Nearest Neighbors (k-NN) stores all training data and makes predictions by looking at the 'k' closest points. For classification, it votes by majority; for regression, it averages their values. This is a model-free method — no assumptions about data distribution, no training phase. The catch? Distance metric (Euclidean, Manhattan) and 'k' selection dominate performance. Small 'k' overfits to noise; large 'k' blurs decision boundaries. Standardize features first — a feature with large scale unfairly dominates distance calculations. k-NN excels on small datasets with clear clusters but becomes computationally expensive as data grows. Use it as a baseline: if k-NN beats your fancy neural network, your problem likely needs simpler solutions.
// io.thecodeforge — ml-ai tutorial import numpy as np from collections import Counter class KNN: def __init__(self, k=3): self.k = k def fit(self, X, y): self.X_train = np.array(X) self.y_train = np.array(y) def predict(self, X): predictions = [] for x in X: distances = np.linalg.norm(self.X_train - x, axis=1) k_indices = np.argsort(distances)[:self.k] k_labels = self.y_train[k_indices] predictions.append(Counter(k_labels).most_common(1)[0][0]) return np.array(predictions) # demo X = [[1,2],[2,3],[3,1],[6,5],[7,7]] y = [0,0,0,1,1] model = KNN(k=3) model.fit(X, y) print(model.predict([[4,4]]))
Naive Bayes: The Classifier That Assumes Independence and Wins Anyway
Naive Bayes applies Bayes' Theorem with a 'naive' assumption: all features are independent given the class label. Despite this rarely holding in real data, it works surprisingly well for text classification (spam detection, sentiment analysis). Why? Because it only needs to estimate class-conditional probabilities for each feature separately — no need for complex covariance structures. For Gaussian features, use Gaussian Naive Bayes; for discrete counts (word frequencies), use Multinomial. The math is cheap: training is just counting frequencies and computing priors. The pitfall: zero probabilities kill predictions. Apply Laplace smoothing (add 1 to all counts) to fix it. Naive Bayes is a model-based method — it assumes a specific probability distribution. Use it as a fast benchmark; if accuracy is critical, move to logistic regression or random forests.
// io.thecodeforge — ml-ai tutorial from sklearn.naive_bayes import GaussianNB from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score X, y = load_iris(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) model = GaussianNB() model.fit(X_train, y_train) y_pred = model.predict(X_test) print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}") print(f"Predicted class probs: {model.predict_proba(X_test[:1])}")
Model-Based Methods: Learning a Map Before You Drive
Model-based methods ask: can we approximate the real world with a simplified map? Instead of memorizing every training example, you learn a mathematical function—a model—that captures the underlying pattern. Think of it like studying a road map before driving: you generalize from the map, not from each street you've walked. Linear regression is the classic example: you assume the data follows a straight line, solve for slope and intercept, then predict new points by plugging inputs into that equation. Decision trees, support vector machines, and neural networks all fall under this umbrella. The key insight is that you trust the model's structure to extrapolate beyond your data. This works brilliantly when your assumption (e.g., linearity) holds, but fails silently when it doesn't. In production, you must validate that your model's form actually matches the data's shape—otherwise you're navigating with a tourist map in a back alley.
// io.thecodeforge — ml-ai tutorial from sklearn.linear_model import LinearRegression import numpy as np X = np.array([[1], [2], [3], [4]]) y = np.array([2.1, 4.0, 5.9, 8.2]) model = LinearRegression() model.fit(X, y) print(f'Slope: {model.coef_[0]:.2f}, Intercept: {model.intercept_:.2f}') prediction = model.predict([[5]]) print(f'Prediction for x=5: {prediction[0]:.2f}')
Model-Free Methods: Learning by Doing, Not by Mapping
Model-free methods skip the map entirely. Instead of building an approximation of the world, they learn directly from experience—often through trial and error. The most famous example is k-Nearest Neighbors (k-NN): to predict a new point, you simply look at the 'k' closest training examples and take a majority vote. No underlying function, no assumptions about shape, no parameters to learn. This is pure memory: the training data IS the model. Reinforcement learning agents also work model-free when they update actions based solely on rewards, without simulating environment dynamics. The upside? You never mis-specify a model because you never build one. The downside? You need the entire dataset at prediction time, which scales horribly—imagine hauling every street map you've ever seen just to cross a block. In production, model-free methods shine for small, well-curated datasets or when the underlying pattern is too complex to approximate. But watch memory and speed: your 'learning' happens at inference, not training.
// io.thecodeforge — ml-ai tutorial from sklearn.neighbors import KNeighborsClassifier import numpy as np X_train = np.array([[1, 2], [2, 3], [3, 1], [6, 5], [7, 7], [8, 6]]) y_train = np.array([0, 0, 0, 1, 1, 1]) knn = KNeighborsClassifier(n_neighbors=3) knn.fit(X_train, y_train) X_new = np.array([[5, 4]]) print(f'Predicted class: {knn.predict(X_new)[0]}')
Silent Model Degradation in Fraud Detection Pipeline
- Models degrade silently — monitoring recall on the minority class in production is mandatory, not optional
- Data drift detection must be a first-class citizen in your deployment pipeline, not an afterthought configured after the first incident
- Plan for model retraining from day one — a deployed model is not a finished product, it is a service that requires ongoing maintenance
- Shadow deployment is the lowest-risk way to validate a replacement model without exposing users to regression
model.predict() call. Feature engineering and data retrieval are common hidden bottlenecks that often dwarf model inference time. Consider model distillation, quantisation (INT8 or FP16), batch inference, or switching to a lighter algorithm. If serving a large foundation model locally, evaluate GGUF quantisation with llama.cpp or vLLM for batched serving.python -c "import pandas as pd; train = pd.read_csv('train.csv'); prod = pd.read_csv('prod.csv'); print('Train stats:\n', train.describe()); print('Prod stats:\n', prod.describe())"python -c "from scipy.stats import ks_2samp; import pandas as pd; t=pd.read_csv('train.csv'); p=pd.read_csv('prod.csv'); [print(f'{col}: KS={ks_2samp(t[col].dropna(), p[col].dropna()).statistic:.4f}, p={ks_2samp(t[col].dropna(), p[col].dropna()).pvalue:.4f}') for col in t.select_dtypes('number').columns]"python -c "import pandas as pd; df = pd.read_csv('latest_batch.csv'); print(df.dtypes); print(df.isnull().sum()); print(df.describe())"python -c "from evidently.report import Report; from evidently.metric_preset import DataDriftPreset; import pandas as pd; ref=pd.read_csv('train.csv'); cur=pd.read_csv('prod.csv'); report=Report(metrics=[DataDriftPreset()]); report.run(reference_data=ref, current_data=cur); report.save_html('drift_report.html'); print('Drift report saved to drift_report.html')"python -c "import joblib; model = joblib.load('model.pkl'); print('Expected features:', getattr(model, 'feature_names_in_', 'not stored — retrain with DataFrame input to capture names')); print('Expected count:', model.n_features_in_)"python -c "import pandas as pd; df = pd.read_csv('failing_inputs.csv'); print('Types:', df.dtypes.to_dict()); print('Nulls:', df.isnull().sum().to_dict()); print('Sample:', df.head(2).to_dict())"| Aspect | Supervised Learning | Unsupervised Learning | Reinforcement Learning | Foundation Models (Self-Supervised) |
|---|---|---|---|---|
| Training Data | Labeled input-output pairs | Unlabeled inputs only | Environment reward signals | Massive unlabeled corpora — model creates its own supervision signal |
| Goal | Predict known target variable | Discover hidden structure | Learn optimal action sequence | Learn general representations, adapt to many downstream tasks |
| Common Algorithms | Linear Regression, Random Forest, XGBoost, Gradient Boosting | K-Means, PCA, DBSCAN, Isolation Forest, Autoencoders | Q-Learning, PPO, DQN, SAC | Transformer LLMs, Vision Transformers, Multimodal models |
| Evaluation Metric | Accuracy, MAE, F1-Score, AUC-ROC | Silhouette Score, Inertia, Davies-Bouldin Index | Cumulative Reward, Episode Return | Perplexity, BLEU, ROUGE, human evaluation, task-specific benchmarks |
| Production Use | Fraud detection, churn prediction, pricing, demand forecasting | Customer segmentation, anomaly detection, dimensionality reduction | Game AI, robotics, ad bidding, autonomous systems | Chatbots, document Q&A, code generation, image captioning, translation |
| Data Requirement | Thousands to millions of labeled examples | Large unlabeled datasets | Simulation environment or real-world interaction loop | Billions of tokens for pre-training — fine-tuning needs hundreds to thousands of examples |
| Training Cost | Low to moderate — minutes to hours on CPU | Low to moderate | High — many environment interactions required | Extremely high for pre-training, moderate for fine-tuning with LoRA or QLoRA |
| Inference Cost | Very low — sub-millisecond on CPU | Low | Variable depending on policy complexity | High — GPU required unless quantised or distilled to a smaller model |
| Explainability | High — SHAP and LIME provide feature-level explanations | Moderate — clusters are interpretable | Low — policy decisions are opaque | Low to very low without additional interpretability tooling |
| Failure Mode | Overfitting, data drift, class imbalance, feature leakage | Clusters without business meaning, sensitivity to scale | Reward hacking, slow convergence, sim-to-real gap | Hallucination, embedding drift, prompt injection, context window limits |
| File | Command / Code | Purpose |
|---|---|---|
| ml_vs_traditional.py | def traditional_spam_filter(email_text: str) -> str: | What Machine Learning Actually Is |
| three_types_demo.py | from sklearn.linear_model import LinearRegression | The Three Types of Machine Learning |
| supervised_learning.py | from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor | Supervised Learning |
| data_preprocessing.py | from sklearn.model_selection import train_test_split | Data Preprocessing |
| ml_pipeline.py | from sklearn.model_selection import train_test_split | Core ML Pipeline |
| your_first_model.py | from sklearn.model_selection import train_test_split, cross_val_score | Your First Complete ML Model |
| ml_pitfalls.py | from sklearn.ensemble import RandomForestClassifier | When Machine Learning Fails |
| classical_vs_foundation.py | def recommend_ml_approach( | Classical ML vs Foundation Models |
| BareMinimumPipeline.py | from sklearn.model_selection import train_test_split | Getting Started With AI |
| PartyRockPrototype.py | WORKFLOW_CHAIN = { | Learn by Doing |
| kmeans_clustering.py | from sklearn.cluster import KMeans | Clustering |
| pca_reduction.py | from sklearn.decomposition import PCA | Dimensionality Reduction |
| knn_classifier.py | from collections import Counter | k-Nearest Neighbors |
| naive_bayes.py | from sklearn.naive_bayes import GaussianNB | Naive Bayes |
| model_based.py | from sklearn.linear_model import LinearRegression | Model-Based Methods |
| model_free.py | from sklearn.neighbors import KNeighborsClassifier | Model-Free Methods |
Key takeaways
Common mistakes to avoid
6 patternsMemorising code patterns without understanding why each step exists
Reading three tutorials without building anything
Deploying a model without data drift monitoring
Using accuracy as the only evaluation metric
Fitting preprocessing on the full dataset before splitting
fit_transform(). Apply to test data using transform() only, never fit_transform(). Save the fitted preprocessing pipeline alongside the model for production use.Defaulting to a foundation model or LLM when a simpler model would work better
Interview Questions on This Topic
Explain the difference between supervised and unsupervised learning with a real-world example of each.
Your model shows 99% accuracy on the test set but performs poorly in production. What are the three most likely causes?
When would you choose classical ML over a foundation model, and when would you choose a foundation model?
Frequently Asked Questions
Machine learning is a way for computers to learn patterns from data instead of being told exactly what to do through hand-written rules. You provide thousands of examples — emails labeled spam or not spam, transactions labeled fraudulent or legitimate — and the algorithm figures out the rules for distinguishing them on its own. The result is a trained model that makes predictions on new data it has never seen before.
No. You need enough linear algebra to understand what a matrix multiplication is doing, enough calculus to understand why gradient descent moves in the direction it does, and enough statistics to interpret what your evaluation metrics actually measure. None of that requires a degree. Start with scikit-learn, which handles the mathematical machinery for you, and build mathematical intuition incrementally as you encounter specific concepts. The goal is understanding what the algorithm is doing at a conceptual level, not being able to derive it from first principles.
Python. It has the richest ML ecosystem: scikit-learn for classical ML, PyTorch for deep learning and fine-tuning foundation models, pandas for data manipulation, and the Hugging Face ecosystem for accessing and adapting pre-trained models. Every major ML library, dataset, and production framework supports Python as its primary interface. If you already know Python fundamentals — loops, functions, data structures — you have everything you need to start building models today.
It depends entirely on data size and model complexity. A logistic regression on 10,000 rows trains in under a second. A gradient boosting model on 1 million rows might take a few minutes. A deep neural network on millions of images can take hours on a GPU. Fine-tuning a small language model on a custom dataset typically takes hours to days depending on the model size and hardware. For production systems, training is done offline and the trained model is deployed for fast inference — most predictions happen in milliseconds regardless of how long training took.
AI is the broad field concerned with building systems that perform tasks requiring human-like intelligence. Machine learning is one specific approach within AI — it learns from data rather than following explicit rules. Deep learning is a subset of ML that uses neural networks with many layers. Large language models are a product of deep learning trained at massive scale. All machine learning is AI, but not all AI is machine learning — rule-based expert systems and search algorithms are AI without being ML.
Absolutely — and in many production environments it is the more valuable skill. Classical ML — gradient boosting, random forests, logistic regression — dominates production systems for structured tabular data: fraud detection, pricing, churn prediction, risk scoring, demand forecasting. These problems make up the majority of real business ML workloads. LLMs are the right tool for unstructured data and language tasks, but reaching for an LLM on a tabular classification problem is an expensive mistake. Engineers who understand both classical ML and foundation models and can choose between them appropriately are significantly more effective than engineers who only know one track.
Fine-tuning updates the internal weights of a pre-trained model using your own labeled data — the model learns new behaviour and retains it permanently in its parameters. It is the right choice when you need the model to adopt a consistent style, follow specific output formats, or perform a specialised task reliably. RAG (Retrieval-Augmented Generation) keeps the base model frozen and instead retrieves relevant documents from an external knowledge base at inference time, injecting them into the prompt. RAG is the right choice when your knowledge base changes frequently, when you need the model to answer questions grounded in specific documents, or when you cannot afford the cost of retraining. Most production systems that use foundation models use RAG rather than fine-tuning because it is cheaper, more auditable, and easier to update.
20+ years shipping production ML systems and the infrastructure behind them. Everything here is grounded in real deployments.
That's ML Basics. Mark it forged?
10 min read · try the examples if you haven't