| import numpy as np
|
| import matplotlib.pyplot as plt
|
| import torch
|
|
|
| from pathlib import Path
|
|
|
| from src.logger import get_logger
|
| from src.model import ResNet18
|
| from src.predict import CLASS_NAMES
|
|
|
| logger = get_logger(__name__)
|
|
|
| DEVICE = torch.device(
|
| "cuda"
|
| if torch.cuda.is_available()
|
| else "cpu"
|
| )
|
|
|
|
|
| def load_model_for_evaluation():
|
| """
|
| Load trained ResNet checkpoint.
|
| """
|
|
|
| logger.info(
|
| "Loading model for evaluation"
|
| )
|
|
|
| model = ResNet18(
|
| num_classes=11
|
| )
|
|
|
| checkpoint = torch.load(
|
| "models/resnet_cifar10.pth",
|
| map_location=DEVICE
|
| )
|
|
|
| model.load_state_dict(
|
| checkpoint["model"]
|
| )
|
|
|
| model.to(DEVICE)
|
|
|
| model.eval()
|
|
|
| logger.info(
|
| "Model loaded successfully"
|
| )
|
|
|
| return model
|
|
|
|
|
| def show_errors(
|
| test_loader,
|
| max_images=9
|
| ):
|
| """
|
| Display misclassified images.
|
|
|
| Parameters
|
| ----------
|
| test_loader : DataLoader
|
| PyTorch test dataloader
|
|
|
| max_images : int
|
| Number of errors to visualize
|
| """
|
|
|
| logger.info(
|
| "Starting error analysis"
|
| )
|
|
|
| model = load_model_for_evaluation()
|
|
|
| all_images = []
|
| all_labels = []
|
| all_predictions = []
|
|
|
| logger.info(
|
| "Running batched inference"
|
| )
|
|
|
| with torch.no_grad():
|
|
|
| for images, labels in test_loader:
|
|
|
| images = images.to(
|
| DEVICE
|
| )
|
|
|
| outputs = model(
|
| images
|
| )
|
|
|
| predictions = torch.argmax(
|
| outputs,
|
| dim=1
|
| )
|
|
|
| all_predictions.extend(
|
| predictions.cpu().numpy()
|
| )
|
|
|
| all_labels.extend(
|
| labels.cpu().numpy()
|
| )
|
|
|
| all_images.extend(
|
| images.cpu()
|
| )
|
|
|
| y_pred = np.array(
|
| all_predictions
|
| )
|
|
|
| y_true = np.array(
|
| all_labels
|
| )
|
|
|
| errors = np.where(
|
| y_pred != y_true
|
| )[0]
|
|
|
| logger.info(
|
| f"Misclassified samples: {len(errors)}"
|
| )
|
|
|
| if len(errors) == 0:
|
|
|
| logger.info(
|
| "No misclassifications found"
|
| )
|
|
|
| return
|
|
|
| Path(
|
| "outputs/plots"
|
| ).mkdir(
|
| parents=True,
|
| exist_ok=True
|
| )
|
|
|
| plt.figure(
|
| figsize=(12, 10)
|
| )
|
|
|
| num_images = min(
|
| max_images,
|
| len(errors)
|
| )
|
|
|
| for i in range(num_images):
|
|
|
| idx = errors[i]
|
|
|
| image = all_images[idx]
|
|
|
| if isinstance(
|
| image,
|
| torch.Tensor
|
| ):
|
| image = image.numpy()
|
|
|
|
|
|
|
| image = np.transpose(
|
| image,
|
| (1, 2, 0)
|
| )
|
|
|
| image = np.clip(
|
| image,
|
| 0,
|
| 1
|
| )
|
|
|
| actual = int(
|
| y_true[idx]
|
| )
|
|
|
| predicted = int(
|
| y_pred[idx]
|
| )
|
|
|
| plt.subplot(
|
| 3,
|
| 3,
|
| i + 1
|
| )
|
|
|
| plt.imshow(
|
| image
|
| )
|
|
|
| plt.title(
|
| f"Actual: {CLASS_NAMES[actual]}\n"
|
| f"Pred: {CLASS_NAMES[predicted]}"
|
| )
|
|
|
| plt.axis(
|
| "off"
|
| )
|
|
|
| plt.tight_layout()
|
|
|
| save_path = (
|
| "outputs/plots/error_analysis.png"
|
| )
|
|
|
| plt.savefig(
|
| save_path,
|
| dpi=300,
|
| bbox_inches="tight"
|
| )
|
|
|
| plt.show()
|
|
|
| plt.close()
|
|
|
| logger.info(
|
| f"Error analysis saved to {save_path}"
|
| )
|
|
|
|
|
| if __name__ == "__main__":
|
|
|
| from src.data import load_dataset
|
|
|
| train_loader, test_loader = (
|
| load_dataset()
|
| )
|
|
|
| show_errors(
|
| test_loader
|
| ) |