Skip to main content

Binary Classification System Design: From Problem Framing to Production Deployment

Comprehensive guide to designing robust binary classification systems with proper framing, feature engineering, and production considerations for real-world ML applications.

Binary classification represents one of the most fundamental and widely deployed machine learning problems in production systems, requiring careful attention to problem framing, data quality, and model robustness for successful real-world deployment.

1. Problem Statement & ML Task Definition

Clarifying questions

  1. What is the expected scale of training data and how frequently will new data arrive?
  2. What is the target latency for real-time predictions (e.g., <10ms, <100ms)?
  3. Is the system expected to serve predictions in real-time or can batch processing be used?
  4. What is the primary business metric that this model should optimize for (e.g., CTR, revenue, engagement)?
  5. What is the acceptable trade-off between model accuracy and prediction latency?
  6. How will model performance be measured in production (e.g., A/B testing, shadow mode)?
  7. What is the expected lifecycle of user and item data in the system?

Business use case and success criteria

The system is designed to predict whether a user will engage with a content item (e.g., click, watch, or purchase) in a binary classification task. The model will power recommendation systems, ad targeting, and content personalization engines to maximize user engagement and business KPIs like revenue or retention.

Success criteria:

  • Offline: AUC-ROC > 0.85, Precision@K > 0.30 for top-K recommendations
  • Online: CTR improvement of 15% over baseline, 20% increase in revenue per user
  • User satisfaction: Engagement rate increase of 10% over control group

ML problem formulation

This is a binary classification task because:

  • The outcome variable is discrete with two possible states (engagement vs. no engagement)
  • The goal is to predict the probability of a user taking an action
  • The problem can be framed as a standard classification task with well-established evaluation metrics

ML evaluation metrics

Offline metrics:

  • AUC-ROC (primary metric for overall discrimination)
  • Precision@K (for top-K recommendation quality)
  • NDCG@K (for ranking quality)
  • F1-score (balanced metric for imbalanced classes)

Online metrics:

  • Click-through rate (CTR) - primary business metric
  • Revenue per user (RPU)
  • Engagement time or session duration
  • Conversion rate (if applicable)

Scale and constraints

Back-of-envelope estimates:

  • Training data size: 50M user-item interactions
  • Number of features: ~10,000 (user features + item features + context features)
  • Embedding matrix memory footprint: 10K * 128 * 4 bytes = ~5MB per embedding table
  • Serving QPS: 10,000 (assuming 10ms p99 latency target)
  • Model size: ~50MB (compressed model size)

Latency budget breakdown:

  • Feature lookup and computation: 5ms
  • Model inference: 3ms
  • Response serialization: 2ms

Non-ML baseline strategy

Rule-based fallback system:

  1. Cold start: Use popularity-based ranking (most viewed items)
  2. User-based: Default to user's historical preferences
  3. Item-based: Use item category or tags for content matching
  4. Time-based: Apply time-sensitive rules (e.g., trending content during peak hours)
  5. A/B test shadow mode: Route 5% of traffic to model for validation before full rollout

2. Data Strategy

Training data sources

  • Explicit feedback: User clicks, likes, purchases, ratings from user actions (e.g., CTR data, purchase conversion)
  • Implicit signals: Session duration, scroll depth, time spent on page, view-through rates, abandon rate
  • Third-party data: Demographic data from partner APIs, contextual signals (weather, events), competitor behavior patterns
  • Synthetic data: Generated for rare event cases or to augment underrepresented user segments

Labeling pipeline design

  • Human annotation: For high-value, rare events (e.g., fraud detection, content moderation) with 3-5 annotators per sample for consensus
  • Weak supervision: Using heuristics and rules to label large volumes of data (e.g., "user clicked > 5 times in 10 min" → positive signal)
  • Programmatic labeling: For real-time signals (e.g., user session with > 10 page views → likely interested)
  • Active learning: Selecting most informative samples for human labeling based on model uncertainty

Class imbalance handling strategy

  • Oversampling: Using SMOTE or random oversampling for minority class to balance training distribution
  • Class weights: Applying inverse frequency weighting in loss function (e.g., weight = 1 / (class_frequency + ε))
  • Focal Loss: For extreme imbalance, reducing loss contribution from easy examples to focus on hard ones
  • Threshold tuning: Adjusting classification threshold at inference time to optimize for precision/recall trade-off

Negative sampling approach

  • Hard negative mining: Selecting hardest negatives from model predictions (e.g., top 10% of lowest confidence negative samples)
  • Negative ratio: Using 1:5 or 1:10 ratio of negative to positive samples in training batches
  • Temporal sampling: Ensuring recent negatives are not over-sampled to avoid data leakage

Data freshness and update frequency requirements

  • Real-time: User action logs updated every 5 minutes (for session-based features)
  • Batch: Historical training data refreshed weekly or monthly for model retraining
  • Freshness SLA: 95% of training data must be < 30 days old for relevance
  • Back-of-envelope estimate: 1M users * 50 interactions/user/day = ~50M samples/day → 1.5B samples/month for training

Privacy compliance

  • GDPR/CCPA: Data anonymization using hashing or tokenization for PII (e.g., user IDs, email addresses)
  • Data retention: 24-month retention policy with automatic deletion of outdated data
  • Consent management: Explicit opt-in for data collection and usage with granular controls
  • Differential privacy: Adding noise to training data for compliance in sensitive domains (e.g., healthcare, finance)

Data quality validation and monitoring

  • Data validation: Schema validation, missing value checks, outlier detection (Z-score > 3)
  • Label consistency: Cross-checking labels across multiple sources and annotators (e.g., > 80% agreement)
  • Data drift detection: Using statistical tests (Kolmogorov-Smirnov) to detect distribution shifts in features or labels
  • Logging: All data ingestion and transformation steps logged with metadata (timestamp, source, batch ID)
import pandas as pd
from sklearn.model_selection import train_test_split

def validate_data_quality(df):
    # Check for missing values
    missing = df.isnull().sum()
    
    # Check for label imbalance
    label_dist = df['label'].value_counts(normalize=True)
    
    # Detect outliers in numerical features
    numeric_cols = df.select_dtypes(include=['number']).columns
    for col in numeric_cols:
        Q1 = df[col].quantile(0.25)
        Q3 = df[col].quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        outliers = df[(df[col] < lower_bound) | (df[col] > upper_bound)]
        print(f"Outliers in {col}: {len(outliers)}")
    
    return missing, label_dist

