100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
TensorFlow & Keras
55 minintermediate

Core Skills Mastery Exercise

What You'll Build

In this exercise, you will construct a complete machine learning pipeline that predicts cricket player performance metrics using TensorFlow and Keras. The system ingests historical player statistics — including batting average, strike rate, wickets taken, and economy rate — then preprocesses the data through normalization and feature engineering before producing predictions.

The pipeline requires building a sequential neural network with multiple dense layers and dropout regularization, training it with appropriate loss functions and optimizers, and evaluating its performance using metrics relevant to cricket analytics. This exercise integrates data handling with pandas, numerical computation with NumPy, model building with the Keras functional and sequential APIs, callback mechanisms for training control, and validation techniques to prevent overfitting.

By the end of this exercise, you will have a production-ready model that demonstrates mastery of core Keras workflows, including data preparation, architecture design, training loops, and performance evaluation.

Analogy🏏Cricket
🏏 Think of it like cricket: Consider a team's coaching staff analyzing a batter's preparation for the upcoming Test series. The coaching team collects detailed statistics: Rohit Sharma's average against fast bowlers in different weather conditions, his boundary percentage in powerplay overs, and his performance trajectory across different venues over five years. The neural network you build is like the team's analytical system: the input layer represents the raw match conditions (opposing team strength, pitch type, weather), the hidden layers work like the coaching strategy—processing and combining information to identify patterns (just as coaches combine multiple data points to predict future form), and the output layer produces the final prediction of expected runs. Training the model with historical data is equivalent to studying past innings footage; the loss function measures how far predictions miss the actual scorecard, and the optimizer adjusts the strategy just as coaches refine tactics based on performance reviews. What this reveals is that both neural networks and cricket teams must learn patterns from history, adapt their approach based on feedback, and balance complex variables to make reliable predictions.

Prerequisites

  • Proficiency with NumPy arrays and Pandas DataFrames for data manipulation and transformation tasks
  • Understanding of neural network fundamentals including layers, neurons, activation functions, and forward propagation
  • Knowledge of supervised learning concepts like training sets, validation sets, loss functions, and performance metrics
  • Experience with Python 3.8+ and familiarity with virtual environments and package managers like pip
  • Conceptual awareness of overfitting, regularization techniques like dropout, and the purpose of callbacks in training

Setup & Project Structure

Begin by setting up a dedicated project directory with organized subdirectories for data, models, and scripts. Install TensorFlow 2.12 or later, which includes Keras as its high-level API, along with supporting libraries such as NumPy, Pandas, and Scikit-learn for data preprocessing. Creating a virtual environment is essential to isolate dependencies and maintain project reproducibility.

Analogy🏏Cricket
🏏 Think of it like cricket: setting up a clean project directory and virtual environment is like a team laying out its dressing room and equipment before a tour begins. Just as a well-run side keeps bats, pads, and match balls in separate labelled kit bags so nobody grabs the wrong gear mid-match, you create isolated folders and a virtual environment so your dependencies — TensorFlow, pandas, numpy, matplotlib, scikit-learn — never clash. Just as a touring team isolates its own supplies rather than borrowing from the host ground, a virtual environment isolates package versions so one project cannot corrupt another. Just as a disorganised dressing room costs precious minutes when a batter is padding up in a hurry, a messy project structure costs hours of debugging later. The payoff: a tidy, isolated setup means every practice session and every model run starts smoothly, the way a well-drilled squad walks onto the field with every piece of kit exactly where it should be.

Organize your project structure with separate modules for data loading and preprocessing, model definition and architecture, training orchestration, and evaluation routines. This modular approach enables code reuse, simplifies debugging, and establishes the professional development practices essential for production machine learning systems.

bash
#!/bin/bash

# Create project structure for cricket performance prediction
mkdir -p cricket_ml_pipeline/{data,models,scripts,notebooks}
cd cricket_ml_pipeline

# Create virtual environment
python3 -m venv cricket_env
source cricket_env/bin/activate

# Install dependencies
pip install --upgrade pip
pip install tensorflow==2.14.0
pip install numpy==1.24.3
pip install pandas==2.0.3
pip install scikit-learn==1.3.0
pip install matplotlib==3.7.2
pip install jupyter==1.0.0

