84 lines
3.2 KiB
Python
84 lines
3.2 KiB
Python
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import numpy as np
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
|
|
|
|
import aggregator
|
|
import router
|
|
from classifier import FaceClassifier, calibrated_softmax
|
|
|
|
|
|
class Story05ClassifierTests(unittest.TestCase):
|
|
def test_calibrated_softmax_sums_to_one(self):
|
|
logits = np.array([[2.0, 1.0], [-1.0, 3.0]], dtype=np.float32)
|
|
probs = calibrated_softmax(logits, temperature=1.0)
|
|
np.testing.assert_allclose(probs.sum(axis=1), [1.0, 1.0], atol=1e-6)
|
|
|
|
def test_temperature_scaling_raises_lower_confidence_entropy(self):
|
|
logits = np.array([[2.0, 0.5]], dtype=np.float32)
|
|
sharp = calibrated_softmax(logits, temperature=0.5)
|
|
soft = calibrated_softmax(logits, temperature=2.0)
|
|
# higher temperature → softer distribution (target class prob moves toward 0.5)
|
|
self.assertGreater(sharp[0, 0], soft[0, 0])
|
|
|
|
def test_classifier_placeholder_returns_neutral_probability(self):
|
|
clf = FaceClassifier(engine_path="/nonexistent/model.trt", temperature=1.0)
|
|
probs = clf.classify([])
|
|
self.assertEqual(probs, [])
|
|
|
|
def test_classifier_placeholder_single_crop_returns_half(self):
|
|
import tempfile
|
|
from PIL import Image
|
|
|
|
clf = FaceClassifier(engine_path="/nonexistent/model.trt", temperature=1.0)
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
crop = Path(tmpdir) / "crop.jpg"
|
|
Image.new("RGB", (224, 224)).save(crop)
|
|
probs = clf.classify([str(crop)])
|
|
# placeholder logits are all zeros → softmax → 0.5 for each class
|
|
self.assertAlmostEqual(probs[0], 0.5, places=5)
|
|
|
|
|
|
class Story05AggregatorTests(unittest.TestCase):
|
|
def test_max_strategy(self):
|
|
self.assertAlmostEqual(aggregator.aggregate([0.3, 0.8, 0.6], strategy="max"), 0.8)
|
|
|
|
def test_empty_confidences_returns_zero(self):
|
|
self.assertEqual(aggregator.aggregate([], strategy="max"), 0.0)
|
|
|
|
def test_top_k_mean(self):
|
|
result = aggregator.aggregate([0.1, 0.9, 0.5, 0.8], strategy="top_k_mean", top_k=2)
|
|
self.assertAlmostEqual(result, (0.9 + 0.8) / 2, places=5)
|
|
|
|
def test_weighted_mean_clamps_to_unit_interval(self):
|
|
result = aggregator.aggregate([1.0, 1.0], strategy="weighted_mean", alpha=100.0, beta=0.0)
|
|
self.assertLessEqual(result, 1.0)
|
|
self.assertGreaterEqual(result, 0.0)
|
|
|
|
|
|
class Story05RouterTests(unittest.TestCase):
|
|
def test_match_at_high_threshold(self):
|
|
self.assertEqual(router.route(0.75), router.MATCH)
|
|
|
|
def test_review_between_thresholds(self):
|
|
self.assertEqual(router.route(0.60), router.REVIEW)
|
|
|
|
def test_skip_below_low_threshold(self):
|
|
self.assertEqual(router.route(0.44), router.SKIP)
|
|
|
|
def test_inclusive_high_threshold_boundary(self):
|
|
self.assertEqual(router.route(0.75, t_high=0.75, t_low=0.45), router.MATCH)
|
|
|
|
def test_inclusive_low_threshold_boundary(self):
|
|
self.assertEqual(router.route(0.45, t_high=0.75, t_low=0.45), router.REVIEW)
|
|
|
|
def test_no_faces_zero_confidence_routes_skip(self):
|
|
self.assertEqual(router.route(0.0), router.SKIP)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|