3. Feature Engineering

Feature Taxonomy

Features are categorized into four main types:

  1. User Features: User demographics, behavior patterns, historical engagement, preferences
  2. Item Features: Content metadata, category, popularity, freshness, quality scores
  3. Context Features: Time-of-day, device type, geographic location, session context
  4. Interaction Features: User-item interaction history, recency, frequency, temporal patterns

Embedding Design

Entity Embeddings:

  • User ID: 128-dimensional, trained via collaborative filtering
  • Item ID: 128-dimensional, trained via matrix factorization
  • Category: 64-dimensional, one-hot encoded then projected

Text Embeddings:

  • Item titles/descriptions: 256-dimensional using BERT-base embeddings
  • User preferences: 128-dimensional using TF-IDF weighted average

Graph Embeddings:

  • User-item interaction graph: 64-dimensional using Node2Vec
  • Content co-occurrence graph: 32-dimensional using GraphSAGE

Real-time vs Batch Feature Computation

Feature TypeComputation ModeReason
User session featuresReal-timeHigh temporal sensitivity
Item popularityBatchStable, computed nightly
User preference embeddingsReal-timeRequires recent interaction updates
Historical engagement countsBatchComputationally expensive, can be pre-computed

Feature Store Design

Online Store:

  • Low-latency key-value store (e.g., Redis or DynamoDB)
  • Serves features with <5ms latency requirement
  • TTL of 1 hour for real-time features

Offline Store:

  • Data warehouse (e.g., BigQuery or Snowflake)
  • For training and batch processing
  • Features stored in parquet format with partitioning by date

Feature Freshness and TTL

FeatureSourceUpdate FrequencyStore Type
User session countReal-time streamEvery 5 minutesOnline
Item categoryCatalog APIDailyOffline
User preference vectorTraining pipelineWeeklyOffline
Session engagement scoreReal-time streamEvery 1 minuteOnline
Item popularity rankBatch jobDailyOffline

Cross Features and Feature Interaction Strategy

Cross features are generated using:

  • Product features: User-item interaction features (e.g., user_age * item_category)
  • Polynomial features: Second-order interactions for key feature pairs
  • Hashing trick: For high-cardinality categorical features to reduce dimensionality

Feature Importance and Selection Methodology

Methodology:

  1. SHAP values: Per-feature contribution to model prediction
  2. Permutation importance: Measure impact of feature removal on AUC
  3. Variance-based selection: Remove features with low variance across training data

Selection criteria:

  • Top 500 features by SHAP importance
  • Remove features with permutation importance < 0.001
  • Maintain feature diversity to avoid overfitting
import numpy as np
from sklearn.feature_selection import SelectKBest, f_classif

class FeatureSelector:
    def __init__(self, k=500):
        self.k = k
        self.selector = SelectKBest(score_func=f_classif, k=k)
    
    def fit_transform(self, X, y):
        return self.selector.fit_transform(X, y)
    
    def get_support_mask(self):
        return self.selector.get_support()

Feature Engineering Pipeline

import pandas as pd
from sklearn.preprocessing import StandardScaler, LabelEncoder

class FeatureEngineeringPipeline:
    def __init__(self):
        self.scaler = StandardScaler()
        self.label_encoders = {}
        
    def preprocess_user_features(self, df):
        # Normalize numerical features
        numeric_cols = ['user_age', 'session_duration', 'items_viewed']
        df[numeric_cols] = self.scaler.fit_transform(df[numeric_cols])
        
        # Encode categorical features
        categorical_cols = ['user_segment', 'device_type']
        for col in categorical_cols:
            if col not in self.label_encoders:
                self.label_encoders[col] = LabelEncoder()
                df[col] = self.label_encoders[col].fit_transform(df[col])
            else:
                df[col] = self.label_encoders[col].transform(df[col])
        
        return df
    
    def create_interaction_features(self, df):
        # Create user-item interaction features
        df['user_item_engagement'] = df['user_age'] * df['item_popularity']
        df['session_category_interaction'] = df['session_duration'] * df['category_encoded']
        
        return df

Feature Validation and Monitoring

  • Feature drift detection: Using statistical tests (Kolmogorov-Smirnov) to detect distribution shifts
  • Missing value monitoring: Alert if >5% of features have missing values
  • Feature distribution validation: Daily checks for mean/variance consistency across user segments

4. Model Architecture

Model Selection with Justification

For binary classification in a recommendation or advertising context, I'll select a Two-Tower Neural Network architecture with the following rationale:

  1. Two-Tower DNN: Scales well to large embedding spaces, handles sparse features efficiently, and supports real-time scoring
  2. Logistic Regression: As baseline - simple, interpretable, good for cold start and rule-based fallback
  3. GBM (XGBoost): Good for feature interaction capture, robust to outliers, and provides feature importance
  4. Transformer: For sequence modeling if user/item history is important (e.g., click sequences, content text)

Trade-offs:

  • Two-Tower DNN: High accuracy but higher latency (50-100ms) and complexity
  • Logistic Regression: Fast inference (1-5ms), interpretable, but limited expressiveness
  • XGBoost: Fast inference (10-20ms), good feature interactions, but less scalable than DNNs
  • Transformer: Highest accuracy for sequence modeling, but highest latency and complexity

Architecture Diagram

graph TD
    A[User Features] --> B[User Tower]
    C[Item Features] --> D[Item Tower]
    B --> E[Merge]
    D --> E
    E --> F[MLP Layers]
    F --> G[Logistic Regression Output]

Two-Tower / Multi-Task / Ensemble Design

The system will use a Two-Tower DNN with optional multi-task learning for related objectives (e.g., CTR prediction, revenue prediction). The architecture will include:

  1. User Tower: Processes user features (demographics, history, preferences)
  2. Item Tower: Processes item features (content, category, metadata)
  3. Interaction Layer: Dot product or MLP-based interaction between towers
  4. Multi-Task Head: Optional shared layers for related objectives