# Create directory structure
touch scripts/data_loader.py
touch scripts/model_trainer.py
touch scripts/evaluator.py
touch data/cricket_stats.csv
touch models/player_performance_model.h5

echo "Cricket ML Pipeline project initialized successfully!"

Step 1 — Foundation

The foundation step establishes data loading, exploration, and preprocessing pipelines that are essential for all downstream machine learning tasks. You will create synthetic cricket player statistics representing variables such as innings played, runs scored, batting average, strike rate, and bowling economy.

Implement data normalization using StandardScaler to ensure all features operate on comparable scales. This is a critical requirement because neural networks learn through gradient descent, which performs poorly when features have vastly different numeric ranges.

Split your dataset into training and validation subsets using stratified sampling to maintain consistent statistical properties across both splits. This foundation work is the most consequential step in the pipeline, since rigorous data preparation directly determines model quality — garbage input invariably produces garbage output.

Analogy🏏Cricket
🏏 Think of it like cricket: Before any captain takes the field, they must understand their squad's composition—which batsmen performed well in the last series against specific bowling types, which bowlers took wickets in similar conditions, and which all-rounders provide flexibility. This mirrors Foundation Step's exploratory analysis where you examine player statistics across different match conditions and formats. Just as a selector checks that opening pairs have actually faced the opposition's bowling attack (data validation), you verify no leaked information from test sets into training sets. The train/validation/test split mirrors match preparation: training data is like practice matches where players learn conditions, validation data is the warm-up match before the main tournament, and test data is the actual championship match where you can only observe performance (never train on it). When Rohit Sharma's career statistics are processed—removing obvious errors (like negative strike rates), handling missing values from cancelled matches, detecting his outlier performance in the 2019 World Cup—you're preparing signal from noise. Understanding why this foundation matters reveals that no sophisticated ensemble technique can rescue a model built on garbage data: the entire model's trustworthiness rests on properly prepared inputs.
python
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import warnings
warnings.filterwarnings('ignore')

# Step 1: Data Loading and Exploration

class CricketPlayerDataLoader:
    """Load and preprocess cricket player performance statistics."""
    
    def __init__(self, random_state=42):
        self.random_state = random_state
        self.scaler = StandardScaler()
        self.feature_names = None
    
    def generate_cricket_data(self, num_players=200):
        """
        Generate synthetic cricket player dataset with performance metrics.
        Mirrors squad analysis: gathering player statistics across formats.
        """
        np.random.seed(self.random_state)
        
        # Cricket player metrics (like coaches analyzing player statistics)
        player_data = {
            'player_id': range(1, num_players + 1),
            'innings_count': np.random.randint(10, 150, num_players),
            'batting_average': np.random.uniform(20, 65, num_players),
            'strike_rate': np.random.uniform(80, 130, num_players),
            'centuries_scored': np.random.randint(0, 20, num_players),
            'fifties_scored': np.random.randint(0, 40, num_players),
            'bowling_average': np.random.uniform(20, 50, num_players),
            'wickets_taken': np.random.randint(0, 300, num_players),
            'economy_rate': np.random.uniform(6, 9, num_players),
            'fitness_score': np.random.uniform(60, 100, num_players),
            'age_years': np.random.randint(18, 42, num_players),
            'career_span_years': np.random.randint(0, 20, num_players)
        }
        
        cricket_df = pd.DataFrame(player_data)
        
        # Create target: Player Selection Status (Selected=1, Not Selected=0)
        # Elite players have high averages + centuries + low bowling averages
        selection_criteria = (
            (cricket_df['batting_average'] > 40) & 
            (cricket_df['centuries_scored'] > 2) |
            (cricket_df['wickets_taken'] > 50) & (cricket_df['bowling_average'] < 30)
        )
        cricket_df['selected_for_match'] = selection_criteria.astype(int)
        
        self.feature_names = [col for col in cricket_df.columns 
                             if col not in ['player_id', 'selected_for_match']]
        
        return cricket_df
    
    def explore_dataset(self, cricket_df):
        """Display dataset exploration - like analyzing squad statistics."""
        print("🏏 CRICKET SQUAD ANALYSIS - DATA EXPLORATION")
        print("=" * 60)
        print(f"\nDataset Shape: {cricket_df.shape}")
        print(f"Total Players: {cricket_df.shape[0]}")
        print(f"Performance Metrics: {len(self.feature_names)}\n")
        
        print("📊 Dataset Overview:")
        print(cricket_df.head(5))
        print("\n📈 Statistical Summary:")
        print(cricket_df.describe().round(2))
        print(f"\n✅ Selected for Match: {cricket_df['selected_for_match'].sum()} players")
        print(f"❌ Not Selected: {(1 - cricket_df['selected_for_match']).sum()} players")
        
        return cricket_df
    
    def normalize_player_features(self, cricket_df):
        """
        Feature normalization - Conditioning protocol ensuring fair comparison.
        Like bringing all players to same fitness baseline for objective evaluation.
        """
        X = cricket_df[self.feature_names].copy()
        y = cricket_df['selected_for_match'].copy()
        
        # Fit scaler on full dataset, then transform
        X_normalized = self.scaler.fit_transform(X)
        X_normalized_df = pd.DataFrame(X_normalized, columns=self.feature_names)
        
        print("\n🎯 FEATURE NORMALIZATION - CONDITIONING PROTOCOL")
        print("=" * 60)
        print("Before Normalization (Raw metrics):")
        print(X.describe().round(2))
        print("\nAfter Normalization (Scaled to fair comparison):")
        print(X_normalized_df.describe().round(2))
        
        return X_normalized_df, y
    
    def split_dataset(self, X_normalized, y):
        """
        Split data for training and evaluation.
        Like dividing squad: Training team vs Test team.
        """
        X_train, X_test, y_train, y_test = train_test_split(
            X_normalized, y, 
            test_size=0.2, 
            random_state=self.random_state,
            stratify=y
        )
        
        print("\n🎓 DATASET SPLIT - SQUAD DIVISION")
        print("=" * 60)
        print(f"Training Set: {X_train.shape[0]} players")
        print(f"Test Set: {X_test.shape[0]} players")
        print(f"Training Data Shape: {X_train.shape}")
        print(f"Test Data Shape: {X_test.shape}")
        
        return X_train, X_test, y_train, y_test


