Files
scene-actor-extraction/model-bakeoff/index.html
T
2026-07-19 22:28:14 +02:00

1897 lines
70 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="description" content="Face-recognition pipeline for finding on-screen actor presence in film/TV, built on KPN++">
<link rel="canonical" href="https://pages.tourolle.paris/dtourolle/scene-actor-extraction/model-bakeoff/">
<link rel="prev" href="../lvface-deep-dive/">
<link rel="next" href="../optimizer-experiments/">
<link rel="icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.6.1, mkdocs-material-9.7.7">
<title>Model Bake-off & Re-tune (full log) - scene-actor-extraction</title>
<link rel="stylesheet" href="../assets/stylesheets/main.ec1eaa64.min.css">
<link rel="stylesheet" href="../assets/stylesheets/palette.ab4e12ef.min.css">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i%7CRoboto+Mono:400,400i,700,700i&display=fallback">
<style>:root{--md-text-font:"Roboto";--md-code-font:"Roboto Mono"}</style>
<link rel="stylesheet" href="../stylesheets/extra.css">
<script>__md_scope=new URL("..",location),__md_hash=e=>[...e].reduce(((e,_)=>(e<<5)-e+_.charCodeAt(0)),0),__md_get=(e,_=localStorage,t=__md_scope)=>JSON.parse(_.getItem(t.pathname+"."+e)),__md_set=(e,_,t=localStorage,a=__md_scope)=>{try{t.setItem(a.pathname+"."+e,JSON.stringify(_))}catch(e){}}</script>
</head>
<body dir="ltr" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="amber">
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" for="__drawer"></label>
<div data-md-component="skip">
<a href="#model-bake-off-threshold-re-tune-experiment-log-2026-07-1819" class="md-skip">
Skip to content
</a>
</div>
<div data-md-component="announce">
</div>
<header class="md-header" data-md-component="header">
<nav class="md-header__inner md-grid" aria-label="Header">
<a href=".." title="scene-actor-extraction" class="md-header__button md-logo" aria-label="scene-actor-extraction" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
<label class="md-header__button md-icon" for="__drawer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M3 6h18v2H3zm0 5h18v2H3zm0 5h18v2H3z"/></svg>
</label>
<div class="md-header__title" data-md-component="header-title">
<div class="md-header__ellipsis">
<div class="md-header__topic">
<span class="md-ellipsis">
scene-actor-extraction
</span>
</div>
<div class="md-header__topic" data-md-component="header-topic">
<span class="md-ellipsis">
Model Bake-off & Re-tune (full log)
</span>
</div>
</div>
</div>
<form class="md-header__option" data-md-component="palette">
<input class="md-option" data-md-color-media="(prefers-color-scheme: dark)" data-md-color-scheme="slate" data-md-color-primary="black" data-md-color-accent="amber" aria-label="Switch to light mode" type="radio" name="__palette" id="__palette_0">
<label class="md-header__button md-icon" title="Switch to light mode" for="__palette_1" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7a5 5 0 0 1 5 5 5 5 0 0 1-5 5 5 5 0 0 1-5-5 5 5 0 0 1 5-5m0 2a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3m0-7 2.39 3.42C13.65 5.15 12.84 5 12 5s-1.65.15-2.39.42zM3.34 7l4.16-.35A7.2 7.2 0 0 0 5.94 8.5c-.44.74-.69 1.5-.83 2.29zm.02 10 1.76-3.77a7.131 7.131 0 0 0 2.38 4.14zM20.65 7l-1.77 3.79a7.02 7.02 0 0 0-2.38-4.15zm-.01 10-4.14.36c.59-.51 1.12-1.14 1.54-1.86.42-.73.69-1.5.83-2.29zM12 22l-2.41-3.44c.74.27 1.55.44 2.41.44.82 0 1.63-.17 2.37-.44z"/></svg>
</label>
<input class="md-option" data-md-color-media="(prefers-color-scheme: light)" data-md-color-scheme="default" data-md-color-primary="black" data-md-color-accent="indigo" aria-label="Switch to dark mode" type="radio" name="__palette" id="__palette_1">
<label class="md-header__button md-icon" title="Switch to dark mode" for="__palette_0" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m17.75 4.09-2.53 1.94.91 3.06-2.63-1.81-2.63 1.81.91-3.06-2.53-1.94L12.44 4l1.06-3 1.06 3zm3.5 6.91-1.64 1.25.59 1.98-1.7-1.17-1.7 1.17.59-1.98L15.75 11l2.06-.05L18.5 9l.69 1.95zm-2.28 4.95c.83-.08 1.72 1.1 1.19 1.85-.32.45-.66.87-1.08 1.27C15.17 23 8.84 23 4.94 19.07c-3.91-3.9-3.91-10.24 0-14.14.4-.4.82-.76 1.27-1.08.75-.53 1.93.36 1.85 1.19-.27 2.86.69 5.83 2.89 8.02a9.96 9.96 0 0 0 8.02 2.89m-1.64 2.02a12.08 12.08 0 0 1-7.8-3.47c-2.17-2.19-3.33-5-3.49-7.82-2.81 3.14-2.7 7.96.31 10.98 3.02 3.01 7.84 3.12 10.98.31"/></svg>
</label>
</form>
<script>var palette=__md_get("__palette");if(palette&&palette.color){if("(prefers-color-scheme)"===palette.color.media){var media=matchMedia("(prefers-color-scheme: light)"),input=document.querySelector(media.matches?"[data-md-color-media='(prefers-color-scheme: light)']":"[data-md-color-media='(prefers-color-scheme: dark)']");palette.color.media=input.getAttribute("data-md-color-media"),palette.color.scheme=input.getAttribute("data-md-color-scheme"),palette.color.primary=input.getAttribute("data-md-color-primary"),palette.color.accent=input.getAttribute("data-md-color-accent")}for(var[key,value]of Object.entries(palette.color))document.body.setAttribute("data-md-color-"+key,value)}</script>
<label class="md-header__button md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
</label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" aria-label="Search" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="search-query" required>
<label class="md-search__icon md-icon" for="__search">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M9.5 3A6.5 6.5 0 0 1 16 9.5c0 1.61-.59 3.09-1.56 4.23l.27.27h.79l5 5-1.5 1.5-5-5v-.79l-.27-.27A6.52 6.52 0 0 1 9.5 16 6.5 6.5 0 0 1 3 9.5 6.5 6.5 0 0 1 9.5 3m0 2C7 5 5 7 5 9.5S7 14 9.5 14 14 12 14 9.5 12 5 9.5 5"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</label>
<nav class="md-search__options" aria-label="Search">
<button type="reset" class="md-search__icon md-icon" title="Clear" aria-label="Clear" tabindex="-1">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M19 6.41 17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>
</button>
</nav>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" tabindex="0" data-md-scrollfix>
<div class="md-search-result" data-md-component="search-result">
<div class="md-search-result__meta">
Initializing search
</div>
<ol class="md-search-result__list" role="presentation"></ol>
</div>
</div>
</div>
</div>
</div>
<div class="md-header__source">
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
dtourolle/scene-actor-extraction
</div>
</a>
</div>
</nav>
</header>
<div class="md-container" data-md-component="container">
<nav class="md-tabs" aria-label="Tabs" data-md-component="tabs">
<div class="md-grid">
<ul class="md-tabs__list">
<li class="md-tabs__item">
<a href=".." class="md-tabs__link">
Home
</a>
</li>
<li class="md-tabs__item">
<a href="../best-model/" class="md-tabs__link">
Findings
</a>
</li>
<li class="md-tabs__item md-tabs__item--active">
<a href="./" class="md-tabs__link">
Model Bake-off & Re-tune (full log)
</a>
</li>
<li class="md-tabs__item">
<a href="../optimizer-experiments/" class="md-tabs__link">
Optimizer Experiments (prior round)
</a>
</li>
<li class="md-tabs__item">
<a href="../service-conversion/" class="md-tabs__link">
Service Conversion (proposal)
</a>
</li>
</ul>
</div>
</nav>
<main class="md-main" data-md-component="main">
<div class="md-main__inner md-grid">
<div class="md-sidebar md-sidebar--primary" data-md-component="sidebar" data-md-type="navigation" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary md-nav--lifted" aria-label="Navigation" data-md-level="0">
<label class="md-nav__title" for="__drawer">
<a href=".." title="scene-actor-extraction" class="md-nav__button md-logo" aria-label="scene-actor-extraction" data-md-component="logo">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 8a3 3 0 0 0 3-3 3 3 0 0 0-3-3 3 3 0 0 0-3 3 3 3 0 0 0 3 3m0 3.54C9.64 9.35 6.5 8 3 8v11c3.5 0 6.64 1.35 9 3.54 2.36-2.19 5.5-3.54 9-3.54V8c-3.5 0-6.64 1.35-9 3.54"/></svg>
</a>
scene-actor-extraction
</label>
<div class="md-nav__source">
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction" title="Go to repository" class="md-source" data-md-component="source">
<div class="md-source__icon md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2025 Fonticons, Inc.--><path d="M439.6 236.1 244 40.5c-5.4-5.5-12.8-8.5-20.4-8.5s-15 3-20.4 8.4L162.5 81l51.5 51.5c27.1-9.1 52.7 16.8 43.4 43.7l49.7 49.7c34.2-11.8 61.2 31 35.5 56.7-26.5 26.5-70.2-2.9-56-37.3L240.3 199v121.9c25.3 12.5 22.3 41.8 9.1 55-6.4 6.4-15.2 10.1-24.3 10.1s-17.8-3.6-24.3-10.1c-17.6-17.6-11.1-46.9 11.2-56v-123c-20.8-8.5-24.6-30.7-18.6-45L142.6 101 8.5 235.1C3 240.6 0 247.9 0 255.5s3 15 8.5 20.4l195.6 195.7c5.4 5.4 12.7 8.4 20.4 8.4s15-3 20.4-8.4l194.7-194.7c5.4-5.4 8.4-12.8 8.4-20.4s-3-15-8.4-20.4"/></svg>
</div>
<div class="md-source__repository">
dtourolle/scene-actor-extraction
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." class="md-nav__link">
<span class="md-ellipsis">
Home
</span>
</a>
</li>
<li class="md-nav__item md-nav__item--nested">
<input class="md-nav__toggle md-toggle " type="checkbox" id="__nav_2" >
<label class="md-nav__link" for="__nav_2" id="__nav_2_label" tabindex="0">
<span class="md-ellipsis">
Findings
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<nav class="md-nav" data-md-level="1" aria-labelledby="__nav_2_label" aria-expanded="false">
<label class="md-nav__title" for="__nav_2">
<span class="md-nav__icon md-icon"></span>
Findings
</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../best-model/" class="md-nav__link">
<span class="md-ellipsis">
Best Model
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../gallery-scope/" class="md-nav__link">
<span class="md-ellipsis">
Gallery Scope (Full vs. Limited)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../pose-expansion/" class="md-nav__link">
<span class="md-ellipsis">
Pose Expansion
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../lvface-deep-dive/" class="md-nav__link">
<span class="md-ellipsis">
LVFace Deep Dive
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-nav__toggle md-toggle" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
<span class="md-ellipsis">
Model Bake-off & Re-tune (full log)
</span>
<span class="md-nav__icon md-icon"></span>
</label>
<a href="./" class="md-nav__link md-nav__link--active">
<span class="md-ellipsis">
Model Bake-off & Re-tune (full log)
</span>
</a>
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#why-this-experiment-and-what-it-actually-delivered" class="md-nav__link">
<span class="md-ellipsis">
Why this experiment, and what it actually delivered
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tldr-what-changed-in-srcconfighpp" class="md-nav__link">
<span class="md-ellipsis">
TL;DR — what changed in src/config.hpp
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#why-re-run-at-all" class="md-nav__link">
<span class="md-ellipsis">
Why re-run at all
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-deadlock-that-was-blocking-all-of-this" class="md-nav__link">
<span class="md-ellipsis">
The deadlock that was blocking all of this
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#concurrency-tuning" class="md-nav__link">
<span class="md-ellipsis">
Concurrency tuning
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#training-films-and-validation-set" class="md-nav__link">
<span class="md-ellipsis">
Training films and validation set
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#search-space-and-de-settings" class="md-nav__link">
<span class="md-ellipsis">
Search space and DE settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#results-all-16-combos-4-models-full-restricted-expand-noexp" class="md-nav__link">
<span class="md-ellipsis">
Results — all 16 combos (4 models × {full, restricted} × {expand, noexp})
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#calibration-curves-discriminative-power-independent-of-the-threshold" class="md-nav__link">
<span class="md-ellipsis">
Calibration curves — discriminative power, independent of the threshold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#two-effects-in-isolation-gallery-scope-and-pose-expansion" class="md-nav__link">
<span class="md-ellipsis">
Two effects in isolation: gallery scope, and pose expansion
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-the-data-says" class="md-nav__link">
<span class="md-ellipsis">
What the data says
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#held-out-validation-the-number-that-actually-matters" class="md-nav__link">
<span class="md-ellipsis">
Held-out validation — the number that actually matters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#caveats-what-this-is-not" class="md-nav__link">
<span class="md-ellipsis">
Caveats / what this is not
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reproduce" class="md-nav__link">
<span class="md-ellipsis">
Reproduce
</span>
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../optimizer-experiments/" class="md-nav__link">
<span class="md-ellipsis">
Optimizer Experiments (prior round)
</span>
</a>
</li>
<li class="md-nav__item">
<a href="../service-conversion/" class="md-nav__link">
<span class="md-ellipsis">
Service Conversion (proposal)
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="sidebar" data-md-type="toc" >
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary" aria-label="Table of contents">
<label class="md-nav__title" for="__toc">
<span class="md-nav__icon md-icon"></span>
Table of contents
</label>
<ul class="md-nav__list" data-md-component="toc" data-md-scrollfix>
<li class="md-nav__item">
<a href="#why-this-experiment-and-what-it-actually-delivered" class="md-nav__link">
<span class="md-ellipsis">
Why this experiment, and what it actually delivered
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#tldr-what-changed-in-srcconfighpp" class="md-nav__link">
<span class="md-ellipsis">
TL;DR — what changed in src/config.hpp
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#why-re-run-at-all" class="md-nav__link">
<span class="md-ellipsis">
Why re-run at all
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#the-deadlock-that-was-blocking-all-of-this" class="md-nav__link">
<span class="md-ellipsis">
The deadlock that was blocking all of this
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#concurrency-tuning" class="md-nav__link">
<span class="md-ellipsis">
Concurrency tuning
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#training-films-and-validation-set" class="md-nav__link">
<span class="md-ellipsis">
Training films and validation set
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#search-space-and-de-settings" class="md-nav__link">
<span class="md-ellipsis">
Search space and DE settings
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#results-all-16-combos-4-models-full-restricted-expand-noexp" class="md-nav__link">
<span class="md-ellipsis">
Results — all 16 combos (4 models × {full, restricted} × {expand, noexp})
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#calibration-curves-discriminative-power-independent-of-the-threshold" class="md-nav__link">
<span class="md-ellipsis">
Calibration curves — discriminative power, independent of the threshold
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#two-effects-in-isolation-gallery-scope-and-pose-expansion" class="md-nav__link">
<span class="md-ellipsis">
Two effects in isolation: gallery scope, and pose expansion
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#what-the-data-says" class="md-nav__link">
<span class="md-ellipsis">
What the data says
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#held-out-validation-the-number-that-actually-matters" class="md-nav__link">
<span class="md-ellipsis">
Held-out validation — the number that actually matters
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#caveats-what-this-is-not" class="md-nav__link">
<span class="md-ellipsis">
Caveats / what this is not
</span>
</a>
</li>
<li class="md-nav__item">
<a href="#reproduce" class="md-nav__link">
<span class="md-ellipsis">
Reproduce
</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content" data-md-component="content">
<article class="md-content__inner md-typeset">
<h1 id="model-bake-off-threshold-re-tune-experiment-log-2026-07-1819">Model bake-off + threshold re-tune — experiment log (2026-07-18/19)<a class="headerlink" href="#model-bake-off-threshold-re-tune-experiment-log-2026-07-1819" title="Permanent link">&para;</a></h1>
<p>Follow-on to <a href="../optimizer-experiments/">the prior optimizer round</a>, which used
an older, since-superseded scene-union metric. This round uses the <strong>per-second</strong> metric
(<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/optimizer/second_score.py"><code>scripts/optimizer/second_score.py</code></a>)
and answers three questions in one 16-run matrix: which embedding model is best, does cast-restriction cut misIDs, and does
per-film gallery expansion help.</p>
<p>(This was the fourth optimizer campaign against the X-Ray corpus, so its on-disk
artifacts carry an internal <code>rep4_</code> prefix — <code>experiments/results/rep4_best_*.json</code>,
<code>experiments/trajectories/rep4_*.jsonl</code>, and the manifests referenced below. The
earlier campaigns used the superseded scene-union metric and were discarded.)</p>
<h2 id="why-this-experiment-and-what-it-actually-delivered">Why this experiment, and what it actually delivered<a class="headerlink" href="#why-this-experiment-and-what-it-actually-delivered" title="Permanent link">&para;</a></h2>
<p>Four goals going in, and an honest read on each after held-out validation (see
below):</p>
<ol>
<li><strong>Find the best default parameters to ship.</strong> Partially delivered. The DE optimum
generalizes <em>unevenly</em> — strong on 3 of 5 held-out films, badly broken on 2 (one
with a 974-count misID blowup). The tuned values are shipped anyway (see
Caveats) because they still beat the old defaults on average, but this is not a
settled, film-agnostic optimum.</li>
<li><strong>Find the best default model.</strong> Delivered with more confidence. LVFace beat
r50/r18/mbf across all 4 training combos, and nothing in held-out validation
contradicts the model choice specifically — the held-out failures trace to
<code>extinction_sec</code>/threshold interactions and gallery coverage, not the embedder.</li>
<li><strong>Provide insight into how the application works.</strong> The strongest, most durable
output. Found and fixed a real teardown deadlock bug (100% reproducible, not the
assumed rare GPU flake), established a real concurrency ceiling (8 parallel
replays, not more), and found a real parameter interaction (a strict
<code>prob_threshold</code> "earns" a longer extinction window before it starts hurting).</li>
<li><strong>Demonstrate limitations.</strong> Delivered, and reinforced hard by held-out
validation — see the "Held-out validation" section below for concrete examples,
including a screenshot of the matcher naming 15 actors, none correctly, on a
completely blank title card.</li>
</ol>
<h2 id="tldr-what-changed-in-srcconfighpp">TL;DR — what changed in <code>src/config.hpp</code><a class="headerlink" href="#tldr-what-changed-in-srcconfighpp" title="Permanent link">&para;</a></h2>
<table>
<thead>
<tr>
<th>knob</th>
<th>old default</th>
<th>new default</th>
<th>why</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>arcface_model</code></td>
<td><code>arcface_w600k_r50.onnx</code></td>
<td><strong><code>LVFace-B_Glint360K.onnx</code></strong></td>
<td>Best F1 in the full-gallery bake-off (75.3% vs r50's 68.5%). LVFace was worth its size.</td>
</tr>
<tr>
<td><code>prob_threshold</code></td>
<td>0.76</td>
<td><strong>0.754</strong></td>
<td>Re-tuned for LVFace + per-second metric.</td>
</tr>
<tr>
<td><code>extinction_sec</code></td>
<td>1.5</td>
<td><strong>57.4</strong></td>
<td>Reverses the earlier "short is better" finding — see below.</td>
</tr>
<tr>
<td><code>anneal_sec</code></td>
<td>10.0</td>
<td><strong>35.5</strong></td>
<td>Same reversal; previously thought insensitive.</td>
</tr>
<tr>
<td><code>expand_gallery</code></td>
<td>false</td>
<td><strong>true</strong></td>
<td>Helped recall on the full (unrestricted) gallery for the winning model — opposite of the earlier assumption.</td>
</tr>
</tbody>
</table>
<p>These are the <strong><code>LVFace-B_Glint360K_full_exp</code></strong> winning values, applied to
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/src/config.hpp"><code>src/config.hpp</code></a> — the best result that
uses only features already live in the running app (full gallery, no cast
restriction; see below for why restricted mode isn't applied even though it scored
higher).</p>
<h2 id="why-re-run-at-all">Why re-run at all<a class="headerlink" href="#why-re-run-at-all" title="Permanent link">&para;</a></h2>
<p><a href="../optimizer-experiments/">The prior round</a>'s scene-union metric hid out-of-cast false positives
behind a gallery∩cast recall mask, and the earlier 9-film benchmark was scene-level
(union over a whole X-Ray scene), not a fair per-timepoint comparison. SESSION_STATE
flagged the old scene-metric bake-off numbers (R50≈LVFace≈MBF ~85%) as superseded.
This round uses <code>second_score.py</code>: uniform per-second sampling, GT = X-Ray scene's
cast at time <em>t</em>, pred = actors whose presence window covers <em>t</em>, FPI weighted 10×
when the named actor isn't in the film's cast at all (true misID) vs. an in-cast
timing slip. FN only counts gallery-known cast (fair recall — 67% of X-Ray cast have
no reference embedding; see
<a href="../optimizer-experiments/#the-gallery-coverage-gap">the prior round's gallery-coverage-gap analysis</a>).</p>
<h2 id="the-deadlock-that-was-blocking-all-of-this">The deadlock that was blocking all of this<a class="headerlink" href="#the-deadlock-that-was-blocking-all-of-this" title="Permanent link">&para;</a></h2>
<p>Every replay in this line of work goes through
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/optimizer/replay.py"><code>scripts/optimizer/replay.py</code></a>,
which runs the real C++ tracker/matcher/scene_tracker nodes inside a
Python-assembled KPN network. Before this session, every subprocess replay <strong>timed out at 45s, 100% of
the time</strong> — not the documented ~20-30% ROCm rocBLAS-GEMM driver flake, but a plain
logic bug: <code>replay.py</code>'s CLI called <code>replay(net, ..., stop=False)</code> to <em>skip</em>
<code>net.stop()</code> (trying to dodge the GEMM deadlock), planning to <code>os._exit(0)</code>
immediately after. But:</p>
<ul>
<li><code>PyNode::stop()</code> (<a href="https://gitea.tourolle.paris/dtourolle/KPN/raw/commit/4b6e498ba7e70a34cc0b57638f9e56a43b7f41ae/include/kpn/python/bindings.hpp"><code>include/kpn/python/bindings.hpp</code></a>
in the KPN++ submodule) is the <em>only</em>
code that sets <code>stop_flag_ = true</code> before joining the node's worker thread.</li>
<li>The source node's <code>run_loop()</code> has <code>while (!stop_flag_)</code> as its only exit
condition (it has no input channels, so it never sees a channel-closed signal
either).</li>
<li>Skipping <code>stop()</code> meant <code>stop_flag_</code> never became true. When <code>replay()</code> returned,
its local <code>net</code> went out of scope immediately, running <code>~PyNetwork</code><code>~PyNode</code>
<code>thread_.join()</code> <strong>synchronously inside <code>replay()</code>'s own call frame</strong> — before
<code>main()</code> ever got control back to run <code>os._exit(0)</code>.</li>
</ul>
<p>Root-caused via <code>gdb -p &lt;pid&gt; -batch -ex "thread apply all bt"</code> on a hung process:
the main thread was stuck in <code>~PyNode</code>'s <code>jthread::join()</code>; the worker thread was in
an ordinary <code>time.sleep()</code> inside the Python source callback, waiting for a stop
signal that was never sent. The two HSA <code>kfd_wait_on_events</code> threads visible in the
same trace are normal ROCm runtime housekeeping, not evidence of a wedged GPU kernel.</p>
<p><strong>Fix:</strong> <code>replay.py</code> now calls <code>replay(..., stop=True)</code> (the removed <code>stop=False</code> +
<code>os._exit</code> workaround was actively harmful). Verified 3/3 clean runs at ~8s each
(down from a guaranteed 45s timeout), and a full DE sweep producing real, sensible
F1/precision/recall instead of flat 0.0%.</p>
<h2 id="concurrency-tuning">Concurrency tuning<a class="headerlink" href="#concurrency-tuning" title="Permanent link">&para;</a></h2>
<p>With the deadlock fixed,
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/optimizer/optimize.py"><code>scripts/optimizer/optimize.py</code></a>
was extended with DE-level parallelism —
<code>differential_evolution(..., workers=ThreadPoolExecutor.map)</code> — so multiple
population candidates evaluate concurrently, each spawning its own per-film replay
subprocesses (<code>REPLAY_WORKERS</code>). Total concurrent GPU replay processes ≈
<code>DE_WORKERS × REPLAY_WORKERS</code>.</p>
<table>
<thead>
<tr>
<th>concurrent replays</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr>
<td>3 (<code>REPLAY_WORKERS=3</code>, no DE parallelism)</td>
<td>baseline, GPU underutilised</td>
</tr>
<tr>
<td>6 (<code>DE_WORKERS=2 × REPLAY_WORKERS=3</code>)</td>
<td>clean, real scores, ~1 isolated timeout per run</td>
</tr>
<tr>
<td>8 (<code>DE_WORKERS=2 × REPLAY_WORKERS=4</code>, 4-film manifest)</td>
<td>clean, real scores</td>
</tr>
<tr>
<td>9 (<code>DE_WORKERS=3 × REPLAY_WORKERS=3</code>)</td>
<td><strong>broken</strong> — every replay blew past the 45s timeout, all scores silently degraded to 0.0%</td>
</tr>
</tbody>
</table>
<p>9 concurrent replays looks like valid output (well-formed JSON, a real number) while
actually being garbage — a dangerous failure mode, not a crash. <strong>8 concurrent is the
practical ceiling</strong> on this GPU (gfx1100) for this workload. The matrix ran at
<code>REPLAY_WORKERS=4 DE_WORKERS=2</code>.</p>
<h2 id="training-films-and-validation-set">Training films and validation set<a class="headerlink" href="#training-films-and-validation-set" title="Permanent link">&para;</a></h2>
<p>9 films total have dumped embeddings across all 4 models. 4 were used for
optimization, leaving 5 held out for validation:</p>
<ul>
<li><strong>Lord of War</strong> (64-cast, "clean")</li>
<li><strong>Scarface</strong> (67-cast, "ensemble/lookalike")</li>
<li><strong>Sound of Metal</strong> (14-cast, "high gallery-coverage")</li>
<li><strong>Café Society</strong> (62-cast, added this round — similar ensemble size to Scarface but
different genre/lighting; picked to add diversity, not genre-overlap, over
Downton Abbey or The Many Saints of Newark)</li>
</ul>
<p>Held out: Benny &amp; Joon, Downton Abbey: A New Era, Lovelace, The Many Saints of
Newark, Valerian and the City of a Thousand Planets. The training-set numbers below are
training-set fit — see "Held-out validation" further down for the real
generalization test.</p>
<h2 id="search-space-and-de-settings">Search space and DE settings<a class="headerlink" href="#search-space-and-de-settings" title="Permanent link">&para;</a></h2>
<p><code>popsize=10, maxiter=15</code> (3 params → ≤480 evals/combo ceiling; DE's <code>tol</code> convergence
usually stops earlier). <code>anneal_sec</code>/<code>extinction_sec</code> bounds were <strong>widened from
130/115 to 160/160 mid-run</strong> (see below) — the 4 <code>arcface_w600k_r50</code> combos
finished before the widening and still use the old, narrower bounds, so they are
<strong>not directly comparable</strong> to the other 12 on those two params. Re-running r50 with
the wider bounds was deferred (diminishing-returns judgment call, not yet done).</p>
<h2 id="results-all-16-combos-4-models-full-restricted-expand-noexp">Results — all 16 combos (4 models × {full, restricted} × {expand, noexp})<a class="headerlink" href="#results-all-16-combos-4-models-full-restricted-expand-noexp" title="Permanent link">&para;</a></h2>
<p>Ranked by F1. <code>misid</code> = FPI_misid, count of true wrong-actor identifications (an
actor named who isn't in the film's cast at all) — distinct from <code>FPI</code>, which
includes in-cast timing slips.</p>
<table>
<thead>
<tr>
<th>combo</th>
<th>F1</th>
<th>P</th>
<th>R</th>
<th>TPI</th>
<th>FPI</th>
<th>misid</th>
<th>FN</th>
</tr>
</thead>
<tbody>
<tr>
<td>LVFace-B_Glint360K_restricted_exp</td>
<td><strong>78.3%</strong></td>
<td>91.0%</td>
<td>68.9%</td>
<td>42830</td>
<td>3782</td>
<td>60</td>
<td>19492</td>
</tr>
<tr>
<td>LVFace-B_Glint360K_restricted_noexp</td>
<td>76.7%</td>
<td>91.5%</td>
<td>66.2%</td>
<td>41149</td>
<td>3400</td>
<td>59</td>
<td>21173</td>
</tr>
<tr>
<td>arcface_w600k_mbf_restricted_exp</td>
<td>76.5%</td>
<td>90.7%</td>
<td>66.3%</td>
<td>41270</td>
<td>4431</td>
<td><strong>0</strong></td>
<td>21052</td>
</tr>
<tr>
<td>arcface_r18_restricted_exp</td>
<td>75.5%</td>
<td>87.6%</td>
<td>66.5%</td>
<td>41399</td>
<td>5666</td>
<td>60</td>
<td>20923</td>
</tr>
<tr>
<td><strong>LVFace-B_Glint360K_full_exp</strong></td>
<td><strong>75.3%</strong></td>
<td>89.7%</td>
<td>65.4%</td>
<td>47757</td>
<td>3407</td>
<td>232</td>
<td>26966</td>
</tr>
<tr>
<td>arcface_w600k_mbf_restricted_noexp</td>
<td>75.0%</td>
<td>91.1%</td>
<td>63.9%</td>
<td>39752</td>
<td>3465</td>
<td>60</td>
<td>22570</td>
</tr>
<tr>
<td>arcface_w600k_mbf_full_noexp</td>
<td>74.2%</td>
<td>87.4%</td>
<td>64.4%</td>
<td>12645</td>
<td>1312</td>
<td>57</td>
<td>6985</td>
</tr>
<tr>
<td>arcface_r18_restricted_noexp</td>
<td>73.5%</td>
<td>91.3%</td>
<td>61.7%</td>
<td>38299</td>
<td>3220</td>
<td>60</td>
<td>24023</td>
</tr>
<tr>
<td>LVFace-B_Glint360K_full_noexp</td>
<td>72.4%</td>
<td>94.2%</td>
<td>58.9%</td>
<td>27077</td>
<td>1725</td>
<td><strong>0</strong></td>
<td>19506</td>
</tr>
<tr>
<td>arcface_w600k_mbf_full_exp</td>
<td>72.0%</td>
<td>87.7%</td>
<td>61.4%</td>
<td>39875</td>
<td>3729</td>
<td>240</td>
<td>26338</td>
</tr>
<tr>
<td>arcface_w600k_r50_full_noexp †</td>
<td>71.6%</td>
<td>96.7%</td>
<td>56.9%</td>
<td>22471</td>
<td>361</td>
<td>45</td>
<td>17012</td>
</tr>
<tr>
<td>arcface_w600k_r50_restricted_exp †</td>
<td>71.1%</td>
<td>96.5%</td>
<td>56.4%</td>
<td>34954</td>
<td>1119</td>
<td>15</td>
<td>27368</td>
</tr>
<tr>
<td>arcface_w600k_r50_restricted_noexp †</td>
<td>69.2%</td>
<td>97.9%</td>
<td>53.6%</td>
<td>21146</td>
<td>327</td>
<td>15</td>
<td>18337</td>
</tr>
<tr>
<td>arcface_r18_full_exp</td>
<td>69.1%</td>
<td>87.6%</td>
<td>57.7%</td>
<td>37342</td>
<td>3119</td>
<td>242</td>
<td>28871</td>
</tr>
<tr>
<td>arcface_w600k_r50_full_exp †</td>
<td>68.5%</td>
<td>94.0%</td>
<td>54.1%</td>
<td>34982</td>
<td>903</td>
<td>150</td>
<td>31231</td>
</tr>
<tr>
<td>arcface_r18_full_noexp</td>
<td>66.6%</td>
<td>91.3%</td>
<td>53.1%</td>
<td>34314</td>
<td>2362</td>
<td>107</td>
<td>31899</td>
</tr>
</tbody>
</table>
<p>† old, narrower anneal/extinction bounds (see above) — not directly comparable to
the other 12 on those two params.</p>
<p>The same 16 results as a picture — the two headline effects are visible without
reading a single row: filled (restricted) dots stack the top of the ranking for
every model color, and yellow (LVFace) leads within both scopes:</p>
<p><img alt="All 16 bake-off combos ranked by training-set F1" src="../assets/images/rep4_matrix_f1.png" /></p>
<h2 id="calibration-curves-discriminative-power-independent-of-the-threshold">Calibration curves — discriminative power, independent of the threshold<a class="headerlink" href="#calibration-curves-discriminative-power-independent-of-the-threshold" title="Permanent link">&para;</a></h2>
<p>Each model's gallery carries a fitted Platt sigmoid <code>P(match | sim) = σ(a·sim + b)</code>
(embedded directly in the gallery HDF5, see
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/src/gallery/gallery_calibration.hpp"><code>src/gallery/gallery_calibration.hpp</code></a>).
Plotting all four side by side shows discriminative power directly, independent of
whatever <code>prob_threshold</code> a particular run happened to use:</p>
<p><img alt="Calibrated P(match|similarity) for all four models" src="../assets/images/calibration_curves.png" /></p>
<p>LVFace-B has both the steepest curve (<code>a=17.7</code>, vs. 15.316.2 for the ArcFace
variants) and the lowest P=0.5 decision boundary (similarity 0.23 vs. 0.270.31) —
it separates same-actor from different-actor pairs more confidently at a lower
similarity, consistent with it winning the full-gallery F1 comparison below.
Generated by
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/docs/calibration_chart.py"><code>scripts/docs/calibration_chart.py</code></a>
(requires each gallery to have been calibrated at least once — run any replay
against it first).</p>
<h2 id="two-effects-in-isolation-gallery-scope-and-pose-expansion">Two effects in isolation: gallery scope, and pose expansion<a class="headerlink" href="#two-effects-in-isolation-gallery-scope-and-pose-expansion" title="Permanent link">&para;</a></h2>
<p>The matrix crosses two independent variables — averaging across all 4 models
isolates each one from model choice:</p>
<p><strong>Gallery scope (whole 2418-actor gallery vs. restricted to the film's credited
cast)</strong> — averaged over both expansion settings and all 4 models:</p>
<table>
<thead>
<tr>
<th>scope</th>
<th>F1</th>
<th>P</th>
<th>R</th>
<th>total misID (16 evals→8 each)</th>
</tr>
</thead>
<tbody>
<tr>
<td>full</td>
<td>71.2%</td>
<td>91.1%</td>
<td>59.0%</td>
<td>1073</td>
</tr>
<tr>
<td><strong>restricted</strong></td>
<td><strong>74.5%</strong></td>
<td>92.2%</td>
<td><strong>62.9%</strong></td>
<td><strong>329</strong></td>
</tr>
</tbody>
</table>
<p>Restriction wins outright on every axis — not a precision/recall trade, a clean
win: <strong>+3.3pp F1, +3.9pp recall, and less than a third the total misIDs.</strong> Fewer
candidates in the matcher's search space means fewer opportunities for a
look-alike false match, and (per the recall gain) doesn't cost real detections.
This is the single cleanest signal in the whole matrix — stronger than the model
choice itself — which is exactly why cast-restriction becoming a real runtime
feature (not just an optimizer trick) is the top item in Caveats below.</p>
<p><strong>Pose expansion (promoting a confidently-identified track's novel-pose views into
a per-film gallery annex — <a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/src/gallery/track_gallery.hpp"><code>src/gallery/track_gallery.hpp</code></a>)</strong>
is smaller and interacts with
scope rather than acting independently:</p>
<table>
<thead>
<tr>
<th>scope</th>
<th>expansion</th>
<th>F1</th>
<th>R</th>
<th>misID</th>
</tr>
</thead>
<tbody>
<tr>
<td>full</td>
<td>off</td>
<td>71.2%</td>
<td>58.3%</td>
<td>209</td>
</tr>
<tr>
<td>full</td>
<td><strong>on</strong></td>
<td>71.2%</td>
<td>59.7%</td>
<td><strong>864</strong></td>
</tr>
<tr>
<td>restricted</td>
<td>off</td>
<td>73.6%</td>
<td>61.3%</td>
<td>194</td>
</tr>
<tr>
<td>restricted</td>
<td><strong>on</strong></td>
<td><strong>75.4%</strong></td>
<td><strong>64.5%</strong></td>
<td>135</td>
</tr>
</tbody>
</table>
<p>In <strong>restricted</strong> mode, expansion is a clean win (+1.8pp F1, +3.2pp recall, misID
actually <em>drops</em>) — the annex only ever competes against the film's own ~15-actor
cast, so a "confidently identified, new pose" view is unlikely to be mistaken for
someone else. In <strong>full</strong> mode, expansion buys essentially nothing on F1 (71.2% →
71.2%, recall +1.4pp) while <strong>quadrupling misIDs</strong> (209 → 864): a novel-pose view
promoted into the annex now competes against the whole 2418-actor gallery, so a
"confident" identity is confident against the wrong universe of candidates — the
expansion mechanism is "learning" a pose correctly, but the enlarged evidence pool
makes it easier for that learned pose to look like a plausible match for a
different actor. <strong>Practical takeaway: gallery expansion should be paired with
cast restriction, not used on the full gallery</strong> — the version currently shipped
as default (<code>full_exp</code>, see TL;DR) sits in the worse of these four cells for this
specific knob, even though it's the best available combo without cast-restriction
support in the app yet (see Caveats).</p>
<h2 id="what-the-data-says">What the data says<a class="headerlink" href="#what-the-data-says" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>LVFace was worth its size.</strong> It wins full-gallery mode outright (75.3% vs r50's
68.5%, r18's 69.1%, mbf's 72.0%) with the highest recall of any full-mode combo —
the earlier scene-union-metric conclusion ("not worth it") doesn't survive the
better metric.</li>
<li><strong>Cast-restriction is a consistent, broad win.</strong> Every model's best combo is
<code>restricted</code>. It isn't just precision-safe: <code>arcface_w600k_mbf_restricted_exp</code> and
<code>LVFace-B_Glint360K_full_noexp</code> both hit <strong>misid=0</strong> — zero true wrong-actor
identifications. But restriction is an <strong>offline optimizer technique, not a live
app feature</strong> — it pre-filters each film's gallery to its Jellyfin-credited cast
before the matcher ever runs; there's no runtime "restrict to this film's cast"
switch in the app today. Implementing it for real is future work, tracked
separately from this defaults update.</li>
<li><strong>Gallery expansion (<code>expand_gallery</code>) is mode-dependent.</strong> It helps on
<code>restricted</code> galleries (smaller, so novel-pose promotion adds real signal) and on
LVFace's full gallery, but <strong>hurts</strong> r50 and mbf in full mode (compare
<code>arcface_w600k_r50_full_exp</code> 68.5% vs <code>full_noexp</code> 71.6%). Don't assume it's a free
win — model- and mode-dependent.</li>
<li><strong>arcface_r18 (smallest/cheapest) is last across all 4 modes</strong> — model capacity
matters here, this isn't just parameter-count padding.</li>
<li><strong><code>anneal_sec</code>/<code>extinction_sec</code> kept pinning at the search ceiling.</strong> With the
original 130/115 bounds, 3 of 4 r50 combos landed at ~93-98% of the upper bound.
Widened to 160/160 mid-run (after the r50 combos had already finished) — every
subsequent combo's best config landed at ~90%+ of the <em>new</em> ceiling too (e.g. the
LVFace winner: <code>ann=59.2, ext=59.2</code>, both ~99% of 60). The likely mechanism: a
strict <code>prob_threshold</code> "earns" a long extinction/anneal window — once false
matches are rare, a long window just bridges real presence gaps (occlusion, turned
face) instead of smearing false positives into later scenes, which is what made
short windows look better under the old, laxer thresholds. <strong>Open question, not
resolved</strong>: does this keep climbing past 60s, or does it actually plateau there?
Decided not to chase further this round (diminishing-returns judgment call) — flag
for a future sweep if it matters.</li>
</ul>
<p>The ceiling-pinning is visible in the raw search itself. Every one of the 512
DE evaluations for the winning combo, plotted over the
<code>prob_threshold</code> × <code>extinction_sec</code> plane:</p>
<p><img alt="DE search landscape: 512 evaluations over prob_threshold × extinction_sec" src="../assets/images/de_search_landscape.png" /></p>
<p>The dark band hugging the top edge <em>is</em> the finding: nearly everything scoring
well sits at <code>extinction_sec</code> ≥ 50, across a wide range of thresholds, and the
population converged into a dense cloud around the optimum (threshold ~0.700.80,
extinction pinned at the 60s bound). Short extinction windows (bottom half) are
uniformly pale — under a strict threshold there is simply no good configuration
down there. Generated by
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/docs/experiment_charts.py"><code>scripts/docs/experiment_charts.py</code></a>
from the DE trajectories (<code>experiments/trajectories/*.jsonl</code>, part of the
<code>experiment-data</code> artifact package).</p>
<h2 id="held-out-validation-the-number-that-actually-matters">Held-out validation — the number that actually matters<a class="headerlink" href="#held-out-validation-the-number-that-actually-matters" title="Permanent link">&para;</a></h2>
<p>The 16-combo matrix above is training-set fit. This is the real test: the shipped
config (<code>LVFace-B_Glint360K_full_exp</code><code>prob_threshold=0.754, anneal_sec=35.5,
extinction_sec=57.4, expand_gallery=true</code>) replayed against the <strong>5 films never seen
by the optimizer</strong> (Benny &amp; Joon, Downton Abbey: A New Era, Lovelace, The Many
Saints of Newark, Valerian and the City of a Thousand Planets), scored the same way.</p>
<table>
<thead>
<tr>
<th>film</th>
<th>F1</th>
<th>P</th>
<th>R</th>
<th>agree</th>
<th>TPI</th>
<th>FPI</th>
<th>misid</th>
<th>FN</th>
</tr>
</thead>
<tbody>
<tr>
<td>Benny &amp; Joon</td>
<td>83.0%</td>
<td>89.1%</td>
<td>77.7%</td>
<td>72.4%</td>
<td>15125</td>
<td>1846</td>
<td>0</td>
<td>4337</td>
</tr>
<tr>
<td>Lovelace</td>
<td>77.5%</td>
<td>90.3%</td>
<td>67.9%</td>
<td>72.1%</td>
<td>14990</td>
<td>1085</td>
<td>58</td>
<td>7085</td>
</tr>
<tr>
<td>Valerian and the City of a Thousand Planets</td>
<td>74.1%</td>
<td>97.1%</td>
<td>60.0%</td>
<td>58.8%</td>
<td>18663</td>
<td>548</td>
<td>0</td>
<td>12467</td>
</tr>
<tr>
<td>Downton Abbey: A New Era</td>
<td>56.2%</td>
<td>97.8%</td>
<td>39.4%</td>
<td>40.6%</td>
<td>52027</td>
<td>1173</td>
<td>0</td>
<td>80084</td>
</tr>
<tr>
<td><strong>The Many Saints of Newark</strong></td>
<td><strong>46.3%</strong></td>
<td><strong>54.7%</strong></td>
<td>40.1%</td>
<td>37.0%</td>
<td>15922</td>
<td>4394</td>
<td><strong>974</strong></td>
<td>23791</td>
</tr>
<tr>
<td><strong>macro average (5 films)</strong></td>
<td><strong>67.4%</strong></td>
<td>85.8%</td>
<td>57.0%</td>
<td>56.2%</td>
<td>116727</td>
<td>9046</td>
<td>1032</td>
<td>127764</td>
</tr>
</tbody>
</table>
<p><img alt="Held-out per-film F1 vs. the training-set fit" src="../assets/images/holdout_f1_by_film.png" /></p>
<p><strong>67.4% held out vs. 75.3% on training</strong> — an ~8pp drop, and a much more informative
number than the training-set F1 alone: a <strong>37pp spread between best and worst film</strong>
(83.0% vs 46.3%). The config does not generalize uniformly.</p>
<p>Two films are outright failure cases, and rendering bounding boxes + names on the
extracted frames (<code>replay.py --raw-out</code> +
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/scripts/optimizer/dump_error_frames.py"><code>dump_error_frames.py</code></a><code>--raw</code>, see
Reproduce) turned what looked like a same-scene misidentification into something
more precise and more damning:</p>
<ul>
<li><strong>The Many Saints of Newark</strong> (mob-family drama, picked as a training-adjacent
genre test) has <strong>974 true misIDs</strong> — far more than any training combo saw at any
setting. The annotated frame below shows the same mechanism as Downton Abbey,
at smaller scale: <strong>"Jon Bernthal 100%", "Joey Diaz 100%", and "Billy Magnussen
100%" are all frozen boxes over empty background — a blurred wall, hanging plates —
with no face in them at all.</strong> Only one real face in frame has a box, and it
carries a <em>second</em>, colliding label ("Leslie Odom Jr." and "Michael Gandolfini"
both at high confidence on the same box) — likely two tracks whose frozen bboxes
happen to overlap.</li>
</ul>
<p><img alt="Frozen ghost boxes over background, The Many Saints of Newark" src="../assets/images/many_saints_ghost_fpi.jpg" />
<em>Frame <code>many_saints/fpi/fpi_t03543.jpg</code> from the <code>montage-frames</code> artifact
package (<code>scripts/artifacts/pull_artifacts.sh montage-frames
Many_Saints_of_Newark</code>).</em></p>
<ul>
<li><strong>Downton Abbey: A New Era</strong> (large ensemble, 36-cast) has high precision (97.8%)
but recall collapses to 39.4% (FN=80084, by far the largest of the 5). Its
starkest failure happens where there is nothing to see at all: the film's hard
cut into its closing credits, where <strong>the matcher kept reporting 15 actors —
all wrong — for nearly a minute of faceless screen.</strong></li>
</ul>
<p>Both are the same mechanism, and it can be <em>measured</em>, not just screenshotted.
Plotting the dump's own per-second <code>face_count</code> (detector output, independent
of the tracker) against the number of actors the tracker reports, through
Downton Abbey's cut to credits:</p>
<p><img alt="Detector face_count vs. tracker-reported actors through the cut to credits" src="../assets/images/downton_ghost_timeline.png" /></p>
<p>From the cut onward the detector sees <strong>zero faces</strong> — yet the tracker holds a
perfectly flat plateau of 15 reported identities for 56 seconds, each with the
<em>exact same bbox, unchanged to the pixel</em> (verified for Hugh Bonneville:
<code>(1743.2, 0.0, 171.3, 317.8)</code> at every sampled second from 7222 through 7279+).
The staircase on the right edge is the extinction window finally expiring,
actor by actor. That plateau is <code>SceneTrackerFunc</code>'s
<code>active_[actor_idx].last_bbox</code>
(<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/src/nodes/scene_tracker_node.hpp"><code>src/nodes/scene_tracker_node.hpp</code></a>)
being re-emitted
unchanged — <strong>the extinction state machine working exactly as coded</strong>, not a
bug in the logic. The film cuts from a packed group shot straight into ~40+
seconds of blank titles/credits with zero faces, and <code>extinction_sec=57.4</code> is
comfortably long enough to bridge that entire gap without expiring, so the
tracker faithfully keeps reporting "last known position" for a cast that is no
longer on screen at all.</p>
<p>This reframes the "long extinction window wins" DE-search pattern (see above): it
isn't unambiguously good. It buys recall by bridging real gaps (occlusion, turned
face) in some films, but on others — specifically, hard cuts into long faceless
footage — it manufactures a frozen-bbox ghost the tracker has no way to verify,
precisely the failure mode the <em>original</em> short-extinction-window default (<code>1.5s</code>)
was chosen to avoid. The training-set films apparently didn't have a long enough
faceless stretch after a confirmed identity to expose this; the held-out set did.</p>
<p>Frames for all three films (<code>benny_joon</code>, <code>many_saints</code>, <code>downton_abbey</code> — one strong
performer, two failure cases) are under <code>experiments/results/holdout/frames/</code>
(not committed — pull per film with <code>scripts/artifacts/pull_artifacts.sh
montage-frames &lt;film-slug&gt;</code>), each
with a <code>manifest.json</code> listing the bucket (<code>best</code>/<code>fpi</code>/<code>fn</code>), timestamp, and
predicted vs. ground-truth actors for every dumped frame. Frames are annotated with
bounding boxes + name/confidence (green = identified, orange = unknown), matching
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/src/nodes/debug_renderer_node.hpp"><code>src/nodes/debug_renderer_node.hpp</code></a>'s
colour convention. Generated by
<code>scripts/optimizer/dump_error_frames.py --raw &lt;replay.py --raw-out output&gt;</code> (see
Reproduce).</p>
<p><code>dump_error_frames.py --interval-sec 600</code> also supports a per-N-second sweep
instead of the fixed best/fpi/fn buckets: one best (highest Jaccard) and one worst
(lowest Jaccard) frame per 10-minute window across the whole film, e.g.
<code>experiments/results/holdout/frames/many_saints_intervals/</code> (13 windows × 2 = 26
frames for the ~2h Many Saints runtime) — a way to sample "how are we doing" evenly
across a film's runtime rather than only at its most extreme seconds.</p>
<h2 id="caveats-what-this-is-not">Caveats / what this is not<a class="headerlink" href="#caveats-what-this-is-not" title="Permanent link">&para;</a></h2>
<ul>
<li><strong>r50's 4 combos used the old, narrower search bounds</strong> and aren't fully
comparable to the other 12 on <code>anneal_sec</code>/<code>extinction_sec</code>.</li>
<li><strong>The applied defaults use <code>full_exp</code>, not the higher-scoring <code>restricted_exp</code></strong>,
because cast-restriction isn't a real runtime feature yet (see above). The
78.3% F1 number is not what the shipped defaults will produce — 75.3% is.</li>
<li><strong><code>full_exp</code> is the best full-gallery combo, but not the safest.</strong> Per the
isolated-effects analysis above, <code>expand_gallery=true</code> only cleanly pays off
when paired with cast-restriction; on the full gallery it's flat on F1 while
~4x-ing misIDs (209→864, averaged across models). <code>full_noexp</code> scores lower
(72.4% vs 75.3% for LVFace) but with <strong>zero</strong> true misIDs and higher precision
(94.2% vs 89.7%). Kept <code>full_exp</code> as shipped since it's the highest-F1 option
available without cast-restriction, but this is a real F1-vs-safety trade, not
a strictly-better choice — worth revisiting if misID rate matters more than
the last few points of F1 for a given deployment.</li>
<li><strong>Switching the default model is an operational change, not just a config tweak</strong>:
any existing gallery built from r50 embeddings is incompatible with LVFace
embeddings and needs rebuilding.</li>
</ul>
<h2 id="reproduce">Reproduce<a class="headerlink" href="#reproduce" title="Permanent link">&para;</a></h2>
<div class="language-bash highlight"><pre><span></span><code><span id="__span-0-1"><a id="__codelineno-0-1" name="__codelineno-0-1" href="#__codelineno-0-1"></a><span class="c1"># 4-film matrix, all 4 models × 2 modes × 2 expansion settings</span>
</span><span id="__span-0-2"><a id="__codelineno-0-2" name="__codelineno-0-2" href="#__codelineno-0-2"></a>bash<span class="w"> </span>experiments/run_rep4_subprocess.sh
</span><span id="__span-0-3"><a id="__codelineno-0-3" name="__codelineno-0-3" href="#__codelineno-0-3"></a>
</span><span id="__span-0-4"><a id="__codelineno-0-4" name="__codelineno-0-4" href="#__codelineno-0-4"></a><span class="c1"># single combo</span>
</span><span id="__span-0-5"><a id="__codelineno-0-5" name="__codelineno-0-5" href="#__codelineno-0-5"></a><span class="nv">SAE_EXPAND</span><span class="o">=</span><span class="m">1</span><span class="w"> </span><span class="nv">REPLAY_WORKERS</span><span class="o">=</span><span class="m">4</span><span class="w"> </span><span class="nv">DE_WORKERS</span><span class="o">=</span><span class="m">2</span><span class="w"> </span>python3<span class="w"> </span>scripts/optimizer/optimize.py<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-6"><a id="__codelineno-0-6" name="__codelineno-0-6" href="#__codelineno-0-6"></a><span class="w"> </span>--manifest<span class="w"> </span>experiments/manifests/rep4_LVFace-B_Glint360K_full.json<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-7"><a id="__codelineno-0-7" name="__codelineno-0-7" href="#__codelineno-0-7"></a><span class="w"> </span>--gallery<span class="w"> </span>experiments/galleries/gallery_LVFace-B_Glint360K.h5<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-8"><a id="__codelineno-0-8" name="__codelineno-0-8" href="#__codelineno-0-8"></a><span class="w"> </span>--params<span class="w"> </span>prob_threshold:0.5:0.999<span class="w"> </span>anneal_sec:1:60<span class="w"> </span>extinction_sec:1:60<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-9"><a id="__codelineno-0-9" name="__codelineno-0-9" href="#__codelineno-0-9"></a><span class="w"> </span>--popsize<span class="w"> </span><span class="m">10</span><span class="w"> </span>--maxiter<span class="w"> </span><span class="m">15</span><span class="w"> </span>--trajectory<span class="w"> </span>traj.jsonl<span class="w"> </span>--out<span class="w"> </span>best.json
</span><span id="__span-0-10"><a id="__codelineno-0-10" name="__codelineno-0-10" href="#__codelineno-0-10"></a>
</span><span id="__span-0-11"><a id="__codelineno-0-11" name="__codelineno-0-11" href="#__codelineno-0-11"></a><span class="c1"># replay the shipped config against a held-out film — --raw-out is needed to draw</span>
</span><span id="__span-0-12"><a id="__codelineno-0-12" name="__codelineno-0-12" href="#__codelineno-0-12"></a><span class="c1"># bboxes later (the merged pred.json has no per-frame bbox, only actor windows)</span>
</span><span id="__span-0-13"><a id="__codelineno-0-13" name="__codelineno-0-13" href="#__codelineno-0-13"></a>python3<span class="w"> </span>scripts/optimizer/replay.py<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-14"><a id="__codelineno-0-14" name="__codelineno-0-14" href="#__codelineno-0-14"></a><span class="w"> </span>--dump<span class="w"> </span>experiments/dumps/LVFace-B_Glint360K/dump_&lt;slug&gt;.h5<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-15"><a id="__codelineno-0-15" name="__codelineno-0-15" href="#__codelineno-0-15"></a><span class="w"> </span>--gallery<span class="w"> </span>experiments/galleries/gallery_LVFace-B_Glint360K.h5<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-16"><a id="__codelineno-0-16" name="__codelineno-0-16" href="#__codelineno-0-16"></a><span class="w"> </span>--out<span class="w"> </span>pred.json<span class="w"> </span>--raw-out<span class="w"> </span>raw.jsonl<span class="w"> </span>--prob-threshold<span class="w"> </span><span class="m">0</span>.754<span class="w"> </span>--anneal-sec<span class="w"> </span><span class="m">35</span>.54<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-17"><a id="__codelineno-0-17" name="__codelineno-0-17" href="#__codelineno-0-17"></a><span class="w"> </span>--extinction-sec<span class="w"> </span><span class="m">57</span>.43<span class="w"> </span>--expand-gallery
</span><span id="__span-0-18"><a id="__codelineno-0-18" name="__codelineno-0-18" href="#__codelineno-0-18"></a>
</span><span id="__span-0-19"><a id="__codelineno-0-19" name="__codelineno-0-19" href="#__codelineno-0-19"></a><span class="c1"># regenerate the report&#39;s charts (16-combo ranking, DE landscape, held-out</span>
</span><span id="__span-0-20"><a id="__codelineno-0-20" name="__codelineno-0-20" href="#__codelineno-0-20"></a><span class="c1"># per-film F1, Downton ghost timeline) from the artifacts under experiments/</span>
</span><span id="__span-0-21"><a id="__codelineno-0-21" name="__codelineno-0-21" href="#__codelineno-0-21"></a>python3<span class="w"> </span>scripts/docs/experiment_charts.py<span class="w"> </span>--out-dir<span class="w"> </span>docs/assets/images
</span><span id="__span-0-22"><a id="__codelineno-0-22" name="__codelineno-0-22" href="#__codelineno-0-22"></a>
</span><span id="__span-0-23"><a id="__codelineno-0-23" name="__codelineno-0-23" href="#__codelineno-0-23"></a><span class="c1"># dump example frames (best-agreement / FPI / FN) for visual inspection, annotated</span>
</span><span id="__span-0-24"><a id="__codelineno-0-24" name="__codelineno-0-24" href="#__codelineno-0-24"></a><span class="c1"># with bounding boxes + names (--raw is optional; omit for unannotated frames)</span>
</span><span id="__span-0-25"><a id="__codelineno-0-25" name="__codelineno-0-25" href="#__codelineno-0-25"></a>python3<span class="w"> </span>scripts/optimizer/dump_error_frames.py<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-26"><a id="__codelineno-0-26" name="__codelineno-0-26" href="#__codelineno-0-26"></a><span class="w"> </span>--pred<span class="w"> </span>pred.json<span class="w"> </span>--raw<span class="w"> </span>raw.jsonl<span class="w"> </span>--xray<span class="w"> </span>experiments/xray/.../&lt;xray_dir&gt;<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-27"><a id="__codelineno-0-27" name="__codelineno-0-27" href="#__codelineno-0-27"></a><span class="w"> </span>--movie<span class="w"> </span><span class="s2">&quot;&lt;path to source video&gt;&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-28"><a id="__codelineno-0-28" name="__codelineno-0-28" href="#__codelineno-0-28"></a><span class="w"> </span>--gallery<span class="w"> </span>experiments/galleries/gallery_LVFace-B_Glint360K.h5<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-29"><a id="__codelineno-0-29" name="__codelineno-0-29" href="#__codelineno-0-29"></a><span class="w"> </span>--out-dir<span class="w"> </span>experiments/results/holdout/frames/&lt;name&gt;<span class="w"> </span>--n-per-bucket<span class="w"> </span><span class="m">4</span>
</span><span id="__span-0-30"><a id="__codelineno-0-30" name="__codelineno-0-30" href="#__codelineno-0-30"></a>
</span><span id="__span-0-31"><a id="__codelineno-0-31" name="__codelineno-0-31" href="#__codelineno-0-31"></a><span class="c1"># or: one best + one worst frame per 10-minute window across the whole film</span>
</span><span id="__span-0-32"><a id="__codelineno-0-32" name="__codelineno-0-32" href="#__codelineno-0-32"></a>python3<span class="w"> </span>scripts/optimizer/dump_error_frames.py<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-33"><a id="__codelineno-0-33" name="__codelineno-0-33" href="#__codelineno-0-33"></a><span class="w"> </span>--pred<span class="w"> </span>pred.json<span class="w"> </span>--raw<span class="w"> </span>raw.jsonl<span class="w"> </span>--xray<span class="w"> </span>experiments/xray/.../&lt;xray_dir&gt;<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-34"><a id="__codelineno-0-34" name="__codelineno-0-34" href="#__codelineno-0-34"></a><span class="w"> </span>--movie<span class="w"> </span><span class="s2">&quot;&lt;path to source video&gt;&quot;</span><span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-35"><a id="__codelineno-0-35" name="__codelineno-0-35" href="#__codelineno-0-35"></a><span class="w"> </span>--gallery<span class="w"> </span>experiments/galleries/gallery_LVFace-B_Glint360K.h5<span class="w"> </span><span class="se">\</span>
</span><span id="__span-0-36"><a id="__codelineno-0-36" name="__codelineno-0-36" href="#__codelineno-0-36"></a><span class="w"> </span>--out-dir<span class="w"> </span>experiments/results/holdout/frames/&lt;name&gt;_intervals<span class="w"> </span>--interval-sec<span class="w"> </span><span class="m">600</span>
</span></code></pre></div>
<p>See also: <a href="../optimizer-experiments/">the prior optimizer round</a> (superseded
metric) and the session log
<a href="https://gitea.tourolle.paris/dtourolle/scene-actor-extraction/raw/commit/4b5557974bef8783bacc375c0869e8f589d1b0a3/experiments/SESSION_STATE.md"><code>experiments/SESSION_STATE.md</code></a>.</p>
</article>
</div>
<script>var target=document.getElementById(location.hash.slice(1));target&&target.name&&(target.checked=target.name.startsWith("__tabbed_"))</script>
</div>
<button type="button" class="md-top md-icon" data-md-component="top" hidden>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8z"/></svg>
Back to top
</button>
</main>
<footer class="md-footer">
<nav class="md-footer__inner md-grid" aria-label="Footer" >
<a href="../lvface-deep-dive/" class="md-footer__link md-footer__link--prev" aria-label="Previous: LVFace Deep Dive">
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M20 11v2H8l5.5 5.5-1.42 1.42L4.16 12l7.92-7.92L13.5 5.5 8 11z"/></svg>
</div>
<div class="md-footer__title">
<span class="md-footer__direction">
Previous
</span>
<div class="md-ellipsis">
LVFace Deep Dive
</div>
</div>
</a>
<a href="../optimizer-experiments/" class="md-footer__link md-footer__link--next" aria-label="Next: Optimizer Experiments (prior round)">
<div class="md-footer__title">
<span class="md-footer__direction">
Next
</span>
<div class="md-ellipsis">
Optimizer Experiments (prior round)
</div>
</div>
<div class="md-footer__button md-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 11v2h12l-5.5 5.5 1.42 1.42L19.84 12l-7.92-7.92L10.5 5.5 16 11z"/></svg>
</div>
</a>
</nav>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-copyright">
Made with
<a href="https://squidfunk.github.io/mkdocs-material/" target="_blank" rel="noopener">
Material for MkDocs
</a>
</div>
</div>
</div>
</footer>
</div>
<div class="md-dialog" data-md-component="dialog">
<div class="md-dialog__inner md-typeset"></div>
</div>
<script id="__config" type="application/json">{"annotate": null, "base": "..", "features": ["navigation.tabs", "navigation.sections", "navigation.top", "navigation.footer", "content.code.copy", "content.code.annotate"], "search": "../assets/javascripts/workers/search.2c215733.min.js", "tags": null, "translations": {"clipboard.copied": "Copied to clipboard", "clipboard.copy": "Copy to clipboard", "search.result.more.one": "1 more on this page", "search.result.more.other": "# more on this page", "search.result.none": "No matching documents", "search.result.one": "1 matching document", "search.result.other": "# matching documents", "search.result.placeholder": "Type to start searching", "search.result.term.missing": "Missing", "select.version": "Select version"}, "version": null}</script>
<script src="../assets/javascripts/bundle.d7400e89.min.js"></script>
</body>
</html>