Pre-trained Model Selection and Fine-tuning Strategy

For foundation models or content-heavy systems:

  • BERT/Transformer: Pre-trained on user/item content (e.g., titles, descriptions) for text embeddings
  • Fine-tuning: Use contrastive learning or supervised fine-tuning on task-specific data
  • Adapter Layers: Insert lightweight adapter modules for faster fine-tuning

Model Complexity vs Latency Trade-off Analysis

ModelMetricLatency (p99)Size
Logistic RegressionAUC2ms1KB
XGBoostAUC15ms5MB
Two-Tower DNNAUC75ms100MB
TransformerAUC200ms500MB

Offline Evaluation Baseline Framing

Baseline model: Logistic Regression with engineered features
Proposed model: Two-Tower DNN with embeddings and real-time features
Expected improvement: 5-10% AUC increase

Core Model Code Snippet

import torch
import torch.nn as nn
import torch.nn.functional as F

class TwoTowerDNN(nn.Module):
    def __init__(self, user_emb_dim=128, item_emb_dim=128, hidden_dims=[256, 128, 64]):
        super(TwoTowerDNN, self).__init__()
        
        # User tower
        self.user_tower = nn.Sequential(
            nn.Linear(100, user_emb_dim),  # 100 = user feature dim
            nn.ReLU(),
            nn.Linear(user_emb_dim, user_emb_dim),
            nn.ReLU()
        )
        
        # Item tower
        self.item_tower = nn.Sequential(
            nn.Linear(150, item_emb_dim),  # 150 = item feature dim
            nn.ReLU(),
            nn.Linear(item_emb_dim, item_emb_dim),
            nn.ReLU()
        )
        
        # Interaction and prediction layers
        self.interaction_layer = nn.Linear(user_emb_dim + item_emb_dim, 256)
        self.mlp_layers = nn.ModuleList([
            nn.Linear(256, hidden_dims[i]) for i in range(len(hidden_dims))
        ])
        self.output_layer = nn.Linear(hidden_dims[-1], 1)
        
    def forward(self, user_features, item_features):
        # Embedding towers
        user_emb = self.user_tower(user_features)
        item_emb = self.item_tower(item_features)
        
        # Concatenate and interact
        combined = torch.cat([user_emb, item_emb], dim=1)
        interaction = self.interaction_layer(combined)
        
        # MLP layers
        for layer in self.mlp_layers:
            interaction = F.relu(layer(interaction))
        
        # Final prediction
        output = torch.sigmoid(self.output_layer(interaction))
        return output
    
    def compute_loss(self, predictions, targets):
        # Binary cross entropy loss
        return F.binary_cross_entropy(predictions.squeeze(), targets.float())

# Example usage:
# model = TwoTowerDNN()
# user_features = torch.randn(32, 100)  # batch_size=32
# item_features = torch.randn(32, 150)
# predictions = model(user_features, item_features)
# loss = model.compute_loss(predictions, torch.randint(0, 2, (32, 1)))

This implementation provides a concrete foundation for the model architecture with clear forward pass logic, loss computation, and real-world applicability. The design balances accuracy requirements with latency constraints while remaining production-ready.

5. Serving & Inference Architecture

System Diagram

graph TD
    A[User Request] --> B[Load Balancer]
    B --> C[Feature Store Lookup]
    C --> D[Model Serving Layer]
    D --> E[Scoring/Classification]
    E --> F[Response Formatter]
    F --> G[User Response]

    subgraph "Online Serving Pipeline"
        C --> D
        D --> E
        E --> F
    end

    subgraph "Feature Store"
        C --> H[Online Feature Store]
        H --> D
    end

    subgraph "Model Serving"
        D --> I[TorchServe/ONNX Runtime]
        I --> J[ML Model]
    end

Online vs Batch Serving Decision

Online serving chosen for this binary classification task because:

  • Real-time user interaction requires immediate response (e.g., ad click prediction, content recommendation)
  • Low latency requirement (< 100ms p99) for user experience
  • High QPS (estimated 50K+ requests/sec) demands efficient real-time processing
  • User-specific personalization requires individualized predictions

Two-Stage Pipeline

1. Candidate Retrieval → 2. Scoring/Classification → 3. Re-ranking (if needed)

Stage 1 - Candidate Retrieval:

  • Uses approximate nearest neighbor (ANN) for fast item filtering
  • Reduces candidate set from millions to hundreds/thousands items

Stage 2 - Scoring:

  • Full model inference on filtered candidates
  • Binary classification probability output

Stage 3 - Re-ranking (optional):

  • For high-precision use cases, re-rank top-K items using more expensive models

Serving Infrastructure

Primary choice: TorchServe with ONNX Runtime

  • Justification:
    • Supports model versioning and A/B testing
    • Optimized for low-latency inference (sub-50ms p99)
    • Easy integration with existing ML pipeline
    • Production-grade stability and monitoring support

Alternative consideration: Triton Inference Server for multi-model serving but rejected due to higher complexity and less mature ecosystem for PyTorch models.

Latency Budget Breakdown

Stagep50 Latencyp99 LatencyBudget
Feature Lookup5ms15ms20ms
Model Inference10ms30ms35ms
Response Formatting2ms5ms10ms
Total17ms50ms65ms

Model Compression Strategy

StrategyAccuracy ImpactLatency ImprovementImplementation
Quantization (INT8)-1% AUC30-40% fasterONNX Runtime
Pruning-2% AUC25-35% fasterPyTorch
Distillation-5% AUC60-70% fasterCustom

Selected approach: INT8 quantization with model distillation for production models to meet 50ms p99 requirement.

Caching Strategy

Cache TypeSize EstimatePurpose
Prediction Cache10GBStore recent user predictions
Embedding Cache5GBPre-computed user/item embeddings
KV Cache (LLMs)2GBFor LLM-based features

Implementation: Redis-based cache with TTL of 1 hour for predictions, 24 hours for embeddings.

Auto-scaling and Capacity Planning

QPS Estimation:

  • 50K QPS peak
  • 10K concurrent requests (assuming 5ms p99 inference)
  • 200ms average latency target

Scaling Strategy:

  • Horizontal scaling with Kubernetes pods (min 50, max 200)
  • Auto-scaling based on p99 latency and QPS
  • GPU instances (A10 or L40s) for optimal performance

