Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
13437e0d8b | ||
|
|
b26c66dcce |
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
@@ -62,7 +62,13 @@ Everything is per second, aligned to the 1-fps presence grid.
|
|||||||
the films where video is weak (Downton, Sound of Metal), so it is included and
|
the films where video is weak (Downton, Sound of Metal), so it is included and
|
||||||
the model uses it where it helps.
|
the model uses it where it helps.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
The left panel is the *feature* development, scored at a strict ±2 s tolerance so
|
||||||
|
each change is visible — this is where "delta beats raw histogram" was measured, not
|
||||||
|
the shipped tolerance. The right panel is the shipped detector at the ±20 s
|
||||||
|
tolerance the pipeline actually uses (see below). The two panels are on different
|
||||||
|
tolerances by design and must not be read as one curve.
|
||||||
|
|
||||||
Dead ends, all measured and discarded: audio-only detection; raw
|
Dead ends, all measured and discarded: audio-only detection; raw
|
||||||
histograms/PSDs as input; a two-tower BiLSTM (no better than the tree, far slower);
|
histograms/PSDs as input; a two-tower BiLSTM (no better than the tree, far slower);
|
||||||
@@ -81,14 +87,22 @@ and TransNetV2 (a Conv3D net that will not co-reside with the ROCm/VAAPI stack).
|
|||||||
real boundaries give way to noise. Selecting at the knee **self-calibrates the
|
real boundaries give way to noise. Selecting at the knee **self-calibrates the
|
||||||
boundary count** to roughly the true scene count, per film, with no global
|
boundary count** to roughly the true scene count, per film, with no global
|
||||||
threshold that would be wrong for every grade.
|
threshold that would be wrong for every grade.
|
||||||
- **Trained on all nine films** for the shipped model. Café Society and Scarface
|
- **Trained on all nine films** for the shipped model. Keeping the low-contrast
|
||||||
(the low-contrast grades) *must* be in training — held out, the model cannot
|
grades (Café Society, Scarface) in training matters most: on its own training
|
||||||
generalise to them; in training they reach 70–86% boundary-F1.
|
films the shipped model reaches **72.9% macro boundary-F1** (per-film 51–86%),
|
||||||
|
versus **29.8%** for the grayscale baseline on the same films.
|
||||||
|
|
||||||
Boundary detection, held out (leave-one-out, ±20 s tolerance — appropriate given
|
Boundary detection, held out (leave-one-out, ±20 s tolerance — appropriate given
|
||||||
~170 s scenes): **~34% F1, versus ~27% for the grayscale baseline.** The absolute
|
~170 s scenes): **44.1% macro F1, versus 29.8% for the grayscale baseline** — the
|
||||||
number is capped by the narrative-vs-audiovisual mismatch above; the point is the
|
honest generalisation number, each film scored by a detector trained on the other
|
||||||
downstream effect.
|
eight. Even the low-contrast grades generalise (Scarface held out 32%, Café Society
|
||||||
|
51%), where the grayscale detector scores 0% and 31%. The absolute number is capped
|
||||||
|
by the narrative-vs-audiovisual mismatch above — many boundaries have no
|
||||||
|
audio-visual signature at all — so the point is the downstream effect, below.
|
||||||
|
|
||||||
|
| boundary-F1 @±20 s | grayscale | learned (LOO) | learned (train-all) |
|
||||||
|
| ------------------ | --------: | ------------: | ------------------: |
|
||||||
|
| macro over 9 films | 29.8% | **44.1%** | 72.9% |
|
||||||
|
|
||||||
## The result that matters: actor presence
|
## The result that matters: actor presence
|
||||||
|
|
||||||
|
|||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Regenerate annotated TP/FP/FN frame examples for ALL 9 films against the current
|
||||||
|
# opencv5 pipeline (learned-boundary flood, shipped config). Replays each film with
|
||||||
|
# --raw-out for bboxes, then dump_error_frames.py draws GT-aware boxes
|
||||||
|
# (green TP / red FP / orange unknown / blue FN panel). Frames land in
|
||||||
|
# experiments/dump_review/<slug>/ (regenerable; gitignored). Hand-pick the ones a
|
||||||
|
# doc needs from there.
|
||||||
|
set -uo pipefail
|
||||||
|
REPO="/home/dtourolle/Development/scene-actor-extraction"; cd "$REPO"
|
||||||
|
export MIOPEN_USER_DB_PATH="$HOME/.cache/miopen-sae"
|
||||||
|
GAL=experiments/galleries/gallery_LVFace-B_Glint360K.h5
|
||||||
|
LUT=experiments/file-lut.json
|
||||||
|
CFG=(--prob-threshold 0.485 --ownership-logodds 1.72 --track-extinction-sec 31
|
||||||
|
--track-alpha 0.435 --evidence-rho-max 0.204 --evidence-admit-below 0.784
|
||||||
|
--match-prior 0.433 --expand-band-lo 0.804 --expand-band-hi 0.952
|
||||||
|
--expand-gallery --presence-mode flood)
|
||||||
|
mapfile -t ROWS < <(python3 -c '
|
||||||
|
import json
|
||||||
|
for f in json.load(open("experiments/manifests/films_LVFace_opencv5.json")):
|
||||||
|
print(f["slug"]+"\t"+f["xray"])')
|
||||||
|
SP=/tmp/claude-1000/-home-dtourolle-Development-scene-actor-extraction/c579f8cf-2974-4cbd-be88-afec68dbbf58/scratchpad
|
||||||
|
for row in "${ROWS[@]}"; do
|
||||||
|
slug="${row%%$'\t'*}"; xray="${row#*$'\t'}"
|
||||||
|
movie="$(python3 -c "import json;print(json.load(open('$LUT'))['$slug'])")"
|
||||||
|
echo "=== $slug ==="
|
||||||
|
[ -f "experiments/dump_review/$slug/manifest.json" ] && { echo " exists, skip"; continue; }
|
||||||
|
# replay the learned-boundary (LOO) dump so frames reflect true generalization
|
||||||
|
dump="experiments/dumps/injected_loo/${slug}.h5"
|
||||||
|
[ -f "$dump" ] || dump="experiments/dumps/LVFace-B_Glint360K_opencv5/dump_${slug}.h5"
|
||||||
|
for try in 1 2 3; do
|
||||||
|
timeout 280 python scripts/optimizer/replay.py --dump "$dump" --gallery "$GAL" \
|
||||||
|
--out "$SP/${slug}_pred.json" --raw-out "$SP/${slug}_raw.jsonl" "${CFG[@]}" \
|
||||||
|
>"$SP/${slug}_replay.log" 2>&1 && break
|
||||||
|
echo " replay try $try failed, retrying"
|
||||||
|
done
|
||||||
|
[ -s "$SP/${slug}_raw.jsonl" ] || { echo " no raw output, skip"; continue; }
|
||||||
|
python3 scripts/optimizer/dump_error_frames.py \
|
||||||
|
--pred "$SP/${slug}_pred.json" --raw "$SP/${slug}_raw.jsonl" \
|
||||||
|
--xray "$xray" --movie "$movie" --gallery "$GAL" \
|
||||||
|
--out-dir "experiments/dump_review/$slug" --n-per-bucket 4 \
|
||||||
|
>"$SP/${slug}_frames.log" 2>&1
|
||||||
|
echo " $(grep -oE 'wrote [0-9]+ frames' "$SP/${slug}_frames.log" | tail -1)"
|
||||||
|
done
|
||||||
|
echo "=== DONE ==="
|
||||||
@@ -57,16 +57,81 @@ def fig_macro():
|
|||||||
fig.tight_layout(); fig.savefig(OUT/"scene_presence_macro.png"); plt.close(fig)
|
fig.tight_layout(); fig.savefig(OUT/"scene_presence_macro.png"); plt.close(fig)
|
||||||
|
|
||||||
# ── Figure 3: feature/model evolution (boundary-F1 development) ──────────────
|
# ── Figure 3: feature/model evolution (boundary-F1 development) ──────────────
|
||||||
|
# Two panels, because the development curve and the shipped result are measured
|
||||||
|
# at DIFFERENT tolerances and must not be plotted on one axis:
|
||||||
|
# left — relative feature progress at the strict ±2 s tolerance (how the LSTM
|
||||||
|
# experiments were scored; establishes which features helped)
|
||||||
|
# right — the shipped XGBoost detector at the ±20 s tolerance the pipeline
|
||||||
|
# actually uses and scores at (grayscale vs learned-LOO vs train-all)
|
||||||
def fig_evolution():
|
def fig_evolution():
|
||||||
|
fig,(axl,axr)=plt.subplots(1,2,figsize=(11,4.5),gridspec_kw={"width_ratios":[1.15,1]})
|
||||||
|
|
||||||
steps=["grayscale\nbaseline","raw-hist\nLSTM","delta\nLSTM","XGBoost\n(delta+debounce)"]
|
steps=["grayscale\nbaseline","raw-hist\nLSTM","delta\nLSTM","XGBoost\n(delta+debounce)"]
|
||||||
f1=[7.2,7.5,10.8,15.2] # boundary-F1 @±2s during development
|
dev=[7.2,7.5,10.8,15.2] # boundary-F1 @±2s during LSTM-era development
|
||||||
fig,ax=plt.subplots(figsize=(6.5,4.5))
|
axl.plot(steps,dev,marker="o",color="#9aa7b4",lw=2,ms=8)
|
||||||
ax.plot(steps,f1,marker="o",color="#3d7ea6",lw=2,ms=8)
|
for i,v in enumerate(dev): axl.text(i,v+0.4,f"{v:.1f}%",ha="center",fontsize=9)
|
||||||
for i,v in enumerate(f1): ax.text(i,v+0.4,f"{v:.1f}%",ha="center",fontsize=10)
|
axl.set_ylabel("boundary F1 @±2 s (%)")
|
||||||
ax.set_ylabel("held-out boundary F1 @±2s (%)")
|
axl.set_title("Feature progress (strict ±2 s)")
|
||||||
ax.set_title("Detector development: features + model")
|
axl.set_ylim(0,18)
|
||||||
ax.set_ylim(0,18)
|
|
||||||
|
# shipped detector at the ±20s tolerance the pipeline uses — real measured
|
||||||
|
# macro numbers: grayscale (xgb_report gray_F1), learned LOO, learned train-all
|
||||||
|
names=["grayscale","learned\n(LOO)","learned\n(train-all)"]
|
||||||
|
f20=[29.8,44.1,72.9]; cols=["#e07a5f","#3d7ea6","#8fb8cf"]
|
||||||
|
bars=axr.bar(names,f20,color=cols)
|
||||||
|
for b,v in zip(bars,f20): axr.text(b.get_x()+b.get_width()/2,v+1.2,f"{v:.1f}%",
|
||||||
|
ha="center",fontsize=10,fontweight="bold")
|
||||||
|
axr.set_ylabel("boundary F1 @±20 s (%)")
|
||||||
|
axr.set_title("Shipped detector (±20 s, macro/9 films)")
|
||||||
|
axr.set_ylim(0,80)
|
||||||
|
fig.suptitle("Detector development, and where it landed",fontsize=13)
|
||||||
fig.tight_layout(); fig.savefig(OUT/"scene_detector_evolution.png"); plt.close(fig)
|
fig.tight_layout(); fig.savefig(OUT/"scene_detector_evolution.png"); plt.close(fig)
|
||||||
|
|
||||||
fig_presence(); fig_macro(); fig_evolution()
|
import csv as _csv
|
||||||
print("wrote:", *(p.name for p in sorted(OUT.glob("scene_*.png"))))
|
|
||||||
|
# ── Figure 4: DE convergence (the 10-knob presence sweep) ────────────────────
|
||||||
|
def fig_de():
|
||||||
|
import json
|
||||||
|
rows=[json.loads(l) for l in open("experiments/trajectories/lvface_opencv5_10knob.FINAL.jsonl")]
|
||||||
|
f1=[r["f1"]*100 for r in rows]
|
||||||
|
run_best=np.maximum.accumulate(f1)
|
||||||
|
fig,ax=plt.subplots(figsize=(8,4.5))
|
||||||
|
ax.scatter(range(len(f1)),f1,s=8,alpha=0.35,color="#9aa7b4",label="candidate")
|
||||||
|
ax.plot(run_best,color="#3d7ea6",lw=2,label="best so far")
|
||||||
|
ax.set_xlabel("DE evaluation"); ax.set_ylabel("macro presence F1 (%)")
|
||||||
|
ax.set_title("10-knob presence sweep (Differential Evolution)")
|
||||||
|
ax.legend(loc="lower right"); ax.set_ylim(0, max(f1)+8)
|
||||||
|
ax.text(0.02,0.95,f"optimum {max(f1):.1f}%",transform=ax.transAxes,va="top",
|
||||||
|
fontsize=10,bbox=dict(boxstyle="round",fc="#f4f4f4",ec="#ccc"))
|
||||||
|
fig.tight_layout(); fig.savefig(OUT/"de_search_landscape.png"); plt.close(fig)
|
||||||
|
|
||||||
|
# ── Figure 5: calibration curve (similarity → P(match)) ──────────────────────
|
||||||
|
def fig_calibration():
|
||||||
|
sims,ps=[],[]
|
||||||
|
with open("experiments/galleries/gallery_LVFace-B_Glint360K.h5.calib_cache.csv") as f:
|
||||||
|
for r in _csv.DictReader(f):
|
||||||
|
sims.append(float(r["similarity"])); ps.append(float(r["p_match"]))
|
||||||
|
fig,ax=plt.subplots(figsize=(6.5,4.5))
|
||||||
|
ax.plot(sims,ps,color="#3d7ea6",lw=2)
|
||||||
|
ax.axhline(0.485,ls="--",color="#e07a5f",lw=1,label="shipped threshold 0.485")
|
||||||
|
ax.set_xlabel("cosine similarity"); ax.set_ylabel("calibrated P(match)")
|
||||||
|
ax.set_title("LVFace-B Glint360K calibration"); ax.set_xlim(-1,1); ax.legend()
|
||||||
|
fig.tight_layout(); fig.savefig(OUT/"calibration_curves.png"); plt.close(fig)
|
||||||
|
|
||||||
|
# ── Figure 6: holdout F1 by film (learned detector, LOO) ─────────────────────
|
||||||
|
def fig_holdout():
|
||||||
|
order=np.argsort(FL)
|
||||||
|
fig,ax=plt.subplots(figsize=(8,4.5))
|
||||||
|
y=np.arange(len(FILMS))
|
||||||
|
ax.barh(y,[FL[i] for i in order],color="#3d7ea6")
|
||||||
|
ax.set_yticks(y); ax.set_yticklabels([FILMS[i] for i in order])
|
||||||
|
ax.set_xlabel("presence F1 (%), learned detector (LOO)")
|
||||||
|
ax.set_title("Per-film presence F1 — leave-one-out")
|
||||||
|
ax.axvline(np.mean(FL),ls="--",color="#333",lw=1)
|
||||||
|
ax.text(np.mean(FL)+1,0.2,f"macro {np.mean(FL):.1f}%",fontsize=9)
|
||||||
|
for i,idx in enumerate(order): ax.text(FL[idx]+0.5,i,f"{FL[idx]:.0f}",va="center",fontsize=8)
|
||||||
|
ax.set_xlim(0,100)
|
||||||
|
fig.tight_layout(); fig.savefig(OUT/"holdout_f1_by_film.png"); plt.close(fig)
|
||||||
|
|
||||||
|
fig_presence(); fig_macro(); fig_evolution(); fig_de(); fig_calibration(); fig_holdout()
|
||||||
|
print("wrote:", *(p.name for p in sorted(OUT.glob("*.png"))))
|
||||||
|
|||||||
Reference in New Issue
Block a user