Appendix
Technical Details
Below are the technical details of our implementation from:
- Training
- Testing
- Evaluation
Model Training
This chapter details our systematic approach to model training. Our training framework serves three key objectives:
- Model Development: Train multiple model architectures with different configurations
- Hyperparameter Optimization: Find optimal parameters through systematic grid search
- Performance Tracking: Monitor and log training metrics for model selection
The core training code is shared between two scripts: src/python/test_train/duration/TRAIN_duration.py and src/python/test_train/occupancy/TRAIN_occupancy.py. Like our testing scripts, these share approximately 95% of their code, differing primarily in:
- Target variable selection (
Duration_In_MinvsOccupancy) - Model definitions import (
algorithms_duration.pyvsalgorithms_occupancy.py) - Output paths (
results/duration/vsresults/occupancy/) - MLflow experiment naming (
Duration_PredvsOccupancy_Pred)
We use MLflow to track experiments and maintain model versioning, enabling systematic comparison of different training configurations and reproducible results.
The complete implementation can be found in our GitHub repository.
Data Preparation and Loading
Our first step involves loading the engineered features and preparing them for training:
def load_and_prepare_data(project_root: str):
"""Load and prepare the dataset."""
df = pd.read_csv(f'{project_root}/data/processed/train_engineered.csv')
target = 'Duration_In_Min' # Changes to 'Occupancy' for occupancy training
features_to_drop = ['Student_IDs', 'Semester', 'Class_Standing', 'Major',
'Expected_Graduation', ...]
X, y = prepare_data(df, target, features_to_drop)
return train_test_split(X, y, test_size=0.2, shuffle=False)We maintain chronological order by setting shuffle=False, as both duration and occupancy predictions exhibit temporal patterns. The features we drop are either identifiers or categorical variables already encoded during feature engineering.
Core Training Architecture
The main training function orchestrates the entire process, setting up experiments and managing model iterations:
def train_models(X_train, y_train, X_test, y_test):
"""Main training function."""
mlflow.set_tracking_uri("http://127.0.0.1:5000")
experiment_base = "Duration_Pred"
# Get definitions
models = get_model_definitions()
pipelines = get_pipeline_definitions()
cv_methods = get_cv_methods(len(X_train))
scalers = [RobustScaler(), StandardScaler(), MinMaxScaler()]This setup enables systematic experimentation across different model types, scaling approaches, and cross-validation strategies. The MLflow integration ensures reproducibility and experiment tracking.
Single Model Training Implementation
The core training logic for individual models incorporates GridSearchCV with RMSE scoring:
def train_single_model(name, scale_type, cv_name, pipeline, params, cv, X_train, y_train):
"""Train a single model configuration."""
rmse_scorer = make_scorer(
lambda y_true, y_pred: np.sqrt(mean_squared_error(y_true, y_pred)),
greater_is_better=False
)
search = GridSearchCV(
pipeline,
params,
scoring=rmse_scorer,
cv=cv,
n_jobs=-1,
verbose=0,
error_score='raise'
)The implementation leverages parallel processing through joblib’s ‘loky’ backend and includes comprehensive error handling. Each training iteration is tracked through MLflow:
with mlflow.start_run(run_name=model_name) as run:
mlflow.log_metric("rmse", rmse_score)
mlflow.log_metric("rmse_std", cv_std)
# Log parameters
for param_name, param_value in search.best_params_.items():
mlflow.log_param(param_name, param_value)Model Evaluation Framework
The evaluation system provides systematic assessment of trained models:
def evaluate_final_models(results, X_test, y_test):
"""Simple evaluation of models on test set without visualization."""
final_results = []
for result in results:
model_name = f"{result['model']}_{result['pipeline_type']}_{result['cv_method']}"
try:
model = mlflow.sklearn.load_model(f"models:/{model_name}/latest")
y_pred = model.predict(X_test)
final_results.append({
'Model': result['model'],
'Pipeline': result['pipeline_type'],
'CV_Method': result['cv_method'],
'RMSE': np.sqrt(mean_squared_error(y_test, y_pred)),
'R2': r2_score(y_test, y_pred)
})This framework enables consistent evaluation across different model configurations while maintaining detailed performance metrics.
Execution Flow
The main execution flow ties all components together:
def main():
# Load and prepare data
X_train, X_test, y_train, y_test = load_and_prepare_data(project_root)
# Train models
results = train_models(X_train, y_train, X_test, y_test)
# Save basic results
results_df = pd.DataFrame(results)
results_df.to_csv(f'{project_root}/results/duration/training_results.csv', index=False)
# Basic evaluation on test set
final_results = evaluate_final_models(results, X_test, y_test)
final_df = pd.DataFrame(final_results)
final_df.to_csv(f'{project_root}/results/duration/test_evaluation.csv', index=False)This structured approach ensures reproducible training runs while maintaining comprehensive result logging and evaluation.
Resource Management
The implementation includes several key resource management features:
- Parallel Processing: Utilizes joblib’s parallel backend for efficient computation
- Memory Management: Implements garbage collection after model training
- Error Recovery: Includes comprehensive exception handling throughout the pipeline
These components work together to provide robust and efficient model training capabilities while maintaining consistent evaluation and deployment readiness.
Model Testing
This chapter details our systematic approach to model evaluation. Our testing framework serves three key objectives:
- Performance Validation: Assess how well our models generalize to unseen data
- Model Comparison: Evaluate different architectures and strategies against each other
- Production Readiness: Ensure models are reliable and stable for deployment
The core testing code is shared between two scripts: src/python/test_train/duration/TEST_duration.py and src/python/test_train/occupancy/TEST_occupancy.py. Like our training scripts, these share approximately 95% of their code, differing primarily in:
- Target variable selection (
Duration_In_MinvsOccupancy) - Output paths (
results/duration/vsresults/occupancy/) - MLflow experiment base name (
Duration_PredvsOccupancy_Pred)
We use MLflow, an open-source platform for machine learning lifecycle management, to track our experiments and maintain model versioning. This enables reproducible testing and systematic comparison of different model configurations.
The complete implementation can be found in our GitHub repository.
Data Preparation
Our testing framework uses the same data split that was established during training to ensure consistent evaluation. We maintain the original 80/20 split of our dataset, where the 20% holdout set was never seen during model training or validation:
df = pd.read_csv(f'{project_root}/data/processed/train_engineered.csv')
target = 'Duration_In_Min' # Changes to 'Occupancy' for occupancy testing
features_to_drop = ['Student_IDs', 'Semester', 'Class_Standing', 'Major',
'Expected_Graduation', ...]
X, y = prepare_data(df, target, features_to_drop)
# Using same holdout set from training
X_train, X_test, y_train, y_test = train_test_split(
X, y,
test_size=0.2,
shuffle=False, # Maintains chronological order
random_state=3 # Same seed used in training
)By using the same holdout set and random seed as training, we ensure:
- No data leakage between training and testing
- Fair comparison across all model variants
- Consistent evaluation of temporal patterns
- Reliable assessment of model generalization
The framework tests models trained with three distinct cross-validation strategies:
- K-Fold (
kfold): Provides baseline performance through random splits, particularly suitable for duration prediction - Rolling Window (
rolling): Uses fixed-size moving windows to capture recent temporal dependencies, optimal for occupancy prediction - Expanding Window (
expanding): Implements growing windows that accumulate historical data, useful for long-term trends
These strategies are systematically evaluated in our testing loop:
pipeline_types = ['vanilla', 'interact_select', 'pca_lda']
cv_methods = ['kfold', 'rolling', 'expanding']
for pipeline_type in pipeline_types:
for cv_name in cv_methods:
full_model_name = f"{model_name}_{pipeline_type}_{cv_name}"This comprehensive approach allows us to assess how different cross-validation strategies affect model performance across our various pipeline configurations.
MLflow Configuration
The testing framework relies heavily on MLflow for model management and experiment tracking:
mlflow.set_tracking_uri("http://127.0.0.1:5000")
def check_mlflow_connection():
try:
client = mlflow.tracking.MlflowClient()
client.search_experiments()
return True
except Exception as e:
print(f"Error connecting to MLflow server: {e}")
print("Please ensure MLflow server is running...")
return FalseThis setup includes robust error handling for MLflow connectivity, ensuring the testing pipeline fails gracefully if the MLflow server is unavailable.
Model Testing Implementation
The core testing loop systematically evaluates each trained model variant:
for experiment in experiments:
model_name = experiment.name.split('/')[-1]
print(f"\nTesting models for {model_name}...")
# Pipeline types we used in training
pipeline_types = ['vanilla', 'interact_select', 'pca_lda']
cv_methods = ['kfold', 'rolling', 'expanding']
for pipeline_type in pipeline_types:
for cv_name in cv_methods:
full_model_name = f"{model_name}_{pipeline_type}_{cv_name}"
try:
model = mlflow.sklearn.load_model(f"models:/{full_model_name}/latest")
y_pred = model.predict(X_test)
metrics = calculate_metrics(y_test, y_pred)The implementation tests each combination of model type, pipeline configuration, and cross-validation method used during training.
Performance Visualization
For each model variant, we generate comprehensive visualization artifacts:
# Create prediction analysis plots
fig = plot_prediction_analysis(y_test, y_pred, full_model_name)
plt.savefig(f'{project_root}/results/duration/prediction_analysis_{full_model_name}.png')
plt.close()
# Create feature importance biplot for the best model
if metrics['RMSE'] < best_model_rmse:
best_model_rmse = metrics['RMSE']
best_model_predictions = y_pred
plot_feature_importance_biplot(
X_test, y_test, y_pred,
X_test.columns,
f'{project_root}/results/duration' # Changes for occupancy
)These visualizations include prediction analysis plots and feature importance biplots, particularly for the best-performing models.
Results Management
The framework maintains systematic records of test results:
# If we have results for this experiment, keep only top 3 based on RMSE
if experiment_results:
top_3_results = sorted(experiment_results, key=lambda x: x['RMSE'])[:3]
test_results.extend(top_3_results)
# Create results DataFrame
results_df = pd.DataFrame(test_results)
print("\nTop 3 Models per Experiment:")
print(results_df.sort_values('RMSE'))
# Save results and create visualization artifacts
save_visualization_results(results_df, project_root)This approach ensures we maintain records of the best-performing models while generating comprehensive visualization artifacts for analysis.
Resource Management
The testing framework includes several key features for robust execution:
- Error Handling: Comprehensive try-except blocks around model loading and prediction
- Resource Cleanup: Systematic closure of visualization artifacts
- Directory Management: Automatic creation of results directories
- Progress Logging: Detailed console output during testing
These components work together to provide reliable model evaluation while maintaining clear performance records and visualization artifacts.
Evaluation Framework
This chapter details our systematic approach to model evaluation and comparison. Our evaluation framework serves three key objectives:
- Performance Analysis: Aggregate and compare metrics across different model configurations
- Model Selection: Identify and extract the best performing model configurations
- Result Documentation: Generate comprehensive performance reports and visualizations
The core evaluation code is shared between two scripts: src/python/evaluation/model_evals.py and src/python/evaluation/best_model_params.py. These scripts work together to provide a complete evaluation pipeline for both duration and occupancy predictions.
Performance Analysis Pipeline
Our framework begins by aggregating performance metrics across different model configurations:
def load_and_analyze(filepath, dataset_name):
"""Hierarchical model performance analysis."""
df = pd.read_csv(filepath)
# Cross-validation strategy analysis
cv_groups = df.groupby('CV_Method')[['RMSE', 'R2']].agg(['mean', 'std'])
cv_groups = cv_groups.sort_values(('RMSE', 'mean'))
# Pipeline architecture analysis
pipeline_groups = df.groupby('Pipeline')[['RMSE', 'R2']].agg(['mean', 'std'])
pipeline_groups = pipeline_groups.sort_values(('RMSE', 'mean'))
# Model type analysis
model_groups = df.groupby('Model')[['RMSE', 'R2']].agg(['mean', 'std'])
model_groups = model_groups.sort_values(('RMSE', 'mean'))This hierarchical analysis enables us to understand performance patterns across different aspects of our modeling approach.
Best Model Identification
The framework systematically identifies and extracts the optimal model configurations:
def get_best_model_params(eval_path, experiment_base):
"""Extract optimal model configuration."""
# Identify best performer
df = pd.read_csv(eval_path)
best_row = df.loc[df['RMSE'].idxmin()]
# Retrieve detailed configuration
model_name = f"{best_row['Model']}_{best_row['Pipeline']}_{best_row['CV_Method']}"
client = MlflowClient()
experiment = client.get_experiment_by_name(f"{experiment_base}/{best_row['Model']}")This process ensures we capture not just the best performance metrics, but the complete configuration that achieved them.
Results Documentation
The framework implements systematic result formatting and storage:
def save_and_print_results(results, dataset_type):
"""Format and persist evaluation results."""
print(f"\n====== Best Model for {dataset_type.title()} Prediction ======")
print(f"Model: {results['model']}")
print(f"Pipeline: {results['pipeline']}")
print(f"CV Method: {results['cv_method']}")
print(f"RMSE: {results['rmse']:.4f}")
print(f"R2: {results['r2']:.4f}")
# Save configuration
output_file = os.path.join(output_dir, f"{dataset_type}_best_model.json")
with open(output_file, 'w') as f:
json.dump(results, f, indent=2)This approach ensures consistent documentation of results across both prediction tasks.
Evaluation Pipeline
The main evaluation pipeline processes both prediction tasks:
def main():
"""Execute comprehensive evaluation pipeline."""
# Process occupancy results
occupancy_eval = "results/occupancy/test_evaluation.csv"
if os.path.exists(occupancy_eval):
occ_results = get_best_model_params(occupancy_eval, "Occupancy_Pred")
save_and_print_results(occ_results, "occupancy")
# Process duration results
duration_eval = "results/duration/test_evaluation.csv"
if os.path.exists(duration_eval):
dur_results = get_best_model_params(duration_eval, "Duration_Pred")
save_and_print_results(dur_results, "duration")Framework Components
The implementation includes several key features for comprehensive evaluation:
- Analysis Systems
- Performance metric aggregation
- Cross-model comparison
- Statistical analysis
- Result Processing
- Parameter extraction
- Configuration logging
- Performance ranking
- Output Generation
- Result formatting
- Metric visualization
- Configuration persistence
These components work together to provide systematic performance analysis and model selection capabilities, ensuring we can confidently identify and document our best performing models.