Capacity Planning:

  • 50K QPS requires ~250 GPU cores (assuming 200ms per request)
  • 100ms p99 target requires 50% headroom for burst traffic

Model Serving Components

class ModelServing:
    def __init__(self, model_path, feature_store):
        self.model = torch.load(model_path)
        self.feature_store = feature_store
        self.model.eval()
        
    def predict(self, user_id, item_id):
        # Fetch features from online store
        features = self.feature_store.get_features(user_id, item_id)
        
        # Prepare input tensor
        input_tensor = torch.tensor(features, dtype=torch.float32)
        
        # Forward pass
        with torch.no_grad():
            logits = self.model(input_tensor)
            probability = torch.sigmoid(logits).item()
            
        return {
            "user_id": user_id,
            "item_id": item_id,
            "probability": probability,
            "prediction": "click" if probability > 0.5 else "no_click"
        }

Fallback Strategy

In case of model failure:

  1. Rule-based fallback: Default probability = 0.1 for new users/items
  2. Stale model serving: Serve last known good model version
  3. Graceful degradation: Return cached predictions if real-time inference fails

Multi-Region Strategy

For global deployment:

  • Regional model replicas in US, EU, Asia
  • Feature store replication with 1-hour TTL
  • Cross-region failover with 500ms latency threshold
  • CDN for static assets and model artifacts

Performance Targets

MetricTargetMeasurement
p50 Latency< 17msProduction monitoring
p99 Latency< 50msProduction monitoring
Availability99.9%SLA compliance
Throughput50K QPSLoad testing
Error Rate< 0.01%Production monitoring

This architecture balances low latency, high availability, and scalability while maintaining flexibility for future model evolution.

6. Evaluation Framework

Offline Evaluation Methodology

For offline evaluation, we use a temporal split approach to ensure that our model is evaluated on data that reflects realistic future conditions. This prevents data leakage and provides a more accurate assessment of model performance in production.

We split the dataset chronologically into:

  • Training set: 60% (oldest data)
  • Validation set: 20% (middle period)
  • Test set: 20% (most recent data)

This ensures that our model is trained on older data and evaluated on newer, unseen data that mirrors real-world conditions.

Cross-validation is not suitable here due to temporal dependencies in user behavior and item popularity trends. Instead, we use time-based holdout validation to simulate real-world deployment scenarios.

Slice-Based Evaluation

We define critical slices for comprehensive evaluation:

Slice TypeDefinitionReasoning
User SegmentsNew vs. returning users, high-traffic vs. low-traffic usersDifferent engagement patterns and feature availability
Geographic RegionsUS, EU, APACRegional content preferences and cultural differences
Device TypesMobile vs. desktop vs. tabletDifferent interaction patterns and feature access
Time of DayMorning, afternoon, eveningUser behavior varies by time context
Content CategoriesNews, entertainment, sportsDifferent engagement dynamics and user preferences

Each slice is evaluated for:

  • AUC-ROC (overall performance)
  • Precision@K (relevance in top-K recommendations)
  • NDCG@K (normalized discounted cumulative gain)

A/B Test Design

We design a randomized controlled experiment with the following specifications:

  • Randomization Unit: User ID (to avoid leakage between users)
  • Sample Size Calculation:
    • Target: 5% lift in CTR (95% confidence, 80% power)
    • Estimated baseline CTR: 2.5%
    • Required sample size: ~180,000 users per variant
  • Test Duration: 30 days (to capture seasonal and weekly patterns)
  • Primary Metric: Click-through rate (CTR) for user engagement
  • Secondary Metrics: Revenue per user, session duration, conversion rate

Guardrail Metrics and Blocking Conditions

To ensure safe deployment, we define guardrail thresholds:

MetricThresholdAction
AUC-ROC Drop< 0.85 (baseline)Block rollout
Precision@10 Drop< 0.75 (baseline)Block rollout
Model Accuracy Drop< 0.80 (baseline)Block rollout
Negative Sample Ratio> 0.25Trigger retraining
Model Confidence Variance> 0.3Investigate drift

Shadow Mode Rollout Plan

Before full A/B testing, we implement a shadow mode rollout:

  1. Phase 1 (Week 1): 5% of traffic routed to new model in shadow mode
  2. Phase 2 (Week 2): 15% of traffic in shadow mode
  3. Phase 3 (Week 3): 50% of traffic in shadow mode
  4. Phase 4 (Week 4): Full production rollout

Shadow mode logs all predictions but does not affect user experience. This allows us to validate model performance under real traffic conditions without risking user impact.

Long-Term Metric Measurement

We track long-term user engagement and business impact:

MetricDefinitionMonitoring Frequency
Delayed Conversion RateUsers who convert within 7 days of exposureDaily
Engagement DecaySession count over time post-exposureWeekly
Revenue Per User (ARPU)Average revenue generated per userWeekly
Retention RateUsers who return after 30 daysWeekly

These metrics help detect long-term performance degradation or unintended consequences of model behavior.

# Example A/B test evaluation code snippet
import numpy as np
from scipy import stats

def ab_test_evaluation(control_results, treatment_results):
    """
    Evaluate A/B test results for binary classification model
    """
    # Calculate CTR for both variants
    control_ctr = np.mean(control_results['clicked'])
    treatment_ctr = np.mean(treatment_results['clicked'])
    
    # Calculate confidence intervals
    control_ci = stats.norm.interval(0.95, 
                                   loc=control_ctr, 
                                   scale=np.sqrt(control_ctr*(1-control_ctr)/len(control_results)))
    treatment_ci = stats.norm.interval(0.95, 
                                     loc=treatment_ctr, 
                                     scale=np.sqrt(treatment_ctr*(1-treatment_ctr)/len(treatment_results)))
    
    # Statistical significance test
    z_stat, p_value = stats.ttest_ind(control_results['clicked'], 
                                     treatment_results['clicked'])
    
    return {
        'control_ctr': control_ctr,
        'treatment_ctr': treatment_ctr,
        'p_value': p_value,
        'confidence_intervals': {
            'control': control_ci,
            'treatment': treatment_ci
        }
    }