# Step 2: Build Neural Network Model

def build_player_selection_model(input_features):
    """
    Build neural network to predict player selection.
    Architecture mirrors decision-making complexity of selection committees.
    """
    model = keras.Sequential([
        # Layer 1: Initial feature processing (scouts initial assessment)
        layers.Dense(64, activation='relu', input_shape=(input_features,),
                    name='scout_assessment'),
        layers.Dropout(0.3, name='dropout_1'),
        
        # Layer 2: Complex pattern recognition (committee deliberation)
        layers.Dense(32, activation='relu', name='committee_deliberation'),
        layers.Dropout(0.2, name='dropout_2'),
        
        # Layer 3: Refined evaluation (coaching staff analysis)
        layers.Dense(16, activation='relu', name='coach_analysis'),
        
        # Output: Final selection decision
        layers.Dense(1, activation='sigmoid', name='selection_decision')
    ])
    
    return model


# Step 3: Complete Training Pipeline

def train_player_selection_model(X_train, X_test, y_train, y_test):
    """Comprehensive training pipeline with evaluation."""
    
    print("\n🏏 PLAYER SELECTION MODEL - TRAINING PIPELINE")
    print("=" * 60)
    
    # Build model
    model = build_player_selection_model(input_features=X_train.shape[1])
    
    # Compile with appropriate loss for binary classification
    model.compile(
        optimizer=keras.optimizers.Adam(learning_rate=0.001),
        loss='binary_crossentropy',
        metrics=['accuracy', keras.metrics.AUC(name='auc')]
    )
    
    print("\n📋 Model Architecture:")
    model.summary()
    
    # Train model
    print("\n🎯 Training in Progress...")
    history = model.fit(
        X_train, y_train,
        epochs=50,
        batch_size=16,
        validation_data=(X_test, y_test),
        verbose=0
    )
    
    # Evaluate on test set
    test_loss, test_accuracy, test_auc = model.evaluate(
        X_test, y_test, 
        verbose=0
    )
    
    print(f"\n✅ Training Complete!")
    print(f"Test Accuracy: {test_accuracy:.4f}")
    print(f"Test AUC: {test_auc:.4f}")
    print(f"Test Loss: {test_loss:.4f}")
    
    return model, history


