import numpy as np import pandas as pd # Configuration (must match training.py) INPUT_DIM = 784 HIDDEN_DIMS = [256, 128] OUTPUT_DIM = 10 def relu(Z): """ReLU activation function""" return np.maximum(0.0, Z) def softmax(Z): """Numerically stable softmax""" Z_shift = Z - np.max(Z, axis=0, keepdims=True) expZ = np.exp(Z_shift) return expZ / np.sum(expZ, axis=0, keepdims=True) def forward_prop(X, params): """ Forward propagation through the network Args: X: Input data (784, m) params: Dictionary containing W1, b1, W2, b2, W3, b3 Returns: cache: Dictionary with intermediate values probs: Output probabilities (10, m) """ W1, b1 = params["W1"], params["b1"] W2, b2 = params["W2"], params["b2"] W3, b3 = params["W3"], params["b3"] Z1 = W1 @ X + b1 A1 = relu(Z1) Z2 = W2 @ A1 + b2 A2 = relu(Z2) Z3 = W3 @ A2 + b3 A3 = softmax(Z3) cache = { "X": X, "Z1": Z1, "A1": A1, "Z2": Z2, "A2": A2, "Z3": Z3, "A3": A3, } return cache, A3 def get_predictions(probs): """Get predicted class from probabilities""" return np.argmax(probs, axis=0) def get_accuracy(probs, labels): """Calculate accuracy""" predictions = get_predictions(probs) return np.mean(predictions == labels) def load_model(filepath="archive/trained_model.npz"): """ Load trained model parameters from disk Args: filepath: Path to the saved model file Returns: params: Dictionary of model parameters """ print(f"Loading model from '{filepath}'...") loaded = np.load(filepath) params = {key: loaded[key] for key in loaded.files if key not in {"mean", "std"}} mean = loaded["mean"] std = loaded["std"] print(f"Model loaded successfully!") print(f"Model contains: {list(params.keys())}") return params, mean, std def load_test_data(path="archive/mnist_test.csv"): """ Load and preprocess test data Args: path: Path to test CSV file Returns: X_test: Test features (784, m) Y_test: Test labels (m,) """ print(f"\nLoading test data from '{path}'...") data = pd.read_csv(path).to_numpy(dtype=np.float32) data_test = data.T Y_test = data_test[0].astype(np.int64) X_test = data_test[1:] print(f"Test set size: {X_test.shape[1]} examples") return X_test, Y_test def normalize_with_stats(X, mean, std): """ Normalize data using provided mean and std Args: X: Input data mean: Mean from training set std: Standard deviation from training set Returns: X_normalized: Normalized data """ return (X - mean) / std def predict_single_image(image, params, mean, std, show_probabilities=True): """ Make prediction on a single image Args: image: Image data (784,) or (784, 1) params: Trained model parameters show_probabilities: Whether to print class probabilities Returns: prediction: Predicted digit probabilities: Probability for each class """ # Ensure correct shape if image.ndim == 1: image = image.reshape(784, 1) image = image.astype(np.float32) / 255.0 image = normalize_with_stats(image, mean, std) # Forward pass _, probs = forward_prop(image, params) prediction = get_predictions(probs)[0] if show_probabilities: print("\nClass probabilities:") for digit in range(10): prob = probs[digit, 0] bar = "█" * int(prob * 50) print(f" {digit}: {prob:.4f} {bar}") return prediction, probs[:, 0] def evaluate_test_set(params, X_test, Y_test): """ Evaluate model on test set Args: params: Trained model parameters X_test: Test features Y_test: Test labels Returns: predictions: Predicted labels accuracy: Test accuracy """ print("\n" + "="*60) print("EVALUATING ON TEST SET") print("="*60) _, probs = forward_prop(X_test, params) predictions = get_predictions(probs) accuracy = np.mean(predictions == Y_test) print(f"Test Accuracy: {accuracy:.4f} ({accuracy*100:.2f}%)") # Per-digit accuracy print("\nPer-digit accuracy:") for digit in range(10): digit_mask = Y_test == digit digit_correct = np.sum((predictions == Y_test) & digit_mask) digit_total = np.sum(digit_mask) digit_accuracy = digit_correct / digit_total if digit_total > 0 else 0 print(f" Digit {digit}: {digit_accuracy*100:5.2f}% ({digit_correct}/{digit_total})") return predictions, accuracy def show_sample_predictions(params, X_test, Y_test, num_samples=10): """ Show sample predictions Args: params: Trained model parameters X_test: Test features Y_test: Test labels num_samples: Number of samples to show """ print("\n" + "="*60) print(f"SAMPLE PREDICTIONS (first {num_samples} examples)") print("="*60) _, probs = forward_prop(X_test[:, :num_samples], params) predictions = get_predictions(probs) print(f"Predicted: {predictions}") print(f"Actual: {Y_test[:num_samples]}") print(f"Match: {predictions == Y_test[:num_samples]}") def main(): # Load the trained model params, mean, std = load_model("archive/trained_model.npz") # Load test data X_test_raw, Y_test = load_test_data("archive/mnist_test.csv") X_test_scaled = X_test_raw / 255.0 X_test = normalize_with_stats(X_test_scaled, mean, std) # Show sample predictions show_sample_predictions(params, X_test, Y_test, num_samples=20) # Evaluate on full test set predictions, accuracy = evaluate_test_set(params, X_test, Y_test) # Test single image prediction print("\n" + "="*60) print("SINGLE IMAGE PREDICTION EXAMPLE") print("="*60) print(f"Testing image #0 (actual label: {int(Y_test[0])})") prediction, probs = predict_single_image(X_test_raw[:, 0], params, mean, std) print(f"\nPredicted digit: {prediction}") print("\n" + "="*60) print(f"FINAL TEST ACCURACY: {accuracy:.4f} ({accuracy*100:.2f}%)") print("="*60) if __name__ == "__main__": main()