# Example usage:
# results = ab_test_evaluation(control_data, treatment_data)

7. Training Pipeline

Training Data Pipeline

The training data pipeline follows a multi-stage ETL process:

  1. Data Extraction: Raw interaction logs from Kafka streams and batch exports from data lakes
  2. Preprocessing:
    • Feature normalization and encoding (one-hot for categorical, min-max scaling for numerical)
    • Missing value imputation using median/mean strategies
    • Outlier detection and handling (Z-score filtering)
  3. Shuffling: Stratified shuffling to maintain label distribution balance
  4. Batching: Batches of 1024 samples with proper shuffling between epochs
import torch
from torch.utils.data import Dataset, DataLoader
from sklearn.preprocessing import StandardScaler, LabelEncoder

class BinaryClassificationDataset(Dataset):
    def __init__(self, features, labels):
        self.features = torch.FloatTensor(features)
        self.labels = torch.FloatTensor(labels).unsqueeze(1)
    
    def __len__(self):
        return len(self.features)
    
    def __getitem__(self, idx):
        return self.features[idx], self.labels[idx]

# Data preprocessing pipeline
def preprocess_data(raw_data):
    # Normalize numerical features
    scaler = StandardScaler()
    numerical_features = raw_data.select_dtypes(include=['float64', 'int64'])
    scaled_features = scaler.fit_transform(numerical_features)
    
    # Encode categorical features
    categorical_features = raw_data.select_dtypes(include=['object'])
    le = LabelEncoder()
    encoded_features = categorical_features.apply(lambda x: le.fit_transform(x))
    
    # Combine features
    processed_features = np.hstack([scaled_features, encoded_features.values])
    return processed_features

# Training data loader setup
train_dataset = BinaryClassificationDataset(processed_features, labels)
train_loader = DataLoader(train_dataset, batch_size=1024, shuffle=True)

Distributed Training Strategy

For large-scale training, we implement data parallelism with PyTorch's DistributedDataParallel (DDP) to distribute training across multiple GPUs:

  • Cluster Configuration: 4x A100 GPUs (40GB VRAM each) with NVLink connectivity
  • Training Duration: Estimated 2 hours for 10M samples with batch size 1024
  • Gradient Accumulation: 4 steps to simulate larger effective batch size (4096)
  • Optimization: AdamW optimizer with learning rate scheduling

Training Infrastructure

  • Compute Cluster: 2x 8-GPU A100 nodes (16 GPUs total)
  • Memory: ~32GB per GPU for model and activations
  • Storage: SSD-backed storage with 100TB capacity for training data
  • Training Time: ~2 hours per epoch for 10M samples

Hyperparameter Tuning Approach

We use Bayesian optimization with Gaussian Process priors to optimize key hyperparameters:

  • Learning Rate: [1e-5, 1e-2] (log scale)
  • Batch Size: [256, 2048] (discrete values)
  • Weight Decay: [1e-6, 1e-2] (log scale)
  • Dropout Rate: [0.1, 0.5] (continuous)
from skopt import gp_minimize
from skopt.space import Real, Integer

def objective(params):
    lr, batch_size, weight_decay, dropout = params
    
    # Initialize model with given hyperparameters
    model = BinaryClassifier(
        input_dim=1000,
        hidden_dims=[512, 256],
        dropout_rate=dropout
    )
    
    # Training loop with given hyperparameters
    optimizer = torch.optim.AdamW(
        model.parameters(), 
        lr=lr, 
        weight_decay=weight_decay
    )
    
    # Return validation AUC-ROC score
    return -validate_model(model, val_loader)

Experiment Tracking and Reproducibility

We implement MLflow for experiment tracking:

  • Experiment Name: "binary_classification_model_v1"
  • Parameters Logged: Learning rate, batch size, weight decay, dropout rate
  • Metrics Tracked: Training loss, validation AUC-ROC, F1 score
  • Model Versioning: Auto-versioned models with Git commit hash attached

Model Versioning and Registry Strategy

  • Versioning: Semantic versioning (v1.0.0, v1.0.1) with automatic increment on successful training
  • Registry: MLflow model registry with staging and production environments
  • Rollback Capability: Versioned models stored in S3 with metadata for quick rollback

Continual / Online Learning Design

For online learning capability:

  • Incremental Updates: Every 24 hours or when data drift exceeds threshold (PSI > 0.1)
  • Online Learning Window: Last 30 days of data for retraining
  • Model Drift Detection: Using KL divergence between training and new data distributions
  • Re-training Trigger: When validation AUC-ROC drops below 0.85 or drift exceeds thresholds
def detect_drift(current_data, reference_data):
    # Calculate KL divergence between feature distributions
    kl_div = 0
    for feature in current_data.columns:
        if feature in reference_data.columns:
            kl_div += calculate_kl_divergence(
                current_data[feature], 
                reference_data[feature]
            )
    return kl_div > 0.1  # Threshold for drift detection

def incremental_update(model, new_data):
    # Update model with new data using smaller batches
    # This enables continuous learning without full retraining
    pass

8. Monitoring & Observability

Data Drift Detection

Data drift detection is critical for maintaining model performance over time. We implement drift monitoring using Population Stability Index (PSI) for categorical features and Kolmogorov-Smirnov (KS) test for continuous features. For numerical features, we use KL divergence to detect distribution shifts.

import numpy as np
from scipy import stats

def calculate_psi(expected, actual, bins=10):
    """Calculate PSI between expected and actual distributions"""
    # Ensure both arrays have same bins
    _, bin_edges = np.histogram(expected, bins=bins)
    
    # Count occurrences in each bin
    expected_counts, _ = np.histogram(expected, bins=bin_edges)
    actual_counts, _ = np.histogram(actual, bins=bin_edges)
    
    # Avoid zero counts
    expected_counts = np.where(expected_counts == 0, 0.0001, expected_counts)
    actual_counts = np.where(actual_counts == 0, 0.0001, actual_counts)
    
    # Calculate PSI
    psi = np.sum((actual_counts - expected_counts) * 
                 np.log(actual_counts / expected_counts))
    return psi

