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.
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.
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.
#!/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.
# 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.
# 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.