# Step 4: Player-specific Predictions

def predict_player_selection(model, X_test, player_names):
    """Make predictions for individual players."""
    
    print("\n🏏 MATCH DAY SQUAD PREDICTIONS")
    print("=" * 60)
    
    predictions = model.predict(X_test, verbose=0)
    
    selected_players = []
    for idx in range(min(10, len(player_names))):
        selection_prob = predictions[idx][0]
        status = "✅ SELECTED" if selection_prob > 0.5 else "❌ NOT SELECTED"
        confidence = selection_prob if selection_prob > 0.5 else 1 - selection_prob
        
        print(f"{player_names[idx]:20} | Confidence: {confidence:.2%} | {status}")
        
        if selection_prob > 0.5:
            selected_players.append(player_names[idx])
    
    print(f"\n🎖️ Final Squad Size: {len(selected_players)} players")
    
    return selected_players


# ========== EXECUTION ==========

if __name__ == "__main__":
    # Initialize data loader
    loader = CricketPlayerDataLoader(random_state=42)
    
    # Generate cricket player dataset
    cricket_df = loader.generate_cricket_data(num_players=200)
    cricket_df = loader.explore_dataset(cricket_df)
    
    # Normalize features (conditioning protocol)
    X_normalized, y = loader.normalize_player_features(cricket_df)
    
    # Split dataset
    X_train, X_test, y_train, y_test = loader.split_dataset(X_normalized, y)
    
    # Train model
    model, history = train_player_selection_model(X_train, X_test, y_train, y_test)
    
    # Generate player names for predictions
    famous_cricketers = [
        'Rohit Sharma', 'Virat Kohli', 'Jasprit Bumrah', 'Hardik Pandya',
        'KL Rahul', 'Ravindra Jadeja', 'Mohammed Shami', 'Rishabh Pant',
        'Suryakumar Yadav', 'Axar Patel'
    ]
    
    # Make predictions
    selected_squad = predict_player_selection(model, X_test[:10], famous_cricketers)
    
    print("\n" + "=" * 60)
    print("🏆 Foundation work complete! Model ready for match day selection.")
    print("=" * 60)

Step 2 — Core Logic

The core logic step constructs the neural network architecture that learns patterns from cricket statistics in order to predict player performance ratings. You will implement a sequential model with multiple dense layers interspersed with dropout regularization to prevent overfitting.

Dense layers perform a linear transformation followed by an activation function — ReLU for hidden layers and a linear activation for the regression output — enabling the network to learn complex non-linear relationships between input features. Dropout layers complement this by randomly deactivating neurons during training, forcing the network to develop robust feature representations that do not depend on any single neuron.

Compile the model with an appropriate loss function (mean squared error for regression), an optimizer (Adam for its adaptive learning rate), and relevant evaluation metrics. This architecture forms the core learning mechanism through which the network discovers how batting statistics, bowling metrics, and fielding contributions collectively influence overall player performance ratings.

Analogy🏏Cricket
🏏 Think of it like cricket: Building your neural network architecture is exactly like Rahul Dravid selecting the ideal batting order for different match situations—you're not using the same eleven for T20 powerplay that you'd use for Test match day-4 grind. Your Keras functional model is the batting order itself: opening batsmen (input layers) handle initial aggressive scoring, middle-order batsmen (hidden Dense layers) stabilize and build partnerships, and tail-enders (final layers) specialize in specific situations (all-rounders contribute to both batting and bowling). Just as Hardik Pandya might bowl fewer overs and focus on death-overs hittability when the team has strong bowlers elsewhere, your Dropout layers randomly 'rest' neurons during training to prevent any single pathway from becoming too dominant—this prevents 'overreliance' on one player's form. Your custom loss function mirrors how a coach weighs different performance dimensions: is it more critical to identify elite players (precision in the "1" class) or to avoid incorrectly ranking average performers as below-average (recall)? The learning rate schedule acts like match-day field placements: aggressive initial setup (high learning rate) early when the model needs foundational learning, then gradually defensive positioning (reduced learning rate) later to fine-tune and hold established advantages. Understanding this architectural thinking reveals that model structure isn't mathematical abstraction but strategic composition—each component serves deliberate performance objectives.
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, models
from tensorflow.keras.optimizers import Adam
import numpy as np

