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

Foundation Concepts Review and Practice

What You'll Build

In this comprehensive hands-on exercise, you will build a cricket performance prediction system using TensorFlow and Keras. The project integrates data preprocessing, neural network architecture design, model training with callbacks, and evaluation metrics to predict batting averages and bowling economy rates based on historical match statistics.

You will work with structured cricket datasets containing features such as runs scored, wickets taken, overs bowled, and match conditions. The system demonstrates core TensorFlow concepts including tensor operations, layer construction, loss function selection, optimizer configuration, and model evaluation.

This real-world scenario requires you to handle data normalization, train sequential models, implement validation strategies, and interpret performance metrics. These are foundational skills for any machine learning practitioner working with Keras on top of TensorFlow.

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

  • Python 3.8+ with proficiency in numpy arrays, pandas DataFrames, and basic statistical concepts like mean, standard deviation, and normalization
  • Familiarity with neural network fundamentals: forward propagation, backpropagation, activation functions (ReLU, sigmoid), and the purpose of hidden layers in learning representations
  • Understanding of TensorFlow 2.x API basics: tf.keras.Sequential models, Dense layers, loss functions (mean squared error, categorical crossentropy), and optimizer mechanics
  • Experience with train-test-validation split methodology, cross-validation principles, and interpretation of performance metrics like accuracy, precision, recall, and mean absolute error
  • Knowledge of Python development tools: pip package management, Jupyter notebooks or Python scripts, and basic file I/O operations for loading and saving models

Setup & Project Structure

Begin by creating a dedicated project directory and virtual environment to isolate dependencies. Install TensorFlow 2.x (which includes Keras), pandas for data manipulation, numpy for numerical operations, matplotlib and seaborn for visualization, and scikit-learn for preprocessing utilities.

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 with a clear folder structure: a data directory for CSV files containing cricket match statistics, a models directory for saved trained models, a notebooks directory for exploratory analysis, and a src directory for reusable Python modules. This structure follows professional machine learning practices and ensures reproducibility.

Initialize a requirements.txt file to document all dependencies, enabling anyone to recreate your environment exactly. Additionally, use a fixed random seed across TensorFlow, numpy, and Python's random module to ensure consistent, reproducible results across runs.

bash
#!/bin/bash
# Cricket Performance Prediction Project Setup

# Create project directory structure
mkdir -p cricket_performance_predictor
cd cricket_performance_predictor

# Create subdirectories
mkdir -p data
mkdir -p models
mkdir -p notebooks
mkdir -p src
mkdir -p output

# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Create requirements.txt with all dependencies
cat > requirements.txt << 'EOF'
tensorflow>=2.12.0
keras>=2.12.0
numpy>=1.24.0
pandas>=2.0.0
scikit-learn>=1.2.0
matplotlib>=3.7.0
seaborn>=0.12.0
ipython>=8.10.0
jupyter>=1.0.0
EOF

# Install dependencies
pip install -r requirements.txt

# Create initial data file for cricket statistics
cat > data/cricket_matches.csv << 'EOF'
player_name,batting_avg,bowling_economy,runs_last_10,wickets_last_5,powerplay_avg,venue_matches,match_type
Rohit Sharma,48.5,0,285,0,45.2,125,ODI
Virat Kohli,51.2,0,302,0,42.8,180,ODI
Jasprit Bumrah,0,5.8,0,18,0,95,ODI
Mohammad Shami,0,6.2,0,22,0,87,ODI
Suryakumar Yadav,44.3,0,198,0,48.5,45,ODI
Kuldeep Yadav,0,6.9,0,25,0,52,ODI
EOF

echo "✓ Project structure created successfully"
echo "✓ Virtual environment initialized"
echo "✓ Dependencies ready to install"
echo "✓ Sample cricket data prepared"

Step 1 — Foundation: Data Loading and Preprocessing

The foundation step establishes your data pipeline by loading cricket match statistics from CSV files and transforming raw numerical values into a format suitable for neural network training. This involves reading CSV data with pandas, performing exploratory data analysis to understand feature distributions and data types, and handling missing values appropriately.

Normalization is a critical part of this process, because neural networks learn more efficiently when input features are centered around zero with similar scales. For example, a batting average of 50 and an economy rate of 6 exist on completely different scales, and leaving them unnormalized causes the model to struggle during gradient descent optimization. You will apply either standardization or min-max scaling to address this.

You will also create train-test splits that preserve temporal integrity in cricket data, thereby avoiding data leakage. Establishing baseline statistics at this stage helps you measure model improvement over time. Ultimately, this step is foundational because poor-quality input directly undermines downstream model performance — investing time in data quality is never wasted.

