From 65fee745859c9cf36eb22daf43b0541a5312e4d3 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 4 Jul 2026 20:41:54 +0200 Subject: [PATCH] perf(movienet): vectorise eval matching; count frames missing from Image.zip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit movienet_eval: replace the per-element dot() with numpy — actor references are loaded once as an ndarray and scored with a single matmul, keeping a whole-library gallery fast. movienet_prep: count and report frames referenced by annotations but absent from Image.zip instead of skipping them silently. --- scripts/movienet_eval.py | 26 +++++++++++++++----------- scripts/movienet_prep.py | 14 +++++++++----- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/scripts/movienet_eval.py b/scripts/movienet_eval.py index 4a40b6c..9bfd6f5 100644 --- a/scripts/movienet_eval.py +++ b/scripts/movienet_eval.py @@ -19,28 +19,32 @@ import json import sys from pathlib import Path +import numpy as np + sys.path.insert(0, str(Path(__file__).resolve().parent)) from sae_embed_loader import load_embedder def load_gallery(path: str) -> dict[str, dict]: - """Return {imdb_id: {"name": str, "embeddings": [[float]]}}.""" + """Return {imdb_id: {"name": str, "refs": np.ndarray[n_refs, dim]}}.""" with open(path) as f: data = json.load(f) - return {a["imdb_id"]: {"name": a["name"], "embeddings": a["embeddings"]} + return {a["imdb_id"]: {"name": a["name"], + "refs": np.asarray(a["embeddings"], dtype=np.float32)} for a in data["actors"]} -def dot(a: list[float], b: list[float]) -> float: - return sum(x * y for x, y in zip(a, b)) - - def match(embedding: list[float], gallery: dict[str, dict]) -> tuple[str, float, dict[str, float]]: - """Return (best_imdb_id, best_similarity, {imdb_id: similarity}).""" - scores: dict[str, float] = {} - for imdb_id, actor in gallery.items(): - # max similarity across all reference embeddings for this actor - scores[imdb_id] = max(dot(embedding, ref) for ref in actor["embeddings"]) + """Return (best_imdb_id, best_similarity, {imdb_id: similarity}). + + Similarity to an actor is the max dot product over that actor's reference + embeddings; vectorised with numpy so a whole-library gallery stays fast. + """ + vec = np.asarray(embedding, dtype=np.float32) + scores: dict[str, float] = { + imdb_id: float((actor["refs"] @ vec).max()) + for imdb_id, actor in gallery.items() + } best_id = max(scores, key=lambda k: scores[k]) return best_id, scores[best_id], scores diff --git a/scripts/movienet_prep.py b/scripts/movienet_prep.py index 331cec1..1aff111 100644 --- a/scripts/movienet_prep.py +++ b/scripts/movienet_prep.py @@ -133,20 +133,24 @@ def main(): image_zip = movienet_root / "Image.zip" frame_cache: dict[str, np.ndarray] = {} + n_missing_in_zip = 0 print(f"[prep] extracting {len(needed_paths)} frames from Image.zip…", file=sys.stderr) with zipfile.ZipFile(image_zip) as zf: for img_path in needed_paths: zip_entry = f"Image/{img_path}" try: data = zf.read(zip_entry) - arr = np.frombuffer(data, dtype=np.uint8) - img = cv2.imdecode(arr, cv2.IMREAD_COLOR) - if img is not None: - frame_cache[img_path] = img except KeyError: - pass # file missing from zip, skip silently + n_missing_in_zip += 1 # frame referenced by an annotation but absent from Image.zip + continue + arr = np.frombuffer(data, dtype=np.uint8) + img = cv2.imdecode(arr, cv2.IMREAD_COLOR) + if img is not None: + frame_cache[img_path] = img print(f"[prep] frames loaded: {len(frame_cache)}/{len(needed_paths)}", file=sys.stderr) + if n_missing_in_zip: + print(f"[prep] frames absent from Image.zip: {n_missing_in_zip}", file=sys.stderr) per_actor_count: dict[str, int] = {} gt_entries = []