# Step 2: Neural Network Architecture Definition

class CricketPerformanceModel:
    """Build and compile neural network for cricket player performance prediction."""
    
    def __init__(self, input_dim, learning_rate=0.001):
        self.input_dim = input_dim
        self.learning_rate = learning_rate
        self.model = None
    
    def build_sequential_model(self):
        """
        Construct sequential model with dropout regularization.
        Architecture:
        - Input: 7 features (cricket statistics)
        - Hidden layer 1: 64 neurons, ReLU activation
        - Dropout: 0.2 (drops 20% of neurons)
        - Hidden layer 2: 32 neurons, ReLU activation
        - Dropout: 0.2
        - Hidden layer 3: 16 neurons, ReLU activation
        - Output layer: 1 neuron, linear activation (regression)
        """
        self.model = models.Sequential([
            # Input layer with 64 neurons
            layers.Dense(64, activation='relu', input_dim=self.input_dim,
                        name='input_layer'),
            
            # Regularization: randomly drop 20% of neurons
            layers.Dropout(0.2, name='dropout_1'),
            
            # Hidden layer with 32 neurons
            layers.Dense(32, activation='relu', name='hidden_layer_1'),
            layers.Dropout(0.2, name='dropout_2'),
            
            # Hidden layer with 16 neurons
            layers.Dense(16, activation='relu', name='hidden_layer_2'),
            layers.Dropout(0.15, name='dropout_3'),
            
            # Output layer for regression (predict continuous match impact rating)
            layers.Dense(1, activation='linear', name='output_layer')
        ])
        
        return self.model
    
    def compile_model(self):
        """
        Compile model with appropriate loss, optimizer, and metrics.
        """
        self.model.compile(
            loss='mse',  # Mean Squared Error for regression
            optimizer=Adam(learning_rate=self.learning_rate),  # Adaptive optimizer
            metrics=['mae']  # Mean Absolute Error for interpretability
        )
        
        return self.model
    
    def print_model_summary(self):
        """Display model architecture and parameter counts."""
        self.model.summary()
    
    def get_model(self):
        """Return compiled model."""
        return self.model


# Execute Step 2
if __name__ == "__main__":
    # Initialize model builder
    model_builder = CricketPerformanceModel(input_dim=7, learning_rate=0.001)
    
    # Build architecture
    model = model_builder.build_sequential_model()
    
    # Compile model
    model = model_builder.compile_model()
    
    # Display architecture
    print("\n" + "="*60)
    print("CRICKET PLAYER PERFORMANCE MODEL ARCHITECTURE")
    print("="*60)
    model_builder.print_model_summary()
    
    print("\nModel Configuration:")
    print(f"  Input dimensions: 7 (cricket statistics)")
    print(f"  Hidden layers: 3 (64 → 32 → 16 neurons)")
    print(f"  Dropout regularization: Applied between layers")
    print(f"  Output: Single continuous value (match impact rating)")
    print(f"  Loss function: Mean Squared Error (MSE)")
    print(f"  Optimizer: Adam (learning_rate={model_builder.learning_rate})")
    print(f"  Total trainable parameters: {model.count_params()}")

Step 3 — Integration & Enhancement

The integration step combines the data pipeline from Step 1 and the model architecture from Step 2 into a complete training system with advanced features. Implement custom callbacks to monitor training progress: EarlyStopping to halt training when validation loss plateaus, ModelCheckpoint to preserve the best model weights, and ReduceLROnPlateau to dynamically decrease the learning rate when improvement stalls.

Create a comprehensive training loop that feeds prepared data through the model, tracks metrics across epochs, and manages the optimization process. Additionally, implement custom metrics that provide cricket-specific insights, such as prediction accuracy within acceptable performance ranges.

This integration step reflects real-world machine learning practice, where disparate components must work cohesively. Robust error handling, logging, and checkpoint mechanisms are essential to ensure reproducible results and to protect against failures during long training runs.

