Solar-Prince's picture
feat: Initial Hugging Face deploy with LFS
2b39664
Raw
History Blame Contribute Delete
6.85 kB
"""
Evaluation script for the Deep Ensemble.
Computes:
- Accuracy on CIFAR-10 test set
- Expected Calibration Error (ECE)
- OOD detection AUROC (CIFAR-10 vs CIFAR-100 using predictive entropy)
Usage:
python evaluate.py
python evaluate.py --checkpoint-dir ./checkpoints --ensemble-size 5
"""
import argparse
import os
import numpy as np
import torch
from torch.utils.data import DataLoader
import torchvision
import torchvision.transforms as T
from model import get_model
from utils import (
ensemble_predict, mean_prediction, predictive_entropy,
mutual_information, expected_calibration_error, ood_auroc,
CIFAR10_CLASSES,
)
def load_ensemble(checkpoint_dir, ensemble_size, device, num_classes=10):
"""Load all ensemble member models from checkpoints."""
models = []
for i in range(ensemble_size):
path = os.path.join(checkpoint_dir, f"model_{i}.pt")
if not os.path.exists(path):
print(f"Warning: Checkpoint not found: {path}")
continue
model = get_model(num_classes=num_classes, depth=16, widen_factor=2)
checkpoint = torch.load(path, map_location=device, weights_only=True)
model.load_state_dict(checkpoint["model_state_dict"])
model.to(device)
model.eval()
models.append(model)
print(f" Loaded model_{i} (test_acc: {checkpoint.get('test_acc', '?'):.2f}%, "
f"seed: {checkpoint.get('seed', '?')})")
print(f"Loaded {len(models)}/{ensemble_size} ensemble members")
return models
def get_test_loader(dataset_class, data_dir="./data", batch_size=128, num_workers=4):
"""Create a test dataloader with standard normalization."""
transform = T.Compose([
T.ToTensor(),
T.Normalize((0.4914, 0.4822, 0.4465),
(0.2470, 0.2435, 0.2616)),
])
dataset = dataset_class(
root=data_dir, train=False, download=True, transform=transform)
return DataLoader(
dataset, batch_size=batch_size, shuffle=False,
num_workers=num_workers, pin_memory=True)
def evaluate_accuracy_and_calibration(models, loader, device):
"""Compute ensemble accuracy and ECE on a dataset."""
all_probs = []
all_targets = []
for inputs, targets in loader:
probs = ensemble_predict(models, inputs, device) # (M, B, K)
all_probs.append(probs)
all_targets.append(targets.numpy())
all_probs = np.concatenate(all_probs, axis=1) # (M, N, K)
all_targets = np.concatenate(all_targets, axis=0) # (N,)
mean_p = mean_prediction(all_probs) # (N, K)
predictions = np.argmax(mean_p, axis=1) # (N,)
confidences = np.max(mean_p, axis=1) # (N,)
accuracies_bin = (predictions == all_targets).astype(float)
accuracy = 100.0 * accuracies_bin.mean()
ece = expected_calibration_error(confidences, accuracies_bin)
return accuracy, ece, all_probs
def compute_ood_scores(models, id_loader, ood_loader, device):
"""Compute uncertainty scores for ID and OOD data."""
id_probs_list = []
ood_probs_list = []
print("Computing ID (CIFAR-10) uncertainty scores...")
for inputs, _ in id_loader:
probs = ensemble_predict(models, inputs, device)
id_probs_list.append(probs)
print("Computing OOD (CIFAR-100) uncertainty scores...")
for inputs, _ in ood_loader:
probs = ensemble_predict(models, inputs, device)
ood_probs_list.append(probs)
id_probs = np.concatenate(id_probs_list, axis=1) # (M, N_id, K)
ood_probs = np.concatenate(ood_probs_list, axis=1) # (M, N_ood, K)
# Predictive entropy as OOD score
id_entropy = predictive_entropy(mean_prediction(id_probs))
ood_entropy = predictive_entropy(mean_prediction(ood_probs))
# Mutual information as OOD score
id_mi = mutual_information(id_probs)
ood_mi = mutual_information(ood_probs)
return {
"id_entropy": id_entropy,
"ood_entropy": ood_entropy,
"id_mi": id_mi,
"ood_mi": ood_mi,
}
def main():
parser = argparse.ArgumentParser(description="Evaluate Deep Ensemble")
parser.add_argument("--checkpoint-dir", type=str, default="./checkpoints")
parser.add_argument("--ensemble-size", type=int, default=5)
parser.add_argument("--data-dir", type=str, default="./data")
parser.add_argument("--batch-size", type=int, default=128)
parser.add_argument("--num-workers", type=int, default=4)
parser.add_argument("--gpu", type=int, default=0)
args = parser.parse_args()
# Device
if torch.cuda.is_available():
device = torch.device(f"cuda:{args.gpu}")
elif hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
print(f"Device: {device}")
# Load ensemble
print("\nLoading ensemble...")
models = load_ensemble(args.checkpoint_dir, args.ensemble_size, device)
if not models:
print("ERROR: No models loaded. Train models first with train.py")
return
# Evaluate on CIFAR-10 (in-distribution)
print("\n" + "=" * 60)
print("IN-DISTRIBUTION EVALUATION (CIFAR-10)")
print("=" * 60)
cifar10_loader = get_test_loader(
torchvision.datasets.CIFAR10, args.data_dir,
args.batch_size, args.num_workers)
accuracy, ece, _ = evaluate_accuracy_and_calibration(
models, cifar10_loader, device)
print(f" Accuracy: {accuracy:.2f}%")
print(f" ECE: {ece:.4f}")
# OOD detection (CIFAR-10 vs CIFAR-100)
print("\n" + "=" * 60)
print("OOD DETECTION (CIFAR-10 vs CIFAR-100)")
print("=" * 60)
cifar100_loader = get_test_loader(
torchvision.datasets.CIFAR100, args.data_dir,
args.batch_size, args.num_workers)
scores = compute_ood_scores(models, cifar10_loader, cifar100_loader, device)
auroc_entropy = ood_auroc(scores["id_entropy"], scores["ood_entropy"])
auroc_mi = ood_auroc(scores["id_mi"], scores["ood_mi"])
print(f" AUROC (Predictive Entropy): {auroc_entropy:.4f}")
print(f" AUROC (Mutual Information): {auroc_mi:.4f}")
# Summary statistics
print("\n" + "=" * 60)
print("UNCERTAINTY STATISTICS")
print("=" * 60)
print(f" ID Entropy — mean: {scores['id_entropy'].mean():.4f}, "
f"std: {scores['id_entropy'].std():.4f}")
print(f" OOD Entropy — mean: {scores['ood_entropy'].mean():.4f}, "
f"std: {scores['ood_entropy'].std():.4f}")
print(f" ID MI — mean: {scores['id_mi'].mean():.4f}, "
f"std: {scores['id_mi'].std():.4f}")
print(f" OOD MI — mean: {scores['ood_mi'].mean():.4f}, "
f"std: {scores['ood_mi'].std():.4f}")
print("\n✓ Evaluation complete.")
if __name__ == "__main__":
main()