ML Algorithm Selection — Why Regression Broke Churn
Ops teams ignored churn scores like 0.73 because they needed yes/no decisions.
20+ years shipping production ML systems and the infrastructure behind them. Written from production experience, not tutorials.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- Algorithm choice depends on your data's label type and the problem you're solving.
- Use regression for predicting continuous numbers (e.g., house prices, temperature, revenue).
- Use classification for predicting discrete categories (e.g., spam/not spam, churn/no churn).
- Use clustering to find natural groupings in unlabeled data (e.g., customer segments).
- Always start with a simple baseline model before adding complexity — a simple model that works beats a complex model you cannot explain.
- The biggest mistake is choosing an algorithm based on hype or familiarity instead of the problem's actual structure.
This article tackles a fundamental mistake beginners make in machine learning: treating a classification problem as a regression problem. The canonical example is churn prediction — you want to predict if a customer will leave (a category: yes/no), not when or how much they'll spend (a number).
Using regression here gives you meaningless floating-point outputs like 0.73, which you then have to threshold arbitrarily, losing calibration and interpretability. The core insight is that your algorithm choice must match your label type: continuous numbers (regression), discrete categories (classification), or unlabeled structure (clustering).
The article walks through each family with concrete use cases — predicting house prices (regression), spam detection (classification), customer segmentation (clustering) — and provides a comparison table mapping problem types to algorithms like linear regression, logistic regression, k-means, and decision trees. By the end, you'll stop reaching for regression when you need classification, and you'll have a mental framework for picking the right tool from the start.
Choosing an algorithm is like picking a tool from a toolbox. You would not use a hammer to turn a screw — not because hammers are bad, but because they are the wrong tool for the job. This guide gives you a simple decision map: look at your data, identify what you need to predict, and pick the tool designed for exactly that job. The map has two forks — do you have labeled examples, and what does your output look like? Every other choice flows from those two answers.
Selecting the wrong algorithm wastes time and produces misleading results. The frustrating part is that most algorithms will run without errors regardless of whether they are appropriate — they just produce results that look plausible but are fundamentally wrong for the problem.
This guide cuts through the noise with a direct decision flow based on your data's structure and prediction goal. We focus on the foundational algorithms every practitioner must know before reaching for advanced variants. The goal is not to catalog every technique in the literature — it is to build a reliable selection framework for the problems you will actually encounter in your first year of ML work.
Why Regression Broke Churn
Choosing the right ML algorithm means matching the problem's output type and data structure to a model's inductive bias. For churn prediction, many beginners default to linear regression because they know it from statistics. But regression predicts continuous values, while churn is binary (yes/no). Using regression forces you to threshold the output manually, which introduces arbitrary cutoffs and poor calibration. The core mechanic is simple: classification algorithms (logistic regression, decision trees, random forests) are built to output probabilities for discrete classes, while regression models minimize squared error for unbounded numbers.
In practice, the key property that matters is the loss function. Logistic regression uses log loss, which penalizes confident wrong predictions heavily — exactly what you want when a false positive (marketing to a non-churner) costs money and a false negative (missing a churner) costs a customer. Decision trees and random forests handle non-linear relationships and feature interactions automatically, which is critical when churn depends on combinations like 'usage drops AND support tickets increase.' Linear regression's squared error loss treats a 0.5 prediction for a churner the same as a 1.5 prediction, which makes no sense for a binary outcome.
Use classification algorithms when the target is categorical — churn, fraud, click-through. Use regression only when the target is a continuous quantity like revenue or latency. In production churn systems, logistic regression or gradient-boosted trees (XGBoost, LightGBM) are standard because they output well-calibrated probabilities that can be thresholded by business cost analysis. Starting with regression for churn wastes time on post-processing and often yields worse AUC-ROC by 10-15% compared to a proper classifier.
The Core Decision: Labels and Goals
Every algorithm choice starts with two questions. First, do you have labeled data? Second, what does your output need to look like? These two questions narrow the entire space of possible algorithms down to two or three candidates before you look at a single line of code.
Labeled data means you have historical examples where you know the correct answer — house prices for each house sold, spam/not-spam labels for each email. The output type determines which algorithm family applies: predicting a number maps to regression, predicting a category maps to classification, finding hidden groups in unlabeled data maps to clustering.
Get these two questions wrong and no amount of hyperparameter tuning will rescue the model. The algorithm will train and evaluate without throwing errors — it will just produce results that are structurally misaligned with the problem.
# TheCodeForge — Algorithm Selection Decision Flow # Run this to map your problem to the right algorithm family def choose_algorithm(has_labels: bool, goal: str, n_classes: int = None) -> str: """ A structured decision flow for algorithm family selection. Parameters: ----------- has_labels : bool True if your dataset has a target variable (supervised learning). goal : str One of: 'predict_number', 'predict_category', 'find_groups', 'reduce_dimensions' n_classes : int or None Number of unique target classes (for classification problems). Returns: -------- str : Recommended algorithm family and starting point. """ if has_labels: if goal == 'predict_number': return ( "Regression family.\n" "Start with: Linear Regression\n" "Evaluate with: MAE, RMSE (not accuracy)\n" "Watch for: outliers skewing coefficients" ) elif goal == 'predict_category': if n_classes == 2: return ( "Binary Classification.\n" "Start with: Logistic Regression\n" "Evaluate with: Precision, Recall, F1-score, AUC-ROC\n" "Watch for: class imbalance inflating accuracy" ) elif n_classes and n_classes > 2: return ( "Multi-class Classification.\n" "Start with: Logistic Regression (multi_class='auto')\n" "Or: Decision Tree for non-linear boundaries\n" "Evaluate with: Macro F1-score, per-class precision/recall" ) else: return "Check your goal definition — labeled data implies supervised learning." else: if goal == 'find_groups': return ( "Clustering family.\n" "Start with: K-Means (if K is known or estimable)\n" "Alternative: DBSCAN (if cluster shapes are irregular)\n" "Evaluate with: Silhouette Score, Inertia (elbow method)" ) elif goal == 'reduce_dimensions': return ( "Dimensionality Reduction.\n" "Start with: PCA for linear reduction\n" "Alternative: t-SNE or UMAP for visualization\n" "Note: scale features first — PCA is sensitive to magnitude" ) else: return "Need more problem definition — can you obtain any labels?" # Example usage print(choose_algorithm(has_labels=True, goal='predict_category', n_classes=2)) print() print(choose_algorithm(has_labels=False, goal='find_groups')) print() print(choose_algorithm(has_labels=True, goal='predict_number'))
- Supervised: You have labeled examples (X maps to Y). The algorithm learns the mapping from inputs to outputs.
- Unsupervised: You have only data (X). The algorithm finds hidden structures — groups, patterns, or compressed representations.
- Semi-supervised: A mix of both. A small number of labels guide discovery across a large unlabeled set.
- The presence or absence of labels is the first fork in every algorithm selection decision.
Regression: Predicting Numbers
Use regression when your target variable is a continuous number — house prices, predicted revenue, temperature forecast, time to failure. The model learns to output any real-valued number, and you evaluate it by measuring the magnitude of prediction errors rather than counting correct or incorrect classifications.
Linear Regression is the correct starting point for most problems. It is fast, interpretable, and the coefficients tell you exactly how each feature contributes to the prediction. If the relationship between features and target is genuinely linear, it is often all you need. If the residuals show patterns — systematic over- or under-prediction — that is the signal to consider a more complex model like a Decision Tree Regressor or Gradient Boosting.
The critical mistake beginners make with regression is evaluating it with accuracy. Accuracy is undefined for continuous outputs. Use Mean Absolute Error for an interpretable error in the same units as your target, and RMSE when large errors are disproportionately expensive.
# TheCodeForge — Regression: Predicting Continuous Values from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.tree import DecisionTreeRegressor from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score import numpy as np # Load dataset — predicting median house values X, y = fetch_california_housing(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) print(f'Target range: ${y.min():.2f} to ${y.max():.2f} (units: $100k)') print(f'Training samples: {len(X_train)}, Test samples: {len(X_test)}') # Model 1: Linear Regression (always start here) lr_pipeline = Pipeline([ ('scaler', StandardScaler()), ('model', LinearRegression()) ]) lr_pipeline.fit(X_train, y_train) y_pred_lr = lr_pipeline.predict(X_test) mae_lr = mean_absolute_error(y_test, y_pred_lr) rmse_lr = np.sqrt(mean_squared_error(y_test, y_pred_lr)) r2_lr = r2_score(y_test, y_pred_lr) print(f'\n=== Linear Regression (Baseline) ===') print(f'MAE: {mae_lr:.4f} (avg error: ${mae_lr * 100:.0f}k)') print(f'RMSE: {rmse_lr:.4f}') print(f'R²: {r2_lr:.4f} (explains {r2_lr:.1%} of variance)') # Model 2: Decision Tree Regressor (for non-linear relationships) dt_pipeline = Pipeline([ ('model', DecisionTreeRegressor(max_depth=6, random_state=42)) ]) dt_pipeline.fit(X_train, y_train) y_pred_dt = dt_pipeline.predict(X_test) mae_dt = mean_absolute_error(y_test, y_pred_dt) rmse_dt = np.sqrt(mean_squared_error(y_test, y_pred_dt)) r2_dt = r2_score(y_test, y_pred_dt) print(f'\n=== Decision Tree Regressor (max_depth=6) ===') print(f'MAE: {mae_dt:.4f} (avg error: ${mae_dt * 100:.0f}k)') print(f'RMSE: {rmse_dt:.4f}') print(f'R²: {r2_dt:.4f} (explains {r2_dt:.1%} of variance)') # Residual check — patterns in residuals indicate missed structure residuals = y_test - y_pred_lr print(f'\n=== Residual Diagnostics (Linear Regression) ===') print(f'Mean residual: {residuals.mean():.4f} (should be near 0)') print(f'Residual std: {residuals.std():.4f}') print(f'Max over-pred: {residuals.min():.4f}') print(f'Max under-pred: {residuals.max():.4f}') print(f'\nIf residuals show patterns, the relationship is non-linear.') print(f'Consider Decision Tree, Random Forest, or feature engineering.')
- MAE: average absolute error in target units — most interpretable for stakeholders
- RMSE: penalizes large errors quadratically — use when large errors are costly
- R²: proportion of variance explained — 1.0 is perfect, 0.0 means the model does no better than predicting the mean
- Never use accuracy for regression — it is undefined for continuous outputs
Classification: Predicting Categories
Use classification when your target is a discrete category — spam or not spam, will churn or will not churn, disease present or absent. The model learns decision boundaries that separate categories in feature space, and the output is either a class label or a probability of belonging to each class.
Logistic Regression is the right starting point for binary problems — two classes. Despite the name, it is a classification algorithm. It outputs a probability between 0 and 1, which you convert to a class label using a decision threshold (typically 0.5, but this is tunable based on the cost of false positives versus false negatives). For multi-class problems — three or more categories — Decision Trees are often more interpretable for initial exploration.
The most dangerous mistake in classification is trusting accuracy on imbalanced datasets. If 95% of your training examples belong to one class, a model that always predicts that class achieves 95% accuracy while being completely useless. Always print the full classification report and confusion matrix.
# TheCodeForge — Classification: Predicting Categories from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.tree import DecisionTreeClassifier from sklearn.preprocessing import StandardScaler from sklearn.pipeline import Pipeline from sklearn.metrics import ( classification_report, confusion_matrix, roc_auc_score, accuracy_score ) import numpy as np # Binary classification with moderate class imbalance X, y = make_classification( n_samples=1000, n_features=10, weights=[0.75, 0.25], # 75% class 0, 25% class 1 random_state=42 ) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42, stratify=y ) print('Class distribution (training):') unique, counts = np.unique(y_train, return_counts=True) for cls, cnt in zip(unique, counts): print(f' Class {cls}: {cnt} samples ({cnt/len(y_train):.1%})') # Model 1: Logistic Regression (start here for binary classification) lr_pipeline = Pipeline([ ('scaler', StandardScaler()), ('classifier', LogisticRegression(class_weight='balanced', random_state=42)) ]) lr_pipeline.fit(X_train, y_train) y_pred_lr = lr_pipeline.predict(X_test) y_prob_lr = lr_pipeline.predict_proba(X_test)[:, 1] print(f'\n=== Logistic Regression ===') print(f'Accuracy: {accuracy_score(y_test, y_pred_lr):.2%} <- often misleading') print(f'AUC-ROC: {roc_auc_score(y_test, y_prob_lr):.4f} <- use this') print(f'\nClassification Report:') print(classification_report(y_test, y_pred_lr, target_names=['No Churn', 'Churn'])) print(f'Confusion Matrix:') print(confusion_matrix(y_test, y_pred_lr)) # Model 2: Decision Tree (for interpretable non-linear boundaries) dt_pipeline = Pipeline([ ('classifier', DecisionTreeClassifier( max_depth=5, class_weight='balanced', random_state=42 )) ]) dt_pipeline.fit(X_train, y_train) y_pred_dt = dt_pipeline.predict(X_test) y_prob_dt = dt_pipeline.predict_proba(X_test)[:, 1] print(f'\n=== Decision Tree (max_depth=5) ===') print(f'Accuracy: {accuracy_score(y_test, y_pred_dt):.2%}') print(f'AUC-ROC: {roc_auc_score(y_test, y_prob_dt):.4f}') print(f'\nClassification Report:') print(classification_report(y_test, y_pred_dt, target_names=['No Churn', 'Churn'])) # Decision threshold tuning print(f'\n=== Threshold Tuning (Logistic Regression) ===') print(f'Default threshold (0.5): predicts class 1 if P(churn) > 0.5') print(f'Lower threshold (0.3): catches more churners, more false alarms') print(f'Higher threshold (0.7): fewer false alarms, misses more churners') for threshold in [0.3, 0.5, 0.7]: y_pred_t = (y_prob_lr >= threshold).astype(int) from sklearn.metrics import precision_score, recall_score p = precision_score(y_test, y_pred_t) r = recall_score(y_test, y_pred_t) print(f' Threshold {threshold}: Precision={p:.2%}, Recall={r:.2%}')
Clustering: Finding Natural Groups
Use clustering when you have no labels and want to discover inherent groupings in your data — customer segments with different spending patterns, documents organized by topic, sensor readings that cluster into operational states. The key distinction from classification is that clustering is exploratory: you are not predicting a known category, you are discovering whether natural categories exist.
K-Means is the standard starting point. It partitions data into K clusters by minimizing within-cluster variance. The constraint is that you must specify K in advance. Use the elbow method (plot inertia vs. K) or the silhouette score to estimate a sensible value. If clusters are irregular in shape, have very different densities, or you genuinely do not know how many groups to expect, DBSCAN is a better choice — it finds dense regions and explicitly marks sparse points as noise rather than forcing them into a cluster.
Clustering results are not self-validating. Statistical measures like silhouette score tell you whether clusters are internally cohesive, but they cannot tell you whether the clusters are meaningful for your business. Always present cluster profiles to domain experts and ask whether the discovered groups make practical sense.
# TheCodeForge — Clustering: Finding Natural Groups import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_blobs from sklearn.cluster import KMeans, DBSCAN from sklearn.preprocessing import StandardScaler from sklearn.metrics import silhouette_score # Simulated customer data — 3 natural segments X, true_labels = make_blobs( n_samples=300, centers=3, cluster_std=1.2, random_state=42 ) # Scale features — critical for distance-based algorithms scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Step 1: Find the right K using the elbow method print('=== Elbow Method: Finding the Right K ===') inertias = [] silhouette_scores = [] K_range = range(2, 9) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X_scaled) inertias.append(kmeans.inertia_) score = silhouette_score(X_scaled, kmeans.labels_) silhouette_scores.append(score) print(f' K={k}: Inertia={kmeans.inertia_:.1f}, Silhouette={score:.3f}') best_k = K_range[np.argmax(silhouette_scores)] print(f'\nBest K by silhouette score: {best_k}') # Step 2: Fit K-Means with the best K kmeans_final = KMeans(n_clusters=best_k, random_state=42, n_init=10) kmeans_final.fit(X_scaled) km_labels = kmeans_final.labels_ print(f'\n=== K-Means Results (K={best_k}) ===') for cluster_id in range(best_k): cluster_size = np.sum(km_labels == cluster_id) print(f' Cluster {cluster_id}: {cluster_size} samples ({cluster_size/len(X):.1%})') print(f'Final silhouette score: {silhouette_score(X_scaled, km_labels):.3f}') print(f' (0 = overlapping, 1 = well-separated — higher is better)') # Step 3: DBSCAN for when K is unknown or clusters are irregular print(f'\n=== DBSCAN (no K required) ===') dbscan = DBSCAN(eps=0.5, min_samples=5) db_labels = dbscan.fit_predict(X_scaled) n_clusters = len(set(db_labels)) - (1 if -1 in db_labels else 0) n_noise = np.sum(db_labels == -1) print(f' Clusters found: {n_clusters}') print(f' Noise points: {n_noise} ({n_noise/len(X):.1%} of data)') if n_clusters > 1: non_noise = db_labels != -1 print(f' Silhouette score: {silhouette_score(X_scaled[non_noise], db_labels[non_noise]):.3f}') # Step 4: Profile the clusters — make them actionable print(f'\n=== Cluster Profiles (K-Means) ===') print('Always profile clusters — statistical groupings need business meaning.') for cluster_id in range(best_k): mask = km_labels == cluster_id cluster_data = X[mask] print(f'\n Cluster {cluster_id} ({np.sum(mask)} members):') print(f' Feature 0 mean: {cluster_data[:, 0].mean():.2f}') print(f' Feature 1 mean: {cluster_data[:, 1].mean():.2f}')
- K-Means: use when clusters are roughly spherical and similarly sized, and you have a reasonable estimate of K
- DBSCAN: use when clusters have irregular shapes, you do not know K, or you expect noise and outliers
- Silhouette score measures how well-separated clusters are — higher is better, range is -1 to 1
- Always scale features before clustering — K-Means is dominated by high-magnitude features
The Comparison Table
This table summarizes the key decision points for the core algorithm families. Use it as a quick-reference after you have answered the two foundational questions — labeled or unlabeled, number or category. The table is not exhaustive. Its purpose is to capture the decisions that matter in the first 80% of problems you will encounter as a beginner practitioner.
The Hidden Cost: Training Time vs. Prediction Time
Here's where beginners burn production budgets. They pick an algorithm based only on accuracy curves in a Jupyter notebook. That's a rookie mistake. In production, you pay for two things: training time and prediction time.
Random Forest trains fast and predicts fast. Good baseline. But when your feature space blows up past 10K dimensions, Random Forest chokes. Linear models train in minutes and predict in microseconds. Perfect for high-traffic APIs where latency matters.
Deep learning? Neural networks train for hours on GPU clusters. Prediction is still milliseconds, but you're paying for that infrastructure. SVM sits in the middle — fine for medium datasets, but prediction time scales linearly with support vectors.
Here's the decision matrix nobody shows you: If your model retrains daily, pick something fast to train. If your model makes millions of predictions per second, pick something fast to predict. Never optimize for training accuracy alone.
I've seen teams kill a recommendation system because they picked XGBoost for offline batch jobs but needed sub-10ms predictions online. Split your pipeline: one algorithm for training, a distilled version for inference.
# io.thecodeforge import time from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression import numpy as np X = np.random.rand(10000, 100) y = np.random.rand(10000) start = time.time() rf = RandomForestRegressor(n_estimators=100).fit(X, y) print(f"RF train: {time.time() - start:.2f}s") start = time.time() start = time.time() lr = LinearRegression().fit(X, y) print(f"LR train: {time.time() - start:.2f}s") # Simulate 1M predictions X_test = np.random.rand(1_000_000, 100) start = time.time() rf.predict(X_test) print(f"RF predict (1M): {time.time() - start:.2f}s") start = time.time() lr.predict(X_test) print(f"LR predict (1M): {time.time() - start:.2f}s")
Data Volume Dictates Architecture
Stop reaching for neural networks because they sound impressive. Your dataset size dictates the algorithm family, not your ambition.
Under 1,000 samples? Stick with linear models or decision trees. Complex models overfit instantly. I've debugged production pipelines where a 50-layer network scored 99% on validation and 50% on live traffic. The model memorized noise. Switched to logistic regression with feature engineering — 85% stable accuracy, zero overnight retraining.
Between 1K and 100K samples? Random Forest or gradient boosting (XGBoost/LightGBM) work well. They handle non-linear relationships without needing a GPU farm. SVM with RBF kernel also shines here.
Over 100K samples? Now you can consider deep learning. But only if features are high-dimensional (images, text, audio). For tabular data with millions of rows, XGBoost still beats most neural architectures in production benchmarks — less tuning, faster training, interpretable.
Rule of thumb: 10x more data than features, minimum. If you have 100 features, you need at least 1,000 clean samples. Less than that? Regularize hard or go Bayesian.
Here's the cold truth: Most ML projects fail because teams don't have enough labeled data, not because they picked the wrong algorithm. Spend 80% of your time on data quality, 20% on model choice.
# io.thecodeforge import pandas as pd from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier from sklearn.neural_network import MLPClassifier # Simulate: small dataset (500 samples), 50 features X_small = pd.DataFrame(np.random.rand(500, 50)) y_small = pd.Series(np.random.randint(0, 2, 500)) rf_cv = cross_val_score(RandomForestClassifier(), X_small, y_small, cv=5) nn_cv = cross_val_score(MLPClassifier(hidden_layer_sizes=(100,)), X_small, y_small, cv=5) print(f"Small data (500 rows) RF CV: {rf_cv.mean():.3f}") print(f"Small data (500 rows) NN CV: {nn_cv.mean():.3f}") # Large dataset (50000 samples) X_large = pd.DataFrame(np.random.rand(50000, 50)) y_large = pd.Series(np.random.randint(0, 2, 50000)) rf_cv_large = cross_val_score(RandomForestClassifier(), X_large, y_large, cv=5) nn_cv_large = cross_val_score(MLPClassifier(hidden_layer_sizes=(100,)), X_large, y_large, cv=5) print(f"Large data (50K rows) RF CV: {rf_cv_large.mean():.3f}") print(f"Large data (50K rows) NN CV: {nn_cv_large.mean():.3f}")
Customer Churn Model Fails in Production After Choosing Regression for a Yes/No Problem
- Match the algorithm's output type to the business decision required — not to what seems technically richer.
- A continuous number is not always more informative than a clear category with an associated confidence.
- Validate model outputs with end-users before deployment — a model nobody acts on provides zero value regardless of its accuracy.
value_counts() on your target. Replace accuracy with precision, recall, and F1-score. Apply class_weight='balanced' in your classifier or use SMOTE oversampling. A model that always predicts 'no churn' on a 95/5 split will report 95% accuracy and catch zero churners.df['target'].value_counts(normalize=True) # For classification — check class proportionsdf['target'].describe() # For regression — check if variance is near zerofrom sklearn.cluster import KMeans; inertias = []; [inertias.append(KMeans(n_clusters=k, random_state=42).fit(X).inertia_) for k in range(1, 11)]import matplotlib.pyplot as plt; plt.plot(range(1, 11), inertias, marker='o'); plt.xlabel('K'); plt.ylabel('Inertia'); plt.title('Elbow Method'); plt.show()print(predictions.min(), predictions.max()) # Check prediction boundsimport numpy as np; print(np.sum(predictions < 0), 'negative predictions out of', len(predictions))| File | Command / Code | Purpose |
|---|---|---|
| decision_flow.py | def choose_algorithm(has_labels: bool, goal: str, n_classes: int = None) -> str: | The Core Decision |
| regression_example.py | from sklearn.datasets import fetch_california_housing | Regression |
| classification_example.py | from sklearn.datasets import make_classification | Classification |
| clustering_example.py | from sklearn.datasets import make_blobs | Clustering |
| benchmark_speed.py | from sklearn.ensemble import RandomForestRegressor | The Hidden Cost |
| data_tipping_points.py | from sklearn.model_selection import cross_val_score | Data Volume Dictates Architecture |
Key takeaways
Interview Questions on This Topic
When would you choose a Decision Tree over Logistic Regression for a classification problem?
Your clustering model produces one very large cluster and several tiny ones. What might be wrong?
Why is accuracy a poor metric for a classification problem with 99% negative examples and 1% positive examples?
Walk me through how you would approach a new ML problem from scratch — starting from data to algorithm selection.
Frequently Asked Questions
Technically yes — it is called the Linear Probability Model and it appears in some econometrics contexts. But for most ML applications it is the wrong choice for two reasons. First, Linear Regression can predict values outside the 0-1 range, which makes the outputs uninterpretable as probabilities. Second, it violates the homoscedasticity assumption because the error variance is not constant across prediction ranges for a binary outcome. Logistic Regression is specifically designed for binary outcomes — it applies a sigmoid transformation to constrain outputs between 0 and 1, producing valid probabilities that can be calibrated and thresholded for business decisions. Use Logistic Regression for binary classification. Always.
Two complementary methods. The Elbow Method: plot inertia (within-cluster sum of squared distances) against K from 1 to 10. Look for the point where adding another cluster produces diminishing returns — the 'elbow' in the curve. This gives a rough upper bound on useful K values. The Silhouette Score: for each candidate K, compute the average silhouette score — it measures how similar each point is to its own cluster compared to other clusters, ranging from -1 (wrong cluster) to 1 (perfectly separated). Choose the K that maximizes the silhouette score. Both methods are quantitative guides, not definitive answers. Always validate the final K with domain knowledge — do the discovered groups make practical business sense? If K=4 gives a slightly better silhouette score but the business can only act on 3 segments, K=3 is the right choice.
The critical difference is whether you have ground truth labels. Classification is supervised — you have historical examples where you know the correct category, and the algorithm learns to predict that category for new inputs. You can measure whether the model is correct because you have something to compare against. Clustering is unsupervised — you have no predefined categories, and the algorithm discovers whether natural groupings exist in the data. You cannot measure 'correctness' the same way because there is no ground truth. Evaluation relies on internal metrics like silhouette score, and on whether the discovered groups are meaningful to domain experts. A common mistake is applying clustering when you actually have labels — if you know the correct categories, classification will always outperform clustering for that task.
Almost always, once you have confirmed that a tree-based approach is appropriate for the problem. A single Decision Tree overfits easily — it will memorize training data, producing a large gap between training and test accuracy. Random Forest reduces overfitting by training many trees on different random subsets of the data and features, then averaging their predictions. The cost is interpretability — you lose the clean if-then rule structure of a single tree. The tradeoff is usually worth it: Random Forest reliably outperforms single Decision Trees on most tabular datasets with lower variance in cross-validation performance. Use a single Decision Tree when you need to explain the exact decision logic to a non-technical stakeholder. Use Random Forest when you need better generalization and can tolerate a less interpretable model.
No, but dataset size affects which algorithms are appropriate and what you can expect from them. Simple algorithms like Linear Regression and Logistic Regression work reasonably well on datasets with a few hundred examples. Complex algorithms like neural networks or gradient boosting generally need thousands to hundreds of thousands of examples to generalize reliably — with less data, they overfit. As a rough rule: with fewer than 1,000 examples, start with simple linear models and use cross-validation aggressively to get reliable performance estimates. Between 1,000 and 100,000 examples, tree-based ensemble methods like Random Forest typically perform well. Above 100,000 examples, gradient boosting methods and neural networks become competitive. Small datasets are also where feature engineering matters most — better features compensate more than more complex models.
20+ years shipping production ML systems and the infrastructure behind them. Written from production experience, not tutorials.
That's ML Basics. Mark it forged?
5 min read · try the examples if you haven't