Analogy🏏Cricket
🏏 Think of it like cricket: After a cricket team's internal training (Step 2), they must validate their preparation through actual match performance (Step 3). During the IPL season, a team doesn't just practice in nets; they play matches, accumulate statistics, analyze scorecard data, and assess whether their strategies actually work against real opposition—this is the integration phase. When Mumbai Indians review their batting performance across the entire tournament, they calculate batting averages, strike rates, and average runs per innings for each player—parallel to calculating MAE and RMSE to understand prediction error patterns. The model persistence (saving weights) mirrors how a team documents successful strategies for future seasons: if Rohit Sharma discovers an effective technique against left-arm spinners during the tournament, the team archives this knowledge (strategy documentation) and reapplies it in future matches without rediscovering it from scratch. Visualization of predictions versus actuals functions like match replays—watching the difference between planned field placements and actual batsman positioning reveals whether the strategy was sound but poorly executed, or fundamentally flawed. The batch processing capability mirrors how franchises use historical match data to profile opposing teams in real-time rather than analyzing each match in isolation. By integrating these elements, you transform a trained neural network into a cricket analytics tool that stakeholders can trust and deploy for actual player selection and strategy decisions.
python
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau
from tensorflow.keras.optimizers import Adam
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# Step 3: Integration & Training with Callbacks