def detect_drift(features_current, features_reference):
    """Detect drift across feature set"""
    drift_results = {}
    
    for feature_name, current_values in features_current.items():
        if feature_name in features_reference:
            reference_values = features_reference[feature_name]
            
            # For continuous features, use KS test
            if current_values.dtype in ['float64', 'int64']:
                ks_stat, p_value = stats.ks_2samp(current_values, reference_values)
                drift_results[feature_name] = {
                    'type': 'ks_test',
                    'statistic': ks_stat,
                    'p_value': p_value,
                    'drift_detected': p_value < 0.05
                }
            else:
                # For categorical features, use PSI
                psi_value = calculate_psi(reference_values, current_values)
                drift_results[feature_name] = {
                    'type': 'psi',
                    'psi_value': psi_value,
                    'drift_detected': psi_value > 0.1
                }
    
    return drift_results

Prediction Drift and Label Drift Monitoring

Prediction drift is monitored by tracking the distribution of model predictions over time. We use statistical tests to detect if prediction distributions have shifted significantly from baseline.

Label drift monitoring involves tracking the distribution of actual labels in the prediction stream to detect if the underlying data distribution has changed.

Model Performance Degradation Alerting

We implement performance degradation alerts using sliding window statistics with thresholds:

  • AUC-ROC drop > 5% over 7-day window triggers alert
  • Precision@K drop > 3% over 30-day window triggers alert
  • Prediction count < 1000/day triggers low-volume alert
class ModelPerformanceMonitor:
    def __init__(self, window_size=30):
        self.window_size = window_size
        self.performance_history = []
        
    def update_performance(self, auc_roc, precision_at_k, timestamp):
        self.performance_history.append({
            'timestamp': timestamp,
            'auc_roc': auc_roc,
            'precision_at_k': precision_at_k
        })
        
        # Keep only last window_size records
        if len(self.performance_history) > self.window_size:
            self.performance_history.pop(0)
            
    def check_degradation(self, threshold_auc=0.05, threshold_precision=0.03):
        if len(self.performance_history) < 10:
            return False, "Insufficient data"
            
        recent_auc = [p['auc_roc'] for p in self.performance_history[-10:]]
        baseline_auc = [p['auc_roc'] for p in self.performance_history[:-10]]
        
        recent_precision = [p['precision_at_k'] for p in self.performance_history[-10:]]
        baseline_precision = [p['precision_at_k'] for p in self.performance_history[:-10]]
        
        auc_drop = np.mean(baseline_auc) - np.mean(recent_auc)
        precision_drop = np.mean(baseline_precision) - np.mean(recent_precision)
        
        if auc_drop > threshold_auc or precision_drop > threshold_precision:
            return True, f"AUC drop: {auc_drop:.3f}, Precision drop: {precision_drop:.3f}"
        return False, "Performance stable"

Retraining Trigger Strategy

We use a hybrid retraining trigger strategy:

  1. Scheduled retraining: Every 30 days or when drift exceeds thresholds
  2. Drift-triggered: When PSI > 0.1 or KS test p-value < 0.05 for any feature
  3. Performance-triggered: When AUC-ROC drops by more than 5% over 7-day window

Logging Design

We implement comprehensive logging for:

  • Prediction logs: Model inputs, predictions, confidence scores, timestamps
  • Feature logs: Real-time feature values, computed at prediction time
  • Label collection pipeline: Ground truth labels for model feedback loop
import json
from datetime import datetime

class PredictionLogger:
    def __init__(self, log_file_path):
        self.log_file_path = log_file_path
        
    def log_prediction(self, user_id, item_id, features, prediction, confidence, 
                       actual_label=None):
        log_entry = {
            'timestamp': datetime.now().isoformat(),
            'user_id': user_id,
            'item_id': item_id,
            'features': features,
            'prediction': float(prediction),
            'confidence': float(confidence),
            'actual_label': actual_label,
            'model_version': 'v1.2'
        }
        
        with open(self.log_file_path, 'a') as f:
            f.write(json.dumps(log_entry) + '\n')

Feedback Loop for Continuous Improvement

We implement a feedback loop that:

  1. Collects labels from user interactions (clicks, purchases, etc.)
  2. Triggers retraining when label volume exceeds threshold (1000 labels/day)
  3. Updates model with new training data in real-time pipeline
  4. Maintains model versioning and rollback capability
class FeedbackLoop:
    def __init__(self, model_version_manager):
        self.model_version_manager = model_version_manager
        self.feedback_queue = []
        
    def collect_feedback(self, user_id, item_id, interaction_type):
        """Collect feedback from user interactions"""
        feedback = {
            'user_id': user_id,
            'item_id': item_id,
            'interaction_type': interaction_type,
            'timestamp': datetime.now(),
            'label': self.convert_interaction_to_label(interaction_type)
        }
        
        self.feedback_queue.append(feedback)
        
        # Trigger retraining if queue is large enough
        if len(self.feedback_queue) >= 1000:
            self.trigger_retraining()
            
    def trigger_retraining(self):
        """Trigger model retraining with new feedback"""
        # Implementation depends on training pipeline
        pass
        
    def convert_interaction_to_label(self, interaction_type):
        """Convert user interaction to binary label"""
        return 1 if interaction_type in ['click', 'purchase', 'view'] else 0

9. Responsible AI & Safety

Bias Analysis

  • Training data audit: Conduct demographic parity checks across user segments (age, gender, geography) to identify underrepresentation in minority groups
  • Slice-based fairness evaluation: Evaluate model performance across user cohorts using metrics like precision, recall, and F1-score to detect disparate impact
  • Disparate treatment detection: Monitor for systematic differences in model behavior between user groups that could indicate bias

Fairness Metrics and Constraints

  • Primary fairness metric: Demographic parity (equalized positive rates across user segments)
  • Secondary constraint: Equalized odds (same true positive and false positive rates across slices)
  • Decision rationale: Demographic parity chosen as primary because it aligns with business goal of equitable user experience while maintaining interpretability for stakeholders

Explainability Requirements and Approach

  • SHAP values: Implement SHAP-based feature importance for model interpretability in production dashboards
  • Attention visualization: For transformer models, provide attention weight heatmaps to explain ranking decisions
  • Local explanations: Generate per-prediction explanations for flagged cases or user feedback