Analogy🏏Cricket
🏏 Think of it like cricket: Before a Test match, the team conducts thorough preparation—much like data preprocessing. The team reviews weather reports, pitch reports from previous games, analyzes opposing batsmen's recent form, and updates their player fitness records. Just as scouts normalize player statistics (converting strike rates, averages, and bowling figures into comparable metrics for decision-making), you normalize features so neural networks can process them equally. The train-test split mirrors how teams prepare: using past seasons' matches (training data) to develop strategies and reserving recent matches (test data) to verify those strategies work against new opponents. Missing values in your cricket dataset (perhaps a player didn't bowl in a match) are like incomplete match records that must be handled before analysis. Feature exploration—understanding which statistics vary widely and which are stable—parallels the coaching staff studying whether a player's form is consistent or highly variable. This preparation phase determines whether your team enters the match with proper intelligence or walks in unprepared; similarly, proper preprocessing determines whether your model will learn meaningful patterns or overfit to noise.
python
# Step 1: Data Loading and Preprocessing
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler, MinMaxScaler
from sklearn.model_selection import train_test_split
import tensorflow as tf

# Set random seeds for reproducibility
np.random.seed(42)
tf.random.set_seed(42)

class CricketDataPipeline:
    """Data loading and preprocessing for cricket performance prediction."""
    
    def __init__(self, data_path):
        """Initialize with path to cricket statistics CSV."""
        self.data_path = data_path
        self.scaler_features = StandardScaler()
        self.scaler_target = StandardScaler()
        self.feature_names = []
        
    def load_and_explore(self):
        """Load cricket data and perform exploratory analysis."""
        # Load CSV with cricket match statistics
        df = pd.read_csv(self.data_path)
        
        print("=" * 60)
        print("CRICKET DATASET EXPLORATION")
        print("=" * 60)
        print(f"Dataset shape: {df.shape}")
        print(f"\nColumn names and types:\n{df.dtypes}")
        print(f"\nMissing values:\n{df.isnull().sum()}")
        print(f"\nBasic statistics:\n{df.describe()}")
        
        # Check for data quality
        missing_pct = (df.isnull().sum() / len(df)) * 100
        print(f"\nMissing percentage: {missing_pct.max():.2f}%")
        
        return df
    
    def preprocess(self, df, target_column='batting_avg', test_size=0.2):
        """Preprocess data: handle missing values, normalize, split."""
        
        # Handle missing values - forward fill for time series, then drop
        df_clean = df.fillna(df.mean())
        
        # Separate features and target
        X = df_clean.drop(columns=[target_column, 'player_name', 'match_type'])
        y = df_clean[target_column].values.reshape(-1, 1)
        
        self.feature_names = X.columns.tolist()
        
        print("\n" + "=" * 60)
        print("PREPROCESSING PIPELINE")
        print("=" * 60)
        print(f"Features selected: {self.feature_names}")
        print(f"Features shape before scaling: {X.shape}")
        print(f"Target shape: {y.shape}")
        
        # Normalize features to zero mean and unit variance
        X_scaled = self.scaler_features.fit_transform(X)
        y_scaled = self.scaler_target.fit_transform(y)
        
        print(f"\nFeature statistics AFTER normalization:")
        print(f"Mean per feature: {X_scaled.mean(axis=0).round(4)}")
        print(f"Std per feature: {X_scaled.std(axis=0).round(4)}")
        print(f"Target mean: {y_scaled.mean():.4f}")
        print(f"Target std: {y_scaled.std():.4f}")
        
        # Train-test split (80-20)
        X_train, X_test, y_train, y_test = train_test_split(
            X_scaled, y_scaled, test_size=test_size, random_state=42
        )
        
        print(f"\nTrain set size: {X_train.shape[0]} samples")
        print(f"Test set size: {X_test.shape[0]} samples")
        print(f"Number of features: {X_train.shape[1]}")
        
        return X_train, X_test, y_train, y_test, X_scaled, y_scaled

# Execute Step 1
if __name__ == "__main__":
    pipeline = CricketDataPipeline('data/cricket_matches.csv')
    
    # Load and explore
    df = pipeline.load_and_explore()
    
    # Preprocess and split
    X_train, X_test, y_train, y_test, X_full, y_full = pipeline.preprocess(df)
    
    print("\n" + "=" * 60)
    print("✓ STEP 1 COMPLETE: Data ready for model training")
    print("=" * 60)

Step 2 — Core Logic: Neural Network Architecture and Training

Step 2 focuses on building the neural network architecture that will learn patterns from cricket data. You will construct a Sequential model in Keras with input layers matching your feature count, hidden layers with appropriate neuron counts, and activation functions suited to the task — specifically ReLU for hidden layers to introduce non-linearity, and a linear activation for the regression output layer.

The model is compiled with an optimizer, a loss function, and performance metrics. Adam is the preferred optimizer due to its adaptive learning rate, while mean squared error serves as the loss function for continuous predictions such as batting averages. The training process feeds batches of normalized cricket data through the network across multiple epochs, using backpropagation to calculate gradients and updating weights via the optimizer to minimize loss.

Validation splits are implemented to monitor for overfitting, since a model that memorizes training data will perform poorly on unseen cricket statistics. The EarlyStopping callback prevents unnecessary computation by halting training when validation loss plateaus, ensuring the model retains its best weights. Understanding the reasoning behind architectural choices — such as why three hidden layers may outperform five, or why ReLU works better than sigmoid in this context — directly impacts prediction accuracy.

Analogy🏏Cricket
🏏 Think of it like cricket: Building and training a neural network parallels how a cricket team develops its batting strategy through repeated practice. Just as a batter practices against different bowling styles (fast bowlers, spinners, short-pitched deliveries) to learn patterns, the neural network practices on multiple batches of historical cricket data. The hidden layers represent the batter's skill development—each layer learns increasingly complex batting techniques (recognizing ball trajectory in the first layer, combining trajectory with field placement in the second layer, integrating all information to make scoring decisions in deeper layers). The activation functions (ReLU) are like the batter's decision-making process: they introduce non-linear responses (sometimes you defend, sometimes you attack the same delivery differently based on context), preventing the strategy from being too simple and linear. The optimizer (Adam) is the batting coach continuously adjusting your stance and technique based on immediate feedback: after each delivery, the coach identifies what worked and what didn't. The loss function (mean squared error) measures how far your predicted runs deviate from actual runs scored—just as match statistics show whether a strategy succeeded or failed. Early stopping prevents over-practice: a batter who practices the same shot against the same bowler 10,000 times develops brittle technique that fails against new bowlers, just as a neural network that trains too long overfits to training data and fails on new matches. What this reveals is that both batters and neural networks must balance learning from past experience with avoiding rigid memorization.
python
# Step 2: Neural Network Architecture and Training
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, callbacks
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np

class CricketPerformancePredictor:
    """Neural network for predicting cricket player performance."""
    
    def __init__(self, input_features):
        """Initialize model architecture."""
        self.input_features = input_features
        self.model = None
        self.history = None
        
    def build_model(self):
        """Construct sequential neural network architecture."""
        # Define model architecture
        self.model = keras.Sequential([
            # Input layer implicitly defined by input_dim
            layers.Dense(
                units=64,
                activation='relu',
                input_dim=self.input_features,
                name='hidden_layer_1'
            ),
            layers.Dropout(0.2),  # Regularization: drop 20% of neurons
            
            layers.Dense(
                units=32,
                activation='relu',
                name='hidden_layer_2'
            ),
            layers.Dropout(0.2),
            
            layers.Dense(
                units=16,
                activation='relu',
                name='hidden_layer_3'
            ),
            
            # Output layer for regression (linear activation)
            layers.Dense(
                units=1,
                activation='linear',
                name='output_layer'
            )
        ])
        
        print("=" * 60)
        print("NEURAL NETWORK ARCHITECTURE")
        print("=" * 60)
        self.model.summary()
        
    def compile_model(self):
        """Compile model with optimizer, loss, and metrics."""
        self.model.compile(
            optimizer=keras.optimizers.Adam(learning_rate=0.001),
            loss='mean_squared_error',
            metrics=['mae', 'mse']  # Mean absolute error, mean squared error
        )
        print("\n✓ Model compiled successfully")
        print(f"Optimizer: Adam (lr=0.001)")
        print(f"Loss: Mean Squared Error (MSE)")
        print(f"Metrics: MAE, MSE")
        
    def train(self, X_train, y_train, X_val, y_val, epochs=100, batch_size=4):
        """Train the model with validation and early stopping."""
        
        # Define callbacks for training
        early_stop = callbacks.EarlyStopping(
            monitor='val_loss',
            patience=15,  # Stop if val_loss doesn't improve for 15 epochs
            restore_best_weights=True,
            verbose=1
        )
        
        print("\n" + "=" * 60)
        print("TRAINING NEURAL NETWORK")
        print("=" * 60)
        print(f"Training samples: {X_train.shape[0]}")
        print(f"Validation samples: {X_val.shape[0]}")
        print(f"Batch size: {batch_size}")
        print(f"Max epochs: {epochs}")
        
        # Train the model
        self.history = self.model.fit(
            X_train, y_train,
            validation_data=(X_val, y_val),
            epochs=epochs,
            batch_size=batch_size,
            callbacks=[early_stop],
            verbose=0
        )
        
        print(f"\n✓ Training complete!")
        print(f"Total epochs trained: {len(self.history.history['loss'])}")
        print(f"Final training loss: {self.history.history['loss'][-1]:.4f}")
        print(f"Final validation loss: {self.history.history['val_loss'][-1]:.4f}")
        
    def plot_training_history(self):
        """Visualize training and validation performance."""
        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_xlabel('Epoch')
        axes[0].set_ylabel('Loss (MSE)')
        axes[0].set_title('Cricket Model Loss Over Epochs')
        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_xlabel('Epoch')
        axes[1].set_ylabel('Mean Absolute Error')
        axes[1].set_title('Cricket Model MAE Over Epochs')
        axes[1].legend()
        axes[1].grid(True, alpha=0.3)
        
        plt.tight_layout()
        plt.savefig('output/training_history.png', dpi=150, bbox_inches='tight')
        print("\n✓ Training history plot saved to output/training_history.png")
        plt.show()

# Execute Step 2
if __name__ == "__main__":
    # Assume X_train, y_train, X_test, y_test from Step 1
    # Create validation set from training data
    from sklearn.model_selection import train_test_split
    
    X_train, X_temp, y_train, y_temp = train_test_split(
        X_train, y_train, test_size=0.2, random_state=42
    )
    
    # Build and train model
    predictor = CricketPerformancePredictor(input_features=X_train.shape[1])
    predictor.build_model()
    predictor.compile_model()
    predictor.train(X_train, y_train, X_temp, y_temp, epochs=100, batch_size=4)
    predictor.plot_training_history()
    
    print("\n" + "=" * 60)
    print("✓ STEP 2 COMPLETE: Model trained and validated")
    print("=" * 60)

Step 3 — Integration & Enhancement: Model Evaluation and Prediction

Step 3 integrates your trained model with comprehensive evaluation mechanisms and implements prediction functionality on unseen cricket data. You will evaluate the model on the held-out test set using multiple metrics: mean absolute error (MAE), which expresses average prediction error in original units; mean squared error (MSE), which penalizes larger errors more heavily; and R² score, which indicates the proportion of variance explained by your model.

Beyond aggregate metrics, you will analyze per-player prediction errors to identify which cricket profiles the model predicts well and which require further refinement. Inverse-scaling of predictions is also necessary to return values in original units — for example, actual batting averages rather than normalized values — ensuring that outputs are interpretable to end users.

To further validate the model, you will create residual analyses to estimate prediction uncertainty and build visualizations comparing predicted versus actual values to identify any systematic biases. The trained model is then saved in TensorFlow's SavedModel format for production deployment.

This integration step transforms the trained model from an abstract mathematical construct into a practical, actionable system for cricket performance prediction. It also surfaces model limitations — for instance, a tendency to underestimate the performance of inexperienced players — which directly informs future iterations and improvements.

Analogy🏏Cricket
🏏 Think of it like cricket: After developing your team's playing strategy through practice and testing it against weak teams in warm-up matches (training phase), you now evaluate performance against genuine Test opponents (test set evaluation). The comprehensive metrics are like different lenses a team analyst uses: MAE is like average runs by which predictions miss the target, R² is like asking 'what percentage of match outcomes could our strategy explain versus random guessing?', and examining prediction errors reveals patterns (our strategy fails against left-arm spinners, excels in powerplay, struggles on turning pitches). Just as a team reviews match footage post-game to identify specific scenarios where the strategy succeeded or failed, you analyze residuals to find patterns in prediction errors—perhaps your model consistently overestimates young players' performance because training data came from experienced cricketers. Inverse-scaling predictions is like converting team analysis from abstract statistics back to practical match situations: instead of communicating in normalized values, you say 'we predict this batter will average 45 runs next series,' which selectors can act upon. Saving the model is like documenting your strategy in a playbook for future teams to use and improve upon. What this reveals is that a trained model is just the beginning; real value comes from understanding its strengths, limitations, and applicability to specific cricket scenarios.
Lesson 10 of 35
0% complete