class CricketModelTrainer:
    """Complete training pipeline with callbacks and monitoring."""
    
    def __init__(self, model, X_train, X_val, y_train, y_val):
        self.model = model
        self.X_train = X_train
        self.X_val = X_val
        self.y_train = y_train
        self.y_val = y_val
        self.history = None
        self.best_val_loss = float('inf')
    
    def define_callbacks(self, checkpoint_path='models/best_model.h5'):
        """
        Create callbacks for enhanced training control.
        """
        callbacks = [
            # Stop training if validation loss doesn't improve for 20 epochs
            EarlyStopping(
                monitor='val_loss',
                patience=20,
                restore_best_weights=True,
                verbose=1,
                mode='min'
            ),
            
            # Save model weights when validation loss improves
            ModelCheckpoint(
                filepath=checkpoint_path,
                monitor='val_loss',
                save_best_only=True,
                verbose=0,
                mode='min'
            ),
            
            # Reduce learning rate when validation loss plateaus
            ReduceLROnPlateau(
                monitor='val_loss',
                factor=0.5,  # Multiply learning rate by 0.5
                patience=10,
                min_lr=0.00001,
                verbose=1,
                mode='min'
            ),
            
            # Custom callback for cricket-specific logging
            CricketTrainingCallback()
        ]
        
        return callbacks
    
    def train(self, epochs=100, batch_size=16, callbacks=None):
        """
        Execute training loop with validation and callbacks.
        """
        if callbacks is None:
            callbacks = self.define_callbacks()
        
        print("\n" + "="*70)
        print("INITIATING CRICKET PLAYER PERFORMANCE MODEL TRAINING")
        print("="*70)
        print(f"Training samples: {len(self.X_train)}")
        print(f"Validation samples: {len(self.X_val)}")
        print(f"Batch size: {batch_size}")
        print(f"Maximum epochs: {epochs}")
        print("="*70 + "\n")
        
        self.history = self.model.fit(
            self.X_train, self.y_train,
            validation_data=(self.X_val, self.y_val),
            epochs=epochs,
            batch_size=batch_size,
            callbacks=callbacks,
            verbose=1
        )
        
        return self.history
    
    def evaluate_on_validation(self):
        """
        Evaluate trained model on validation set.
        """
        val_loss, val_mae = self.model.evaluate(
            self.X_val, self.y_val, verbose=0
        )
        
        print("\n" + "="*70)
        print("VALIDATION PERFORMANCE")
        print("="*70)
        print(f"Validation MSE Loss: {val_loss:.6f}")
        print(f"Validation MAE: {val_mae:.6f}")
        print("="*70)
        
        return val_loss, val_mae
    
    def plot_training_history(self):
        """
        Visualize training and validation metrics over epochs.
        """
        if self.history is None:
            print("No training history available. Train model first.")
            return
        
        fig, axes = plt.subplots(1, 2, figsize=(14, 5))
        
        # Plot loss
        axes[0].plot(self.history.history['loss'], label='Training Loss', linewidth=2)
        axes[0].plot(self.history.history['val_loss'], label='Validation Loss', linewidth=2)
        axes[0].set_title('Model Loss Over Epochs', fontsize=12, fontweight='bold')
        axes[0].set_xlabel('Epoch')
        axes[0].set_ylabel('MSE Loss')
        axes[0].legend()
        axes[0].grid(True, alpha=0.3)
        
        # Plot MAE
        axes[1].plot(self.history.history['mae'], label='Training MAE', linewidth=2)
        axes[1].plot(self.history.history['val_mae'], label='Validation MAE', linewidth=2)
        axes[1].set_title('Mean Absolute Error Over Epochs', fontsize=12, fontweight='bold')
        axes[1].set_xlabel('Epoch')
        axes[1].set_ylabel('MAE')
        axes[1].legend()
        axes[1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.savefig('models/training_history.png', dpi=100, bbox_inches='tight')
        print("Training history plot saved to 'models/training_history.png'")
        plt.show()


class CricketTrainingCallback(keras.callbacks.Callback):
    """Custom callback providing cricket-themed training feedback."""
    
    def on_epoch_end(self, epoch, logs=None):
        if epoch % 10 == 0 and epoch > 0:
            val_loss = logs.get('val_loss', 0)
            train_loss = logs.get('loss', 0)
            
            print(f"\n🏏 Innings Update (Epoch {epoch}):")
            print(f"   Batting performance (training): {train_loss:.6f}")
            print(f"   Bowling analysis (validation): {val_loss:.6f}")
            print(f"   Run rate trajectory: {'Improving' if train_loss < logs.get('loss', float('inf')) else 'Stable'}")


# Execute Step 3
if __name__ == "__main__":
    from Step2 import CricketPlayerDataLoader, CricketPerformanceModel
    
    # Load and prepare data (from Step 1)
    loader = CricketPlayerDataLoader(random_state=42)
    cricket_df = loader.generate_cricket_dataset(num_players=200)
    X_train, X_val, y_train, y_val, _ = loader.preprocess_data(cricket_df)
    
    # Build and compile model (from Step 2)
    model_builder = CricketPerformanceModel(input_dim=7, learning_rate=0.001)
    model = model_builder.build_sequential_model()
    model = model_builder.compile_model()
    
    # Initialize trainer and configure callbacks
    trainer = CricketModelTrainer(model, X_train, X_val, y_train, y_val)
    callbacks = trainer.define_callbacks(checkpoint_path='models/best_model.h5')
    
    # Execute training
    history = trainer.train(epochs=100, batch_size=16, callbacks=callbacks)
    
    # Evaluate performance
    trainer.evaluate_on_validation()
    
    # Visualize training progression
    trainer.plot_training_history()
    
    print("\n✓ Step 3 Complete: Model trained with integration and enhancement!")

Step 4 — Testing & Verification

Execute comprehensive testing and verification to validate both model correctness and overall pipeline performance. Run the complete pipeline using bash scripts that load data, train the model, and evaluate predictions, then generate predictions on validation data and analyze error distributions to identify any systematic biases.

Analogy🏏Cricket
🏏 Think of it like cricket: testing and verifying your pipeline before deployment is like a full dress-rehearsal match and fitness certification before a player is cleared for the real series. Just as the medical team deliberately stress-tests a returning player against extreme scenarios — sudden sprints, awkward landings — to be sure he will not break down, you write test cases covering edge cases like extreme feature values and missing data. Just as a physio checks that every vital sign falls within a safe range before signing the clearance, you verify that all predictions fall within reasonable ranges for the cricket performance metrics and document expected loss and accuracy values. Just as a team that skips the fitness check risks a breakdown in the middle of a Test, deploying an unverified pipeline risks silent failures in production. The payoff: passing comprehensive verification gives you confidence the system generalises beyond training, the way a cleared, match-fit player can be trusted to last the whole series.

Create test cases that cover edge cases such as extreme feature values and missing data scenarios, and verify that all predictions fall within reasonable ranges for cricket performance metrics. Document expected outputs, including loss values, prediction accuracy metrics, and visual confirmation through training curves.

This verification step ensures the entire system functions correctly before deployment and provides confidence that the model generalizes beyond the training data.

Lesson 25 of 35
0% complete