For LLM systems: Safety Guardrails

  • Content filtering: Implement toxicity and safety content filters using pre-trained classifiers (e.g., toxicity classifier from HuggingFace)
  • Output validation: Validate generated content against factual accuracy using external knowledge bases or fact-checking APIs
  • Rate limiting: Enforce rate limits per user to prevent abuse and ensure system stability
  • Red teaming: Regular adversarial testing with human red teams to identify potential safety gaps and harmful outputs

Model Card Documentation Plan

  • Model metadata: Include model version, training data sources, performance metrics, and known limitations
  • Usage guidelines: Document intended use cases, restrictions, and recommended fallback strategies
  • Bias documentation: Record bias analysis results, fairness metrics, and mitigation steps taken

Incident Response Plan for Model Failures or Harmful Outputs

  • Failure detection: Alert system when prediction drift exceeds thresholds (e.g., >15% change in prediction distribution)
  • Harmful output handling: Immediate flagging and human review for outputs that trigger content safety filters
  • Rollback capability: Automated rollback to previous model version with A/B testing of new versions
  • Escalation path: Define clear escalation paths for safety incidents involving human reviewers and legal teams
# Example safety guardrail implementation for content filtering
import torch
from transformers import pipeline

class ContentSafetyFilter:
    def __init__(self):
        self.toxicity_classifier = pipeline(
            "text-classification",
            model="unitary/toxic-bert"
        )
    
    def is_safe(self, text: str) -> bool:
        """Check if generated content passes safety thresholds"""
        toxicity_scores = self.toxicity_classifier(text)
        # Flag if any toxicity score exceeds threshold
        return not any(score['score'] > 0.7 for score in toxicity_scores)
    
    def filter_output(self, text: str) -> str:
        """Filter content and return safe version or fallback"""
        if not self.is_safe(text):
            return "Content filtered due to safety concerns"
        return text

# Example usage in inference pipeline
safety_filter = ContentSafetyFilter()
safe_output = safety_filter.filter_output(generated_text)

10. Scalability & Reliability

Bottleneck Analysis per Pipeline Stage

Training Pipeline:

  • Data loading: 10GB/s → 50GB/s (estimated) for large-scale datasets
  • Model training: 100ms per batch → 500ms (with distributed training)
  • Feature engineering: 200ms per sample → 1s (real-time features)

Serving Pipeline:

  • Feature lookup: 5ms per user → 20ms (with embedding cache)
  • Model inference: 10ms per request → 50ms (with quantization)
  • Response generation: 5ms per request → 25ms (with caching)

Back-of-envelope estimates:

  • Training data size: 100M samples → 50GB raw + 200GB processed
  • Number of features: 10K → 50K (after feature engineering)
  • Embedding matrix memory footprint: 10K entities × 128 dimensions × 4 bytes = 5MB (per entity)
  • Serving QPS math: 100K QPS → 50ms latency budget (5ms feature lookup + 25ms model inference + 20ms response)

Scale-up Strategy

Horizontal Scaling of Serving:

  • Auto-scaling groups for model instances (AWS ECS/Google Cloud Run)
  • Load balancing across regions with consistent hashing
  • Multi-zone deployment for fault tolerance

Distributed Feature Computation:

  • Feature store with distributed lookup (e.g., Redis clusters)
  • Batch feature computation pipeline with Spark or Dask
  • Feature caching with TTL to reduce database pressure

Fault Tolerance

Model Serving Fallbacks:

  • Primary model failure → fallback to rule-based classifier or previous model version
  • Graceful degradation: serve most recent valid model if new model fails to load
  • Health checks every 5 seconds with automatic failover

Stale Model Serving on Failure:

  • Model versioning with rollback capability (MLflow or custom registry)
  • Cache warming with model preloading
  • Shadow mode for model validation before full switch

Cold Start Handling Strategy

New Users:

  • Rule-based fallback using demographic or behavioral signals
  • Content-based recommendation from user profile features
  • Default model prediction with high uncertainty threshold

New Items:

  • Collaborative filtering fallback using item popularity
  • Content-based matching with item metadata features
  • Default ranking score from historical item performance

Multi-region Serving Strategy

Deployment Architecture:

  • Primary region (e.g., us-west-2) for low-latency serving
  • Secondary regions (us-east-1, eu-central-1) for redundancy and global coverage
  • Cross-region replication with eventual consistency model

Traffic Management:

  • Global load balancer with health checks
  • Region-specific feature stores with cross-region sync
  • Failover logic using latency-based routing

Latency Budget Allocation:

Stagep50 Latencyp99 LatencyBudget
Feature Lookup5ms15ms20ms
Model Inference10ms30ms40ms
Response Generation5ms15ms20ms
Total20ms60ms80ms

Capacity Planning:

  • 100K QPS → 500K model instances across regions (with auto-scaling)
  • Feature store capacity: 10GB memory per region with 2x redundancy
  • Model cache size: 500MB per instance for embedding and model weights

Reliability Metrics:

  • SLA: 99.9% availability (1.44 min downtime/month)
  • P99 latency: < 80ms for 100K QPS
  • Error rate: < 0.01% for all stages

Cross-region Consistency:

  • Feature sync with 5-second TTL for real-time features
  • Model version synchronization every 15 minutes
  • Eventual consistency with conflict resolution strategy for feature updates

11. Open Questions, Risks & Trade-offs

Decisions requiring product / engineering / policy alignment

  • User consent and data usage policies - Do we have explicit user consent for behavioral tracking and model training? How do we handle opt-out mechanisms?
  • Model interpretability requirements - Will stakeholders require model explainability for regulatory compliance or business transparency?
  • Latency vs accuracy trade-off tolerance - What is the maximum acceptable latency for real-time predictions in production?
  • Cold start handling strategy - Should we prioritize new user/item handling with rule-based fallbacks or invest in more sophisticated cold start models?

Key ML risks

RiskImpactLikelihoodMitigation
Data drift in user behavior patternsHigh - model performance degradationMedium - continuous monitoring can detect driftImplement feature drift detection, scheduled retraining
Label noise in implicit feedbackMedium - reduced model qualityHigh - noisy signals from user interactionsImplement label validation, negative sampling strategies
Model bias toward popular itemsMedium - reduced diversity and fairnessMedium - inherent popularity biasImplement fairness constraints, diversity-aware ranking
Production model underperformanceHigh - business impactMedium - gap between offline and online performanceImplement shadow testing, A/B test validation
Cold start failures for new users/itemsMedium - poor user experienceHigh - frequent in productionDesign robust fallback strategies and warm-up procedures

Trade-offs made with justification

  • Accuracy vs latency: Prioritized model efficiency over marginal accuracy gains by using a lighter DNN architecture with pre-trained embeddings rather than heavy transformer models for real-time serving
  • Freshness vs cost: Accepting 1-hour feature update frequency to reduce infrastructure costs and simplify the data pipeline, balancing with business requirements for timely signal updates
  • Model size vs quality: Chose model compression techniques (quantization, pruning) to enable serving on edge devices and reduce latency, accepting potential accuracy trade-offs for broader deployment
  • Online vs batch training: Prioritized batch training with scheduled retraining over continuous online learning to maintain model stability and reduce system complexity

Alternatives considered and rejected

  • Transformer-based architectures: Rejected due to high latency requirements and computational overhead for real-time serving, despite superior accuracy in some scenarios
  • Ensemble of complex models: Rejected for operational complexity and latency overhead, preferring simpler yet effective single model approaches
  • Real-time feature computation: Rejected due to high latency and infrastructure costs, preferring batch-computed features with online store caching
  • Full online learning: Rejected for stability concerns and complexity in maintaining model consistency across distributed systems

Technical debt acknowledged

  • Feature store implementation: Current design assumes stable feature schemas, requiring future extension for dynamic schema evolution and backward compatibility
  • Model versioning: Basic version tracking in MLflow, but lacks automated rollback and A/B testing infrastructure for production deployments
  • Shadow mode integration: Limited integration with existing monitoring systems, requiring future investment for comprehensive model validation pipeline
  • Cold start handling: Fallback logic is basic and may not generalize well to all user/item types, requiring ongoing refinement based on production feedback

12. Phased Implementation Roadmap

Phase 1: MVP (Weeks 1-4)

Objective: Establish baseline system with rule-based fallback and simple ML model

Deliverables:

  • Rule-based recommendation engine (cold start handling)
  • Basic feature extraction pipeline
  • Logistic regression model with 100K training samples
  • Simple online serving infrastructure (Flask/REST API)
  • A/B testing framework with 10% randomization

Dependencies:

  • Data pipeline availability (Week 1)
  • Feature store setup (Week 2)
  • Model training infrastructure (Week 3)

Phase 2: Scale (Weeks 5-8)

Objective: Expand to production-scale serving and feature engineering

Deliverables:

  • Feature store with online/offline stores (Redis + Parquet)
  • Distributed training pipeline (Ray/Dask)
  • Multi-stage serving pipeline (retrieval → scoring → re-ranking)
  • Real-time feature computation (Kafka streams)
  • Model versioning and registry (MLflow)

Dependencies:

  • Feature store availability (Week 5)
  • Distributed training infrastructure (Week 6)
  • A/B testing infrastructure (Week 7)

Phase 3: Excellence (Weeks 9-12)

Objective: Advanced model capabilities and continuous learning

Deliverables:

  • Transformer-based model (BERT/Transformer)
  • Online learning pipeline (concept drift detection)
  • Advanced evaluation framework (slice-based A/B testing)
  • Continuous model retraining with drift detection
  • Multi-region serving capability

Dependencies:

  • Model training infrastructure (Week 9)
  • Monitoring system (Week 10)
  • Production-scale feature store (Week 11)

Critical Path Dependencies:

  1. Data Pipeline - Must be available before model training
  2. Feature Store - Required for both training and serving
  3. Serving Infrastructure - Must be ready before A/B testing
  4. Model Training - Blocks model versioning and deployment

Risk Mitigation:

  • Data Quality: Implement data validation in Phase 1
  • Latency: Design two-stage pipeline from start
  • Scalability: Use distributed training from Phase 2
  • Monitoring: Build observability in Phase 1

Success Criteria:

  • Phase 1: 90% rule-based fallback coverage, 85% model accuracy
  • Phase 2: 10x QPS improvement, 95% feature freshness
  • Phase 3: 20% accuracy improvement, 50% reduction in retraining latency
# Example MVP model training pipeline (Phase 1)
import pandas as pd
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split

def train_mvp_model(data_path):
    # Load and prepare data
    df = pd.read_parquet(data_path)
    
    # Basic feature engineering for MVP
    X = df[['user_age', 'item_category', 'session_duration']]
    y = df['clicked']
    
    # Split and train
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)
    
    model = LogisticRegression()
    model.fit(X_train, y_train)
    
    # Evaluate baseline
    baseline_score = model.score(X_test, y_test)
    return model, baseline_score

# Example serving endpoint (Phase 1)
from flask import Flask, request, jsonify
import joblib

app = Flask(__name__)
model = joblib.load('mvp_model.pkl')

@app.route('/predict', methods=['POST'])
def predict():
    data = request.json
    features = [data['user_age'], data['item_category'], data['session_duration']]
    
    # Fallback to rule-based if model fails
    try:
        prediction = model.predict_proba([features])[0][1]
        return jsonify({'score': float(prediction)})
    except:
        # Simple rule-based fallback
        return jsonify({'score': 0.1 if data['session_duration'] > 30 else 0.05})

Was this article helpful?

Topics in this article

More from AI/ML System Design

  • AI/ML System Design

    Feature Backfill Strategy for ML Systems

    Comprehensive guide to designing robust feature backfill strategies for machine learning pipelines, covering incremental updates, data consistency, and system reliability patterns.

  • AI/ML System Design

    Real-time Feature Computation at Serving Time

    Comprehensive system design for real-time feature computation in machine learning serving systems with low latency and high throughput requirements.

  • AI/ML System Design

    Point-in-Time Correct Feature Retrieval System Design

    Comprehensive system design for ensuring feature data consistency and temporal correctness in machine learning pipelines.

Stay in the Loop

Get curated DSA patterns, coding problems, frontend and backend LLD/HLD, and AI/ML System Design insights delivered straight to your inbox. No spam, just focused interview and engineering content.